- Remove OtpGateGuard from transactions controller (OTP verified at login) - Fix categories controller to use authenticated user instead of TEMP_USER_ID - Add comprehensive implementation plan document - Update .env.example with WEB_APP_URL - Prepare for admin dashboard development
9.3 KiB
9.3 KiB
📋 Profile Page Improvements - Implementation Plan
✅ Completed:
- Avatar download and local storage (fixes Google rate limit)
- WhatsApp OTP full implementation
⏳ TODO:
1. Avatar Download & Storage ✅ DONE
Implementation:
- Downloads Google avatar and stores in
/public/avatars/ - Serves from
http://localhost:3001/avatars/{userId}.jpg - No more rate limits!
Files Modified:
apps/api/src/auth/auth.service.ts- AddeddownloadAndStoreAvatar()methodapps/api/src/main.ts- Configured static file serving
Testing:
- Login with Google
- Avatar downloads to
apps/api/public/avatars/{userId}.jpg - Avatar URL in database:
/avatars/{userId}.jpg - Accessible at:
http://localhost:3001/avatars/{userId}.jpg
2. Profile Page Redesign with Tabs
Current Structure:
Profile Page
├── Account Information Card
├── Password Change Card
└── OTP Security Card (all methods together)
New Structure:
Profile Page
├── Profile Card (with tabs)
│ ├── Edit Profile Tab
│ │ ├── Avatar Upload
│ │ ├── Name
│ │ ├── Email (readonly)
│ │ └── Phone
│ └── Security Tab
│ ├── Change Password Card
│ │ ├── Current Password
│ │ ├── New Password
│ │ └── Confirm Password
│ └── Two-Factor Authentication Card
│ ├── Phone Number
│ ├── WhatsApp OTP
│ ├── Email OTP
│ └── TOTP
Implementation Steps:
A. Add Avatar Upload
Backend:
- Create upload endpoint:
POST /api/users/avatar - Use
multerfor file upload - Save to
/public/avatars/{userId}.jpg - Update user's
avatarUrl
Frontend:
- Add file input with preview
- Show current avatar
- Upload button
- Loading state
B. Reorganize Profile Page
Create Tabs:
<Tabs defaultValue="profile">
<TabsList>
<TabsTrigger value="profile">Edit Profile</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
</TabsList>
<TabsContent value="profile">
{/* Avatar, Name, Email, Phone */}
</TabsContent>
<TabsContent value="security">
{/* Password Change + 2FA */}
</TabsContent>
</Tabs>
3. Account Deletion
Backend Implementation:
Endpoint: DELETE /api/users/account
Steps:
- Verify user password
- Delete related data (transactions, categories, etc.)
- Delete user account
- Return success
Code:
// users.controller.ts
@Delete('account')
async deleteAccount(
@Req() req: RequestWithUser,
@Body() body: { password: string }
) {
return this.users.deleteAccount(req.user.userId, body.password)
}
// users.service.ts
async deleteAccount(userId: string, password: string) {
// 1. Get user and verify password
const user = await this.prisma.user.findUnique({
where: { id: userId },
select: { passwordHash: true }
})
if (user?.passwordHash) {
const isValid = await bcrypt.compare(password, user.passwordHash)
if (!isValid) {
throw new UnauthorizedException('Invalid password')
}
}
// 2. Delete related data
await this.prisma.$transaction([
this.prisma.transaction.deleteMany({ where: { userId } }),
this.prisma.category.deleteMany({ where: { userId } }),
this.prisma.authAccount.deleteMany({ where: { userId } }),
this.prisma.user.delete({ where: { id: userId } })
])
return { success: true, message: 'Account deleted successfully' }
}
Frontend Implementation:
Add to Security Tab:
<Card className="border-destructive">
<CardHeader>
<CardTitle className="text-destructive">Danger Zone</CardTitle>
<CardDescription>
Permanently delete your account and all data
</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="destructive"
onClick={() => setShowDeleteDialog(true)}
>
Delete Account
</Button>
</CardContent>
</Card>
{/* Confirmation Dialog */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove all your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<div className="space-y-2">
<Label>Enter your password to confirm</Label>
<Input
type="password"
value={deletePassword}
onChange={(e) => setDeletePassword(e.target.value)}
/>
</div>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteAccount}
className="bg-destructive"
>
Delete Account
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
4. WhatsApp OTP Resend
Backend Implementation:
Endpoint: POST /api/otp/whatsapp/resend
Code:
// otp.controller.ts
@Public()
@Post('whatsapp/resend')
async resendWhatsappOtp(@Body() body: { tempToken: string }) {
try {
// Verify temp token
const payload = this.jwtService.verify(body.tempToken) as {
temp?: boolean;
userId?: string;
sub?: string;
};
if (!payload.temp) {
throw new UnauthorizedException('Invalid token type');
}
const userId = payload.userId || payload.sub;
if (!userId) {
throw new UnauthorizedException('Invalid token payload');
}
// Send WhatsApp OTP (live mode for login)
return this.otpService.sendWhatsappOtp(userId, 'live');
} catch {
throw new UnauthorizedException('Invalid or expired token');
}
}
Frontend Implementation:
Update OTP Verification Page:
// Add resend handler for WhatsApp
const handleResendWhatsApp = async () => {
setResendLoading(true)
setError('')
try {
await axios.post(`${API_URL}/api/otp/whatsapp/resend`, {
tempToken
})
setResendTimer(30)
setCanResend(false)
setError('')
} catch (err) {
setError('Failed to resend code. Please try again.')
} finally {
setResendLoading(false)
}
}
// Add resend button in WhatsApp tab
<TabsContent value="whatsapp" className="space-y-4">
{/* ... existing code ... */}
<Button
type="button"
variant="outline"
className="w-full"
onClick={handleResendWhatsApp}
disabled={!canResend || resendLoading || loading}
>
{resendLoading ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Sending...
</>
) : canResend ? (
<>
<Smartphone className="mr-2 h-4 w-4" />
Resend Code
</>
) : (
<>
<Smartphone className="mr-2 h-4 w-4" />
Resend in {resendTimer}s
</>
)}
</Button>
</TabsContent>
5. Auth Pages Design Restoration
Check Git History:
# Find commits that modified Login/Register pages
git log --all --full-history -- "apps/web/src/components/pages/Login.tsx"
git log --all --full-history -- "apps/web/src/components/pages/Register.tsx"
# View specific commit
git show <commit-hash>:apps/web/src/components/pages/Login.tsx
Steps:
- Find the preferred design commit
- Compare with current version
- Restore styling and layout
- Keep current functionality (OTP, Google OAuth)
- Test responsiveness
📊 Implementation Priority:
Phase 1: Critical (Do First)
- ✅ Avatar download & storage - DONE
- ⏳ WhatsApp OTP resend
- ⏳ Profile page tabs reorganization
Phase 2: Important (Do Next)
- ⏳ Avatar upload functionality
- ⏳ Account deletion
Phase 3: Nice to Have (Do Last)
- ⏳ Auth pages design restoration
📝 Files to Modify:
Backend:
- ✅
apps/api/src/auth/auth.service.ts- Avatar download (DONE) - ✅
apps/api/src/main.ts- Static files (DONE) - ⏳
apps/api/src/otp/otp.controller.ts- WhatsApp resend - ⏳
apps/api/src/users/users.controller.ts- Avatar upload, delete account - ⏳
apps/api/src/users/users.service.ts- Delete account logic
Frontend:
- ⏳
apps/web/src/components/pages/Profile.tsx- Tabs, avatar upload - ⏳
apps/web/src/components/pages/OtpVerification.tsx- WhatsApp resend - ⏳
apps/web/src/components/pages/Login.tsx- Design restoration - ⏳
apps/web/src/components/pages/Register.tsx- Design restoration
🧪 Testing Checklist:
Avatar Download:
- Login with Google
- Check
apps/api/public/avatars/folder - Avatar file exists
- Avatar loads in Profile page
- No rate limit errors
WhatsApp Resend:
- Login triggers WhatsApp OTP
- Wait 30 seconds
- Click "Resend Code"
- New code received
- Timer resets
Profile Tabs:
- See "Edit Profile" and "Security" tabs
- Edit Profile shows avatar, name, email, phone
- Security shows password change and 2FA
- Tab switching works
Account Deletion:
- Click "Delete Account"
- Confirmation dialog appears
- Enter password
- Account deleted
- Redirected to login
- Cannot login with deleted account
Ready to implement! Start with WhatsApp resend, then profile tabs! 🚀