mirror of
https://github.com/spliit-app/spliit.git
synced 2025-12-06 01:19:29 +01:00
Titles and navigation
This commit is contained in:
1
public/logo.svg
Normal file
1
public/logo.svg
Normal 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
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 |
@@ -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 },
|
||||
}: {
|
||||
|
||||
@@ -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 },
|
||||
}: {
|
||||
|
||||
@@ -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 },
|
||||
}: {
|
||||
|
||||
@@ -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 },
|
||||
}: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 },
|
||||
}: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
15
src/app/groups/not-found.tsx
Normal file
15
src/app/groups/not-found.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
1
src/app/icon.svg
Normal 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 |
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
9
src/lib/env.ts
Normal 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)
|
||||
Reference in New Issue
Block a user