205418fc4e
Fix blank /org/login (and /admin/login): the guarded portal layout was wrapping its own login route, rendering null when unauthenticated. Move each portal's authed pages into a (authed)/(chapter) route group so login pages live outside the guard. Structure: - app/(chapter)/* — chapter admin pages + guarded layout (was root-level) - app/org/(authed)/* — org pages + guarded layout; /org/login now free - app/admin/(authed)/* — admin pages + guarded layout; /admin/login free - Root layout slimmed to providers only (no shared sidebar) - Convert moved files' relative imports to @/app alias Design system: - PortalShell: shared sidebar shell (brand mark, themed active nav with accent bar, avatar dropdown user menu, loading skeletons) - portal-theme.ts: per-portal theme tokens (violet/slate/blue/emerald) - PortalLoginShell redesigned: two-column with gradient branding panel, dotted pattern, value-prop highlights, built-in back link - New shadcn components: Avatar, DropdownMenu, Skeleton - Move shared DraftCard to _components/draft-card.tsx (used by 2 portals) - Portal selector: remove Super Admin card, icon tiles, hover lift - All 4 login pages use react-hook-form + Zod + themed shell - Member nav restored to full 11-section list with icons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
|
|
type Status = 'ACTIVE' | 'DISCONNECTED' | 'BANNED' | 'PAIRING';
|
|
|
|
interface BotSummary {
|
|
id: string;
|
|
jid: string | null;
|
|
displayName: string | null;
|
|
status: Status;
|
|
}
|
|
|
|
export function BotSettingsCard({ initial }: { initial: { bot: BotSummary | null; shared: boolean; sharedBotId?: string } }) {
|
|
const [bot, setBot] = useState<BotSummary | null>(initial.bot);
|
|
const [revealedJid, setRevealedJid] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
async function onReveal() {
|
|
setError(null);
|
|
const res = await fetch('/api/bot/reveal', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
});
|
|
if (res.ok) {
|
|
const data = (await res.json()) as { jid: string };
|
|
setRevealedJid(data.jid);
|
|
} else {
|
|
setError('Reveal failed');
|
|
}
|
|
}
|
|
|
|
if (!bot) {
|
|
return (
|
|
<div className="rounded-lg border border-gray-200 p-6 bg-white">
|
|
<h2 className="text-lg font-medium mb-2">Bot</h2>
|
|
<p className="text-sm text-gray-600">
|
|
No bot assigned yet. Contact your platform administrator to get one assigned.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border border-gray-200 p-6 bg-white">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h2 className="text-lg font-medium">Bot</h2>
|
|
<span
|
|
className={`text-xs px-2 py-1 rounded ${
|
|
bot.status === 'ACTIVE'
|
|
? 'bg-green-100 text-green-800'
|
|
: bot.status === 'PAIRING'
|
|
? 'bg-yellow-100 text-yellow-800'
|
|
: 'bg-red-100 text-red-800'
|
|
}`}
|
|
>
|
|
{bot.status}
|
|
</span>
|
|
</div>
|
|
<dl className="text-sm space-y-1 mb-4">
|
|
<div>
|
|
<dt className="inline font-medium">Number: </dt>
|
|
<dd className="inline">
|
|
{revealedJid ?? (bot.jid ? '••••••••••' : '—')}
|
|
{!revealedJid && bot.status === 'ACTIVE' && (
|
|
<button
|
|
type="button"
|
|
onClick={onReveal}
|
|
className="ml-2 text-xs text-blue-600 underline"
|
|
>
|
|
Reveal
|
|
</button>
|
|
)}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="inline font-medium">Display name: </dt>
|
|
<dd className="inline">{bot.displayName ?? '—'}</dd>
|
|
</div>
|
|
</dl>
|
|
<p className="text-xs text-gray-400">
|
|
Bot is assigned by the platform administrator. Contact support to change or remove it.
|
|
</p>
|
|
{error && <p className="text-sm text-red-600 mt-2">{error}</p>}
|
|
</div>
|
|
);
|
|
}
|