z1amez's picture
v.1
2dddd1f
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input";
import { AmountInput } from "../../../components/ui/AmountInput";
import { Select } from "../../../components/ui/select";
import { Label } from "../../../components/ui/label";
import { cn } from "../../../lib/utils";
import type { Wallet } from "../../../types";
import { useEffect } from "react";
export const transactionSchema = z.object({
type: z.enum(['expense', 'income', 'transfer']),
amount: z.coerce.number().positive("Amount must be positive"),
currency: z.string().min(1, "Currency is required"),
wallet_id: z.coerce.number().min(1, "Wallet is required"),
to_wallet_id: z.coerce.number().optional().nullable(),
category: z.string().optional(),
note: z.string().optional()
}).refine(data => {
if (data.type === 'transfer' && !data.to_wallet_id) return false;
return true;
}, {
message: "Destination wallet is required",
path: ["to_wallet_id"]
}).refine(data => {
if (data.type === 'transfer' && data.wallet_id === data.to_wallet_id) return false;
return true;
}, {
message: "Cannot transfer to same wallet",
path: ["to_wallet_id"]
});
export type TransactionFormValues = z.infer<typeof transactionSchema>;
const CATEGORIES = ['Food', 'Transport', 'Utilities', 'Shopping', 'Entertainment', 'Health', 'Salary', 'Investment', 'Other'];
export function TransactionForm({
wallets,
onSubmit,
isSubmitting,
submitError
}: {
wallets: Wallet[],
onSubmit: (values: TransactionFormValues) => void,
isSubmitting: boolean,
submitError: string
}) {
const form = useForm<TransactionFormValues>({
resolver: zodResolver(transactionSchema),
defaultValues: {
type: 'expense',
amount: '' as any,
currency: wallets[0]?.currency || 'USD',
wallet_id: wallets[0]?.id || 0,
category: 'Other',
note: ''
}
});
const type = form.watch('type');
const walletId = form.watch('wallet_id');
useEffect(() => {
const w = wallets.find(w => w.id === Number(walletId));
if (w) {
form.setValue('currency', w.currency);
}
}, [walletId, wallets, form]);
return (
<div className="glass-panel p-6 mb-8 border border-blue-500/30 shadow-blue-500/5 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-indigo-500" />
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="flex flex-wrap gap-2 md:gap-4 mb-4 md:mb-6">
{['expense', 'income', 'transfer'].map(t => (
<button
key={t} type="button"
onClick={() => form.setValue('type', t as any)}
className={cn(
"flex-1 min-w-[30%] py-2 rounded-lg font-medium capitalize border transition-all text-sm md:text-base",
type === t
? "bg-blue-500/20 border-blue-500/50 text-blue-400"
: "bg-white/5 border-white/10 text-neutral-400 hover:text-neutral-200"
)}
>
{t}
</button>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>Amount</Label>
<AmountInput {...form.register('amount')} />
{form.formState.errors.amount && <p className="text-rose-400 text-xs mt-1">{form.formState.errors.amount.message}</p>}
</div>
<div>
<Label>Currency</Label>
<Select {...form.register('currency')}>
<option value="USD">USD</option>
<option value="IQD">IQD</option>
<option value="RMB">RMB</option>
</Select>
</div>
<div>
<Label>Wallet</Label>
<Select {...form.register('wallet_id')}>
{wallets.map(w => <option key={w.id} value={w.id}>{w.name} ({w.currency})</option>)}
</Select>
</div>
{type === 'transfer' && (
<div>
<Label>To Wallet</Label>
<Select {...form.register('to_wallet_id')}>
<option value="">Select Destination</option>
{wallets.filter(w => w.id.toString() !== walletId.toString()).map(w => <option key={w.id} value={w.id}>{w.name}</option>)}
</Select>
{form.formState.errors.to_wallet_id && <p className="text-rose-400 text-xs mt-1">{form.formState.errors.to_wallet_id.message}</p>}
</div>
)}
{type !== 'transfer' && (
<div>
<Label>Category</Label>
<Select {...form.register('category')}>
{CATEGORIES.map(cat => <option key={cat} value={cat}>{cat}</option>)}
</Select>
</div>
)}
<div className={cn("col-span-2", type === 'transfer' && "md:col-span-2")}>
<Label>Note (Optional)</Label>
<Input type="text" {...form.register('note')} placeholder="e.g. Paid university fee" />
</div>
</div>
{submitError && (
<div className="bg-rose-500/10 border border-rose-500/20 text-rose-400 p-3 rounded-xl text-sm mb-4">
{submitError}
</div>
)}
<div className="flex justify-end pt-2 md:pt-4">
<Button type="submit" disabled={isSubmitting} className="w-full md:w-auto">
{isSubmitting ? (
<span className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Saving...
</span>
) : (
<>Save {type}</>
)}
</Button>
</div>
</form>
</div>
);
}