@jhits/plugin-website 0.0.16 → 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.
- package/dist/131._entry.js +2 -0
- package/dist/131._entry.js.LICENSE.txt +6 -0
- package/dist/303._entry.js +1 -0
- package/dist/_entry.js +2 -0
- package/dist/_entry.js.LICENSE.txt +6 -0
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +15 -4
- package/dist/remoteEntry.js +1 -0
- package/dist/server.js +232 -0
- package/dist/types/settings.d.ts +18 -11
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/views/Settings/components/ContactInfoSection.d.ts +12 -0
- package/dist/views/Settings/components/ContactInfoSection.d.ts.map +1 -0
- package/dist/views/Settings/components/ContactInfoSection.js +14 -0
- package/dist/views/Settings/components/DomainLanguagesCard.d.ts +8 -0
- package/dist/views/Settings/components/DomainLanguagesCard.d.ts.map +1 -0
- package/dist/views/Settings/components/DomainLanguagesCard.js +12 -0
- package/dist/views/Settings/components/DomainLanguagesModal.d.ts +12 -0
- package/dist/views/Settings/components/DomainLanguagesModal.d.ts.map +1 -0
- package/dist/views/Settings/components/DomainLanguagesModal.js +38 -0
- package/dist/views/Settings/components/FlagIcon.d.ts +6 -0
- package/dist/views/Settings/components/FlagIcon.d.ts.map +1 -0
- package/dist/views/Settings/components/FlagIcon.js +11 -0
- package/dist/views/Settings/components/LaunchDateSection.d.ts +8 -0
- package/dist/views/Settings/components/LaunchDateSection.d.ts.map +1 -0
- package/dist/views/Settings/components/LaunchDateSection.js +22 -0
- package/dist/views/Settings/components/LocalesManagementCard.d.ts +8 -0
- package/dist/views/Settings/components/LocalesManagementCard.d.ts.map +1 -0
- package/dist/views/Settings/components/LocalesManagementCard.js +66 -0
- package/dist/views/Settings/components/LocalizedContentSection.d.ts +12 -0
- package/dist/views/Settings/components/LocalizedContentSection.d.ts.map +1 -0
- package/dist/views/Settings/components/LocalizedContentSection.js +17 -0
- package/dist/views/Settings/components/SeoIdentitySection.d.ts +9 -0
- package/dist/views/Settings/components/SeoIdentitySection.d.ts.map +1 -0
- package/dist/views/Settings/components/SeoIdentitySection.js +12 -0
- package/dist/views/Settings/components/SocialLinksSection.d.ts +13 -0
- package/dist/views/Settings/components/SocialLinksSection.d.ts.map +1 -0
- package/dist/views/Settings/components/SocialLinksSection.js +13 -0
- package/dist/views/Settings/components/StatusSection.d.ts +8 -0
- package/dist/views/Settings/components/StatusSection.d.ts.map +1 -0
- package/dist/views/Settings/components/StatusSection.js +27 -0
- package/dist/views/Settings/constants.d.ts +12 -0
- package/dist/views/Settings/constants.d.ts.map +1 -0
- package/dist/views/Settings/constants.js +23 -0
- package/dist/views/Settings/hooks/useWebsiteSettings.d.ts +24 -0
- package/dist/views/Settings/hooks/useWebsiteSettings.d.ts.map +1 -0
- package/dist/views/Settings/hooks/useWebsiteSettings.js +217 -0
- package/dist/views/Settings/types.d.ts +5 -0
- package/dist/views/Settings/types.d.ts.map +1 -0
- package/dist/views/Settings/types.js +1 -0
- package/dist/views/SettingsView.d.ts +3 -6
- package/dist/views/SettingsView.d.ts.map +1 -1
- package/dist/views/SettingsView.js +59 -262
- package/package.json +14 -10
- package/src/api/handler.ts +41 -4
- package/src/types/settings.ts +28 -12
- package/src/views/Settings/components/ContactInfoSection.tsx +109 -0
- package/src/views/Settings/components/DomainLanguagesCard.tsx +91 -0
- package/src/views/Settings/components/DomainLanguagesModal.tsx +216 -0
- package/src/views/Settings/components/FlagIcon.tsx +20 -0
- package/src/views/Settings/components/LaunchDateSection.tsx +111 -0
- package/src/views/Settings/components/LocalesManagementCard.tsx +198 -0
- package/src/views/Settings/components/LocalizedContentSection.tsx +155 -0
- package/src/views/Settings/components/SeoIdentitySection.tsx +98 -0
- package/src/views/Settings/components/SocialLinksSection.tsx +98 -0
- package/src/views/Settings/components/StatusSection.tsx +181 -0
- package/src/views/Settings/constants.ts +26 -0
- package/src/views/Settings/hooks/useWebsiteSettings.ts +240 -0
- package/src/views/Settings/types.ts +4 -0
- package/src/views/SettingsView.tsx +226 -674
|
@@ -1,726 +1,278 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Website Settings View
|
|
3
|
-
*
|
|
3
|
+
* Refactored modular interface with sidebar navigation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
|
-
import React, { useState
|
|
9
|
-
import { Save, RefreshCw,
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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="
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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="
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
<div className="
|
|
454
|
-
<
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
</
|
|
458
|
-
|
|
459
|
-
|
|
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={`
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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 ?
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
{/*
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
666
|
-
|
|
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
|
-
<
|
|
252
|
+
<FlagIcon code={loc.code} countryCode={loc.countryCode} />
|
|
253
|
+
{loc.code.toUpperCase()}
|
|
669
254
|
</button>
|
|
670
|
-
|
|
671
|
-
</
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
</
|
|
266
|
+
</main>
|
|
267
|
+
|
|
716
268
|
|
|
717
|
-
{/*
|
|
269
|
+
{/* Modals */}
|
|
718
270
|
<DomainLanguagesModal
|
|
719
271
|
isOpen={showDomainModal}
|
|
720
272
|
config={settings.domainLocaleConfig || []}
|
|
721
|
-
|
|
273
|
+
supportedLocales={activeLocales}
|
|
274
|
+
onClose={() => setShowDomainModal(false)}
|
|
722
275
|
onSaveAndClose={saveDomainConfigAndClose}
|
|
723
|
-
onClose={closeDomainModal}
|
|
724
276
|
isSaving={isSaving}
|
|
725
277
|
/>
|
|
726
278
|
</div>
|