implement better-auth auth with postgres and route protection
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Activity, BookOpenText, ChartCandlestick, Eye } from 'lucide-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { Activity, BookOpenText, ChartCandlestick, Eye, LogOut } from 'lucide-react';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type AppShellProps = {
|
||||
@@ -21,6 +24,31 @@ const NAV_ITEMS = [
|
||||
|
||||
export function AppShell({ title, subtitle, actions, children }: AppShellProps) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
const { data: session } = authClient.useSession();
|
||||
const sessionUser = (session?.user ?? null) as { name?: string | null; email?: string | null; role?: unknown } | null;
|
||||
|
||||
const role = typeof sessionUser?.role === 'string'
|
||||
? sessionUser.role
|
||||
: Array.isArray(sessionUser?.role)
|
||||
? sessionUser.role.filter((entry): entry is string => typeof entry === 'string').join(', ')
|
||||
: null;
|
||||
const displayName = sessionUser?.name || sessionUser?.email || 'Authenticated user';
|
||||
|
||||
const signOut = async () => {
|
||||
if (isSigningOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSigningOut(true);
|
||||
try {
|
||||
await authClient.signOut();
|
||||
router.replace('/auth/signin');
|
||||
} finally {
|
||||
setIsSigningOut(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app-surface">
|
||||
@@ -62,10 +90,15 @@ export function AppShell({ title, subtitle, actions, children }: AppShellProps)
|
||||
|
||||
<div className="mt-auto rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] p-3">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-[color:var(--terminal-muted)]">Runtime</p>
|
||||
<p className="mt-1 truncate text-sm text-[color:var(--terminal-bright)]">local operator mode</p>
|
||||
<p className="mt-1 truncate text-sm text-[color:var(--terminal-bright)]">{displayName}</p>
|
||||
{role ? <p className="mt-1 text-xs uppercase tracking-[0.16em] text-[color:var(--terminal-muted)]">Role: {role}</p> : null}
|
||||
<p className="mt-2 text-xs text-[color:var(--terminal-muted)]">
|
||||
OpenClaw and market data are driven by environment configuration and live API tasks.
|
||||
</p>
|
||||
<Button className="mt-3 w-full" variant="ghost" onClick={() => void signOut()} disabled={isSigningOut}>
|
||||
<LogOut className="size-4" />
|
||||
{isSigningOut ? 'Signing out...' : 'Sign out'}
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -79,7 +112,13 @@ export function AppShell({ title, subtitle, actions, children }: AppShellProps)
|
||||
<p className="mt-1 text-sm text-[color:var(--terminal-muted)]">{subtitle}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{actions}
|
||||
<Button variant="ghost" onClick={() => void signOut()} disabled={isSigningOut}>
|
||||
<LogOut className="size-4" />
|
||||
{isSigningOut ? 'Signing out...' : 'Sign out'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user