this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(group): full management

+115 -27
+1 -1
src/app/layout.tsx
··· 18 18 return ( 19 19 <html 20 20 lang="en" 21 - className={`${figtree.variable} max-w-md mx-auto`} 21 + className={`${figtree.variable} max-w-lg mx-auto`} 22 22 suppressHydrationWarning 23 23 > 24 24 <body>
+88 -23
src/app/page.tsx
··· 1 1 "use client"; 2 2 3 + import { Delete01Icon } from "@hugeicons/core-free-icons"; 4 + import { HugeiconsIcon } from "@hugeicons/react"; 3 5 import Link from "next/link"; 4 6 import { useEffect, useState } from "react"; 5 7 import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; ··· 12 14 CardHeader, 13 15 CardTitle, 14 16 } from "@/components/ui/card"; 15 - import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; 17 + import { 18 + Dialog, 19 + DialogClose, 20 + DialogContent, 21 + DialogTrigger, 22 + } from "@/components/ui/dialog"; 16 23 import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; 17 24 import { Input } from "@/components/ui/input"; 25 + import type { group } from "@/db/schema"; 18 26 import { authClient } from "@/lib/auth-client"; 19 - import { createGroup, getGroups } from "@/lib/group"; 27 + import { createGroup, deleteGroup, getGroups, updateGroup } from "@/lib/group"; 20 28 import { getInitials } from "@/lib/utils"; 21 29 22 - type Group = { 23 - id: number; 24 - userId: string; 25 - name: string; 26 - }; 30 + export type Group = typeof group.$inferSelect; 27 31 28 32 export default function Home() { 29 33 const { data: session } = authClient.useSession(); 30 34 const user = session?.user; 31 35 32 36 const [groups, setGroups] = useState<Group[]>([]); 33 - const [open, setOpen] = useState(false); 34 37 35 38 useEffect(() => { 36 39 const id = user?.id; ··· 46 49 <CardTitle>Vouch</CardTitle> 47 50 <CardDescription>Your shared promises</CardDescription> 48 51 <CardAction> 49 - <Avatar render={<Link href="/settings" />}> 52 + <Avatar render={<Link href="/settings" />} size="lg"> 50 53 <AvatarImage src={user.image as string} /> 51 54 <AvatarFallback>{getInitials(user.name)}</AvatarFallback> 52 55 </Avatar> ··· 54 57 </CardHeader> 55 58 <CardContent className="grid gap-4"> 56 59 {groups.map((group) => ( 57 - <Link key={group.id} href={`/${group.id}`}> 58 - <Card> 59 - <CardHeader> 60 + <Card key={group.id}> 61 + <CardHeader> 62 + <Link href={`/${group.id}`}> 60 63 <CardTitle>{group.name}</CardTitle> 61 64 {/* <CardDescription> 62 - {group.stats.members} members, {group.stats.total} vouches 63 - </CardDescription> */} 64 - {group.userId === user.id && <CardAction>Host</CardAction>} 65 - </CardHeader> 66 - </Card> 67 - </Link> 65 + {group.stats.members} members, {group.stats.total} vouches 66 + </CardDescription> */} 67 + </Link> 68 + <CardAction> 69 + {group.userId === user.id && ( 70 + <Dialog> 71 + <DialogTrigger render={<Button>Edit</Button>} /> 72 + <DialogContent> 73 + <form 74 + onSubmit={(e) => { 75 + e.preventDefault(); 76 + const formData = new FormData(e.currentTarget); 77 + const name = formData.get("name"); 78 + updateGroup(group.id, name as string).then((res) => { 79 + if (!res) return; 80 + setGroups((prevGroups) => 81 + prevGroups.map((g) => 82 + g.id === group.id ? res : g, 83 + ), 84 + ); 85 + }); 86 + }} 87 + > 88 + <FieldGroup> 89 + <Field> 90 + <FieldLabel htmlFor="name">Name</FieldLabel> 91 + <Input 92 + id="name" 93 + name="name" 94 + defaultValue={group.name} 95 + required 96 + /> 97 + </Field> 98 + <div className="flex justify-end gap-2"> 99 + <Field className="flex-1"> 100 + <DialogClose 101 + render={ 102 + <Button 103 + variant="destructive" 104 + onClick={() => { 105 + deleteGroup(group.id).then((res) => { 106 + if (!res) return; 107 + setGroups((prevGroups) => 108 + prevGroups.filter( 109 + (g) => g.id !== group.id, 110 + ), 111 + ); 112 + }); 113 + }} 114 + > 115 + <HugeiconsIcon icon={Delete01Icon} /> 116 + </Button> 117 + } 118 + /> 119 + </Field> 120 + <Field> 121 + <DialogClose 122 + render={<Button type="submit">Update</Button>} 123 + /> 124 + </Field> 125 + </div> 126 + </FieldGroup> 127 + </form> 128 + </DialogContent> 129 + </Dialog> 130 + )} 131 + </CardAction> 132 + </CardHeader> 133 + </Card> 68 134 ))} 69 - <Dialog open={open} onOpenChange={setOpen}> 135 + <Dialog> 70 136 <DialogTrigger render={<Button>Create group</Button>} /> 71 137 <DialogContent> 72 138 <form ··· 75 141 const formData = new FormData(e.currentTarget); 76 142 const name = formData.get("name"); 77 143 createGroup(name as string).then((res) => { 78 - if (!res?.length) return; 79 - setGroups((prevGroups) => [...prevGroups, ...res]); 80 - setOpen(false); 144 + if (!res) return; 145 + setGroups((prevGroups) => [...prevGroups, res]); 81 146 }); 82 147 }} 83 148 > ··· 92 157 /> 93 158 </Field> 94 159 <Field> 95 - <Button type="submit">Create</Button> 160 + <DialogClose render={<Button type="submit">Create</Button>} /> 96 161 </Field> 97 162 </FieldGroup> 98 163 </form>
+3 -1
src/lib/db.ts
··· 1 + import { neon } from "@neondatabase/serverless"; 1 2 import { drizzle } from "drizzle-orm/neon-http"; 2 3 3 4 import * as schema from "@/db/schema"; ··· 5 6 const connection = new URL(process.env.DATABASE_URL!); 6 7 connection.pathname = "vouch"; 7 8 8 - export const db = drizzle(connection.toString(), { schema }); 9 + const sql = neon(connection.toString()); 10 + export const db = drizzle({ client: sql, schema });
+23 -2
src/lib/group.ts
··· 1 1 "use server"; 2 2 3 - import { eq } from "drizzle-orm"; 3 + import { and, eq } from "drizzle-orm"; 4 4 5 5 import { group } from "@/db/schema"; 6 6 import { getUserId } from "@/lib/auth"; ··· 15 15 export async function createGroup(name: string) { 16 16 const userId = await getUserId(); 17 17 if (!userId) return null; 18 - return await db.insert(group).values({ name, userId }).returning(); 18 + const [res] = await db.insert(group).values({ name, userId }).returning(); 19 + return res; 20 + } 21 + 22 + export async function deleteGroup(id: number) { 23 + const userId = await getUserId(); 24 + if (!userId) return false; 25 + const res = await db 26 + .delete(group) 27 + .where(and(eq(group.id, id), eq(group.userId, userId))); 28 + return res.rowCount > 0; 29 + } 30 + 31 + export async function updateGroup(id: number, name: string) { 32 + const userId = await getUserId(); 33 + if (!userId) return null; 34 + const [res] = await db 35 + .update(group) 36 + .set({ name }) 37 + .where(and(eq(group.id, id), eq(group.userId, userId))) 38 + .returning(); 39 + return res; 19 40 }