@jhits/plugin-website 0.0.15 → 0.0.17

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.
Files changed (70) hide show
  1. package/dist/131._entry.js +2 -0
  2. package/dist/131._entry.js.LICENSE.txt +6 -0
  3. package/dist/303._entry.js +1 -0
  4. package/dist/_entry.js +2 -0
  5. package/dist/_entry.js.LICENSE.txt +6 -0
  6. package/dist/api/handler.d.ts.map +1 -1
  7. package/dist/api/handler.js +16 -31
  8. package/dist/remoteEntry.js +1 -0
  9. package/dist/server.js +232 -0
  10. package/dist/types/settings.d.ts +18 -11
  11. package/dist/types/settings.d.ts.map +1 -1
  12. package/dist/views/Settings/components/ContactInfoSection.d.ts +12 -0
  13. package/dist/views/Settings/components/ContactInfoSection.d.ts.map +1 -0
  14. package/dist/views/Settings/components/ContactInfoSection.js +14 -0
  15. package/dist/views/Settings/components/DomainLanguagesCard.d.ts +8 -0
  16. package/dist/views/Settings/components/DomainLanguagesCard.d.ts.map +1 -0
  17. package/dist/views/Settings/components/DomainLanguagesCard.js +12 -0
  18. package/dist/views/Settings/components/DomainLanguagesModal.d.ts +12 -0
  19. package/dist/views/Settings/components/DomainLanguagesModal.d.ts.map +1 -0
  20. package/dist/views/Settings/components/DomainLanguagesModal.js +38 -0
  21. package/dist/views/Settings/components/FlagIcon.d.ts +6 -0
  22. package/dist/views/Settings/components/FlagIcon.d.ts.map +1 -0
  23. package/dist/views/Settings/components/FlagIcon.js +11 -0
  24. package/dist/views/Settings/components/LaunchDateSection.d.ts +8 -0
  25. package/dist/views/Settings/components/LaunchDateSection.d.ts.map +1 -0
  26. package/dist/views/Settings/components/LaunchDateSection.js +22 -0
  27. package/dist/views/Settings/components/LocalesManagementCard.d.ts +8 -0
  28. package/dist/views/Settings/components/LocalesManagementCard.d.ts.map +1 -0
  29. package/dist/views/Settings/components/LocalesManagementCard.js +66 -0
  30. package/dist/views/Settings/components/LocalizedContentSection.d.ts +12 -0
  31. package/dist/views/Settings/components/LocalizedContentSection.d.ts.map +1 -0
  32. package/dist/views/Settings/components/LocalizedContentSection.js +17 -0
  33. package/dist/views/Settings/components/SeoIdentitySection.d.ts +9 -0
  34. package/dist/views/Settings/components/SeoIdentitySection.d.ts.map +1 -0
  35. package/dist/views/Settings/components/SeoIdentitySection.js +12 -0
  36. package/dist/views/Settings/components/SocialLinksSection.d.ts +13 -0
  37. package/dist/views/Settings/components/SocialLinksSection.d.ts.map +1 -0
  38. package/dist/views/Settings/components/SocialLinksSection.js +13 -0
  39. package/dist/views/Settings/components/StatusSection.d.ts +8 -0
  40. package/dist/views/Settings/components/StatusSection.d.ts.map +1 -0
  41. package/dist/views/Settings/components/StatusSection.js +27 -0
  42. package/dist/views/Settings/constants.d.ts +12 -0
  43. package/dist/views/Settings/constants.d.ts.map +1 -0
  44. package/dist/views/Settings/constants.js +23 -0
  45. package/dist/views/Settings/hooks/useWebsiteSettings.d.ts +24 -0
  46. package/dist/views/Settings/hooks/useWebsiteSettings.d.ts.map +1 -0
  47. package/dist/views/Settings/hooks/useWebsiteSettings.js +217 -0
  48. package/dist/views/Settings/types.d.ts +5 -0
  49. package/dist/views/Settings/types.d.ts.map +1 -0
  50. package/dist/views/Settings/types.js +1 -0
  51. package/dist/views/SettingsView.d.ts +3 -6
  52. package/dist/views/SettingsView.d.ts.map +1 -1
  53. package/dist/views/SettingsView.js +59 -262
  54. package/package.json +14 -10
  55. package/src/api/handler.ts +42 -34
  56. package/src/types/settings.ts +28 -12
  57. package/src/views/Settings/components/ContactInfoSection.tsx +109 -0
  58. package/src/views/Settings/components/DomainLanguagesCard.tsx +91 -0
  59. package/src/views/Settings/components/DomainLanguagesModal.tsx +216 -0
  60. package/src/views/Settings/components/FlagIcon.tsx +20 -0
  61. package/src/views/Settings/components/LaunchDateSection.tsx +111 -0
  62. package/src/views/Settings/components/LocalesManagementCard.tsx +198 -0
  63. package/src/views/Settings/components/LocalizedContentSection.tsx +155 -0
  64. package/src/views/Settings/components/SeoIdentitySection.tsx +98 -0
  65. package/src/views/Settings/components/SocialLinksSection.tsx +98 -0
  66. package/src/views/Settings/components/StatusSection.tsx +181 -0
  67. package/src/views/Settings/constants.ts +26 -0
  68. package/src/views/Settings/hooks/useWebsiteSettings.ts +240 -0
  69. package/src/views/Settings/types.ts +4 -0
  70. package/src/views/SettingsView.tsx +226 -674
@@ -1,726 +1,278 @@
1
1
  /**
2
2
  * Website Settings View
3
- * Main interface for managing website settings
3
+ * Refactored modular interface with sidebar navigation
4
4
  */
5
5
 
6
6
  'use client';
7
7
 
8
- import React, { useState, useEffect, useRef } from 'react';
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
- import { FaFacebook, FaInstagram, FaLinkedin, FaPinterest, FaTiktok } from 'react-icons/fa';
12
- import { FaXTwitter } from 'react-icons/fa6';
8
+ import React, { useState } from 'react';
9
+ import { Save, RefreshCw, Settings2, Globe, Search, Mail, Check, Lock as LockIcon } from 'lucide-react';
10
+ import { SettingsViewProps } from './Settings/types';
11
+ import { useWebsiteSettings } from './Settings/hooks/useWebsiteSettings';
12
+ import { FlagIcon } from './Settings/components/FlagIcon';
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
-
20
- const AVAILABLE_PLATFORMS = [
21
- { name: 'Instagram', icon: <FaInstagram size={18} /> },
22
- { name: 'Facebook', icon: <FaFacebook size={18} /> },
23
- { name: 'Twitter', icon: <FaXTwitter size={18} /> },
24
- { name: 'Pinterest', icon: <FaPinterest size={18} /> },
25
- { name: 'LinkedIn', icon: <FaLinkedin size={18} /> },
26
- { name: 'TikTok', icon: <FaTiktok size={18} /> },
27
- ];
28
-
29
- export interface SettingsViewProps {
30
- siteId: string;
31
- locale: string;
32
- }
33
-
34
- // =============================================================================
35
14
  // Sub-components
36
- // =============================================================================
15
+ import { SeoIdentitySection } from './Settings/components/SeoIdentitySection';
16
+ import { LocalizedContentSection } from './Settings/components/LocalizedContentSection';
17
+ import { LocalesManagementCard } from './Settings/components/LocalesManagementCard';
18
+ import { StatusSection } from './Settings/components/StatusSection';
19
+ import { LaunchDateSection } from './Settings/components/LaunchDateSection';
20
+ import { DomainLanguagesCard } from './Settings/components/DomainLanguagesCard';
21
+ import { DomainLanguagesModal } from './Settings/components/DomainLanguagesModal';
22
+
23
+ type SettingsTab = 'general' | 'localization' | 'seo' | 'contact';
24
+
25
+ export function SettingsView({ siteId }: SettingsViewProps) {
26
+ const [currentTab, setCurrentTab] = useState<SettingsTab>('general');
27
+
28
+ const {
29
+ settings,
30
+ isLoading,
31
+ isSaving,
32
+ isDirty,
33
+ showSuccess,
34
+ activeTab,
35
+ setActiveTab,
36
+ activeLocales,
37
+ showDomainModal,
38
+ setShowDomainModal,
39
+ handleUpdateSettings,
40
+ handleUpdateLocalizedIdentity,
41
+ handleUpdateLocalizedContact,
42
+ handleAddLocalizedSocial,
43
+ handleUpdateLocalizedSocial,
44
+ handleRemoveLocalizedSocial,
45
+ handleUpdateLocales,
46
+ handleSave,
47
+ handleReset,
48
+ saveDomainConfigAndClose
49
+ } = useWebsiteSettings(siteId);
37
50
 
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
51
+ if (isLoading) {
52
+ return (
53
+ <div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
54
+ <div className="text-center">
55
+ <RefreshCw className="w-8 h-8 animate-spin text-primary mx-auto mb-4" />
56
+ <p className="text-sm text-dashboard-text-secondary">Loading settings...</p>
50
57
  </div>
51
58
  </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
- onSaveAndClose: (config: DomainLocaleConfig[]) => void;
89
- isSaving?: boolean;
90
- }
91
-
92
- function DomainLanguagesModal({ isOpen, config, onSave, onClose, onSaveAndClose, isSaving = false }: DomainLanguagesModalProps) {
93
- const [tempConfig, setTempConfig] = useState<DomainLocaleConfig[]>(config);
94
- const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
95
-
96
- useEffect(() => {
97
- setTempConfig(config);
98
- setExpandedIndex(null);
99
- }, [config, isOpen]);
100
-
101
- if (!isOpen) return null;
102
-
103
- const handleAdd = () => {
104
- setTempConfig([...tempConfig, { domain: '', locale: 'en', allowUserOverride: true }]);
105
- setExpandedIndex(tempConfig.length);
106
- };
59
+ );
60
+ }
107
61
 
108
- const handleUpdate = (index: number, field: keyof DomainLocaleConfig, value: any) => {
109
- const updated = [...tempConfig];
110
- updated[index] = { ...updated[index], [field]: value };
111
- setTempConfig(updated);
112
- };
62
+ const hasLocales = settings.supportedLocales && settings.supportedLocales.length > 0;
113
63
 
114
- const handleRemove = (index: number) => {
115
- setTempConfig(tempConfig.filter((_, i) => i !== index));
116
- if (expandedIndex === index) setExpandedIndex(null);
117
- };
64
+ const navigationItems = [
65
+ { id: 'general', label: 'General', icon: Settings2, description: 'Status & Launch Date', localized: false },
66
+ { id: 'localization', label: 'Localization', icon: Globe, description: 'Languages & Domains', localized: false },
67
+ { id: 'seo', label: 'Identity & SEO', icon: Search, description: 'Site Name & Meta', localized: true },
68
+ { id: 'contact', label: 'Contact & Social', icon: Mail, description: 'Address & Links', localized: true },
69
+ ];
118
70
 
119
- const toggleExpand = (index: number) => {
120
- setExpandedIndex(expandedIndex === index ? null : index);
121
- };
122
-
123
- return (
124
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
125
- <div className="bg-dashboard-bg rounded-3xl border border-dashboard-border w-full max-w-lg max-h-[80vh] overflow-hidden flex flex-col">
126
- <div className="flex items-center justify-between p-6 border-b border-dashboard-border">
127
- <h2 className="text-xl font-bold text-dashboard-text">Domain Languages</h2>
128
- <button onClick={onClose} className="p-2 text-dashboard-text-secondary hover:text-dashboard-text transition-colors">
129
- <X size={20} />
130
- </button>
71
+ const renderMissingLocalesState = () => (
72
+ <div className="h-full flex items-center justify-center p-12">
73
+ <div className="max-w-md w-full text-center space-y-8">
74
+ <div className="size-24 bg-primary/15 rounded-[2rem] flex items-center justify-center mx-auto shadow-2xl shadow-primary/10 border border-primary/30">
75
+ <Globe size={48} className="text-primary animate-pulse" />
131
76
  </div>
132
- <div className="flex-1 overflow-y-auto p-6 space-y-4">
133
- {/* Existing entries - collapsed by default */}
134
- {tempConfig.length > 0 && (
135
- <div className="space-y-2">
136
- <p className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider">Current Config</p>
137
- {tempConfig.map((entry, index) => (
138
- <div key={index} className="bg-dashboard-card rounded-xl border border-dashboard-border overflow-hidden">
139
- <div
140
- onClick={() => toggleExpand(index)}
141
- className="w-full flex items-center justify-between p-3 text-left hover:bg-dashboard-border/50 transition-colors cursor-pointer"
142
- >
143
- <div className="flex items-center gap-2">
144
- <span className="font-medium text-dashboard-text">{entry.domain || '(no domain)'}</span>
145
- <span className="text-dashboard-text-secondary">→</span>
146
- <span>{AVAILABLE_LOCALES.find(l => l.code === entry.locale)?.flag}</span>
147
- {entry.allowUserOverride === false && <span className="text-dashboard-text-secondary">🔒</span>}
148
- </div>
149
- <div className="flex items-center gap-2">
150
- <button
151
- onClick={(e) => { e.stopPropagation(); handleRemove(index); }}
152
- className="p-1 text-red-500 hover:bg-red-500/10 rounded transition-colors"
153
- title="Remove"
154
- >
155
- <Trash2 size={14} />
156
- </button>
157
- <span className="text-dashboard-text-secondary text-xs">
158
- {expandedIndex === index ? '▲' : '▼'}
159
- </span>
160
- </div>
161
- </div>
162
- {expandedIndex === index && (
163
- <div className="p-3 pt-0 space-y-3 border-t border-dashboard-border">
164
- <div className="space-y-1">
165
- <label className="text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider">Domain</label>
166
- <input
167
- type="text"
168
- value={entry.domain}
169
- onChange={(e) => handleUpdate(index, 'domain', e.target.value)}
170
- placeholder="example.com"
171
- 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"
172
- />
173
- </div>
174
- <div className="flex items-center gap-2">
175
- <div className="flex-1 space-y-1">
176
- <label className="text-[10px] font-bold text-dashboard-text-secondary uppercase tracking-wider">Language</label>
177
- <select
178
- value={entry.locale}
179
- onChange={(e) => handleUpdate(index, 'locale', e.target.value)}
180
- 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"
181
- >
182
- {AVAILABLE_LOCALES.map(loc => (<option key={loc.code} value={loc.code}>{loc.flag} {loc.name}</option>))}
183
- </select>
184
- </div>
185
- </div>
186
- <div className="flex items-center justify-between">
187
- <label className="text-[10px] text-dashboard-text-secondary">Allow user to change</label>
188
- <button
189
- onClick={() => handleUpdate(index, 'allowUserOverride', !entry.allowUserOverride)}
190
- className={`relative w-10 h-5 rounded-full transition-colors ${entry.allowUserOverride ? 'bg-primary' : 'bg-neutral-300 dark:bg-neutral-600'}`}
191
- >
192
- <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'}`} />
193
- </button>
194
- </div>
195
- </div>
196
- )}
197
- </div>
198
- ))}
199
- </div>
200
- )}
201
-
202
- {/* Add new domain - always visible */}
203
- <div className="border-t border-dashboard-border pt-4">
204
- <p className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-wider mb-3">Add New Domain</p>
205
- <button
206
- onClick={handleAdd}
207
- 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"
208
- >
209
- <Plus size={16} />
210
- Add Domain
211
- </button>
212
- </div>
213
-
214
- <p className="text-[10px] text-dashboard-text-secondary text-center pt-2">
215
- Configure which language to use per domain. E.g., botanicsandyou.nl → Dutch, botanicsandyou.se → Swedish.
77
+ <div className="space-y-4">
78
+ <h3 className="text-3xl font-bold text-dashboard-text tracking-tight">Configuration Required</h3>
79
+ <p className="text-sm text-dashboard-text-secondary font-medium leading-relaxed opacity-80">
80
+ To manage localized content like site names and contact info, you first need to define which languages your website supports.
216
81
  </p>
217
82
  </div>
218
- <div className="flex gap-3 p-6 border-t border-dashboard-border">
219
- <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">
220
- Cancel
221
- </button>
222
- <button
223
- onClick={() => onSaveAndClose(tempConfig)}
224
- disabled={isSaving}
225
- className="flex-1 px-4 py-2 bg-primary text-white rounded-xl text-sm font-bold hover:bg-primary/90 transition-colors disabled:opacity-50"
226
- >
227
- {isSaving ? 'Saving...' : 'Save'}
228
- </button>
229
- </div>
83
+ <button
84
+ onClick={() => setCurrentTab('localization')}
85
+ className="inline-flex items-center gap-3 px-8 py-4 bg-primary text-white rounded-2xl text-xs font-bold uppercase tracking-wider hover:bg-primary/90 transition-all shadow-lg shadow-primary/25 active:scale-95"
86
+ >
87
+ Setup Languages Now
88
+ </button>
230
89
  </div>
231
90
  </div>
232
91
  );
233
- }
234
-
235
- // =============================================================================
236
- // Main Component
237
- // =============================================================================
238
-
239
- export function SettingsView({ siteId, locale }: SettingsViewProps) {
240
- const [isLoading, setIsLoading] = useState(true);
241
- const [isSaving, setIsSaving] = useState(false);
242
- const [showSuccess, setShowSuccess] = useState(false);
243
- const [activeTab, setActiveTab] = useState<SupportedLocale>('en');
244
- const [showDomainModal, setShowDomainModal] = useState(false);
245
- const [settings, setSettings] = useState<WebsiteSettings>({
246
- identifier: 'site_config',
247
- siteName: '',
248
- siteTagline: '',
249
- siteDescription: '',
250
- keywords: '',
251
- contactEmail: '',
252
- phoneNumber: '',
253
- physicalAddress: '',
254
- smtpUser: '',
255
- maintenanceMode: false,
256
- launch_date: '',
257
- socials: [],
258
- localizedContactInfo: {
259
- en: { physicalAddress: '', phoneNumber: '' },
260
- sv: { physicalAddress: '', phoneNumber: '' },
261
- nl: { physicalAddress: '', phoneNumber: '' },
262
- },
263
- localizedSocials: {
264
- en: [],
265
- sv: [],
266
- nl: [],
267
- },
268
- domainLocaleConfig: [],
269
- });
270
92
 
271
- // Fetch settings on load
272
- useEffect(() => {
273
- const fetchSettings = async () => {
274
- try {
275
- setIsLoading(true);
276
- const response = await fetch('/api/plugin-website/settings', { credentials: 'include' });
277
- if (!response.ok) throw new Error('Failed to fetch settings');
278
- const data = await response.json();
279
-
280
- if (data.siteName !== undefined) {
281
- let launchDate = '';
282
- if (data.launch_date) {
283
- const date = new Date(data.launch_date);
284
- if (!isNaN(date.getTime())) {
285
- const year = date.getFullYear();
286
- const month = String(date.getMonth() + 1).padStart(2, '0');
287
- const day = String(date.getDate()).padStart(2, '0');
288
- const hours = String(date.getHours()).padStart(2, '0');
289
- const minutes = String(date.getMinutes()).padStart(2, '0');
290
- launchDate = `${year}-${month}-${day}T${hours}:${minutes}`;
291
- }
292
- }
293
-
294
- setSettings({
295
- identifier: 'site_config',
296
- siteName: data.siteName || '',
297
- siteTagline: data.siteTagline || '',
298
- siteDescription: data.siteDescription || '',
299
- keywords: data.keywords || '',
300
- contactEmail: data.contactEmail || '',
301
- phoneNumber: data.phoneNumber || '',
302
- physicalAddress: data.physicalAddress || '',
303
- smtpUser: data.smtpUser || '',
304
- maintenanceMode: data.maintenanceMode ?? false,
305
- launch_date: launchDate,
306
- socials: data.socials || [],
307
- localizedContactInfo: data.localizedContactInfo || {
308
- en: { physicalAddress: '', phoneNumber: '' },
309
- sv: { physicalAddress: '', phoneNumber: '' },
310
- nl: { physicalAddress: '', phoneNumber: '' },
311
- },
312
- localizedSocials: data.localizedSocials || { en: [], sv: [], nl: [] },
313
- domainLocaleConfig: data.domainLocaleConfig || [],
314
- });
315
- }
316
- } catch (error) {
317
- console.error('Failed to load settings:', error);
318
- } finally {
319
- setIsLoading(false);
320
- }
321
- };
322
- fetchSettings();
323
- }, []);
324
-
325
- // Use ref to always get latest state
326
- const settingsRef = useRef(settings);
327
- useEffect(() => { settingsRef.current = settings; }, [settings]);
328
-
329
- // Save settings
330
- const handleSave = async () => {
331
- try {
332
- setIsSaving(true);
333
- const currentSettings = settingsRef.current;
334
- const settingsToSave: any = { ...currentSettings };
335
-
336
- if (currentSettings.launch_date && currentSettings.launch_date.trim() !== '') {
337
- settingsToSave.launch_date = currentSettings.launch_date;
338
- } else {
339
- delete settingsToSave.launch_date;
340
- }
341
-
342
- const response = await fetch('/api/plugin-website/settings', {
343
- method: 'POST',
344
- headers: { 'Content-Type': 'application/json' },
345
- credentials: 'include',
346
- body: JSON.stringify(settingsToSave),
347
- });
348
-
349
- if (!response.ok) {
350
- const error = await response.json();
351
- throw new Error(error.error || 'Failed to save settings');
352
- }
353
-
354
- setShowSuccess(true);
355
- setTimeout(() => setShowSuccess(false), 3000);
356
- } catch (error: any) {
357
- console.error('Failed to save settings:', error);
358
- alert(error.message || 'Failed to save settings');
359
- } finally {
360
- setIsSaving(false);
93
+ const renderContent = () => {
94
+ // If trying to access localized tab without locales, show prompt
95
+ const activeItem = navigationItems.find(i => i.id === currentTab);
96
+ if (activeItem?.localized && !hasLocales) {
97
+ return renderMissingLocalesState();
361
98
  }
362
- };
363
99
 
364
- // Update localized contact info
365
- const handleUpdateLocalizedContact = (field: 'physicalAddress' | 'phoneNumber', value: string) => {
366
- setSettings({
367
- ...settings,
368
- localizedContactInfo: {
369
- ...settings.localizedContactInfo,
370
- [activeTab]: { ...settings.localizedContactInfo?.[activeTab], [field]: value },
371
- },
372
- });
373
- };
374
-
375
- // Social links handlers
376
- const handleAddLocalizedSocial = () => {
377
- const currentSocials = settings.localizedSocials?.[activeTab] || [];
378
- const newId = currentSocials.length ? Math.max(...currentSocials.map(s => s.id)) + 1 : 1;
379
- setSettings({
380
- ...settings,
381
- localizedSocials: {
382
- ...settings.localizedSocials,
383
- [activeTab]: [...currentSocials, { id: newId, platform: '', url: '' }],
384
- },
385
- });
386
- };
387
-
388
- const handleUpdateLocalizedSocial = (id: number, field: 'platform' | 'url', value: string) => {
389
- const currentSocials = settings.localizedSocials?.[activeTab] || [];
390
- setSettings({
391
- ...settings,
392
- localizedSocials: {
393
- ...settings.localizedSocials,
394
- [activeTab]: currentSocials.map(s => s.id === id ? { ...s, [field]: value } : s),
395
- },
396
- });
397
- };
398
-
399
- const handleRemoveLocalizedSocial = (id: number) => {
400
- const currentSocials = settings.localizedSocials?.[activeTab] || [];
401
- setSettings({
402
- ...settings,
403
- localizedSocials: {
404
- ...settings.localizedSocials,
405
- [activeTab]: currentSocials.filter(s => s.id !== id),
406
- },
407
- });
408
- };
409
-
410
- // Domain locale handlers
411
- const openDomainModal = () => setShowDomainModal(true);
412
- const closeDomainModal = () => setShowDomainModal(false);
413
- const saveDomainConfig = (config: DomainLocaleConfig[]) => {
414
- setSettings({ ...settings, domainLocaleConfig: config });
415
- setShowDomainModal(false);
416
- };
417
- const saveDomainConfigAndClose = async (config: DomainLocaleConfig[]) => {
418
- setSettings({ ...settings, domainLocaleConfig: config });
419
- setShowDomainModal(false);
420
-
421
- // Immediately save to API
422
- try {
423
- const response = await fetch('/api/plugin-website/settings', {
424
- method: 'POST',
425
- headers: { 'Content-Type': 'application/json' },
426
- credentials: 'include',
427
- body: JSON.stringify({ ...settings, domainLocaleConfig: config }),
428
- });
429
- if (response.ok) {
430
- setShowSuccess(true);
431
- setTimeout(() => setShowSuccess(false), 3000);
432
- }
433
- } catch (error) {
434
- console.error('Failed to save domain config:', error);
100
+ switch (currentTab) {
101
+ case 'general':
102
+ return (
103
+ <div className="space-y-10 max-w-3xl">
104
+ <StatusSection settings={settings} onUpdate={handleUpdateSettings} />
105
+ <LaunchDateSection settings={settings} onUpdate={handleUpdateSettings} />
106
+ </div>
107
+ );
108
+ case 'localization':
109
+ return (
110
+ <div className="space-y-10 max-w-3xl">
111
+ <LocalesManagementCard supportedLocales={settings.supportedLocales} onUpdate={handleUpdateLocales} />
112
+ <DomainLanguagesCard config={settings.domainLocaleConfig || []} onEdit={() => setShowDomainModal(true)} />
113
+ </div>
114
+ );
115
+ case 'seo':
116
+ return (
117
+ <div className="max-w-3xl">
118
+ <SeoIdentitySection
119
+ settings={settings}
120
+ activeTab={activeTab}
121
+ onUpdateLocalized={handleUpdateLocalizedIdentity}
122
+ />
123
+ </div>
124
+ );
125
+ case 'contact':
126
+ return (
127
+ <div className="max-w-3xl">
128
+ <LocalizedContentSection
129
+ settings={settings}
130
+ activeTab={activeTab}
131
+ onUpdateContact={handleUpdateLocalizedContact}
132
+ onAddSocial={handleAddLocalizedSocial}
133
+ onUpdateSocial={handleUpdateLocalizedSocial}
134
+ onRemoveSocial={handleRemoveLocalizedSocial}
135
+ />
136
+ </div>
137
+ );
138
+ default:
139
+ return null;
435
140
  }
436
141
  };
437
142
 
438
- if (isLoading) {
439
- return (
440
- <div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
441
- <div className="text-center">
442
- <RefreshCw className="w-8 h-8 animate-spin text-primary mx-auto mb-4" />
443
- <p className="text-sm text-dashboard-text-secondary">Loading settings...</p>
444
- </div>
445
- </div>
446
- );
447
- }
448
-
449
143
  return (
450
- <div className="h-full w-full rounded-[2.5rem] bg-dashboard-card p-8 overflow-y-auto">
451
- <div className="max-w-6xl mx-auto">
452
- {/* Header */}
453
- <div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
454
- <div>
455
- <h1 className="text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2">
456
- Website Settings
457
- </h1>
458
- <p className="text-sm text-dashboard-text-secondary">
459
- Manage your website identity, contact information, and social links
144
+ <div className="absolute inset-0 w-full flex overflow-hidden bg-dashboard-bg/50">
145
+ {/* Sidebar Navigation */}
146
+ <aside className="w-80 bg-dashboard-card/40 backdrop-blur-2xl border-r border-dashboard-border/60 flex flex-col p-6 flex-shrink-0 h-full">
147
+ <div className="mb-12 px-2 shrink-0 pt-8">
148
+ <h1 className="text-3xl font-bold text-dashboard-text tracking-tight flex items-center gap-3">
149
+ <div className="size-8 bg-primary rounded-lg flex items-center justify-center shadow-lg shadow-primary/20">
150
+ <Settings2 size={18} className="text-white" />
151
+ </div>
152
+ Settings
153
+ </h1>
154
+ <div className="flex items-center gap-2 mt-3 ml-11">
155
+ <p className="text-[10px] font-bold text-primary/80 uppercase tracking-widest">
156
+ Website Engine
460
157
  </p>
158
+ {isDirty && (
159
+ <span className="flex size-2 rounded-full bg-amber-500 animate-pulse shadow-[0_0_8px_rgba(245,158,11,0.5)]" title="Unsaved changes" />
160
+ )}
461
161
  </div>
162
+ </div>
163
+
164
+ <nav className="space-y-2 flex-1 overflow-y-auto custom-scrollbar pr-2">
165
+ {navigationItems.map((item) => {
166
+ const Icon = item.icon;
167
+ const isActive = currentTab === item.id;
168
+ const isLocked = item.localized && !hasLocales;
169
+
170
+ return (
171
+ <button
172
+ key={item.id}
173
+ onClick={() => !isLocked && setCurrentTab(item.id as SettingsTab)}
174
+ className={`w-full flex items-center gap-4 p-4 rounded-2xl transition-all text-left group relative ${isActive
175
+ ? 'bg-primary text-white shadow-xl shadow-primary/25 border border-primary/20'
176
+ : 'hover:bg-primary/8 text-dashboard-text-secondary hover:text-primary border border-transparent'
177
+ } ${isLocked ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}`}
178
+ >
179
+ <div className={`flex items-center justify-center shrink-0 transition-transform duration-300 ${isActive ? 'scale-110' : 'group-hover:scale-110'}`}>
180
+ <Icon size={20} className={isActive ? 'text-white' : 'text-primary/60 group-hover:text-primary'} />
181
+ </div>
182
+ <div className="flex-1 min-w-0">
183
+ <div className="flex items-center justify-between">
184
+ <span className={`block text-sm font-semibold tracking-tight truncate ${isActive ? 'text-white' : 'text-dashboard-text'}`}>
185
+ {item.label}
186
+ </span>
187
+ {isLocked && <LockIcon size={12} className="text-dashboard-text-secondary opacity-50" />}
188
+ </div>
189
+ <span className={`text-[10px] font-medium opacity-70 truncate block mt-0.5 ${isActive ? 'text-white/80' : 'text-dashboard-text-secondary'}`}>
190
+ {item.description}
191
+ </span>
192
+ </div>
193
+ </button>
194
+ );
195
+ })}
196
+ </nav>
197
+
198
+ <div className="mt-auto space-y-4 pt-8 border-t border-dashboard-border/40">
199
+ {isDirty && (
200
+ <button
201
+ onClick={handleReset}
202
+ className="w-full py-3 rounded-xl text-xs font-bold text-dashboard-text-secondary hover:text-dashboard-text hover:bg-dashboard-card/60 transition-all active:scale-[0.98]"
203
+ >
204
+ Discard Changes
205
+ </button>
206
+ )}
462
207
  <button
463
208
  onClick={handleSave}
464
209
  disabled={isSaving}
465
- 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 ${
466
- isSaving ? 'bg-neutral-400 text-white cursor-not-allowed' :
467
- showSuccess ? 'bg-green-600 text-white' :
468
- 'bg-primary text-white hover:bg-primary/90'
469
- }`}
210
+ className={`w-full flex items-center justify-center gap-3 py-4 rounded-2xl text-sm font-bold transition-all active:scale-[0.98] ${isSaving ? 'bg-neutral-200 dark:bg-neutral-800 text-neutral-400 cursor-not-allowed' :
211
+ showSuccess ? 'bg-emerald-500 text-white shadow-lg shadow-emerald-500/20' :
212
+ isDirty ? 'bg-primary text-white hover:bg-primary/90 shadow-xl shadow-primary/30' :
213
+ 'bg-neutral-100 dark:bg-neutral-800/40 text-dashboard-text-secondary opacity-40 cursor-not-allowed'
214
+ }`}
470
215
  >
471
- {isSaving ? <><RefreshCw className="w-4 h-4 animate-spin" /> Saving...</> :
472
- showSuccess ? <><Settings2 className="w-4 h-4" /> Saved!</> :
473
- <><Save className="w-4 h-4" /> Save Settings</>}
216
+ {isSaving ? <RefreshCw className="w-4 h-4 animate-spin" /> :
217
+ showSuccess ? <Check className="w-4 h-4" /> :
218
+ <Save className="w-4 h-4" />}
219
+ {isSaving ? 'Saving...' : showSuccess ? 'Saved!' : 'Save Changes'}
474
220
  </button>
475
221
  </div>
222
+ </aside>
476
223
 
477
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
478
- {/* Main Content */}
479
- <div className="lg:col-span-2 space-y-8">
480
- {/* SEO & Website Identity */}
481
- <section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
482
- <div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
483
- <Search size={20} className="text-primary" />
484
- Website Identity & SEO
485
- </div>
486
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
487
- <div className="space-y-2">
488
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Name</label>
489
- <input
490
- type="text"
491
- value={settings.siteName || ''}
492
- onChange={(e) => setSettings({ ...settings, siteName: e.target.value })}
493
- placeholder="e.g., Botanics & You"
494
- 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"
495
- />
496
- </div>
497
- <div className="space-y-2">
498
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Tagline</label>
499
- <input
500
- type="text"
501
- value={settings.siteTagline || ''}
502
- onChange={(e) => setSettings({ ...settings, siteTagline: e.target.value })}
503
- placeholder="e.g., Sharing my knowledge with you"
504
- 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"
505
- />
506
- </div>
507
- </div>
508
- <div className="mt-6 space-y-2">
509
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Website Description</label>
510
- <textarea
511
- value={settings.siteDescription || ''}
512
- onChange={(e) => setSettings({ ...settings, siteDescription: e.target.value })}
513
- placeholder="A brief description of your website"
514
- rows={3}
515
- 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"
516
- />
517
- </div>
518
- <div className="mt-6 space-y-2">
519
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Keywords (comma-separated)</label>
520
- <input
521
- type="text"
522
- value={settings.keywords || ''}
523
- onChange={(e) => setSettings({ ...settings, keywords: e.target.value })}
524
- placeholder="keyword1, keyword2, keyword3"
525
- 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"
526
- />
527
- </div>
528
- </section>
529
-
530
- {/* Contact Information */}
531
- <section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
532
- <div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
533
- <Mail size={20} className="text-primary" />
534
- Contact Information
535
- </div>
536
- <div className="flex gap-2 mb-6">
537
- {AVAILABLE_LOCALES.map((loc) => (
538
- <button
539
- key={loc.code}
540
- onClick={() => setActiveTab(loc.code)}
541
- className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
542
- activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
543
- }`}
544
- >
545
- <span className="mr-1">{loc.flag}</span>
546
- {loc.code.toUpperCase()}
547
- </button>
548
- ))}
549
- </div>
550
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
551
- <div className="space-y-2">
552
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Contact Email (global)</label>
553
- <input
554
- type="email"
555
- value={settings.contactEmail || ''}
556
- onChange={(e) => setSettings({ ...settings, contactEmail: e.target.value })}
557
- placeholder="contact@example.com"
558
- 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"
559
- />
560
- </div>
561
- <div className="space-y-2">
562
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Phone Number ({activeTab.toUpperCase()})</label>
563
- <input
564
- type="tel"
565
- value={settings.localizedContactInfo?.[activeTab]?.phoneNumber || settings.phoneNumber || ''}
566
- onChange={(e) => handleUpdateLocalizedContact('phoneNumber', e.target.value)}
567
- placeholder="+31 6 12345678"
568
- 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"
569
- />
570
- </div>
571
- </div>
572
- <div className="mt-6 space-y-2">
573
- <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest">Physical Address ({activeTab.toUpperCase()})</label>
574
- <textarea
575
- value={settings.localizedContactInfo?.[activeTab]?.physicalAddress || settings.physicalAddress || ''}
576
- onChange={(e) => handleUpdateLocalizedContact('physicalAddress', e.target.value)}
577
- placeholder="Street address, City, Country"
578
- rows={2}
579
- 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"
580
- />
581
- </div>
582
- </section>
583
-
584
- {/* Social Links (Localized) */}
585
- <section className="bg-dashboard-bg p-8 rounded-3xl border border-dashboard-border">
586
- <div className="flex items-center justify-between border-b border-dashboard-border pb-4 mb-6">
587
- <div className="flex items-center gap-2 font-bold text-dashboard-text">
588
- <Globe size={20} className="text-primary" />
589
- Social Links ({activeTab.toUpperCase()})
590
- </div>
591
- <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">
592
- Add Social
593
- </button>
594
- </div>
595
- <div className="flex gap-2 mb-6">
596
- {AVAILABLE_LOCALES.map((loc) => (
597
- <button
598
- key={loc.code}
599
- onClick={() => setActiveTab(loc.code)}
600
- className={`px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest transition-all ${
601
- activeTab === loc.code ? 'bg-primary text-white' : 'bg-dashboard-card text-dashboard-text-secondary hover:bg-dashboard-border'
602
- }`}
603
- >
604
- <span className="mr-1">{loc.flag}</span>
605
- {loc.code.toUpperCase()}
606
- </button>
607
- ))}
608
- </div>
609
- <div className="space-y-4">
610
- {(() => {
611
- const currentSocials = settings.localizedSocials?.[activeTab] || [];
612
- return currentSocials.length > 0 ? (
613
- currentSocials.map((social) => {
614
- const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
615
- return (
616
- <div key={social.id} className="flex items-center gap-4 p-4 bg-dashboard-card rounded-2xl border border-dashboard-border">
617
- <div className="flex-shrink-0 text-dashboard-text-secondary">
618
- {platform?.icon || <Globe size={18} />}
619
- </div>
620
- <select
621
- value={social.platform}
622
- onChange={(e) => handleUpdateLocalizedSocial(social.id, 'platform', e.target.value)}
623
- 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"
624
- >
625
- <option value="">Select Platform</option>
626
- {AVAILABLE_PLATFORMS.map(p => (<option key={p.name} value={p.name}>{p.name}</option>))}
627
- </select>
628
- <input
629
- type="url"
630
- value={social.url}
631
- onChange={(e) => handleUpdateLocalizedSocial(social.id, 'url', e.target.value)}
632
- placeholder="https://..."
633
- 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"
634
- />
635
- <button onClick={() => handleRemoveLocalizedSocial(social.id)} className="px-4 py-2 text-xs font-bold text-red-500 hover:text-red-700 transition-colors">
636
- Remove
637
- </button>
638
- </div>
639
- );
640
- })
641
- ) : (
642
- <p className="text-sm text-dashboard-text-secondary text-center py-8">
643
- No social links added for {activeTab.toUpperCase()}. Click "Add Social" to get started.
644
- </p>
645
- );
646
- })()}
647
- </div>
648
- </section>
224
+ {/* Main Content Area */}
225
+ <main className="flex-1 flex flex-col min-w-0 bg-transparent">
226
+ {/* Header */}
227
+ <div className="h-24 px-12 flex items-center justify-between shrink-0 z-10 bg-dashboard-bg/20 backdrop-blur-sm border-b border-dashboard-border/30">
228
+ <div className="flex items-center gap-6">
229
+ <div className="h-10 w-1.5 bg-primary rounded-full shadow-[0_0_10px_rgba(var(--color-primary),0.3)]" />
230
+ <div>
231
+ <h2 className="text-2xl font-bold text-dashboard-text tracking-tight leading-none mb-1.5">
232
+ {navigationItems.find(i => i.id === currentTab)?.label}
233
+ </h2>
234
+ <p className="text-xs font-medium text-dashboard-text-secondary opacity-80">
235
+ {navigationItems.find(i => i.id === currentTab)?.description}
236
+ </p>
237
+ </div>
649
238
  </div>
650
239
 
651
- {/* Sidebar */}
652
- <div className="space-y-8">
653
- {/* Maintenance Mode */}
654
- <section className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
655
- <div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
656
- <Settings2 size={18} className="text-primary" />
657
- Maintenance
658
- </div>
659
- <div className="flex items-center justify-between">
660
- <div>
661
- <label className="text-xs font-bold text-dashboard-text block mb-1">Maintenance Mode</label>
662
- <p className="text-[10px] text-dashboard-text-secondary">Show maintenance page to visitors</p>
663
- </div>
240
+ {/* Global Language Switcher - Only visible for localized tabs */}
241
+ {(currentTab === 'seo' || currentTab === 'contact') && (
242
+ <div className="flex gap-1.5 p-1.5 bg-dashboard-card/50 backdrop-blur-md rounded-2xl border border-dashboard-border/40 shadow-sm">
243
+ {activeLocales.map((loc) => (
664
244
  <button
665
- onClick={() => setSettings({ ...settings, maintenanceMode: !settings.maintenanceMode })}
666
- className={`relative w-12 h-6 rounded-full transition-colors ${settings.maintenanceMode ? 'bg-primary' : 'bg-neutral-200 dark:bg-neutral-700'}`}
245
+ key={loc.code}
246
+ onClick={() => setActiveTab(loc.code)}
247
+ className={`px-4 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-2.5 ${activeTab === loc.code
248
+ ? 'bg-primary text-white shadow-md'
249
+ : 'text-dashboard-text-secondary hover:text-dashboard-text hover:bg-dashboard-card/80'
250
+ }`}
667
251
  >
668
- <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'}`} />
252
+ <FlagIcon code={loc.code} countryCode={loc.countryCode} />
253
+ {loc.code.toUpperCase()}
669
254
  </button>
670
- </div>
671
- </section>
672
-
673
- {/* Launch Date */}
674
- <section className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
675
- <div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
676
- <Calendar size={18} className="text-primary" />
677
- Launch Date
678
- </div>
679
- <div className="space-y-4">
680
- <div className="space-y-2">
681
- <label className="text-xs font-bold text-dashboard-text block">Date</label>
682
- <input
683
- type="date"
684
- value={settings.launch_date ? settings.launch_date.split('T')[0] : ''}
685
- onChange={(e) => {
686
- const dateValue = e.target.value;
687
- const currentTime = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00';
688
- setSettings(prev => ({ ...prev, launch_date: dateValue ? `${dateValue}T${currentTime}` : '' }));
689
- }}
690
- 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"
691
- />
692
- </div>
693
- <div className="space-y-2">
694
- <label className="text-xs font-bold text-dashboard-text block">Time (24-hour format)</label>
695
- <input
696
- type="time"
697
- step="60"
698
- value={settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00'}
699
- onChange={(e) => {
700
- const timeValue = e.target.value;
701
- const currentDate = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[0] : new Date().toISOString().split('T')[0];
702
- setSettings(prev => ({ ...prev, launch_date: timeValue ? `${currentDate}T${timeValue}` : '' }));
703
- }}
704
- 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"
705
- />
706
- </div>
707
- <p className="text-[10px] text-dashboard-text-secondary">Used for countdown timers.</p>
708
- </div>
709
- </section>
255
+ ))}
256
+ </div>
257
+ )}
258
+ </div>
710
259
 
711
- {/* Domain Languages Card */}
712
- <DomainLanguagesCard config={settings.domainLocaleConfig || []} onEdit={openDomainModal} />
260
+ {/* Content */}
261
+ <div className="flex-1 overflow-y-auto px-12 pb-12 pt-6 custom-scrollbar">
262
+ <div className="max-w-4xl">
263
+ {renderContent()}
713
264
  </div>
714
265
  </div>
715
- </div>
266
+ </main>
267
+
716
268
 
717
- {/* Domain Languages Modal */}
269
+ {/* Modals */}
718
270
  <DomainLanguagesModal
719
271
  isOpen={showDomainModal}
720
272
  config={settings.domainLocaleConfig || []}
721
- onSave={saveDomainConfig}
273
+ supportedLocales={activeLocales}
274
+ onClose={() => setShowDomainModal(false)}
722
275
  onSaveAndClose={saveDomainConfigAndClose}
723
- onClose={closeDomainModal}
724
276
  isSaving={isSaving}
725
277
  />
726
278
  </div>