@jhits/plugin-website 0.0.12 β†’ 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.
@@ -6,11 +6,17 @@
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 } 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
 
14
+ const AVAILABLE_LOCALES: { code: SupportedLocale; name: string; flag: string }[] = [
15
+ { code: 'en', name: 'English', flag: 'πŸ‡¬πŸ‡§' },
16
+ { code: 'sv', name: 'Swedish', flag: 'πŸ‡ΈπŸ‡ͺ' },
17
+ { code: 'nl', name: 'Dutch', flag: 'πŸ‡³πŸ‡±' },
18
+ ];
19
+
14
20
  const AVAILABLE_PLATFORMS = [
15
21
  { name: 'Instagram', icon: <FaInstagram size={18} /> },
16
22
  { name: 'Facebook', icon: <FaFacebook size={18} /> },
@@ -25,10 +31,211 @@ export interface SettingsViewProps {
25
31
  locale: string;
26
32
  }
27
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
+
28
233
  export function SettingsView({ siteId, locale }: SettingsViewProps) {
29
234
  const [isLoading, setIsLoading] = useState(true);
30
235
  const [isSaving, setIsSaving] = useState(false);
31
236
  const [showSuccess, setShowSuccess] = useState(false);
237
+ const [activeTab, setActiveTab] = useState<SupportedLocale>('en');
238
+ const [showDomainModal, setShowDomainModal] = useState(false);
32
239
  const [settings, setSettings] = useState<WebsiteSettings>({
33
240
  identifier: 'site_config',
34
241
  siteName: '',
@@ -42,6 +249,17 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
42
249
  maintenanceMode: false,
43
250
  launch_date: '',
44
251
  socials: [],
252
+ localizedContactInfo: {
253
+ en: { physicalAddress: '', phoneNumber: '' },
254
+ sv: { physicalAddress: '', phoneNumber: '' },
255
+ nl: { physicalAddress: '', phoneNumber: '' },
256
+ },
257
+ localizedSocials: {
258
+ en: [],
259
+ sv: [],
260
+ nl: [],
261
+ },
262
+ domainLocaleConfig: [],
45
263
  });
46
264
 
47
265
  // Fetch settings on load
@@ -49,37 +267,21 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
49
267
  const fetchSettings = async () => {
50
268
  try {
51
269
  setIsLoading(true);
52
- const response = await fetch('/api/plugin-website/settings', {
53
- credentials: 'include',
54
- });
55
- if (!response.ok) {
56
- throw new Error('Failed to fetch settings');
57
- }
270
+ const response = await fetch('/api/plugin-website/settings', { credentials: 'include' });
271
+ if (!response.ok) throw new Error('Failed to fetch settings');
58
272
  const data = await response.json();
273
+
59
274
  if (data.siteName !== undefined) {
60
- // Convert launch_date to datetime-local format if it exists
61
275
  let launchDate = '';
62
276
  if (data.launch_date) {
63
- // If it's a Date object or ISO string, convert to datetime-local format
64
277
  const date = new Date(data.launch_date);
65
278
  if (!isNaN(date.getTime())) {
66
- // Format as YYYY-MM-DDTHH:mm for datetime-local input (24-hour format)
67
279
  const year = date.getFullYear();
68
280
  const month = String(date.getMonth() + 1).padStart(2, '0');
69
281
  const day = String(date.getDate()).padStart(2, '0');
70
282
  const hours = String(date.getHours()).padStart(2, '0');
71
283
  const minutes = String(date.getMinutes()).padStart(2, '0');
72
284
  launchDate = `${year}-${month}-${day}T${hours}:${minutes}`;
73
- } else {
74
- // If it's already a string in the correct format, use it
75
- // Ensure it has time component, default to 00:00 if missing
76
- if (data.launch_date.includes('T') && data.launch_date.split('T')[1]) {
77
- launchDate = data.launch_date;
78
- } else if (data.launch_date.includes('T')) {
79
- launchDate = `${data.launch_date.split('T')[0]}T00:00`;
80
- } else {
81
- launchDate = `${data.launch_date}T00:00`;
82
- }
83
285
  }
84
286
  }
85
287
 
@@ -96,6 +298,13 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
96
298
  maintenanceMode: data.maintenanceMode ?? false,
97
299
  launch_date: launchDate,
98
300
  socials: data.socials || [],
301
+ localizedContactInfo: data.localizedContactInfo || {
302
+ en: { physicalAddress: '', phoneNumber: '' },
303
+ sv: { physicalAddress: '', phoneNumber: '' },
304
+ nl: { physicalAddress: '', phoneNumber: '' },
305
+ },
306
+ localizedSocials: data.localizedSocials || { en: [], sv: [], nl: [] },
307
+ domainLocaleConfig: data.domainLocaleConfig || [],
99
308
  });
100
309
  }
101
310
  } catch (error) {
@@ -109,39 +318,21 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
109
318
 
110
319
  // Use ref to always get latest state
111
320
  const settingsRef = useRef(settings);
112
- useEffect(() => {
113
- settingsRef.current = settings;
114
- }, [settings]);
321
+ useEffect(() => { settingsRef.current = settings; }, [settings]);
115
322
 
116
323
  // Save settings
117
324
  const handleSave = async () => {
118
325
  try {
119
326
  setIsSaving(true);
120
-
121
- // Get the latest state from ref
122
327
  const currentSettings = settingsRef.current;
123
-
124
- // Prepare settings for API - ensure launch_date is properly formatted
125
- const settingsToSave: any = {
126
- ...currentSettings,
127
- };
128
-
129
- // Handle launch_date - keep it if it has a value, otherwise don't include it
328
+ const settingsToSave: any = { ...currentSettings };
329
+
130
330
  if (currentSettings.launch_date && currentSettings.launch_date.trim() !== '') {
131
331
  settingsToSave.launch_date = currentSettings.launch_date;
132
332
  } else {
133
- // Don't include launch_date if it's empty
134
333
  delete settingsToSave.launch_date;
135
334
  }
136
335
 
137
- console.log('[SettingsView] Saving settings:', {
138
- launch_date: settingsToSave.launch_date,
139
- launch_date_type: typeof settingsToSave.launch_date,
140
- has_launch_date: 'launch_date' in settingsToSave,
141
- currentState_launch_date: currentSettings.launch_date,
142
- all_keys: Object.keys(settingsToSave),
143
- });
144
-
145
336
  const response = await fetch('/api/plugin-website/settings', {
146
337
  method: 'POST',
147
338
  headers: { 'Content-Type': 'application/json' },
@@ -164,33 +355,60 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
164
355
  }
165
356
  };
166
357
 
167
- // Add social link
168
- const handleAddSocial = () => {
169
- const newId = settings.socials?.length ? Math.max(...settings.socials.map(s => s.id)) + 1 : 1;
358
+ // Update localized contact info
359
+ const handleUpdateLocalizedContact = (field: 'physicalAddress' | 'phoneNumber', value: string) => {
360
+ setSettings({
361
+ ...settings,
362
+ localizedContactInfo: {
363
+ ...settings.localizedContactInfo,
364
+ [activeTab]: { ...settings.localizedContactInfo?.[activeTab], [field]: value },
365
+ },
366
+ });
367
+ };
368
+
369
+ // Social links handlers
370
+ const handleAddLocalizedSocial = () => {
371
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
372
+ const newId = currentSocials.length ? Math.max(...currentSocials.map(s => s.id)) + 1 : 1;
170
373
  setSettings({
171
374
  ...settings,
172
- socials: [...(settings.socials || []), { id: newId, platform: '', url: '' }],
375
+ localizedSocials: {
376
+ ...settings.localizedSocials,
377
+ [activeTab]: [...currentSocials, { id: newId, platform: '', url: '' }],
378
+ },
173
379
  });
174
380
  };
175
381
 
176
- // Update social link
177
- const handleUpdateSocial = (id: number, field: 'platform' | 'url', value: string) => {
382
+ const handleUpdateLocalizedSocial = (id: number, field: 'platform' | 'url', value: string) => {
383
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
178
384
  setSettings({
179
385
  ...settings,
180
- socials: settings.socials?.map(social =>
181
- social.id === id ? { ...social, [field]: value } : social
182
- ) || [],
386
+ localizedSocials: {
387
+ ...settings.localizedSocials,
388
+ [activeTab]: currentSocials.map(s => s.id === id ? { ...s, [field]: value } : s),
389
+ },
183
390
  });
184
391
  };
185
392
 
186
- // Remove social link
187
- const handleRemoveSocial = (id: number) => {
393
+ const handleRemoveLocalizedSocial = (id: number) => {
394
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
188
395
  setSettings({
189
396
  ...settings,
190
- socials: settings.socials?.filter(social => social.id !== id) || [],
397
+ localizedSocials: {
398
+ ...settings.localizedSocials,
399
+ [activeTab]: currentSocials.filter(s => s.id !== id),
400
+ },
191
401
  });
192
402
  };
193
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
+
194
412
  if (isLoading) {
195
413
  return (
196
414
  <div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
@@ -218,29 +436,15 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
218
436
  <button
219
437
  onClick={handleSave}
220
438
  disabled={isSaving}
221
- 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
222
- ? 'bg-neutral-400 text-white cursor-not-allowed'
223
- : showSuccess
224
- ? 'bg-green-600 text-white'
225
- : 'bg-primary text-white hover:bg-primary/90'
226
- }`}
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
+ }`}
227
444
  >
228
- {isSaving ? (
229
- <>
230
- <RefreshCw className="w-4 h-4 animate-spin" />
231
- Saving...
232
- </>
233
- ) : showSuccess ? (
234
- <>
235
- <Settings2 className="w-4 h-4" />
236
- Saved!
237
- </>
238
- ) : (
239
- <>
240
- <Save className="w-4 h-4" />
241
- Save Settings
242
- </>
243
- )}
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</>}
244
448
  </button>
245
449
  </div>
246
450
 
@@ -255,9 +459,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
255
459
  </div>
256
460
  <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
257
461
  <div className="space-y-2">
258
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
259
- Website Name
260
- </label>
462
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Name</label>
261
463
  <input
262
464
  type="text"
263
465
  value={settings.siteName || ''}
@@ -267,9 +469,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
267
469
  />
268
470
  </div>
269
471
  <div className="space-y-2">
270
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
271
- Website Tagline
272
- </label>
472
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Tagline</label>
273
473
  <input
274
474
  type="text"
275
475
  value={settings.siteTagline || ''}
@@ -280,9 +480,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
280
480
  </div>
281
481
  </div>
282
482
  <div className="mt-6 space-y-2">
283
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
284
- Website Description
285
- </label>
483
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Description</label>
286
484
  <textarea
287
485
  value={settings.siteDescription || ''}
288
486
  onChange={(e) => setSettings({ ...settings, siteDescription: e.target.value })}
@@ -292,9 +490,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
292
490
  />
293
491
  </div>
294
492
  <div className="mt-6 space-y-2">
295
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
296
- Keywords (comma-separated)
297
- </label>
493
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Keywords (comma-separated)</label>
298
494
  <input
299
495
  type="text"
300
496
  value={settings.keywords || ''}
@@ -311,11 +507,23 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
311
507
  <Mail size={20} className="text-primary" />
312
508
  Contact Information
313
509
  </div>
510
+ <div className="flex gap-2 mb-6">
511
+ {AVAILABLE_LOCALES.map((loc) => (
512
+ <button
513
+ key={loc.code}
514
+ onClick={() => setActiveTab(loc.code)}
515
+ className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
516
+ activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
517
+ }`}
518
+ >
519
+ <span className="mr-1">{loc.flag}</span>
520
+ {loc.code.toUpperCase()}
521
+ </button>
522
+ ))}
523
+ </div>
314
524
  <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
315
525
  <div className="space-y-2">
316
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
317
- Contact Email
318
- </label>
526
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Contact Email (global)</label>
319
527
  <input
320
528
  type="email"
321
529
  value={settings.contactEmail || ''}
@@ -325,25 +533,21 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
325
533
  />
326
534
  </div>
327
535
  <div className="space-y-2">
328
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
329
- Phone Number
330
- </label>
536
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Phone Number ({activeTab.toUpperCase()})</label>
331
537
  <input
332
538
  type="tel"
333
- value={settings.phoneNumber || ''}
334
- onChange={(e) => setSettings({ ...settings, phoneNumber: e.target.value })}
539
+ value={settings.localizedContactInfo?.[activeTab]?.phoneNumber || settings.phoneNumber || ''}
540
+ onChange={(e) => handleUpdateLocalizedContact('phoneNumber', e.target.value)}
335
541
  placeholder="+31 6 12345678"
336
542
  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"
337
543
  />
338
544
  </div>
339
545
  </div>
340
546
  <div className="mt-6 space-y-2">
341
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">
342
- Physical Address
343
- </label>
547
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Physical Address ({activeTab.toUpperCase()})</label>
344
548
  <textarea
345
- value={settings.physicalAddress || ''}
346
- onChange={(e) => setSettings({ ...settings, physicalAddress: e.target.value })}
549
+ value={settings.localizedContactInfo?.[activeTab]?.physicalAddress || settings.physicalAddress || ''}
550
+ onChange={(e) => handleUpdateLocalizedContact('physicalAddress', e.target.value)}
347
551
  placeholder="Street address, City, Country"
348
552
  rows={2}
349
553
  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"
@@ -351,60 +555,69 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
351
555
  </div>
352
556
  </section>
353
557
 
354
- {/* Social Links */}
558
+ {/* Social Links (Localized) */}
355
559
  <section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
356
560
  <div className="flex items-center justify-between border-b border-dashboard-border pb-4 mb-6">
357
561
  <div className="flex items-center gap-2 font-bold text-dashboard-text">
358
562
  <Globe size={20} className="text-primary" />
359
- Social Links
563
+ Social Links ({activeTab.toUpperCase()})
360
564
  </div>
361
- <button
362
- onClick={handleAddSocial}
363
- className="px-4 py-2 text-xs font-bold uppercase tracking-widest bg-primary text-white rounded-full hover:bg-primary/90 transition-all"
364
- >
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">
365
566
  Add Social
366
567
  </button>
367
568
  </div>
569
+ <div className="flex gap-2 mb-6">
570
+ {AVAILABLE_LOCALES.map((loc) => (
571
+ <button
572
+ key={loc.code}
573
+ onClick={() => setActiveTab(loc.code)}
574
+ className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
575
+ activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
576
+ }`}
577
+ >
578
+ <span className="mr-1">{loc.flag}</span>
579
+ {loc.code.toUpperCase()}
580
+ </button>
581
+ ))}
582
+ </div>
368
583
  <div className="space-y-4">
369
- {settings.socials && settings.socials.length > 0 ? (
370
- settings.socials.map((social) => {
371
- const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
372
- return (
373
- <div key={social.id} className="flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border">
374
- <div className="flex-shrink-0 text-dashboard-text-secondary">
375
- {platform?.icon || <Globe size={18} />}
584
+ {(() => {
585
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
586
+ return currentSocials.length > 0 ? (
587
+ currentSocials.map((social) => {
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>
376
612
  </div>
377
- <select
378
- value={social.platform}
379
- onChange={(e) => handleUpdateSocial(social.id, 'platform', e.target.value)}
380
- 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"
381
- >
382
- <option value="">Select Platform</option>
383
- {AVAILABLE_PLATFORMS.map(p => (
384
- <option key={p.name} value={p.name}>{p.name}</option>
385
- ))}
386
- </select>
387
- <input
388
- type="url"
389
- value={social.url}
390
- onChange={(e) => handleUpdateSocial(social.id, 'url', e.target.value)}
391
- placeholder="https://..."
392
- 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"
393
- />
394
- <button
395
- onClick={() => handleRemoveSocial(social.id)}
396
- className="px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors"
397
- >
398
- Remove
399
- </button>
400
- </div>
401
- );
402
- })
403
- ) : (
404
- <p className="text-sm text-dashboard-text-secondary text-center py-8">
405
- No social links added yet. Click "Add Social" to get started.
406
- </p>
407
- )}
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
+ );
620
+ })()}
408
621
  </div>
409
622
  </section>
410
623
  </div>
@@ -419,21 +632,14 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
419
632
  </div>
420
633
  <div className="flex items-center justify-between">
421
634
  <div>
422
- <label className="text-xs font-bold text-dashboard-text block mb-1">
423
- Maintenance Mode
424
- </label>
425
- <p className="text-[10px] text-dashboard-text-secondary">
426
- Show maintenance page to visitors
427
- </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>
428
637
  </div>
429
638
  <button
430
639
  onClick={() => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode })}
431
- className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'
432
- }`}
640
+ className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`}
433
641
  >
434
- <div
435
- 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'}`}
436
- />
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'}`} />
437
643
  </button>
438
644
  </div>
439
645
  </section>
@@ -446,75 +652,49 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
446
652
  </div>
447
653
  <div className="space-y-4">
448
654
  <div className="space-y-2">
449
- <label className="text-xs font-bold text-dashboard-text block">
450
- Date
451
- </label>
655
+ <label className="text-xs font-bold text-dashboard-text block">Date</label>
452
656
  <input
453
657
  type="date"
454
658
  value={settings.launch_date ? settings.launch_date.split('T')[0] : ''}
455
659
  onChange={(e) => {
456
660
  const dateValue = e.target.value;
457
- const currentTime = settings.launch_date?.includes('T')
458
- ? settings.launch_date.split('T')[1]
459
- : '00:00';
460
-
461
- const newLaunchDate = dateValue ? `${dateValue}T${currentTime}` : '';
462
-
463
- console.log('[SettingsView] Date changed:', {
464
- dateValue,
465
- currentTime,
466
- newLaunchDate,
467
- });
468
-
469
- setSettings(prev => ({
470
- ...prev,
471
- launch_date: newLaunchDate,
472
- }));
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}` : '' }));
473
663
  }}
474
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"
475
665
  />
476
666
  </div>
477
667
  <div className="space-y-2">
478
- <label className="text-xs font-bold text-dashboard-text block">
479
- Time (24-hour format)
480
- </label>
668
+ <label className="text-xs font-bold text-dashboard-text block">Time (24-hour format)</label>
481
669
  <input
482
670
  type="time"
483
671
  step="60"
484
- value={settings.launch_date?.includes('T')
485
- ? settings.launch_date.split('T')[1]
486
- : '00:00'}
672
+ value={settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00'}
487
673
  onChange={(e) => {
488
674
  const timeValue = e.target.value;
489
- const currentDate = settings.launch_date?.includes('T')
490
- ? settings.launch_date.split('T')[0]
491
- : new Date().toISOString().split('T')[0];
492
-
493
- const newLaunchDate = timeValue ? `${currentDate}T${timeValue}` : '';
494
-
495
- console.log('[SettingsView] Time changed:', {
496
- timeValue,
497
- currentDate,
498
- newLaunchDate,
499
- });
500
-
501
- setSettings(prev => ({
502
- ...prev,
503
- launch_date: newLaunchDate,
504
- }));
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}` : '' }));
505
677
  }}
506
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"
507
679
  />
508
680
  </div>
509
- <p className="text-[10px] text-dashboard-text-secondary">
510
- Used for countdown timers. Time defaults to 00:00 if not specified.
511
- </p>
681
+ <p className="text-[10px] text-dashboard-text-secondary">Used for countdown timers.</p>
512
682
  </div>
513
683
  </section>
684
+
685
+ {/* Domain Languages Card */}
686
+ <DomainLanguagesCard config={settings.domainLocaleConfig || []} onEdit={openDomainModal} />
514
687
  </div>
515
688
  </div>
516
689
  </div>
690
+
691
+ {/* Domain Languages Modal */}
692
+ <DomainLanguagesModal
693
+ isOpen={showDomainModal}
694
+ config={settings.domainLocaleConfig || []}
695
+ onSave={saveDomainConfig}
696
+ onClose={closeDomainModal}
697
+ />
517
698
  </div>
518
699
  );
519
700
  }
520
-