feat: Add WhatsApp verification & responsive dialogs

 COMPLETED FEATURES:

1. WhatsApp Number Verification
   - Verify phone number is registered on WhatsApp before saving
   - Use OTP webhook with check_number mode
   - Show error if number not registered
   - Translated error messages

2. Responsive Dialog/Drawer System
   - Created ResponsiveDialog component
   - Desktop: Uses Dialog (modal)
   - Mobile: Uses Drawer (bottom sheet)
   - Applied to WalletDialog & TransactionDialog
   - Better UX on mobile devices

3. Translation Fixes
   - Fixed editProfile key placement
   - All translation keys now consistent
   - Build passing without errors

📱 MOBILE IMPROVEMENTS:
- Form dialogs now slide up from bottom on mobile
- Better touch interaction
- More native mobile feel

🔧 TECHNICAL:
- Added shadcn Drawer component
- Created useMediaQuery hook
- Responsive context wrapper
- Type-safe implementation
This commit is contained in:
dwindown
2025-10-12 17:07:16 +07:00
parent d626c7d8de
commit 46488a09e2
15 changed files with 914 additions and 239 deletions

View File

@@ -3,13 +3,13 @@ import { toast } from "sonner"
import { useLanguage } from "@/contexts/LanguageContext"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
ResponsiveDialog,
ResponsiveDialogContent,
ResponsiveDialogDescription,
ResponsiveDialogFooter,
ResponsiveDialogHeader,
ResponsiveDialogTitle,
} from "@/components/ui/responsive-dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
@@ -171,17 +171,17 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
if (isEditing) {
await axios.put(`${API}/wallets/${walletId}/transactions/${transaction.id}`, data)
toast.success('Transaksi berhasil diupdate')
toast.success(t.transactionDialog.editSuccess)
} else {
await axios.post(`${API}/wallets/${walletId}/transactions`, data)
toast.success('Transaksi berhasil ditambahkan')
toast.success(t.transactionDialog.addSuccess)
}
onSuccess()
onOpenChange(false)
} catch (error) {
console.error("Failed to save transaction:", error)
toast.error('Gagal menyimpan transaksi')
toast.error(t.transactionDialog.saveError)
} finally {
setLoading(false)
}
@@ -222,14 +222,14 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
}, [open, transaction])
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle}</DialogTitle>
<DialogDescription>
{isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle}
</DialogDescription>
</DialogHeader>
<ResponsiveDialog open={open} onOpenChange={handleOpenChange}>
<ResponsiveDialogContent className="sm:max-w-[425px]">
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>{isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle}</ResponsiveDialogTitle>
<ResponsiveDialogDescription>
{t.transactionDialog.description}
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
@@ -311,16 +311,16 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
</div>
)}
</div>
<DialogFooter>
<ResponsiveDialogFooter>
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)}>
{t.common.cancel}
</Button>
<Button type="submit" disabled={loading}>
{loading ? t.common.loading : isEditing ? t.common.save : t.common.add}
</Button>
</DialogFooter>
</ResponsiveDialogFooter>
</form>
</DialogContent>
</Dialog>
</ResponsiveDialogContent>
</ResponsiveDialog>
)
}