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