From 3f4cc126eccb485ac0b6cab033a2425ea9cc3f5e Mon Sep 17 00:00:00 2001 From: mr-shortman Date: Sun, 13 Apr 2025 23:17:43 +0200 Subject: [PATCH] started add Group to expense ui; ui improvments --- README.md | 6 ++ src/app/(router)/page.tsx | 28 ++++-- src/app/_components/expense/expense-form.tsx | 7 +- .../expense/expense-participants.tsx | 60 ++++++------ .../expense/select-participants.tsx | 42 ++++++++ src/app/_components/friend/friend-select.tsx | 3 +- .../_components/group/add-to-group-drawer.tsx | 2 +- src/app/_components/group/group-page.tsx | 2 +- src/components/header.tsx | 8 +- src/components/icons.tsx | 96 +++++++++++++++++++ src/components/mode-toggle.tsx | 66 +++++++------ src/server/api/routers/expense.ts | 39 +++++++- 12 files changed, 275 insertions(+), 84 deletions(-) create mode 100644 src/app/_components/expense/select-participants.tsx diff --git a/README.md b/README.md index 67943c7..f44a7d5 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,9 @@ You can check out the [create-t3-app GitHub repository](https://github.com/t3-os ## How do I deploy this? Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. + +to test webhooks locally + +```bash + ngrok http --url=nkrok-url 3000 +``` diff --git a/src/app/(router)/page.tsx b/src/app/(router)/page.tsx index 5b40229..24f18a1 100644 --- a/src/app/(router)/page.tsx +++ b/src/app/(router)/page.tsx @@ -1,22 +1,32 @@ import { appConfig } from "@/app.config"; import Header from "@/components/header"; import Section from "@/components/section"; -import UserDropdown from "../_components/user-dropdown"; + import { ModeToggle } from "@/components/mode-toggle"; import { UserButton } from "@clerk/nextjs"; +import { Icons } from "@/components/icons"; export default async function Home() { - // const session = await auth(); - return ( <> -
- - {/* */} +
+ + {appConfig.name} + + + } + > +
+ +
-
- -
+
); } diff --git a/src/app/_components/expense/expense-form.tsx b/src/app/_components/expense/expense-form.tsx index 3ba1c49..6e52512 100644 --- a/src/app/_components/expense/expense-form.tsx +++ b/src/app/_components/expense/expense-form.tsx @@ -15,7 +15,6 @@ import { } from "@/components/ui/form"; import { expenseSchema } from "@/lib/validations/expense"; import { Textarea } from "@/components/ui/textarea"; - import { NumberInput } from "@/components/number-input"; import { EuroIcon } from "lucide-react"; import ExpenseSplit from "./expense-split"; @@ -118,9 +117,6 @@ function ExpenseForm({ )} - - - )} /> + + + {/* diff --git a/src/app/_components/expense/expense-participants.tsx b/src/app/_components/expense/expense-participants.tsx index 7f4dba5..3d4352a 100644 --- a/src/app/_components/expense/expense-participants.tsx +++ b/src/app/_components/expense/expense-participants.tsx @@ -1,3 +1,4 @@ +"use client"; import Avatar from "@/components/avatar"; import { Input } from "@/components/ui/input"; import { useExpenseStore } from "@/lib/store/expense-store"; @@ -7,6 +8,7 @@ import { api } from "@/trpc/react"; import { Button } from "@/components/ui/button"; import { XIcon } from "lucide-react"; import type { User } from "@/server/db/schema"; +import AddParticipants from "./select-participants"; export const UserBadge = ({ user, @@ -30,7 +32,6 @@ export default function ExpenseParticipants({ }: { sessionUserId: string; }) { - const [friends] = api.friend.getAll.useSuspenseQuery(); const participants = useExpenseStore((state) => state.participants); const addParticipant = useExpenseStore((state) => state.addParticipant); const removeParticipant = useExpenseStore((state) => state.removeParticipant); @@ -38,37 +39,32 @@ export default function ExpenseParticipants({ return (
-
-
You and
- - { - addParticipant( - friends.find((friend) => friend.user.id === userId)!.user - ); - }} - /> - - {participants.map((user) => ( -
    - - {sessionUserId !== user.id && ( - - )} - -
- ))} -
+ {participants.map((user) => ( +
    + + {sessionUserId !== user.id && ( + + )} + +
+ ))} + { + // addParticipant( + // friends.find((friend) => friend.user.id === userId)!.user + // ); + }} + />
); } diff --git a/src/app/_components/expense/select-participants.tsx b/src/app/_components/expense/select-participants.tsx new file mode 100644 index 0000000..a6813c2 --- /dev/null +++ b/src/app/_components/expense/select-participants.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { Combobox } from "@/components/combobox"; +import { api } from "@/trpc/react"; +import Avatar from "@/components/avatar"; +import type { User } from "@/server/db/schema"; + +export default function AddParticipants({ + onAdd, + initialValue, + className, + excludeIds, +}: { + onAdd: (users: Array) => void; + initialValue?: string; + className?: string; + excludeIds?: Array; +}) { + const [friends] = api.friend.getAll.useSuspenseQuery(); + + const friendsData = friends + .filter((friend) => !excludeIds?.includes(friend.user.id)) + .map(({ user }) => ({ + label: user.name!, + value: user.id, + children: , + })); + + return ( + // Custom Combobox needed mby go with command dialog + onAdd([])} + initialValue={initialValue} + messageUi={{ + select: "Add Friends or Groups", + empty: "No friends or groups found", + placeholder: "Search friends or groups", + }} + /> + ); +} diff --git a/src/app/_components/friend/friend-select.tsx b/src/app/_components/friend/friend-select.tsx index e1173ea..efcedaa 100644 --- a/src/app/_components/friend/friend-select.tsx +++ b/src/app/_components/friend/friend-select.tsx @@ -1,8 +1,8 @@ "use client"; import Avatar from "@/components/avatar"; import { Combobox } from "@/components/combobox"; -import { Icons } from "@/components/icons"; import { api } from "@/trpc/react"; +import { Icons } from "@/components/icons"; import React from "react"; @@ -34,7 +34,6 @@ export default function FriendSelect({ initialValue={initialValue} messageUi={{ select: "Select Friend", - empty: "No friends found", placeholder: "Search friends", }} diff --git a/src/app/_components/group/add-to-group-drawer.tsx b/src/app/_components/group/add-to-group-drawer.tsx index 3cbd898..de26afd 100644 --- a/src/app/_components/group/add-to-group-drawer.tsx +++ b/src/app/_components/group/add-to-group-drawer.tsx @@ -82,7 +82,7 @@ export default function AddToGroupDrawer({ return ( - diff --git a/src/app/_components/group/group-page.tsx b/src/app/_components/group/group-page.tsx index 6615035..b1c6f20 100644 --- a/src/app/_components/group/group-page.tsx +++ b/src/app/_components/group/group-page.tsx @@ -46,7 +46,7 @@ function GroupPage({ groupId }: { groupId: string }) {

Expenses

- - - - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - - - + ); } diff --git a/src/server/api/routers/expense.ts b/src/server/api/routers/expense.ts index c5c3d08..ea5bf8e 100644 --- a/src/server/api/routers/expense.ts +++ b/src/server/api/routers/expense.ts @@ -1,9 +1,14 @@ import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; -import { expenses, expenseSplits, type ExpenseSplit } from "@/server/db/schema"; +import { + expenses, + expenseSplits, + friendships, + type ExpenseSplit, +} from "@/server/db/schema"; import { expenseSchema, expenseSplitSchema } from "@/lib/validations/expense"; -import { eq, or } from "drizzle-orm"; +import { and, eq, inArray, not, notInArray, or } from "drizzle-orm"; function restructureExpenseSplits(expenseSplits: Array) { const expensesMap = new Map(); @@ -41,6 +46,36 @@ export const expenseRouter = createTRPCRouter({ const expenses = restructureExpenseSplits(splits); return expenses; }), + + searchParticipants: protectedProcedure + .input(z.object({ search: z.string(), excludedIds: z.array(z.string()) })) + .query(async ({ ctx, input }) => { + const userId = ctx.auth.userId; + const friendResult = await ctx.db.query.friendships.findMany({ + where: and( + or( + eq(friendships.userOneId, userId), + eq(friendships.userTwoId, userId) + ), + notInArray(friendships.userOneId, input.excludedIds), + notInArray(friendships.userTwoId, input.excludedIds) + ), + with: { + userOne: true, + userTwo: true, + }, + }); + const friends = + friendResult?.map(({ userOne, userTwo }) => + ctx.auth.userId === userOne.id ? userTwo : userOne + ) ?? []; + return { + friends, + groups: [], + }; + }), + + // mutations create: protectedProcedure .input( z.object({