- 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
435 lines
11 KiB
Markdown
435 lines
11 KiB
Markdown
# 🎯 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** ✅:
|
|
```typescript
|
|
// 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**:
|
|
|
|
```typescript
|
|
// 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):
|
|
|
|
```tsx
|
|
{/* 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**:
|
|
```typescript
|
|
const availableMethods = {
|
|
email: methods?.email || false,
|
|
whatsapp: methods?.whatsapp || false,
|
|
totp: methods?.totp || false,
|
|
}
|
|
```
|
|
|
|
2. **Add WhatsApp tab button**:
|
|
```tsx
|
|
{availableMethods.whatsapp && (
|
|
<Button
|
|
variant={selectedMethod === "whatsapp" ? "default" : "outline"}
|
|
onClick={() => setSelectedMethod("whatsapp")}
|
|
className="flex-1"
|
|
>
|
|
<Smartphone className="mr-2 h-4 w-4" />
|
|
WhatsApp
|
|
</Button>
|
|
)}
|
|
```
|
|
|
|
3. **Add WhatsApp content section**:
|
|
```tsx
|
|
{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>
|
|
)}
|
|
```
|
|
|
|
4. **Update resend handler** to support WhatsApp:
|
|
```typescript
|
|
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**:
|
|
```bash
|
|
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!** 🚀
|