diff --git a/.env.example b/.env.example index e189117..515a268 100644 --- a/.env.example +++ b/.env.example @@ -1,25 +1,51 @@ -# Environment Variables Examples +# Found-U Environment Variables +# See README "Deploy โรงเรียนใหม่" for 1-click Vercel + Supabase setup + +# ═══════════════════════════════════════════════════════════════════ +# REQUIRED — auto-injected by Vercel + Supabase integration +# ═══════════════════════════════════════════════════════════════════ -# Supabase (Client-side / Server-side) NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY -# Cloudflare R2 API KEY +# Direct Postgres (first-boot schema hydration — Module 2) +# Vercel + Supabase: auto-synced. Local: Dashboard → Database or `vercel env pull` +# Alternative local: `bun run db:push` if schema is already complete +POSTGRES_URL_NON_POOLING=postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres +POSTGRES_URL=postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true + +# App identity (prompted on Vercel Deploy Button) +NEXT_PUBLIC_APP_URL=https://your-school.example.com +SCHOOL_AUTH_DOMAIN=your-school.ac.th + +# ═══════════════════════════════════════════════════════════════════ +# OPTIONAL — AI (Setup Wizard Step 2 or manual env; Module 5 reads DB first) +# ═══════════════════════════════════════════════════════════════════ + +GEMMA_API_KEY=YOUR_GEMMA_API_KEY +OPENROUTER_API_KEY=YOUR_OPENROUTER_API_KEY +OPENROUTER_MODEL=google/gemini-2.0-flash-exp:free + +# Optional encryption key override for wizard-stored AI keys (defaults to service role key) +# SETUP_SECRETS_KEY= + +# ═══════════════════════════════════════════════════════════════════ +# OPTIONAL — Storage (R2 for existing production; new deploys use Supabase Storage) +# ═══════════════════════════════════════════════════════════════════ + R2_ACCOUNT_ID=YOUR_R2_ACCOUNT_ID R2_ACCESS_KEY_ID=YOUR_R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY=YOUR_R2_SECRET_ACCESS_KEY R2_BUCKET_NAME=YOUR_R2_BUCKET_NAME R2_PUBLIC_BASE_URL=YOUR_R2_PUBLIC_BASE_URL -# Rate Limiting (Optional) +# ═══════════════════════════════════════════════════════════════════ +# OPTIONAL — Rate limiting & search tuning +# ═══════════════════════════════════════════════════════════════════ + RATE_LIMIT_MAX=100 RATE_LIMIT_WINDOW_MS=60000 - -# School login (synthetic Supabase auth email domain) -SCHOOL_AUTH_DOMAIN=your.domain.com -# App URL (ใช้โดเมนหลักใดก็ได้) -NEXT_PUBLIC_APP_URL=https://your.domain.com - -# Gemini API KEY -GEMMA_API_KEY=YOUR_GEMMA_API_KEY \ No newline at end of file +SEARCH_USE_TRGM=true +SEARCH_SIMILARITY_THRESHOLD=0.15 +AGENT_SEARCH_SIMILARITY_THRESHOLD=0.30 diff --git a/.gitignore b/.gitignore index 9a02c60..7f39dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,9 +48,11 @@ service-account*.json *.tsbuildinfo next-env.d.ts +# development docs/ .vscode/ .cursor/ +supabase/.temp/ ืtools/ *.csv tools/tools.txt @@ -60,4 +62,7 @@ screencapture/ Mermaid_Flowchart/ .audit/ .VSCodeCounter/ -tests/ \ No newline at end of file +tests/ +AGENTS.md +DESIGN.md +PRODUCT.md \ No newline at end of file diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 0000000..cce5e2d --- /dev/null +++ b/.impeccable/design.json @@ -0,0 +1,125 @@ +{ + "schemaVersion": 2, + "generatedAt": "2026-07-09T16:30:00.000Z", + "title": "Design System: Found-U", + "extensions": { + "colorMeta": { + "line-green": { + "role": "primary", + "displayName": "Campus Green", + "canonical": "#06C755", + "tonalRamp": ["#024d20", "#036b2c", "#049c42", "#05b34d", "#06C755", "#3dd47a", "#6ee09a", "#e8f8ef"] + }, + "bg-secondary": { + "role": "neutral", + "displayName": "Hall Gray", + "canonical": "#F7F8FA", + "tonalRamp": ["#111111", "#1A1A1A", "#262626", "#374151", "#6B7280", "#9CA3AF", "#E5E7EB", "#F7F8FA"] + }, + "text-primary": { + "role": "neutral", + "displayName": "Ink", + "canonical": "#191919", + "tonalRamp": ["#0A0A0A", "#111111", "#191919", "#374151", "#4B5563", "#6B7280", "#9CA3AF", "#D1D5DB"] + } + }, + "typographyMeta": { + "display": { "displayName": "Display", "purpose": "Page greetings and primary headings." }, + "body": { "displayName": "Body", "purpose": "Forms, lists, assistant messages—always Ink on light surfaces." } + }, + "shadows": [ + { "name": "card-rest", "value": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", "purpose": "Default card separation—minimal elevation only." }, + { "name": "shadow-md", "value": "0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05)", "purpose": "Modals and elevated overlays sparingly." } + ], + "motion": [ + { "name": "ease-standard", "value": "cubic-bezier(0.4, 0, 0.2, 1)", "purpose": "Default UI transitions (200ms)." }, + { "name": "ease-entrance", "value": "cubic-bezier(0.22, 1, 0.36, 1)", "purpose": "Scale-up entrance animations (280ms)." } + ], + "breakpoints": [ + { "name": "md", "value": "768px" } + ] + }, + "components": [ + { + "name": "Primary Button", + "kind": "button", + "refersTo": "button-primary", + "description": "Main CTA—report lost, submit, confirm match.", + "html": "", + "css": ".ds-btn-primary { background: #06C755; color: #fff; border: none; border-radius: 9999px; padding: 14px 32px; font-family: Kanit, sans-serif; font-size: 1rem; font-weight: 500; cursor: pointer; transition: background 0.2s ease, transform 0.2s ease; } .ds-btn-primary:hover { background: #05b34d; transform: translateY(-1px); } .ds-btn-primary:active { transform: translateY(0); } .ds-btn-primary:focus-visible { outline: 2px solid #e8f8ef; outline-offset: 2px; }" + }, + { + "name": "Secondary Button", + "kind": "button", + "refersTo": "button-secondary", + "description": "Cancel, back, low-emphasis actions.", + "html": "", + "css": ".ds-btn-secondary { background: #ECEEF1; color: #191919; border: none; border-radius: 9999px; padding: 14px 32px; font-family: Kanit, sans-serif; font-size: 1rem; font-weight: 500; cursor: pointer; transition: background 0.2s ease; } .ds-btn-secondary:hover { background: #e5e5e5; } .ds-btn-secondary:focus-visible { outline: 2px solid #e8f8ef; outline-offset: 2px; }" + }, + { + "name": "Text Field", + "kind": "input", + "refersTo": "input-field", + "description": "Filled input for forms—lost/found reports, search.", + "html": "", + "css": ".ds-input { width: 100%; background: #f5f5f5; border: none; border-radius: 0.75rem; padding: 14px 16px; font-family: Kanit, sans-serif; font-size: 1rem; color: #191919; transition: background 0.2s ease, box-shadow 0.2s ease; } .ds-input::placeholder { color: #9CA3AF; } .ds-input:focus { outline: none; background: #eeeeee; box-shadow: 0 0 0 2px #e8f8ef; }" + }, + { + "name": "Card Surface", + "kind": "card", + "refersTo": "card-surface", + "description": "List items, dashboard rows, content grouping.", + "html": "
พบที่ห้องสมุด ชั้น 2