started add Group to expense ui; ui improvments
This commit is contained in:
parent
3fc08a3c10
commit
3f4cc126ec
@ -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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<Header text={appConfig.name}>
|
||||
<UserButton />
|
||||
{/* <UserDropdown user={session?.user!} /> */}
|
||||
<Header
|
||||
text={
|
||||
<>
|
||||
<Icons.logo
|
||||
className="size-32 scale-125 mr-2 absolute -left-4 -top-1/2 text-muted-foreground/5 -z-10
|
||||
mask-r-from-30%
|
||||
"
|
||||
/>
|
||||
<span>{appConfig.name}</span>
|
||||
<ModeToggle className="ml-4" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex gap-6 items-center ">
|
||||
<UserButton />
|
||||
</div>
|
||||
</Header>
|
||||
<Section>
|
||||
<ModeToggle />
|
||||
</Section>
|
||||
<Section></Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<ExpenseParticipants sessionUserId={sessionUser.id} />
|
||||
<Separator />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
@ -168,6 +164,9 @@ function ExpenseForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ExpenseParticipants sessionUserId={sessionUser.id} />
|
||||
<Separator />
|
||||
<ExpenseSplit sessionUser={sessionUser} />
|
||||
|
||||
{/* <Tabs defaultValue="expense" className="size-full space-y-4">
|
||||
|
||||
@ -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 (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className=" flex gap-2 w-full items-center flex-wrap">
|
||||
<h5 className="w-18">You and</h5>
|
||||
|
||||
<FriendSelect
|
||||
excludeIds={excludeIds}
|
||||
onSelect={(userId) => {
|
||||
addParticipant(
|
||||
friends.find((friend) => friend.user.id === userId)!.user
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{participants.map((user) => (
|
||||
<ul key={user.id}>
|
||||
<UserBadge user={user}>
|
||||
{sessionUserId !== user.id && (
|
||||
<Button
|
||||
className=" rounded-full aspect-square size-6"
|
||||
variant="destructive"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
removeParticipant(user.id!);
|
||||
}}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</UserBadge>
|
||||
</ul>
|
||||
))}
|
||||
</div>
|
||||
{participants.map((user) => (
|
||||
<ul key={user.id} className="flex gap-2 w-full items-center flex-wrap">
|
||||
<UserBadge user={user}>
|
||||
{sessionUserId !== user.id && (
|
||||
<Button
|
||||
className=" rounded-full aspect-square size-6"
|
||||
variant="destructive"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
removeParticipant(user.id!);
|
||||
}}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</UserBadge>
|
||||
</ul>
|
||||
))}
|
||||
<AddParticipants
|
||||
excludeIds={excludeIds}
|
||||
onAdd={(users) => {
|
||||
// addParticipant(
|
||||
// friends.find((friend) => friend.user.id === userId)!.user
|
||||
// );
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
42
src/app/_components/expense/select-participants.tsx
Normal file
42
src/app/_components/expense/select-participants.tsx
Normal file
@ -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<User>) => void;
|
||||
initialValue?: string;
|
||||
className?: string;
|
||||
excludeIds?: Array<string>;
|
||||
}) {
|
||||
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: <Avatar src={user.image} fb={user.name} className="size-6" />,
|
||||
}));
|
||||
|
||||
return (
|
||||
// Custom Combobox needed mby go with command dialog
|
||||
<Combobox
|
||||
className={"w-full "}
|
||||
data={friendsData}
|
||||
onSelect={(userId) => onAdd([])}
|
||||
initialValue={initialValue}
|
||||
messageUi={{
|
||||
select: "Add Friends or Groups",
|
||||
empty: "No friends or groups found",
|
||||
placeholder: "Search friends or groups",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -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",
|
||||
}}
|
||||
|
||||
@ -82,7 +82,7 @@ export default function AddToGroupDrawer({
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button size={"sm"} className={className}>
|
||||
<Button size={"sm"} className={className} variant={"outline"}>
|
||||
Add People
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
|
||||
@ -46,7 +46,7 @@ function GroupPage({ groupId }: { groupId: string }) {
|
||||
|
||||
<div className="flex gap-4 w-full items-center">
|
||||
<h4 className="text-lg font-semibold">Expenses</h4>
|
||||
<Button asChild size={"sm"} className="ml-auto">
|
||||
<Button asChild size={"sm"} className="ml-auto" variant={"outline"}>
|
||||
<Link href={`/add?groupId=${group.id}`}>
|
||||
<Icons.wallet />
|
||||
<span>Add Expense</span>
|
||||
|
||||
@ -5,13 +5,15 @@ export default function Header({
|
||||
children,
|
||||
glow = true,
|
||||
}: {
|
||||
text: string;
|
||||
text: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
glow?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full p-4 border-b flex justify-between items-center relative">
|
||||
<h2 className="font-black text-2xl uppercase">{text}</h2>
|
||||
<div className="w-full p-4 border-b flex justify-between items-center relative overflow-hidden h-16">
|
||||
<div className="font-black text-2xl uppercase flex items-center">
|
||||
{text}
|
||||
</div>
|
||||
{children}
|
||||
|
||||
{/* Glow effect */}
|
||||
|
||||
@ -14,6 +14,9 @@ type IconName =
|
||||
| "user"
|
||||
| "addSquare"
|
||||
| "check"
|
||||
| "sun"
|
||||
| "moon"
|
||||
| "display"
|
||||
| "loading";
|
||||
|
||||
export const Icons: Record<IconName, IconComponent> = {
|
||||
@ -285,6 +288,99 @@ export const Icons: Record<IconName, IconComponent> = {
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
sun(props) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_197_4386)">
|
||||
<path
|
||||
d="M12.75 1C12.75 0.585786 12.4142 0.25 12 0.25C11.5858 0.25 11.25 0.585786 11.25 1V3C11.25 3.41421 11.5858 3.75 12 3.75C12.4142 3.75 12.75 3.41421 12.75 3V1Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.75216 3.69149C4.45926 3.3986 3.98439 3.3986 3.6915 3.69149C3.3986 3.98439 3.3986 4.45926 3.6915 4.75216L5.10571 6.16637C5.3986 6.45926 5.87348 6.45926 6.16637 6.16637C6.45926 5.87348 6.45926 5.3986 6.16637 5.10571L4.75216 3.69149Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M20.3085 4.75216C20.6014 4.45926 20.6014 3.98439 20.3085 3.6915C20.0156 3.3986 19.5407 3.3986 19.2478 3.69149L17.8336 5.10571C17.5407 5.3986 17.5407 5.87348 17.8336 6.16637C18.1265 6.45926 18.6014 6.45926 18.8943 6.16637L20.3085 4.75216Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12 5.25C8.27208 5.25 5.25 8.27208 5.25 12C5.25 15.7279 8.27208 18.75 12 18.75C15.7279 18.75 18.75 15.7279 18.75 12C18.75 8.27208 15.7279 5.25 12 5.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M1 11.25C0.585786 11.25 0.25 11.5858 0.25 12C0.25 12.4142 0.585786 12.75 1 12.75H3C3.41421 12.75 3.75 12.4142 3.75 12C3.75 11.5858 3.41421 11.25 3 11.25H1Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M21 11.25C20.5858 11.25 20.25 11.5858 20.25 12C20.25 12.4142 20.5858 12.75 21 12.75H23C23.4142 12.75 23.75 12.4142 23.75 12C23.75 11.5858 23.4142 11.25 23 11.25H21Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M6.16637 18.8943C6.45926 18.6014 6.45926 18.1265 6.16637 17.8336C5.87348 17.5407 5.3986 17.5407 5.10571 17.8336L3.6915 19.2478C3.3986 19.5407 3.3986 20.0156 3.6915 20.3085C3.98439 20.6014 4.45926 20.6014 4.75216 20.3085L6.16637 18.8943Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M18.8943 17.8336C18.6014 17.5407 18.1265 17.5407 17.8336 17.8336C17.5407 18.1265 17.5407 18.6014 17.8336 18.8943L19.2478 20.3085C19.5407 20.6014 20.0156 20.6014 20.3085 20.3085C20.6014 20.0156 20.6014 19.5407 20.3085 19.2478L18.8943 17.8336Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12.75 21C12.75 20.5858 12.4142 20.25 12 20.25C11.5858 20.25 11.25 20.5858 11.25 21V23C11.25 23.4142 11.5858 23.75 12 23.75C12.4142 23.75 12.75 23.4142 12.75 23V21Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_197_4386">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
moon(props) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.9191 12.0134C11.051 8.77773 11.3963 5.88815 11.6711 4.0347L11.6997 3.84335C11.7573 3.46024 11.8126 3.09144 11.8314 2.7912C11.8425 2.61306 11.8442 2.41736 11.8136 2.22926C11.7836 2.04435 11.7113 1.79715 11.5173 1.58811C11.0765 1.11318 10.4574 1.24503 10.1231 1.35182C9.72572 1.47873 9.22917 1.7308 8.62667 2.07865C3.48503 5.04718 1.72337 11.6218 4.6919 16.7634C7.66043 21.9051 14.235 23.6667 19.3767 20.6982C19.9792 20.3503 20.4457 20.0464 20.7543 19.7657C21.014 19.5295 21.4377 19.0593 21.2468 18.4401C21.1628 18.1676 20.9848 17.9814 20.8397 17.8629C20.6921 17.7424 20.5218 17.646 20.362 17.5665C20.0926 17.4327 19.7455 17.2962 19.385 17.1545L19.2049 17.0836C17.4624 16.3949 14.7873 15.2491 12.9191 12.0134Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
display(props) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.0336 2.25C16.4053 2.25 17.4807 2.24999 18.3451 2.32061C19.2252 2.39252 19.9523 2.54138 20.6104 2.87671C21.6924 3.42798 22.572 4.30762 23.1233 5.38955C23.4586 6.04769 23.6075 6.77479 23.6794 7.65494C23.75 8.51924 23.75 9.59466 23.75 10.9663V11.0336C23.75 12.4052 23.75 13.4808 23.6794 14.3451C23.6075 15.2252 23.4586 15.9523 23.1233 16.6104C22.572 17.6924 21.6924 18.572 20.6104 19.1233C19.9523 19.4586 19.2252 19.6075 18.3451 19.6794C17.4808 19.75 16.4053 19.75 15.0337 19.75H12.75V21.584C13.3744 21.6276 13.9959 21.7259 14.6073 21.8787L16.1819 22.2724C16.5837 22.3729 16.8281 22.7801 16.7276 23.1819C16.6271 23.5837 16.2199 23.8281 15.8181 23.7276L14.2435 23.3339C13.507 23.1498 12.7535 23.0578 12 23.0578C11.2465 23.0578 10.493 23.1498 9.75655 23.3339L8.1819 23.7276C7.78006 23.8281 7.37285 23.5837 7.27239 23.1819C7.17193 22.7801 7.41625 22.3729 7.8181 22.2724L9.39274 21.8787C10.0041 21.7259 10.6256 21.6276 11.25 21.584V19.75H8.96637C7.59476 19.75 6.51924 19.75 5.65494 19.6794C4.77479 19.6075 4.04769 19.4586 3.38955 19.1233C2.30762 18.572 1.42798 17.6924 0.876713 16.6104C0.541379 15.9523 0.392522 15.2252 0.320612 14.3451C0.249992 13.4807 0.249996 12.4053 0.25 11.0336V10.9664C0.249996 9.59472 0.249992 8.51929 0.320612 7.65494C0.392522 6.7748 0.541379 6.04769 0.876713 5.38955C1.42798 4.30762 2.30762 3.42798 3.38955 2.87671C4.04769 2.54138 4.77479 2.39252 5.65494 2.32061C6.51929 2.24999 7.59472 2.25 8.96644 2.25H15.0336ZM11.25 16C11.25 16.4142 11.5858 16.75 12 16.75L18 16.75C18.4142 16.75 18.75 16.4142 18.75 16C18.75 15.5858 18.4142 15.25 18 15.25L12 15.25C11.5858 15.25 11.25 15.5858 11.25 16ZM8 16C8 16.5523 7.55229 17 7 17C6.44772 17 6 16.5523 6 16C6 15.4477 6.44772 15 7 15C7.55229 15 8 15.4477 8 16Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
|
||||
check(props) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@ -1,40 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Icons } from "./icons";
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme } = useTheme();
|
||||
const themeIcons = {
|
||||
light: Icons.sun,
|
||||
dark: Icons.moon,
|
||||
system: Icons.display,
|
||||
};
|
||||
|
||||
export function ModeToggle({ className }: { className?: string }) {
|
||||
const { setTheme, theme, themes } = useTheme();
|
||||
const currentThemeIndex = theme ? themes.indexOf(theme) : 0;
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"px-0 py-0 rounded-full size-max w-max justify-start bg-background/50 gap-1",
|
||||
className
|
||||
)}
|
||||
onClick={() =>
|
||||
setTheme(themes[(currentThemeIndex + 1) % themes.length] as any)
|
||||
}
|
||||
>
|
||||
{themes.map((selectTheme) => {
|
||||
const Icon = themeIcons[selectTheme as keyof typeof themeIcons];
|
||||
return (
|
||||
<div
|
||||
key={selectTheme}
|
||||
className={cn(
|
||||
"size-5 rounded-full flex items-center justify-center",
|
||||
selectTheme === theme && "bg-foreground text-background"
|
||||
)}
|
||||
>
|
||||
<Icon className="size-3.5 " />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<ExpenseSplit>) {
|
||||
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({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user