aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSebastien Castiel <sebastien@castiel.me>2023-12-07 16:44:01 -0500
committerSebastien Castiel <sebastien@castiel.me>2023-12-07 16:44:01 -0500
commit0b27f90fb76a2492c17a958b6dd807e6fbbf8030 (patch)
treebfc9c0e533eb28b43bcded9c53020dc15bca1955 /src
parent6611e3a187e5398f686449938b7cf1ddbfcb5392 (diff)
Titles and navigation
Diffstat (limited to 'src')
-rw-r--r--src/app/apple-icon.pngbin0 -> 19143 bytes
-rw-r--r--src/app/favicon.icobin25931 -> 15086 bytes
-rw-r--r--src/app/groups/[groupId]/balances/page.tsx5
-rw-r--r--src/app/groups/[groupId]/edit/page.tsx5
-rw-r--r--src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx5
-rw-r--r--src/app/groups/[groupId]/expenses/create/page.tsx5
-rw-r--r--src/app/groups/[groupId]/expenses/expense-list.tsx2
-rw-r--r--src/app/groups/[groupId]/expenses/page.tsx5
-rw-r--r--src/app/groups/[groupId]/layout.tsx34
-rw-r--r--src/app/groups/create/page.tsx16
-rw-r--r--src/app/groups/not-found.tsx15
-rw-r--r--src/app/groups/page.tsx8
-rw-r--r--src/app/icon.svg1
-rw-r--r--src/app/layout.tsx45
-rw-r--r--src/app/page.tsx11
-rw-r--r--src/components/group-form.tsx10
-rw-r--r--src/components/progress-bar.tsx2
-rw-r--r--src/lib/env.ts9
18 files changed, 133 insertions, 45 deletions
diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png
new file mode 100644
index 0000000..1083802
--- /dev/null
+++ b/src/app/apple-icon.png
Binary files differ
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fe..dd2aca1 100644
--- a/src/app/favicon.ico
+++ b/src/app/favicon.ico
Binary files differ
diff --git a/src/app/groups/[groupId]/balances/page.tsx b/src/app/groups/[groupId]/balances/page.tsx
index ab2c582..31dbf63 100644
--- a/src/app/groups/[groupId]/balances/page.tsx
+++ b/src/app/groups/[groupId]/balances/page.tsx
@@ -9,8 +9,13 @@ import {
} from '@/components/ui/card'
import { getGroup, getGroupExpenses } from '@/lib/api'
import { getBalances, getSuggestedReimbursements } from '@/lib/balances'
+import { Metadata } from 'next'
import { notFound } from 'next/navigation'
+export const metadata: Metadata = {
+ title: 'Balances',
+}
+
export default async function GroupPage({
params: { groupId },
}: {
diff --git a/src/app/groups/[groupId]/edit/page.tsx b/src/app/groups/[groupId]/edit/page.tsx
index f902428..1fcf225 100644
--- a/src/app/groups/[groupId]/edit/page.tsx
+++ b/src/app/groups/[groupId]/edit/page.tsx
@@ -1,8 +1,13 @@
import { GroupForm } from '@/components/group-form'
import { getGroup, updateGroup } from '@/lib/api'
import { groupFormSchema } from '@/lib/schemas'
+import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
+export const metadata: Metadata = {
+ title: 'Settings',
+}
+
export default async function EditGroupPage({
params: { groupId },
}: {
diff --git a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
index 0623f50..188d08f 100644
--- a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
+++ b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
@@ -1,8 +1,13 @@
import { ExpenseForm } from '@/components/expense-form'
import { deleteExpense, getExpense, getGroup, updateExpense } from '@/lib/api'
import { expenseFormSchema } from '@/lib/schemas'
+import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
+export const metadata: Metadata = {
+ title: 'Edit expense',
+}
+
export default async function EditExpensePage({
params: { groupId, expenseId },
}: {
diff --git a/src/app/groups/[groupId]/expenses/create/page.tsx b/src/app/groups/[groupId]/expenses/create/page.tsx
index 3a730d5..e603e59 100644
--- a/src/app/groups/[groupId]/expenses/create/page.tsx
+++ b/src/app/groups/[groupId]/expenses/create/page.tsx
@@ -1,8 +1,13 @@
import { ExpenseForm } from '@/components/expense-form'
import { createExpense, getGroup } from '@/lib/api'
import { expenseFormSchema } from '@/lib/schemas'
+import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
+export const metadata: Metadata = {
+ title: 'Create expense',
+}
+
export default async function ExpensePage({
params: { groupId },
}: {
diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx
index 1ba7df7..907a37a 100644
--- a/src/app/groups/[groupId]/expenses/expense-list.tsx
+++ b/src/app/groups/[groupId]/expenses/expense-list.tsx
@@ -66,7 +66,7 @@ export function ExpenseList({
) : (
<p className="px-6 text-sm py-6">
Your group doesn’t contain any expense yet.{' '}
- <Button variant="link" asChild className="-m-3">
+ <Button variant="link" asChild className="-m-4">
<Link href={`/groups/${groupId}/expenses/create`}>
Create the first one
</Link>
diff --git a/src/app/groups/[groupId]/expenses/page.tsx b/src/app/groups/[groupId]/expenses/page.tsx
index 48f355b..0ec5086 100644
--- a/src/app/groups/[groupId]/expenses/page.tsx
+++ b/src/app/groups/[groupId]/expenses/page.tsx
@@ -9,9 +9,14 @@ import {
} from '@/components/ui/card'
import { getGroup, getGroupExpenses } from '@/lib/api'
import { Plus } from 'lucide-react'
+import { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
+export const metadata: Metadata = {
+ title: 'Expenses',
+}
+
export default async function GroupExpensesPage({
params: { groupId },
}: {
diff --git a/src/app/groups/[groupId]/layout.tsx b/src/app/groups/[groupId]/layout.tsx
index 5b99dd2..5fbd7cb 100644
--- a/src/app/groups/[groupId]/layout.tsx
+++ b/src/app/groups/[groupId]/layout.tsx
@@ -1,31 +1,39 @@
import { GroupTabs } from '@/app/groups/[groupId]/group-tabs'
import { SaveGroupLocally } from '@/app/groups/[groupId]/save-recent-group'
-import { Button } from '@/components/ui/button'
import { getGroup } from '@/lib/api'
-import { ChevronLeft } from 'lucide-react'
+import { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { PropsWithChildren } from 'react'
+type Props = {
+ params: {
+ groupId: string
+ }
+}
+
+export async function generateMetadata({
+ params: { groupId },
+}: Props): Promise<Metadata> {
+ const group = await getGroup(groupId)
+
+ return {
+ title: {
+ default: group?.name ?? '',
+ template: `%s · ${group?.name} · Spliit`,
+ },
+ }
+}
+
export default async function GroupLayout({
children,
params: { groupId },
-}: PropsWithChildren<{
- params: { groupId: string }
-}>) {
+}: PropsWithChildren<Props>) {
const group = await getGroup(groupId)
if (!group) notFound()
return (
<>
- <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>
- </div>
-
<h1 className="font-bold text-2xl mb-4">
<Link href={`/groups/${groupId}`}>{group.name}</Link>
</h1>
diff --git a/src/app/groups/create/page.tsx b/src/app/groups/create/page.tsx
index 4f220d2..82047a5 100644
--- a/src/app/groups/create/page.tsx
+++ b/src/app/groups/create/page.tsx
@@ -1,9 +1,6 @@
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() {
@@ -14,16 +11,5 @@ export default function CreateGroupPage() {
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>
- )
+ return <GroupForm onSubmit={createGroupAction} />
}
diff --git a/src/app/groups/not-found.tsx b/src/app/groups/not-found.tsx
new file mode 100644
index 0000000..d4532e1
--- /dev/null
+++ b/src/app/groups/not-found.tsx
@@ -0,0 +1,15 @@
+import { Button } from '@/components/ui/button'
+import Link from 'next/link'
+
+export default function NotFound() {
+ return (
+ <div className="flex flex-col gap-2">
+ <p>This group does not exist.</p>
+ <p>
+ <Button asChild variant="secondary">
+ <Link href="/groups">Go to recently visited groups</Link>
+ </Button>
+ </p>
+ </div>
+ )
+}
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx
index 7e4f484..86c63ac 100644
--- a/src/app/groups/page.tsx
+++ b/src/app/groups/page.tsx
@@ -1,10 +1,18 @@
import { RecentGroupList } from '@/app/groups/recent-group-list'
import { Button } from '@/components/ui/button'
+import { Metadata } from 'next'
import Link from 'next/link'
+export const metadata: Metadata = {
+ title: 'Recently visited groups',
+}
+
export default async function GroupsPage() {
return (
<main>
+ <h1 className="font-bold text-2xl mb-4">
+ <Link href="/groups">Recently visited groups</Link>
+ </h1>
<Button asChild>
<Link href="/groups/create">New group</Link>
</Button>
diff --git a/src/app/icon.svg b/src/app/icon.svg
new file mode 100644
index 0000000..b94b8c4
--- /dev/null
+++ b/src/app/icon.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ccc;}.cls-2{fill:#ed5167;}.cls-3{fill:#72d3b8;}.cls-4{fill:#55bc9c;}.cls-5{fill:#e8e8e8;}.cls-6{fill:#333538;}</style></defs><title/><g data-name="1 funnel" id="_1_funnel"><path class="cls-1" d="M261.25,134.9h0a76.62,76.62,0,0,1,76.62,76.62V423.22a0,0,0,0,1,0,0H261.25a0,0,0,0,1,0,0V134.9a0,0,0,0,1,0,0Z" transform="translate(285.06 -130.08) rotate(45)"/><path class="cls-2" d="M161.87,470.93l75.07-75.08a17.06,17.06,0,0,0,0-24.14l-40.19-40.19a17.06,17.06,0,0,0-24.14,0l-10.8,10.8a91,91,0,0,0-129.58.92c-34.17,35.14-34.17,91.69,0,126.83a91,91,0,0,0,129.58.92ZM57,447.11a57.21,57.21,0,1,1,80.9,0A57.22,57.22,0,0,1,57,447.11Z"/><rect class="cls-3" height="253.49" rx="47.61" transform="translate(511.66 282.22) rotate(180)" width="344.78" x="83.44" y="14.36"/><path class="cls-4" d="M126,184.86V97.36a40.46,40.46,0,0,0,40.46-40.45H345.23a40.44,40.44,0,0,0,40.44,40.45v87.5a40.44,40.44,0,0,0-40.44,40.44H166.44A40.46,40.46,0,0,0,126,184.86Z"/><circle class="cls-3" cx="255.83" cy="141.11" r="60.11"/><circle class="cls-3" cx="349.72" cy="141.11" r="21.88"/><circle class="cls-3" cx="161.94" cy="141.11" r="21.88"/><path class="cls-5" d="M174.13,134.9h76.62a0,0,0,0,1,0,0V346.61a76.62,76.62,0,0,1-76.62,76.62h0a0,0,0,0,1,0,0V134.9a0,0,0,0,1,0,0Z" transform="translate(559.99 326.17) rotate(135)"/><path class="cls-2" d="M350.19,471a91,91,0,0,0,129.58-.92c34.17-35.14,34.17-91.69,0-126.83a91,91,0,0,0-129.58-.92l-10.8-10.8a17.06,17.06,0,0,0-24.14,0l-40.19,40.19a17.06,17.06,0,0,0,0,24.14l75.07,75.08Zm23.89-23.88a57.21,57.21,0,1,1,80.9,0A57.2,57.2,0,0,1,374.08,447.11Z"/><circle class="cls-6" cx="256" cy="322.62" r="20.01"/></g></svg> \ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 7c5afbe..894e2d0 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,10 +1,36 @@
import { ProgressBar } from '@/components/progress-bar'
+import { Button } from '@/components/ui/button'
+import { env } from '@/lib/env'
import type { Metadata } from 'next'
+import Image from 'next/image'
+import Link from 'next/link'
import './globals.css'
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
+ metadataBase: new URL(env.NEXT_PUBLIC_BASE_URL),
+ title: {
+ default: 'Spliit · Share expenses with friends and family',
+ template: '%s · Spliit',
+ },
+ description:
+ 'Spliit is a minimalist web application to share expenses with friends and family. No ads, no account, no problem.',
+ openGraph: {
+ title: 'Spliit · Share expenses with friends and family',
+ description:
+ 'Spliit is a minimalist web application to share expenses with friends and family. No ads, no account, no problem.',
+ images: `/banner.png`,
+ type: 'website',
+ url: '/',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ creator: '@scastiel',
+ site: '@scastiel',
+ images: `/banner.png`,
+ title: 'Spliit · Share expenses with friends and family',
+ description:
+ 'Spliit is a minimalist web application to share expenses with friends and family. No ads, no account, no problem.',
+ },
}
export default function RootLayout({
@@ -16,7 +42,20 @@ export default function RootLayout({
<html lang="en">
<body className="">
<ProgressBar />
- <div className="max-w-screen-md mx-auto p-4">{children}</div>
+ <header className="fixed top-0 left-0 right-0 flex justify-between bg-white bg-opacity-50 p-2 border-b backdrop-blur-sm">
+ <Link className="flex items-center gap-2" href="/">
+ <Image src="/logo.svg" width={30} height={30} alt="" />
+ <h1 className="font-bold text-primary">Spliit</h1>
+ </Link>
+ <ul role="nav" className="flex items-center text-sm">
+ <li>
+ <Button variant="link" asChild className="-my-3">
+ <Link href="/groups">Groups</Link>
+ </Button>
+ </li>
+ </ul>
+ </header>
+ <div className="max-w-screen-md mx-auto p-4 pt-16">{children}</div>
</body>
</html>
)
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a94e2d5..dedeb98 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,12 +1,3 @@
-import { Button } from '@/components/ui/button'
-import Link from 'next/link'
-
export default function HomePage() {
- return (
- <main>
- <Button asChild variant="link">
- <Link href="/groups">My groups</Link>
- </Button>
- </main>
- )
+ return <main></main>
}
diff --git a/src/components/group-form.tsx b/src/components/group-form.tsx
index 39edd72..d2140f0 100644
--- a/src/components/group-form.tsx
+++ b/src/components/group-form.tsx
@@ -22,7 +22,7 @@ import { Input } from '@/components/ui/input'
import { getGroup } from '@/lib/api'
import { GroupFormValues, groupFormSchema } from '@/lib/schemas'
import { zodResolver } from '@hookform/resolvers/zod'
-import { Trash2 } from 'lucide-react'
+import { Save, Trash2 } from 'lucide-react'
import { useFieldArray, useForm } from 'react-hook-form'
export type Props = {
@@ -106,6 +106,7 @@ export function GroupForm({ group, onSubmit }: Props) {
/>
</CardContent>
</Card>
+
<Card className="mb-4">
<CardHeader>
<CardTitle>Participants</CardTitle>
@@ -160,7 +161,12 @@ export function GroupForm({ group, onSubmit }: Props) {
</CardFooter>
</Card>
- <SubmitButton loadingContent="Submitting…">Submit</SubmitButton>
+ <SubmitButton
+ size="lg"
+ loadingContent={group ? 'Saving…' : 'Creating…'}
+ >
+ <Save className="w-4 h-4 mr-2" /> {group ? <>Save</> : <> Create</>}
+ </SubmitButton>
</form>
</Form>
)
diff --git a/src/components/progress-bar.tsx b/src/components/progress-bar.tsx
index cbf068e..f776c97 100644
--- a/src/components/progress-bar.tsx
+++ b/src/components/progress-bar.tsx
@@ -6,7 +6,7 @@ export function ProgressBar() {
<Next13ProgressBar
height="2px"
color="#64748b"
- options={{ showSpinner: true }}
+ options={{ showSpinner: false }}
showOnShallow
/>
)
diff --git a/src/lib/env.ts b/src/lib/env.ts
new file mode 100644
index 0000000..af5616c
--- /dev/null
+++ b/src/lib/env.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod'
+
+const envSchema = z.object({
+ NEXT_PUBLIC_BASE_URL: z.string().url(),
+ POSTGRES_URL_NON_POOLING: z.string().url(),
+ POSTGRES_PRISMA_URL: z.string().url(),
+})
+
+export const env = envSchema.parse(process.env)