@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.
Files changed (70) hide show
  1. package/dist/131._entry.js +2 -0
  2. package/dist/131._entry.js.LICENSE.txt +6 -0
  3. package/dist/303._entry.js +1 -0
  4. package/dist/_entry.js +2 -0
  5. package/dist/_entry.js.LICENSE.txt +6 -0
  6. package/dist/api/handler.d.ts.map +1 -1
  7. package/dist/api/handler.js +15 -4
  8. package/dist/remoteEntry.js +1 -0
  9. package/dist/server.js +232 -0
  10. package/dist/types/settings.d.ts +18 -11
  11. package/dist/types/settings.d.ts.map +1 -1
  12. package/dist/views/Settings/components/ContactInfoSection.d.ts +12 -0
  13. package/dist/views/Settings/components/ContactInfoSection.d.ts.map +1 -0
  14. package/dist/views/Settings/components/ContactInfoSection.js +14 -0
  15. package/dist/views/Settings/components/DomainLanguagesCard.d.ts +8 -0
  16. package/dist/views/Settings/components/DomainLanguagesCard.d.ts.map +1 -0
  17. package/dist/views/Settings/components/DomainLanguagesCard.js +12 -0
  18. package/dist/views/Settings/components/DomainLanguagesModal.d.ts +12 -0
  19. package/dist/views/Settings/components/DomainLanguagesModal.d.ts.map +1 -0
  20. package/dist/views/Settings/components/DomainLanguagesModal.js +38 -0
  21. package/dist/views/Settings/components/FlagIcon.d.ts +6 -0
  22. package/dist/views/Settings/components/FlagIcon.d.ts.map +1 -0
  23. package/dist/views/Settings/components/FlagIcon.js +11 -0
  24. package/dist/views/Settings/components/LaunchDateSection.d.ts +8 -0
  25. package/dist/views/Settings/components/LaunchDateSection.d.ts.map +1 -0
  26. package/dist/views/Settings/components/LaunchDateSection.js +22 -0
  27. package/dist/views/Settings/components/LocalesManagementCard.d.ts +8 -0
  28. package/dist/views/Settings/components/LocalesManagementCard.d.ts.map +1 -0
  29. package/dist/views/Settings/components/LocalesManagementCard.js +66 -0
  30. package/dist/views/Settings/components/LocalizedContentSection.d.ts +12 -0
  31. package/dist/views/Settings/components/LocalizedContentSection.d.ts.map +1 -0
  32. package/dist/views/Settings/components/LocalizedContentSection.js +17 -0
  33. package/dist/views/Settings/components/SeoIdentitySection.d.ts +9 -0
  34. package/dist/views/Settings/components/SeoIdentitySection.d.ts.map +1 -0
  35. package/dist/views/Settings/components/SeoIdentitySection.js +12 -0
  36. package/dist/views/Settings/components/SocialLinksSection.d.ts +13 -0
  37. package/dist/views/Settings/components/SocialLinksSection.d.ts.map +1 -0
  38. package/dist/views/Settings/components/SocialLinksSection.js +13 -0
  39. package/dist/views/Settings/components/StatusSection.d.ts +8 -0
  40. package/dist/views/Settings/components/StatusSection.d.ts.map +1 -0
  41. package/dist/views/Settings/components/StatusSection.js +27 -0
  42. package/dist/views/Settings/constants.d.ts +12 -0
  43. package/dist/views/Settings/constants.d.ts.map +1 -0
  44. package/dist/views/Settings/constants.js +23 -0
  45. package/dist/views/Settings/hooks/useWebsiteSettings.d.ts +24 -0
  46. package/dist/views/Settings/hooks/useWebsiteSettings.d.ts.map +1 -0
  47. package/dist/views/Settings/hooks/useWebsiteSettings.js +217 -0
  48. package/dist/views/Settings/types.d.ts +5 -0
  49. package/dist/views/Settings/types.d.ts.map +1 -0
  50. package/dist/views/Settings/types.js +1 -0
  51. package/dist/views/SettingsView.d.ts +3 -6
  52. package/dist/views/SettingsView.d.ts.map +1 -1
  53. package/dist/views/SettingsView.js +59 -262
  54. package/package.json +14 -10
  55. package/src/api/handler.ts +41 -4
  56. package/src/types/settings.ts +28 -12
  57. package/src/views/Settings/components/ContactInfoSection.tsx +109 -0
  58. package/src/views/Settings/components/DomainLanguagesCard.tsx +91 -0
  59. package/src/views/Settings/components/DomainLanguagesModal.tsx +216 -0
  60. package/src/views/Settings/components/FlagIcon.tsx +20 -0
  61. package/src/views/Settings/components/LaunchDateSection.tsx +111 -0
  62. package/src/views/Settings/components/LocalesManagementCard.tsx +198 -0
  63. package/src/views/Settings/components/LocalizedContentSection.tsx +155 -0
  64. package/src/views/Settings/components/SeoIdentitySection.tsx +98 -0
  65. package/src/views/Settings/components/SocialLinksSection.tsx +98 -0
  66. package/src/views/Settings/components/StatusSection.tsx +181 -0
  67. package/src/views/Settings/constants.ts +26 -0
  68. package/src/views/Settings/hooks/useWebsiteSettings.ts +240 -0
  69. package/src/views/Settings/types.ts +4 -0
  70. package/src/views/SettingsView.tsx +226 -674
@@ -0,0 +1,198 @@
1
+ import React, { useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Globe, Plus, Search, Loader2, X, Sparkles, Info } from 'lucide-react';
4
+ import { LocaleDefinition } from '../../../types/settings';
5
+ import { GLOBAL_LOCALE_DATABASE } from '../constants';
6
+ import { FlagIcon } from './FlagIcon';
7
+ import { clsx, type ClassValue } from 'clsx';
8
+ import { twMerge } from 'tailwind-merge';
9
+
10
+ function cn(...inputs: ClassValue[]) {
11
+ return twMerge(clsx(inputs));
12
+ }
13
+
14
+ interface LocalesManagementCardProps {
15
+ supportedLocales: LocaleDefinition[];
16
+ onUpdate: (locales: LocaleDefinition[]) => void;
17
+ }
18
+
19
+ export function LocalesManagementCard({ supportedLocales, onUpdate }: LocalesManagementCardProps) {
20
+ const [searchCode, setSearchCode] = useState('');
21
+ const [isSearching, setIsSearching] = useState(false);
22
+ const [searchError, setSearchError] = useState('');
23
+
24
+ const handleToggleLocale = (locale: LocaleDefinition) => {
25
+ const isEnabled = supportedLocales.some(l => l.code === locale.code);
26
+ if (isEnabled) {
27
+ if (supportedLocales.length <= 1) {
28
+ alert('You must have at least one language enabled.');
29
+ return;
30
+ }
31
+ onUpdate(supportedLocales.filter(l => l.code !== locale.code));
32
+ } else {
33
+ onUpdate([...supportedLocales, locale]);
34
+ }
35
+ };
36
+
37
+ const handleSearchAndAdd = async () => {
38
+ const code = searchCode.trim().toLowerCase();
39
+ if (!code) return;
40
+
41
+ if (supportedLocales.some(l => l.code === code || l.countryCode === code)) {
42
+ setSearchError('Language already added');
43
+ return;
44
+ }
45
+
46
+ setIsSearching(true);
47
+ setSearchError('');
48
+
49
+ try {
50
+ const response = await fetch(`https://restcountries.com/v3.1/alpha/${code}`);
51
+ if (!response.ok) throw new Error('Country not found');
52
+
53
+ const data = await response.json();
54
+ const country = data[0];
55
+
56
+ if (country) {
57
+ const newLocale: LocaleDefinition = {
58
+ code: code,
59
+ name: country.name.common,
60
+ countryCode: country.cca2.toLowerCase()
61
+ };
62
+
63
+ onUpdate([...supportedLocales, newLocale]);
64
+ setSearchCode('');
65
+ }
66
+ } catch (err) {
67
+ setSearchError('Could not find country code');
68
+ } finally {
69
+ setIsSearching(false);
70
+ }
71
+ };
72
+
73
+ return (
74
+ <section className="space-y-8">
75
+ <div className="flex items-center gap-4">
76
+ <div className="size-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
77
+ <Globe size={20} />
78
+ </div>
79
+ <h3 className="text-2xl lg:text-3xl font-bold text-dashboard-text leading-none tracking-tight">
80
+ Localization <span className="text-primary">Matrix</span>
81
+ </h3>
82
+ </div>
83
+
84
+ <motion.div
85
+ initial={{ opacity: 0, y: 20 }}
86
+ animate={{ opacity: 1, y: 0 }}
87
+ className="bg-dashboard-card/50 border border-dashboard-border/40 p-8 rounded-[2rem] space-y-8 relative overflow-hidden"
88
+ >
89
+ {/* Active / Enabled Languages */}
90
+ <div className="space-y-4">
91
+ <div className="flex items-center gap-3 ml-2">
92
+ <Sparkles size={12} className="text-primary animate-pulse" />
93
+ <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/60">Active Translation Nodes</span>
94
+ </div>
95
+
96
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
97
+ <AnimatePresence mode="popLayout">
98
+ {supportedLocales.length > 0 ? (
99
+ supportedLocales.map((loc) => (
100
+ <motion.div
101
+ layout
102
+ initial={{ opacity: 0, scale: 0.95 }}
103
+ animate={{ opacity: 1, scale: 1 }}
104
+ exit={{ opacity: 0, scale: 0.95 }}
105
+ key={loc.code}
106
+ 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"
107
+ >
108
+ <div className="flex items-center gap-4">
109
+ <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">
110
+ <FlagIcon countryCode={loc.countryCode} />
111
+ </div>
112
+ <div className="flex flex-col">
113
+ <span className="text-sm font-bold text-dashboard-text/90 group-hover:text-primary transition-colors">{loc.name}</span>
114
+ <span className="text-[10px] text-dashboard-text-secondary/70 font-bold tracking-widest uppercase mt-0.5">{loc.code}</span>
115
+ </div>
116
+ </div>
117
+ <button
118
+ onClick={() => handleToggleLocale(loc)}
119
+ className="p-2 text-dashboard-text-secondary/40 hover:text-red-500 hover:bg-red-500/10 rounded-lg opacity-0 group-hover:opacity-100 transition-all active:scale-90"
120
+ >
121
+ <X size={16} />
122
+ </button>
123
+ </motion.div>
124
+ ))
125
+ ) : (
126
+ <div className="col-span-2 py-12 text-center bg-dashboard-card/30 border-dashed border border-dashboard-border/40 rounded-2xl">
127
+ <p className="text-xs text-dashboard-text-secondary/60 font-medium">No active localization nodes detected.</p>
128
+ </div>
129
+ )}
130
+ </AnimatePresence>
131
+ </div>
132
+ </div>
133
+
134
+ {/* Search & Add New */}
135
+ <div className="space-y-4 pt-8 border-t border-dashboard-border/40">
136
+ <div className="flex items-center justify-between px-2">
137
+ <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/60">Initialize New Region</span>
138
+ {searchError && <p className="text-[10px] font-bold text-red-500 animate-pulse">{searchError}</p>}
139
+ </div>
140
+ <div className="flex gap-3">
141
+ <div className="relative flex-1">
142
+ <input
143
+ type="text"
144
+ value={searchCode}
145
+ onChange={(e) => setSearchCode(e.target.value.toUpperCase())}
146
+ onKeyDown={(e) => e.key === 'Enter' && handleSearchAndAdd()}
147
+ placeholder="COUNTRY CODE (e.g. BE, JP, FR)"
148
+ className="w-full pl-6 pr-12 py-3 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-xl text-xs font-bold uppercase tracking-wider outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30 transition-all text-dashboard-text placeholder:text-dashboard-text-secondary/40"
149
+ maxLength={3}
150
+ />
151
+ {isSearching && (
152
+ <Loader2 className="absolute right-4 top-1/2 -translate-y-1/2 size-4 animate-spin text-primary" />
153
+ )}
154
+ </div>
155
+ <button
156
+ onClick={handleSearchAndAdd}
157
+ disabled={isSearching || !searchCode}
158
+ className="px-6 bg-primary text-white rounded-xl hover:bg-primary/90 transition-all shadow-lg shadow-primary/10 disabled:opacity-50 active:scale-95 flex items-center justify-center shrink-0"
159
+ >
160
+ <Plus size={20} strokeWidth={2.5} />
161
+ </button>
162
+ </div>
163
+ </div>
164
+
165
+ {/* Quick Suggestions */}
166
+ <div className="space-y-4 pt-8 border-t border-dashboard-border/40">
167
+ <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary/60 ml-2">Recommended Protocols</span>
168
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-3">
169
+ {GLOBAL_LOCALE_DATABASE
170
+ .filter(loc => !supportedLocales.some(s => s.code === loc.code))
171
+ .slice(0, 6)
172
+ .map((loc) => (
173
+ <button
174
+ key={loc.code}
175
+ onClick={() => handleToggleLocale(loc)}
176
+ className="flex flex-col items-center gap-2 p-3 bg-dashboard-bg/30 border border-dashboard-border/40 hover:border-primary/40 hover:bg-primary/5 group transition-all duration-500 rounded-xl"
177
+ >
178
+ <div className="size-8 rounded-lg bg-dashboard-card/50 flex items-center justify-center border border-dashboard-border/40 group-hover:border-primary/20 transition-all shadow-inner">
179
+ <FlagIcon countryCode={loc.countryCode} />
180
+ </div>
181
+ <span className="text-[10px] font-bold text-dashboard-text-secondary/60 group-hover:text-primary transition-colors truncate w-full text-center">{loc.name}</span>
182
+ </button>
183
+ ))}
184
+ </div>
185
+ </div>
186
+
187
+ <div className="bg-primary/5 border border-primary/10 p-6 rounded-2xl flex items-start gap-4 group">
188
+ <div className="size-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0 border border-primary/20">
189
+ <Info size={16} />
190
+ </div>
191
+ <p className="text-xs text-dashboard-text-secondary/70 font-medium leading-relaxed">
192
+ JHITS operates on a modular localization engine. Provisioning a region here will automatically synchronize translation schemas and localized data structures across the entire infrastructure.
193
+ </p>
194
+ </div>
195
+ </motion.div>
196
+ </section>
197
+ );
198
+ }
@@ -0,0 +1,155 @@
1
+ import React from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Mail, Globe, Plus, Trash2, Smartphone, MapPin, Share2 } from 'lucide-react';
4
+ import { WebsiteSettings, LocaleDefinition, LocalizedContactInfo } from '../../../types/settings';
5
+ import { FlagIcon } from './FlagIcon';
6
+ import { AVAILABLE_PLATFORMS } from '../constants';
7
+ import { clsx, type ClassValue } from 'clsx';
8
+ import { twMerge } from 'tailwind-merge';
9
+
10
+ function cn(...inputs: ClassValue[]) {
11
+ return twMerge(clsx(inputs));
12
+ }
13
+
14
+ interface LocalizedContentSectionProps {
15
+ settings: WebsiteSettings;
16
+ activeTab: string;
17
+ onUpdateContact: (field: keyof LocalizedContactInfo, value: string) => void;
18
+ onAddSocial: () => void;
19
+ onUpdateSocial: (id: number, field: string, value: string) => void;
20
+ onRemoveSocial: (id: number) => void;
21
+ }
22
+
23
+ export function LocalizedContentSection({
24
+ settings,
25
+ activeTab,
26
+ onUpdateContact,
27
+ onAddSocial,
28
+ onUpdateSocial,
29
+ onRemoveSocial
30
+ }: LocalizedContentSectionProps) {
31
+ const contact = settings.localizedContactInfo?.[activeTab] || {};
32
+ const socials = settings.localizedSocials?.[activeTab] || [];
33
+
34
+ return (
35
+ <section className="space-y-8">
36
+ <div className="flex items-center gap-4">
37
+ <div className="size-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
38
+ <Share2 size={20} />
39
+ </div>
40
+ <h3 className="text-2xl lg:text-3xl font-bold text-dashboard-text leading-none tracking-tight">
41
+ Localized <span className="text-primary">Content</span>
42
+ </h3>
43
+ </div>
44
+
45
+ <motion.div
46
+ initial={{ opacity: 0, y: 20 }}
47
+ animate={{ opacity: 1, y: 0 }}
48
+ className="bg-dashboard-card/50 border border-dashboard-border/40 p-8 rounded-[2rem] space-y-8 relative overflow-hidden"
49
+ >
50
+ {/* 1. Contact Info Sub-section */}
51
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
52
+ <div className="space-y-2">
53
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Communication Vector ({activeTab})</label>
54
+ <div className="relative group">
55
+ <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" />
56
+ <input
57
+ type="tel"
58
+ value={contact.phoneNumber || ''}
59
+ onChange={(e) => onUpdateContact('phoneNumber', e.target.value)}
60
+ placeholder="INITIALIZE_PHONE"
61
+ 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"
62
+ />
63
+ </div>
64
+ </div>
65
+ <div className="space-y-2">
66
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Geospatial Coordinates ({activeTab})</label>
67
+ <div className="relative group">
68
+ <MapPin 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" />
69
+ <input
70
+ type="text"
71
+ value={contact.physicalAddress || ''}
72
+ onChange={(e) => onUpdateContact('physicalAddress', e.target.value)}
73
+ placeholder="STREET_ADDRESS_PROTOCOL"
74
+ 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"
75
+ />
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ {/* 2. Social Links Sub-section */}
81
+ <div className="pt-8 border-t border-dashboard-border/40">
82
+ <div className="flex items-center justify-between mb-4 ml-2">
83
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] opacity-60">Social Ecosystem Nodes ({activeTab})</label>
84
+ <button
85
+ onClick={onAddSocial}
86
+ className="flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-xl text-[10px] font-bold uppercase tracking-[0.15em] hover:bg-primary/90 transition-all shadow-lg shadow-primary/10 active:scale-95 group/btn overflow-hidden relative"
87
+ >
88
+ <div className="absolute inset-0 bg-linear-to-br from-white/20 to-transparent opacity-0 group-hover/btn:opacity-100 transition-opacity" />
89
+ <Plus size={12} strokeWidth={3} /> <span>Link Node</span>
90
+ </button>
91
+ </div>
92
+
93
+ <div className="space-y-3">
94
+ <AnimatePresence mode="popLayout">
95
+ {socials.length > 0 ? (
96
+ socials.map((social, idx) => {
97
+ const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
98
+ return (
99
+ <motion.div
100
+ layout
101
+ initial={{ opacity: 0, x: -20 }}
102
+ animate={{ opacity: 1, x: 0 }}
103
+ exit={{ opacity: 0, x: 20 }}
104
+ transition={{ delay: idx * 0.05 }}
105
+ key={social.id}
106
+ className="flex items-center gap-4 p-3 bg-dashboard-bg/30 border border-dashboard-border/40 rounded-xl group hover:border-primary/30 transition-all duration-500"
107
+ >
108
+ <div className="shrink-0 size-10 rounded-lg bg-dashboard-card/50 flex items-center justify-center border border-dashboard-border/40 text-dashboard-text-secondary/40 group-hover:text-primary group-hover:bg-primary/10 group-hover:border-primary/20 transition-all duration-500 shadow-inner">
109
+ {platform?.icon || <Globe size={18} />}
110
+ </div>
111
+
112
+ <div className="flex-1 flex flex-col md:flex-row items-center gap-4">
113
+ <div className="w-full md:w-32 relative">
114
+ <select
115
+ value={social.platform}
116
+ onChange={(e) => onUpdateSocial(social.id, 'platform', e.target.value)}
117
+ className="w-full bg-transparent border-none outline-none text-[10px] font-bold uppercase tracking-wider text-dashboard-text/90 cursor-pointer focus:text-primary transition-colors appearance-none"
118
+ >
119
+ <option value="" className="bg-neutral-900">PLATFORM</option>
120
+ {AVAILABLE_PLATFORMS.map(p => (<option key={p.name} value={p.name} className="bg-neutral-900">{p.name}</option>))}
121
+ </select>
122
+ </div>
123
+
124
+ <div className="hidden md:block h-5 w-px bg-dashboard-border/40" />
125
+
126
+ <input
127
+ type="url"
128
+ value={social.url}
129
+ onChange={(e) => onUpdateSocial(social.id, 'url', e.target.value)}
130
+ placeholder="URL_ENDPOINT"
131
+ className="flex-1 bg-transparent border-none outline-none text-xs font-bold text-dashboard-text/90 placeholder:text-dashboard-text-secondary/30 w-full"
132
+ />
133
+ </div>
134
+
135
+ <button
136
+ onClick={() => onRemoveSocial(social.id)}
137
+ className="p-2 text-dashboard-text-secondary/40 hover:text-red-500 hover:bg-red-500/10 rounded-lg opacity-0 group-hover:opacity-100 transition-all active:scale-90"
138
+ >
139
+ <Trash2 size={16} />
140
+ </button>
141
+ </motion.div>
142
+ );
143
+ })
144
+ ) : (
145
+ <div className="text-center py-12 bg-dashboard-card/20 border border-dashed border-dashboard-border/40 rounded-2xl">
146
+ <p className="text-[10px] text-dashboard-text-secondary/40 font-bold uppercase tracking-[0.2em]">No social platforms initialized</p>
147
+ </div>
148
+ )}
149
+ </AnimatePresence>
150
+ </div>
151
+ </div>
152
+ </motion.div>
153
+ </section>
154
+ );
155
+ }
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Search, Sparkles, Fingerprint } from 'lucide-react';
4
+ import { WebsiteSettings, LocalizedIdentity } 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 SeoIdentitySectionProps {
14
+ settings: WebsiteSettings;
15
+ activeTab: string;
16
+ onUpdateLocalized: (field: keyof LocalizedIdentity, value: string) => void;
17
+ }
18
+
19
+ export function SeoIdentitySection({
20
+ settings,
21
+ activeTab,
22
+ onUpdateLocalized
23
+ }: SeoIdentitySectionProps) {
24
+ const identity = settings.localizedIdentity?.[activeTab] || {};
25
+
26
+ return (
27
+ <section className="space-y-8">
28
+ <div className="flex items-center gap-4">
29
+ <div className="size-10 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shadow-inner">
30
+ <Fingerprint size={20} />
31
+ </div>
32
+ <h3 className="text-2xl lg:text-3xl font-bold text-dashboard-text leading-none tracking-tight">
33
+ Identity <span className="text-primary">& SEO</span>
34
+ </h3>
35
+ </div>
36
+
37
+ <motion.div
38
+ initial={{ opacity: 0, y: 20 }}
39
+ animate={{ opacity: 1, y: 0 }}
40
+ className="bg-dashboard-card/50 border border-dashboard-border/40 p-8 rounded-[2rem] space-y-6 relative overflow-hidden"
41
+ >
42
+ <div className="absolute top-0 right-0 w-64 h-64 blur-3xl opacity-5 bg-primary pointer-events-none" />
43
+
44
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
45
+ <div className="space-y-2">
46
+ <div className="flex items-center justify-between ml-2">
47
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] opacity-60">System Name ({activeTab})</label>
48
+ <Sparkles size={10} className="text-primary animate-pulse" />
49
+ </div>
50
+ <input
51
+ type="text"
52
+ value={identity.siteName || ''}
53
+ onChange={(e) => onUpdateLocalized('siteName', e.target.value)}
54
+ placeholder="INITIALIZE_NAME"
55
+ className="w-full px-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"
56
+ />
57
+ </div>
58
+ <div className="space-y-2">
59
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Strategic Tagline ({activeTab})</label>
60
+ <input
61
+ type="text"
62
+ value={identity.siteTagline || ''}
63
+ onChange={(e) => onUpdateLocalized('siteTagline', e.target.value)}
64
+ placeholder="DEFINE_TAGLINE"
65
+ className="w-full px-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"
66
+ />
67
+ </div>
68
+ </div>
69
+
70
+ <div className="space-y-2">
71
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Global Description Protocol ({activeTab})</label>
72
+ <textarea
73
+ value={identity.siteDescription || ''}
74
+ onChange={(e) => onUpdateLocalized('siteDescription', e.target.value)}
75
+ placeholder="A comprehensive architectural description for search engine crawlers..."
76
+ rows={3}
77
+ className="w-full px-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"
78
+ />
79
+ </div>
80
+
81
+ <div className="space-y-2">
82
+ <label className="text-[10px] font-bold text-dashboard-text-secondary/70 uppercase tracking-[0.15em] ml-2 opacity-60">Indexing Keywords ({activeTab})</label>
83
+ <div className="relative group">
84
+ <Search 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" />
85
+ <input
86
+ type="text"
87
+ value={identity.keywords || ''}
88
+ onChange={(e) => onUpdateLocalized('keywords', e.target.value)}
89
+ placeholder="comma, separated, identifiers"
90
+ 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"
91
+ />
92
+ </div>
93
+ <p className="text-[10px] text-dashboard-text-secondary/60 ml-4 font-medium opacity-60">Separated by commas. These tokens facilitate high-signal discovery during search engine scanning.</p>
94
+ </div>
95
+ </motion.div>
96
+ </section>
97
+ );
98
+ }
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { Globe, X } from 'lucide-react';
3
+ import { WebsiteSettings, LocaleDefinition, LocalizedSocialLink } from '../../../types/settings';
4
+ import { AVAILABLE_PLATFORMS } from '../constants';
5
+ import { FlagIcon } from './FlagIcon';
6
+
7
+ interface SocialLinksSectionProps {
8
+ settings: WebsiteSettings;
9
+ activeTab: string;
10
+ onTabChange: (code: string) => void;
11
+ activeLocales: LocaleDefinition[];
12
+ onAdd: () => void;
13
+ onUpdate: (id: number, field: string, value: string) => void;
14
+ onRemove: (id: number) => void;
15
+ }
16
+
17
+ export function SocialLinksSection({
18
+ settings,
19
+ activeTab,
20
+ onTabChange,
21
+ activeLocales,
22
+ onAdd,
23
+ onUpdate,
24
+ onRemove
25
+ }: SocialLinksSectionProps) {
26
+ const currentSocials = settings.localizedSocials?.[activeTab] || [];
27
+
28
+ return (
29
+ <section className="bg-dashboard-card/50 p-6 rounded-[2rem] border border-dashboard-border/40">
30
+ <div className="flex items-center justify-between border-b border-dashboard-border/40 pb-4 mb-4">
31
+ <div className="flex items-center gap-2 text-xl font-bold text-dashboard-text leading-none tracking-tight">
32
+ <Globe size={18} className="text-primary" />
33
+ Social <span className="text-primary">Links</span> ({activeTab.toUpperCase()})
34
+ </div>
35
+ <button
36
+ onClick={onAdd}
37
+ className="px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider bg-primary text-white rounded-xl hover:bg-primary/90 transition-all shadow-lg shadow-primary/10"
38
+ >
39
+ Add Social
40
+ </button>
41
+ </div>
42
+
43
+ <div className="flex gap-2 mb-4 overflow-x-auto pb-2 custom-scrollbar">
44
+ {activeLocales.map((loc) => (
45
+ <button
46
+ key={loc.code}
47
+ onClick={() => onTabChange(loc.code)}
48
+ className={`px-3 py-1.5 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-all flex items-center gap-2 shrink-0 border ${
49
+ activeTab === loc.code
50
+ ? 'bg-primary text-white border-primary shadow-lg shadow-primary/10'
51
+ : 'bg-dashboard-bg/30 text-dashboard-text-secondary/50 border-dashboard-border/40 hover:border-primary/30'
52
+ }`}
53
+ >
54
+ <FlagIcon code={loc.code} countryCode={loc.countryCode} />
55
+ {loc.code.toUpperCase()}
56
+ </button>
57
+ ))}
58
+ </div>
59
+
60
+ <div className="space-y-3">
61
+ {currentSocials.length > 0 ? (
62
+ currentSocials.map((social) => {
63
+ const platform = AVAILABLE_PLATFORMS.find(p => p.name === social.platform);
64
+ return (
65
+ <div key={social.id} className="flex items-center gap-3 p-3 bg-dashboard-bg/30 rounded-xl border border-dashboard-border/40">
66
+ <div className="flex-shrink-0 text-dashboard-text-secondary/40">
67
+ {platform?.icon || <Globe size={16} />}
68
+ </div>
69
+ <select
70
+ value={social.platform}
71
+ onChange={(e) => onUpdate(social.id, 'platform', e.target.value)}
72
+ className="flex-1 px-3 py-1.5 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-lg outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 text-sm font-bold text-dashboard-text/90 cursor-pointer appearance-none"
73
+ >
74
+ <option value="" className="bg-neutral-900">PLATFORM</option>
75
+ {AVAILABLE_PLATFORMS.map(p => (<option key={p.name} value={p.name} className="bg-neutral-900">{p.name}</option>))}
76
+ </select>
77
+ <input
78
+ type="url"
79
+ value={social.url}
80
+ onChange={(e) => onUpdate(social.id, 'url', e.target.value)}
81
+ placeholder="https://..."
82
+ className="flex-[2] px-3 py-1.5 bg-dashboard-bg/50 border border-dashboard-border/40 rounded-lg outline-none focus:ring-2 focus:ring-primary/10 focus:border-primary/30 text-sm font-bold text-dashboard-text/90 placeholder:text-dashboard-text-secondary/30"
83
+ />
84
+ <button onClick={() => onRemove(social.id)} className="p-2 text-dashboard-text-secondary/40 hover:text-red-500 hover:bg-red-500/10 rounded-lg transition-all">
85
+ <X size={14} />
86
+ </button>
87
+ </div>
88
+ );
89
+ })
90
+ ) : (
91
+ <p className="text-[10px] text-dashboard-text-secondary/50 font-bold uppercase tracking-wider text-center py-8 bg-dashboard-card/20 border border-dashed border-dashboard-border/40 rounded-xl">
92
+ No social links added for {activeTab.toUpperCase()}
93
+ </p>
94
+ )}
95
+ </div>
96
+ </section>
97
+ );
98
+ }