Files
tower/apps/web/app/_components/portal-login-shell.tsx
T
maaz519 d7b5988858 feat: portal redesign — shadcn/ui, React Query, Zod, isolated portals
Foundation:
- CLAUDE.md with full coding rules (tech stack, patterns, redirect rules)
- Install @tanstack/react-query, zod, react-hook-form, shadcn/ui deps
- CSS variables design system (Tailwind v4 + tw-animate-css)
- Shared components: Button, Input, Label, Card, Badge, Separator, Form
- PortalLoginShell: two-column login layout (branding left, form right)
- PortalNav: shared sidebar nav used by all portals
- ReactQueryProvider in root layout
- api-client.ts: typed fetch wrapper (no raw fetch in components)

Portal isolation:
- Sidebar now returns null for all non-chapter routes; portals own their layout
- Admin layout: auth guard + PortalNav (slate accent)
- Org layout: auth guard + PortalNav (violet accent)
- Member layout: server-side cookie check → MemberNav client component
- Chapter admin sidebar: PortalNav (blue accent)

Login pages (all use react-hook-form + Zod + PortalLoginShell):
- /login → defaults next to /search (was /) — fixes redirect bug
- /admin/login → replaces /admin
- /org/login → replaces /org
- /member-login → two-step phone+OTP with proper form validation

Portal selector (/) redesigned with accent border-left cards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:01:27 +05:30

50 lines
1.6 KiB
TypeScript

import { cn } from '../_lib/utils';
interface PortalLoginShellProps {
title: string;
subtitle: string;
accentClass?: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
export function PortalLoginShell({ title, subtitle, accentClass = 'bg-primary', children, footer }: PortalLoginShellProps) {
return (
<div className="min-h-screen grid lg:grid-cols-2">
{/* Left branding panel */}
<div className={cn('hidden lg:flex flex-col justify-between p-10 text-white', accentClass)}>
<div className="flex items-center gap-2">
<span className="font-bold text-xl tracking-tight">TOWER</span>
</div>
<div>
<blockquote className="space-y-2">
<p className="text-lg font-medium leading-relaxed">
"Community knowledge, beautifully organised."
</p>
<footer className="text-sm opacity-70">Insignia TOWER Platform</footer>
</blockquote>
</div>
</div>
{/* Right form panel */}
<div className="flex items-center justify-center p-8 bg-background">
<div className="w-full max-w-sm space-y-6">
{/* Mobile logo */}
<div className="flex items-center gap-2 lg:hidden">
<span className="font-bold text-lg">TOWER</span>
</div>
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
<p className="text-sm text-muted-foreground">{subtitle}</p>
</div>
{children}
{footer && <div className="text-center text-sm text-muted-foreground">{footer}</div>}
</div>
</div>
</div>
);
}