- 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
385 lines
9.3 KiB
Markdown
385 lines
9.3 KiB
Markdown
# 📋 Profile Page Improvements - Implementation Plan
|
|
|
|
## ✅ **Completed:**
|
|
1. Avatar download and local storage (fixes Google rate limit)
|
|
2. 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` - Added `downloadAndStoreAvatar()` method
|
|
- `apps/api/src/main.ts` - Configured static file serving
|
|
|
|
### **Testing:**
|
|
1. Login with Google
|
|
2. Avatar downloads to `apps/api/public/avatars/{userId}.jpg`
|
|
3. Avatar URL in database: `/avatars/{userId}.jpg`
|
|
4. 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:**
|
|
1. Create upload endpoint: `POST /api/users/avatar`
|
|
2. Use `multer` for file upload
|
|
3. Save to `/public/avatars/{userId}.jpg`
|
|
4. Update user's `avatarUrl`
|
|
|
|
**Frontend:**
|
|
1. Add file input with preview
|
|
2. Show current avatar
|
|
3. Upload button
|
|
4. Loading state
|
|
|
|
#### **B. Reorganize Profile Page**
|
|
|
|
**Create Tabs:**
|
|
```tsx
|
|
<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:**
|
|
1. Verify user password
|
|
2. Delete related data (transactions, categories, etc.)
|
|
3. Delete user account
|
|
4. Return success
|
|
|
|
**Code:**
|
|
```typescript
|
|
// 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:**
|
|
```tsx
|
|
<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:**
|
|
```typescript
|
|
// 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:**
|
|
```tsx
|
|
// 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:**
|
|
```bash
|
|
# 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:**
|
|
1. Find the preferred design commit
|
|
2. Compare with current version
|
|
3. Restore styling and layout
|
|
4. Keep current functionality (OTP, Google OAuth)
|
|
5. Test responsiveness
|
|
|
|
---
|
|
|
|
## 📊 **Implementation Priority:**
|
|
|
|
### **Phase 1: Critical** (Do First)
|
|
1. ✅ Avatar download & storage - DONE
|
|
2. ⏳ WhatsApp OTP resend
|
|
3. ⏳ Profile page tabs reorganization
|
|
|
|
### **Phase 2: Important** (Do Next)
|
|
4. ⏳ Avatar upload functionality
|
|
5. ⏳ Account deletion
|
|
|
|
### **Phase 3: Nice to Have** (Do Last)
|
|
6. ⏳ Auth pages design restoration
|
|
|
|
---
|
|
|
|
## 📝 **Files to Modify:**
|
|
|
|
### **Backend:**
|
|
1. ✅ `apps/api/src/auth/auth.service.ts` - Avatar download (DONE)
|
|
2. ✅ `apps/api/src/main.ts` - Static files (DONE)
|
|
3. ⏳ `apps/api/src/otp/otp.controller.ts` - WhatsApp resend
|
|
4. ⏳ `apps/api/src/users/users.controller.ts` - Avatar upload, delete account
|
|
5. ⏳ `apps/api/src/users/users.service.ts` - Delete account logic
|
|
|
|
### **Frontend:**
|
|
1. ⏳ `apps/web/src/components/pages/Profile.tsx` - Tabs, avatar upload
|
|
2. ⏳ `apps/web/src/components/pages/OtpVerification.tsx` - WhatsApp resend
|
|
3. ⏳ `apps/web/src/components/pages/Login.tsx` - Design restoration
|
|
4. ⏳ `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!** 🚀
|