diff --git a/package.json b/package.json index 4c07b5e..b431a32 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "tw-animate-css": "^1.2.5", "use-debounce": "^10.0.4", "vaul": "^1.1.2", - "zod": "^3.24.2" + "zod": "^3.24.2", + "zustand": "^5.0.3" }, "devDependencies": { "@biomejs/biome": "1.9.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd07740..8e21cff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,6 +119,9 @@ importers: zod: specifier: ^3.24.2 version: 3.24.2 + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.1.0)(react@19.1.0) devDependencies: '@biomejs/biome': specifier: 1.9.4 @@ -1792,6 +1795,24 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3086,3 +3107,8 @@ snapshots: isexe: 3.1.1 zod@3.24.2: {} + + zustand@5.0.3(@types/react@19.1.0)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.0 + react: 19.1.0 diff --git a/src/app/(router)/add/page.tsx b/src/app/(router)/add/page.tsx index 4a557c6..ffdbb6d 100644 --- a/src/app/(router)/add/page.tsx +++ b/src/app/(router)/add/page.tsx @@ -16,9 +16,7 @@ export default async function Page() { Create Expense -
- -
+ ); } diff --git a/src/app/(router)/expense/page.tsx b/src/app/(router)/expense/page.tsx index 8445150..0f883dc 100644 --- a/src/app/(router)/expense/page.tsx +++ b/src/app/(router)/expense/page.tsx @@ -1,9 +1,12 @@ import Header from "@/components/header"; +import Section from "@/components/section"; import { Button } from "@/components/ui/button"; +import { api } from "@/trpc/server"; import Link from "next/link"; import React from "react"; -export default function Page() { +export default async function Page() { + const splits = await api.expense.getAll(); return ( <>
@@ -11,6 +14,14 @@ export default function Page() { Add Expense
+
+ {splits.map(({ expense }) => ( +
+

{expense.description}

+

{expense.amount}

+
+ ))} +
); } diff --git a/src/app/(router)/page.tsx b/src/app/(router)/page.tsx index f2f25bf..5100239 100644 --- a/src/app/(router)/page.tsx +++ b/src/app/(router)/page.tsx @@ -3,6 +3,7 @@ import Header from "@/components/header"; import Section from "@/components/section"; import { auth } from "@/server/auth"; import UserDropdown from "../_components/user-dropdown"; +import { ModeToggle } from "@/components/mode-toggle"; export default async function Home() { const session = await auth(); @@ -12,7 +13,9 @@ export default async function Home() {
-
+
+ +
); } diff --git a/src/app/_components/expense/expense-form.tsx b/src/app/_components/expense/expense-form.tsx index b2ec971..5912993 100644 --- a/src/app/_components/expense/expense-form.tsx +++ b/src/app/_components/expense/expense-form.tsx @@ -20,37 +20,92 @@ import { NumberInput } from "@/components/number-input"; import { EuroIcon } from "lucide-react"; import ExpenseSplit from "./expense-split"; import type { Session } from "next-auth"; -import { useExpenseSplit } from "@/hooks/use-expense-split"; import { toast } from "sonner"; import { api } from "@/trpc/react"; +import { useExpenseStore } from "@/lib/store/expense-store"; +import ExpenseParticipants from "./expense-participants"; +import { Separator } from "@/components/ui/separator"; +import { calculateDepts } from "@/lib/utils/expense"; +import { cn } from "@/lib/utils"; +import { useRouter } from "next/navigation"; function ExpenseForm({ - initialExpense, session, + initialExpense, hideSubmit, }: { - initialExpense?: Expense; session: Session; + initialExpense?: Expense; hideSubmit?: boolean; }) { - const createExpense = api.expense.create.useMutation(); const form = useForm>({ resolver: zodResolver(expenseSchema), defaultValues: { description: initialExpense?.description ?? "", - amount: initialExpense?.amount ?? 0, + amount: initialExpense?.amount ? Number(initialExpense?.amount) : 0, }, }); + const participants = useExpenseStore((state) => state.participants); + const addParticipant = useExpenseStore((state) => state.addParticipant); + const setAmount = useExpenseStore((state) => state.setAmount); + const splitType = useExpenseStore((state) => state.splitType); + const setPayments = useExpenseStore((state) => state.setPayments); + const payments = useExpenseStore((state) => state.payments); + const recalculateSplits = useExpenseStore((state) => state.recalculateSplits); const amount = form.watch("amount"); - const expenseSplitHook = useExpenseSplit(amount, session); - function onSubmit(values: z.infer) { - if (expenseSplitHook.participants.length <= 1) + const resetExpenseStore = useExpenseStore((state) => state.resetExpenseStore); + const router = useRouter(); + + const createExpense = api.expense.create.useMutation({ + onSuccess(expense) { + form.reset(); + resetExpenseStore(); + addParticipant(session.user); + setPayments([{ amount: amount, userId: session.user.id }]); + toast.message("Expense Created", { + position: "bottom-center", + action: { + children: "test", + onClick: () => { + router.push(`/expense/${expense.id}`); + }, + label: "View Expense", + }, + }); + }, + }); + function onSubmit(expense: z.infer) { + if (createExpense.isPending) return; + if (participants.length <= 1) return toast.error("Please add at least 2 participants"); - if (expenseSplitHook.splitType !== "Equal") + if (splitType !== "Equal") return toast.error("You can only split equal for now."); - toast.success("Expense created!"); + + const paymentAmount = payments.reduce((acc, curr) => acc + curr.amount, 0); + + if (amount !== paymentAmount) return toast.error("Invalid payment amount"); + const debs = calculateDepts( + amount, + participants.map((u) => u.id!), + payments + ); + + createExpense.mutate({ expense, debs }); } + + React.useEffect(() => { + addParticipant(session.user); + setPayments([{ amount, userId: session.user.id }]); + }, []); + + const handleAmountChange = (value: number) => { + setAmount(value); + const firstUserId = payments[0]?.userId ?? session.user.id; + setPayments([{ amount: value, userId: firstUserId }]); + recalculateSplits(); + }; + return (
)} - ( - - Amount -
- - -
- -
- )} - /> + + + ( - Description + Description