feat(web): add authenticated app shell

This commit is contained in:
zl-q
2026-05-09 16:00:29 +08:00
parent c12320cb79
commit 5aa46d3311
73 changed files with 2571 additions and 250 deletions
+67 -206
View File
@@ -1,232 +1,93 @@
# Web Development Guidelines
# Web Spec
> Astro 6 + React 19 + Tailwind CSS 4 + shadcn/ui
## Scope
---
This spec applies to `web/**`.
## Tech Stack
Read this file before changing the Astro site, React app islands, authenticated app routes, API clients, i18n, or responsive layout.
| Layer | Technology | Version |
|-------|-----------|---------|
| Framework | Astro | 6.x |
| Interactive UI | React | 19.x (client-only, no RSC) |
| Styling | Tailwind CSS | 4.x (via `@tailwindcss/vite`) |
| Component Library | shadcn/ui | latest |
| Language | TypeScript | 5.x (strict) |
| i18n | Built-in (type-safe object in `src/i18n/utils.ts`) | - |
| Markdown Rendering | `marked` | latest |
| Auth | Supabase Auth JS SDK | - |
## Current Stack
### Architecture Decision
- Astro 6 for static public pages and route files.
- React 19 for interactive client UI.
- React Router DOM for the authenticated business app shell.
- Tailwind CSS 4 through `@tailwindcss/vite`.
- TypeScript strict mode.
- Local i18n from `web/src/i18n/utils.ts`.
- Backend API base for production: `https://api.meeyao.com`.
- Local development API access uses the Vite `/api` proxy in `web/astro.config.mjs`.
- **Astro SSG** for marketing/public pages (SEO-critical)
- **React client islands** for interactive parts (Login, Dashboard, Notifications)
- **No React Server Components** — CVE-2025-55182 risk eliminated
- **No Next.js** — multiple critical CVEs (CVE-2025-55182, CVE-2025-29927, CVE-2025-66478)
Do not introduce a second frontend framework, a second router, or scattered API URL construction for web code.
### Actual Dependencies
## Route Architecture
```
astro, @astrojs/react, react, react-dom, @tailwindcss/vite, tailwindcss, marked
```
Public pages are Astro pages under `web/src/pages/{locale}/` and use `Marketing.astro`.
---
Authenticated pages are Astro route shells that all render `DashboardAppPage.astro`. The actual logged-in application is a single React Router app:
## Pre-Development Checklist
- `DashboardApp.tsx` owns React Router routes for dashboard, store, history, notifications, profile, settings, and divination pages.
- `AppShell.tsx` owns the persistent sidebar, mobile drawer, route guard, and authenticated layout.
- Business page components render only their page body. They must not wrap themselves in `AppShell`.
- Sidebar navigation must use React Router navigation so the shell remains mounted and only the right-side content changes.
- Direct browser refresh on each existing business route must still render the app shell through Astro.
Before writing web code, read:
Login and public marketing/legal pages are not part of the authenticated app shell.
- [ ] This index
- [ ] Design file: `web/design/eryao.pen`
- [ ] Backend API contracts: `docs/protocols/`
- [ ] Error code mapping: `docs/protocols/common/http-error-codes.md`
- [ ] Mobile i18n files (for translation sync): `apps/lib/l10n/app_*.arb`
## Auth Rules
---
- Login and registration are the same email-code flow. The backend auto-registers new email accounts.
- Test credentials for local verification: `test@example.com` with code `123456`.
- Auth state is stored by `web/src/lib/auth.ts` under one local storage key.
- Every authenticated route must recover or refresh the session before showing business content.
- Missing, expired, invalid, or refresh-failed tokens must clear local auth and redirect to `/{locale}/login`.
- Do not add silent success paths for auth failures.
## Project Structure
## API Rules
```
web/
├── design/ # Pencil design files (.pen)
│ ├── eryao.pen
│ └── assets/ # Shared assets (images, legal)
│ ├── images/
│ └── legal/{zh,zh_Hant,en}/
├── public/
│ ├── images -> ../design/assets/images # symlink, no duplication
│ ├── legal -> ../design/assets/legal # symlink, no duplication
│ ├── favicon.ico
│ └── favicon.svg
├── src/
│ ├── components/
│ │ ├── Navbar.astro # Marketing nav (responsive, lang switcher)
│ │ ├── Footer.astro # Marketing footer (responsive)
│ │ ├── Hero.astro # Landing hero section
│ │ ├── Showcase.astro # Landing showcase section
│ │ ├── Testimonials.astro # Landing testimonials section
│ │ ├── CtaSection.astro # Landing CTA section
│ │ ├── FeaturesPage.astro # Features grid page
│ │ ├── PricingPage.astro # Pricing cards page
│ │ ├── AboutPage.astro # About + company info page
│ │ └── LegalPage.astro # Markdown legal page (generic)
│ ├── layouts/
│ │ └── Marketing.astro # Nav + content + footer + scroll animations
│ ├── pages/
│ │ ├── index.astro # Root redirect -> /zh/
│ │ ├── {zh,zh_Hant,en}/
│ │ │ ├── index.astro # Landing
│ │ │ ├── features.astro
│ │ │ ├── pricing.astro
│ │ │ ├── about.astro
│ │ │ ├── privacy.astro
│ │ │ └── terms.astro
│ ├── i18n/
│ │ └── utils.ts # Type-safe translations (all inline)
│ └── styles/
│ ├── global.css # Tailwind entry
│ └── animations.css # Scroll reveal animations
├── astro.config.mjs
├── tsconfig.json
└── package.json
```
- All API paths live in `web/src/lib/api-routes.ts`.
- Shared request behavior lives in `web/src/lib/api-client.ts`.
- Auth/session behavior lives in `web/src/lib/auth.ts`.
- Business API functions live in `web/src/lib/api.ts`.
- Components must call typed API helper functions, not inline `fetch('/api/...')`.
- Dashboard-visible user, points, notification, and history data must come from the backend. Do not hardcode those values.
- Production API host is `https://api.meeyao.com`; local dev should use same-origin `/api` and the Vite proxy.
---
## Layout Rules
## Responsive Layout
- Build mobile-first, then add `sm:`, `md:`, `lg:`, and `xl:` refinements.
- Business pages must not require horizontal scrolling at common phone widths such as `390x844`.
- Use responsive stacks for fixed-width desktop columns: `flex-col lg:flex-row`, `w-full lg:w-[...]`.
- Keep the authenticated shell as `h-screen` with the main content scrollable.
- Mobile sidebar must be reachable through the menu button and must not hide the page content permanently.
- Public header mobile navigation must expose feature, pricing, about, login, and language switching.
**All pages must be fully responsive: full-width sections, no fixed pixel widths.**
## i18n Rules
### Layout Principles
- Supported locales: `zh`, `zh_Hant`, `en`.
- Routes are prefixed by locale, including the default locale.
- User-visible text should come from `web/src/i18n/utils.ts` or locale-specific content assets.
- Do not add user-facing strings in only one locale.
1. **Full-width sections** — Each section spans `w-full`, content is centered with `max-w-7xl mx-auto`
2. **No fixed pixel widths** — Use `w-full`, `max-w-*`, and `flex/grid` for all layouts
3. **Mobile-first** — Base styles target mobile, `md:` breakpoint for desktop
4. **Viewport-filling sections** — Hero and CTA use `min-h-screen` or generous padding to fill viewport
5. **Smooth scroll transitions** — Each section flows into the next with gradients or color changes
## Design Source
### Breakpoints
- Pencil design files under `web/design/` are the visual source for login and public page design.
- If UI implementation diverges from Pencil, inspect the design first and keep the code aligned unless the user explicitly asks to change the design.
- Assets in `web/public/images` and `web/public/legal` are symlinks to `web/design/assets`; do not duplicate them.
| Breakpoint | Target |
|-----------|--------|
| Base (< 768px) | Mobile phones |
| `md:` (>= 768px) | Tablets and desktop |
## Verification
### Section Patterns
Before finishing meaningful web changes:
```
Hero: w-full, min-h-screen, bg-gradient, centered content
Showcase: w-full, flex-col md:flex-row, gap, centered max-w
Testimonials: w-full, bg-slate-900, grid cols-1 md:cols-3
CTA: w-full, bg-violet-600, centered
Footer: w-full, bg-slate-950, responsive flex
```
- Run `npm run build` in `web/`.
- Use Chrome DevTools on `http://localhost:4322/` for at least one desktop and one phone viewport when layout or routing changed.
- For authenticated changes, verify the test account can log in and lands on `/{locale}/dashboard`.
- Verify direct unauthenticated access to a business route redirects to login.
- Verify sidebar navigation changes content without a full document reload.
---
## Forbidden
## Design Tokens
All colors from design map directly to Tailwind defaults:
| Token | Tailwind | Hex |
|-------|----------|-----|
| Primary | `violet-600` | #7C3AED |
| Primary light | `violet-100` | — |
| Background | `white` | #FFFFFF |
| Surface | `slate-50` | #F8FAFC |
| Dark bg | `slate-900` | #0F172A |
| Footer bg | `slate-950` | #020617 |
| Border | `slate-200` | #E2E8F0 |
| Warning bg | `amber-50` | #FFFBEB |
| Gradient top | `white` -> `violet-50` | — |
---
## Scroll Animations
Defined in `src/styles/animations.css`, triggered by `IntersectionObserver` in `Marketing.astro`.
| Class | Effect | Use Case |
|-------|--------|----------|
| `.reveal` | Fade up (translateY 24px) | Default for most elements |
| `.reveal-left` | Slide from left | Showcase left column |
| `.reveal-right` | Slide from right | Showcase right column |
| `.reveal-scale` | Scale in (0.95 -> 1) | CTA section |
| `.stagger-1` to `.stagger-4` | Delay 0.1s-0.4s | Grouped elements (cards, grid items) |
- All animations: `0.6-0.7s`, `cubic-bezier(0.22, 1, 0.36, 1)` (ease-out-quint)
- `prefers-reduced-motion: reduce` disables all animations
- Observer `rootMargin: 0px 0px -60px 0px` for pre-trigger
- Elements are `opacity: 0` until `.visible` class is added, then `animation-fill-mode: forwards`
---
## i18n (CRITICAL)
**Every user-visible text must use i18n keys. No hardcoded strings.**
### Supported Locales
| Code | Language | Brand Name |
|------|----------|------------|
| `zh` | 简体中文 | 觅爻签问 |
| `zh_Hant` | 繁體中文 | 覓爻簽問 |
| `en` | English | MeiYao Divination |
### Implementation
- All translations are in `src/i18n/utils.ts` as a typed `Record<Locale, Translations>` object
- Access via `t(locale, 'section')` which returns the section object (e.g., `t(locale, 'nav').features`)
- URL strategy: `/{locale}/path` prefix (e.g., `/zh/`, `/en/`)
- Default locale: `zh`, root `/` redirects to `/zh/`
- Config in `astro.config.mjs` with `prefixDefaultLocale: true`
### Rules
1. **All text must come from `t()`** — never inline strings
2. **Brand name** is always from `footer.brandName` (consistent across Navbar/Footer)
3. **Check Flutter i18n first** (`apps/lib/l10n/app_*.arb`) before inventing translations
4. **Legal content** loaded from `public/legal/{locale}/` markdown files
---
## Assets
- Shared assets live in `web/design/assets/` (used by Pencil)
- `web/public/images` and `web/public/legal` are **symlinks** to `../design/assets/*`
- **Never duplicate** assets — always use symlinks or references
---
## Cross-Layer Contracts
### API Integration
- Backend: FastAPI REST endpoints (see `docs/protocols/`)
- Auth: Supabase Auth JS SDK (client-side)
- Error format: RFC 7807 `ApiProblem` (same as mobile)
### Brand Consistency (Flutter app)
- App title: "觅爻签问" / "MeiYao Divination" (NOT "MiYao")
- Company: 洵觅科技(深圳)有限公司 / Xunmee Technology (Shenzhen) Co., Ltd.
- Contact: xuyunlong@xunmee.com
- ICP: 粤ICP备2025428416号-1A
---
## Quality Rules
### Forbidden
- Do not use React Server Components (RSC)
- Do not import server-only modules in client islands
- Do not hardcode colors outside Tailwind tokens
- Do not use fixed pixel widths for layout (use responsive utilities)
- Do not use `any` in TypeScript
- Do not use third-party shadcn registries (official only)
- Do not duplicate assets — use symlinks
### Required
- All pages must be fully responsive (mobile + desktop)
- All sections must be full-width with centered content
- All pages must pass Lighthouse SEO >= 95 (marketing pages)
- All interactive components must be accessible (ARIA)
- `npm audit` must be clean before merge
- TypeScript strict mode enabled
- `prefers-reduced-motion` must disable animations
- Do not hardcode API URLs or endpoint paths in components.
- Do not add `.env` as a required file for normal local development.
- Do not reintroduce page-refresh sidebar navigation inside the authenticated app.
- Do not wrap business page components in nested cards or duplicate `AppShell`.
- Do not add fallback/mock success behavior for failed API calls.