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
This commit is contained in:
313
PROFILE_UI_COMPLETE.md
Normal file
313
PROFILE_UI_COMPLETE.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# ✅ 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!** 🚀
|
||||
Reference in New Issue
Block a user