feat: onboarding pre-work

This commit is contained in:
Eric Gullickson
2025-12-22 21:34:05 -06:00
parent 4897f0a52c
commit 55cf4923b8
12 changed files with 537 additions and 71 deletions

View File

@@ -1,4 +1,4 @@
.PHONY: help setup start stop clean logs shell-backend shell-frontend migrate rebuild traefik-dashboard traefik-logs service-discovery network-inspect health-check-all mobile-setup db-shell-app install type-check lint build-local .PHONY: help setup start stop clean logs shell-backend shell-frontend migrate create-admin rebuild traefik-dashboard traefik-logs service-discovery network-inspect health-check-all mobile-setup db-shell-app install type-check lint build-local
help: help:
@echo "MotoVaultPro - Simplified 5-Container Architecture" @echo "MotoVaultPro - Simplified 5-Container Architecture"
@@ -14,6 +14,7 @@ help:
@echo " make shell-backend - Open shell in backend container" @echo " make shell-backend - Open shell in backend container"
@echo " make shell-frontend - Open shell in frontend container" @echo " make shell-frontend - Open shell in frontend container"
@echo " make migrate - Run database migrations" @echo " make migrate - Run database migrations"
@echo " make create-admin - Create initial admin user (fresh deployments only)"
@echo "" @echo ""
@echo "K8s-Ready Architecture Commands:" @echo "K8s-Ready Architecture Commands:"
@echo " make traefik-dashboard - Access Traefik service discovery dashboard" @echo " make traefik-dashboard - Access Traefik service discovery dashboard"
@@ -94,6 +95,13 @@ migrate:
@docker compose exec mvp-backend node dist/_system/migrations/run-all.js @docker compose exec mvp-backend node dist/_system/migrations/run-all.js
@echo "Migrations completed." @echo "Migrations completed."
create-admin:
@echo ""
@echo "Creating initial admin user..."
@echo "This command is only for fresh deployments with no existing admins."
@echo ""
@docker compose exec -it mvp-backend node dist/_system/cli/create-admin.js
rebuild: rebuild:
@echo "Rebuilding containers with latest code changes..." @echo "Rebuilding containers with latest code changes..."
@docker compose up -d --build --remove-orphans @docker compose up -d --build --remove-orphans

193
ONBOARDING-FIX.md Normal file
View File

@@ -0,0 +1,193 @@
# Fix New User Signup Wizard Flow
## Problem Summary
The new user signup wizard is broken:
1. After Auth0 callback, users go to `/garage` instead of `/verify-email`
2. Users can access `/garage/*` without verified email
3. Onboarding flow is bypassed entirely
4. **New Requirement**: Block login completely at Auth0 for unverified users
## Root Causes
1. **Auth0Provider.tsx:29** - `onRedirectCallback` defaults to `/garage` without checking verification
2. **App.tsx:472-481** - Callback route just shows "Processing login..." with no routing logic
3. **App.tsx:549+** - Protected routes have no email verification check
4. **Auth0** - No rule/action blocking unverified users from logging in
---
## Implementation Plan
### Phase 1: Auth0 Configuration (Manual Step)
**Auth0 Dashboard -> Actions -> Flows -> Login**
Create a Post Login Action to block unverified users:
```javascript
exports.onExecutePostLogin = async (event, api) => {
if (!event.user.email_verified) {
api.access.deny('Please verify your email address before logging in. Check your inbox for a verification link.');
}
};
```
This ensures:
- Unverified users cannot get a JWT
- They see a clear error on the Auth0 login screen
- They must click the verification link before logging in
### Phase 2: Update Signup Flow
**After signup, redirect to a "Check Your Email" page (not /verify-email)**
The new flow:
1. User submits signup form
2. Backend creates Auth0 user (unverified)
3. Auth0 automatically sends verification email
4. Frontend shows "Check Your Email" page with:
- Message: "We've sent a verification link to your email"
- Resend button (calls public resend endpoint)
- "Back to Login" button
5. User clicks email link -> Auth0 marks as verified
6. User can now login -> goes to /onboarding
### Phase 3: Backend Changes
**File: `backend/src/features/auth/api/auth.routes.ts`**
- Add `POST /api/auth/resend-verification-public` (no JWT required)
- Takes email address, looks up user, resends verification
**File: `backend/src/features/auth/api/auth.controller.ts`**
- Add `resendVerificationPublic` handler
**File: `backend/src/features/auth/domain/auth.service.ts`**
- Add `resendVerificationByEmail` method
**File: `backend/src/features/auth/api/auth.routes.ts`**
- Add `GET /api/auth/user-status` (JWT required)
- Returns: `{ emailVerified, onboardingCompleted, email }`
**File: `backend/src/core/plugins/auth.plugin.ts`**
- Add `/api/auth/user-status` to `VERIFICATION_EXEMPT_ROUTES`
### Phase 4: Create Callback Handler
**File: `frontend/src/features/auth/pages/CallbackPage.tsx`** (NEW)
- Fetches user status after Auth0 callback
- Routes based on status:
- Not onboarded -> `/onboarding`
- Onboarded -> `/garage` (or returnTo)
- Note: Unverified users never reach this (blocked by Auth0)
**File: `frontend/src/features/auth/mobile/CallbackMobileScreen.tsx`** (NEW)
- Mobile version
### Phase 5: Update Auth0Provider
**File: `frontend/src/core/auth/Auth0Provider.tsx`**
Update `onRedirectCallback` (line 27-31):
```typescript
const onRedirectCallback = (appState?: { returnTo?: string }) => {
navigate('/callback', {
replace: true,
state: { returnTo: appState?.returnTo || '/garage' }
});
};
```
### Phase 6: Rename/Update Verify Email Page
**File: `frontend/src/features/auth/pages/VerifyEmailPage.tsx`**
- Rename concept to "Check Your Email" page
- Remove polling (user can't be authenticated)
- Show static message + resend button (calls public endpoint)
- Add "Back to Login" button
**File: `frontend/src/features/auth/mobile/VerifyEmailMobileScreen.tsx`**
- Same changes for mobile
### Phase 7: Update App.tsx Routing
**File: `frontend/src/App.tsx`**
1. Replace callback handling (lines 472-481) with CallbackPage
2. Add onboarding guard after authentication check
3. Remove email verification check from frontend (Auth0 handles it)
```typescript
// After isAuthenticated check:
// Fetch onboarding status
// If not onboarded -> /onboarding
// Otherwise -> proceed to /garage
```
### Phase 8: Create Supporting Files
**File: `frontend/src/core/auth/useUserStatus.ts`** (NEW)
- Hook for fetching user status
**File: `frontend/src/features/auth/api/auth.api.ts`**
- Add `getUserStatus()`
- Add `resendVerificationPublic(email)` (no auth)
---
## Files to Modify
### Auth0 (Manual Configuration)
- Create Post Login Action to block unverified users
### Backend
- `backend/src/features/auth/api/auth.routes.ts` - Add endpoints
- `backend/src/features/auth/api/auth.controller.ts` - Add handlers
- `backend/src/features/auth/domain/auth.service.ts` - Add methods
- `backend/src/core/plugins/auth.plugin.ts` - Update exempt routes
### Frontend
- `frontend/src/core/auth/Auth0Provider.tsx` - Fix onRedirectCallback
- `frontend/src/App.tsx` - Add route guards and callback handler
- `frontend/src/features/auth/pages/CallbackPage.tsx` - NEW
- `frontend/src/features/auth/mobile/CallbackMobileScreen.tsx` - NEW
- `frontend/src/features/auth/pages/VerifyEmailPage.tsx` - Update to static page
- `frontend/src/features/auth/mobile/VerifyEmailMobileScreen.tsx` - Update
- `frontend/src/core/auth/useUserStatus.ts` - NEW
- `frontend/src/features/auth/api/auth.api.ts` - Add functions
---
## New User Flow (After Fix)
```
1. Signup form submission
2. -> POST /api/auth/signup (creates unverified Auth0 user)
3. -> Navigate to /verify-email (static "Check Your Email" page)
4. User clicks verification link in email
5. -> Auth0 marks user as verified
6. User clicks "Login" on /verify-email page
7. -> Auth0 login succeeds (user is now verified)
8. -> /callback page fetches status
9. -> Not onboarded? -> /onboarding
10. -> Complete onboarding -> /garage
```
## Returning User Flow
```
1. Login attempt (unverified) -> Auth0 blocks with error message
2. Login attempt (verified, not onboarded) -> /callback -> /onboarding
3. Login attempt (verified, onboarded) -> /callback -> /garage
```
---
## Testing Checklist
- [ ] Auth0 Action blocks unverified login with clear error
- [ ] Signup -> check-email page -> verify via email -> login works
- [ ] Resend verification from check-email page works
- [ ] Verified user (no onboarding) -> onboarding wizard
- [ ] Verified + onboarded user -> direct to garage
- [ ] Direct URL access to /garage -> requires login
- [ ] All flows work on mobile
- [ ] All flows work on desktop

View File

@@ -0,0 +1,315 @@
/**
* @ai-summary CLI script for creating initial admin user on fresh deployments
* @ai-context Runs inside backend container with access to Auth0 and PostgreSQL
*
* Usage: node dist/_system/cli/create-admin.js
*
* Interactive prompts for email and password, then:
* 1. Checks if any admin exists (errors if yes)
* 2. Creates user in Auth0 via Management API
* 3. Creates user_profile record
* 4. Creates admin_users record with audit log
*/
import * as readline from 'readline';
import { Pool } from 'pg';
import { appConfig } from '../../core/config/config-loader';
import { auth0ManagementClient } from '../../core/auth/auth0-management.client';
import { UserProfileRepository } from '../../features/user-profile/data/user-profile.repository';
import { AdminRepository } from '../../features/admin/data/admin.repository';
import { AdminService } from '../../features/admin/domain/admin.service';
// Terminal color codes
const colors = {
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
reset: '\x1b[0m',
bold: '\x1b[1m',
};
function printInfo(msg: string): void {
console.log(`${colors.green}[INFO]${colors.reset} ${msg}`);
}
function printError(msg: string): void {
console.log(`${colors.red}[ERROR]${colors.reset} ${msg}`);
}
function printWarn(msg: string): void {
console.log(`${colors.yellow}[WARN]${colors.reset} ${msg}`);
}
/**
* Prompt for text input
*/
function prompt(rl: readline.Interface, question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer.trim());
});
});
}
/**
* Prompt for password input (masked - characters not echoed)
*/
function promptPassword(question: string): Promise<string> {
return new Promise((resolve) => {
process.stdout.write(question);
const stdin = process.stdin;
const wasRaw = stdin.isRaw;
stdin.setRawMode?.(true);
stdin.resume();
let password = '';
const onData = (char: Buffer): void => {
const c = char.toString();
if (c === '\n' || c === '\r') {
stdin.removeListener('data', onData);
stdin.setRawMode?.(wasRaw);
stdin.pause();
console.log(); // newline after password
resolve(password);
} else if (c === '\u0003') {
// Ctrl+C - exit gracefully
console.log('\n\nAborted by user.');
process.exit(1);
} else if (c === '\u007f' || c === '\b') {
// Backspace
password = password.slice(0, -1);
// Optionally show backspace effect
if (password.length >= 0) {
process.stdout.write('\b \b');
}
} else if (c.charCodeAt(0) >= 32) {
// Printable character
password += c;
process.stdout.write('*'); // Show asterisk for each character
}
};
stdin.on('data', onData);
});
}
/**
* Validate email format
*/
function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validate password strength
* Returns error message or null if valid
*/
function validatePassword(password: string): string | null {
if (password.length < 8) {
return 'Password must be at least 8 characters long';
}
if (!/[A-Z]/.test(password)) {
return 'Password must contain at least one uppercase letter';
}
if (!/[a-z]/.test(password)) {
return 'Password must contain at least one lowercase letter';
}
if (!/[0-9]/.test(password)) {
return 'Password must contain at least one number';
}
return null;
}
/**
* Wait for database to be available
*/
async function waitForDatabase(pool: Pool, timeoutMs = 30000): Promise<void> {
const start = Date.now();
while (true) {
try {
await pool.query('SELECT 1');
return;
} catch (error) {
if (Date.now() - start > timeoutMs) {
throw new Error('Database connection timeout');
}
await new Promise((res) => setTimeout(res, 1000));
}
}
}
async function main(): Promise<void> {
console.log('');
console.log(`${colors.bold}${colors.cyan}========================================${colors.reset}`);
console.log(`${colors.bold}${colors.cyan} MotoVaultPro - Create Admin User${colors.reset}`);
console.log(`${colors.bold}${colors.cyan}========================================${colors.reset}`);
console.log('');
// Initialize database pool
const pool = new Pool({
connectionString: appConfig.getDatabaseUrl(),
});
try {
// Wait for database
printInfo('Connecting to database...');
await waitForDatabase(pool);
printInfo('Database connected.');
// Initialize repositories and services
const userProfileRepo = new UserProfileRepository(pool);
const adminRepo = new AdminRepository(pool);
const adminService = new AdminService(adminRepo);
// Step 1: Check if any admin already exists
printInfo('Checking for existing admin users...');
const existingAdmins = await adminService.getActiveAdmins();
if (existingAdmins.length > 0) {
console.log('');
printError('An admin user already exists!');
console.log('');
console.log(` Found ${existingAdmins.length} existing admin(s):`);
for (const admin of existingAdmins) {
console.log(` - ${admin.email} (${admin.role})`);
}
console.log('');
console.log(' This command is only for initial admin setup on fresh deployments.');
console.log(' To create additional admins, use the admin panel.');
console.log('');
process.exit(1);
}
printInfo('No existing admins found. Proceeding with setup.');
console.log('');
// Step 2: Create readline interface for interactive prompts
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// Prompt for email
let email: string;
while (true) {
email = await prompt(rl, 'Enter admin email: ');
if (!email) {
printError('Email is required');
continue;
}
if (!validateEmail(email)) {
printError('Please enter a valid email address');
continue;
}
break;
}
// Close readline before password prompts (we handle raw input directly)
rl.close();
// Prompt for password
let password: string;
while (true) {
password = await promptPassword('Enter password: ');
const passwordError = validatePassword(password);
if (passwordError) {
printError(passwordError);
continue;
}
const confirmPassword = await promptPassword('Confirm password: ');
if (password !== confirmPassword) {
printError('Passwords do not match');
continue;
}
break;
}
console.log('');
printInfo('Creating admin user...');
// Step 3: Create user in Auth0 (with email pre-verified for trusted CLI-created admins)
printInfo('Creating user in Auth0...');
let auth0Sub: string;
try {
auth0Sub = await auth0ManagementClient.createUser({ email, password, emailVerified: true });
printInfo(`Auth0 user created: ${auth0Sub}`);
} catch (error) {
printError(
`Failed to create Auth0 user: ${error instanceof Error ? error.message : 'Unknown error'}`
);
process.exit(1);
}
// Step 4: Create user_profile record (with email verified for trusted CLI-created admins)
printInfo('Creating user profile...');
try {
const displayName = email.split('@')[0]; // Use email prefix as display name
await userProfileRepo.create(auth0Sub, email, displayName);
// Mark email as verified in database (trusted admin created via CLI)
await userProfileRepo.updateEmailVerified(auth0Sub, true);
printInfo('User profile created (email verified).');
} catch (error) {
printError(
`Failed to create user profile: ${error instanceof Error ? error.message : 'Unknown error'}`
);
// Attempt to clean up Auth0 user
printWarn('Attempting to clean up Auth0 user...');
try {
await auth0ManagementClient.deleteUser(auth0Sub);
printInfo('Auth0 user cleaned up.');
} catch (cleanupError) {
printError('Failed to clean up Auth0 user - manual cleanup may be required');
}
process.exit(1);
}
// Step 5: Create admin_users record
printInfo('Creating admin record...');
try {
const admin = await adminService.createAdmin(
email,
'admin',
auth0Sub,
'system-cli' // createdBy indicator for CLI-created admins
);
printInfo(`Admin record created: ${admin.email} (${admin.role})`);
} catch (error) {
printError(
`Failed to create admin record: ${error instanceof Error ? error.message : 'Unknown error'}`
);
printWarn('User was created but admin privileges were not assigned.');
printWarn('You may need to manually grant admin access via the database.');
process.exit(1);
}
// Success
console.log('');
console.log(`${colors.bold}${colors.green}========================================${colors.reset}`);
console.log(`${colors.bold}${colors.green} Admin User Created Successfully!${colors.reset}`);
console.log(`${colors.bold}${colors.green}========================================${colors.reset}`);
console.log('');
console.log(` Email: ${email}`);
console.log(` Role: admin`);
console.log(` Auth0 ID: ${auth0Sub}`);
console.log('');
console.log(' You can now log in at https://motovaultpro.com');
console.log('');
} catch (error) {
printError(`Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`);
process.exit(1);
} finally {
await pool.end();
}
}
// Only run if executed directly
if (require.main === module) {
main();
}

View File

@@ -9,6 +9,7 @@ import { logger } from '../logging/logger';
interface CreateUserParams { interface CreateUserParams {
email: string; email: string;
password: string; password: string;
emailVerified?: boolean; // Optional: set true for trusted users (e.g., CLI-created admins)
} }
interface UserDetails { interface UserDetails {
@@ -46,7 +47,7 @@ class Auth0ManagementClientSingleton {
* @param password User's password * @param password User's password
* @returns Auth0 user ID * @returns Auth0 user ID
*/ */
async createUser({ email, password }: CreateUserParams): Promise<string> { async createUser({ email, password, emailVerified = false }: CreateUserParams): Promise<string> {
try { try {
const client = this.getClient(); const client = this.getClient();
@@ -54,7 +55,7 @@ class Auth0ManagementClientSingleton {
connection: this.CONNECTION_NAME, connection: this.CONNECTION_NAME,
email, email,
password, password,
email_verified: false, email_verified: emailVerified,
}); });
const user = response.data; const user = response.data;

View File

@@ -18,10 +18,8 @@ CREATE INDEX IF NOT EXISTS idx_admin_users_created_at ON admin_users(created_at)
-- Create index on revoked_at for active admin queries -- Create index on revoked_at for active admin queries
CREATE INDEX IF NOT EXISTS idx_admin_users_revoked_at ON admin_users(revoked_at); CREATE INDEX IF NOT EXISTS idx_admin_users_revoked_at ON admin_users(revoked_at);
-- Seed initial admin user (idempotent) -- Note: Initial admin user is created via `make create-admin` command
INSERT INTO admin_users (auth0_sub, email, role, created_by) -- This allows for dynamic email/password configuration on fresh deployments
VALUES ('system|bootstrap', 'admin@motovaultpro.com', 'admin', 'system')
ON CONFLICT (auth0_sub) DO NOTHING;
-- Create update trigger function (if not exists) -- Create update trigger function (if not exists)
DO $$ DO $$

View File

@@ -157,12 +157,11 @@ export class UserProfileController {
}); });
} }
const { password, confirmationText } = validation.data; const { confirmationText } = validation.data;
// Request deletion // Request deletion (user is already authenticated via JWT)
const profile = await this.userProfileService.requestDeletion( const profile = await this.userProfileService.requestDeletion(
auth0Sub, auth0Sub,
password,
confirmationText confirmationText
); );
@@ -178,13 +177,6 @@ export class UserProfileController {
userId: request.userContext?.userId, userId: request.userContext?.userId,
}); });
if (error.message.includes('Invalid password')) {
return reply.code(401).send({
error: 'Unauthorized',
message: 'Invalid password',
});
}
if (error.message.includes('Invalid confirmation')) { if (error.message.includes('Invalid confirmation')) {
return reply.code(400).send({ return reply.code(400).send({
error: 'Bad Request', error: 'Bad Request',

View File

@@ -18,7 +18,6 @@ export const updateProfileSchema = z.object({
export type UpdateProfileInput = z.infer<typeof updateProfileSchema>; export type UpdateProfileInput = z.infer<typeof updateProfileSchema>;
export const requestDeletionSchema = z.object({ export const requestDeletionSchema = z.object({
password: z.string().min(1, 'Password is required'),
confirmationText: z.string().refine((val) => val === 'DELETE', { confirmationText: z.string().refine((val) => val === 'DELETE', {
message: 'Confirmation text must be exactly "DELETE"', message: 'Confirmation text must be exactly "DELETE"',
}), }),

View File

@@ -328,12 +328,12 @@ export class UserProfileService {
// ============================================ // ============================================
/** /**
* Request account deletion with password verification * Request account deletion
* Sets 30-day grace period before permanent deletion * Sets 30-day grace period before permanent deletion
* Note: User is already authenticated via JWT, confirmation text is sufficient
*/ */
async requestDeletion( async requestDeletion(
auth0Sub: string, auth0Sub: string,
password: string,
confirmationText: string confirmationText: string
): Promise<UserProfile> { ): Promise<UserProfile> {
try { try {
@@ -353,12 +353,6 @@ export class UserProfileService {
throw new Error('Deletion already requested'); throw new Error('Deletion already requested');
} }
// Verify password with Auth0
const passwordValid = await auth0ManagementClient.verifyPassword(profile.email, password);
if (!passwordValid) {
throw new Error('Invalid password');
}
// Request deletion // Request deletion
const updatedProfile = await this.repository.requestDeletion(auth0Sub); const updatedProfile = await this.repository.requestDeletion(auth0Sub);

View File

@@ -19,7 +19,7 @@ comprehensive spec.md - containing requirements, architecture decisions, data mo
You are a senior software engineer specializsing in NodeJS, Typescript, front end and back end development. You will be delegating tasks to the platform-agent, feature-agent, first-frontend-agent and quality-agent when appropriate. You are a senior software engineer specializsing in NodeJS, Typescript, front end and back end development. You will be delegating tasks to the platform-agent, feature-agent, first-frontend-agent and quality-agent when appropriate.
*** ACTION *** *** ACTION ***
- You will be implementing improvements to the User Management. - You will be fixing a workflow logic in the new user sign up wizard.
- Make no assumptions. - Make no assumptions.
- Ask clarifying questions. - Ask clarifying questions.
- Ultrathink - Ultrathink
@@ -27,11 +27,10 @@ You are a senior software engineer specializsing in NodeJS, Typescript, front en
*** CONTEXT *** *** CONTEXT ***
- This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s. - This is a modern web app for managing a vehicle fleet. It has both a desktop and mobile versions of the site that both need to maintain feature parity. It's currently deployed via docker compose but in the future will be deployed via k8s.
- Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change. - Read README.md CLAUDE.md and AI-INDEX.md and follow relevant instructions to understand this code repository in the context of this change.
- There is no delete option for users - When a new user signs up, they are immediately redirected to https://motovaultpro.com/verify-email which is supposed to send them through a new user wizard.
- GPDR requires that users are able to fully delete their information - The user is also allowed to login before the email is confirmed. There are API errors but the login is allowed.
- There is a Delete button in the user settings. This needs to be implemented - It should not even let people login without a verified email.
- The same functionality should be enabled admin settings for user management. - The new user wizard exists. It worked in the past. Recent user changes must have broken the workflow.
*** CHANGES TO IMPLEMENT *** *** CHANGES TO IMPLEMENT ***
- Research this code base and ask iterative questions to compile a complete plan. - Research this code base and ask iterative questions to compile a complete plan.

View File

@@ -23,28 +23,26 @@ interface DeleteAccountDialogProps {
} }
export const DeleteAccountDialog: React.FC<DeleteAccountDialogProps> = ({ open, onClose }) => { export const DeleteAccountDialog: React.FC<DeleteAccountDialogProps> = ({ open, onClose }) => {
const [password, setPassword] = useState('');
const [confirmationText, setConfirmationText] = useState(''); const [confirmationText, setConfirmationText] = useState('');
const requestDeletionMutation = useRequestDeletion(); const requestDeletionMutation = useRequestDeletion();
// Clear form when dialog closes // Clear form when dialog closes
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
setPassword('');
setConfirmationText(''); setConfirmationText('');
} }
}, [open]); }, [open]);
const handleSubmit = async () => { const handleSubmit = async () => {
if (!password || confirmationText !== 'DELETE') { if (confirmationText !== 'DELETE') {
return; return;
} }
await requestDeletionMutation.mutateAsync({ password, confirmationText }); await requestDeletionMutation.mutateAsync({ confirmationText });
onClose(); onClose();
}; };
const isValid = password.length > 0 && confirmationText === 'DELETE'; const isValid = confirmationText === 'DELETE';
return ( return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth> <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
@@ -61,17 +59,6 @@ export const DeleteAccountDialog: React.FC<DeleteAccountDialogProps> = ({ open,
</Alert> </Alert>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<TextField
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
fullWidth
required
autoComplete="current-password"
helperText="Enter your password to confirm"
/>
<TextField <TextField
label="Type DELETE to confirm" label="Type DELETE to confirm"
value={confirmationText} value={confirmationText}

View File

@@ -11,14 +11,12 @@ interface DeleteAccountModalProps {
} }
export const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({ isOpen, onClose }) => { export const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({ isOpen, onClose }) => {
const [password, setPassword] = useState('');
const [confirmationText, setConfirmationText] = useState(''); const [confirmationText, setConfirmationText] = useState('');
const requestDeletionMutation = useRequestDeletion(); const requestDeletionMutation = useRequestDeletion();
// Clear form when modal closes // Clear form when modal closes
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
setPassword('');
setConfirmationText(''); setConfirmationText('');
} }
}, [isOpen]); }, [isOpen]);
@@ -26,15 +24,15 @@ export const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({ isOpen,
if (!isOpen) return null; if (!isOpen) return null;
const handleSubmit = async () => { const handleSubmit = async () => {
if (!password || confirmationText !== 'DELETE') { if (confirmationText !== 'DELETE') {
return; return;
} }
await requestDeletionMutation.mutateAsync({ password, confirmationText }); await requestDeletionMutation.mutateAsync({ confirmationText });
onClose(); onClose();
}; };
const isValid = password.length > 0 && confirmationText === 'DELETE'; const isValid = confirmationText === 'DELETE';
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
@@ -50,23 +48,6 @@ export const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({ isOpen,
</p> </p>
</div> </div>
{/* Password Input */}
<div className="mb-4">
<label className="block text-sm font-medium text-slate-700 mb-1">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500"
style={{ fontSize: '16px', minHeight: '44px' }}
autoComplete="current-password"
/>
<p className="text-xs text-slate-500 mt-1">Enter your password to confirm</p>
</div>
{/* Confirmation Input */} {/* Confirmation Input */}
<div className="mb-6"> <div className="mb-6">
<label className="block text-sm font-medium text-slate-700 mb-1"> <label className="block text-sm font-medium text-slate-700 mb-1">

View File

@@ -25,7 +25,6 @@ export interface DeletionStatus {
} }
export interface RequestDeletionRequest { export interface RequestDeletionRequest {
password: string;
confirmationText: string; confirmationText: string;
} }