` for conditional rendering, `children` via `props.children` (not `{children}` destructure -- SolidJS requires `props.children` to maintain reactivity).
```typescript
import { Component, Show, createSignal } from 'solid-js';
import { useAuth } from '~/context/AuthContext';
import Navbar from './Navbar';
import MobileMenu from './MobileMenu';
const AppShell: Component = (props) => {
const { user } = useAuth();
const [mobileMenuOpen, setMobileMenuOpen] = createSignal(false);
return (
setMobileMenuOpen(!mobileMenuOpen())}
/>
setMobileMenuOpen(false)} />
{props.children}
);
};
```
**Key SolidJS difference:** In React, `{children}` is destructured from props. In SolidJS, always access as `props.children` to preserve reactivity tracking. Destructuring breaks the reactive subscription.
---
#### `src/components/layout/Navbar.tsx` -- Navigation Bar
**Purpose:** Top navigation bar with logo, nav links, user info, settings icon, admin dropdown (if admin), logout. Includes mobile hamburger button.
**Props:**
```typescript
interface NavbarProps {
user: User | null;
onToggleMobile: () => void;
}
```
**SolidJS patterns:**
- `useLocation()` from `@solidjs/router` for active route detection
- `` component from `@solidjs/router` (replaces React Router's ``)
- `` for admin nav items
- `class` attribute (not `className`)
**Active route indicator:** Compute active class with:
```typescript
const location = useLocation();
const isActive = (path: string) =>
location.pathname === path
? 'border-indigo-500 text-gray-900'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700';
```
**React pattern replaced:** Current `Layout` component in `App.tsx`. The current app has no active route indicator and no mobile menu -- both are added in this rewrite.
---
#### `src/components/layout/MobileMenu.tsx` -- Mobile Navigation
**Purpose:** Slide-out mobile menu (hamburger), fixing the current app's missing mobile navigation. Renders nav links + user info + logout.
**Props:**
```typescript
interface MobileMenuProps {
onClose: () => void;
}
```
**SolidJS patterns:** `onCleanup` to add/remove click-outside handler. `` to iterate nav items.
---
#### `src/components/layout/AdminLayout.tsx` -- Admin Section Layout
**Purpose:** Layout for `/admin/*` routes. Includes sidebar with links to provider catalog, rate limits, user management. Wraps admin pages.
**SolidJS patterns:** Nested `` children via `props.children`. Active sidebar link via `useLocation()`.
---
#### `src/components/auth/ProtectedRoute.tsx` -- Route Guard
**Purpose:** Checks auth state. If unauthenticated, redirects to `/login`. If loading, shows full-page spinner.
**SolidJS patterns:**
- Reads `user` and `loading` from `AuthContext`
- Uses `` with `fallback` for the loading state
- Uses `` from `@solidjs/router` for redirect
```typescript
import { Component, Show } from 'solid-js';
import { Navigate } from '@solidjs/router';
import { useAuth } from '~/context/AuthContext';
import Spinner from '~/components/ui/Spinner';
const ProtectedRoute: Component = (props) => {
const { user, loading } = useAuth();
return (
}
>
}
>
{props.children}
);
};
```
**React pattern replaced:** Current `ProtectedRoute` uses `if (loading) return ...` and `if (!user) return `. SolidJS uses nested `` components instead of early returns.
---
### 2.2 Auth Pages
#### `src/pages/auth/Login.tsx` -- Magic Link Login
**Purpose:** Email field + Cloudflare Turnstile captcha + submit to request magic link. Shows confirmation message after submit with resend button.
**Signals:**
```typescript
const [email, setEmail] = createSignal('');
const [turnstileToken, setTurnstileToken] = createSignal(null);
const [submitted, setSubmitted] = createSignal(false);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
const [resendCooldown, setResendCooldown] = createSignal(0);
```
**API calls:**
- `POST /api/v1/auth/login` with `{ email, captcha_token }`
**SolidJS patterns:**
- `onMount` to load the Turnstile script if not already loaded
- `createEffect` to manage resend cooldown timer (countdown from 60s)
- `onCleanup` to clear the timer interval
- `` to toggle between form view and confirmation view
**Turnstile integration:** Load via `