Ashraf Al-Kassem commited on
Commit
01feb7e
·
1 Parent(s): a3ba894

Update frontend settings pages

Browse files
frontend/src/app/(dashboard)/agency/settings/page.tsx CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import { useEffect, useState } from "react";
4
  import { apiClient } from "@/lib/api";
5
- import { Settings, Loader2, Save, Palette, SlidersHorizontal } from "lucide-react";
6
 
7
  type SettingsData = Record<string, any>;
8
 
@@ -49,45 +49,51 @@ export default function AgencySettingsPage() {
49
  setSettings(res.data.settings);
50
  setVersion(res.data.version);
51
  setDirty({});
52
- showToast("success", "Agency settings saved");
53
  } else {
54
- showToast("error", res.error || "Failed to save");
55
  }
56
  };
57
 
58
  if (loading) {
59
  return (
60
  <div className="flex items-center justify-center h-64">
61
- <Loader2 className="w-6 h-6 animate-spin text-slate-400" />
62
  </div>
63
  );
64
  }
65
 
66
  if (!settings) {
67
- return <p className="text-slate-400 p-8">No agency found or failed to load settings.</p>;
68
  }
69
 
70
  return (
71
- <div>
72
- <div className="flex items-center justify-between mb-6">
 
73
  <div>
74
- <h1 className="text-2xl font-bold tracking-tight flex items-center gap-2">
75
- <Settings className="w-6 h-6" /> Agency Settings
76
  </h1>
77
- <p className="text-slate-500 text-sm mt-1">Version {version}</p>
78
  </div>
79
  <button
80
  onClick={handleSave}
81
  disabled={saving || Object.keys(dirty).length === 0}
82
- className="flex items-center gap-2 px-4 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium"
83
  >
84
  {saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
85
  Save
86
  </button>
87
  </div>
88
 
 
89
  {toast && (
90
- <div className={`mb-4 px-4 py-2 rounded-lg text-sm font-medium ${toast.type === "success" ? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20" : "bg-red-500/10 text-red-400 border border-red-500/20"}`}>
 
 
 
 
91
  {toast.msg}
92
  </div>
93
  )}
@@ -95,7 +101,7 @@ export default function AgencySettingsPage() {
95
  <div className="space-y-6">
96
  {/* Branding */}
97
  <Section title="Branding" icon={Palette}>
98
- <Field label="Logo URL" type="text" value={settings.branding?.logo_url ?? ""} onChange={(v) => updateField("branding", "logo_url", v || null)} />
99
  <Field label="Primary Color" type="text" value={settings.branding?.primary_color ?? ""} onChange={(v) => updateField("branding", "primary_color", v || null)} placeholder="#6D28D9" />
100
  </Section>
101
 
@@ -107,12 +113,12 @@ export default function AgencySettingsPage() {
107
 
108
  {/* Limits Override */}
109
  <Section title="Limits Override" icon={SlidersHorizontal}>
110
- <Field label="Max Workspaces Override (leave empty for plan default)" type="number" value={settings.limits_override?.max_workspaces_override ?? ""} onChange={(v) => updateField("limits_override", "max_workspaces_override", v ? Number(v) : null)} />
111
  </Section>
112
 
113
  {/* Notifications */}
114
- <Section title="Notifications" icon={Settings}>
115
- <Field label="Agency Alert Email" type="email" value={settings.notifications?.agency_alert_email ?? ""} onChange={(v) => updateField("notifications", "agency_alert_email", v || null)} />
116
  </Section>
117
  </div>
118
  </div>
@@ -121,11 +127,11 @@ export default function AgencySettingsPage() {
121
 
122
  function Section({ title, icon: Icon, children }: { title: string; icon: any; children: React.ReactNode }) {
123
  return (
124
- <div className="bg-white/5 rounded-xl border border-white/10 p-6">
125
- <h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-4">
126
- <Icon className="w-5 h-5 text-violet-400" /> {title}
127
  </h2>
128
- <div className="space-y-4">{children}</div>
129
  </div>
130
  );
131
  }
@@ -133,8 +139,14 @@ function Section({ title, icon: Icon, children }: { title: string; icon: any; ch
133
  function Field({ label, type, value, onChange, ...props }: { label: string; type: string; value: any; onChange: (v: string) => void; [k: string]: any }) {
134
  return (
135
  <div>
136
- <label className="block text-sm font-medium text-slate-300 mb-1.5">{label}</label>
137
- <input type={type} value={value ?? ""} onChange={(e) => onChange(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50" {...props} />
 
 
 
 
 
 
138
  </div>
139
  );
140
  }
@@ -142,9 +154,15 @@ function Field({ label, type, value, onChange, ...props }: { label: string; type
142
  function SelectField({ label, value, options, onChange }: { label: string; value: string; options: string[]; onChange: (v: string) => void }) {
143
  return (
144
  <div>
145
- <label className="block text-sm font-medium text-slate-300 mb-1.5">{label}</label>
146
- <select value={value} onChange={(e) => onChange(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50">
147
- {options.map((o) => (<option key={o} value={o}>{o}</option>))}
 
 
 
 
 
 
148
  </select>
149
  </div>
150
  );
 
2
 
3
  import { useEffect, useState } from "react";
4
  import { apiClient } from "@/lib/api";
5
+ import { Settings, Loader2, Save, Palette, SlidersHorizontal, Bell } from "lucide-react";
6
 
7
  type SettingsData = Record<string, any>;
8
 
 
49
  setSettings(res.data.settings);
50
  setVersion(res.data.version);
51
  setDirty({});
52
+ showToast("success", "Agency settings saved successfully");
53
  } else {
54
+ showToast("error", res.error || "Failed to save settings");
55
  }
56
  };
57
 
58
  if (loading) {
59
  return (
60
  <div className="flex items-center justify-center h-64">
61
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#0F766E]" />
62
  </div>
63
  );
64
  }
65
 
66
  if (!settings) {
67
+ return <p className="text-slate-500 p-8">No agency found or failed to load settings.</p>;
68
  }
69
 
70
  return (
71
+ <div className="space-y-8 pb-10">
72
+ {/* Header */}
73
+ <div className="flex items-center justify-between">
74
  <div>
75
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900 flex items-center gap-2">
76
+ <Settings className="w-6 h-6 text-teal-700" /> Agency Settings
77
  </h1>
78
+ <p className="text-slate-500 text-sm mt-1">Configure your agency defaults and branding &middot; Version {version}</p>
79
  </div>
80
  <button
81
  onClick={handleSave}
82
  disabled={saving || Object.keys(dirty).length === 0}
83
+ className="flex items-center gap-2 px-5 py-2.5 bg-teal-700 text-white rounded-lg hover:bg-teal-800 disabled:opacity-50 disabled:cursor-not-allowed text-sm font-bold transition-all"
84
  >
85
  {saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
86
  Save
87
  </button>
88
  </div>
89
 
90
+ {/* Toast */}
91
  {toast && (
92
+ <div className={`px-4 py-3 rounded-xl text-sm font-medium ${
93
+ toast.type === "success"
94
+ ? "bg-emerald-50 text-emerald-700 border border-emerald-100"
95
+ : "bg-red-50 text-red-700 border border-red-100"
96
+ }`}>
97
  {toast.msg}
98
  </div>
99
  )}
 
101
  <div className="space-y-6">
102
  {/* Branding */}
103
  <Section title="Branding" icon={Palette}>
104
+ <Field label="Logo URL" type="text" value={settings.branding?.logo_url ?? ""} onChange={(v) => updateField("branding", "logo_url", v || null)} placeholder="https://example.com/logo.png" />
105
  <Field label="Primary Color" type="text" value={settings.branding?.primary_color ?? ""} onChange={(v) => updateField("branding", "primary_color", v || null)} placeholder="#6D28D9" />
106
  </Section>
107
 
 
113
 
114
  {/* Limits Override */}
115
  <Section title="Limits Override" icon={SlidersHorizontal}>
116
+ <Field label="Max Workspaces Override" type="number" value={settings.limits_override?.max_workspaces_override ?? ""} onChange={(v) => updateField("limits_override", "max_workspaces_override", v ? Number(v) : null)} placeholder="Leave empty to use plan default" />
117
  </Section>
118
 
119
  {/* Notifications */}
120
+ <Section title="Notifications" icon={Bell}>
121
+ <Field label="Agency Alert Email" type="email" value={settings.notifications?.agency_alert_email ?? ""} onChange={(v) => updateField("notifications", "agency_alert_email", v || null)} placeholder="alerts@agency.com" />
122
  </Section>
123
  </div>
124
  </div>
 
127
 
128
  function Section({ title, icon: Icon, children }: { title: string; icon: any; children: React.ReactNode }) {
129
  return (
130
+ <div className="bg-white p-6 md:p-8 rounded-xl border border-slate-100 shadow-sm">
131
+ <h2 className="text-sm font-bold uppercase tracking-wider text-slate-500 flex items-center gap-2 mb-5">
132
+ <Icon className="w-4 h-4 text-teal-600" /> {title}
133
  </h2>
134
+ <div className="space-y-5">{children}</div>
135
  </div>
136
  );
137
  }
 
139
  function Field({ label, type, value, onChange, ...props }: { label: string; type: string; value: any; onChange: (v: string) => void; [k: string]: any }) {
140
  return (
141
  <div>
142
+ <label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1.5">{label}</label>
143
+ <input
144
+ type={type}
145
+ value={value ?? ""}
146
+ onChange={(e) => onChange(e.target.value)}
147
+ className="w-full p-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-900 focus:ring-2 focus:ring-teal-500/20 focus:border-teal-500 outline-none transition-all"
148
+ {...props}
149
+ />
150
  </div>
151
  );
152
  }
 
154
  function SelectField({ label, value, options, onChange }: { label: string; value: string; options: string[]; onChange: (v: string) => void }) {
155
  return (
156
  <div>
157
+ <label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1.5">{label}</label>
158
+ <select
159
+ value={value}
160
+ onChange={(e) => onChange(e.target.value)}
161
+ className="w-full p-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-900 focus:ring-2 focus:ring-teal-500/20 focus:border-teal-500 outline-none transition-all"
162
+ >
163
+ {options.map((o) => (
164
+ <option key={o} value={o}>{o.charAt(0).toUpperCase() + o.slice(1)}</option>
165
+ ))}
166
  </select>
167
  </div>
168
  );
frontend/src/app/(dashboard)/settings/page.tsx CHANGED
@@ -67,58 +67,68 @@ export default function WorkspaceSettingsPage() {
67
  setSettings(res.data.settings);
68
  setVersion(res.data.version);
69
  setDirty({});
70
- showToast("success", "Settings saved");
71
  } else {
72
- showToast("error", res.error || "Failed to save");
73
  }
74
  };
75
 
76
  if (loading) {
77
  return (
78
  <div className="flex items-center justify-center h-64">
79
- <Loader2 className="w-6 h-6 animate-spin text-slate-400" />
80
  </div>
81
  );
82
  }
83
 
84
  if (!settings) {
85
- return <p className="text-slate-400 p-8">Failed to load settings.</p>;
86
  }
87
 
88
  const s = settings[activeTab] || {};
89
 
90
  return (
91
- <div>
92
- <div className="flex items-center justify-between mb-6">
 
93
  <div>
94
- <h1 className="text-2xl font-bold tracking-tight flex items-center gap-2">
95
- <Settings className="w-6 h-6" /> Workspace Settings
96
  </h1>
97
- <p className="text-slate-500 text-sm mt-1">Version {version}</p>
98
  </div>
99
  <button
100
  onClick={handleSave}
101
  disabled={saving || Object.keys(dirty).length === 0}
102
- className="flex items-center gap-2 px-4 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-700 disabled:opacity-50 disabled:cursor-not-allowed text-sm font-medium"
103
  >
104
  {saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
105
  Save Changes
106
  </button>
107
  </div>
108
 
 
109
  {toast && (
110
- <div className={`mb-4 px-4 py-2 rounded-lg text-sm font-medium ${toast.type === "success" ? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20" : "bg-red-500/10 text-red-400 border border-red-500/20"}`}>
 
 
 
 
111
  {toast.msg}
112
  </div>
113
  )}
114
 
115
  {/* Tabs */}
116
- <div className="flex gap-1 border-b border-white/10 mb-6">
117
  {TABS.map((tab) => (
118
  <button
119
  key={tab.key}
120
  onClick={() => setActiveTab(tab.key)}
121
- className={`flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors ${activeTab === tab.key ? "border-violet-500 text-violet-300" : "border-transparent text-slate-400 hover:text-white"}`}
 
 
 
 
122
  >
123
  <tab.icon className="w-4 h-4" />
124
  {tab.label}
@@ -127,11 +137,11 @@ export default function WorkspaceSettingsPage() {
127
  </div>
128
 
129
  {/* Fields */}
130
- <div className="bg-white/5 rounded-xl border border-white/10 p-6 space-y-5">
131
  {activeTab === "general" && (
132
  <>
133
- <Field label="Name Override" type="text" value={s.name_override ?? ""} onChange={(v) => updateField("general", "name_override", v || null)} />
134
- <Field label="Logo URL" type="text" value={s.logo_url ?? ""} onChange={(v) => updateField("general", "logo_url", v || null)} />
135
  <SelectField label="Timezone" value={s.timezone} options={["UTC", "US/Eastern", "US/Pacific", "Europe/London", "Asia/Dubai", "Asia/Tokyo"]} onChange={(v) => updateField("general", "timezone", v)} />
136
  <SelectField label="Default Language" value={s.default_language} options={["en", "ar", "es", "fr", "de", "zh"]} onChange={(v) => updateField("general", "default_language", v)} />
137
  </>
@@ -139,8 +149,8 @@ export default function WorkspaceSettingsPage() {
139
  {activeTab === "messaging" && (
140
  <>
141
  <Field label="Default Reply Delay (seconds)" type="number" value={s.default_reply_delay_seconds} onChange={(v) => updateField("messaging", "default_reply_delay_seconds", Number(v))} />
142
- <Toggle label="Fallback Message Enabled" value={s.fallback_message_enabled} onChange={(v) => updateField("messaging", "fallback_message_enabled", v)} />
143
- <Toggle label="Auto-Retry Failed Dispatch" value={s.auto_retry_failed_dispatch} onChange={(v) => updateField("messaging", "auto_retry_failed_dispatch", v)} />
144
  </>
145
  )}
146
  {activeTab === "ai" && (
@@ -148,19 +158,19 @@ export default function WorkspaceSettingsPage() {
148
  <SelectField label="Default Model" value={s.default_model} options={["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-flash"]} onChange={(v) => updateField("ai", "default_model", v)} />
149
  <Field label="Temperature" type="number" value={s.temperature} step="0.1" min="0" max="2" onChange={(v) => updateField("ai", "temperature", Number(v))} />
150
  <Field label="Max Tokens" type="number" value={s.max_tokens} onChange={(v) => updateField("ai", "max_tokens", Number(v))} />
151
- <Toggle label="Guardrails Enabled" value={s.guardrails_enabled} onChange={(v) => updateField("ai", "guardrails_enabled", v)} />
152
  </>
153
  )}
154
  {activeTab === "automation" && (
155
  <>
156
- <Toggle label="Auto-Publish" value={s.auto_publish} onChange={(v) => updateField("automation", "auto_publish", v)} />
157
  <Field label="Draft Expiry (days)" type="number" value={s.draft_expiry_days} onChange={(v) => updateField("automation", "draft_expiry_days", Number(v))} />
158
  </>
159
  )}
160
  {activeTab === "notifications" && (
161
  <>
162
- <Toggle label="Email Notifications Enabled" value={s.email_notifications_enabled} onChange={(v) => updateField("notifications", "email_notifications_enabled", v)} />
163
- <Field label="Webhook URL" type="text" value={s.webhook_url ?? ""} onChange={(v) => updateField("notifications", "webhook_url", v || null)} />
164
  </>
165
  )}
166
  </div>
@@ -173,12 +183,12 @@ export default function WorkspaceSettingsPage() {
173
  function Field({ label, type, value, onChange, ...props }: { label: string; type: string; value: any; onChange: (v: string) => void; [k: string]: any }) {
174
  return (
175
  <div>
176
- <label className="block text-sm font-medium text-slate-300 mb-1.5">{label}</label>
177
  <input
178
  type={type}
179
  value={value ?? ""}
180
  onChange={(e) => onChange(e.target.value)}
181
- className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50"
182
  {...props}
183
  />
184
  </div>
@@ -188,11 +198,11 @@ function Field({ label, type, value, onChange, ...props }: { label: string; type
188
  function SelectField({ label, value, options, onChange }: { label: string; value: string; options: string[]; onChange: (v: string) => void }) {
189
  return (
190
  <div>
191
- <label className="block text-sm font-medium text-slate-300 mb-1.5">{label}</label>
192
  <select
193
  value={value}
194
  onChange={(e) => onChange(e.target.value)}
195
- className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50"
196
  >
197
  {options.map((o) => (
198
  <option key={o} value={o}>{o}</option>
@@ -202,16 +212,19 @@ function SelectField({ label, value, options, onChange }: { label: string; value
202
  );
203
  }
204
 
205
- function Toggle({ label, value, onChange }: { label: string; value: boolean; onChange: (v: boolean) => void }) {
206
  return (
207
- <div className="flex items-center justify-between">
208
- <span className="text-sm font-medium text-slate-300">{label}</span>
 
 
 
209
  <button
210
  type="button"
211
  onClick={() => onChange(!value)}
212
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${value ? "bg-violet-600" : "bg-white/10"}`}
213
  >
214
- <span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${value ? "translate-x-6" : "translate-x-1"}`} />
215
  </button>
216
  </div>
217
  );
 
67
  setSettings(res.data.settings);
68
  setVersion(res.data.version);
69
  setDirty({});
70
+ showToast("success", "Settings saved successfully");
71
  } else {
72
+ showToast("error", res.error || "Failed to save settings");
73
  }
74
  };
75
 
76
  if (loading) {
77
  return (
78
  <div className="flex items-center justify-center h-64">
79
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#0F766E]" />
80
  </div>
81
  );
82
  }
83
 
84
  if (!settings) {
85
+ return <p className="text-slate-500 p-8">Failed to load settings.</p>;
86
  }
87
 
88
  const s = settings[activeTab] || {};
89
 
90
  return (
91
+ <div className="space-y-8 pb-10">
92
+ {/* Header */}
93
+ <div className="flex items-center justify-between">
94
  <div>
95
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900 flex items-center gap-2">
96
+ <Settings className="w-6 h-6 text-teal-700" /> Workspace Settings
97
  </h1>
98
+ <p className="text-slate-500 text-sm mt-1">Manage your workspace configuration &middot; Version {version}</p>
99
  </div>
100
  <button
101
  onClick={handleSave}
102
  disabled={saving || Object.keys(dirty).length === 0}
103
+ className="flex items-center gap-2 px-5 py-2.5 bg-teal-700 text-white rounded-lg hover:bg-teal-800 disabled:opacity-50 disabled:cursor-not-allowed text-sm font-bold transition-all"
104
  >
105
  {saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
106
  Save Changes
107
  </button>
108
  </div>
109
 
110
+ {/* Toast */}
111
  {toast && (
112
+ <div className={`px-4 py-3 rounded-xl text-sm font-medium flex items-center gap-2 ${
113
+ toast.type === "success"
114
+ ? "bg-emerald-50 text-emerald-700 border border-emerald-100"
115
+ : "bg-red-50 text-red-700 border border-red-100"
116
+ }`}>
117
  {toast.msg}
118
  </div>
119
  )}
120
 
121
  {/* Tabs */}
122
+ <div className="flex border-b border-slate-100 bg-slate-50/50 rounded-t-xl">
123
  {TABS.map((tab) => (
124
  <button
125
  key={tab.key}
126
  onClick={() => setActiveTab(tab.key)}
127
+ className={`flex items-center gap-2 px-6 py-3.5 text-sm font-bold border-b-2 transition-all ${
128
+ activeTab === tab.key
129
+ ? "border-teal-600 text-teal-700 bg-white"
130
+ : "border-transparent text-slate-400 hover:text-slate-600"
131
+ }`}
132
  >
133
  <tab.icon className="w-4 h-4" />
134
  {tab.label}
 
137
  </div>
138
 
139
  {/* Fields */}
140
+ <div className="bg-white p-6 md:p-8 rounded-xl border border-slate-100 shadow-sm space-y-6">
141
  {activeTab === "general" && (
142
  <>
143
+ <Field label="Name Override" type="text" value={s.name_override ?? ""} onChange={(v) => updateField("general", "name_override", v || null)} placeholder="Leave empty to use workspace name" />
144
+ <Field label="Logo URL" type="text" value={s.logo_url ?? ""} onChange={(v) => updateField("general", "logo_url", v || null)} placeholder="https://example.com/logo.png" />
145
  <SelectField label="Timezone" value={s.timezone} options={["UTC", "US/Eastern", "US/Pacific", "Europe/London", "Asia/Dubai", "Asia/Tokyo"]} onChange={(v) => updateField("general", "timezone", v)} />
146
  <SelectField label="Default Language" value={s.default_language} options={["en", "ar", "es", "fr", "de", "zh"]} onChange={(v) => updateField("general", "default_language", v)} />
147
  </>
 
149
  {activeTab === "messaging" && (
150
  <>
151
  <Field label="Default Reply Delay (seconds)" type="number" value={s.default_reply_delay_seconds} onChange={(v) => updateField("messaging", "default_reply_delay_seconds", Number(v))} />
152
+ <Toggle label="Fallback Message Enabled" description="Send a fallback message when AI cannot generate a response" value={s.fallback_message_enabled} onChange={(v) => updateField("messaging", "fallback_message_enabled", v)} />
153
+ <Toggle label="Auto-Retry Failed Dispatch" description="Automatically retry failed message deliveries with exponential backoff" value={s.auto_retry_failed_dispatch} onChange={(v) => updateField("messaging", "auto_retry_failed_dispatch", v)} />
154
  </>
155
  )}
156
  {activeTab === "ai" && (
 
158
  <SelectField label="Default Model" value={s.default_model} options={["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-flash"]} onChange={(v) => updateField("ai", "default_model", v)} />
159
  <Field label="Temperature" type="number" value={s.temperature} step="0.1" min="0" max="2" onChange={(v) => updateField("ai", "temperature", Number(v))} />
160
  <Field label="Max Tokens" type="number" value={s.max_tokens} onChange={(v) => updateField("ai", "max_tokens", Number(v))} />
161
+ <Toggle label="Guardrails Enabled" description="Enforce safety guardrails on AI-generated responses" value={s.guardrails_enabled} onChange={(v) => updateField("ai", "guardrails_enabled", v)} />
162
  </>
163
  )}
164
  {activeTab === "automation" && (
165
  <>
166
+ <Toggle label="Auto-Publish" description="Automatically publish new automation flows" value={s.auto_publish} onChange={(v) => updateField("automation", "auto_publish", v)} />
167
  <Field label="Draft Expiry (days)" type="number" value={s.draft_expiry_days} onChange={(v) => updateField("automation", "draft_expiry_days", Number(v))} />
168
  </>
169
  )}
170
  {activeTab === "notifications" && (
171
  <>
172
+ <Toggle label="Email Notifications Enabled" description="Receive email notifications for important workspace events" value={s.email_notifications_enabled} onChange={(v) => updateField("notifications", "email_notifications_enabled", v)} />
173
+ <Field label="Webhook URL" type="text" value={s.webhook_url ?? ""} onChange={(v) => updateField("notifications", "webhook_url", v || null)} placeholder="https://example.com/webhook" />
174
  </>
175
  )}
176
  </div>
 
183
  function Field({ label, type, value, onChange, ...props }: { label: string; type: string; value: any; onChange: (v: string) => void; [k: string]: any }) {
184
  return (
185
  <div>
186
+ <label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1.5">{label}</label>
187
  <input
188
  type={type}
189
  value={value ?? ""}
190
  onChange={(e) => onChange(e.target.value)}
191
+ className="w-full p-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-900 focus:ring-2 focus:ring-teal-500/20 focus:border-teal-500 outline-none transition-all"
192
  {...props}
193
  />
194
  </div>
 
198
  function SelectField({ label, value, options, onChange }: { label: string; value: string; options: string[]; onChange: (v: string) => void }) {
199
  return (
200
  <div>
201
+ <label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1.5">{label}</label>
202
  <select
203
  value={value}
204
  onChange={(e) => onChange(e.target.value)}
205
+ className="w-full p-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-900 focus:ring-2 focus:ring-teal-500/20 focus:border-teal-500 outline-none transition-all"
206
  >
207
  {options.map((o) => (
208
  <option key={o} value={o}>{o}</option>
 
212
  );
213
  }
214
 
215
+ function Toggle({ label, description, value, onChange }: { label: string; description?: string; value: boolean; onChange: (v: boolean) => void }) {
216
  return (
217
+ <div className="flex items-center justify-between py-2 border-b border-slate-50 last:border-0">
218
+ <div>
219
+ <span className="text-sm font-medium text-slate-700">{label}</span>
220
+ {description && <p className="text-xs text-slate-400 mt-0.5">{description}</p>}
221
+ </div>
222
  <button
223
  type="button"
224
  onClick={() => onChange(!value)}
225
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${value ? "bg-teal-600" : "bg-slate-200"}`}
226
  >
227
+ <span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow-sm transition-transform ${value ? "translate-x-6" : "translate-x-1"}`} />
228
  </button>
229
  </div>
230
  );