diff --git a/package.json b/package.json
index fce81ea..4c07b5e 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,8 @@
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-select": "^2.1.6",
+ "@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.3",
"@t3-oss/env-nextjs": "^0.12.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ce78df9..fd07740 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,12 @@ importers:
'@radix-ui/react-popover':
specifier: ^1.1.6
version: 1.1.6(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-select':
+ specifier: ^2.1.6
+ version: 2.1.6(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-separator':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.1.2
version: 1.1.2(@types/react@19.1.0)(react@19.1.0)
@@ -705,6 +711,9 @@ packages:
'@petamoriken/float16@3.9.2':
resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==}
+ '@radix-ui/number@1.1.0':
+ resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
+
'@radix-ui/primitive@1.1.1':
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
@@ -948,6 +957,32 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-select@2.1.6':
+ resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.2':
+ resolution: {integrity: sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-slot@1.1.2':
resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
peerDependencies:
@@ -1006,6 +1041,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-previous@1.1.0':
+ resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-use-rect@1.1.0':
resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
peerDependencies:
@@ -1024,6 +1068,19 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-visually-hidden@1.1.2':
+ resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
@@ -2085,6 +2142,8 @@ snapshots:
'@petamoriken/float16@3.9.2': {}
+ '@radix-ui/number@1.1.0': {}
+
'@radix-ui/primitive@1.1.1': {}
'@radix-ui/react-arrow@1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
@@ -2334,6 +2393,44 @@ snapshots:
'@types/react': 19.1.0
'@types/react-dom': 19.1.1(@types/react@19.1.0)
+ '@radix-ui/react-select@2.1.6(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.4(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.2(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@19.1.0)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.0)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.0
+ '@types/react-dom': 19.1.1(@types/react@19.1.0)
+
+ '@radix-ui/react-separator@1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.0
+ '@types/react-dom': 19.1.1(@types/react@19.1.0)
+
'@radix-ui/react-slot@1.1.2(@types/react@19.1.0)(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.0)(react@19.1.0)
@@ -2383,6 +2480,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.0
+ '@radix-ui/react-use-previous@1.1.0(@types/react@19.1.0)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.0
+
'@radix-ui/react-use-rect@1.1.0(@types/react@19.1.0)(react@19.1.0)':
dependencies:
'@radix-ui/rect': 1.1.0
@@ -2397,6 +2500,15 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.0
+ '@radix-ui/react-visually-hidden@1.1.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.0
+ '@types/react-dom': 19.1.1(@types/react@19.1.0)
+
'@radix-ui/rect@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
diff --git a/src/app.config.ts b/src/app.config.ts
index 74e4280..0ed1e52 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -9,27 +9,27 @@ export const appConfig: AppConfig = {
{
name: "Home",
href: "/",
- icon: "Home",
+ icon: "home",
},
{
name: "Expenses",
href: "/expense",
- icon: "Wallet",
+ icon: "wallet",
},
{
- name: "Add Expense",
+ name: "Add",
href: "/add",
- icon: "Plus",
+ icon: "addSquare",
},
{
name: "Groups",
href: "/group",
- icon: "Users",
+ icon: "group",
},
{
name: "Friends",
href: "/friend",
- icon: "User",
+ icon: "friends",
},
],
};
diff --git a/src/app/(router)/add/page.tsx b/src/app/(router)/add/page.tsx
index af36544..4a557c6 100644
--- a/src/app/(router)/add/page.tsx
+++ b/src/app/(router)/add/page.tsx
@@ -1,6 +1,7 @@
import ExpenseForm from "@/app/_components/expense/expense-form";
import Header from "@/components/header";
import Section from "@/components/section";
+import { Button } from "@/components/ui/button";
import { auth } from "@/server/auth";
import { api, HydrateClient } from "@/trpc/server";
import React from "react";
@@ -10,9 +11,13 @@ export default async function Page() {
if (session?.user) void api.friend.getAll.prefetch();
return (
-
+
);
diff --git a/src/app/(router)/expense/page.tsx b/src/app/(router)/expense/page.tsx
index 2394fdf..8445150 100644
--- a/src/app/(router)/expense/page.tsx
+++ b/src/app/(router)/expense/page.tsx
@@ -1,5 +1,16 @@
+import Header from "@/components/header";
+import { Button } from "@/components/ui/button";
+import Link from "next/link";
import React from "react";
export default function Page() {
- return
Page
;
+ return (
+ <>
+
+ >
+ );
}
diff --git a/src/app/(router)/friend/page.tsx b/src/app/(router)/friend/page.tsx
index 9a84e5a..37acb6c 100644
--- a/src/app/(router)/friend/page.tsx
+++ b/src/app/(router)/friend/page.tsx
@@ -1,48 +1,18 @@
import AddFriendDrawer from "@/app/_components/friend/add-friend-drawer";
-import FriendCard from "@/app/_components/friend/friend-card";
-import PendingRequestButton from "@/app/_components/friend/pending-request-button";
+import FriendList from "@/app/_components/friend/friend-list";
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 { api, HydrateClient } from "@/trpc/server";
import React from "react";
export default async function Page() {
- const friendResult = await api.friend.getAll();
- const pendingFriends = friendResult.filter((f) => f.status === "pending");
- const friends = friendResult.filter((f) => f.status === "accepted");
+ void api.friend.getAll.prefetch();
+
return (
- <>
+
- {pendingFriends.length ? (
-
- {pendingFriends.map(({ user, requestedBy, id }) => (
-
-
-
- ))}
-
- ) : null}
-
-
- {friends?.length ? (
- friends.map(({ user }) => (
-
-
-
- ))
- ) : (
- No friends yet
- )}
-
- >
+
+
);
}
diff --git a/src/app/(router)/group/page.tsx b/src/app/(router)/group/page.tsx
index 2394fdf..04330e3 100644
--- a/src/app/(router)/group/page.tsx
+++ b/src/app/(router)/group/page.tsx
@@ -1,5 +1,13 @@
+import CreateGroupDrawer from "@/app/_components/group/create-group-drawer";
+import Header from "@/components/header";
import React from "react";
export default function Page() {
- return Page
;
+ return (
+ <>
+
+ >
+ );
}
diff --git a/src/app/(router)/layout.tsx b/src/app/(router)/layout.tsx
index 282b01f..cab1dec 100644
--- a/src/app/(router)/layout.tsx
+++ b/src/app/(router)/layout.tsx
@@ -11,8 +11,10 @@ export default async function Layout({
const session = await auth();
if (!session?.user) return redirect("/api/auth/signin");
return (
-
-
{children}
+
+
+ {children}
+
);
diff --git a/src/app/(router)/page.tsx b/src/app/(router)/page.tsx
index ecccc87..f2f25bf 100644
--- a/src/app/(router)/page.tsx
+++ b/src/app/(router)/page.tsx
@@ -1,13 +1,18 @@
import { appConfig } from "@/app.config";
import Header from "@/components/header";
-import { ModeToggle } from "@/components/mode-toggle";
+import Section from "@/components/section";
+import { auth } from "@/server/auth";
+import UserDropdown from "../_components/user-dropdown";
export default async function Home() {
+ const session = await auth();
+
return (
<>
+
>
);
}
diff --git a/src/app/_components/expense/expense-form.tsx b/src/app/_components/expense/expense-form.tsx
index 7797d82..b2ec971 100644
--- a/src/app/_components/expense/expense-form.tsx
+++ b/src/app/_components/expense/expense-form.tsx
@@ -15,33 +15,92 @@ import {
} from "@/components/ui/form";
import { expenseSchema } from "@/lib/validations/expense";
import { Textarea } from "@/components/ui/textarea";
-import { MoneyInput } from "@/components/number-input";
-import FriendSelect from "../friend/friend-select";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-function ExpenseForm({ initialExpense }: { initialExpense?: Expense }) {
+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";
+
+function ExpenseForm({
+ initialExpense,
+ session,
+ hideSubmit,
+}: {
+ initialExpense?: Expense;
+ session: Session;
+ hideSubmit?: boolean;
+}) {
+ const createExpense = api.expense.create.useMutation();
const form = useForm
>({
resolver: zodResolver(expenseSchema),
defaultValues: {
description: initialExpense?.description ?? "",
amount: initialExpense?.amount ?? 0,
- groupId: initialExpense?.groupId ?? "",
},
});
+ const amount = form.watch("amount");
+ const expenseSplitHook = useExpenseSplit(amount, session);
function onSubmit(values: z.infer) {
- console.log(values);
+ if (expenseSplitHook.participants.length <= 1)
+ return toast.error("Please add at least 2 participants");
+ if (expenseSplitHook.splitType !== "Equal")
+ return toast.error("You can only split equal for now.");
+ toast.success("Expense created!");
}
-
return (
);
diff --git a/src/app/_components/expense/expense-split.tsx b/src/app/_components/expense/expense-split.tsx
new file mode 100644
index 0000000..0df1d85
--- /dev/null
+++ b/src/app/_components/expense/expense-split.tsx
@@ -0,0 +1,148 @@
+"use client";
+import React from "react";
+import FriendSelect from "../friend/friend-select";
+import { Button } from "@/components/ui/button";
+
+import type { Session } from "next-auth";
+import FriendCard from "../friend/friend-card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Icons } from "@/components/icons";
+import {
+ splitTypeKeys,
+ type ExpenseSplitHook,
+ type SplitType,
+} from "@/hooks/use-expense-split";
+import PaidByInput from "./paid-by";
+import { Separator } from "@radix-ui/react-select";
+
+export default function ExpenseSplit({
+ expenseSplitHook,
+ session,
+}: {
+ expenseSplitHook: ExpenseSplitHook;
+ session: Session;
+}) {
+ const {
+ friends,
+ splitType,
+ setSplitType,
+ friendTarget,
+ setFriendTarget,
+ addParticipant,
+ removeParticipant,
+ participants,
+ splits,
+ } = expenseSplitHook;
+ return (
+
+
+
+
+
+ user.id!)}
+ />
+
+
+
Owed to
+
+ {participants.map((user) => (
+ -
+
+ {session.user.id !== user.id ? (
+
+ ) : null}
+
+
+
+ ))}
+
+
Paid from
+
+ {participants.map((user) => (
+ -
+
+ {session.user.id !== user.id ? (
+
+ ) : null}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/_components/expense/paid-by-custom-split.tsx b/src/app/_components/expense/paid-by-custom-split.tsx
new file mode 100644
index 0000000..4936ced
--- /dev/null
+++ b/src/app/_components/expense/paid-by-custom-split.tsx
@@ -0,0 +1,86 @@
+"use client";
+import type { Payment } from "@/lib/utils/expense";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { DialogClose } from "@radix-ui/react-dialog";
+import type { PublicUser } from "next-auth";
+import Avatar from "@/components/avatar";
+import React from "react";
+import { Input } from "@/components/ui/input";
+import { NumberInput } from "@/components/number-input";
+import { EuroIcon } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+function PaidByCustomSplit({
+ users,
+ payments,
+}: {
+ users: Array;
+ payments: Array;
+}) {
+ const [customAmounts, setCustomAmounts] = React.useState<
+ Record
+ >({});
+ const error = Object.values(customAmounts).reduce(
+ (acc, curr) => acc + curr,
+ 0
+ );
+ console.log(error);
+
+ return (
+
+ );
+}
+
+export default PaidByCustomSplit;
diff --git a/src/app/_components/expense/paid-by.tsx b/src/app/_components/expense/paid-by.tsx
new file mode 100644
index 0000000..9884583
--- /dev/null
+++ b/src/app/_components/expense/paid-by.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { DialogClose } from "@radix-ui/react-dialog";
+import type { PublicUser } from "next-auth";
+import Avatar from "@/components/avatar";
+import type { ExpenseSplitHook } from "@/hooks/use-expense-split";
+import { Icons } from "@/components/icons";
+import { cn } from "@/lib/utils";
+
+import PaidByCustomSplit from "./paid-by-custom-split";
+
+/* saved result is an array of objects with user and amount */
+export default function PaidByInput({
+ sessionUser,
+ expenseSplitHook,
+}: {
+ sessionUser: PublicUser;
+ expenseSplitHook: ExpenseSplitHook;
+}) {
+ const { amount, payments, setPayments, participants } = expenseSplitHook;
+
+ return (
+
+ );
+}
diff --git a/src/app/_components/expense/split-expense.tsx b/src/app/_components/expense/split-expense.tsx
deleted file mode 100644
index 2bd780b..0000000
--- a/src/app/_components/expense/split-expense.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-function SplitExpense() {
- return SplitExpense
;
-}
-
-export default SplitExpense;
diff --git a/src/app/_components/friend/add-friend-drawer.tsx b/src/app/_components/friend/add-friend-drawer.tsx
index 478a04e..6dd94b9 100644
--- a/src/app/_components/friend/add-friend-drawer.tsx
+++ b/src/app/_components/friend/add-friend-drawer.tsx
@@ -45,6 +45,7 @@ const SearchResultCard = ({
};
export default function AddFriendDrawer() {
+ const utils = api.useUtils();
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState("");
const [searchValue] = useDebounce(value, 1000);
@@ -65,7 +66,8 @@ export default function AddFriendDrawer() {
onSuccess() {
toast.success("Friend request sent!");
setOpen(false);
- refetch();
+ setValue("");
+ utils.friend.getAll.invalidate();
},
});
const handleAddFriend = (userId: string) => {
@@ -88,6 +90,7 @@ export default function AddFriendDrawer() {
autoFocus
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
+ placeholder="Search for a friend"
/>
{isFetching ? (
diff --git a/src/app/_components/friend/friend-card.tsx b/src/app/_components/friend/friend-card.tsx
index 03ddb1c..b04b475 100644
--- a/src/app/_components/friend/friend-card.tsx
+++ b/src/app/_components/friend/friend-card.tsx
@@ -1,16 +1,24 @@
import Avatar from "@/components/avatar";
+import { cn } from "@/lib/utils";
import type { PublicUser } from "next-auth";
import React from "react";
export default function FriendCard({
user: { name, image },
children,
+ className,
}: {
user: PublicUser;
children?: React.ReactNode;
+ className?: string;
}) {
return (
-
+
{name}
{children}
diff --git a/src/app/_components/friend/friend-list.tsx b/src/app/_components/friend/friend-list.tsx
new file mode 100644
index 0000000..184d8f3
--- /dev/null
+++ b/src/app/_components/friend/friend-list.tsx
@@ -0,0 +1,50 @@
+"use client";
+import React from "react";
+import FriendCard from "@/app/_components/friend/friend-card";
+import PendingRequestButton from "@/app/_components/friend/pending-request-button";
+import Section from "@/components/section";
+import { Button } from "@/components/ui/button";
+import Link from "next/link";
+import { api } from "@/trpc/react";
+
+function FriendList() {
+ const [friendResult] = api.friend.getAll.useSuspenseQuery();
+ const pendingFriends = friendResult.filter((f) => f.status === "pending");
+ const friends = friendResult.filter((f) => f.status === "accepted");
+ return (
+ <>
+ {pendingFriends.length ? (
+
+ {pendingFriends.map(({ user, requestedBy, id }) => (
+
+
+
+ ))}
+
+ ) : null}
+
+ {friends?.length ? (
+ friends.map(({ user }) => (
+
+
+
+ ))
+ ) : (
+ No friends yet
+ )}
+
+ >
+ );
+}
+
+export default FriendList;
diff --git a/src/app/_components/friend/friend-select.tsx b/src/app/_components/friend/friend-select.tsx
index 6b17792..e1173ea 100644
--- a/src/app/_components/friend/friend-select.tsx
+++ b/src/app/_components/friend/friend-select.tsx
@@ -1,6 +1,7 @@
"use client";
import Avatar from "@/components/avatar";
import { Combobox } from "@/components/combobox";
+import { Icons } from "@/components/icons";
import { api } from "@/trpc/react";
import React from "react";
@@ -8,22 +9,35 @@ import React from "react";
export default function FriendSelect({
onSelect,
initialValue,
+ className,
+ excludeIds,
}: {
onSelect: (value?: string) => void;
initialValue?: string;
+ className?: string;
+ excludeIds?: Array
;
}) {
const [friends] = api.friend.getAll.useSuspenseQuery();
+ const data = friends
+ .filter((friend) => !excludeIds?.includes(friend.user.id))
+ .map(({ user }) => ({
+ label: user.name!,
+ value: user.id,
+ children: ,
+ }));
return (
({
- label: user.name!,
- value: user.id,
- children: ,
- }))}
+ className={className}
+ data={data}
onSelect={onSelect}
initialValue={initialValue}
+ messageUi={{
+ select: "Select Friend",
+
+ empty: "No friends found",
+ placeholder: "Search friends",
+ }}
/>
);
}
diff --git a/src/app/_components/friend/pending-request-button.tsx b/src/app/_components/friend/pending-request-button.tsx
index b886526..3a61fc0 100644
--- a/src/app/_components/friend/pending-request-button.tsx
+++ b/src/app/_components/friend/pending-request-button.tsx
@@ -11,20 +11,23 @@ export default function PendingRequestButton({
friendshipId: string;
requestedBy: string;
}) {
+ const utils = api.useUtils();
+ const handleSuccess = () => {
+ toast.success(
+ `Friend request ${requestedBy === "them" ? "accepted" : "cancelled"}`
+ );
+ utils.friend.getAll.invalidate();
+ };
const acceptRequest = api.friend.accept.useMutation({
- onSuccess() {
- toast.success("Friend request accepted!");
- },
+ onSuccess: handleSuccess,
});
const cancleRequest = api.friend.cancel.useMutation({
- onSuccess() {
- toast.success("Friend request cancelled!");
- },
+ onSuccess: handleSuccess,
});
return (