@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import { Mail, Smartphone, MapPin, Globe } from 'lucide-react';
|
|
4
|
+
import { WebsiteSettings, LocaleDefinition } from '../../../types/settings';
|
|
5
|
+
import { FlagIcon } from './FlagIcon';
|
|
6
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
|
|
9
|
+
function cn(...inputs: ClassValue[]) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ContactInfoSectionProps {
|
|
14
|
+
settings: WebsiteSettings;
|
|
15
|
+
activeTab: string;
|
|
16
|
+
onTabChange: (code: string) => void;
|
|
17
|
+
activeLocales: LocaleDefinition[];
|
|
18
|
+
onUpdate: (updates: Partial<WebsiteSettings>) => void;
|
|
19
|
+
onUpdateLocalized: (field: string, value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ContactInfoSection({
|
|
23
|
+
settings,
|
|
24
|
+
activeTab,
|
|
25
|
+
onTabChange,
|
|
26
|
+
activeLocales,
|
|
27
|
+
onUpdate,
|
|
28
|
+
onUpdateLocalized
|
|
29
|
+
}: ContactInfoSectionProps) {
|
|
30
|
+
return (
|
|
31
|
+
<motion.section
|
|
32
|
+
initial={{ opacity: 0, y: 20 }}
|
|
33
|
+
animate={{ opacity: 1, y: 0 }}
|
|
34
|
+
className="bg-dashboard-card/50 border border-dashboard-border/40 p-8 rounded-[2rem] space-y-6 relative overflow-hidden"
|
|
35
|
+
>
|
|
36
|
+
<div className="absolute top-0 right-0 w-64 h-64 blur-3xl opacity-5 bg-primary pointer-events-none" />
|
|
37
|
+
|
|
38
|
+
<div className="flex items-center gap-3 relative z-10 border-b border-dashboard-border/40 pb-4">
|
|
39
|
+
<div className="size-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
|
|
40
|
+
<Mail size={16} />
|
|
41
|
+
</div>
|
|
42
|
+
<h3 className="text-xl font-bold text-dashboard-text leading-none tracking-tight">
|
|
43
|
+
Core <span className="text-primary">Intelligence</span>
|
|
44
|
+
</h3>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div className="flex gap-2 relative z-10 overflow-x-auto pb-2 custom-scrollbar">
|
|
48
|
+
{activeLocales.map((loc) => (
|
|
49
|
+
<button
|
|
50
|
+
key={loc.code}
|
|
51
|
+
onClick={() => onTabChange(loc.code)}
|
|
52
|
+
className={cn(
|
|
53
|
+
"px-4 py-2 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-all flex items-center gap-2 shrink-0 border",
|
|
54
|
+
activeTab === loc.code
|
|
55
|
+
? "bg-primary text-white border-primary shadow-lg shadow-primary/10"
|
|
56
|
+
: "bg-dashboard-bg/30 text-dashboard-text-secondary/50 border-dashboard-border/40 hover:border-primary/30"
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<FlagIcon code={loc.code} countryCode={loc.countryCode} />
|
|
60
|
+
<span>{loc.code}</span>
|
|
61
|
+
</button>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 relative z-10">
|
|
66
|
+
<div className="space-y-2">
|
|
67
|
+
<label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Global Sync Email</label>
|
|
68
|
+
<div className="relative group">
|
|
69
|
+
<Globe className="absolute left-5 top-1/2 -translate-y-1/2 size-4 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors" />
|
|
70
|
+
<input
|
|
71
|
+
type="email"
|
|
72
|
+
value={settings.contactEmail || ''}
|
|
73
|
+
onChange={(e) => onUpdate({ contactEmail: e.target.value })}
|
|
74
|
+
placeholder="SYNC_EMAIL_ENDPOINT"
|
|
75
|
+
className="w-full pl-12 pr-6 py-3 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-xl text-sm font-bold outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 transition-all text-dashboard-text placeholder:text-dashboard-text-secondary/30"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="space-y-2">
|
|
80
|
+
<label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Regional Phone ({activeTab})</label>
|
|
81
|
+
<div className="relative group">
|
|
82
|
+
<Smartphone className="absolute left-5 top-1/2 -translate-y-1/2 size-4 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors" />
|
|
83
|
+
<input
|
|
84
|
+
type="tel"
|
|
85
|
+
value={settings.localizedContactInfo?.[activeTab]?.phoneNumber || ''}
|
|
86
|
+
onChange={(e) => onUpdateLocalized('phoneNumber', e.target.value)}
|
|
87
|
+
placeholder="INITIALIZE_PHONE"
|
|
88
|
+
className="w-full pl-12 pr-6 py-3 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-xl text-sm font-bold outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 transition-all text-dashboard-text placeholder:text-dashboard-text-secondary/30"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="space-y-2 relative z-10">
|
|
95
|
+
<label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Geospatial Protocol ({activeTab})</label>
|
|
96
|
+
<div className="relative group">
|
|
97
|
+
<MapPin className="absolute left-5 top-5 size-4 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors" />
|
|
98
|
+
<textarea
|
|
99
|
+
value={settings.localizedContactInfo?.[activeTab]?.physicalAddress || ''}
|
|
100
|
+
onChange={(e) => onUpdateLocalized('physicalAddress', e.target.value)}
|
|
101
|
+
placeholder="STREET_ADDRESS_ORCHESTRATION"
|
|
102
|
+
rows={3}
|
|
103
|
+
className="w-full pl-12 pr-6 py-4 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-xl text-sm font-medium leading-relaxed outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 transition-all text-dashboard-text placeholder:text-dashboard-text-secondary/30 resize-none"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</motion.section>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import { Globe2, Edit3, ArrowRight, ShieldCheck } from 'lucide-react';
|
|
4
|
+
import { DomainLocaleConfig } from '../../../types/settings';
|
|
5
|
+
import { FlagIcon } from './FlagIcon';
|
|
6
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
|
|
9
|
+
function cn(...inputs: ClassValue[]) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface DomainLanguagesCardProps {
|
|
14
|
+
config: DomainLocaleConfig[];
|
|
15
|
+
onEdit: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function DomainLanguagesCard({ config, onEdit }: DomainLanguagesCardProps) {
|
|
19
|
+
return (
|
|
20
|
+
<section className="space-y-8">
|
|
21
|
+
<div className="flex items-center gap-4">
|
|
22
|
+
<div className="size-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
|
|
23
|
+
<Globe2 size={20} />
|
|
24
|
+
</div>
|
|
25
|
+
<h3 className="text-2xl lg:text-3xl font-bold text-dashboard-text leading-none tracking-tight">
|
|
26
|
+
Domain <span className="text-primary">Strategy</span>
|
|
27
|
+
</h3>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<motion.div
|
|
31
|
+
initial={{ opacity: 0, y: 20 }}
|
|
32
|
+
animate={{ opacity: 1, y: 0 }}
|
|
33
|
+
className="bg-dashboard-card/50 border border-dashboard-border/40 p-8 rounded-[2rem] relative overflow-hidden"
|
|
34
|
+
>
|
|
35
|
+
<div className="space-y-6">
|
|
36
|
+
{config && config.length > 0 ? (
|
|
37
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
38
|
+
{config.map((entry, index) => (
|
|
39
|
+
<motion.div
|
|
40
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
41
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
42
|
+
transition={{ delay: index * 0.1 }}
|
|
43
|
+
key={index}
|
|
44
|
+
className="flex items-center justify-between p-4 bg-dashboard-bg/30 border border-dashboard-border/40 rounded-xl group hover:border-primary/30 transition-all duration-500 shadow-sm"
|
|
45
|
+
>
|
|
46
|
+
<div className="flex items-center gap-4">
|
|
47
|
+
<div className="size-10 rounded-lg bg-dashboard-card/50 flex items-center justify-center border border-dashboard-border/40 group-hover:bg-primary/10 group-hover:border-primary/20 transition-all duration-500 shadow-inner">
|
|
48
|
+
<FlagIcon code={entry.locale} />
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex flex-col">
|
|
51
|
+
<span className="text-sm font-bold text-dashboard-text/90 group-hover:text-primary transition-colors">{entry.domain}</span>
|
|
52
|
+
<div className="flex items-center gap-2 mt-0.5">
|
|
53
|
+
<ArrowRight size={10} className="text-primary opacity-40" />
|
|
54
|
+
<p className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-widest">
|
|
55
|
+
Primary Region
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
{!entry.allowUserOverride && (
|
|
61
|
+
<div className="size-8 bg-dashboard-card/50 flex items-center justify-center text-dashboard-text-secondary/50 rounded-lg border border-dashboard-border/40" title="Locked to this language">
|
|
62
|
+
<ShieldCheck size={14} />
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</motion.div>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<div className="py-12 text-center bg-dashboard-card/30 border-dashed border border-dashboard-border/40 rounded-2xl">
|
|
70
|
+
<p className="text-[10px] text-dashboard-text-secondary/40 font-bold uppercase tracking-[0.2em]">No domain-specific routing configured</p>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="mt-8 pt-8 border-t border-dashboard-border/40 flex flex-col items-center">
|
|
76
|
+
<button
|
|
77
|
+
onClick={onEdit}
|
|
78
|
+
className="w-full max-w-sm relative group/btn flex items-center justify-center gap-4 px-8 py-3 bg-primary text-white rounded-xl text-[10px] font-bold uppercase tracking-[0.2em] transition-all shadow-xl shadow-primary/10 hover:shadow-primary/20 active:scale-[0.98] overflow-hidden"
|
|
79
|
+
>
|
|
80
|
+
<div className="absolute inset-0 bg-linear-to-br from-white/20 to-transparent opacity-0 group-hover/btn:opacity-100 transition-opacity" />
|
|
81
|
+
<Edit3 size={16} />
|
|
82
|
+
<span>{config?.length ? 'Update Configurations' : 'Configure Domain Routing'}</span>
|
|
83
|
+
</button>
|
|
84
|
+
<p className="text-[10px] text-dashboard-text-secondary/60 text-center mt-6 font-medium max-w-md">
|
|
85
|
+
Domain-based routing allows you to orchestrate specific regional delivery protocols based on the incoming hostname.
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
</motion.div>
|
|
89
|
+
</section>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { X, Trash2, Plus, Globe2, Shield, ArrowRight, Save } from 'lucide-react';
|
|
4
|
+
import { DomainLocaleConfig, LocaleDefinition } from '../../../types/settings';
|
|
5
|
+
import { FlagIcon } from './FlagIcon';
|
|
6
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
|
|
9
|
+
function cn(...inputs: ClassValue[]) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface DomainLanguagesModalProps {
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
config: DomainLocaleConfig[];
|
|
16
|
+
supportedLocales: LocaleDefinition[];
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
onSaveAndClose: (config: DomainLocaleConfig[]) => void;
|
|
19
|
+
isSaving?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DomainLanguagesModal({
|
|
23
|
+
isOpen,
|
|
24
|
+
config,
|
|
25
|
+
supportedLocales,
|
|
26
|
+
onClose,
|
|
27
|
+
onSaveAndClose,
|
|
28
|
+
isSaving = false
|
|
29
|
+
}: DomainLanguagesModalProps) {
|
|
30
|
+
const [tempConfig, setTempConfig] = useState<DomainLocaleConfig[]>(config);
|
|
31
|
+
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setTempConfig(config);
|
|
35
|
+
setExpandedIndex(null);
|
|
36
|
+
}, [config, isOpen]);
|
|
37
|
+
|
|
38
|
+
if (!isOpen) return null;
|
|
39
|
+
|
|
40
|
+
const handleAdd = () => {
|
|
41
|
+
setTempConfig([...tempConfig, { domain: '', locale: supportedLocales[0]?.code || 'en', allowUserOverride: true }]);
|
|
42
|
+
setExpandedIndex(tempConfig.length);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleUpdate = (index: number, field: keyof DomainLocaleConfig, value: any) => {
|
|
46
|
+
const updated = [...tempConfig];
|
|
47
|
+
updated[index] = { ...updated[index], [field]: value };
|
|
48
|
+
setTempConfig(updated);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleRemove = (index: number) => {
|
|
52
|
+
setTempConfig(tempConfig.filter((_, i) => i !== index));
|
|
53
|
+
if (expandedIndex === index) setExpandedIndex(null);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const toggleExpand = (index: number) => {
|
|
57
|
+
setExpandedIndex(expandedIndex === index ? null : index);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
|
62
|
+
<motion.div
|
|
63
|
+
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
64
|
+
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
65
|
+
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
66
|
+
className="bg-dashboard-card/50 w-full max-w-xl rounded-[2rem] border border-dashboard-border/40 shadow-2xl flex flex-col max-h-[90vh] overflow-hidden relative"
|
|
67
|
+
>
|
|
68
|
+
<div className="absolute top-0 right-0 w-64 h-64 blur-3xl opacity-5 bg-primary pointer-events-none" />
|
|
69
|
+
|
|
70
|
+
<div className="flex items-center justify-between p-6 border-b border-dashboard-border/40 relative z-10">
|
|
71
|
+
<div className="flex items-center gap-3">
|
|
72
|
+
<div className="size-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
|
|
73
|
+
<Globe2 size={16} />
|
|
74
|
+
</div>
|
|
75
|
+
<h2 className="text-xl font-bold text-dashboard-text/90">
|
|
76
|
+
Domain <span className="text-primary">Routing</span>
|
|
77
|
+
</h2>
|
|
78
|
+
</div>
|
|
79
|
+
<button onClick={onClose} className="p-2 text-dashboard-text-secondary/50 hover:text-dashboard-text transition-all hover:bg-white/5 rounded-lg">
|
|
80
|
+
<X size={18} />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="flex-1 overflow-y-auto p-6 space-y-4 relative z-10 custom-scrollbar">
|
|
85
|
+
<AnimatePresence mode="popLayout">
|
|
86
|
+
{tempConfig.length > 0 ? (
|
|
87
|
+
<div className="space-y-3">
|
|
88
|
+
<span className="text-[10px] font-bold text-primary/60 uppercase tracking-[0.2em] ml-2">Active Configurations</span>
|
|
89
|
+
{tempConfig.map((entry, index) => (
|
|
90
|
+
<motion.div
|
|
91
|
+
layout
|
|
92
|
+
initial={{ opacity: 0, x: -20 }}
|
|
93
|
+
animate={{ opacity: 1, x: 0 }}
|
|
94
|
+
exit={{ opacity: 0, x: 20 }}
|
|
95
|
+
key={index}
|
|
96
|
+
className="bg-dashboard-bg/30 rounded-xl border border-dashboard-border/40 overflow-hidden transition-all duration-300 hover:border-primary/20"
|
|
97
|
+
>
|
|
98
|
+
<div
|
|
99
|
+
onClick={() => toggleExpand(index)}
|
|
100
|
+
className="w-full flex items-center justify-between p-3.5 text-left hover:bg-primary/5 transition-all cursor-pointer"
|
|
101
|
+
>
|
|
102
|
+
<div className="flex items-center gap-3">
|
|
103
|
+
<span className="text-sm font-bold text-dashboard-text/90">{entry.domain || 'UNCONFIGURED_HOST'}</span>
|
|
104
|
+
<ArrowRight size={12} className="text-primary opacity-40" />
|
|
105
|
+
<div className="px-2 py-0.5 bg-dashboard-card/50 border border-dashboard-border/40 rounded-full flex items-center gap-2 bg-primary/5">
|
|
106
|
+
<FlagIcon code={entry.locale} />
|
|
107
|
+
<span className="text-[9px] font-bold uppercase tracking-wider text-primary">{entry.locale}</span>
|
|
108
|
+
</div>
|
|
109
|
+
{entry.allowUserOverride === false && <Shield size={10} className="text-dashboard-text-secondary/50" />}
|
|
110
|
+
</div>
|
|
111
|
+
<div className="flex items-center gap-2">
|
|
112
|
+
<button
|
|
113
|
+
onClick={(e) => { e.stopPropagation(); handleRemove(index); }}
|
|
114
|
+
className="p-1.5 text-dashboard-text-secondary/40 hover:text-red-500 hover:bg-red-500/10 rounded-lg transition-all"
|
|
115
|
+
>
|
|
116
|
+
<Trash2 size={14} />
|
|
117
|
+
</button>
|
|
118
|
+
<div className={cn("transition-transform duration-300", expandedIndex === index && "rotate-180")}>
|
|
119
|
+
<X size={12} className="text-dashboard-text-secondary/40 rotate-45" />
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<AnimatePresence>
|
|
125
|
+
{expandedIndex === index && (
|
|
126
|
+
<motion.div
|
|
127
|
+
initial={{ height: 0, opacity: 0 }}
|
|
128
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
129
|
+
exit={{ height: 0, opacity: 0 }}
|
|
130
|
+
className="overflow-hidden"
|
|
131
|
+
>
|
|
132
|
+
<div className="p-4 pt-0 space-y-4 border-t border-dashboard-border/40 bg-dashboard-card/20">
|
|
133
|
+
<div className="space-y-1.5 mt-4">
|
|
134
|
+
<label className="text-[10px] font-bold text-dashboard-text-secondary/60 uppercase tracking-[0.15em] ml-1">Hostname Protocol</label>
|
|
135
|
+
<input
|
|
136
|
+
type="text"
|
|
137
|
+
value={entry.domain}
|
|
138
|
+
onChange={(e) => handleUpdate(index, 'domain', e.target.value)}
|
|
139
|
+
placeholder="e.g. jhits.tech"
|
|
140
|
+
className="w-full px-4 py-2 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-lg text-sm font-bold outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 transition-all text-dashboard-text placeholder:text-dashboard-text-secondary/30"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div className="flex items-center gap-4">
|
|
145
|
+
<div className="flex-1 space-y-1.5">
|
|
146
|
+
<label className="text-[10px] font-bold text-dashboard-text-secondary/60 uppercase tracking-[0.15em] ml-1">Regional Node</label>
|
|
147
|
+
<select
|
|
148
|
+
value={entry.locale}
|
|
149
|
+
onChange={(e) => handleUpdate(index, 'locale', e.target.value)}
|
|
150
|
+
className="w-full px-4 py-2 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-lg text-sm font-bold uppercase tracking-wider outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 transition-all text-dashboard-text cursor-pointer appearance-none"
|
|
151
|
+
>
|
|
152
|
+
{supportedLocales.map(loc => (
|
|
153
|
+
<option key={loc.code} value={loc.code} className="bg-neutral-900">{loc.name}</option>
|
|
154
|
+
))}
|
|
155
|
+
</select>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="shrink-0 flex flex-col items-center gap-1.5 pt-4">
|
|
158
|
+
<label className="text-[9px] font-bold text-dashboard-text-secondary/50 uppercase tracking-widest">User Override</label>
|
|
159
|
+
<button
|
|
160
|
+
onClick={() => handleUpdate(index, 'allowUserOverride', !entry.allowUserOverride)}
|
|
161
|
+
className={cn(
|
|
162
|
+
"relative w-10 h-5 rounded-full transition-all duration-300 p-0.5",
|
|
163
|
+
entry.allowUserOverride ? "bg-primary" : "bg-neutral-700"
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
<div className={cn(
|
|
167
|
+
"w-4 h-4 bg-white rounded-full transition-transform duration-300 shadow-sm",
|
|
168
|
+
entry.allowUserOverride ? "translate-x-5" : "translate-x-0"
|
|
169
|
+
)} />
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</motion.div>
|
|
175
|
+
)}
|
|
176
|
+
</AnimatePresence>
|
|
177
|
+
</motion.div>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
) : (
|
|
181
|
+
<div className="py-12 text-center bg-dashboard-card/20 border-dashed border border-dashboard-border/40 rounded-2xl">
|
|
182
|
+
<p className="text-[10px] text-dashboard-text-secondary/40 font-bold uppercase tracking-[0.2em]">No domain-specific routing configured</p>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
</AnimatePresence>
|
|
186
|
+
|
|
187
|
+
<div className="pt-2">
|
|
188
|
+
<button
|
|
189
|
+
onClick={handleAdd}
|
|
190
|
+
className="w-full flex items-center justify-center gap-2 px-4 py-3 border border-dashed border-dashboard-border/40 text-dashboard-text-secondary/50 hover:border-primary/40 hover:text-primary rounded-xl transition-all duration-500 font-bold uppercase text-[10px] tracking-[0.2em]"
|
|
191
|
+
>
|
|
192
|
+
<Plus size={16} />
|
|
193
|
+
Add System Domain
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div className="flex gap-3 p-6 border-t border-dashboard-border/40 relative z-10">
|
|
199
|
+
<button onClick={onClose} className="flex-1 px-4 py-2.5 bg-dashboard-card/30 hover:bg-white/5 text-dashboard-text/80 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-all border border-dashboard-border/40">
|
|
200
|
+
Cancel
|
|
201
|
+
</button>
|
|
202
|
+
<button
|
|
203
|
+
onClick={() => onSaveAndClose(tempConfig)}
|
|
204
|
+
disabled={isSaving}
|
|
205
|
+
className="flex-1 px-4 py-2.5 bg-primary text-white rounded-xl text-[10px] font-bold uppercase tracking-[0.15em] transition-all shadow-xl shadow-primary/10 hover:shadow-primary/20 disabled:opacity-50 flex items-center justify-center gap-2"
|
|
206
|
+
>
|
|
207
|
+
{isSaving ? (
|
|
208
|
+
<div className="size-3.5 border-2 border-white/20 border-t-white rounded-full animate-spin" />
|
|
209
|
+
) : <Save size={14} />}
|
|
210
|
+
<span>{isSaving ? 'Syncing...' : 'Deploy Config'}</span>
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
</motion.div>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { GLOBAL_LOCALE_DATABASE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export function FlagIcon({ code, countryCode, className = "w-4 h-3" }: { code?: string; countryCode?: string; className?: string }) {
|
|
5
|
+
let finalCountryCode = countryCode;
|
|
6
|
+
|
|
7
|
+
if (!finalCountryCode && code) {
|
|
8
|
+
finalCountryCode = GLOBAL_LOCALE_DATABASE.find(l => l.code === code)?.countryCode || code;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!finalCountryCode) return null;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<img
|
|
15
|
+
src={`https://flagcdn.com/w20/${finalCountryCode.toLowerCase()}.png`}
|
|
16
|
+
alt={code || finalCountryCode}
|
|
17
|
+
className={`${className} object-contain rounded-xs shadow-xs`}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import { Calendar, Timer, Info } from 'lucide-react';
|
|
4
|
+
import { WebsiteSettings } from '../../../types/settings';
|
|
5
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
|
|
8
|
+
function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface LaunchDateSectionProps {
|
|
13
|
+
settings: WebsiteSettings;
|
|
14
|
+
onUpdate: (updates: Partial<WebsiteSettings>) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function LaunchDateSection({ settings, onUpdate }: LaunchDateSectionProps) {
|
|
18
|
+
if (settings.launched) {
|
|
19
|
+
return (
|
|
20
|
+
<section className="space-y-12">
|
|
21
|
+
<div className="flex items-center gap-6">
|
|
22
|
+
<div className="size-14 rounded-2xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
|
|
23
|
+
<Calendar size={28} />
|
|
24
|
+
</div>
|
|
25
|
+
<h3 className="text-4xl lg:text-5xl font-bold text-dashboard-text leading-none">
|
|
26
|
+
Launch <span className="text-primary">History</span>
|
|
27
|
+
</h3>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<motion.div
|
|
31
|
+
initial={{ opacity: 0, scale: 0.98 }}
|
|
32
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
33
|
+
className="bg-dashboard-card/50 border border-dashboard-border/50 p-10 rounded-[3rem] flex items-center gap-8 relative overflow-hidden group"
|
|
34
|
+
>
|
|
35
|
+
<div className="absolute top-0 right-0 w-48 h-48 blur-3xl opacity-5 bg-emerald-500 pointer-events-none" />
|
|
36
|
+
|
|
37
|
+
<div className="size-20 rounded-2xl bg-emerald-500/10 flex items-center justify-center border border-emerald-500/20 shadow-inner shrink-0">
|
|
38
|
+
<div className="size-5 rounded-full bg-emerald-500 animate-pulse shadow-[0_0_20px_rgba(16,185,129,0.4)]" />
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
<span className="text-[10px] font-bold text-emerald-500 uppercase tracking-[0.3em] mb-2 block opacity-60">System Temporal Status</span>
|
|
42
|
+
<h4 className="text-2xl font-bold text-dashboard-text">System Fully Operational</h4>
|
|
43
|
+
{settings.launch_date && (
|
|
44
|
+
<p className="text-sm text-neutral-500 dark:text-neutral-400 font-medium mt-2">
|
|
45
|
+
Initial Deployment Sequence: {new Date(settings.launch_date).toLocaleDateString(undefined, { dateStyle: 'long' })}
|
|
46
|
+
</p>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
</motion.div>
|
|
50
|
+
</section>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<section className="space-y-12">
|
|
56
|
+
<div className="flex items-center gap-6">
|
|
57
|
+
<div className="size-14 rounded-2xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
|
|
58
|
+
<Timer size={28} />
|
|
59
|
+
</div>
|
|
60
|
+
<h3 className="text-4xl lg:text-5xl font-bold text-dashboard-text leading-none">
|
|
61
|
+
Launch <span className="text-primary">Sequence</span>
|
|
62
|
+
</h3>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<motion.div
|
|
66
|
+
initial={{ opacity: 0, y: 20 }}
|
|
67
|
+
animate={{ opacity: 1, y: 0 }}
|
|
68
|
+
className="bg-dashboard-card/50 border border-dashboard-border/50 p-10 rounded-[3rem] space-y-10 relative overflow-hidden"
|
|
69
|
+
>
|
|
70
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
|
71
|
+
<div className="space-y-4">
|
|
72
|
+
<label className="text-[10px] font-bold text-primary uppercase tracking-[0.3em] ml-2 opacity-60">Deployment Date</label>
|
|
73
|
+
<input
|
|
74
|
+
type="date"
|
|
75
|
+
value={settings.launch_date ? settings.launch_date.split('T')[0] : ''}
|
|
76
|
+
onChange={(e) => {
|
|
77
|
+
const dateValue = e.target.value;
|
|
78
|
+
const currentTime = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00';
|
|
79
|
+
onUpdate({ launch_date: dateValue ? `${dateValue}T${currentTime}` : '' });
|
|
80
|
+
}}
|
|
81
|
+
className="w-full px-8 py-5 bg-dashboard-card/50 border border-dashboard-border/50 rounded-[2rem] text-sm font-bold outline-none focus:ring-4 focus:ring-primary/10 transition-all text-dashboard-text placeholder:text-neutral-500"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="space-y-4">
|
|
85
|
+
<label className="text-[10px] font-bold text-primary uppercase tracking-[0.3em] ml-2 opacity-60">Synchronization Time (24h)</label>
|
|
86
|
+
<input
|
|
87
|
+
type="time"
|
|
88
|
+
step="60"
|
|
89
|
+
value={settings.launch_date?.includes('T') ? settings.launch_date.split('T')[1] : '00:00'}
|
|
90
|
+
onChange={(e) => {
|
|
91
|
+
const timeValue = e.target.value;
|
|
92
|
+
const currentDate = settings.launch_date?.includes('T') ? settings.launch_date.split('T')[0] : new Date().toISOString().split('T')[0];
|
|
93
|
+
onUpdate({ launch_date: timeValue ? `${currentDate}T${timeValue}` : '' });
|
|
94
|
+
}}
|
|
95
|
+
className="w-full px-8 py-5 bg-dashboard-card/50 border border-dashboard-border/50 rounded-[2rem] text-sm font-bold outline-none focus:ring-4 focus:ring-primary/10 transition-all text-dashboard-text placeholder:text-neutral-500"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="bg-dashboard-card/50 border border-dashboard-border/50 p-8 rounded-[2rem] bg-primary/5 flex items-start gap-5 group">
|
|
101
|
+
<div className="size-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary shrink-0 border border-primary/20">
|
|
102
|
+
<Info size={18} />
|
|
103
|
+
</div>
|
|
104
|
+
<p className="text-xs text-neutral-500 dark:text-neutral-400 font-medium leading-relaxed">
|
|
105
|
+
The temporal countdown will be active on the system frontend. Upon reaching the zero-point, the architecture will automatically transition to an active production state.
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
</motion.div>
|
|
109
|
+
</section>
|
|
110
|
+
);
|
|
111
|
+
}
|