aboutsummaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/globals.css85
-rw-r--r--src/app/groups/[groupId]/edit/page.tsx36
-rw-r--r--src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx42
-rw-r--r--src/app/groups/[groupId]/expenses/create/page.tsx37
-rw-r--r--src/app/groups/[groupId]/page.tsx141
-rw-r--r--src/app/groups/[groupId]/save-recent-group.tsx18
-rw-r--r--src/app/groups/create/page.tsx29
-rw-r--r--src/app/groups/page.tsx14
-rw-r--r--src/app/groups/recent-group-list.tsx43
-rw-r--r--src/app/groups/recent-groups-helpers.ts30
-rw-r--r--src/app/layout.tsx7
-rw-r--r--src/app/page.tsx115
12 files changed, 467 insertions, 130 deletions
diff --git a/src/app/globals.css b/src/app/globals.css
index fd81e88..8abdb15 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,26 +2,75 @@
@tailwind components;
@tailwind utilities;
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
+@layer base {
:root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
}
}
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
}
diff --git a/src/app/groups/[groupId]/edit/page.tsx b/src/app/groups/[groupId]/edit/page.tsx
new file mode 100644
index 0000000..23bd865
--- /dev/null
+++ b/src/app/groups/[groupId]/edit/page.tsx
@@ -0,0 +1,36 @@
+import { GroupForm } from '@/components/group-form'
+import { Button } from '@/components/ui/button'
+import { getGroup, updateGroup } from '@/lib/api'
+import { groupFormSchema } from '@/lib/schemas'
+import { ChevronLeft } from 'lucide-react'
+import Link from 'next/link'
+import { notFound, redirect } from 'next/navigation'
+
+export default async function EditGroupPage({
+ params: { groupId },
+}: {
+ params: { groupId: string }
+}) {
+ const group = await getGroup(groupId)
+ if (!group) notFound()
+
+ async function updateGroupAction(values: unknown) {
+ 'use server'
+ const groupFormValues = groupFormSchema.parse(values)
+ const group = await updateGroup(groupId, groupFormValues)
+ redirect(`/groups/${group.id}`)
+ }
+
+ return (
+ <main>
+ <div className="mb-4">
+ <Button variant="ghost" asChild>
+ <Link href={`/groups/${groupId}`}>
+ <ChevronLeft className="w-4 h-4 mr-2" /> Back to group
+ </Link>
+ </Button>
+ </div>
+ <GroupForm group={group} onSubmit={updateGroupAction} />
+ </main>
+ )
+}
diff --git a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
new file mode 100644
index 0000000..f22ac08
--- /dev/null
+++ b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
@@ -0,0 +1,42 @@
+import { ExpenseForm } from '@/components/expense-form'
+import { Button } from '@/components/ui/button'
+import { getExpense, getGroup, updateExpense } from '@/lib/api'
+import { expenseFormSchema } from '@/lib/schemas'
+import { ChevronLeft } from 'lucide-react'
+import Link from 'next/link'
+import { notFound, redirect } from 'next/navigation'
+
+export default async function EditExpensePage({
+ params: { groupId, expenseId },
+}: {
+ params: { groupId: string; expenseId: string }
+}) {
+ const group = await getGroup(groupId)
+ if (!group) notFound()
+ const expense = await getExpense(groupId, expenseId)
+ if (!expense) notFound()
+
+ async function updateExpenseAction(values: unknown) {
+ 'use server'
+ const expenseFormValues = expenseFormSchema.parse(values)
+ await updateExpense(groupId, expenseId, expenseFormValues)
+ redirect(`/groups/${groupId}`)
+ }
+
+ return (
+ <main>
+ <div className="mb-4">
+ <Button variant="ghost" asChild>
+ <Link href={`/groups/${groupId}`}>
+ <ChevronLeft className="w-4 h-4 mr-2" /> Back to group
+ </Link>
+ </Button>
+ </div>
+ <ExpenseForm
+ group={group}
+ expense={expense}
+ onSubmit={updateExpenseAction}
+ />
+ </main>
+ )
+}
diff --git a/src/app/groups/[groupId]/expenses/create/page.tsx b/src/app/groups/[groupId]/expenses/create/page.tsx
new file mode 100644
index 0000000..f8d0ff9
--- /dev/null
+++ b/src/app/groups/[groupId]/expenses/create/page.tsx
@@ -0,0 +1,37 @@
+import { ExpenseForm } from '@/components/expense-form'
+import { Button } from '@/components/ui/button'
+import { createExpense, getGroup } from '@/lib/api'
+import { expenseFormSchema } from '@/lib/schemas'
+import { ChevronLeft } from 'lucide-react'
+import Link from 'next/link'
+import { notFound, redirect } from 'next/navigation'
+
+export default async function ExpensePage({
+ params: { groupId },
+}: {
+ params: { groupId: string }
+}) {
+ const group = await getGroup(groupId)
+ if (!group) notFound()
+
+ async function createExpenseAction(values: unknown) {
+ 'use server'
+ const expenseFormValues = expenseFormSchema.parse(values)
+ await createExpense(expenseFormValues, groupId)
+ redirect(`/groups/${groupId}`)
+ }
+
+ return (
+ <main>
+ <div className="mb-4">
+ <Button variant="ghost" asChild>
+ <Link href={`/groups/${groupId}`}>
+ <ChevronLeft className="w-4 h-4 mr-2" /> Back to group
+ </Link>
+ </Button>
+ </div>
+
+ <ExpenseForm group={group} onSubmit={createExpenseAction} />
+ </main>
+ )
+}
diff --git a/src/app/groups/[groupId]/page.tsx b/src/app/groups/[groupId]/page.tsx
new file mode 100644
index 0000000..1577d7a
--- /dev/null
+++ b/src/app/groups/[groupId]/page.tsx
@@ -0,0 +1,141 @@
+import { SaveGroupLocally } from '@/app/groups/[groupId]/save-recent-group'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table'
+import { getGroup, getGroupExpenses } from '@/lib/api'
+import { ChevronLeft, ChevronRight, Edit, Plus } from 'lucide-react'
+import Link from 'next/link'
+import { notFound } from 'next/navigation'
+
+export default async function GroupPage({
+ params: { groupId },
+}: {
+ params: { groupId: string }
+}) {
+ const group = await getGroup(groupId)
+ if (!group) notFound()
+
+ const expenses = await getGroupExpenses(groupId)
+
+ return (
+ <main>
+ <div className="mb-4 flex justify-between">
+ <Button variant="ghost" asChild>
+ <Link href="/groups">
+ <ChevronLeft className="w-4 h-4 mr-2" /> Back to recent groups
+ </Link>
+ </Button>
+ <Button variant="secondary" asChild>
+ <Link href={`/groups/${groupId}/edit`}>
+ <Edit className="w-4 h-4 mr-2" /> Edit group settings
+ </Link>
+ </Button>
+ </div>
+
+ <h1 className="font-bold mb-4">{group.name}</h1>
+
+ <Card className="mb-4">
+ <div className="flex flex-1">
+ <CardHeader className="flex-1">
+ <CardTitle>Expenses</CardTitle>
+ <CardDescription>
+ Here are the expenses that you created for your group.
+ </CardDescription>
+ </CardHeader>
+ <CardHeader>
+ <Button asChild size="icon">
+ <Link href={`/groups/${groupId}/expenses/create`}>
+ <Plus />
+ </Link>
+ </Button>
+ </CardHeader>
+ </div>
+ <CardContent className="p-0">
+ <Table className="">
+ <TableHeader>
+ <TableRow>
+ <TableHead>Title</TableHead>
+ <TableHead>Paid by</TableHead>
+ <TableHead>Paid for</TableHead>
+ <TableHead className="text-right">Amount</TableHead>
+ <TableHead className="w-0"></TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {expenses.map((expense) => (
+ <TableRow key={expense.id}>
+ <TableCell>{expense.title}</TableCell>
+ <TableCell>
+ <Badge variant="secondary">
+ {
+ group.participants.find(
+ (p) => p.id === expense.paidById,
+ )?.name
+ }
+ </Badge>
+ </TableCell>
+ <TableCell className="flex flex-wrap gap-1">
+ {expense.paidFor.map((paidFor, index) => (
+ <Badge variant="secondary" key={index}>
+ {
+ group.participants.find(
+ (p) => p.id === paidFor.participantId,
+ )?.name
+ }
+ </Badge>
+ ))}
+ </TableCell>
+ <TableCell className="text-right tabular-nums">
+ $ {expense.amount.toFixed(2)}
+ </TableCell>
+ <TableCell>
+ <Button
+ size="icon"
+ variant="link"
+ className="-my-2"
+ asChild
+ >
+ <Link
+ href={`/groups/${groupId}/expenses/${expense.id}/edit`}
+ >
+ <ChevronRight className="w-4 h-4" />
+ </Link>
+ </Button>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </CardContent>
+ </Card>
+
+ <Card className="mb-4">
+ <CardHeader>
+ <CardTitle>Participants</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <ul>
+ {group.participants.map((participant) => (
+ <li key={participant.id}>{participant.name}</li>
+ ))}
+ </ul>
+ </CardContent>
+ </Card>
+ <SaveGroupLocally group={{ id: group.id, name: group.name }} />
+ </main>
+ )
+}
diff --git a/src/app/groups/[groupId]/save-recent-group.tsx b/src/app/groups/[groupId]/save-recent-group.tsx
new file mode 100644
index 0000000..e55d7c8
--- /dev/null
+++ b/src/app/groups/[groupId]/save-recent-group.tsx
@@ -0,0 +1,18 @@
+'use client'
+import {
+ RecentGroup,
+ saveRecentGroup,
+} from '@/app/groups/recent-groups-helpers'
+import { useEffect } from 'react'
+
+type Props = {
+ group: RecentGroup
+}
+
+export function SaveGroupLocally({ group }: Props) {
+ useEffect(() => {
+ saveRecentGroup(group)
+ }, [])
+
+ return null
+}
diff --git a/src/app/groups/create/page.tsx b/src/app/groups/create/page.tsx
new file mode 100644
index 0000000..4f220d2
--- /dev/null
+++ b/src/app/groups/create/page.tsx
@@ -0,0 +1,29 @@
+import { GroupForm } from '@/components/group-form'
+import { Button } from '@/components/ui/button'
+import { createGroup } from '@/lib/api'
+import { groupFormSchema } from '@/lib/schemas'
+import { ChevronLeft } from 'lucide-react'
+import Link from 'next/link'
+import { redirect } from 'next/navigation'
+
+export default function CreateGroupPage() {
+ async function createGroupAction(values: unknown) {
+ 'use server'
+ const groupFormValues = groupFormSchema.parse(values)
+ const group = await createGroup(groupFormValues)
+ redirect(`/groups/${group.id}`)
+ }
+
+ return (
+ <main>
+ <div className="mb-4">
+ <Button variant="ghost" asChild>
+ <Link href="/groups">
+ <ChevronLeft className="w-4 h-4 mr-2" /> Back to recent groups
+ </Link>
+ </Button>
+ </div>
+ <GroupForm onSubmit={createGroupAction} />
+ </main>
+ )
+}
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx
new file mode 100644
index 0000000..7e4f484
--- /dev/null
+++ b/src/app/groups/page.tsx
@@ -0,0 +1,14 @@
+import { RecentGroupList } from '@/app/groups/recent-group-list'
+import { Button } from '@/components/ui/button'
+import Link from 'next/link'
+
+export default async function GroupsPage() {
+ return (
+ <main>
+ <Button asChild>
+ <Link href="/groups/create">New group</Link>
+ </Button>
+ <RecentGroupList />
+ </main>
+ )
+}
diff --git a/src/app/groups/recent-group-list.tsx b/src/app/groups/recent-group-list.tsx
new file mode 100644
index 0000000..e728193
--- /dev/null
+++ b/src/app/groups/recent-group-list.tsx
@@ -0,0 +1,43 @@
+'use client'
+import { getRecentGroups } from '@/app/groups/recent-groups-helpers'
+import { Button } from '@/components/ui/button'
+import Link from 'next/link'
+import { useEffect, useState } from 'react'
+import { z } from 'zod'
+
+const recentGroupsSchema = z.array(
+ z.object({
+ id: z.string().min(1),
+ name: z.string(),
+ }),
+)
+type RecentGroups = z.infer<typeof recentGroupsSchema>
+
+type State = { status: 'pending' } | { status: 'success'; groups: RecentGroups }
+
+export function RecentGroupList() {
+ const [state, setState] = useState<State>({ status: 'pending' })
+
+ useEffect(() => {
+ const groupsInStorage = getRecentGroups()
+ setState({ status: 'success', groups: groupsInStorage })
+ }, [])
+
+ return (
+ <ul className="flex flex-col gap-2 mt-2">
+ {state.status === 'pending' ? (
+ <li>
+ <em>Loading recent groups…</em>
+ </li>
+ ) : (
+ state.groups.map(({ id, name }) => (
+ <li key={id}>
+ <Button asChild variant="outline">
+ <Link href={`/groups/${id}`}>{name}</Link>
+ </Button>
+ </li>
+ ))
+ )}
+ </ul>
+ )
+}
diff --git a/src/app/groups/recent-groups-helpers.ts b/src/app/groups/recent-groups-helpers.ts
new file mode 100644
index 0000000..5d52141
--- /dev/null
+++ b/src/app/groups/recent-groups-helpers.ts
@@ -0,0 +1,30 @@
+import { z } from 'zod'
+
+export const recentGroupsSchema = z.array(
+ z.object({
+ id: z.string().min(1),
+ name: z.string(),
+ }),
+)
+
+export type RecentGroups = z.infer<typeof recentGroupsSchema>
+export type RecentGroup = RecentGroups[number]
+
+const STORAGE_KEY = 'recentGroups'
+
+export function getRecentGroups() {
+ const groupsInStorageJson = localStorage.getItem(STORAGE_KEY)
+ const groupsInStorageRaw = groupsInStorageJson
+ ? JSON.parse(groupsInStorageJson)
+ : []
+ const parseResult = recentGroupsSchema.safeParse(groupsInStorageRaw)
+ return parseResult.success ? parseResult.data : []
+}
+
+export function saveRecentGroup(group: RecentGroup) {
+ const recentGroups = getRecentGroups()
+ localStorage.setItem(
+ STORAGE_KEY,
+ JSON.stringify([group, ...recentGroups.filter((rg) => rg.id !== group.id)]),
+ )
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 40e027f..4a83b57 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,9 +1,6 @@
import type { Metadata } from 'next'
-import { Inter } from 'next/font/google'
import './globals.css'
-const inter = Inter({ subsets: ['latin'] })
-
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
@@ -16,7 +13,9 @@ export default function RootLayout({
}) {
return (
<html lang="en">
- <body className={inter.className}>{children}</body>
+ <body>
+ <div className="max-w-screen-md mx-auto p-4">{children}</div>
+ </body>
</html>
)
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e38c626..a94e2d5 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,113 +1,12 @@
-import Image from 'next/image'
+import { Button } from '@/components/ui/button'
+import Link from 'next/link'
-export default function Home() {
+export default function HomePage() {
return (
- <main className="flex min-h-screen flex-col items-center justify-between p-24">
- <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
- <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
- Get started by editing&nbsp;
- <code className="font-mono font-bold">src/app/page.tsx</code>
- </p>
- <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
- <a
- className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
- href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- By{' '}
- <Image
- src="/vercel.svg"
- alt="Vercel Logo"
- className="dark:invert"
- width={100}
- height={24}
- priority
- />
- </a>
- </div>
- </div>
-
- <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
- <Image
- className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
- src="/next.svg"
- alt="Next.js Logo"
- width={180}
- height={37}
- priority
- />
- </div>
-
- <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
- <a
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
- target="_blank"
- rel="noopener noreferrer"
- >
- <h2 className={`mb-3 text-2xl font-semibold`}>
- Docs{' '}
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
- -&gt;
- </span>
- </h2>
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
- Find in-depth information about Next.js features and API.
- </p>
- </a>
-
- <a
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
- target="_blank"
- rel="noopener noreferrer"
- >
- <h2 className={`mb-3 text-2xl font-semibold`}>
- Learn{' '}
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
- -&gt;
- </span>
- </h2>
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
- Learn about Next.js in an interactive course with&nbsp;quizzes!
- </p>
- </a>
-
- <a
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
- target="_blank"
- rel="noopener noreferrer"
- >
- <h2 className={`mb-3 text-2xl font-semibold`}>
- Templates{' '}
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
- -&gt;
- </span>
- </h2>
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
- Explore the Next.js 13 playground.
- </p>
- </a>
-
- <a
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
- target="_blank"
- rel="noopener noreferrer"
- >
- <h2 className={`mb-3 text-2xl font-semibold`}>
- Deploy{' '}
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
- -&gt;
- </span>
- </h2>
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
- Instantly deploy your Next.js site to a shareable URL with Vercel.
- </p>
- </a>
- </div>
+ <main>
+ <Button asChild variant="link">
+ <Link href="/groups">My groups</Link>
+ </Button>
</main>
)
}