import { useEffect, useRef, useState, type ChangeEvent } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
User,
Bell,
Shield,
Database,
Monitor,
RefreshCw,
Power,
Server,
Smartphone,
Wifi,
Copy,
Check,
ChevronDown,
LogOut,
Pencil,
MessageCircle,
type LucideIcon,
} from 'lucide-react';
import { useStore } from '@/store/useStore';
import { useCurrency, CURRENCY_OPTIONS } from '@/store/useCurrencyStore';
import type { CurrencyCode } from '@/store/useCurrencyStore';
import { API_URL } from '@/lib/api';
import { fetchCommsSettings, testCommsProviderConnection, updateCommsSettings } from '@/lib/commsApi';
import type { CommsProvider, CommsSettings } from '@/types/commsTypes';
import {
clearVelocityToken,
getVelocityToken,
normalizeVelocityRole,
resolveVelocityFirstName,
uploadVelocityAvatar,
} from '@/lib/velocityPlatformClient';
// ── Design tokens (matching inventory glassmorphism) ─────────────────────────
const GLASS = {
background: 'rgba(14, 16, 21, 0.72)',
border: '1px solid rgba(255,255,255,0.08)',
backdropFilter: 'blur(18px)',
WebkitBackdropFilter: 'blur(18px)',
} as const;
const INNER_SURFACE = {
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.07)',
} as const;
// ── Shared primitives ────────────────────────────────────────────────────────
function GlassCard({
children,
delay = 0,
className = '',
}: {
children: React.ReactNode;
delay?: number;
className?: string;
}) {
return (
{children}
);
}
function SectionHeader({ icon: Icon, title, accent = 'hsl(var(--accent))' }: { icon: LucideIcon; title: string; accent?: string }) {
return (
);
}
function SettingsRow({
label,
description,
children,
danger = false,
}: {
label: string;
description?: string;
children: React.ReactNode;
danger?: boolean;
}) {
return (
{label}
{description && (
{description}
)}
{children}
);
}
// ── Toggle ───────────────────────────────────────────────────────────────────
function Toggle({ enabled, onChange }: { enabled: boolean; onChange: (v: boolean) => void }) {
return (
);
}
// ── Dark Select ──────────────────────────────────────────────────────────────
function DarkSelect({
value,
onChange,
options,
}: {
value: string;
onChange: (v: string) => void;
options: { value: string; label: string }[];
}) {
const [open, setOpen] = useState(false);
const current = options.find((o) => o.value === value) ?? options[0];
return (
{open && (
{options.map((opt) => (
))}
)}
{open &&
setOpen(false)} />}
);
}
// ── Ghost button ─────────────────────────────────────────────────────────────
function GhostButton({ children, onClick, danger = false }: { children: React.ReactNode; onClick?: () => void; danger?: boolean }) {
return (
{children}
);
}
// ── Text input ───────────────────────────────────────────────────────────────
// ── System Status ────────────────────────────────────────────────────────────
function SystemStatusCard() {
const { status, updateStatus } = useStore();
const lastSync = status.lastSync instanceof Date ? status.lastSync : new Date(status.lastSync);
return (
{/* Connection pill */}
Backend Connection
{status.isConnected ? 'Connected to live Velocity backend' : 'Connection unavailable'}
{status.serverStatus.toUpperCase()}
{/* Version / sync grid */}
{[
{ label: 'Version', value: status.version },
{ label: 'Last Sync', value: Number.isNaN(lastSync.getTime()) ? 'Unavailable' : lastSync.toLocaleTimeString() },
].map(({ label, value }) => (
))}
{/* Actions */}
{
updateStatus({ serverStatus: 'syncing' });
window.location.reload();
}}
>
Sync Now
window.location.reload()}
>
Restart
);
}
// ── iOS Device Connection ────────────────────────────────────────────────────
function CompanionSurfacesCard() {
const [copied, setCopied] = useState(false);
const token = getVelocityToken();
const maskedToken = token ? `${token.slice(0, 8)}...${token.slice(-6)}` : 'No active bearer token';
const handleCopy = (text: string) => {
void navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1800);
};
return (
WebOS Session Token
{token ? 'Reusable by Oracle and protected WebOS routes.' : 'No authenticated backend session is currently present.'}
{token
?
ACTIVE
:
}
Runtime Access
{maskedToken}
Mobile and tablet pairing is intentionally deferred until the next delivery phase. This WebOS pass does not simulate device pairing.
window.location.reload()}
className="flex-1 py-2.5 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all"
style={{ background: 'hsl(var(--accent))', color: 'hsl(var(--accent-fg))' }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.97 }}
>
<> Refresh WebOS>
handleCopy(API_URL)}>Copy API URL
);
}
// ── Profile ──────────────────────────────────────────────────────────────────
function ProfileSettings() {
const { user, login } = useStore();
const fileInputRef = useRef
(null);
const [avatarError, setAvatarError] = useState(null);
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
const displayName = user?.fullName?.trim() || user?.name?.trim() || user?.email?.trim() || user?.id || 'Authenticated User';
const firstName = resolveVelocityFirstName({
user_id: user?.id ?? '',
full_name: user?.fullName ?? user?.name ?? null,
email: user?.email ?? null,
});
const initials = (user?.fullName || user?.name || user?.email || 'AU')
.split(/[\s@._-]+/)
.filter(Boolean)
.slice(0, 2)
.map((n) => n[0]?.toUpperCase() ?? '')
.join('') || 'AU';
const roleLabel = normalizeVelocityRole(user?.role)
.toLowerCase()
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ') || 'Authenticated User';
const handleAvatarUpload = async (event: ChangeEvent) => {
const file = event.target.files?.[0];
if (!file || !user) {
return;
}
setAvatarError(null);
setIsUploadingAvatar(true);
try {
const { avatar_url } = await uploadVelocityAvatar(file);
login({
...user,
avatar: avatar_url,
});
} catch (error) {
setAvatarError(error instanceof Error ? error.message : 'Failed to upload profile picture.');
} finally {
setIsUploadingAvatar(false);
if (event.target) {
event.target.value = '';
}
}
};
return (
{user?.avatar ? (

) : (
{initials}
)}
{displayName}
{roleLabel}
{isUploadingAvatar && (
Uploading profile picture...
)}
{avatarError && (
{avatarError}
)}
{firstName || 'Unavailable'}
{user?.id ?? 'Unavailable'}
{roleLabel}
);
}
// ── Notifications ────────────────────────────────────────────────────────────
function NotificationSettings() {
const [s, setS] = useState({ newLeads: true, sentimentAlerts: true, viewings: true, systemUpdates: false, emailDigest: true });
const rows: { key: keyof typeof s; label: string; desc: string }[] = [
{ key: 'newLeads', label: 'New Lead Alerts', desc: 'Get notified when a new lead is captured' },
{ key: 'sentimentAlerts', label: 'Sentiment Alerts', desc: 'Alert when visitor sentiment drops' },
{ key: 'viewings', label: 'Viewing Reminders', desc: 'Reminders for scheduled viewings' },
{ key: 'systemUpdates', label: 'System Updates', desc: 'Notifications about system maintenance' },
{ key: 'emailDigest', label: 'Daily Email Digest', desc: 'Summary of daily activity' },
];
return (
{rows.map(({ key, label, desc }) => (
setS({ ...s, [key]: v })} />
))}
);
}
// ── Security ─────────────────────────────────────────────────────────────────
function SecuritySettings() {
const [timeout, setTimeout_] = useState('30');
const { logout } = useStore();
const token = getVelocityToken();
return (
{token ? 'Present' : 'Missing'}
Managed outside WebOS
{
clearVelocityToken();
logout();
}}
>
Sign Out
);
}
// ── Display ──────────────────────────────────────────────────────────────────
function DisplaySettings() {
const [reducedMotion, setReducedMotion] = useState(false);
const [compactMode, setCompactMode] = useState(false);
const [language, setLanguage] = useState('en');
const [timezone, setTimezone] = useState('dxb');
const { currency, setCurrency } = useCurrency();
return (
{/* ── Currency ── */}
setCurrency(v as CurrencyCode)}
options={CURRENCY_OPTIONS.map((o) => ({
value: o.code,
label: `${o.flag} ${o.symbol} — ${o.label}`,
}))}
/>
);
}
// ── Data & Privacy ───────────────────────────────────────────────────────────
function CommunicationsSettings() {
const [settings, setSettings] = useState(null);
const [draft, setDraft] = useState>({});
const [statusText, setStatusText] = useState('Loading provider settings...');
const [saving, setSaving] = useState(false);
const current: CommsSettings = {
provider: 'mock',
providerBaseUrl: '',
providerApiKey: '',
instanceId: '',
phoneNumberId: '',
webhookCallbackUrl: '/api/comms/webhooks/{provider}',
webhookSecretSet: false,
autoLinkByPhone: true,
createCrmInteractionOnInbound: true,
defaultCountryCode: '91',
transcriptionProvider: 'none',
...(settings ?? {}),
...draft,
};
useEffect(() => {
let cancelled = false;
void fetchCommsSettings()
.then((value) => {
if (cancelled) return;
setSettings(value);
setStatusText('Settings loaded from backend.');
})
.catch((error) => {
if (cancelled) return;
setStatusText(error instanceof Error ? error.message : 'Unable to load comms settings.');
});
return () => {
cancelled = true;
};
}, []);
const update = (key: K, value: CommsSettings[K]) => {
setDraft((prev) => ({ ...prev, [key]: value }));
};
const save = async () => {
setSaving(true);
try {
await updateCommsSettings(draft);
const latest = await fetchCommsSettings();
setSettings(latest);
setDraft({});
setStatusText('Communications settings saved.');
} catch (error) {
setStatusText(error instanceof Error ? error.message : 'Failed to save communications settings.');
} finally {
setSaving(false);
}
};
const test = async () => {
try {
const result = await testCommsProviderConnection();
setStatusText(result.message || (result.success ? 'Provider connection succeeded.' : 'Provider connection failed.'));
} catch (error) {
setStatusText(error instanceof Error ? error.message : 'Provider test failed.');
}
};
const fieldClass = "w-64 rounded-xl px-3 py-2 text-sm text-white placeholder-zinc-500 outline-none";
return (
update('provider', v as CommsProvider)}
options={[
{ value: 'mock', label: 'Mock' },
{ value: 'waha', label: 'WAHA' },
{ value: 'evolution', label: 'Evolution API' },
{ value: 'meta_cloud', label: 'Meta Cloud API' },
]}
/>
update('providerBaseUrl', event.target.value)}
placeholder="http://127.0.0.1:3000"
/>
update('providerApiKey', event.target.value)}
placeholder="Provider API key"
/>
update('instanceId', event.target.value)}
placeholder="default"
/>
{current.webhookCallbackUrl || '/api/comms/webhooks/{provider}'}
update('autoLinkByPhone', v)} />
update('createCrmInteractionOnInbound', v)} />
update('transcriptionProvider', v as CommsSettings['transcriptionProvider'])}
options={[
{ value: 'none', label: 'None' },
{ value: 'local', label: 'Local Whisper' },
{ value: 'openai', label: 'OpenAI' },
]}
/>
{statusText}
Test
{saving ? 'Saving...' : 'Save'}
);
}
function DataSettings() {
const [retention, setRetention] = useState('90');
const { leads, messages, units, status } = useStore();
const exportSnapshot = () => {
const blob = new Blob([
JSON.stringify(
{
exported_at: new Date().toISOString(),
status,
lead_count: leads.length,
message_threads: Object.keys(messages).length,
inventory_count: units.length,
leads,
messages,
units,
},
null,
2,
),
], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = `velocity-webos-export-${Date.now()}.json`;
anchor.click();
URL.revokeObjectURL(url);
};
const clearUiCache = () => {
localStorage.removeItem('velocity-webos-storage');
localStorage.removeItem('pv-currency');
window.location.reload();
};
return (
Backend managed
Export
Clear
);
}
// ── About ────────────────────────────────────────────────────────────────────
function AboutSection() {
const token = getVelocityToken();
return (
V
Velocity WebOS
Real Estate Operating System
Version 2.2.0 CRM
•
{token ? 'Authenticated session active' : 'No active session'}
Backend origin: {API_URL}
);
}
// ── Main ─────────────────────────────────────────────────────────────────────
export function Settings() {
return (
{/* Row 1: System + iOS */}
{/* Row 2: Profile + Notifications */}
{/* Row 3: Security + Display */}
{/* Row 4: Communications + Data */}
{/* Row 5: About */}
);
}