feat(CC-084): add Professional Identity settings (bio/role/links/locale via workspace PATCH)
This commit is contained in:
parent
3a9245b186
commit
a25c54970f
1 changed files with 152 additions and 0 deletions
152
src/app/dashboard/settings/identity/page.tsx
Normal file
152
src/app/dashboard/settings/identity/page.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../../../lib/api';
|
||||
import { useSession } from '../../../../components/session-provider';
|
||||
|
||||
interface Workspace {
|
||||
id: string; name: string; slug: string | null; settings: Record<string, unknown> | null;
|
||||
logoUrl: string | null; timezone: string | null; locale: string | null; createdAt: string;
|
||||
}
|
||||
|
||||
const TIMEZONES = [
|
||||
'Europe/Bucharest', 'Europe/Berlin', 'Europe/London', 'Europe/Paris',
|
||||
'America/New_York', 'America/Chicago', 'America/Los_Angeles',
|
||||
'Asia/Kolkata', 'Asia/Tokyo', 'UTC',
|
||||
];
|
||||
|
||||
const LOCALES = [
|
||||
{ value: 'ro-RO', label: 'Română (RO)' },
|
||||
{ value: 'de-DE', label: 'Deutsch (DE)' },
|
||||
{ value: 'en-US', label: 'English (US)' },
|
||||
{ value: 'es-ES', label: 'Español (ES)' },
|
||||
];
|
||||
|
||||
export default function SettingsIdentityPage() {
|
||||
const { activeTenant } = useSession();
|
||||
const tenantId = activeTenant?.tenantId ?? '';
|
||||
const qc = useQueryClient();
|
||||
|
||||
const { data: ws, isLoading } = useQuery({
|
||||
queryKey: ['ws-identity', tenantId],
|
||||
queryFn: () => apiFetch<Workspace>('/v1/workspace', { tenantId }),
|
||||
enabled: Boolean(tenantId), staleTime: 60_000,
|
||||
});
|
||||
|
||||
const [form, setForm] = useState({ name: '', slug: '', timezone: '', locale: '', bio: '', website: '', linkedin: '', role: '' });
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (ws) {
|
||||
const s = (ws.settings ?? {}) as Record<string, string>;
|
||||
setForm({
|
||||
name: ws.name ?? '',
|
||||
slug: ws.slug ?? '',
|
||||
timezone: ws.timezone ?? 'Europe/Bucharest',
|
||||
locale: ws.locale ?? 'ro-RO',
|
||||
bio: s.bio ?? '',
|
||||
website: s.website ?? '',
|
||||
linkedin: s.linkedin ?? '',
|
||||
role: s.role ?? '',
|
||||
});
|
||||
}
|
||||
}, [ws]);
|
||||
|
||||
const saveMut = useMutation({
|
||||
mutationFn: () => apiFetch('/v1/workspace', { tenantId, method: 'PATCH', body: {
|
||||
name: form.name, slug: form.slug || undefined,
|
||||
timezone: form.timezone, locale: form.locale,
|
||||
settings: { bio: form.bio, website: form.website, linkedin: form.linkedin, role: form.role },
|
||||
}}),
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['ws-identity', tenantId] }); setSaved(true); setTimeout(() => setSaved(false), 3000); },
|
||||
});
|
||||
|
||||
if (isLoading) return <div className="p-6 text-sm text-ink-faint">Se încarcă…</div>;
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-6 p-6">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl font-semibold text-ink">Identitate Profesională</h1>
|
||||
<p className="text-sm text-ink-faint mt-1">Profilul și setările de bază ale workspace-ului CEO OS.</p>
|
||||
</div>
|
||||
|
||||
{/* Identity */}
|
||||
<div className="card p-5 space-y-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Informații de bază</p>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Nume workspace / Nume profesional</label>
|
||||
<input value={form.name} onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Slug URL (identificator unic)</label>
|
||||
<input value={form.slug} onChange={(e) => setForm((p) => ({ ...p, slug: e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '') }))}
|
||||
placeholder="ex: valentin-ion"
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink font-mono focus:outline-none focus:ring-2 focus:ring-ring" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Rol / Titlu profesional</label>
|
||||
<input value={form.role} onChange={(e) => setForm((p) => ({ ...p, role: e.target.value }))}
|
||||
placeholder="ex: CTO & AI Strategist"
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Bio scurt</label>
|
||||
<textarea value={form.bio} onChange={(e) => setForm((p) => ({ ...p, bio: e.target.value }))}
|
||||
rows={3} placeholder="Câteva propoziții despre tine și activitatea ta…"
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring resize-none" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="card p-5 space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Link-uri publice</p>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Website personal</label>
|
||||
<input value={form.website} onChange={(e) => setForm((p) => ({ ...p, website: e.target.value }))}
|
||||
placeholder="https://example.com"
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink font-mono focus:outline-none focus:ring-2 focus:ring-ring" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">LinkedIn URL</label>
|
||||
<input value={form.linkedin} onChange={(e) => setForm((p) => ({ ...p, linkedin: e.target.value }))}
|
||||
placeholder="https://linkedin.com/in/username"
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink font-mono focus:outline-none focus:ring-2 focus:ring-ring" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Locale */}
|
||||
<div className="card p-5 space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Localizare</p>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Fus orar</label>
|
||||
<select value={form.timezone} onChange={(e) => setForm((p) => ({ ...p, timezone: e.target.value }))}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
{TIMEZONES.map((tz) => <option key={tz} value={tz}>{tz}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-ink-faint">Limbă & format</label>
|
||||
<select value={form.locale} onChange={(e) => setForm((p) => ({ ...p, locale: e.target.value }))}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
{LOCALES.map((l) => <option key={l.value} value={l.value}>{l.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => saveMut.mutate()} disabled={saveMut.isPending || !form.name}
|
||||
className="rounded-lg bg-primary px-6 py-2 text-sm font-medium text-white hover:bg-primary/90 disabled:opacity-50">
|
||||
{saveMut.isPending ? 'Se salvează…' : 'Salvează modificările'}
|
||||
</button>
|
||||
{saved && <span className="text-sm text-signal-ok">✓ Salvat</span>}
|
||||
{saveMut.isError && <span className="text-sm text-signal-danger">Eroare la salvare</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue