Files
tabungin/PROFILE_UI_COMPLETE.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

314 lines
7.6 KiB
Markdown

# ✅ Profile UI Improvements - COMPLETE
## 🎉 **ALL FEATURES IMPLEMENTED:**
### **1. Avatar Upload** ✅
- **For non-Google users**: Upload button on avatar
- **For Google users**: Avatar synced from Google (no upload)
- **Features**:
- File type validation (images only)
- File size validation (max 5MB)
- Upload icon with loading state
- Error messages
- Automatic page reload after upload
### **2. Editable Name** ✅
- **For non-Google users**: Edit button with Save/Cancel
- **For Google users**: Readonly, synced from Google
- **Features**:
- Inline editing
- Validation (name cannot be empty)
- Loading states
- Success/error messages
- Automatic page reload after save
### **3. Email Field** ✅
- **Always readonly** (best practice)
- **Reason**: Email is primary identifier, changing it causes security risks
- **Helper text**: "Email cannot be changed"
### **4. Danger Zone** ✅
- **Location**: Security tab (not Edit Profile)
- **Features**:
- Red border card
- Warning message
- Password confirmation required
- Two-step confirmation (button → password input)
- Delete button with trash icon
- Loading states
- Error messages
- Automatic logout and redirect after deletion
---
## 📊 **Email Editability - Best Practices Explained:**
### **❌ Why Email Should NOT Be Editable:**
1. **Security Risk**:
- Email is the primary identifier
- Changing it can enable account takeover
- Requires complex verification flow
2. **OAuth Complications**:
- Breaks Google OAuth connection
- User loses access to "Continue with Google"
- Requires re-linking accounts
3. **Verification Complexity**:
- Need to verify NEW email
- Keep OLD email active until verified
- Send notifications to both emails
- Add cooldown period
4. **User Confusion**:
- Login with old email won't work
- Password reset goes to wrong email
- Support tickets increase
### **✅ Recommended Approach:**
- **Keep email readonly**
- **If user wants different email**: Create new account
- **Alternative**: Add secondary email for notifications (not for login)
---
## 🎨 **UI Features:**
### **Avatar Section**:
- ✅ Larger avatar (20x20)
- ✅ Upload button (bottom-right corner)
- ✅ Conditional display (Google vs non-Google)
- ✅ Loading spinner during upload
- ✅ Helper text explaining sync status
- ✅ Error messages below avatar
### **Name Field**:
- ✅ Conditional editing (Google vs non-Google)
- ✅ Edit/Save/Cancel buttons
- ✅ Inline editing (no modal)
- ✅ Validation messages
- ✅ Loading states
- ✅ Disabled state when not editing
### **Danger Zone**:
- ✅ Red border card
- ✅ Warning icon
- ✅ Clear warning message
- ✅ Two-step confirmation
- ✅ Password input
- ✅ Delete/Cancel buttons
- ✅ Loading states
- ✅ Error handling
---
## 🔧 **Backend Requirements:**
### **New Endpoints Needed**:
1. **`GET /api/auth/accounts`** - Check if user has Google OAuth
```typescript
// Returns array of auth accounts
[{ provider: 'google', ... }]
```
2. **`POST /api/users/avatar`** - Upload avatar
```typescript
// Accepts multipart/form-data
// Field name: 'avatar'
// Returns updated user with new avatarUrl
```
3. **`PUT /api/users/profile`** - Update name (already exists for phone)
```typescript
// Add support for 'name' field
{ name: string }
```
4. **`DELETE /api/users/account`** - Delete account
```typescript
// Requires password confirmation
{ password: string }
// Deletes all user data
```
---
## 📝 **Implementation Details:**
### **Google Auth Detection**:
```typescript
const checkGoogleAuth = async () => {
const response = await axios.get(`${API}/auth/accounts`)
const hasGoogle = response.data.some(acc => acc.provider === 'google')
setHasGoogleAuth(hasGoogle)
}
```
### **Avatar Upload**:
```typescript
const handleAvatarUpload = async (event) => {
const file = event.target.files?.[0]
// Validate type
if (!file.type.startsWith('image/')) {
setAvatarError("Please select an image file")
return
}
// Validate size (5MB)
if (file.size > 5 * 1024 * 1024) {
setAvatarError("Image size must be less than 5MB")
return
}
const formData = new FormData()
formData.append('avatar', file)
await axios.post(`${API}/users/avatar`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
window.location.reload()
}
```
### **Name Update**:
```typescript
const handleUpdateName = async () => {
if (!editedName || editedName.trim().length === 0) {
setNameError("Name cannot be empty")
return
}
await axios.put(`${API}/users/profile`, { name: editedName })
setNameSuccess("Name updated successfully!")
setIsEditingName(false)
window.location.reload()
}
```
### **Account Deletion**:
```typescript
const handleDeleteAccount = async () => {
if (!deletePassword) {
setDeleteError("Please enter your password")
return
}
await axios.delete(`${API}/users/account`, {
data: { password: deletePassword }
})
localStorage.removeItem('token')
window.location.href = '/auth/login'
}
```
---
## 🧪 **Testing Checklist:**
### **For Google Users**:
- [ ] Avatar shows Google profile picture
- [ ] No upload button on avatar
- [ ] Name field is disabled (gray)
- [ ] Helper text says "synced from Google"
- [ ] Email is readonly
- [ ] Phone is editable
- [ ] Danger Zone works
### **For Email/Password Users**:
- [ ] Avatar shows default icon or uploaded image
- [ ] Upload button appears on avatar
- [ ] Click upload → file picker opens
- [ ] Upload image → avatar updates
- [ ] Name field has Edit button
- [ ] Click Edit → input becomes editable
- [ ] Change name → click Save → name updates
- [ ] Click Cancel → changes discarded
- [ ] Email is readonly
- [ ] Phone is editable
- [ ] Danger Zone works
### **Danger Zone**:
- [ ] Located in Security tab
- [ ] Red border card
- [ ] Click "Delete Account" → password input appears
- [ ] Enter wrong password → error message
- [ ] Enter correct password → account deleted
- [ ] Redirects to login page
- [ ] Cannot login with deleted account
---
## ✅ **ESLint:**
```bash
npm run lint
# ✓ 0 errors, 0 warnings
```
---
## 📊 **Files Modified:**
1. **`apps/web/src/components/pages/Profile.tsx`**
- Added Google auth detection
- Added avatar upload
- Added name editing
- Added danger zone
- Added all handlers and states
---
## 🎯 **User Experience:**
### **Before**:
- All users see same UI
- No way to upload avatar
- No way to edit name
- No way to delete account
- Confusing for non-Google users
### **After**:
- ✅ Conditional UI based on auth method
- ✅ Avatar upload for non-Google users
- ✅ Name editing for non-Google users
- ✅ Clear helper text explaining restrictions
- ✅ Danger zone for account deletion
- ✅ Professional, intuitive interface
---
## 🚀 **Next Steps:**
### **Backend Implementation Required**:
1. Create `GET /api/auth/accounts` endpoint
2. Create `POST /api/users/avatar` endpoint with multer
3. Update `PUT /api/users/profile` to support name
4. Create `DELETE /api/users/account` endpoint
### **Optional Enhancements**:
- Avatar cropping before upload
- Image compression
- Multiple avatar options
- Account export before deletion
- Deletion cooldown period (30 days)
---
## 🎉 **COMPLETE!**
**All UI improvements implemented:**
- ✅ Avatar upload (non-Google users)
- ✅ Editable name (non-Google users)
- ✅ Email readonly (best practice)
- ✅ Danger zone (Security tab)
- ✅ Conditional UI (Google vs non-Google)
- ✅ All validations
- ✅ All error handling
- ✅ ESLint clean
**Ready for backend implementation!** 🚀