feat(cc-050): Roles & Permissions page
This commit is contained in:
parent
f9c0b71c4d
commit
11d5261f47
1 changed files with 95 additions and 0 deletions
95
src/app/dashboard/privacy/roles/page.tsx
Normal file
95
src/app/dashboard/privacy/roles/page.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
'use client';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../../../lib/api';
|
||||
import type { Member } from '../../../../lib/api';
|
||||
import { useSession } from '../../../../components/session-provider';
|
||||
|
||||
const ROLE_PERMS: Record<string, string[]> = {
|
||||
owner: ['Acces complet', 'Gestionare membri', 'Configurare workspace', 'Date administrative'],
|
||||
admin: ['Citire și scriere date', 'Gestionare membri', 'Configurare workspace'],
|
||||
member: ['Citire și scriere date'],
|
||||
};
|
||||
|
||||
const ROLE_BADGE: Record<string, string> = {
|
||||
owner: 'bg-bronze-wash text-bronze-deep',
|
||||
admin: 'bg-paper-sunken text-ink',
|
||||
member: 'bg-paper-sunken text-ink-faint',
|
||||
};
|
||||
|
||||
export default function RolesPage() {
|
||||
const { activeTenant } = useSession();
|
||||
const tenantId = activeTenant?.tenantId ?? '';
|
||||
|
||||
const { data: members = [], isLoading } = useQuery({
|
||||
queryKey: ['members-roles', tenantId],
|
||||
queryFn: () => apiFetch<Member[]>('/v1/tenants/current/members', { tenantId }),
|
||||
enabled: Boolean(tenantId),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-6">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl font-semibold text-ink">Roluri & Permisiuni</h1>
|
||||
<p className="mt-1 text-sm text-ink-faint">
|
||||
RBAC — roluri active și permisiunile asociate în workspace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="card p-5">
|
||||
<h2 className="mb-4 text-xs font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Definiții roluri
|
||||
</h2>
|
||||
<div className="divide-y divide-paper-sunken">
|
||||
{Object.entries(ROLE_PERMS).map(([role, perms]) => (
|
||||
<div key={role} className="py-4 first:pt-0 last:pb-0">
|
||||
<span className={`rounded-full px-2.5 py-1 text-xs font-semibold capitalize ${ROLE_BADGE[role] ?? 'bg-paper-sunken text-ink-faint'}`}>
|
||||
{role}
|
||||
</span>
|
||||
<ul className="mt-2 space-y-1">
|
||||
{perms.map((p) => (
|
||||
<li key={p} className="flex items-center gap-2 text-sm text-ink-faint">
|
||||
<span className="text-signal-ok text-xs">✓</span> {p}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="mb-3 text-xs font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Membri ({isLoading ? '…' : members.length})
|
||||
</h2>
|
||||
{isLoading ? (
|
||||
<p className="text-sm text-ink-faint">Se încarcă…</p>
|
||||
) : members.length === 0 ? (
|
||||
<div className="card p-6 text-center text-sm text-ink-faint">Niciun membru găsit.</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{members.map((m) => (
|
||||
<li key={m.userId} className="card flex items-center justify-between px-4 py-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-ink">{m.email ?? '(fără email)'}</p>
|
||||
<p className="mt-0.5 font-mono text-[10px] text-ink-faint">{m.userId}</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-3">
|
||||
<span className={`rounded-full px-2.5 py-1 text-xs font-medium capitalize ${ROLE_BADGE[m.role] ?? 'bg-paper-sunken text-ink-faint'}`}>
|
||||
{m.role}
|
||||
</span>
|
||||
{m.createdAt && (
|
||||
<span className="text-[11px] text-ink-line">
|
||||
{new Date(m.createdAt).toLocaleDateString('ro-RO')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue