Files
tabungin/PROFILE_IMPROVEMENTS_PLAN.md
dwindown 249f3a9d7d feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- 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
2025-10-11 14:00:11 +07:00

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!** 🚀