From c65051cf7db6db504f668a874ddae2b8ba6c50e4 Mon Sep 17 00:00:00 2001 From: Mannu Date: Sat, 16 May 2026 15:12:23 +0530 Subject: [PATCH] Add password management to admin users page - Add PATCH endpoint to set user passwords - Add password modal UI in admin panel - Update CLAUDE.md with latest features Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 27 +++++++------ next-env.d.ts | 2 +- src/app/admin/users/page.tsx | 67 +++++++++++++++++++++++++++++++- src/app/api/admin/users/route.ts | 41 +++++++++++++++++++ 4 files changed, 120 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dc91533..c3cf0c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,9 +42,9 @@ src/ │ ├── menu/ # Navigation menu │ ├── onboarding/ # First-time setup │ ├── settings/ # Settings with theme picker -│ ├── login/ # User login (magic) +│ ├── login/ # User login (email + password) │ ├── admin/ # Admin panel -│ └── admin-login/ # Admin login (separate) +│ └── admin-login/ # Admin login (separate) ├── ThemeProvider.tsx # Theme context (light/dark/system/time) ├── FamilyProvider.tsx # Family/child context (resolves from session) drizzle/ # Database migrations @@ -67,6 +67,7 @@ docs/ # Design docs - **Vaccinations:** IAP schedule tracking - **Growth:** Weight/height over time - **Memories:** Photos with R2 storage +- **Chat Sessions:** User conversations with AI (chat_sessions, chat_messages) ### Key Patterns @@ -94,7 +95,7 @@ const { familyId, familyName, child, children, tier, memberCount } = useFamily() **Offline Queue:** Uses localStorage (`tia_offline_queue`) for failed API calls, retries when online. -**Chat Sessions:** Stored in localStorage (`tia_chat_sessions`) - shared between home page AI card and /ai page. +**Chat Sessions:** Stored in localStorage (`tia_chat_sessions`) - shared between home page AI card and /ai page. Database tables: `chat_sessions`, `chat_messages`. **API Routes:** Return standard JSON `{ success: true, items: [...] }` format for lists. @@ -104,14 +105,12 @@ const { familyId, familyName, child, children, tier, memberCount } = useFamily() - Model: `minimax-2.7` - See `/docs/debugging.md` for troubleshooting -## Authentication (Database Sessions) +## Authentication (Email + Password) -### Session Flow - -1. User logs in at `/login` with email -2. API `/api/auth/signin` creates session in `sessions` table +1. User visits `/login` with email + password (or signs up for new account) +2. API `/api/auth/signin` verifies password hash and creates session in `sessions` table 3. Session token stored in **httpOnly cookie** (NOT localStorage!) -4. On each request, session resolved from database via cookie +4. Password stored with simple hash in `users.password_hash` ### Tables Used @@ -129,7 +128,7 @@ const { familyId, familyName, child, children, tier, memberCount } = useFamily() ### localStorage Acceptable For: - Theme preference (user-specific display only) - Temporary cache (offline queue for retry) -- Chat sessions (upcoming feature: move to database) +- Chat sessions local cache (synced from database) ## Admin Panel @@ -137,9 +136,9 @@ Access at: `/admin-login` (username: `admin`, password: `admin123`) ### Pages -- `/admin` - Dashboard with stats -- `/admin/families` - Manage families -- `/admin/users` - Manage users +- `/admin` - Dashboard with clickable stat cards +- `/admin/families` - Manage families (create, view/add/remove members, set tier) +- `/admin/users` - Manage users (add to family, password status, delete) - `/admin/children` - Manage children - `/admin/revenue` - Revenue analytics - `/admin/analytics` - Feature usage @@ -160,7 +159,7 @@ Access at: `/admin-login` (username: `admin`, password: `admin123`) | Memories/Photos | Database + R2 | `/api/upload` | ✅ Yes | ✅ Yes | | Auth Session | Database + Cookie | `/api/auth/signin` | ✅ Yes | ✅ No | | Theme | localStorage | `tia_theme` | ✅ Yes | ✅ Yes | -| Chat Sessions | localStorage | `tia_chat_sessions` | ✅ Yes | ❌ No | +| Chat Sessions | Database | `/api/chat` | ✅ Yes | ✅ Yes | | Offline Queue | localStorage | `tia_offline_queue` | ✅ Yes | ❌ No | ## R2 Storage (Cloudflare) diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index b5087e2..bfa0b78 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -26,6 +26,7 @@ export default function AdminUsers() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [showAdd, setShowAdd] = useState(false); + const [showPassword, setShowPassword] = useState(null); const [newUser, setNewUser] = useState({ email: "", name: "", familyId: "", role: "caregiver" }); useEffect(() => { @@ -98,6 +99,26 @@ export default function AdminUsers() { } }; + const handleSetPassword = async (userId: string, password: string) => { + if (!password) return; + try { + const res = await fetch("/api/admin/users", { + method: "PATCH", + headers: { + Authorization: `Bearer ${localStorage.getItem("admin_token")}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ userId, password }), + }); + if (res.ok) { + fetchUsers(); + setShowPassword(null); + } + } catch (err) { + console.error("Failed to set password:", err); + } + }; + const filteredUsers = users.filter((u) => u.email.toLowerCase().includes(search.toLowerCase()) || (u.name || "").toLowerCase().includes(search.toLowerCase()) @@ -190,9 +211,19 @@ export default function AdminUsers() { {user.familyName || "-"} {user.hasPassword ? ( - ✓ Set + ) : ( - Not set + )} @@ -214,6 +245,38 @@ export default function AdminUsers() {
No users found
)} + + {/* Password Modal */} + {showPassword && ( +
+
+

Set Password

+ +
+ + +
+
+
+ )} ); } \ No newline at end of file diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts index e88f3d0..80b3307 100644 --- a/src/app/api/admin/users/route.ts +++ b/src/app/api/admin/users/route.ts @@ -87,6 +87,47 @@ export async function POST(request: Request) { } } +// Update user password or other fields +export async function PATCH(request: Request) { + try { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await request.json(); + const { userId, password } = body; + + if (!userId) { + return NextResponse.json({ error: "userId required" }, { status: 400 }); + } + + // Simple hash function + function hashPassword(pwd: string): string { + let hash = 0; + for (let i = 0; i < pwd.length; i++) { + const char = pwd.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return "hash_" + hash.toString(16); + } + + if (password) { + const passwordHash = hashPassword(password); + await sql` + UPDATE users SET password_hash = ${passwordHash}, password_updated_at = NOW(), updated_at = NOW() + WHERE id = ${userId} + `; + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Admin password update error:", error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + // Remove user from family export async function DELETE(request: Request) { try {