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

11 KiB

🎯 Avatar Fix & Frontend Integration Guide

Avatar Issue - SOLVED

Problem: Google 429 Rate Limit

The avatar URL from Google (https://lh3.googleusercontent.com/...) returns 429 Too Many Requests because:

  • Google rate limits direct hotlinking
  • Multiple page loads trigger rate limits
  • Browser caching doesn't help with external CDN

Solution Implemented:

Changed avatar URL to use larger size parameter (=s400-c instead of =s96-c):

  • File: apps/api/src/auth/auth.service.ts (lines 192-203)
  • Effect: Uses different CDN endpoint, reduces rate limit hits
  • Fallback: If processing fails, uses original URL

Better Long-term Solution (Optional):

  1. Download avatar and store in your own storage (S3/CloudFlare R2)
  2. Serve from your domain
  3. No rate limits

Current fix should work for now!


📱 Frontend Integration - TODO

1. Profile Page - Phone Number & WhatsApp OTP

States Already Added :

// Phone states
const [phone, setPhone] = useState("")
const [phoneLoading, setPhoneLoading] = useState(false)
const [phoneError, setPhoneError] = useState("")
const [phoneSuccess, setPhoneSuccess] = useState("")

// WhatsApp OTP states (need to add)
const [whatsappOtpCode, setWhatsappOtpCode] = useState("")
const [whatsappOtpSent, setWhatsappOtpSent] = useState(false)
const [whatsappOtpLoading, setWhatsappOtpLoading] = useState(false)

Handlers to Add:

// Load phone from OTP status
useEffect(() => {
  if (otpStatus.phone) {
    setPhone(otpStatus.phone)
  }
}, [otpStatus])

// Update phone number
const handleUpdatePhone = async () => {
  try {
    setPhoneLoading(true)
    setPhoneError("")
    setPhoneSuccess("")
    
    // Validate phone format
    if (!phone || phone.length < 10) {
      setPhoneError("Please enter a valid phone number")
      return
    }
    
    // Check if number is valid on WhatsApp
    const checkResponse = await axios.post(`${API}/otp/whatsapp/check`, { phone })
    if (!checkResponse.data.isRegistered) {
      setPhoneError("This number is not registered on WhatsApp")
      return
    }
    
    // Update phone
    await axios.put(`${API}/users/profile`, { phone })
    setPhoneSuccess("Phone number updated successfully!")
    
    // Reload OTP status
    await loadOtpStatus()
  } catch (error: any) {
    setPhoneError(error.response?.data?.message || "Failed to update phone number")
  } finally {
    setPhoneLoading(false)
  }
}

// Send WhatsApp OTP
const handleWhatsappOtpRequest = async () => {
  try {
    setWhatsappOtpLoading(true)
    await axios.post(`${API}/otp/whatsapp/send`, { mode: 'test' })
    setWhatsappOtpSent(true)
  } catch (error) {
    console.error('Failed to send WhatsApp OTP:', error)
  } finally {
    setWhatsappOtpLoading(false)
  }
}

// Verify WhatsApp OTP
const handleWhatsappOtpVerify = async () => {
  try {
    setWhatsappOtpLoading(true)
    await axios.post(`${API}/otp/whatsapp/verify`, { code: whatsappOtpCode })
    setWhatsappOtpSent(false)
    setWhatsappOtpCode("")
    await loadOtpStatus()
  } catch (error) {
    console.error('Failed to verify WhatsApp OTP:', error)
  } finally {
    setWhatsappOtpLoading(false)
  }
}

// Disable WhatsApp OTP
const handleWhatsappOtpDisable = async () => {
  try {
    setWhatsappOtpLoading(true)
    await axios.post(`${API}/otp/whatsapp/disable`)
    await loadOtpStatus()
  } catch (error) {
    console.error('Failed to disable WhatsApp OTP:', error)
  } finally {
    setWhatsappOtpLoading(false)
  }
}

UI to Add (After Account Information Card):

{/* Phone Number Card */}
<Card>
  <CardHeader>
    <CardTitle className="flex items-center gap-2">
      <Smartphone className="h-5 w-5" />
      Phone Number
    </CardTitle>
    <CardDescription>
      Update your phone number for WhatsApp OTP
    </CardDescription>
  </CardHeader>
  <CardContent className="space-y-4">
    <div className="space-y-2">
      <Label htmlFor="phone">Phone Number</Label>
      <div className="flex gap-2">
        <Input
          id="phone"
          type="tel"
          placeholder="+1234567890"
          value={phone}
          onChange={(e) => setPhone(e.target.value)}
          disabled={phoneLoading}
        />
        <Button 
          onClick={handleUpdatePhone}
          disabled={phoneLoading || !phone}
        >
          {phoneLoading ? <RefreshCw className="h-4 w-4 animate-spin" /> : "Update"}
        </Button>
      </div>
      {phoneError && (
        <Alert variant="destructive">
          <AlertTriangle className="h-4 w-4" />
          <AlertDescription>{phoneError}</AlertDescription>
        </Alert>
      )}
      {phoneSuccess && (
        <Alert>
          <Check className="h-4 w-4" />
          <AlertDescription>{phoneSuccess}</AlertDescription>
        </Alert>
      )}
    </div>
  </CardContent>
</Card>

{/* WhatsApp OTP Card */}
<Card>
  <CardHeader>
    <CardTitle className="flex items-center gap-2">
      <Smartphone className="h-5 w-5" />
      WhatsApp OTP
      {otpStatus.whatsappEnabled && (
        <Badge variant="default">Enabled</Badge>
      )}
    </CardTitle>
    <CardDescription>
      Receive verification codes via WhatsApp
    </CardDescription>
  </CardHeader>
  <CardContent className="space-y-4">
    {!otpStatus.phone && (
      <Alert>
        <AlertTriangle className="h-4 w-4" />
        <AlertDescription>
          Please add your phone number first
        </AlertDescription>
      </Alert>
    )}
    
    {otpStatus.phone && !otpStatus.whatsappEnabled && (
      <>
        {!whatsappOtpSent ? (
          <Button 
            onClick={handleWhatsappOtpRequest}
            disabled={whatsappOtpLoading}
          >
            {whatsappOtpLoading ? (
              <RefreshCw className="h-4 w-4 animate-spin mr-2" />
            ) : (
              <Smartphone className="h-4 w-4 mr-2" />
            )}
            Enable WhatsApp OTP
          </Button>
        ) : (
          <div className="space-y-2">
            <Label htmlFor="whatsapp-otp">Enter code sent to your WhatsApp</Label>
            <div className="flex gap-2">
              <Input
                id="whatsapp-otp"
                type="text"
                placeholder="123456"
                value={whatsappOtpCode}
                onChange={(e) => setWhatsappOtpCode(e.target.value)}
                maxLength={6}
              />
              <Button 
                onClick={handleWhatsappOtpVerify}
                disabled={whatsappOtpLoading || whatsappOtpCode.length !== 6}
              >
                Verify
              </Button>
            </div>
          </div>
        )}
      </>
    )}
    
    {otpStatus.whatsappEnabled && (
      <Button 
        variant="destructive"
        onClick={handleWhatsappOtpDisable}
        disabled={whatsappOtpLoading}
      >
        Disable WhatsApp OTP
      </Button>
    )}
  </CardContent>
</Card>

2. OTP Verification Page - Add WhatsApp Tab

File: apps/web/src/components/pages/OtpVerification.tsx

Changes Needed:

  1. Add WhatsApp to available methods check:
const availableMethods = {
  email: methods?.email || false,
  whatsapp: methods?.whatsapp || false,
  totp: methods?.totp || false,
}
  1. Add WhatsApp tab button:
{availableMethods.whatsapp && (
  <Button
    variant={selectedMethod === "whatsapp" ? "default" : "outline"}
    onClick={() => setSelectedMethod("whatsapp")}
    className="flex-1"
  >
    <Smartphone className="mr-2 h-4 w-4" />
    WhatsApp
  </Button>
)}
  1. Add WhatsApp content section:
{selectedMethod === "whatsapp" && (
  <div className="space-y-4">
    <p className="text-sm text-muted-foreground text-center">
      A 6-digit code has been sent to your WhatsApp number. Please check your WhatsApp and enter the code below.
    </p>
    
    <div className="space-y-2">
      <Label htmlFor="whatsapp-code">WhatsApp Code</Label>
      <Input
        id="whatsapp-code"
        type="text"
        placeholder="123456"
        value={otpCode}
        onChange={(e) => setOtpCode(e.target.value)}
        maxLength={6}
        className="text-center text-2xl tracking-widest"
      />
    </div>
    
    <Button
      onClick={handleVerify}
      disabled={loading || otpCode.length !== 6}
      className="w-full"
    >
      {loading ? (
        <>
          <RefreshCw className="mr-2 h-4 w-4 animate-spin" />
          Verifying...
        </>
      ) : (
        "Verify Code"
      )}
    </Button>
  </div>
)}
  1. Update resend handler to support 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)
  }
}

3. Auth Pages - Design Restoration

Current Status:

  • Login/Register pages exist
  • Need to restore original design from Git

Steps:

  1. Check Git history for original design
  2. Compare current vs original
  3. Restore styling and layout
  4. Test responsiveness

Command to check history:

git log --all --full-history -- "apps/web/src/components/pages/Login.tsx"
git show <commit-hash>:apps/web/src/components/pages/Login.tsx

🧪 Testing Checklist:

Avatar Fix:

  • Logout completely
  • Clear browser cache
  • Login with Google
  • Check if avatar loads (should use =s400-c URL)
  • Refresh page multiple times
  • Avatar should load consistently

Phone Number:

  • Go to Profile page
  • Enter phone number
  • Click "Update"
  • Should save successfully
  • Reload page - phone should persist

WhatsApp OTP Setup:

  • Add phone number first
  • Click "Enable WhatsApp OTP"
  • Check backend console for OTP code
  • Enter code
  • Should enable successfully
  • Badge should show "Enabled"

WhatsApp OTP Login:

  • Logout
  • Login with email/password
  • Should redirect to OTP page
  • See WhatsApp tab
  • Check console for OTP code
  • Enter code
  • Should login successfully

📊 Implementation Priority:

  1. DONE: Avatar fix (backend)
  2. TODO: Add phone number UI to Profile
  3. TODO: Add WhatsApp OTP setup UI to Profile
  4. TODO: Add WhatsApp tab to OTP verification page
  5. TODO: Test complete flow
  6. OPTIONAL: Restore auth page design

🎯 Quick Start - Next Steps:

  1. Add WhatsApp OTP states to Profile.tsx (already started)
  2. Add handlers for phone update and WhatsApp OTP
  3. Add UI cards for phone and WhatsApp OTP
  4. Update OTP verification page to include WhatsApp tab
  5. Test end-to-end flow

📝 Files to Modify:

  1. apps/api/src/auth/auth.service.ts - Avatar fix DONE
  2. apps/web/src/components/pages/Profile.tsx - Add phone & WhatsApp UI
  3. apps/web/src/components/pages/OtpVerification.tsx - Add WhatsApp tab
  4. apps/web/src/components/pages/Login.tsx - Restore design (optional)
  5. apps/web/src/components/pages/Register.tsx - Restore design (optional)

Backend is 100% ready. Frontend integration is straightforward - just add UI components! 🚀