Titles and navigation

This commit is contained in:
Sebastien Castiel
2023-12-07 16:44:01 -05:00
parent 6611e3a187
commit 0b27f90fb7
19 changed files with 134 additions and 45 deletions

1
public/logo.svg Normal file
View File

@@ -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>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/app/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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 },
}: {

View File

@@ -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 },
}: {

View File

@@ -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 },
}: {

View File

@@ -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 },
}: {

View File

@@ -66,7 +66,7 @@ export function ExpenseList({
) : (
<p className="px-6 text-sm py-6">
Your group doesnt 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>

View File

@@ -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 },
}: {

View File

@@ -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>

View File

@@ -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} />
}

View File

@@ -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>
)
}

View File

@@ -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>

1
src/app/icon.svg Normal file
View File

@@ -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>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -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>
)

View File

@@ -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>
}

View File

@@ -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>
)

View File

@@ -6,7 +6,7 @@ export function ProgressBar() {
<Next13ProgressBar
height="2px"
color="#64748b"
options={{ showSpinner: true }}
options={{ showSpinner: false }}
showOnShallow
/>
)

9
src/lib/env.ts Normal file
View File

@@ -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)