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

7.6 KiB

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
  • 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

    // Returns array of auth accounts
    [{ provider: 'google', ... }]
    
  2. POST /api/users/avatar - Upload avatar

    // Accepts multipart/form-data
    // Field name: 'avatar'
    // Returns updated user with new avatarUrl
    
  3. PUT /api/users/profile - Update name (already exists for phone)

    // Add support for 'name' field
    { name: string }
    
  4. DELETE /api/users/account - Delete account

    // Requires password confirmation
    { password: string }
    // Deletes all user data
    

📝 Implementation Details:

Google Auth Detection:

const checkGoogleAuth = async () => {
  const response = await axios.get(`${API}/auth/accounts`)
  const hasGoogle = response.data.some(acc => acc.provider === 'google')
  setHasGoogleAuth(hasGoogle)
}

Avatar Upload:

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:

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:

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:

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