@jhits/plugin-website 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,11 @@
2
2
  * Website Settings Types
3
3
  */
4
4
  export type SupportedLocale = 'en' | 'sv' | 'nl';
5
+ export interface DomainLocaleConfig {
6
+ domain: string;
7
+ locale: SupportedLocale;
8
+ allowUserOverride?: boolean;
9
+ }
5
10
  export interface LocalizedContactInfo {
6
11
  physicalAddress?: string;
7
12
  phoneNumber?: string;
@@ -26,6 +31,7 @@ export interface WebsiteSettings {
26
31
  socials?: SocialLink[];
27
32
  localizedContactInfo?: Partial<Record<SupportedLocale, LocalizedContactInfo>>;
28
33
  localizedSocials?: Partial<Record<SupportedLocale, LocalizedSocialLink[]>>;
34
+ domainLocaleConfig?: DomainLocaleConfig[];
29
35
  updatedAt?: Date;
30
36
  createdAt?: Date;
31
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/types/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,oBAAoB;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC;IAC3E,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/types/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC;IAC3E,kBAAkB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC1C,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,GAAG,CAAA;KAAE,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsView.d.ts","sourceRoot":"","sources":["../../src/views/SettingsView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAyBH,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA2kBjE"}
1
+ {"version":3,"file":"SettingsView.d.ts","sourceRoot":"","sources":["../../src/views/SettingsView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAyBH,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAyMD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CAmdjE"}
@@ -5,7 +5,7 @@
5
5
  'use client';
6
6
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
7
  import { useState, useEffect, useRef } from 'react';
8
- import { Save, RefreshCw, Globe, Mail, Search, Settings2, Calendar } from 'lucide-react';
8
+ import { Save, RefreshCw, Globe, Globe2, Mail, Search, Settings2, Calendar, Plus, Trash2, X, Edit3 } from 'lucide-react';
9
9
  import { FaFacebook, FaInstagram, FaLinkedin, FaPinterest, FaTiktok } from 'react-icons/fa';
10
10
  import { FaXTwitter } from 'react-icons/fa6';
11
11
  const AVAILABLE_LOCALES = [
@@ -21,11 +21,46 @@ const AVAILABLE_PLATFORMS = [
21
21
  { name: 'LinkedIn', icon: _jsx(FaLinkedin, { size: 18 }) },
22
22
  { name: 'TikTok', icon: _jsx(FaTiktok, { size: 18 }) },
23
23
  ];
24
+ function DomainLanguagesCard({ config, onEdit }) {
25
+ return (_jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsx("div", { className: "flex items-center justify-between border-b border-dashboard-border pb-4 mb-4", children: _jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text", children: [_jsx(Globe2, { size: 18, className: "text-primary" }), "Domain Languages"] }) }), _jsx("div", { className: "space-y-2", children: config && config.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: config.map((entry, index) => (_jsxs("div", { className: "inline-flex items-center gap-2 px-3 py-1.5 bg-dashboard-card rounded-full border border-dashboard-border text-xs", children: [_jsx("span", { className: "font-medium text-dashboard-text", children: entry.domain }), _jsx("span", { className: "text-dashboard-text-secondary", children: "\u2192" }), _jsx("span", { children: AVAILABLE_LOCALES.find(l => l.code === entry.locale)?.flag }), _jsx("span", { className: "text-dashboard-text-secondary", children: entry.allowUserOverride ? '' : '🔒' })] }, index))) })) : (_jsx("p", { className: "text-xs text-dashboard-text-secondary", children: "No domain languages configured." })) }), _jsxs("button", { onClick: onEdit, className: "mt-4 w-full flex items-center justify-center gap-2 px-4 py-2 bg-primary text-white rounded-xl text-xs font-bold uppercase tracking-wider hover:bg-primary/90 transition-colors", children: [_jsx(Edit3, { size: 14 }), config?.length ? 'Edit Domains' : 'Add Domains'] })] }));
26
+ }
27
+ function DomainLanguagesModal({ isOpen, config, onSave, onClose }) {
28
+ const [tempConfig, setTempConfig] = useState(config);
29
+ const [expandedIndex, setExpandedIndex] = useState(null);
30
+ useEffect(() => {
31
+ setTempConfig(config);
32
+ setExpandedIndex(null);
33
+ }, [config, isOpen]);
34
+ if (!isOpen)
35
+ return null;
36
+ const handleAdd = () => {
37
+ setTempConfig([...tempConfig, { domain: '', locale: 'en', allowUserOverride: true }]);
38
+ setExpandedIndex(tempConfig.length);
39
+ };
40
+ const handleUpdate = (index, field, value) => {
41
+ const updated = [...tempConfig];
42
+ updated[index] = { ...updated[index], [field]: value };
43
+ setTempConfig(updated);
44
+ };
45
+ const handleRemove = (index) => {
46
+ setTempConfig(tempConfig.filter((_, i) => i !== index));
47
+ if (expandedIndex === index)
48
+ setExpandedIndex(null);
49
+ };
50
+ const toggleExpand = (index) => {
51
+ setExpandedIndex(expandedIndex === index ? null : index);
52
+ };
53
+ return (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4", children: _jsxs("div", { className: "bg-dashboard-bg rounded-3xl border border-dashboard-border w-full max-w-lg max-h-[80vh] overflow-hidden flex flex-col", children: [_jsxs("div", { className: "flex items-center justify-between p-6 border-b border-dashboard-border", children: [_jsx("h2", { className: "text-xl font-bold text-dashboard-text", children: "Domain Languages" }), _jsx("button", { onClick: onClose, className: "p-2 text-dashboard-text-secondary hover:text-dashboard-text transition-colors", children: _jsx(X, { size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto p-6 space-y-4", children: [tempConfig.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider", children: "Current Config" }), tempConfig.map((entry, index) => (_jsxs("div", { className: "bg-dashboard-card rounded-xl border border-dashboard-border overflow-hidden", children: [_jsxs("div", { onClick: () => toggleExpand(index), className: "w-full flex items-center justify-between p-3 text-left hover:bg-dashboard-border/50 transition-colors cursor-pointer", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-dashboard-text", children: entry.domain || '(no domain)' }), _jsx("span", { className: "text-dashboard-text-secondary", children: "\u2192" }), _jsx("span", { children: AVAILABLE_LOCALES.find(l => l.code === entry.locale)?.flag }), entry.allowUserOverride === false && _jsx("span", { className: "text-dashboard-text-secondary", children: "\uD83D\uDD12" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { onClick: (e) => { e.stopPropagation(); handleRemove(index); }, className: "p-1 text-red-500 hover:bg-red-500/10 rounded transition-colors", title: "Remove", children: _jsx(Trash2, { size: 14 }) }), _jsx("span", { className: "text-dashboard-text-secondary text-xs", children: expandedIndex === index ? '▲' : '▼' })] })] }), expandedIndex === index && (_jsxs("div", { className: "p-3 pt-0 space-y-3 border-t border-dashboard-border", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider", children: "Domain" }), _jsx("input", { type: "text", value: entry.domain, onChange: (e) => handleUpdate(index, 'domain', e.target.value), placeholder: "example.com", className: "w-full px-3 py-2 bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:ring-2 focus:ring-primary text-dashboard-text text-sm" })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("div", { className: "flex-1 space-y-1", children: [_jsx("label", { className: "text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider", children: "Language" }), _jsx("select", { value: entry.locale, onChange: (e) => handleUpdate(index, 'locale', e.target.value), className: "w-full px-3 py-2 bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:ring-2 focus:ring-primary text-dashboard-text text-sm", children: AVAILABLE_LOCALES.map(loc => (_jsxs("option", { value: loc.code, children: [loc.flag, " ", loc.name] }, loc.code))) })] }) }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { className: "text-[10px] text-dashboard-text-secondary", children: "Allow user to change" }), _jsx("button", { onClick: () => handleUpdate(index, 'allowUserOverride', !entry.allowUserOverride), className: `relative w-10 h-5 rounded-full transition-colors ${entry.allowUserOverride ? 'bg-primary' : 'bg-neutral-300 dark:bg-neutral-600'}`, children: _jsx("div", { className: `absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${entry.allowUserOverride ? 'translate-x-5' : 'translate-x-0'}` }) })] })] }))] }, index)))] })), _jsxs("div", { className: "border-t border-dashboard-border pt-4", children: [_jsx("p", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider mb-3", children: "Add New Domain" }), _jsxs("button", { onClick: handleAdd, className: "w-full flex items-center justify-center gap-2 px-4 py-3 border-2 border-dashed border-dashboard-border text-dashboard-text-secondary hover:border-primary hover:text-primary rounded-xl transition-colors", children: [_jsx(Plus, { size: 16 }), "Add Domain"] })] }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary text-center pt-2", children: "Configure which language to use per domain. E.g., botanicsandyou.nl \u2192 Dutch, botanicsandyou.se \u2192 Swedish." })] }), _jsxs("div", { className: "flex gap-3 p-6 border-t border-dashboard-border", children: [_jsx("button", { onClick: onClose, className: "flex-1 px-4 py-2 bg-dashboard-card text-dashboard-text rounded-xl text-sm font-bold hover:bg-dashboard-border transition-colors", children: "Cancel" }), _jsx("button", { onClick: () => onSave(tempConfig), className: "flex-1 px-4 py-2 bg-primary text-white rounded-xl text-sm font-bold hover:bg-primary/90 transition-colors", children: "Save" })] })] }) }));
54
+ }
55
+ // =============================================================================
56
+ // Main Component
57
+ // =============================================================================
24
58
  export function SettingsView({ siteId, locale }) {
25
59
  const [isLoading, setIsLoading] = useState(true);
26
60
  const [isSaving, setIsSaving] = useState(false);
27
61
  const [showSuccess, setShowSuccess] = useState(false);
28
62
  const [activeTab, setActiveTab] = useState('en');
63
+ const [showDomainModal, setShowDomainModal] = useState(false);
29
64
  const [settings, setSettings] = useState({
30
65
  identifier: 'site_config',
31
66
  siteName: '',
@@ -49,27 +84,22 @@ export function SettingsView({ siteId, locale }) {
49
84
  sv: [],
50
85
  nl: [],
51
86
  },
87
+ domainLocaleConfig: [],
52
88
  });
53
89
  // Fetch settings on load
54
90
  useEffect(() => {
55
91
  const fetchSettings = async () => {
56
92
  try {
57
93
  setIsLoading(true);
58
- const response = await fetch('/api/plugin-website/settings', {
59
- credentials: 'include',
60
- });
61
- if (!response.ok) {
94
+ const response = await fetch('/api/plugin-website/settings', { credentials: 'include' });
95
+ if (!response.ok)
62
96
  throw new Error('Failed to fetch settings');
63
- }
64
97
  const data = await response.json();
65
98
  if (data.siteName !== undefined) {
66
- // Convert launch_date to datetime-local format if it exists
67
99
  let launchDate = '';
68
100
  if (data.launch_date) {
69
- // If it's a Date object or ISO string, convert to datetime-local format
70
101
  const date = new Date(data.launch_date);
71
102
  if (!isNaN(date.getTime())) {
72
- // Format as YYYY-MM-DDTHH:mm for datetime-local input (24-hour format)
73
103
  const year = date.getFullYear();
74
104
  const month = String(date.getMonth() + 1).padStart(2, '0');
75
105
  const day = String(date.getDate()).padStart(2, '0');
@@ -77,19 +107,6 @@ export function SettingsView({ siteId, locale }) {
77
107
  const minutes = String(date.getMinutes()).padStart(2, '0');
78
108
  launchDate = `${year}-${month}-${day}T${hours}:${minutes}`;
79
109
  }
80
- else {
81
- // If it's already a string in the correct format, use it
82
- // Ensure it has time component, default to 00:00 if missing
83
- if (data.launch_date.includes('T') && data.launch_date.split('T')[1]) {
84
- launchDate = data.launch_date;
85
- }
86
- else if (data.launch_date.includes('T')) {
87
- launchDate = `${data.launch_date.split('T')[0]}T00:00`;
88
- }
89
- else {
90
- launchDate = `${data.launch_date}T00:00`;
91
- }
92
- }
93
110
  }
94
111
  setSettings({
95
112
  identifier: 'site_config',
@@ -109,11 +126,8 @@ export function SettingsView({ siteId, locale }) {
109
126
  sv: { physicalAddress: '', phoneNumber: '' },
110
127
  nl: { physicalAddress: '', phoneNumber: '' },
111
128
  },
112
- localizedSocials: data.localizedSocials || {
113
- en: [],
114
- sv: [],
115
- nl: [],
116
- },
129
+ localizedSocials: data.localizedSocials || { en: [], sv: [], nl: [] },
130
+ domainLocaleConfig: data.domainLocaleConfig || [],
117
131
  });
118
132
  }
119
133
  }
@@ -128,34 +142,19 @@ export function SettingsView({ siteId, locale }) {
128
142
  }, []);
129
143
  // Use ref to always get latest state
130
144
  const settingsRef = useRef(settings);
131
- useEffect(() => {
132
- settingsRef.current = settings;
133
- }, [settings]);
145
+ useEffect(() => { settingsRef.current = settings; }, [settings]);
134
146
  // Save settings
135
147
  const handleSave = async () => {
136
148
  try {
137
149
  setIsSaving(true);
138
- // Get the latest state from ref
139
150
  const currentSettings = settingsRef.current;
140
- // Prepare settings for API - ensure launch_date is properly formatted
141
- const settingsToSave = {
142
- ...currentSettings,
143
- };
144
- // Handle launch_date - keep it if it has a value, otherwise don't include it
151
+ const settingsToSave = { ...currentSettings };
145
152
  if (currentSettings.launch_date && currentSettings.launch_date.trim() !== '') {
146
153
  settingsToSave.launch_date = currentSettings.launch_date;
147
154
  }
148
155
  else {
149
- // Don't include launch_date if it's empty
150
156
  delete settingsToSave.launch_date;
151
157
  }
152
- console.log('[SettingsView] Saving settings:', {
153
- launch_date: settingsToSave.launch_date,
154
- launch_date_type: typeof settingsToSave.launch_date,
155
- has_launch_date: 'launch_date' in settingsToSave,
156
- currentState_launch_date: currentSettings.launch_date,
157
- all_keys: Object.keys(settingsToSave),
158
- });
159
158
  const response = await fetch('/api/plugin-website/settings', {
160
159
  method: 'POST',
161
160
  headers: { 'Content-Type': 'application/json' },
@@ -177,27 +176,17 @@ export function SettingsView({ siteId, locale }) {
177
176
  setIsSaving(false);
178
177
  }
179
178
  };
180
- // Remove social link
181
- const handleRemoveSocial = (id) => {
182
- setSettings({
183
- ...settings,
184
- socials: settings.socials?.filter(social => social.id !== id) || [],
185
- });
186
- };
187
179
  // Update localized contact info
188
180
  const handleUpdateLocalizedContact = (field, value) => {
189
181
  setSettings({
190
182
  ...settings,
191
183
  localizedContactInfo: {
192
184
  ...settings.localizedContactInfo,
193
- [activeTab]: {
194
- ...settings.localizedContactInfo?.[activeTab],
195
- [field]: value,
196
- },
185
+ [activeTab]: { ...settings.localizedContactInfo?.[activeTab], [field]: value },
197
186
  },
198
187
  });
199
188
  };
200
- // Add localized social link
189
+ // Social links handlers
201
190
  const handleAddLocalizedSocial = () => {
202
191
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
203
192
  const newId = currentSocials.length ? Math.max(...currentSocials.map(s => s.id)) + 1 : 1;
@@ -209,76 +198,53 @@ export function SettingsView({ siteId, locale }) {
209
198
  },
210
199
  });
211
200
  };
212
- // Update localized social link
213
201
  const handleUpdateLocalizedSocial = (id, field, value) => {
214
202
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
215
203
  setSettings({
216
204
  ...settings,
217
205
  localizedSocials: {
218
206
  ...settings.localizedSocials,
219
- [activeTab]: currentSocials.map(social => social.id === id ? { ...social, [field]: value } : social),
207
+ [activeTab]: currentSocials.map(s => s.id === id ? { ...s, [field]: value } : s),
220
208
  },
221
209
  });
222
210
  };
223
- // Remove localized social link
224
211
  const handleRemoveLocalizedSocial = (id) => {
225
212
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
226
213
  setSettings({
227
214
  ...settings,
228
215
  localizedSocials: {
229
216
  ...settings.localizedSocials,
230
- [activeTab]: currentSocials.filter(social => social.id !== id),
217
+ [activeTab]: currentSocials.filter(s => s.id !== id),
231
218
  },
232
219
  });
233
220
  };
221
+ // Domain locale handlers
222
+ const openDomainModal = () => setShowDomainModal(true);
223
+ const closeDomainModal = () => setShowDomainModal(false);
224
+ const saveDomainConfig = (config) => {
225
+ setSettings({ ...settings, domainLocaleConfig: config });
226
+ setShowDomainModal(false);
227
+ };
234
228
  if (isLoading) {
235
229
  return (_jsx("div", { className: "h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx(RefreshCw, { className: "w-8 h-8 animate-spin text-primary mx-auto mb-4" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Loading settings..." })] }) }));
236
230
  }
237
- return (_jsx("div", { className: "h-full w-full rounded-[2.5rem] bg-dashboard-card p-8 overflow-y-auto", children: _jsxs("div", { className: "max-w-6xl mx-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2", children: "Website Settings" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Manage your website identity, contact information, and social links" })] }), _jsx("button", { onClick: handleSave, disabled: isSaving, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${isSaving
238
- ? 'bg-neutral-400 text-white cursor-not-allowed'
239
- : showSuccess
240
- ? 'bg-green-600 text-white'
241
- : 'bg-primary text-white hover:bg-primary/90'}`, children: isSaving ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : showSuccess ? (_jsxs(_Fragment, { children: [_jsx(Settings2, { className: "w-4 h-4" }), "Saved!"] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "w-4 h-4" }), "Save Settings"] })) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-8", children: [_jsxs("div", { className: "lg:col-span-2 space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Search, { size: 20, className: "text-primary" }), "Website Identity & SEO"] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Name" }), _jsx("input", { type: "text", value: settings.siteName || '', onChange: (e) => setSettings({ ...settings, siteName: e.target.value }), placeholder: "e.g., Botanics & You", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Tagline" }), _jsx("input", { type: "text", value: settings.siteTagline || '', onChange: (e) => setSettings({ ...settings, siteTagline: e.target.value }), placeholder: "e.g., Sharing my knowledge with you", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Description" }), _jsx("textarea", { value: settings.siteDescription || '', onChange: (e) => setSettings({ ...settings, siteDescription: e.target.value }), placeholder: "A brief description of your website", rows: 3, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Keywords (comma-separated)" }), _jsx("input", { type: "text", value: settings.keywords || '', onChange: (e) => setSettings({ ...settings, keywords: e.target.value }), placeholder: "keyword1, keyword2, keyword3", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Mail, { size: 20, className: "text-primary" }), "Contact Information"] }), _jsx("div", { className: "flex gap-2 mb-6", children: AVAILABLE_LOCALES.map((loc) => (_jsxs("button", { onClick: () => setActiveTab(loc.code), className: `px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${activeTab === loc.code
242
- ? 'bg-primary text-white'
243
- : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'}`, children: [_jsx("span", { className: "mr-1", children: loc.flag }), loc.code.toUpperCase()] }, loc.code))) }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Contact Email (global)" }), _jsx("input", { type: "email", value: settings.contactEmail || '', onChange: (e) => setSettings({ ...settings, contactEmail: e.target.value }), placeholder: "contact@example.com", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: ["Phone Number (", activeTab.toUpperCase(), ")"] }), _jsx("input", { type: "tel", value: settings.localizedContactInfo?.[activeTab]?.phoneNumber || settings.phoneNumber || '', onChange: (e) => handleUpdateLocalizedContact('phoneNumber', e.target.value), placeholder: "+31 6 12345678", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: ["Physical Address (", activeTab.toUpperCase(), ")"] }), _jsx("textarea", { value: settings.localizedContactInfo?.[activeTab]?.physicalAddress || settings.physicalAddress || '', onChange: (e) => handleUpdateLocalizedContact('physicalAddress', e.target.value), placeholder: "Street address, City, Country", rows: 2, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-dashboard-border pb-4 mb-6", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text", children: [_jsx(Globe, { size: 20, className: "text-primary" }), "Social Links (", activeTab.toUpperCase(), ")"] }), _jsx("button", { onClick: handleAddLocalizedSocial, className: "px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all", children: "Add Social" })] }), _jsx("div", { className: "flex gap-2 mb-6", children: AVAILABLE_LOCALES.map((loc) => (_jsxs("button", { onClick: () => setActiveTab(loc.code), className: `px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${activeTab === loc.code
244
- ? 'bg-primary text-white'
245
- : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'}`, children: [_jsx("span", { className: "mr-1", children: loc.flag }), loc.code.toUpperCase()] }, loc.code))) }), _jsx("div", { className: "space-y-4", children: (() => {
246
- const currentSocials = settings.localizedSocials?.[activeTab] || [];
247
- return currentSocials.length > 0 ? (currentSocials.map((social) => {
248
- const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
249
- return (_jsxs("div", { className: "flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border", children: [_jsx("div", { className: "flex-shrink-0 text-dashboard-text-secondary", children: platform?.icon || _jsx(Globe, { size: 18 }) }), _jsxs("select", { value: social.platform, onChange: (e) => handleUpdateLocalizedSocial(social.id, 'platform', e.target.value), className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text", children: [_jsx("option", { value: "", children: "Select Platform" }), AVAILABLE_PLATFORMS.map(p => (_jsx("option", { value: p.name, children: p.name }, p.name)))] }), _jsx("input", { type: "url", value: social.url, onChange: (e) => handleUpdateLocalizedSocial(social.id, 'url', e.target.value), placeholder: "https://...", className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" }), _jsx("button", { onClick: () => handleRemoveLocalizedSocial(social.id), className: "px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors", children: "Remove" })] }, social.id));
250
- })) : (_jsxs("p", { className: "text-sm text-dashboard-text-secondary text-center py-8", children: ["No social links added for ", activeTab.toUpperCase(), ". Click \"Add Social\" to get started."] }));
251
- })() })] })] }), _jsxs("div", { className: "space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Settings2, { size: 18, className: "text-primary" }), "Maintenance"] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block mb-1", children: "Maintenance Mode" }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Show maintenance page to visitors" })] }), _jsx("button", { onClick: () => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode }), className: `relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`, children: _jsx("div", { className: `absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}` }) })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Calendar, { size: 18, className: "text-primary" }), "Launch Date"] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Date" }), _jsx("input", { type: "date", value: settings.launch_date ? settings.launch_date.split('T')[0] : '', onChange: (e) => {
252
- const dateValue = e.target.value;
253
- const currentTime = settings.launch_date?.includes('T')
254
- ? settings.launch_date.split('T')[1]
255
- : '00:00';
256
- const newLaunchDate = dateValue ? `${dateValue}T${currentTime}` : '';
257
- console.log('[SettingsView] Date changed:', {
258
- dateValue,
259
- currentTime,
260
- newLaunchDate,
261
- });
262
- setSettings(prev => ({
263
- ...prev,
264
- launch_date: newLaunchDate,
265
- }));
266
- }, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Time (24-hour format)" }), _jsx("input", { type: "time", step: "60", value: settings.launch_date?.includes('T')
267
- ? settings.launch_date.split('T')[1]
268
- : '00:00', onChange: (e) => {
269
- const timeValue = e.target.value;
270
- const currentDate = settings.launch_date?.includes('T')
271
- ? settings.launch_date.split('T')[0]
272
- : new Date().toISOString().split('T')[0];
273
- const newLaunchDate = timeValue ? `${currentDate}T${timeValue}` : '';
274
- console.log('[SettingsView] Time changed:', {
275
- timeValue,
276
- currentDate,
277
- newLaunchDate,
278
- });
279
- setSettings(prev => ({
280
- ...prev,
281
- launch_date: newLaunchDate,
282
- }));
283
- }, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Used for countdown timers. Time defaults to 00:00 if not specified." })] })] })] })] })] }) }));
231
+ return (_jsxs("div", { className: "h-full w-full rounded-[2.5rem] bg-dashboard-card p-8 overflow-y-auto", children: [_jsxs("div", { className: "max-w-6xl mx-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2", children: "Website Settings" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Manage your website identity, contact information, and social links" })] }), _jsx("button", { onClick: handleSave, disabled: isSaving, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${isSaving ? 'bg-neutral-400 text-white cursor-not-allowed' :
232
+ showSuccess ? 'bg-green-600 text-white' :
233
+ 'bg-primary text-white hover:bg-primary/90'}`, children: isSaving ? _jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), " Saving..."] }) :
234
+ showSuccess ? _jsxs(_Fragment, { children: [_jsx(Settings2, { className: "w-4 h-4" }), " Saved!"] }) :
235
+ _jsxs(_Fragment, { children: [_jsx(Save, { className: "w-4 h-4" }), " Save Settings"] }) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-8", children: [_jsxs("div", { className: "lg:col-span-2 space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Search, { size: 20, className: "text-primary" }), "Website Identity & SEO"] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Name" }), _jsx("input", { type: "text", value: settings.siteName || '', onChange: (e) => setSettings({ ...settings, siteName: e.target.value }), placeholder: "e.g., Botanics & You", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Tagline" }), _jsx("input", { type: "text", value: settings.siteTagline || '', onChange: (e) => setSettings({ ...settings, siteTagline: e.target.value }), placeholder: "e.g., Sharing my knowledge with you", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Website Description" }), _jsx("textarea", { value: settings.siteDescription || '', onChange: (e) => setSettings({ ...settings, siteDescription: e.target.value }), placeholder: "A brief description of your website", rows: 3, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Keywords (comma-separated)" }), _jsx("input", { type: "text", value: settings.keywords || '', onChange: (e) => setSettings({ ...settings, keywords: e.target.value }), placeholder: "keyword1, keyword2, keyword3", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Mail, { size: 20, className: "text-primary" }), "Contact Information"] }), _jsx("div", { className: "flex gap-2 mb-6", children: AVAILABLE_LOCALES.map((loc) => (_jsxs("button", { onClick: () => setActiveTab(loc.code), className: `px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'}`, children: [_jsx("span", { className: "mr-1", children: loc.flag }), loc.code.toUpperCase()] }, loc.code))) }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "Contact Email (global)" }), _jsx("input", { type: "email", value: settings.contactEmail || '', onChange: (e) => setSettings({ ...settings, contactEmail: e.target.value }), placeholder: "contact@example.com", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: ["Phone Number (", activeTab.toUpperCase(), ")"] }), _jsx("input", { type: "tel", value: settings.localizedContactInfo?.[activeTab]?.phoneNumber || settings.phoneNumber || '', onChange: (e) => handleUpdateLocalizedContact('phoneNumber', e.target.value), placeholder: "+31 6 12345678", className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text" })] })] }), _jsxs("div", { className: "mt-6 space-y-2", children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: ["Physical Address (", activeTab.toUpperCase(), ")"] }), _jsx("textarea", { value: settings.localizedContactInfo?.[activeTab]?.physicalAddress || settings.physicalAddress || '', onChange: (e) => handleUpdateLocalizedContact('physicalAddress', e.target.value), placeholder: "Street address, City, Country", rows: 2, className: "w-full px-4 py-3 bg-dashboard-card border border-dashboard-border rounded-2xl outline-none focus:ring-2 focus:ring-primary transition-all text-dashboard-text resize-none" })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-dashboard-border pb-4 mb-6", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text", children: [_jsx(Globe, { size: 20, className: "text-primary" }), "Social Links (", activeTab.toUpperCase(), ")"] }), _jsx("button", { onClick: handleAddLocalizedSocial, className: "px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all", children: "Add Social" })] }), _jsx("div", { className: "flex gap-2 mb-6", children: AVAILABLE_LOCALES.map((loc) => (_jsxs("button", { onClick: () => setActiveTab(loc.code), className: `px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'}`, children: [_jsx("span", { className: "mr-1", children: loc.flag }), loc.code.toUpperCase()] }, loc.code))) }), _jsx("div", { className: "space-y-4", children: (() => {
236
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
237
+ return currentSocials.length > 0 ? (currentSocials.map((social) => {
238
+ const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
239
+ return (_jsxs("div", { className: "flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border", children: [_jsx("div", { className: "flex-shrink-0 text-dashboard-text-secondary", children: platform?.icon || _jsx(Globe, { size: 18 }) }), _jsxs("select", { value: social.platform, onChange: (e) => handleUpdateLocalizedSocial(social.id, 'platform', e.target.value), className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text", children: [_jsx("option", { value: "", children: "Select Platform" }), AVAILABLE_PLATFORMS.map(p => (_jsx("option", { value: p.name, children: p.name }, p.name)))] }), _jsx("input", { type: "url", value: social.url, onChange: (e) => handleUpdateLocalizedSocial(social.id, 'url', e.target.value), placeholder: "https://...", className: "flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" }), _jsx("button", { onClick: () => handleRemoveLocalizedSocial(social.id), className: "px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 transition-colors", children: "Remove" })] }, social.id));
240
+ })) : (_jsxs("p", { className: "text-sm text-dashboard-text-secondary text-center py-8", children: ["No social links added for ", activeTab.toUpperCase(), ". Click \"Add Social\" to get started."] }));
241
+ })() })] })] }), _jsxs("div", { className: "space-y-8", children: [_jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Settings2, { size: 18, className: "text-primary" }), "Maintenance"] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block mb-1", children: "Maintenance Mode" }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Show maintenance page to visitors" })] }), _jsx("button", { onClick: () => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode }), className: `relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`, children: _jsx("div", { className: `absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}` }) })] })] }), _jsxs("section", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Calendar, { size: 18, className: "text-primary" }), "Launch Date"] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Date" }), _jsx("input", { type: "date", value: settings.launch_date ? settings.launch_date.split('T')[0] : '', onChange: (e) => {
242
+ const dateValue = e.target.value;
243
+ const currentTime = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00';
244
+ setSettings(prev => ({ ...prev, launch_date: dateValue ? `${dateValue}T${currentTime}` : '' }));
245
+ }, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text block", children: "Time (24-hour format)" }), _jsx("input", { type: "time", step: "60", value: settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00', onChange: (e) => {
246
+ const timeValue = e.target.value;
247
+ const currentDate = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[0] : new Date().toISOString().split('T')[0];
248
+ setSettings(prev => ({ ...prev, launch_date: timeValue ? `${currentDate}T${timeValue}` : '' }));
249
+ }, className: "w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text" })] }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary", children: "Used for countdown timers." })] })] }), _jsx(DomainLanguagesCard, { config: settings.domainLocaleConfig || [], onEdit: openDomainModal })] })] })] }), _jsx(DomainLanguagesModal, { isOpen: showDomainModal, config: settings.domainLocaleConfig || [], onSave: saveDomainConfig, onClose: closeDomainModal })] }));
284
250
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhits/plugin-website",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Website management and configuration plugin for the JHITS ecosystem",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,10 +25,10 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "lucide-react": "^0.564.0",
28
+ "lucide-react": "^0.577.0",
29
29
  "mongodb": "^7.1.0",
30
30
  "next-auth": "^4.24.13",
31
- "react-icons": "^5.5.0",
31
+ "react-icons": "^5.6.0",
32
32
  "@jhits/plugin-core": "0.0.10"
33
33
  },
34
34
  "peerDependencies": {
@@ -37,7 +37,7 @@
37
37
  "react-dom": ">=18.0.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/node": "^25.2.3",
40
+ "@types/node": "^25.3.3",
41
41
  "@types/react": "^19.2.14",
42
42
  "@types/react-dom": "^19.2.3",
43
43
  "next": "16.1.6",
@@ -4,6 +4,12 @@
4
4
 
5
5
  export type SupportedLocale = 'en' | 'sv' | 'nl';
6
6
 
7
+ export interface DomainLocaleConfig {
8
+ domain: string;
9
+ locale: SupportedLocale;
10
+ allowUserOverride?: boolean;
11
+ }
12
+
7
13
  export interface LocalizedContactInfo {
8
14
  physicalAddress?: string;
9
15
  phoneNumber?: string;
@@ -30,6 +36,7 @@ export interface WebsiteSettings {
30
36
  socials?: SocialLink[];
31
37
  localizedContactInfo?: Partial<Record<SupportedLocale, LocalizedContactInfo>>;
32
38
  localizedSocials?: Partial<Record<SupportedLocale, LocalizedSocialLink[]>>;
39
+ domainLocaleConfig?: DomainLocaleConfig[];
33
40
  updatedAt?: Date;
34
41
  createdAt?: Date;
35
42
  }
@@ -6,8 +6,8 @@
6
6
  'use client';
7
7
 
8
8
  import React, { useState, useEffect, useRef } from 'react';
9
- import { Save, RefreshCw, Globe, Mail, Phone, MapPin, Search, Settings2, Calendar } from 'lucide-react';
10
- import { WebsiteSettings, SocialLink, SupportedLocale, LocalizedSocialLink } from '../types/settings';
9
+ import { Save, RefreshCw, Globe, Globe2, Mail, Search, Settings2, Calendar, Plus, Trash2, X, Edit3 } from 'lucide-react';
10
+ import { WebsiteSettings, SupportedLocale, LocalizedSocialLink, DomainLocaleConfig } from '../types/settings';
11
11
  import { FaFacebook, FaInstagram, FaLinkedin, FaPinterest, FaTiktok } from 'react-icons/fa';
12
12
  import { FaXTwitter } from 'react-icons/fa6';
13
13
 
@@ -31,11 +31,211 @@ export interface SettingsViewProps {
31
31
  locale: string;
32
32
  }
33
33
 
34
+ // =============================================================================
35
+ // Sub-components
36
+ // =============================================================================
37
+
38
+ interface DomainLanguagesCardProps {
39
+ config: DomainLocaleConfig[];
40
+ onEdit: () => void;
41
+ }
42
+
43
+ function DomainLanguagesCard({ config, onEdit }: DomainLanguagesCardProps) {
44
+ return (
45
+ <section className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
46
+ <div className="flex items-center justify-between border-b border-dashboard-border pb-4 mb-4">
47
+ <div className="flex items-center gap-2 font-bold text-dashboard-text">
48
+ <Globe2 size={18} className="text-primary" />
49
+ Domain Languages
50
+ </div>
51
+ </div>
52
+ <div className="space-y-2">
53
+ {config && config.length > 0 ? (
54
+ <div className="flex flex-wrap gap-2">
55
+ {config.map((entry, index) => (
56
+ <div key={index} className="inline-flex items-center gap-2 px-3 py-1.5 bg-dashboard-card rounded-full border border-dashboard-border text-xs">
57
+ <span className="font-medium text-dashboard-text">{entry.domain}</span>
58
+ <span className="text-dashboard-text-secondary">→</span>
59
+ <span>{AVAILABLE_LOCALES.find(l => l.code === entry.locale)?.flag}</span>
60
+ <span className="text-dashboard-text-secondary">
61
+ {entry.allowUserOverride ? '' : '🔒'}
62
+ </span>
63
+ </div>
64
+ ))}
65
+ </div>
66
+ ) : (
67
+ <p className="text-xs text-dashboard-text-secondary">
68
+ No domain languages configured.
69
+ </p>
70
+ )}
71
+ </div>
72
+ <button
73
+ onClick={onEdit}
74
+ className="mt-4 w-full flex items-center justify-center gap-2 px-4 py-2 bg-primary text-white rounded-xl text-xs font-bold uppercase tracking-wider hover:bg-primary/90 transition-colors"
75
+ >
76
+ <Edit3 size={14} />
77
+ {config?.length ? 'Edit Domains' : 'Add Domains'}
78
+ </button>
79
+ </section>
80
+ );
81
+ }
82
+
83
+ interface DomainLanguagesModalProps {
84
+ isOpen: boolean;
85
+ config: DomainLocaleConfig[];
86
+ onSave: (config: DomainLocaleConfig[]) => void;
87
+ onClose: () => void;
88
+ }
89
+
90
+ function DomainLanguagesModal({ isOpen, config, onSave, onClose }: DomainLanguagesModalProps) {
91
+ const [tempConfig, setTempConfig] = useState<DomainLocaleConfig[]>(config);
92
+ const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
93
+
94
+ useEffect(() => {
95
+ setTempConfig(config);
96
+ setExpandedIndex(null);
97
+ }, [config, isOpen]);
98
+
99
+ if (!isOpen) return null;
100
+
101
+ const handleAdd = () => {
102
+ setTempConfig([...tempConfig, { domain: '', locale: 'en', allowUserOverride: true }]);
103
+ setExpandedIndex(tempConfig.length);
104
+ };
105
+
106
+ const handleUpdate = (index: number, field: keyof DomainLocaleConfig, value: any) => {
107
+ const updated = [...tempConfig];
108
+ updated[index] = { ...updated[index], [field]: value };
109
+ setTempConfig(updated);
110
+ };
111
+
112
+ const handleRemove = (index: number) => {
113
+ setTempConfig(tempConfig.filter((_, i) => i !== index));
114
+ if (expandedIndex === index) setExpandedIndex(null);
115
+ };
116
+
117
+ const toggleExpand = (index: number) => {
118
+ setExpandedIndex(expandedIndex === index ? null : index);
119
+ };
120
+
121
+ return (
122
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
123
+ <div className="bg-dashboard-bg rounded-3xl border border-dashboard-border w-full max-w-lg max-h-[80vh] overflow-hidden flex flex-col">
124
+ <div className="flex items-center justify-between p-6 border-b border-dashboard-border">
125
+ <h2 className="text-xl font-bold text-dashboard-text">Domain Languages</h2>
126
+ <button onClick={onClose} className="p-2 text-dashboard-text-secondary hover:text-dashboard-text transition-colors">
127
+ <X size={20} />
128
+ </button>
129
+ </div>
130
+ <div className="flex-1 overflow-y-auto p-6 space-y-4">
131
+ {/* Existing entries - collapsed by default */}
132
+ {tempConfig.length > 0 && (
133
+ <div className="space-y-2">
134
+ <p className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider">Current Config</p>
135
+ {tempConfig.map((entry, index) => (
136
+ <div key={index} className="bg-dashboard-card rounded-xl border border-dashboard-border overflow-hidden">
137
+ <div
138
+ onClick={() => toggleExpand(index)}
139
+ className="w-full flex items-center justify-between p-3 text-left hover:bg-dashboard-border/50 transition-colors cursor-pointer"
140
+ >
141
+ <div className="flex items-center gap-2">
142
+ <span className="font-medium text-dashboard-text">{entry.domain || '(no domain)'}</span>
143
+ <span className="text-dashboard-text-secondary">→</span>
144
+ <span>{AVAILABLE_LOCALES.find(l => l.code === entry.locale)?.flag}</span>
145
+ {entry.allowUserOverride === false && <span className="text-dashboard-text-secondary">🔒</span>}
146
+ </div>
147
+ <div className="flex items-center gap-2">
148
+ <button
149
+ onClick={(e) => { e.stopPropagation(); handleRemove(index); }}
150
+ className="p-1 text-red-500 hover:bg-red-500/10 rounded transition-colors"
151
+ title="Remove"
152
+ >
153
+ <Trash2 size={14} />
154
+ </button>
155
+ <span className="text-dashboard-text-secondary text-xs">
156
+ {expandedIndex === index ? '▲' : '▼'}
157
+ </span>
158
+ </div>
159
+ </div>
160
+ {expandedIndex === index && (
161
+ <div className="p-3 pt-0 space-y-3 border-t border-dashboard-border">
162
+ <div className="space-y-1">
163
+ <label className="text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider">Domain</label>
164
+ <input
165
+ type="text"
166
+ value={entry.domain}
167
+ onChange={(e) => handleUpdate(index, 'domain', e.target.value)}
168
+ placeholder="example.com"
169
+ className="w-full px-3 py-2 bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:ring-2 focus:ring-primary text-dashboard-text text-sm"
170
+ />
171
+ </div>
172
+ <div className="flex items-center gap-2">
173
+ <div className="flex-1 space-y-1">
174
+ <label className="text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider">Language</label>
175
+ <select
176
+ value={entry.locale}
177
+ onChange={(e) => handleUpdate(index, 'locale', e.target.value)}
178
+ className="w-full px-3 py-2 bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:ring-2 focus:ring-primary text-dashboard-text text-sm"
179
+ >
180
+ {AVAILABLE_LOCALES.map(loc => (<option key={loc.code} value={loc.code}>{loc.flag} {loc.name}</option>))}
181
+ </select>
182
+ </div>
183
+ </div>
184
+ <div className="flex items-center justify-between">
185
+ <label className="text-[10px] text-dashboard-text-secondary">Allow user to change</label>
186
+ <button
187
+ onClick={() => handleUpdate(index, 'allowUserOverride', !entry.allowUserOverride)}
188
+ className={`relative w-10 h-5 rounded-full transition-colors ${entry.allowUserOverride ? 'bg-primary' : 'bg-neutral-300 dark:bg-neutral-600'}`}
189
+ >
190
+ <div className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${entry.allowUserOverride ? 'translate-x-5' : 'translate-x-0'}`} />
191
+ </button>
192
+ </div>
193
+ </div>
194
+ )}
195
+ </div>
196
+ ))}
197
+ </div>
198
+ )}
199
+
200
+ {/* Add new domain - always visible */}
201
+ <div className="border-t border-dashboard-border pt-4">
202
+ <p className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider mb-3">Add New Domain</p>
203
+ <button
204
+ onClick={handleAdd}
205
+ className="w-full flex items-center justify-center gap-2 px-4 py-3 border-2 border-dashed border-dashboard-border text-dashboard-text-secondary hover:border-primary hover:text-primary rounded-xl transition-colors"
206
+ >
207
+ <Plus size={16} />
208
+ Add Domain
209
+ </button>
210
+ </div>
211
+
212
+ <p className="text-[10px] text-dashboard-text-secondary text-center pt-2">
213
+ Configure which language to use per domain. E.g., botanicsandyou.nl → Dutch, botanicsandyou.se → Swedish.
214
+ </p>
215
+ </div>
216
+ <div className="flex gap-3 p-6 border-t border-dashboard-border">
217
+ <button onClick={onClose} className="flex-1 px-4 py-2 bg-dashboard-card text-dashboard-text rounded-xl text-sm font-bold hover:bg-dashboard-border transition-colors">
218
+ Cancel
219
+ </button>
220
+ <button onClick={() => onSave(tempConfig)} className="flex-1 px-4 py-2 bg-primary text-white rounded-xl text-sm font-bold hover:bg-primary/90 transition-colors">
221
+ Save
222
+ </button>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ );
227
+ }
228
+
229
+ // =============================================================================
230
+ // Main Component
231
+ // =============================================================================
232
+
34
233
  export function SettingsView({ siteId, locale }: SettingsViewProps) {
35
234
  const [isLoading, setIsLoading] = useState(true);
36
235
  const [isSaving, setIsSaving] = useState(false);
37
236
  const [showSuccess, setShowSuccess] = useState(false);
38
237
  const [activeTab, setActiveTab] = useState<SupportedLocale>('en');
238
+ const [showDomainModal, setShowDomainModal] = useState(false);
39
239
  const [settings, setSettings] = useState<WebsiteSettings>({
40
240
  identifier: 'site_config',
41
241
  siteName: '',
@@ -59,6 +259,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
59
259
  sv: [],
60
260
  nl: [],
61
261
  },
262
+ domainLocaleConfig: [],
62
263
  });
63
264
 
64
265
  // Fetch settings on load
@@ -66,37 +267,21 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
66
267
  const fetchSettings = async () => {
67
268
  try {
68
269
  setIsLoading(true);
69
- const response = await fetch('/api/plugin-website/settings', {
70
- credentials: 'include',
71
- });
72
- if (!response.ok) {
73
- throw new Error('Failed to fetch settings');
74
- }
270
+ const response = await fetch('/api/plugin-website/settings', { credentials: 'include' });
271
+ if (!response.ok) throw new Error('Failed to fetch settings');
75
272
  const data = await response.json();
273
+
76
274
  if (data.siteName !== undefined) {
77
- // Convert launch_date to datetime-local format if it exists
78
275
  let launchDate = '';
79
276
  if (data.launch_date) {
80
- // If it's a Date object or ISO string, convert to datetime-local format
81
277
  const date = new Date(data.launch_date);
82
278
  if (!isNaN(date.getTime())) {
83
- // Format as YYYY-MM-DDTHH:mm for datetime-local input (24-hour format)
84
279
  const year = date.getFullYear();
85
280
  const month = String(date.getMonth() + 1).padStart(2, '0');
86
281
  const day = String(date.getDate()).padStart(2, '0');
87
282
  const hours = String(date.getHours()).padStart(2, '0');
88
283
  const minutes = String(date.getMinutes()).padStart(2, '0');
89
284
  launchDate = `${year}-${month}-${day}T${hours}:${minutes}`;
90
- } else {
91
- // If it's already a string in the correct format, use it
92
- // Ensure it has time component, default to 00:00 if missing
93
- if (data.launch_date.includes('T') && data.launch_date.split('T')[1]) {
94
- launchDate = data.launch_date;
95
- } else if (data.launch_date.includes('T')) {
96
- launchDate = `${data.launch_date.split('T')[0]}T00:00`;
97
- } else {
98
- launchDate = `${data.launch_date}T00:00`;
99
- }
100
285
  }
101
286
  }
102
287
 
@@ -118,11 +303,8 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
118
303
  sv: { physicalAddress: '', phoneNumber: '' },
119
304
  nl: { physicalAddress: '', phoneNumber: '' },
120
305
  },
121
- localizedSocials: data.localizedSocials || {
122
- en: [],
123
- sv: [],
124
- nl: [],
125
- },
306
+ localizedSocials: data.localizedSocials || { en: [], sv: [], nl: [] },
307
+ domainLocaleConfig: data.domainLocaleConfig || [],
126
308
  });
127
309
  }
128
310
  } catch (error) {
@@ -136,39 +318,21 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
136
318
 
137
319
  // Use ref to always get latest state
138
320
  const settingsRef = useRef(settings);
139
- useEffect(() => {
140
- settingsRef.current = settings;
141
- }, [settings]);
321
+ useEffect(() => { settingsRef.current = settings; }, [settings]);
142
322
 
143
323
  // Save settings
144
324
  const handleSave = async () => {
145
325
  try {
146
326
  setIsSaving(true);
147
-
148
- // Get the latest state from ref
149
327
  const currentSettings = settingsRef.current;
150
-
151
- // Prepare settings for API - ensure launch_date is properly formatted
152
- const settingsToSave: any = {
153
- ...currentSettings,
154
- };
155
-
156
- // Handle launch_date - keep it if it has a value, otherwise don't include it
328
+ const settingsToSave: any = { ...currentSettings };
329
+
157
330
  if (currentSettings.launch_date && currentSettings.launch_date.trim() !== '') {
158
331
  settingsToSave.launch_date = currentSettings.launch_date;
159
332
  } else {
160
- // Don't include launch_date if it's empty
161
333
  delete settingsToSave.launch_date;
162
334
  }
163
335
 
164
- console.log('[SettingsView] Saving settings:', {
165
- launch_date: settingsToSave.launch_date,
166
- launch_date_type: typeof settingsToSave.launch_date,
167
- has_launch_date: 'launch_date' in settingsToSave,
168
- currentState_launch_date: currentSettings.launch_date,
169
- all_keys: Object.keys(settingsToSave),
170
- });
171
-
172
336
  const response = await fetch('/api/plugin-website/settings', {
173
337
  method: 'POST',
174
338
  headers: { 'Content-Type': 'application/json' },
@@ -191,29 +355,18 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
191
355
  }
192
356
  };
193
357
 
194
- // Remove social link
195
- const handleRemoveSocial = (id: number) => {
196
- setSettings({
197
- ...settings,
198
- socials: settings.socials?.filter(social => social.id !== id) || [],
199
- });
200
- };
201
-
202
358
  // Update localized contact info
203
359
  const handleUpdateLocalizedContact = (field: 'physicalAddress' | 'phoneNumber', value: string) => {
204
360
  setSettings({
205
361
  ...settings,
206
362
  localizedContactInfo: {
207
363
  ...settings.localizedContactInfo,
208
- [activeTab]: {
209
- ...settings.localizedContactInfo?.[activeTab],
210
- [field]: value,
211
- },
364
+ [activeTab]: { ...settings.localizedContactInfo?.[activeTab], [field]: value },
212
365
  },
213
366
  });
214
367
  };
215
368
 
216
- // Add localized social link
369
+ // Social links handlers
217
370
  const handleAddLocalizedSocial = () => {
218
371
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
219
372
  const newId = currentSocials.length ? Math.max(...currentSocials.map(s => s.id)) + 1 : 1;
@@ -226,32 +379,36 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
226
379
  });
227
380
  };
228
381
 
229
- // Update localized social link
230
382
  const handleUpdateLocalizedSocial = (id: number, field: 'platform' | 'url', value: string) => {
231
383
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
232
384
  setSettings({
233
385
  ...settings,
234
386
  localizedSocials: {
235
387
  ...settings.localizedSocials,
236
- [activeTab]: currentSocials.map(social =>
237
- social.id === id ? { ...social, [field]: value } : social
238
- ),
388
+ [activeTab]: currentSocials.map(s => s.id === id ? { ...s, [field]: value } : s),
239
389
  },
240
390
  });
241
391
  };
242
392
 
243
- // Remove localized social link
244
393
  const handleRemoveLocalizedSocial = (id: number) => {
245
394
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
246
395
  setSettings({
247
396
  ...settings,
248
397
  localizedSocials: {
249
398
  ...settings.localizedSocials,
250
- [activeTab]: currentSocials.filter(social => social.id !== id),
399
+ [activeTab]: currentSocials.filter(s => s.id !== id),
251
400
  },
252
401
  });
253
402
  };
254
403
 
404
+ // Domain locale handlers
405
+ const openDomainModal = () => setShowDomainModal(true);
406
+ const closeDomainModal = () => setShowDomainModal(false);
407
+ const saveDomainConfig = (config: DomainLocaleConfig[]) => {
408
+ setSettings({ ...settings, domainLocaleConfig: config });
409
+ setShowDomainModal(false);
410
+ };
411
+
255
412
  if (isLoading) {
256
413
  return (
257
414
  <div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
@@ -279,29 +436,15 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
279
436
  <button
280
437
  onClick={handleSave}
281
438
  disabled={isSaving}
282
- className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${isSaving
283
- ? 'bg-neutral-400 text-white cursor-not-allowed'
284
- : showSuccess
285
- ? 'bg-green-600 text-white'
286
- : 'bg-primary text-white hover:bg-primary/90'
287
- }`}
439
+ className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-primary/20 ${
440
+ isSaving ? 'bg-neutral-400 text-white cursor-not-allowed' :
441
+ showSuccess ? 'bg-green-600 text-white' :
442
+ 'bg-primary text-white hover:bg-primary/90'
443
+ }`}
288
444
  >
289
- {isSaving ? (
290
- <>
291
- <RefreshCw className="w-4 h-4 animate-spin" />
292
- Saving...
293
- </>
294
- ) : showSuccess ? (
295
- <>
296
- <Settings2 className="w-4 h-4" />
297
- Saved!
298
- </>
299
- ) : (
300
- <>
301
- <Save className="w-4 h-4" />
302
- Save Settings
303
- </>
304
- )}
445
+ {isSaving ? <><RefreshCw className="w-4 h-4 animate-spin" /> Saving...</> :
446
+ showSuccess ? <><Settings2 className="w-4 h-4" /> Saved!</> :
447
+ <><Save className="w-4 h-4" /> Save Settings</>}
305
448
  </button>
306
449
  </div>
307
450
 
@@ -316,9 +459,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
316
459
  </div>
317
460
  <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
318
461
  <div className="space-y-2">
319
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
320
- Website Name
321
- </label>
462
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Name</label>
322
463
  <input
323
464
  type="text"
324
465
  value={settings.siteName || ''}
@@ -328,9 +469,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
328
469
  />
329
470
  </div>
330
471
  <div className="space-y-2">
331
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
332
- Website Tagline
333
- </label>
472
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Tagline</label>
334
473
  <input
335
474
  type="text"
336
475
  value={settings.siteTagline || ''}
@@ -341,9 +480,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
341
480
  </div>
342
481
  </div>
343
482
  <div className="mt-6 space-y-2">
344
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
345
- Website Description
346
- </label>
483
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Description</label>
347
484
  <textarea
348
485
  value={settings.siteDescription || ''}
349
486
  onChange={(e) => setSettings({ ...settings, siteDescription: e.target.value })}
@@ -353,9 +490,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
353
490
  />
354
491
  </div>
355
492
  <div className="mt-6 space-y-2">
356
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
357
- Keywords (comma-separated)
358
- </label>
493
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Keywords (comma-separated)</label>
359
494
  <input
360
495
  type="text"
361
496
  value={settings.keywords || ''}
@@ -372,17 +507,13 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
372
507
  <Mail size={20} className="text-primary" />
373
508
  Contact Information
374
509
  </div>
375
-
376
- {/* Language Tabs */}
377
510
  <div className="flex gap-2 mb-6">
378
511
  {AVAILABLE_LOCALES.map((loc) => (
379
512
  <button
380
513
  key={loc.code}
381
514
  onClick={() => setActiveTab(loc.code)}
382
515
  className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
383
- activeTab === loc.code
384
- ? 'bg-primary text-white'
385
- : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
516
+ activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
386
517
  }`}
387
518
  >
388
519
  <span className="mr-1">{loc.flag}</span>
@@ -390,12 +521,9 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
390
521
  </button>
391
522
  ))}
392
523
  </div>
393
-
394
524
  <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
395
525
  <div className="space-y-2">
396
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
397
- Contact Email (global)
398
- </label>
526
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Contact Email (global)</label>
399
527
  <input
400
528
  type="email"
401
529
  value={settings.contactEmail || ''}
@@ -405,9 +533,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
405
533
  />
406
534
  </div>
407
535
  <div className="space-y-2">
408
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
409
- Phone Number ({activeTab.toUpperCase()})
410
- </label>
536
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Phone Number ({activeTab.toUpperCase()})</label>
411
537
  <input
412
538
  type="tel"
413
539
  value={settings.localizedContactInfo?.[activeTab]?.phoneNumber || settings.phoneNumber || ''}
@@ -418,9 +544,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
418
544
  </div>
419
545
  </div>
420
546
  <div className="mt-6 space-y-2">
421
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
422
- Physical Address ({activeTab.toUpperCase()})
423
- </label>
547
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Physical Address ({activeTab.toUpperCase()})</label>
424
548
  <textarea
425
549
  value={settings.localizedContactInfo?.[activeTab]?.physicalAddress || settings.physicalAddress || ''}
426
550
  onChange={(e) => handleUpdateLocalizedContact('physicalAddress', e.target.value)}
@@ -438,24 +562,17 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
438
562
  <Globe size={20} className="text-primary" />
439
563
  Social Links ({activeTab.toUpperCase()})
440
564
  </div>
441
- <button
442
- onClick={handleAddLocalizedSocial}
443
- className="px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all"
444
- >
565
+ <button onClick={handleAddLocalizedSocial} className="px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all">
445
566
  Add Social
446
567
  </button>
447
568
  </div>
448
-
449
- {/* Language Tabs for Socials */}
450
569
  <div className="flex gap-2 mb-6">
451
570
  {AVAILABLE_LOCALES.map((loc) => (
452
571
  <button
453
572
  key={loc.code}
454
573
  onClick={() => setActiveTab(loc.code)}
455
574
  className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
456
- activeTab === loc.code
457
- ? 'bg-primary text-white'
458
- : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
575
+ activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
459
576
  }`}
460
577
  >
461
578
  <span className="mr-1">{loc.flag}</span>
@@ -463,49 +580,43 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
463
580
  </button>
464
581
  ))}
465
582
  </div>
466
-
467
583
  <div className="space-y-4">
468
584
  {(() => {
469
585
  const currentSocials = settings.localizedSocials?.[activeTab] || [];
470
586
  return currentSocials.length > 0 ? (
471
587
  currentSocials.map((social) => {
472
- const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
473
- return (
474
- <div key={social.id} className="flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border">
475
- <div className="flex-shrink-0 text-dashboard-text-secondary">
476
- {platform?.icon || <Globe size={18} />}
588
+ const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
589
+ return (
590
+ <div key={social.id} className="flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border">
591
+ <div className="flex-shrink-0 text-dashboard-text-secondary">
592
+ {platform?.icon || <Globe size={18} />}
593
+ </div>
594
+ <select
595
+ value={social.platform}
596
+ onChange={(e) => handleUpdateLocalizedSocial(social.id, 'platform', e.target.value)}
597
+ className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
598
+ >
599
+ <option value="">Select Platform</option>
600
+ {AVAILABLE_PLATFORMS.map(p => (<option key={p.name} value={p.name}>{p.name}</option>))}
601
+ </select>
602
+ <input
603
+ type="url"
604
+ value={social.url}
605
+ onChange={(e) => handleUpdateLocalizedSocial(social.id, 'url', e.target.value)}
606
+ placeholder="https://..."
607
+ className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
608
+ />
609
+ <button onClick={() => handleRemoveLocalizedSocial(social.id)} className="px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 transition-colors">
610
+ Remove
611
+ </button>
477
612
  </div>
478
- <select
479
- value={social.platform}
480
- onChange={(e) => handleUpdateLocalizedSocial(social.id, 'platform', e.target.value)}
481
- className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
482
- >
483
- <option value="">Select Platform</option>
484
- {AVAILABLE_PLATFORMS.map(p => (
485
- <option key={p.name} value={p.name}>{p.name}</option>
486
- ))}
487
- </select>
488
- <input
489
- type="url"
490
- value={social.url}
491
- onChange={(e) => handleUpdateLocalizedSocial(social.id, 'url', e.target.value)}
492
- placeholder="https://..."
493
- className="flex-1 px-4 py-2 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
494
- />
495
- <button
496
- onClick={() => handleRemoveLocalizedSocial(social.id)}
497
- className="px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors"
498
- >
499
- Remove
500
- </button>
501
- </div>
502
- );
503
- })
504
- ) : (
505
- <p className="text-sm text-dashboard-text-secondary text-center py-8">
506
- No social links added for {activeTab.toUpperCase()}. Click "Add Social" to get started.
507
- </p>
508
- );
613
+ );
614
+ })
615
+ ) : (
616
+ <p className="text-sm text-dashboard-text-secondary text-center py-8">
617
+ No social links added for {activeTab.toUpperCase()}. Click "Add Social" to get started.
618
+ </p>
619
+ );
509
620
  })()}
510
621
  </div>
511
622
  </section>
@@ -521,21 +632,14 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
521
632
  </div>
522
633
  <div className="flex items-center justify-between">
523
634
  <div>
524
- <label className="text-xs font-bold text-dashboard-text block mb-1">
525
- Maintenance Mode
526
- </label>
527
- <p className="text-[10px] text-dashboard-text-secondary">
528
- Show maintenance page to visitors
529
- </p>
635
+ <label className="text-xs font-bold text-dashboard-text block mb-1">Maintenance Mode</label>
636
+ <p className="text-[10px] text-dashboard-text-secondary">Show maintenance page to visitors</p>
530
637
  </div>
531
638
  <button
532
639
  onClick={() => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode })}
533
- className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'
534
- }`}
640
+ className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`}
535
641
  >
536
- <div
537
- className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}`}
538
- />
642
+ <div className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform duration-200 ${settings.maintenanceMode ? 'translate-x-6' : 'translate-x-0'}`} />
539
643
  </button>
540
644
  </div>
541
645
  </section>
@@ -548,75 +652,49 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
548
652
  </div>
549
653
  <div className="space-y-4">
550
654
  <div className="space-y-2">
551
- <label className="text-xs font-bold text-dashboard-text block">
552
- Date
553
- </label>
655
+ <label className="text-xs font-bold text-dashboard-text block">Date</label>
554
656
  <input
555
657
  type="date"
556
658
  value={settings.launch_date ? settings.launch_date.split('T')[0] : ''}
557
659
  onChange={(e) => {
558
660
  const dateValue = e.target.value;
559
- const currentTime = settings.launch_date?.includes('T')
560
- ? settings.launch_date.split('T')[1]
561
- : '00:00';
562
-
563
- const newLaunchDate = dateValue ? `${dateValue}T${currentTime}` : '';
564
-
565
- console.log('[SettingsView] Date changed:', {
566
- dateValue,
567
- currentTime,
568
- newLaunchDate,
569
- });
570
-
571
- setSettings(prev => ({
572
- ...prev,
573
- launch_date: newLaunchDate,
574
- }));
661
+ const currentTime = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00';
662
+ setSettings(prev => ({ ...prev, launch_date: dateValue ? `${dateValue}T${currentTime}` : '' }));
575
663
  }}
576
664
  className="w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
577
665
  />
578
666
  </div>
579
667
  <div className="space-y-2">
580
- <label className="text-xs font-bold text-dashboard-text block">
581
- Time (24-hour format)
582
- </label>
668
+ <label className="text-xs font-bold text-dashboard-text block">Time (24-hour format)</label>
583
669
  <input
584
670
  type="time"
585
671
  step="60"
586
- value={settings.launch_date?.includes('T')
587
- ? settings.launch_date.split('T')[1]
588
- : '00:00'}
672
+ value={settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00'}
589
673
  onChange={(e) => {
590
674
  const timeValue = e.target.value;
591
- const currentDate = settings.launch_date?.includes('T')
592
- ? settings.launch_date.split('T')[0]
593
- : new Date().toISOString().split('T')[0];
594
-
595
- const newLaunchDate = timeValue ? `${currentDate}T${timeValue}` : '';
596
-
597
- console.log('[SettingsView] Time changed:', {
598
- timeValue,
599
- currentDate,
600
- newLaunchDate,
601
- });
602
-
603
- setSettings(prev => ({
604
- ...prev,
605
- launch_date: newLaunchDate,
606
- }));
675
+ const currentDate = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[0] : new Date().toISOString().split('T')[0];
676
+ setSettings(prev => ({ ...prev, launch_date: timeValue ? `${currentDate}T${timeValue}` : '' }));
607
677
  }}
608
678
  className="w-full px-4 py-2 bg-dashboard-card border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary text-dashboard-text"
609
679
  />
610
680
  </div>
611
- <p className="text-[10px] text-dashboard-text-secondary">
612
- Used for countdown timers. Time defaults to 00:00 if not specified.
613
- </p>
681
+ <p className="text-[10px] text-dashboard-text-secondary">Used for countdown timers.</p>
614
682
  </div>
615
683
  </section>
684
+
685
+ {/* Domain Languages Card */}
686
+ <DomainLanguagesCard config={settings.domainLocaleConfig || []} onEdit={openDomainModal} />
616
687
  </div>
617
688
  </div>
618
689
  </div>
690
+
691
+ {/* Domain Languages Modal */}
692
+ <DomainLanguagesModal
693
+ isOpen={showDomainModal}
694
+ config={settings.domainLocaleConfig || []}
695
+ onSave={saveDomainConfig}
696
+ onClose={closeDomainModal}
697
+ />
619
698
  </div>
620
699
  );
621
700
  }
622
-