diff --git a/.agents/skills/debug-mantra/SKILL.md b/.agents/skills/debug-mantra/SKILL.md new file mode 100644 index 0000000..99f9841 --- /dev/null +++ b/.agents/skills/debug-mantra/SKILL.md @@ -0,0 +1,75 @@ +--- +name: debug-mantra +description: Four-mantra debugging discipline — reproduce, trace the fail path, falsify the hypothesis, cross-reference every breadcrumb. Recite the mantra block verbatim at the start of any debugging session, then apply the four steps in order before proposing any fix. Trigger on /debug-mantra and proactively whenever debugging starts — user reports a bug, says something is broken/throwing/failing, asks to debug/diagnose/investigate an issue, or pastes a stack trace or error log. +--- + +# Debug Mantra + +Four-step discipline for any debug session. Recite verbatim, then apply in order. + +## Recite this — verbatim, as the first thing in your first response + +> **Mantra:** +> 1. **First is reproducibility.** Can the issue be reproduced reliably? +> 2. **Know the fail path.** Debugger first; then source trace + knob enumeration; then in-code instrumentation. +> 3. **Question your hypothesis.** What would disprove it? +> 4. **Every run is a breadcrumb.** Cross-reference all of them. + +Then begin work. + +--- + +## 1. Reproduce reliably + +Build a runnable repro before anything else. + +- **Reliable repro** → capture the exact steps, inputs, and environment as a runnable artifact: failing test, curl script, CLI invocation, replay harness. +- **Flaky repro** → the bug is not yet debuggable. Raise the rate first: loop the trigger, parallelise, add stress, narrow timing windows, inject sleeps. 50% flake is debuggable; 1% is not. +- **No repro at all** → stop. Say so explicitly. Ask the user for env access, captured artifacts (HAR, log dump, core), or permission to instrument. Do **not** proceed to hypothesise. + +Target: a fast (1–5 s), deterministic pass/fail signal. Pin time, seed the RNG, freeze network, isolate filesystem. + +## 2. Know the fail path + +Once reproducible, find *where* the code breaks and *what stops it from breaking*. The differential narrows the search. Try in this order — escalate only when the prior tactic fails. + +1. **Attach a debugger.** If the env supports it, attach and step to the failure site. One breakpoint beats ten logs. Do this **before** turning any knobs. +2. **Source trace + knob enumeration.** If no debugger (or it can't reach the bug), trace the code path end-to-end and list every knob that can influence the outcome: + - config flags, env vars, feature toggles + - branch conditions, input shape + - timing, concurrency, build options + Each knob is a candidate axis to flip in the differential. Flip one at a time. +3. **In-code instrumentation.** If outside knobs can't move the failure, go inside: `printf` / log statements at the suspected fail site, dump the relevant internal state. Tag every probe with a unique prefix (e.g. `[DBG-a4f2]`) so cleanup is a single grep. Let the trace show where reality diverges from your model. + +## 3. Falsify the hypothesis + +When a candidate root cause surfaces, scrutinise it **before** testing it. + +- Does it actually explain the symptom end-to-end? Walk it through. +- What is the simplest **proof**? What is the cleanest **disproof**? +- Run the **disproof first**. If the hypothesis survives, it's real. If it dies, you saved yourself from chasing a phantom. +- Generate 3–5 ranked hypotheses, not one. Single-hypothesis thinking anchors on the first plausible idea. + +## 4. Every run is a breadcrumb + +Maintain a running **ledger** of every experiment in this session. Each entry: what changed, what happened, what it ruled in or out. + +- When a new hypothesis surfaces, walk the ledger. Does it hold for **every** prior observation, not just the most recent? +- If any past run contradicts it, the hypothesis is wrong or incomplete — refine or discard. +- When in doubt, design the **single experiment** whose outcome makes it certain. Run that next, instead of churning on adjacent runs. +- Update the ledger after every run. It is your memory across the session. + +--- + +## Operating rules + +- Recite the mantra block **once** per debug session, in your first response. Do not re-recite mid-session. +- Recite **verbatim**. Never paraphrase, shorten, or skip lines of the recital. +- If the user says "skip the mantra" → skip the recital but still apply the four steps silently. +- Apply the four steps **in order**: + - Do not propose a fix before #1 is satisfied (reliable repro exists). + - Do not start testing hypotheses before #2 has narrowed the fail path. + - Do not commit to a hypothesis before #3 has tried to disprove it. + - Do not declare a hypothesis correct until #4 confirms it against every prior breadcrumb. +- If you catch yourself proposing a fix without a reliable repro, stop and return to step 1. +- The mantra is a constraint **you** carry through the session — not advice to deliver back to the user. \ No newline at end of file diff --git a/.agents/skills/design-system-auditor/SKILL.md b/.agents/skills/design-system-auditor/SKILL.md new file mode 100644 index 0000000..4e442cd --- /dev/null +++ b/.agents/skills/design-system-auditor/SKILL.md @@ -0,0 +1,200 @@ +--- +name: design-system-auditor +description: Design System Auditor Agent ตรวจสอบและแก้ไขปัญหา Color Contrast, Text Readability, และ Component Visibility ทั่วทั้งโปรเจกต์ HomAroma ให้ผ่านมาตรฐาน WCAG 2.1 AA โดยอิงกับ `spa` color palette ที่กำหนดไว้ใน tailwind.config.js +--- + +# Skill: Design System Auditor Agent + +## Role +คุณคือ **Design System Auditor Agent** ของโปรเจกต์ HomAroma +ทำหน้าที่เป็น "ผู้พิทักษ์ความชัดเจนในการอ่าน (Readability Guardian)" ตรวจสอบทุก Component และ Page ให้แน่ใจว่าข้อความอ่านออกได้ชัดเจนบนพื้นหลังทุกสี และ UI Elements ต่างๆ มองเห็นได้ชัดเจนพอ + +## Context: ปัญหาที่พบบ่อยในโปรเจกต์นี้ + +จากการตรวจสอบ พบปัญหาหลัก 3 ประเภทที่เกิดซ้ำๆ: + +1. **ตัวหนังสือกลืนพื้นหลัง** — เช่น ปุ่มสีอ่อนบนพื้น `spa-light` (#FDFDF9 ชมพูอ่อน/ขาวครีม) +2. **Menu / Navigation Links ไม่ชัด** — สีเทาอ่อนบนพื้นขาว อ่านแทบไม่ออก +3. **Input/Textbox กลมกลืนพื้น** — Border, Placeholder ไม่ตัดกับพื้นหลัง + +--- + +## 🎨 HomAroma Spa Color Palette (ข้อมูลอ้างอิง) + +```js +// tailwind.config.js — spa palette +spa: { + light: '#FDFDF9', // พื้นหลังหลัก (ขาวครีม) + sage: '#EAEFEA', // พื้นหลัง Section (เขียวตุ่น) + stone: '#4B4B46', // ตัวอักษรทั่วไป (เทาเข้มอมน้ำตาล) + forest: '#1F3D2D', // ตัวอักษรหลัก/หัวข้อ (เขียวเข้ม) + gold: '#D4AF37', // จุดเน้น / Accent (ทอง) +} +``` + +### WCAG 2.1 Contrast Ratio Requirements +| ระดับ | Body Text (< 18px) | Large Text (≥ 18px / Bold 14px) | +|---|---|---| +| **AA (ต้องผ่าน)** | 4.5:1 | 3:1 | +| **AAA (ดีที่สุด)** | 7:1 | 4.5:1 | + +### Approved Color Combinations (ผ่านแล้ว ✅) +| Text Color | Background | Ratio | Use Case | +|---|---|---|---| +| `spa-forest` (#1F3D2D) | `spa-light` (#FDFDF9) | ~14:1 ✅ | หัวข้อบนพื้นขาว | +| `spa-stone` (#4B4B46) | `spa-light` (#FDFDF9) | ~9:1 ✅ | Body text | +| `spa-stone` (#4B4B46) | `spa-sage` (#EAEFEA) | ~7:1 ✅ | Text บน Section | +| `white` (#FFFFFF) | `spa-forest` (#1F3D2D) | ~14:1 ✅ | ปุ่มหลัก | +| `spa-forest` (#1F3D2D) | `spa-gold` (#D4AF37) | ~4.7:1 ✅ | Badge/Tag | + +### ❌ Combinations ที่ห้ามใช้ (Fail) +| Text Color | Background | ปัญหา | +|---|---|---| +| `spa-gold` (#D4AF37) | `spa-light` (#FDFDF9) | Ratio ~1.8:1 ❌ ทองบนขาว อ่านไม่ออก | +| Gray-300 / slate-300 | White | Ratio < 2:1 ❌ เทาอ่อนบนขาว | +| `spa-sage` (#EAEFEA) | `spa-light` (#FDFDF9) | ต่างกันแค่เล็กน้อย ❌ | +| White text | White/Light bg | ❌ มองไม่เห็นเลย | + +--- + +## 🛠️ Capabilities (ขีดความสามารถ) + +### 1. Contrast Audit (ตรวจสอบความต่าง) +- ไล่ดูทุก Component ที่มีสีตัวหนังสือและพื้นหลัง +- คำนวณ Contrast Ratio และรายงานว่า Fail หรือ Pass +- ระบุ Class Tailwind ที่ต้องแก้ไขพร้อม Fix ที่แนะนำ + +### 2. Button & CTA Audit +**ปัญหาที่ต้องตรวจ:** +- ปุ่มที่ใช้ `bg-white/10` หรือ `bg-transparent` บนพื้น Light → ตัวหนังสือหาย +- ปุ่ม "DISCOVER" / "Learn More" สีอ่อนบนพื้นอ่อน + +**Fix Pattern:** +```tsx +// ❌ Bad — กลมกลืน + + +// ✅ Good — ชัดเจน + + +// หรือ Outlined ที่ยังชัด + +``` + +### 3. Navigation & Menu Audit +**ปัญหาที่ต้องตรวจ:** +- Menu items ใช้ `text-slate-300` หรือ `text-gray-400` บนพื้นขาว +- Mobile Drawer menu links ที่อ่านยาก + +**Fix Pattern:** +```tsx +// ❌ Bad +ติดต่อเรา +ค้าที่พบบ่อย + +// ✅ Good +ติดต่อเรา +ค้าที่พบบ่อย +``` + +### 4. Input / Textbox Audit +**ปัญหาที่ต้องตรวจ:** +- Input ที่มี `border-white/20` หรือ `bg-white/10` บนพื้นขาว → มองไม่เห็นกรอบ +- Placeholder สีอ่อนเกินไป + +**Fix Pattern:** +```tsx +// ❌ Bad — กรอบกลมกลืน + + +// ✅ Good — กรอบชัดเจน + +``` + +### 5. Price & Label Audit +**ปัญหาที่ต้องตรวจ:** +- ราคา `฿69` ที่แสดงด้วยสีอ่อนบน `spa-light` + +**Fix Pattern:** +```tsx +// ❌ Bad +฿69 + +// ✅ Good +฿69 +``` + +--- + +## 🔍 Audit Workflow (ขั้นตอนการตรวจสอบ) + +### Step 1: Scan ไฟล์ Component ทั้งหมด +```bash +# ค้นหา Tailwind class สีที่น่าสงสัย +grep -rn "text-slate\|text-gray\|text-white\|bg-white/\|text-amber\|text-cyan\|opacity-" \ + apps/spa/src/ +``` + +### Step 2: จัดกลุ่มปัญหา +แบ่งเป็น 3 ระดับความเร่งด่วน: +- 🔴 **Critical** — อ่านไม่ออกเลย (ratio < 2:1) +- 🟡 **Warning** — อ่านยาก (ratio 2:1 – 4.5:1) +- 🟢 **Pass** — ผ่าน WCAG AA (ratio ≥ 4.5:1) + +### Step 3: สร้าง Audit Report +สร้างรายงานในรูปแบบ Markdown ที่ระบุ: +- ชื่อไฟล์ + Line Number +- Class ที่ปัจจุบัน (ปัญหา) +- Class ที่แนะนำ (Fix) +- Contrast Ratio ก่อน/หลัง + +### Step 4: แก้ไขโค้ด +แก้ไขตามลำดับ Critical → Warning โดยใช้ `multi_replace_file_content` tool + +### Step 5: ตรวจสอบซ้ำ +หลังแก้ไข ให้ทำ Re-scan เพื่อยืนยันว่าแก้ครบแล้ว + +--- + +## 🚫 Constraints & Rules (กฎเหล็กในการทำงาน) + +> [!IMPORTANT] +> **RULE #1: ห้ามเปลี่ยน Color Palette ใน tailwind.config.js** +> หน้าที่ของคุณคือ "ใช้สีที่มีอยู่ให้ถูกต้อง" ไม่ใช่สร้างสีใหม่ หากเห็นว่า Palette ต้องการสีเพิ่ม ให้แจ้ง Orchestrator + +> [!WARNING] +> **RULE #2: ห้ามแตะต้อง Business Logic** +> แก้ได้เฉพาะ Tailwind CSS classes ที่เกี่ยวกับสีและการแสดงผลเท่านั้น + +> [!CAUTION] +> **RULE #3: ต้อง Cross-check กับ Dark Mode / Overlay Components** +> บาง Component อาจมีพื้นหลังซ้อนกัน (เช่น Image + Gradient Overlay) ต้องตรวจสอบให้ครบทุก Layer + +- **RULE #4: รายงานต้องครบก่อนแก้** + สร้าง Audit Report ให้ครบก่อนเสมอ ไม่แก้ทีละจุดแบบไม่มีแผน +- **RULE #5: Hover/Focus State ต้องตรวจด้วย** + ตรวจทั้ง Default, Hover, Focus, Disabled State ไม่ใช่แค่ Default เท่านั้น + +--- + +## 📋 Quick Reference: Fix Cheatsheet สำหรับ HomAroma + +| สถานการณ์ | ❌ ห้ามใช้ | ✅ ควรใช้ | +|---|---|---| +| ตัวหนังสือบนพื้น Light/White | `text-slate-300`, `text-gray-400`, `text-white/60` | `text-spa-stone`, `text-spa-forest` | +| ตัวหนังสือ Accent/เน้น | `text-spa-gold` บนพื้นขาว | `text-spa-gold` บน `spa-forest` เท่านั้น | +| ปุ่มหลัก (Primary) | `bg-white/10 text-white` | `bg-spa-forest text-white` | +| ปุ่มรอง (Secondary/Ghost) | `border-white/20 text-white/50` | `border-spa-forest text-spa-forest` | +| Input box บนพื้นขาว | `border-white/20 bg-white/10` | `border-spa-stone/40 bg-white` | +| Placeholder text | `placeholder-white/30` | `placeholder-spa-stone/50` | +| ราคาสินค้า | `text-amber-200` | `text-spa-forest font-semibold` | +| หัวข้อ Section | `text-white` บนพื้น Light | `text-spa-forest` | +| Icon บน Light bg | `text-white`, `text-gray-300` | `text-spa-stone`, `text-spa-forest` | diff --git a/.agents/skills/management-talk/SKILL.md b/.agents/skills/management-talk/SKILL.md new file mode 100644 index 0000000..f6ad9b7 --- /dev/null +++ b/.agents/skills/management-talk/SKILL.md @@ -0,0 +1,85 @@ +--- +name: management-talk +description: Rewrite engineer-to-engineer content for engineering-org leadership (VPs, directors, PMs) and shape it specifically for Taskboard MCP operations (submit_task_result, add_comment, create_sub_task). Trigger when the user asks to write/rewrite an update, report, or summary for management, or asks to make technical text less jargony specifically for taskboard updates. +--- + +# Management Talk for Taskboard MCP + +Same audience and translation rules as a traditional written status report, but **shaped specifically for Taskboard MCP operations** — `submit_task_result`, `add_comment`, or `create_sub_task`. The audience reads project names, task numbers, and high-level impacts, but not deep code logic. + +Use this any time engineering content needs to be summarized and logged into the Taskboard for management, product teams, or cross-functional tracking. + +## When to invoke + +- "write something for management / PM to put in the taskboard" +- "rewrite this technical update for the taskboard comment" +- "make this non-technical" / "less techy" / "less jargony" for a task update +- "executive summary" / "status update" to submit the task result +- "create a sub-task description based on this technical bug" + +## Audience — what "engineering-org leadership" means + +Engineering-savvy non-engineers: VPs, directors, PMs, release managers. They read product/framework names and cross-reference Task IDs. They do not read code. + +They want: *what's the state, what does it mean for customers, who owns it, what's next.* They do not want: how the bug works at the function level. + +This is **not** for marketing, finance, or customer-facing audiences — those need a different rewrite. Flag and confirm before producing one. + +## Tone + +**Keep.** Product names, framework names, team-owned component names, Task IDs, PR numbers, customer/workload identifiers. These are the bridge between engineering and leadership tracking. + +**Strip.** Function names, file paths, struct fields, commit SHAs, code expressions, env var names, line numbers, internal data-structure jargon. None of this is actionable to the audience. + +**Translate.** Mechanism into one or two sentences of plain-English cause-and-effect. Not *"the kernel reads from `scratchBuf == NULL`"* but *"the GPUs end up reading from an uninitialized buffer and wait forever for a signal that never arrives."* + +**Bias toward** active voice, concrete subjects, short paragraphs. *"We found the bug. Alex wrote the fix. PR is up for review."* beats *"The root cause has been identified and a fix has been authored and submitted for review."* + +## Channel shapes (Taskboard MCP Tools) + +Same content, different shell. Pick the shape that matches the target MCP tool. + +### 1. Task Result (`submit_task_result`) + +Full structured block used when closing a task. Easy to scan. +Building blocks (use as many as fit): +- **Status / TL;DR.** One clear line. *"Fixed and merged."* / *"Workaround applied, full fix pending next sprint."* +- **Impact.** Who's affected, how badly, what they see. +- **What broke / What was done.** Short paragraph. Plain-English mechanism, no code identifiers. +- **Owner / Links.** PR number or reference links. +- **Next steps / Risks.** Real risks only or what follows after closing this. + +### 2. Task Comment (`add_comment`) + +Used for continuous updates, flagging blockers, or daily progress. No walls of text. +- One **TL;DR** as the first line. +- 2–3 short bullets underneath: current state, blockers (if any), next step. +- Do not use heavy bolded section labels like the Task Result. Keep it brief and scannable. + +### 3. Sub-Task Description (`create_sub_task`) + +Used when breaking down a highly technical issue into actionable work for others. +- **Goal:** One sentence explaining what needs to be done. +- **Context:** Why we are doing this (plain English). +- **Technical Hints (Optional):** Keep this brief, pointing to where the engineer should look (this is the only place some technical jargon is acceptable if the assignee is an engineer, but keep it high-level). +- **Acceptance Criteria:** How to know it's done. + +## Source material + +The input is one of: +1. **A Task ID** → fetch via `call_mcp_tool` `get_task_detail` to read current status and comments. +2. **Pasted technical text** → use directly. +3. **The current conversation** → if you just produced engineering content, reuse what's in context. + +## Output flow + +1. **Confirm the target tool** if it's not stated (Result, Comment, or Sub-task). +2. **Produce the draft** formatted for the Taskboard as a chat message. +3. **Ask before executing:** Show the exact drafted text, wait for explicit *"post it"* / *"go ahead"*, then execute the corresponding MCP tool (`submit_task_result`, `add_comment`, etc.). +4. **Never post automatically** without the user seeing the drafted management-talk first. + +## Rules + +- **Never invent facts** to make the rewrite cleaner. +- **Never strip a Task ID, PR number, or product name** during de-jargoning. +- **Get sign-off before executing the MCP tool.** \ No newline at end of file diff --git a/.agents/skills/product-manager/SKILL.md b/.agents/skills/product-manager/SKILL.md new file mode 100644 index 0000000..dabed0b --- /dev/null +++ b/.agents/skills/product-manager/SKILL.md @@ -0,0 +1,142 @@ +--- +name: product-manager +description: Product Manager Agent วิเคราะห์ความสมบูรณ์ของ Product วางแผน Feature Roadmap ตรวจจับ Gap ที่ขาดหายไป และจัดลำดับความสำคัญด้วย Business Impact × Effort Matrix สำหรับโปรเจกต์ HomAroma E-commerce +--- + +# Skill: Product Manager Agent + +## Role +คุณคือ **Product Manager Agent** ของโปรเจกต์ HomAroma +ทำหน้าที่วิเคราะห์ความสมบูรณ์ของระบบ E-commerce เทียบกับ Industry Standard เพื่อค้นหา Feature Gap และวางแผน Roadmap ที่สร้าง Business Value สูงสุดในเวลาน้อยที่สุด + +## Context: โปรเจกต์ HomAroma +- **Product Type:** E-commerce สำหรับผลิตภัณฑ์เครื่องหอม / น้ำมันหอมระเหย +- **Primary Channel:** `apps/spa` (LINE LIFF Mobile App) — ลูกค้าซื้อผ่าน LINE OA +- **Secondary Channel:** `apps/website` (Next.js SSR) — ดึงลูกค้าใหม่ผ่าน Google +- **Backend:** ElysiaJS + Prisma + PostgreSQL +- **Business Model:** Direct-to-Consumer (DTC), รับ Payment ผ่านโอนเงิน + แนบสลิป + +--- + +## 🛠️ Capabilities + +### 1. Feature Gap Analysis +เปรียบเทียบกับ E-commerce Checklist มาตรฐาน แล้วระบุว่าสิ่งใด: +- ✅ มีแล้ว (Implemented) +- 🚧 มีบางส่วน (Partial) +- ❌ ขาดหายไป (Missing) + +### 2. Priority Matrix (Impact × Effort) +จัดลำดับ Feature ด้วยกรอบ: +``` +High Impact + Low Effort = 🔴 DO FIRST (Quick Win) +High Impact + High Effort = 🟡 PLAN CAREFULLY (Big Bets) +Low Impact + Low Effort = 🟢 FILL IN GAPS (Nice to Have) +Low Impact + High Effort = ⚫ AVOID (Time Sink) +``` + +### 3. Roadmap Planning +วางแผนเป็น Phase ที่ชัดเจน: +- **Phase 1 (MVP Fix):** แก้ไขสิ่งที่มีแต่พังหรืออ่านไม่ออก +- **Phase 2 (Core Features):** เพิ่ม Feature ที่ขาดแต่จำเป็น +- **Phase 3 (Growth):** Feature ที่เพิ่ม Conversion และ Retention + +### 4. Taskboard Integration +สร้าง Task ใน Taskboard MCP โดยอัตโนมัติ: +- Main Task = Feature หรือ Epic +- Sub Task = งานย่อยที่ต้องทำ +- ใส่ Priority, Effort, Business Impact ลงใน Description + +--- + +## 📋 E-commerce Feature Checklist (HomAroma Context) + +### 🛍️ Customer-Facing (apps/spa) + +#### Browse & Discovery +- [ ] หน้าแรก / Hero Banner +- [ ] หมวดหมู่สินค้า (Category Grid) +- [ ] หน้ารายการสินค้า (Shop/All Products) +- [ ] หน้าสินค้าในหมวดหมู่ (Category Page) +- [ ] หน้ารายละเอียดสินค้า (Product Detail) +- [ ] ค้นหาสินค้า (Search) +- [ ] กรองและเรียงสินค้า (Filter & Sort) +- [ ] สินค้าที่เกี่ยวข้อง (Related Products) +- [ ] สินค้าแนะนำล่าสุด (Recently Viewed) + +#### Cart & Checkout +- [ ] ตะกร้าสินค้า (Cart Drawer) +- [ ] แก้ไขจำนวน / ลบสินค้าในตะกร้า +- [ ] ใส่โค้ดคูปอง (Coupon Code) +- [ ] กรอกข้อมูลจัดส่ง (Checkout Form) +- [ ] สรุปออเดอร์ก่อนจ่ายเงิน (Order Summary) +- [ ] อัปโหลดสลิปโอนเงิน (Payment Slip Upload) +- [ ] บันทึกที่อยู่จัดส่ง (Address Book) +- [ ] เลือก Variant สินค้า (Size/Type Selector) + +#### Post-Purchase +- [ ] หน้ายืนยันออเดอร์ (Order Success Page) +- [ ] ติดตามสถานะออเดอร์ (Order Tracking) +- [ ] ประวัติการสั่งซื้อ (Order History) +- [ ] แก้ไข / ยกเลิกออเดอร์ (Order Edit/Cancel) +- [ ] แจ้งเตือนสถานะ (LINE Notification) + +#### Customer Account +- [ ] Login ด้วย LINE (LIFF Login) +- [ ] โปรไฟล์ลูกค้า (Customer Profile) +- [ ] Wishlist / รายการโปรด +- [ ] สะสมแต้ม / Loyalty Program +- [ ] รีวิวสินค้า (Product Review) + +#### Customer Service +- [ ] หน้า FAQ +- [ ] หน้าติดต่อเรา (Contact) +- [ ] แชทสด / LINE OA Integration +- [ ] ระบบแจ้งปัญหา (Support Ticket) + +#### Content & Trust +- [ ] หน้าเกี่ยวกับเรา (About) +- [ ] บทความ / Scent Guide (Blog) +- [ ] นโยบายความเป็นส่วนตัว (Privacy Policy) +- [ ] นโยบายการคืนสินค้า (Return Policy) +- [ ] คูปองและโปรโมชั่น (Promotions Page) + +### ⚙️ Admin-Facing (apps/admin) +- [ ] จัดการสินค้า (Product CRUD) +- [ ] จัดการออเดอร์ (Order Management) +- [ ] ค้นหาออเดอร์ (Order Search/Filter) +- [ ] แก้ไขสถานะออเดอร์ (Order Status Update) +- [ ] จัดการคูปอง (Coupon Management) +- [ ] จัดการลูกค้า (Customer Management) +- [ ] Dashboard ยอดขาย (Sales Dashboard) +- [ ] รายงานสินค้าขายดี (Product Analytics) +- [ ] จัดการบทความ (Article CMS) +- [ ] ตั้งค่าร้านค้า (Store Settings) +- [ ] จัดการ Hero Banner (Hero CMS) + +--- + +## 🚫 Constraints & Rules + +> [!IMPORTANT] +> **RULE #1: วิเคราะห์จากโค้ดจริงเสมอ ไม่ใช่แค่เดา** +> ก่อนตัดสินว่า Feature ไหน "ขาด" ต้องตรวจดูโค้ดใน `apps/spa/src/pages/` และ `apps/backend/src/routes/` ก่อนเสมอ + +> [!WARNING] +> **RULE #2: ระบุ Business Impact ทุกครั้ง** +> Feature แต่ละอันต้องบอกได้ว่าช่วยเรื่องใด: Revenue / Retention / Cost Reduction / Trust + +- **RULE #3: สร้าง Task ใน Taskboard เสมอหลังวิเคราะห์เสร็จ** + ผลการวิเคราะห์ต้องถูกแปลงเป็น Actionable Tasks ใน Taskboard MCP ไม่ใช่แค่รายงาน +- **RULE #4: จัดกลุ่ม Task ตาม Phase เสมอ** + Phase 1 (Fix), Phase 2 (Core), Phase 3 (Growth) — ห้ามเอาทุกอย่างมาปนกัน + +--- + +## 🔄 Workflow + +1. **Scan:** ตรวจสอบ `apps/spa/src/pages/`, `apps/backend/src/routes/`, `apps/admin/src/` +2. **Audit:** เปรียบเทียบกับ Feature Checklist ด้านบน +3. **Prioritize:** จัดลำดับด้วย Impact × Effort Matrix +4. **Report:** สร้าง Audit Report พร้อม Gap List +5. **Create Tasks:** สร้าง Main Tasks + Sub Tasks ใน Taskboard MCP diff --git a/.agents/skills/qa-product-auditor/SKILL.md b/.agents/skills/qa-product-auditor/SKILL.md new file mode 100644 index 0000000..7f9f1b0 --- /dev/null +++ b/.agents/skills/qa-product-auditor/SKILL.md @@ -0,0 +1,137 @@ +--- +name: qa-product-auditor +description: QA Product Auditor Agent ตรวจสอบว่า User Flow ทุกเส้นทางใน apps/spa ทำงานได้จริงหรือไม่ ค้นหา Broken Flows, Missing Error States, Empty States และ UX Issues ก่อน Production Release +--- + +# Skill: QA Product Auditor Agent + +## Role +คุณคือ **QA Product Auditor Agent** ของโปรเจกต์ HomAroma +ทำหน้าที่เดินผ่านทุก User Flow และตรวจสอบว่าแต่ละขั้นตอนทำงานได้ครบถ้วน มี Error Handling ที่ดี และ UX ไม่ติดขัด ก่อนที่จะ Release ให้ลูกค้าจริงใช้งาน + +## Context +- **Primary App:** `apps/spa` (LINE LIFF Mobile App) +- **Backend:** `apps/backend` (ElysiaJS API at PORT 3051) +- **Stack:** React + Vite + TailwindCSS + +--- + +## 🛠️ Audit Scope + +### Flow ที่ต้องตรวจทุกครั้ง: + +#### Flow 1: Browse & Discovery +``` +หน้าแรก → เลือกหมวดหมู่ → ดูสินค้า → คลิกสินค้า +``` +ตรวจสอบ: +- [ ] หน้าแรกโหลดข้อมูลจาก API ได้ +- [ ] Hero Banner แสดงภาพถูกต้อง +- [ ] Category Grid แสดงครบ +- [ ] Product Card แสดงชื่อ, ราคา, รูปภาพ ชัดเจน อ่านออก +- [ ] Loading State ขณะดึงข้อมูล +- [ ] Empty State เมื่อไม่มีสินค้า + +#### Flow 2: Product Detail +``` +คลิกสินค้า → ดูรายละเอียด → เลือก Variant → เพิ่มลงตะกร้า +``` +ตรวจสอบ: +- [ ] ข้อมูลสินค้าโหลดครบ (ชื่อ, ราคา, รายละเอียด, รูปภาพ) +- [ ] Variant Selector (เลือกขนาด/ประเภท) ทำงานได้ +- [ ] ราคาเปลี่ยนตาม Variant ที่เลือก +- [ ] ปุ่ม "เพิ่มลงตะกร้า" เพิ่มสินค้าจริง +- [ ] แสดง Feedback เมื่อเพิ่มสำเร็จ +- [ ] Related Products แสดงและ clickable + +#### Flow 3: Cart Management +``` +เปิดตะกร้า → แก้จำนวน → ลบสินค้า → ปิดตะกร้า +``` +ตรวจสอบ: +- [ ] Cart Drawer เปิด/ปิดได้ +- [ ] รายการสินค้าในตะกร้าแสดงถูกต้อง +- [ ] เพิ่ม/ลดจำนวนได้ +- [ ] ลบสินค้าออกได้ +- [ ] ราคารวมคำนวณถูกต้อง +- [ ] Empty Cart State + +#### Flow 4: Checkout (Critical Path) +``` +กดชำระเงิน → กรอกข้อมูล → ใส่คูปอง → ยืนยันออเดอร์ +``` +ตรวจสอบ: +- [ ] Form Validation ครบทุก Field +- [ ] คูปองโค้ด validate ได้ +- [ ] ส่วนลดคำนวณถูกต้อง +- [ ] ค่าส่งแสดงถูกต้อง +- [ ] ยอดรวมสุดท้ายถูกต้อง +- [ ] กด "สั่งซื้อ" สร้าง Order ใน Database ได้ +- [ ] Error Handling เมื่อ API ล้มเหลว + +#### Flow 5: Payment +``` +ยืนยันออเดอร์ → หน้า Success → อัปโหลดสลิป +``` +ตรวจสอบ: +- [ ] หน้า Order Success แสดง Order ID ถูกต้อง +- [ ] แสดงข้อมูลบัญชีธนาคาร (จาก Settings API) +- [ ] Upload สลิปได้ (Image Picker) +- [ ] สลิปถูก Upload ไปยัง Server +- [ ] Order ถูก Update สถานะหลัง Upload สำเร็จ + +#### Flow 6: Order Tracking +``` +หน้าติดตามพัสดุ → ค้นหา Order → ดูสถานะ +``` +ตรวจสอบ: +- [ ] ค้นหาด้วย Order ID / เบอร์โทร +- [ ] แสดงสถานะออเดอร์ที่อ่านเข้าใจง่าย +- [ ] Timeline สถานะ (Pending → Processing → Shipped → Delivered) + +#### Flow 7: Error & Edge Cases +ตรวจสอบ: +- [ ] API ไม่ตอบสนอง → แสดง Error Message ที่เข้าใจได้ +- [ ] Network Offline → แสดงข้อความ +- [ ] สินค้าหมด → แสดง "Out of Stock" และปิดปุ่มซื้อ +- [ ] รูปภาพโหลดไม่ได้ → แสดง Fallback Image +- [ ] ราคา null/undefined → แสดง "-" ไม่ใช่ crash + +--- + +## 📊 Severity Levels + +| Level | ความหมาย | ตัวอย่าง | +|---|---|---| +| 🔴 **Blocker** | ทำให้ใช้งานไม่ได้เลย | กด "สั่งซื้อ" แล้ว Error | +| 🟠 **Critical** | Flow หลักเสียหาย | ราคาคำนวณผิด | +| 🟡 **Major** | ประสบการณ์แย่มาก | Loading ไม่มี Indicator | +| 🟢 **Minor** | ปัญหาเล็กน้อย | ข้อความ Placeholder ยังเป็นภาษาอังกฤษ | + +--- + +## 🔄 Audit Workflow + +1. **Setup:** ตรวจสอบว่า `apps/backend` รันอยู่ที่ port 3051 +2. **Code Scan:** อ่านโค้ดใน `apps/spa/src/pages/` ทุกไฟล์ +3. **API Check:** ตรวจสอบว่า API Endpoint ที่ใช้มีอยู่ใน `apps/backend/src/routes/` +4. **Flow Trace:** Trace โค้ดของแต่ละ Flow ว่า Success/Error Path ครบไหม +5. **Report:** สร้าง Audit Report จัดกลุ่มตาม Severity +6. **Tasks:** สร้าง Bug/Fix Tasks ใน Taskboard MCP + +--- + +## 🚫 Constraints & Rules + +> [!IMPORTANT] +> **RULE #1: ตรวจจาก Source Code ไม่ใช่การ Assume** +> ต้องอ่านไฟล์จริงก่อนตัดสินว่า Feature ใด "พัง" หรือ "ขาด" + +> [!WARNING] +> **RULE #2: แยก Bug จาก Feature Gap ให้ชัด** +> - Bug = มีโค้ดแล้วแต่ทำงานผิด +> - Feature Gap = ยังไม่มีโค้ดเลย +> สองอย่างนี้ต้องรายงานแยกกัน + +- **RULE #3: ทุก Bug ต้องระบุ File + Line Number** + ห้ามรายงานแบบคลุมเครือ ต้องชี้ได้ว่าปัญหาอยู่ตรงไหน diff --git a/PROJECT_DETAILS.md b/PROJECT_DETAILS.md index 307becc..0898c08 100644 --- a/PROJECT_DETAILS.md +++ b/PROJECT_DETAILS.md @@ -10,7 +10,7 @@ * **เทคโนโลยีที่ใช้:** Bun, Elysia.js, Prisma 7, PostgreSQL * **การทำงาน:** ทำหน้าที่เป็น RESTful API Server ที่จัดการข้อมูลทั้งหมดของระบบ ตั้งแต่การตรวจสอบสิทธิ์ (Authentication), การจัดการสินค้า (Products), หมวดหมู่ (Categories), คำสั่งซื้อ (Orders) ไปจนถึงการตั้งค่าต่างๆ ของร้านค้า * **เป้าหมาย:** เพื่อให้เป็นจุดเชื่อมต่อข้อมูลเดียว (Single Source of Truth) ที่มีความรวดเร็วสูงด้วย Bun และมีความปลอดภัยในการเข้าถึงข้อมูล -* **การเชื่อมโยง:** เชื่อมต่อกับฐานข้อมูล PostgreSQL และให้บริการ API แก่ `admin`, `ssr`, และ `website` +* **การเชื่อมโยง:** เชื่อมต่อกับฐานข้อมูล PostgreSQL และให้บริการ API แก่ `admin`, `website`, และ `spa` ## 2. Admin Portal (`apps/admin`) **ระบบจัดการหลังบ้าน (Back-office)** @@ -20,7 +20,7 @@ * **เป้าหมาย:** เพื่อให้ผู้ดูแลระบบสามารถบริหารจัดการร้านค้าได้อย่างมีประสิทธิภาพผ่าน Interface ที่ใช้งานง่าย * **การเชื่อมโยง:** เชื่อมต่อกับ `backend` ผ่าน API เพื่อดึงและอัปเดตข้อมูลต่างๆ -## 3. Main Website (`apps/ssr`) +## 3. Main Website (`apps/website`) **เว็บไซต์หลัก (Public Interface)** * **เทคโนโลยีที่ใช้:** Next.js (React), Tailwind CSS @@ -28,7 +28,7 @@ * **เป้าหมาย:** เพื่อสร้างการรับรู้แบรนด์ (Brand Awareness) และช่วยให้ลูกค้าค้นหาพบผ่าน Search Engine ได้ง่าย * **การเชื่อมโยง:** เชื่อมต่อกับ `backend` เพื่อดึงข้อมูลสินค้าและเนื้อหามาแสดงผลบนหน้าเว็บ -## 4. LIFF Mobile App (`apps/website`) +## 4. LIFF Mobile App (`apps/spa`) **หน้ากากมือถือสำหรับลูกค้า (Customer Interface)** * **เทคโนโลยีที่ใช้:** React, Vite, LINE Front-end Framework (LIFF), Tailwind CSS @@ -91,16 +91,16 @@ git push server master * Sync โค้ดไปยัง `/home/ti/projects/homaroma-api` * รัน `prisma generate` และ `prisma migrate deploy` เพื่ออัปเดตฐานข้อมูล * Restart บริการด้วย **PM2** (ชื่อ process: `homaroma-backend`) -4. **SSR Deployment**: - * Build Next.js app และ Sync ไปยัง `/home/ti/projects/homaroma-ssr` - * Restart บริการด้วย **PM2** (ชื่อ process: `homaroma-ssr`) บนพอร์ต `3001` +4. **Website (SSR) Deployment**: + * Build Next.js app และ Sync ไปยัง `/home/ti/projects/homaroma-website` + * Restart บริการด้วย **PM2** (ชื่อ process: `homaroma-website`) บนพอร์ต `3002` 5. **Cleanup**: ลบไฟล์ชั่วคราวที่ใช้ในการ Build ออก ### สรุป URL การเข้าใช้งาน (Production) -* **Website (SPA):** [https://homaroma.shop](https://homaroma.shop) +* **Website (SSR Next.js):** [https://homaroma.shop](https://homaroma.shop) +* **SPA (LIFF Mobile App):** [https://spa.homaroma.shop](https://spa.homaroma.shop) * **Admin Panel:** [https://admin.homaroma.shop](https://admin.homaroma.shop) * **API Backend:** [https://api.homaroma.shop](https://api.homaroma.shop) -* **SSR Next.js:** [https://ssr.homaroma.shop](https://ssr.homaroma.shop) --- diff --git a/apps/spa/src/components/CartDrawer.tsx b/apps/spa/src/components/CartDrawer.tsx index 67b3754..93152da 100644 --- a/apps/spa/src/components/CartDrawer.tsx +++ b/apps/spa/src/components/CartDrawer.tsx @@ -16,81 +16,81 @@ export default function CartDrawer() {
{/* Backdrop */}
toggleCart(false)} /> {/* Drawer */} -
+
{/* Header */} -
-

- +
+

+ {t('cart.title')} - + {t('cart.items', { count: items.length })}

{/* Cart Items */}
{items.length === 0 ? ( -
+

{t('cart.empty')}

) : ( items.map((item) => ( -
-
+
+
{item.productName}
-

{item.productName}

+

{item.productName}

{item.variantSize && ( -

{item.variantSize}

+

{item.variantSize}

)} {/* Price row */}
- {formatPrice(item.price)} - × - {item.quantity} - = - {formatPrice(item.price * item.quantity)} + {formatPrice(item.price)} + × + {item.quantity} + = + {formatPrice(item.price * item.quantity)}
{/* Quantity controls */}
-
+
- {item.quantity} + {item.quantity} @@ -99,7 +99,7 @@ export default function CartDrawer() { - {isFreeShipping ? 'ส่งฟรี' : `- ${formatPrice(discountAmount)}`} + {isFreeShipping ? 'ส่งฟรี' : `- ${formatPrice(discountAmount)}`}
)} {savedCoupon && !couponValid && ( -
+
คูปอง {savedCoupon} (ยังไม่ถึงยอดขั้นต่ำ) -
)} -
+
รวมสุทธิ - {formatPrice(Math.max(0, subtotal - discountAmount))} + {formatPrice(Math.max(0, subtotal - discountAmount))}
@@ -202,7 +202,7 @@ function CartDrawerFooter({ subtotal, onClose }: { subtotal: number; onClose: () ดำเนินการชำระเงิน diff --git a/apps/spa/src/components/EmptyState.tsx b/apps/spa/src/components/EmptyState.tsx new file mode 100644 index 0000000..203f8e1 --- /dev/null +++ b/apps/spa/src/components/EmptyState.tsx @@ -0,0 +1,40 @@ +import { PackageOpen } from 'lucide-react' +import { Link } from 'react-router-dom' + +interface EmptyStateProps { + title?: string + message?: string + actionText?: string + actionLink?: string + actionClick?: () => void +} + +export default function EmptyState({ + title = 'Nothing found', + message = 'We could not find any items here.', + actionText = 'Browse Shop', + actionLink, + actionClick +}: EmptyStateProps) { + const ButtonContent = () => ( + + {actionText} + + ) + + return ( +
+
+ +
+

{title}

+

{message}

+ + {actionClick ? ( +
+ ) : actionLink ? ( + + ) : null} +
+ ) +} diff --git a/apps/spa/src/components/ErrorState.tsx b/apps/spa/src/components/ErrorState.tsx new file mode 100644 index 0000000..b91fdb8 --- /dev/null +++ b/apps/spa/src/components/ErrorState.tsx @@ -0,0 +1,41 @@ +import { AlertCircle } from 'lucide-react' +import { Link } from 'react-router-dom' + +interface ErrorStateProps { + message?: string + fullScreen?: boolean + showHomeButton?: boolean +} + +export default function ErrorState({ + message = 'Something went wrong', + fullScreen = false, + showHomeButton = true +}: ErrorStateProps) { + const content = ( +
+
+ +
+

{message}

+ {showHomeButton && ( + + Return Home + + )} +
+ ) + + if (fullScreen) { + return ( +
+ {content} +
+ ) + } + + return content +} diff --git a/apps/spa/src/components/LoadingState.tsx b/apps/spa/src/components/LoadingState.tsx new file mode 100644 index 0000000..a922c80 --- /dev/null +++ b/apps/spa/src/components/LoadingState.tsx @@ -0,0 +1,25 @@ +import { Loader2 } from 'lucide-react' + +interface LoadingStateProps { + message?: string + fullScreen?: boolean +} + +export default function LoadingState({ message = 'Loading...', fullScreen = false }: LoadingStateProps) { + const content = ( +
+ +

{message}

+
+ ) + + if (fullScreen) { + return ( +
+ {content} +
+ ) + } + + return content +} diff --git a/apps/spa/src/components/Navbar.tsx b/apps/spa/src/components/Navbar.tsx index f8d3fd0..91eab4b 100644 --- a/apps/spa/src/components/Navbar.tsx +++ b/apps/spa/src/components/Navbar.tsx @@ -1,7 +1,7 @@ import { Link, NavLink, useNavigate } from 'react-router-dom' import { useState, useEffect } from 'react' import type { Category } from '../types' -import { ShoppingBag, MapPin } from 'lucide-react' +import { ShoppingBag, MapPin, Menu as MenuIcon, X } from 'lucide-react' import { useCartStore } from '../store/cartStore' import { useAuthStore } from '../store/authStore' import { logoutLiff, loginLiff } from '../lib/liff' @@ -63,19 +63,19 @@ export default function Navbar({ categories = [] }: NavbarProps) { } const navLinkClass = ({ isActive }: { isActive: boolean }) => - isActive ? 'text-cyan-glow' : 'text-slate-300 hover:text-white transition-colors' + isActive ? 'text-spa-forest font-semibold' : 'text-spa-stone hover:text-spa-forest transition-colors' return ( <> {/* ── NAVBAR BAR ── */} @@ -275,24 +275,24 @@ export default function Navbar({ categories = [] }: NavbarProps) { > {/* Backdrop */}
setMobileOpen(false)} /> {/* Drawer */}
{/* Drawer Header */} -
- {t('nav.menu')} +
+ {t('nav.menu')}
{customer && ( - + )} - -
-
- +
+ setSearchQuery(e.target.value)} - placeholder="Search scents, categories..." - className="bg-transparent border-none outline-none text-lg md:text-xl p-4 pl-0 w-full text-white placeholder-slate-600" + placeholder="ค้นหากลิ่น, หมวดหมู่..." + className="bg-transparent border-none outline-none text-lg md:text-xl p-4 pl-0 w-full text-spa-forest placeholder-spa-stone/40" autoFocus /> -
-

Quick Links

+

Quick Links

{[ ...categories.map(c => ({ label: c.name, to: `/category/${c.id}` })), @@ -430,7 +430,7 @@ export default function Navbar({ categories = [] }: NavbarProps) { key={item.to} to={item.to} onClick={() => setSearchOpen(false)} - className="glass px-4 py-1.5 rounded-full text-xs font-medium hover:bg-white/10 hover:text-cyan-glow transition-all border border-white/5" + className="px-4 py-1.5 rounded-full text-xs font-medium text-spa-stone hover:bg-spa-forest hover:text-white transition-all border border-spa-stone/20 bg-white" > {item.label} diff --git a/apps/spa/src/components/ProductCard.tsx b/apps/spa/src/components/ProductCard.tsx index c1e5138..d1f0928 100644 --- a/apps/spa/src/components/ProductCard.tsx +++ b/apps/spa/src/components/ProductCard.tsx @@ -8,56 +8,45 @@ interface ProductCardProps { export default function ProductCard({ product }: ProductCardProps) { return ( -
-
+ +
{product.scent.name} {/* Category badge */} -
+
{product.category.name}
{/* Available badge */} -
+
Available
-
-
-
-

- {product.scent.name} -

-

- {product.category.name} -

-
- +
+
+

+ {product.scent.name} +

+

+ {product.category.name} +

+
+ +
+ {formatPrice(product.variants[0]?.price)} + + View Details → +
- - {/* Variants (sizes) */} -
- {product.variants.map((v) => ( -
- {v.size} -
- ))} -
- -
- - - Discover -
-
+ ) } diff --git a/apps/spa/src/index.css b/apps/spa/src/index.css index 8f629b2..6ab6e27 100644 --- a/apps/spa/src/index.css +++ b/apps/spa/src/index.css @@ -11,6 +11,13 @@ --color-pop-bg: #FFF0F5; --color-pop-black: #333333; + /* Spa Theme Colors */ + --color-spa-light: #FDFDF9; + --color-spa-sage: #EAEFEA; + --color-spa-stone: #4B4B46; + --color-spa-forest: #1F3D2D; + --color-spa-gold: #D4AF37; + --shadow-brutal: 0 10px 25px rgba(0,0,0,0.05); --shadow-brutal-hover: 0 20px 40px rgba(0,0,0,0.1); diff --git a/apps/spa/src/pages/CategoryPage.tsx b/apps/spa/src/pages/CategoryPage.tsx index fed6865..b086cf8 100644 --- a/apps/spa/src/pages/CategoryPage.tsx +++ b/apps/spa/src/pages/CategoryPage.tsx @@ -85,26 +85,27 @@ export default function CategoryPage() { alt={category?.name} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} /> -
+
+
{/* Breadcrumb */} -
+
Home / Shop / - {category?.name ?? '...'} + {category?.name ?? '...'}
-

+

{loading ? ( -
+
) : ( category?.name )}

{!loading && category?._count && ( -

+

{category._count.products} products available

)} @@ -120,10 +121,10 @@ export default function CategoryPage() { {cat.name} @@ -134,19 +135,19 @@ export default function CategoryPage() { {error ? (

⚠️

-

{error}

- +

{error}

+ Back to Shop
) : loading ? (
{Array.from({ length: 9 }).map((_, i) => ( -
+
))}
) : products.length === 0 ? ( -
+

🌿

No products in this category yet.

@@ -161,7 +162,7 @@ export default function CategoryPage() { {/* Invisible target for IntersectionObserver */} {displayCount < products.length && (
-
+
)}
diff --git a/apps/spa/src/pages/CheckoutPage.tsx b/apps/spa/src/pages/CheckoutPage.tsx index 7162105..fe306e1 100644 --- a/apps/spa/src/pages/CheckoutPage.tsx +++ b/apps/spa/src/pages/CheckoutPage.tsx @@ -219,61 +219,67 @@ export default function CheckoutPage() { } return ( -
+
-
+

Checkout

{/* Form Section */}
-
-

Shipping Information

+
+

Shipping Information

- + setFormData({ ...formData, name: e.target.value })} - className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all" + className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="Jane Doe" />
- + setFormData({ ...formData, phone: e.target.value })} - className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all" + className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="0812345678" />
- + setFormData({ ...formData, email: e.target.value })} - className="w-full bg-night-950 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all" + className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-pop-black focus:border-spa-forest focus:ring-1 focus:ring-spa-forest outline-none transition-all placeholder:text-spa-stone/50" placeholder="jane@example.com" />
- +