UI/UX improvements: Premium redesign with navigation restructure, glassmorphism stat cards, and background integration
This commit is contained in:
301
src/pages/SettingsPage.tsx
Normal file
301
src/pages/SettingsPage.tsx
Normal file
@@ -0,0 +1,301 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user