diff --git a/components/ProfileSettings.tsx b/components/ProfileSettings.tsx
index 1d5f734..0cac415 100644
--- a/components/ProfileSettings.tsx
+++ b/components/ProfileSettings.tsx
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
-import { Loader2, Check } from 'lucide-react';
+import { Loader2, Check, AlertTriangle } from 'lucide-react';
import { Button } from './ui/button';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Input } from './ui/input';
import { Label } from './ui/label';
-import { useSession } from '../lib/auth-client';
+import { useSession, signOut } from '../lib/auth-client';
import { validatePhoneOrEmail } from '../lib/validators';
export function ProfileSettings() {
@@ -17,6 +17,9 @@ export function ProfileSettings() {
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const [saved, setSaved] = useState(false);
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const [deleting, setDeleting] = useState(false);
+ const [deleteError, setDeleteError] = useState('');
useEffect(() => {
fetch('/api/profile', { credentials: 'include' })
@@ -172,6 +175,78 @@ export function ProfileSettings() {
+
+
+
+ Delete account
+
+
+
+ {!showDeleteConfirm ? (
+ <>
+
+ Permanently delete your account and all associated data including
+ chemicals and protocols. This action cannot be undone.
+
+
+ >
+ ) : (
+ <>
+
+ Are you sure? All your data will be permanently deleted.
+
+ {deleteError && (
+ {deleteError}
+ )}
+
+
+
+
+ >
+ )}
+
+
+
Privacy Policy
diff --git a/server/src/index.ts b/server/src/index.ts
index 47853ee..f87fa50 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -7,6 +7,7 @@ import { authRateLimiter, apiRateLimiter } from './auth/rateLimiter.js';
import chemicalsRouter from './routes/chemicals.js';
import protocolsRouter from './routes/protocols.js';
import profileRouter from './routes/profile.js';
+import accountRouter from './routes/account.js';
import path from 'path';
import { fileURLToPath } from 'url';
@@ -93,6 +94,7 @@ app.use('/api', apiRateLimiter);
app.use('/api/chemicals', chemicalsRouter);
app.use('/api/protocols', protocolsRouter);
app.use('/api/profile', profileRouter);
+app.use('/api/account', accountRouter);
app.get('/api/health', (_req, res) => res.json({ ok: true }));
diff --git a/server/src/routes/account.ts b/server/src/routes/account.ts
new file mode 100644
index 0000000..d4d2793
--- /dev/null
+++ b/server/src/routes/account.ts
@@ -0,0 +1,57 @@
+import { Router } from 'express';
+import { pool } from '../db/pool.js';
+import { requireAuth } from '../auth/middleware.js';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const uploadsDir = process.env.UPLOADS_DIR || path.join(__dirname, '../../uploads');
+
+const router = Router();
+router.use(requireAuth);
+
+// POST /api/account/delete
+router.post('/delete', async (req, res) => {
+ const userId = req.user!.id;
+ const client = await pool.connect();
+
+ try {
+ await client.query('BEGIN');
+
+ // 1. Delete all chemicals for this user
+ await client.query('DELETE FROM chemicals WHERE user_id = $1', [userId]);
+
+ // 2. Delete all protocols for this user
+ await client.query('DELETE FROM protocols WHERE user_id = $1', [userId]);
+
+ // 3. Delete user profile (has CASCADE, but be explicit)
+ await client.query('DELETE FROM user_profile WHERE user_id = $1', [userId]);
+
+ // 4. Delete sessions and accounts (CASCADE from user, but be explicit)
+ await client.query('DELETE FROM session WHERE "userId" = $1', [userId]);
+ await client.query('DELETE FROM account WHERE "userId" = $1', [userId]);
+ await client.query('DELETE FROM verification WHERE identifier = $1', [userId]);
+
+ // 5. Delete the user record itself
+ await client.query('DELETE FROM "user" WHERE id = $1', [userId]);
+
+ await client.query('COMMIT');
+
+ // 6. Clean up uploaded files outside the transaction
+ const userUploadsDir = path.join(uploadsDir, userId);
+ if (fs.existsSync(userUploadsDir)) {
+ fs.rmSync(userUploadsDir, { recursive: true, force: true });
+ }
+
+ res.json({ deleted: true });
+ } catch (err) {
+ await client.query('ROLLBACK');
+ console.error('Account deletion failed:', err);
+ res.status(500).json({ error: 'Account deletion failed' });
+ } finally {
+ client.release();
+ }
+});
+
+export default router;