Loading screens & layouts

This commit is contained in:
Sebastien Castiel
2023-12-06 12:22:24 -05:00
parent ed55c696cd
commit 6c4ced0f79
12 changed files with 104 additions and 37 deletions

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p>Loading</p>
}

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

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p>Loading</p>
}

View File

@@ -17,7 +17,7 @@ import {
TableRow,
} from '@/components/ui/table'
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 { notFound } from 'next/navigation'
@@ -32,22 +32,7 @@ export default async function GroupPage({
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">
@@ -136,6 +121,6 @@ export default async function GroupPage({
</CardContent>
</Card>
<SaveGroupLocally group={{ id: group.id, name: group.name }} />
</main>
</>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p>Loading</p>
}

3
src/app/loading.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p>Loading</p>
}

View File

@@ -1,5 +1,5 @@
'use client'
import { Button } from '@/components/ui/button'
import { SubmitButton } from '@/components/submit-button'
import {
Card,
CardContent,
@@ -175,9 +175,7 @@ export function ExpenseForm({ group, expense, onSubmit }: Props) {
</CardContent>
<CardFooter>
<Button variant="secondary" type="submit">
Submit
</Button>
<SubmitButton loadingContent="Submitting…">Submit</SubmitButton>
</CardFooter>
</Card>
</form>

View File

@@ -1,4 +1,5 @@
'use client'
import { SubmitButton } from '@/components/submit-button'
import { Button } from '@/components/ui/button'
import {
Card,
@@ -25,7 +26,7 @@ import { useFieldArray, useForm } from 'react-hook-form'
export type Props = {
group?: NonNullable<Awaited<ReturnType<typeof getGroup>>>
onSubmit: (groupFormValues: GroupFormValues) => void
onSubmit: (groupFormValues: GroupFormValues) => Promise<void>
}
export function GroupForm({ group, onSubmit }: Props) {
@@ -49,8 +50,8 @@ export function GroupForm({ group, onSubmit }: Props) {
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit((values) => {
onSubmit(values)
onSubmit={form.handleSubmit(async (values) => {
await onSubmit(values)
})}
className="space-y-8"
>
@@ -129,7 +130,7 @@ export function GroupForm({ group, onSubmit }: Props) {
</CardFooter>
</Card>
<Button type="submit">Submit</Button>
<SubmitButton loadingContent="Submitting…">Submit</SubmitButton>
</form>
</Form>
)

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

View File

@@ -4,7 +4,8 @@ import { Expense } from '@prisma/client'
import { v4 as uuidv4 } from 'uuid'
export async function createGroup(groupFormValues: GroupFormValues) {
return getPrisma().group.create({
const prisma = await getPrisma()
return prisma.group.create({
data: {
id: uuidv4(),
name: groupFormValues.name,
@@ -36,7 +37,8 @@ export async function createExpense(
throw new Error(`Invalid participant ID: ${participant}`)
}
return getPrisma().expense.create({
const prisma = await getPrisma()
return prisma.expense.create({
data: {
id: uuidv4(),
groupId,
@@ -73,7 +75,8 @@ export async function updateExpense(
throw new Error(`Invalid participant ID: ${participant}`)
}
return getPrisma().expense.update({
const prisma = await getPrisma()
return prisma.expense.update({
where: { id: expenseId },
data: {
amount: expenseFormValues.amount,
@@ -104,7 +107,8 @@ export async function updateGroup(
const existingGroup = await getGroup(groupId)
if (!existingGroup) throw new Error('Invalid group ID')
return getPrisma().group.update({
const prisma = await getPrisma()
return prisma.group.update({
where: { id: groupId },
data: {
name: groupFormValues.name,
@@ -134,21 +138,24 @@ export async function updateGroup(
}
export async function getGroup(groupId: string) {
return getPrisma().group.findUnique({
const prisma = await getPrisma()
return prisma.group.findUnique({
where: { id: groupId },
include: { participants: true },
})
}
export async function getGroupExpenses(groupId: string) {
return getPrisma().expense.findMany({
const prisma = await getPrisma()
return prisma.expense.findMany({
where: { groupId },
include: { paidFor: { include: { participant: true } }, paidBy: true },
})
}
export async function getExpense(groupId: string, expenseId: string) {
return getPrisma().expense.findUnique({
const prisma = await getPrisma()
return prisma.expense.findUnique({
where: { id: expenseId },
include: { paidBy: true, paidFor: true },
})

View File

@@ -2,7 +2,7 @@ import { PrismaClient } from '@prisma/client'
let prisma: PrismaClient
export function getPrisma() {
export async function getPrisma() {
if (!prisma) {
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient()

View File

@@ -1,6 +1,10 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}