diff --git a/src/app/(router)/group/[id]/page.tsx b/src/app/(router)/group/[id]/page.tsx new file mode 100644 index 0000000..e5ebb76 --- /dev/null +++ b/src/app/(router)/group/[id]/page.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import GroupPage from "@/app/_components/group/group-page"; +import { api, HydrateClient } from "@/trpc/server"; + +async function Page({ + params, +}: { + params: Promise<{ + id: string; + }>; +}) { + const { id } = await params; + void api.group.get.prefetch({ id }); + + return ( + + + + ); +} + +export default Page; diff --git a/src/app/(router)/group/page.tsx b/src/app/(router)/group/page.tsx index 04330e3..906abe0 100644 --- a/src/app/(router)/group/page.tsx +++ b/src/app/(router)/group/page.tsx @@ -1,13 +1,29 @@ -import CreateGroupDrawer from "@/app/_components/group/create-group-drawer"; -import Header from "@/components/header"; import React from "react"; +import GroupFormDrawer from "@/app/_components/group/group-form-drawer"; +import Header from "@/components/header"; +import Section from "@/components/section"; +import { api } from "@/trpc/server"; +import Link from "next/link"; +import GroupCard from "@/app/_components/group/group-card"; -export default function Page() { +export default async function Page() { + const groups = await api.group.getAll(); return ( <>
- +
+
+ + {groups.map((group) => ( +
  • + + + +
  • + ))} +
    +
    ); } diff --git a/src/app/_components/expense/expense-card.tsx b/src/app/_components/expense/expense-card.tsx index 46369b8..e2f7f9a 100644 --- a/src/app/_components/expense/expense-card.tsx +++ b/src/app/_components/expense/expense-card.tsx @@ -31,10 +31,12 @@ function ExpenseCard({ expense }: { expense: Expense }) { return ( - + -

    {expense.description}

    +

    + {expense.description} +

    {getAmount(Number(expense.amount))} diff --git a/src/app/_components/friend/add-friend-drawer.tsx b/src/app/_components/friend/add-friend-drawer.tsx index 6dd94b9..cd25f35 100644 --- a/src/app/_components/friend/add-friend-drawer.tsx +++ b/src/app/_components/friend/add-friend-drawer.tsx @@ -15,8 +15,8 @@ import { Input } from "@/components/ui/input"; import { useDebounce } from "use-debounce"; import { Skeleton } from "@/components/ui/skeleton"; import { toast } from "sonner"; -import type { PublicUser } from "next-auth"; -import FriendCard from "./friend-card"; +import UserCard from "../user-card"; +import type { User } from "@/server/db/schema"; const SearchResultCard = ({ addFriend, @@ -25,10 +25,10 @@ const SearchResultCard = ({ }: { addFriend: (userId: string) => void; pending: boolean; - user: PublicUser; + user: User; }) => { return ( - + - + ); }; @@ -50,11 +50,7 @@ export default function AddFriendDrawer() { const [value, setValue] = React.useState(""); const [searchValue] = useDebounce(value, 1000); - const { - data: searchResult, - isFetching, - refetch, - } = api.friend.search.useQuery( + const { data: searchResult, isFetching } = api.friend.search.useQuery( { search: searchValue, }, @@ -70,6 +66,7 @@ export default function AddFriendDrawer() { utils.friend.getAll.invalidate(); }, }); + const handleAddFriend = (userId: string) => { addFriend.mutate({ userId, diff --git a/src/app/_components/friend/friend-list.tsx b/src/app/_components/friend/friend-list.tsx index 184d8f3..093cdbf 100644 --- a/src/app/_components/friend/friend-list.tsx +++ b/src/app/_components/friend/friend-list.tsx @@ -1,6 +1,6 @@ "use client"; import React from "react"; -import FriendCard from "@/app/_components/friend/friend-card"; +import UserCard from "@/app/_components/user-card"; import PendingRequestButton from "@/app/_components/friend/pending-request-button"; import Section from "@/components/section"; import { Button } from "@/components/ui/button"; @@ -16,19 +16,19 @@ function FriendList() { {pendingFriends.length ? (
    {pendingFriends.map(({ user, requestedBy, id }) => ( - + - + ))}
    ) : null}
    {friends?.length ? ( friends.map(({ user }) => ( - + - + )) ) : (

    No friends yet

    diff --git a/src/app/_components/group/add-to-group-drawer.tsx b/src/app/_components/group/add-to-group-drawer.tsx new file mode 100644 index 0000000..3cbd898 --- /dev/null +++ b/src/app/_components/group/add-to-group-drawer.tsx @@ -0,0 +1,126 @@ +"use client"; +import React from "react"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { Button } from "@/components/ui/button"; +import { api } from "@/trpc/react"; +import { Input } from "@/components/ui/input"; +import { useDebounce } from "use-debounce"; +import { Skeleton } from "@/components/ui/skeleton"; +import { toast } from "sonner"; + +import type { User } from "@/server/db/schema"; +import UserCard from "../user-card"; + +const SearchResultCard = ({ + addUser, + user, +}: { + addUser: (userId: string) => void; + + user: User; +}) => { + return ( + + + + ); +}; + +export default function AddToGroupDrawer({ + groupId, + className, +}: { + groupId: string; + className?: string; +}) { + const utils = api.useUtils(); + const [open, setOpen] = React.useState(false); + const [value, setValue] = React.useState(""); + const [searchValue] = useDebounce(value, 1000); + + const { data: searchResult, isFetching } = api.user.search.useQuery( + { + search: searchValue, + }, + { + enabled: !!searchValue, + } + ); + const addFriend = api.group.addMember.useMutation({ + onSuccess() { + toast.success("User added to group."); + setOpen(false); + setValue(""); + utils.group.get.invalidate(); + utils.group.getAll.invalidate(); + }, + }); + const handleAddFriend = (userId: string) => { + addFriend.mutate({ + groupId, + userId, + }); + }; + + return ( + + + + + + + Add a user to group + +
    + setValue(e.currentTarget.value)} + placeholder="Search for a user" + /> +
      + {isFetching ? ( + + ) : searchResult?.length ? ( + searchResult.map((user) => ( +
    • + +
    • + )) + ) : ( +

      + {searchValue.length + ? "No results found" + : "Start typing to search for a user"} +

      + )} +
    +
    + + + + + +
    +
    + ); +} diff --git a/src/app/_components/group/group-card.tsx b/src/app/_components/group/group-card.tsx new file mode 100644 index 0000000..355873c --- /dev/null +++ b/src/app/_components/group/group-card.tsx @@ -0,0 +1,15 @@ +import { Card, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Group } from "@/server/db/schema"; +import React from "react"; + +function GroupCard({ group }: { group: Group }) { + return ( + + + {group.name} + + + ); +} + +export default GroupCard; diff --git a/src/app/_components/group/create-group-drawer.tsx b/src/app/_components/group/group-form-drawer.tsx similarity index 52% rename from src/app/_components/group/create-group-drawer.tsx rename to src/app/_components/group/group-form-drawer.tsx index 09d4362..84beb83 100644 --- a/src/app/_components/group/create-group-drawer.tsx +++ b/src/app/_components/group/group-form-drawer.tsx @@ -10,24 +10,28 @@ import { DrawerTrigger, } from "@/components/ui/drawer"; import { Button } from "@/components/ui/button"; +import GroupForm from "./group-form"; +import type { Group } from "@/server/db/schema"; -export default function CreateGroupDrawer() { +export default function GroupFormDrawer({ + initialGroup, +}: { + initialGroup?: Group; +}) { const [open, setOpen] = React.useState(false); + const existing = Boolean(initialGroup?.id?.length); return ( - + - Create Group + {existing ? "Update" : "Create"} Group + - - - - - - ); diff --git a/src/app/_components/group/group-form.tsx b/src/app/_components/group/group-form.tsx new file mode 100644 index 0000000..2a9aad7 --- /dev/null +++ b/src/app/_components/group/group-form.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; + +const formSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), +}); + +import React from "react"; +import { Input } from "@/components/ui/input"; +import { groupSchema } from "@/lib/validations/group"; +import { api } from "@/trpc/react"; +import { useRouter } from "next/navigation"; +import type { Group } from "@/server/db/schema"; +import { Textarea } from "@/components/ui/textarea"; + +function GroupForm({ initialGroup }: { initialGroup?: Group }) { + const router = useRouter(); + const utils = api.useUtils(); + const createGroup = api.group.create.useMutation({ + onSuccess(group) { + router.push(`/group/${group?.id}`); + utils.invalidate(); + }, + }); + const updateGroup = api.group.update.useMutation({ + onSuccess() { + utils.group.get.invalidate({ id: initialGroup?.id }); + }, + }); + const form = useForm>({ + resolver: zodResolver(groupSchema), + defaultValues: { + name: initialGroup?.name ?? "", + description: initialGroup?.description ?? "", + }, + }); + + const existing = Boolean(initialGroup?.id?.length); + + function onSubmit(group: z.infer) { + if (existing) { + updateGroup.mutate({ group, groupId: initialGroup?.id! }); + } else { + createGroup.mutate({ group }); + } + } + + return ( +
    + + ( + + + + + + + )} + /> + ( + + +