mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-05 04:06:13 +01:00
Loading screens & layouts
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return <p>Loading…</p>
|
||||||
|
}
|
||||||
37
src/app/groups/[groupId]/layout.tsx
Normal file
37
src/app/groups/[groupId]/layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { getGroup } from '@/lib/api'
|
||||||
|
import { ChevronLeft, Edit } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { notFound } from 'next/navigation'
|
||||||
|
import { PropsWithChildren } from 'react'
|
||||||
|
|
||||||
|
export default async function GroupLayout({
|
||||||
|
children,
|
||||||
|
params: { groupId },
|
||||||
|
}: PropsWithChildren<{
|
||||||
|
params: { groupId: string }
|
||||||
|
}>) {
|
||||||
|
const group = await getGroup(groupId)
|
||||||
|
if (!group) notFound()
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
3
src/app/groups/[groupId]/loading.tsx
Normal file
3
src/app/groups/[groupId]/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return <p>Loading…</p>
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table'
|
} from '@/components/ui/table'
|
||||||
import { getGroup, getGroupExpenses } from '@/lib/api'
|
import { getGroup, getGroupExpenses } from '@/lib/api'
|
||||||
import { ChevronLeft, ChevronRight, Edit, Plus } from 'lucide-react'
|
import { ChevronRight, Plus } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
|
|
||||||
@@ -32,22 +32,7 @@ export default async function GroupPage({
|
|||||||
const expenses = await getGroupExpenses(groupId)
|
const expenses = await getGroupExpenses(groupId)
|
||||||
|
|
||||||
return (
|
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">
|
<Card className="mb-4">
|
||||||
<div className="flex flex-1">
|
<div className="flex flex-1">
|
||||||
<CardHeader className="flex-1">
|
<CardHeader className="flex-1">
|
||||||
@@ -136,6 +121,6 @@ export default async function GroupPage({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<SaveGroupLocally group={{ id: group.id, name: group.name }} />
|
<SaveGroupLocally group={{ id: group.id, name: group.name }} />
|
||||||
</main>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/app/groups/loading.tsx
Normal file
3
src/app/groups/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return <p>Loading…</p>
|
||||||
|
}
|
||||||
3
src/app/loading.tsx
Normal file
3
src/app/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return <p>Loading…</p>
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Button } from '@/components/ui/button'
|
import { SubmitButton } from '@/components/submit-button'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -175,9 +175,7 @@ export function ExpenseForm({ group, expense, onSubmit }: Props) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<Button variant="secondary" type="submit">
|
<SubmitButton loadingContent="Submitting…">Submit</SubmitButton>
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
import { SubmitButton } from '@/components/submit-button'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -25,7 +26,7 @@ import { useFieldArray, useForm } from 'react-hook-form'
|
|||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
group?: NonNullable<Awaited<ReturnType<typeof getGroup>>>
|
group?: NonNullable<Awaited<ReturnType<typeof getGroup>>>
|
||||||
onSubmit: (groupFormValues: GroupFormValues) => void
|
onSubmit: (groupFormValues: GroupFormValues) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupForm({ group, onSubmit }: Props) {
|
export function GroupForm({ group, onSubmit }: Props) {
|
||||||
@@ -49,8 +50,8 @@ export function GroupForm({ group, onSubmit }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit((values) => {
|
onSubmit={form.handleSubmit(async (values) => {
|
||||||
onSubmit(values)
|
await onSubmit(values)
|
||||||
})}
|
})}
|
||||||
className="space-y-8"
|
className="space-y-8"
|
||||||
>
|
>
|
||||||
@@ -129,7 +130,7 @@ export function GroupForm({ group, onSubmit }: Props) {
|
|||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Button type="submit">Submit</Button>
|
<SubmitButton loadingContent="Submitting…">Submit</SubmitButton>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
|||||||
23
src/components/submit-button.tsx
Normal file
23
src/components/submit-button.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Loader2 } from 'lucide-react'
|
||||||
|
import { PropsWithChildren, ReactNode } from 'react'
|
||||||
|
import { useFormState } from 'react-hook-form'
|
||||||
|
|
||||||
|
type Props = PropsWithChildren<{
|
||||||
|
loadingContent: ReactNode
|
||||||
|
}>
|
||||||
|
|
||||||
|
export function SubmitButton({ children, loadingContent }: Props) {
|
||||||
|
const { isSubmitting } = useFormState()
|
||||||
|
return (
|
||||||
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> {loadingContent}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import { Expense } from '@prisma/client'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
export async function createGroup(groupFormValues: GroupFormValues) {
|
export async function createGroup(groupFormValues: GroupFormValues) {
|
||||||
return getPrisma().group.create({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.group.create({
|
||||||
data: {
|
data: {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
name: groupFormValues.name,
|
name: groupFormValues.name,
|
||||||
@@ -36,7 +37,8 @@ export async function createExpense(
|
|||||||
throw new Error(`Invalid participant ID: ${participant}`)
|
throw new Error(`Invalid participant ID: ${participant}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getPrisma().expense.create({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.expense.create({
|
||||||
data: {
|
data: {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
groupId,
|
groupId,
|
||||||
@@ -73,7 +75,8 @@ export async function updateExpense(
|
|||||||
throw new Error(`Invalid participant ID: ${participant}`)
|
throw new Error(`Invalid participant ID: ${participant}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getPrisma().expense.update({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.expense.update({
|
||||||
where: { id: expenseId },
|
where: { id: expenseId },
|
||||||
data: {
|
data: {
|
||||||
amount: expenseFormValues.amount,
|
amount: expenseFormValues.amount,
|
||||||
@@ -104,7 +107,8 @@ export async function updateGroup(
|
|||||||
const existingGroup = await getGroup(groupId)
|
const existingGroup = await getGroup(groupId)
|
||||||
if (!existingGroup) throw new Error('Invalid group ID')
|
if (!existingGroup) throw new Error('Invalid group ID')
|
||||||
|
|
||||||
return getPrisma().group.update({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.group.update({
|
||||||
where: { id: groupId },
|
where: { id: groupId },
|
||||||
data: {
|
data: {
|
||||||
name: groupFormValues.name,
|
name: groupFormValues.name,
|
||||||
@@ -134,21 +138,24 @@ export async function updateGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getGroup(groupId: string) {
|
export async function getGroup(groupId: string) {
|
||||||
return getPrisma().group.findUnique({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.group.findUnique({
|
||||||
where: { id: groupId },
|
where: { id: groupId },
|
||||||
include: { participants: true },
|
include: { participants: true },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGroupExpenses(groupId: string) {
|
export async function getGroupExpenses(groupId: string) {
|
||||||
return getPrisma().expense.findMany({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.expense.findMany({
|
||||||
where: { groupId },
|
where: { groupId },
|
||||||
include: { paidFor: { include: { participant: true } }, paidBy: true },
|
include: { paidFor: { include: { participant: true } }, paidBy: true },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExpense(groupId: string, expenseId: string) {
|
export async function getExpense(groupId: string, expenseId: string) {
|
||||||
return getPrisma().expense.findUnique({
|
const prisma = await getPrisma()
|
||||||
|
return prisma.expense.findUnique({
|
||||||
where: { id: expenseId },
|
where: { id: expenseId },
|
||||||
include: { paidBy: true, paidFor: true },
|
include: { paidBy: true, paidFor: true },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client'
|
|||||||
|
|
||||||
let prisma: PrismaClient
|
let prisma: PrismaClient
|
||||||
|
|
||||||
export function getPrisma() {
|
export async function getPrisma() {
|
||||||
if (!prisma) {
|
if (!prisma) {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
prisma = new PrismaClient()
|
prisma = new PrismaClient()
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { clsx, type ClassValue } from 'clsx'
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user