From 205418fc4ee1de6d74500d9448982e477809fb59 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 20 Jun 2026 18:20:06 +0530 Subject: [PATCH] feat: isolate all four portals with route groups + polished design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitignore | 1 + apps/web/app/{ => (chapter)}/drafts/page.tsx | 6 +- .../app/{ => (chapter)}/groups/GroupsTabs.tsx | 0 .../groups/RouteManager.test.tsx | 0 .../{ => (chapter)}/groups/RouteManager.tsx | 0 apps/web/app/{ => (chapter)}/groups/page.tsx | 2 +- apps/web/app/(chapter)/layout.tsx | 40 +++++ .../{ => (chapter)}/messages/[id]/page.tsx | 2 +- .../{ => (chapter)}/messages/pending/page.tsx | 2 +- .../app/{ => (chapter)}/search/page.test.tsx | 0 apps/web/app/{ => (chapter)}/search/page.tsx | 2 +- .../settings/bot/BotSettingsCard.tsx | 0 .../app/{ => (chapter)}/settings/bot/page.tsx | 2 +- .../settings/rules/RuleManager.tsx | 0 .../{ => (chapter)}/settings/rules/page.tsx | 2 +- .../app/{ => (chapter)}/threads/[id]/page.tsx | 2 +- apps/web/app/{ => (chapter)}/threads/page.tsx | 2 +- .../draft-card.tsx} | 0 .../app/_components/portal-login-shell.tsx | 74 ++++++--- apps/web/app/_components/portal-nav.tsx | 82 ---------- apps/web/app/_components/portal-shell.tsx | 151 ++++++++++++++++++ apps/web/app/_components/portal-theme.ts | 58 +++++++ apps/web/app/_components/ui/avatar.tsx | 39 +++++ apps/web/app/_components/ui/dropdown-menu.tsx | 76 +++++++++ apps/web/app/_components/ui/skeleton.tsx | 7 + apps/web/app/_lib/sidebar.tsx | 57 ------- .../app/admin/{ => (authed)}/bots/page.tsx | 2 +- .../app/admin/{ => (authed)}/drafts/page.tsx | 6 +- apps/web/app/admin/(authed)/layout.tsx | 38 +++++ .../admin/{ => (authed)}/orgs/[id]/page.tsx | 2 +- .../app/admin/{ => (authed)}/orgs/page.tsx | 2 +- apps/web/app/admin/{ => (authed)}/page.tsx | 2 +- .../{ => (authed)}/tenants/[id]/page.tsx | 2 +- .../app/admin/{ => (authed)}/tenants/page.tsx | 2 +- apps/web/app/admin/layout.tsx | 47 ------ apps/web/app/admin/login/page.tsx | 5 +- apps/web/app/layout.tsx | 8 +- apps/web/app/login/page.tsx | 5 +- apps/web/app/member-login/page.tsx | 5 +- apps/web/app/my/layout.tsx | 6 +- apps/web/app/my/member-nav.tsx | 53 +++--- apps/web/app/org/(authed)/layout.tsx | 36 +++++ apps/web/app/org/{ => (authed)}/page.tsx | 2 +- .../web/app/org/{ => (authed)}/rules/page.tsx | 2 +- .../org/{ => (authed)}/tenants/[id]/page.tsx | 2 +- .../app/org/{ => (authed)}/tenants/page.tsx | 2 +- apps/web/app/org/layout.tsx | 45 ------ apps/web/app/org/login/page.tsx | 5 +- apps/web/app/page.tsx | 92 +++++------ apps/web/tsconfig.tsbuildinfo | 1 - 50 files changed, 619 insertions(+), 360 deletions(-) rename apps/web/app/{ => (chapter)}/drafts/page.tsx (95%) rename apps/web/app/{ => (chapter)}/groups/GroupsTabs.tsx (100%) rename apps/web/app/{ => (chapter)}/groups/RouteManager.test.tsx (100%) rename apps/web/app/{ => (chapter)}/groups/RouteManager.tsx (100%) rename apps/web/app/{ => (chapter)}/groups/page.tsx (98%) create mode 100644 apps/web/app/(chapter)/layout.tsx rename apps/web/app/{ => (chapter)}/messages/[id]/page.tsx (99%) rename apps/web/app/{ => (chapter)}/messages/pending/page.tsx (98%) rename apps/web/app/{ => (chapter)}/search/page.test.tsx (100%) rename apps/web/app/{ => (chapter)}/search/page.tsx (98%) rename apps/web/app/{ => (chapter)}/settings/bot/BotSettingsCard.tsx (100%) rename apps/web/app/{ => (chapter)}/settings/bot/page.tsx (96%) rename apps/web/app/{ => (chapter)}/settings/rules/RuleManager.tsx (100%) rename apps/web/app/{ => (chapter)}/settings/rules/page.tsx (95%) rename apps/web/app/{ => (chapter)}/threads/[id]/page.tsx (98%) rename apps/web/app/{ => (chapter)}/threads/page.tsx (98%) rename apps/web/app/{admin/drafts/DraftCard.tsx => _components/draft-card.tsx} (100%) delete mode 100644 apps/web/app/_components/portal-nav.tsx create mode 100644 apps/web/app/_components/portal-shell.tsx create mode 100644 apps/web/app/_components/portal-theme.ts create mode 100644 apps/web/app/_components/ui/avatar.tsx create mode 100644 apps/web/app/_components/ui/dropdown-menu.tsx create mode 100644 apps/web/app/_components/ui/skeleton.tsx delete mode 100644 apps/web/app/_lib/sidebar.tsx rename apps/web/app/admin/{ => (authed)}/bots/page.tsx (99%) rename apps/web/app/admin/{ => (authed)}/drafts/page.tsx (95%) create mode 100644 apps/web/app/admin/(authed)/layout.tsx rename apps/web/app/admin/{ => (authed)}/orgs/[id]/page.tsx (99%) rename apps/web/app/admin/{ => (authed)}/orgs/page.tsx (98%) rename apps/web/app/admin/{ => (authed)}/page.tsx (97%) rename apps/web/app/admin/{ => (authed)}/tenants/[id]/page.tsx (98%) rename apps/web/app/admin/{ => (authed)}/tenants/page.tsx (98%) delete mode 100644 apps/web/app/admin/layout.tsx create mode 100644 apps/web/app/org/(authed)/layout.tsx rename apps/web/app/org/{ => (authed)}/page.tsx (97%) rename apps/web/app/org/{ => (authed)}/rules/page.tsx (99%) rename apps/web/app/org/{ => (authed)}/tenants/[id]/page.tsx (98%) rename apps/web/app/org/{ => (authed)}/tenants/page.tsx (98%) delete mode 100644 apps/web/app/org/layout.tsx delete mode 100644 apps/web/tsconfig.tsbuildinfo diff --git a/.gitignore b/.gitignore index 1329850..9ab55a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage *.env.local .env.production sessions/ +*.tsbuildinfo diff --git a/apps/web/app/drafts/page.tsx b/apps/web/app/(chapter)/drafts/page.tsx similarity index 95% rename from apps/web/app/drafts/page.tsx rename to apps/web/app/(chapter)/drafts/page.tsx index 0a510a2..b066727 100644 --- a/apps/web/app/drafts/page.tsx +++ b/apps/web/app/(chapter)/drafts/page.tsx @@ -1,7 +1,7 @@ -import { apiFetch } from '../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; import { redirect } from 'next/navigation'; -import { getToken } from '../_lib/api'; -import { DraftCard } from '../admin/drafts/DraftCard'; +import { getToken } from '@/app/_lib/api'; +import { DraftCard } from '@/app/_components/draft-card'; import Link from 'next/link'; type DraftType = 'EVENT' | 'SEVA_TASK' | 'FAQ_ITEM'; diff --git a/apps/web/app/groups/GroupsTabs.tsx b/apps/web/app/(chapter)/groups/GroupsTabs.tsx similarity index 100% rename from apps/web/app/groups/GroupsTabs.tsx rename to apps/web/app/(chapter)/groups/GroupsTabs.tsx diff --git a/apps/web/app/groups/RouteManager.test.tsx b/apps/web/app/(chapter)/groups/RouteManager.test.tsx similarity index 100% rename from apps/web/app/groups/RouteManager.test.tsx rename to apps/web/app/(chapter)/groups/RouteManager.test.tsx diff --git a/apps/web/app/groups/RouteManager.tsx b/apps/web/app/(chapter)/groups/RouteManager.tsx similarity index 100% rename from apps/web/app/groups/RouteManager.tsx rename to apps/web/app/(chapter)/groups/RouteManager.tsx diff --git a/apps/web/app/groups/page.tsx b/apps/web/app/(chapter)/groups/page.tsx similarity index 98% rename from apps/web/app/groups/page.tsx rename to apps/web/app/(chapter)/groups/page.tsx index cfd600a..7773387 100644 --- a/apps/web/app/groups/page.tsx +++ b/apps/web/app/(chapter)/groups/page.tsx @@ -1,6 +1,6 @@ import { RouteManager } from './RouteManager'; import { GroupsTabs } from './GroupsTabs'; -import { apiFetch } from '../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface Group { id: string; diff --git a/apps/web/app/(chapter)/layout.tsx b/apps/web/app/(chapter)/layout.tsx new file mode 100644 index 0000000..f66f1a6 --- /dev/null +++ b/apps/web/app/(chapter)/layout.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { useEffect } from 'react'; +import { usePathname, useRouter } from 'next/navigation'; +import { useAuth } from '@/app/_lib/auth-context'; +import { PortalShell } from '@/app/_components/portal-shell'; +import { Search, Users, Inbox, FileText, SlidersHorizontal, Bot } from 'lucide-react'; + +const NAV = [ + { href: '/search', label: 'Search', icon: }, + { href: '/groups', label: 'Groups & Routes', icon: }, + { href: '/messages/pending', label: 'Pending', icon: }, + { href: '/drafts', label: 'AI Drafts', icon: }, + { href: '/settings/rules', label: 'Rules', icon: }, + { href: '/settings/bot', label: 'Bot', icon: }, +]; + +export default function ChapterLayout({ children }: { children: React.ReactNode }) { + const { admin, loading, logout } = useAuth(); + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + if (!loading && !admin) router.replace(`/login?next=${encodeURIComponent(pathname)}`); + }, [admin, loading, pathname, router]); + + return ( + { void logout(); router.replace('/login'); }} + > + {children} + + ); +} diff --git a/apps/web/app/messages/[id]/page.tsx b/apps/web/app/(chapter)/messages/[id]/page.tsx similarity index 99% rename from apps/web/app/messages/[id]/page.tsx rename to apps/web/app/(chapter)/messages/[id]/page.tsx index 4fed4b4..b552503 100644 --- a/apps/web/app/messages/[id]/page.tsx +++ b/apps/web/app/(chapter)/messages/[id]/page.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { apiFetch } from '../../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface MessageDetail { id: string; diff --git a/apps/web/app/messages/pending/page.tsx b/apps/web/app/(chapter)/messages/pending/page.tsx similarity index 98% rename from apps/web/app/messages/pending/page.tsx rename to apps/web/app/(chapter)/messages/pending/page.tsx index 6709e65..314c744 100644 --- a/apps/web/app/messages/pending/page.tsx +++ b/apps/web/app/(chapter)/messages/pending/page.tsx @@ -1,4 +1,4 @@ -import { apiFetch } from '../../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface PendingMessage { id: string; diff --git a/apps/web/app/search/page.test.tsx b/apps/web/app/(chapter)/search/page.test.tsx similarity index 100% rename from apps/web/app/search/page.test.tsx rename to apps/web/app/(chapter)/search/page.test.tsx diff --git a/apps/web/app/search/page.tsx b/apps/web/app/(chapter)/search/page.tsx similarity index 98% rename from apps/web/app/search/page.tsx rename to apps/web/app/(chapter)/search/page.tsx index f126e12..12089e9 100644 --- a/apps/web/app/search/page.tsx +++ b/apps/web/app/(chapter)/search/page.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { apiFetch } from '../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface MeiliHit { id: string; diff --git a/apps/web/app/settings/bot/BotSettingsCard.tsx b/apps/web/app/(chapter)/settings/bot/BotSettingsCard.tsx similarity index 100% rename from apps/web/app/settings/bot/BotSettingsCard.tsx rename to apps/web/app/(chapter)/settings/bot/BotSettingsCard.tsx diff --git a/apps/web/app/settings/bot/page.tsx b/apps/web/app/(chapter)/settings/bot/page.tsx similarity index 96% rename from apps/web/app/settings/bot/page.tsx rename to apps/web/app/(chapter)/settings/bot/page.tsx index a384605..d394b09 100644 --- a/apps/web/app/settings/bot/page.tsx +++ b/apps/web/app/(chapter)/settings/bot/page.tsx @@ -1,5 +1,5 @@ import { BotSettingsCard } from './BotSettingsCard'; -import { apiFetch } from '../../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface BotSummary { id: string; diff --git a/apps/web/app/settings/rules/RuleManager.tsx b/apps/web/app/(chapter)/settings/rules/RuleManager.tsx similarity index 100% rename from apps/web/app/settings/rules/RuleManager.tsx rename to apps/web/app/(chapter)/settings/rules/RuleManager.tsx diff --git a/apps/web/app/settings/rules/page.tsx b/apps/web/app/(chapter)/settings/rules/page.tsx similarity index 95% rename from apps/web/app/settings/rules/page.tsx rename to apps/web/app/(chapter)/settings/rules/page.tsx index ab309b4..5a20157 100644 --- a/apps/web/app/settings/rules/page.tsx +++ b/apps/web/app/(chapter)/settings/rules/page.tsx @@ -1,5 +1,5 @@ import { RuleManager } from './RuleManager'; -import { apiFetch } from '../../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface RuleData { id: string; diff --git a/apps/web/app/threads/[id]/page.tsx b/apps/web/app/(chapter)/threads/[id]/page.tsx similarity index 98% rename from apps/web/app/threads/[id]/page.tsx rename to apps/web/app/(chapter)/threads/[id]/page.tsx index 2958d6a..291ad27 100644 --- a/apps/web/app/threads/[id]/page.tsx +++ b/apps/web/app/(chapter)/threads/[id]/page.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { apiFetch } from '../../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface ThreadMessage { id: string; diff --git a/apps/web/app/threads/page.tsx b/apps/web/app/(chapter)/threads/page.tsx similarity index 98% rename from apps/web/app/threads/page.tsx rename to apps/web/app/(chapter)/threads/page.tsx index cf6c4e1..379cee8 100644 --- a/apps/web/app/threads/page.tsx +++ b/apps/web/app/(chapter)/threads/page.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { apiFetch } from '../_lib/api'; +import { apiFetch } from '@/app/_lib/api'; interface ThreadSummary { id: string; diff --git a/apps/web/app/admin/drafts/DraftCard.tsx b/apps/web/app/_components/draft-card.tsx similarity index 100% rename from apps/web/app/admin/drafts/DraftCard.tsx rename to apps/web/app/_components/draft-card.tsx diff --git a/apps/web/app/_components/portal-login-shell.tsx b/apps/web/app/_components/portal-login-shell.tsx index c300647..cb80742 100644 --- a/apps/web/app/_components/portal-login-shell.tsx +++ b/apps/web/app/_components/portal-login-shell.tsx @@ -1,47 +1,83 @@ +import Link from 'next/link'; import { cn } from '../_lib/utils'; +import { PORTAL_THEMES, type PortalTheme } from './portal-theme'; +import { Check, ArrowLeft } from 'lucide-react'; interface PortalLoginShellProps { title: string; subtitle: string; - accentClass?: string; + theme: PortalTheme; + /** Short value-prop bullets shown on the branding panel */ + highlights?: string[]; children: React.ReactNode; footer?: React.ReactNode; } -export function PortalLoginShell({ title, subtitle, accentClass = 'bg-primary', children, footer }: PortalLoginShellProps) { +export function PortalLoginShell({ title, subtitle, theme, highlights = [], children, footer }: PortalLoginShellProps) { + const t = PORTAL_THEMES[theme]; + return (
- {/* Left branding panel */} -
-
- TOWER + {/* Branding panel */} +