App Structure
App Structure
A complete directory layout of the foundation with descriptions for every folder and key file.
Directory Layout
src/
app/
layout.tsx # Root layout (providers, AppInitializer)
(marketing)/ # Public pages
page.tsx # Landing page
auth/ # Auth pages (public)
login/page.tsx
signup/page.tsx
forgot-password/page.tsx
(admin)/ # Protected area
layout.tsx # Auth layout + sidebar + AppStoreHydrator
dashboard/page.tsx
habits/page.tsx # Example feature page
api/ # API routes
tenants/
route.ts
switch/route.ts
habits/ # Example CRUD API
route.ts
[id]/route.ts
components/
AppInitializer.tsx # Client-side user state init
ui/ # Base UI components (shadcn)
navigations/ # Nav components
app-sidebar.tsx
app-store-hydrator.tsx # Server → Client state hydration
WorkspaceSwitcher.tsx # Tenant switcher
AccessControl.tsx # Conditional rendering by permissions
marketing/ # Marketing page components
dialogs/ # Dialog components
DialogManager.tsx # Global dialog controller
habits/ # Example feature components
lib/
api/
responses.ts # ok(), fail(), created(), notFound()
client/ # Client-side API functions
auth/
requireUser.ts # Auth gate for API routes
db/
client.ts # Postgres pool
withAuthDb.ts # JWT → Postgres session binding
queries/ # Query functions (always filter by tenantId)
schema/ # Drizzle schema definitions
supabase/
client.ts # Browser Supabase client
server.ts # Server Supabase client
middleware.ts # Edge middleware
tenants/
activeTenant.ts # Cookie read/write for active tenant
resolveActiveTenant.ts # Validate + resolve tenant from cookie
helpers/
routes.ts # Centralized route definitions (ROUTES)
validation/ # Zod schemas for API input validation
store/
appStore.ts # Zustand: user, tenants, toasts
useDialogStore.ts # Zustand: dialog state
context/
QueryClientProviderWrapper.tsx # All providers wrapper
ToastContext.tsx # Toast UI (renders from appStore)
ConfirmProvider.tsx # Promise-based confirm dialogs
hooks/
useHabits.ts # Example React Query hooks
useConfirm.ts # Access confirm() function
use-mobile.tsx # Responsive breakpoint detection
use-local-storage.ts # Persistent local storage
useScroll.ts # Scroll position tracking
types/ # TypeScript type definitions
utils/
constants.ts # ToastType, DialogTypes, Roles
formatters.ts # Number/date/file formattersRoute Groups
| Group | Purpose | Auth |
|---|---|---|
(marketing) | Public pages (landing, etc.) | No |
auth/ | Auth flows (login, signup) | No |
(admin) | Protected app area | Yes |
Routing Rules
/and/auth/*are public- All other routes require authentication
- Unauthenticated users redirect to
/auth/login?redirectTo=... - Authenticated users on auth pages redirect to
/dashboard
App Initialization Flow
1. Root Layout (app/layout.tsx)
└── QueryClientProviderWrapper (providers: React Query, Theme, Toast, Confirm)
└── AppInitializer (client: loads user from Supabase → appStore)
└── Children
2. Admin Layout (app/(admin)/layout.tsx) — Server Component
└── requireUser() — verify auth
└── withAuthDb() — load tenants + resolve active tenant
└── AppStoreHydrator — hydrate tenants into Zustand
└── SidebarProvider + AppSidebar
└── Children (pages)Key Components
AppInitializer
Client component that loads the current user from Supabase Auth into the Zustand store on mount.
AppStoreHydrator
Receives server-fetched tenants and hydrates them into Zustand. Runs once on admin layout mount.
AccessControl
Conditional rendering based on permissions:
<AccessControl isAllowed={role === 'owner'}>
<AdminButton />
</AccessControl>WorkspaceSwitcher
Tenant switcher in the sidebar. For Personal Apps (single-user), hide this component.
DialogManager
Global dialog controller. Dialogs are opened via:
openDialog(DIALOG_TYPES.HABIT.CREATE)