302 lines
13 KiB
TypeScript
302 lines
13 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import DashboardLayout from '../layout/DashboardLayout';
|
|
import { useDashboardScaffold } from '../hooks/useDashboardScaffold';
|
|
import { SectionCard } from '../components/SectionCard';
|
|
import PrimaryButton from '../components/PrimaryButton';
|
|
|
|
type ToggleGroup<T extends string> = Record<T, boolean>;
|
|
|
|
type SecurityToggle = 'twoFactor' | 'deviceApprovals' | 'sessionTimeout';
|
|
type NotificationToggle = 'email' | 'sms' | 'whatsapp' | 'slack';
|
|
|
|
type WorkspacePreference = {
|
|
timezone: string;
|
|
language: string;
|
|
weekStartsOn: string;
|
|
};
|
|
|
|
const SettingsPage: React.FC = () => {
|
|
const { topBarProps } = useDashboardScaffold();
|
|
const { t } = useTranslation();
|
|
|
|
const [security, setSecurity] = useState<ToggleGroup<SecurityToggle>>({
|
|
twoFactor: true,
|
|
deviceApprovals: true,
|
|
sessionTimeout: false,
|
|
});
|
|
|
|
const [notifications, setNotifications] = useState<ToggleGroup<NotificationToggle>>({
|
|
email: true,
|
|
sms: false,
|
|
whatsapp: true,
|
|
slack: true,
|
|
});
|
|
|
|
const [workspace, setWorkspace] = useState<WorkspacePreference>({
|
|
timezone: 'Asia/Kolkata (IST)',
|
|
language: 'English',
|
|
weekStartsOn: 'Monday',
|
|
});
|
|
|
|
const toggleSecurity = (key: SecurityToggle) => {
|
|
setSecurity((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
};
|
|
|
|
const toggleNotification = (key: NotificationToggle) => {
|
|
setNotifications((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
};
|
|
|
|
const handleWorkspaceChange = (field: keyof WorkspacePreference) => (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
setWorkspace((prev) => ({ ...prev, [field]: event.target.value }));
|
|
};
|
|
|
|
return (
|
|
<DashboardLayout topBarProps={topBarProps}>
|
|
<div className="flex flex-col gap-10">
|
|
<div className="space-y-3">
|
|
<h1 className="font-playfair text-4xl text-cream">{t('settings.workspace_settings')}</h1>
|
|
<p className="max-w-2xl text-sm text-cream/70">
|
|
{t('settings.workspace_settings_desc')}
|
|
</p>
|
|
</div>
|
|
|
|
<SectionCard
|
|
title={t('settings.security_posture')}
|
|
subtitle={t('settings.security_posture_subtitle')}
|
|
>
|
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
{(
|
|
[
|
|
{
|
|
key: 'twoFactor' as SecurityToggle,
|
|
label: 'Two-factor authentication',
|
|
description: 'Require OTP or authenticator approval on every new login.',
|
|
},
|
|
{
|
|
key: 'deviceApprovals' as SecurityToggle,
|
|
label: 'Device approvals',
|
|
description: 'Concierge pings admins when a new laptop or phone signs in.',
|
|
},
|
|
{
|
|
key: 'sessionTimeout' as SecurityToggle,
|
|
label: '90 minute session timeout',
|
|
description: 'Auto-sign out idle consoles to protect sensitive briefs.',
|
|
},
|
|
]
|
|
).map((item) => (
|
|
<div
|
|
key={item.key}
|
|
className="flex flex-col gap-3 rounded-2xl border border-luxury-gold/25 bg-[#0B312A]/70 p-5"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-sm font-semibold text-cream">{item.label}</p>
|
|
<p className="text-xs text-cream/60">{item.description}</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => toggleSecurity(item.key)}
|
|
className={`relative h-7 w-12 rounded-full border-2 border-luxury-gold/50 transition-colors duration-200 ${security[item.key] ? 'bg-luxury-gold/80' : 'bg-transparent'}`}
|
|
aria-pressed={security[item.key]}
|
|
>
|
|
<span
|
|
className={`absolute top-1/2 h-5 w-5 -translate-y-1/2 rounded-full bg-cream transition-transform duration-200 ${security[item.key] ? 'translate-x-5' : 'translate-x-1'}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-4 rounded-2xl bg-[#0B312A]/70 p-5">
|
|
<div className="max-w-xl">
|
|
<h3 className="text-sm uppercase tracking-[0.18em] text-cream/70">Admin alerting</h3>
|
|
<p className="text-xs text-cream/60">
|
|
Pennyzo will escalate high-risk sign-ins to {security.deviceApprovals ? 'leadership Slack' : 'email only'} and include geo-location snapshots.
|
|
</p>
|
|
</div>
|
|
<PrimaryButton label="View audit trail" variant="outline" className="min-h-[46px] px-5" />
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
title="Notification matrix"
|
|
subtitle="Choose where credit events, agent output, and approvals are broadcast"
|
|
>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
{(
|
|
[
|
|
{
|
|
key: 'email' as NotificationToggle,
|
|
label: 'Email digests',
|
|
description: 'Daily summary of agent runs, documents prepared, and credit snapshots.',
|
|
},
|
|
{
|
|
key: 'sms' as NotificationToggle,
|
|
label: 'SMS nudges',
|
|
description: 'Instant message when concierge requires leadership approval.',
|
|
},
|
|
{
|
|
key: 'whatsapp' as NotificationToggle,
|
|
label: 'WhatsApp alerts',
|
|
description: 'Pipeline wins and budget approvals piped to the CXO group.',
|
|
},
|
|
{
|
|
key: 'slack' as NotificationToggle,
|
|
label: 'Slack channel updates',
|
|
description: 'Live feed in #pennyzo-warroom of agent completions and credit usage.',
|
|
},
|
|
]
|
|
).map((item) => (
|
|
<div
|
|
key={item.key}
|
|
className="flex flex-col gap-3 rounded-2xl border border-luxury-gold/25 bg-[#0B312A]/70 p-5"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div>
|
|
<p className="text-sm font-semibold text-cream">{item.label}</p>
|
|
<p className="text-xs text-cream/60">{item.description}</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => toggleNotification(item.key)}
|
|
className={`relative h-7 w-12 rounded-full border-2 border-luxury-gold/50 transition-colors duration-200 ${notifications[item.key] ? 'bg-luxury-gold/80' : 'bg-transparent'}`}
|
|
aria-pressed={notifications[item.key]}
|
|
>
|
|
<span
|
|
className={`absolute top-1/2 h-5 w-5 -translate-y-1/2 rounded-full bg-cream transition-transform duration-200 ${notifications[item.key] ? 'translate-x-5' : 'translate-x-1'}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-luxury-gold/20 bg-[#041815]/80 p-6">
|
|
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
<div>
|
|
<h3 className="text-sm uppercase tracking-[0.18em] text-cream/70">Escalation window</h3>
|
|
<p className="text-xs text-cream/60">Escalate to leadership if concierge waits more than 4 hours for approvals.</p>
|
|
</div>
|
|
<PrimaryButton label="Edit SLA" variant="outline" className="min-h-[46px] px-5" />
|
|
</div>
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
title="Workspace defaults"
|
|
subtitle="Default context for new tasks, documents, and cross-team handoffs"
|
|
>
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
<label className="flex flex-col gap-2 text-sm uppercase tracking-[0.18em] text-cream/60">
|
|
Timezone
|
|
<select
|
|
value={workspace.timezone}
|
|
onChange={handleWorkspaceChange('timezone')}
|
|
className="rounded-xl border border-luxury-gold/40 bg-[#041815] px-4 py-3 text-sm text-cream focus:border-luxury-gold focus:outline-none"
|
|
>
|
|
<option className="bg-[#041815]">Asia/Kolkata (IST)</option>
|
|
<option className="bg-[#041815]">Asia/Dubai (GST)</option>
|
|
<option className="bg-[#041815]">Europe/London (BST)</option>
|
|
</select>
|
|
</label>
|
|
<label className="flex flex-col gap-2 text-sm uppercase tracking-[0.18em] text-cream/60">
|
|
Language for briefs
|
|
<select
|
|
value={workspace.language}
|
|
onChange={handleWorkspaceChange('language')}
|
|
className="rounded-xl border border-luxury-gold/40 bg-[#041815] px-4 py-3 text-sm text-cream focus:border-luxury-gold focus:outline-none"
|
|
>
|
|
<option className="bg-[#041815]">English</option>
|
|
<option className="bg-[#041815]">हिन्दी</option>
|
|
<option className="bg-[#041815]">தமிழ்</option>
|
|
</select>
|
|
</label>
|
|
<label className="flex flex-col gap-2 text-sm uppercase tracking-[0.18em] text-cream/60">
|
|
Week starts on
|
|
<select
|
|
value={workspace.weekStartsOn}
|
|
onChange={handleWorkspaceChange('weekStartsOn')}
|
|
className="rounded-xl border border-luxury-gold/40 bg-[#041815] px-4 py-3 text-sm text-cream focus:border-luxury-gold focus:outline-none"
|
|
>
|
|
<option className="bg-[#041815]">Monday</option>
|
|
<option className="bg-[#041815]">Sunday</option>
|
|
</select>
|
|
</label>
|
|
<div className="flex flex-col gap-2 text-sm uppercase tracking-[0.18em] text-cream/60">
|
|
Preferred export format
|
|
<div className="flex flex-wrap gap-3">
|
|
{['CSV', 'Excel', 'Google Sheets'].map((option) => (
|
|
<button
|
|
key={option}
|
|
type="button"
|
|
className={`rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] transition-colors ${option === 'CSV' ? 'border-luxury-gold text-luxury-gold' : 'border-luxury-gold/30 text-cream/70 hover:border-luxury-gold/60 hover:text-cream'}`}
|
|
>
|
|
{option}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center justify-end gap-3">
|
|
<PrimaryButton label="Reset to defaults" variant="outline" className="min-h-[46px] px-5" />
|
|
<PrimaryButton label="Save workspace settings" className="min-h-[46px] px-6" />
|
|
</div>
|
|
</SectionCard>
|
|
|
|
<SectionCard
|
|
title="Connected integrations"
|
|
subtitle="Concierge keeps your finance, HR, and CRM stacks synchronised"
|
|
>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
{[{
|
|
name: 'Slack',
|
|
status: 'Live',
|
|
description: 'Real-time agent completions in #pennyzo-warroom channel.',
|
|
}, {
|
|
name: 'HubSpot',
|
|
status: 'Live',
|
|
description: 'Sync CRM deals to concierge for follow-up summaries.',
|
|
}, {
|
|
name: 'Google Drive',
|
|
status: 'Pending approval',
|
|
description: 'Route auto-prepped decks to investor-ready folders.',
|
|
}, {
|
|
name: 'SAP SuccessFactors',
|
|
status: 'Live',
|
|
description: 'Push payroll exports and compliance letters on schedule.',
|
|
}].map((integration) => (
|
|
<div
|
|
key={integration.name}
|
|
className="flex flex-col gap-3 rounded-2xl border border-luxury-gold/25 bg-[#0B312A]/70 p-5"
|
|
>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<h3 className="text-base font-semibold text-cream">{integration.name}</h3>
|
|
<span
|
|
className={`rounded-full border px-3 py-1 text-xs uppercase tracking-[0.16em] ${
|
|
integration.status === 'Live'
|
|
? 'border-emerald-400/60 text-emerald-300'
|
|
: 'border-luxury-gold/50 text-luxury-gold'
|
|
}`}
|
|
>
|
|
{integration.status}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-cream/60">{integration.description}</p>
|
|
<div className="flex gap-2">
|
|
<PrimaryButton label="Manage" variant="outline" className="min-h-[42px] px-4" />
|
|
<PrimaryButton label="Logs" variant="outline" className="min-h-[42px] px-4" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
};
|
|
|
|
export default SettingsPage;
|