Skip to content

Commit 73c6945

Browse files
committed
feat: return button
1 parent c23c60c commit 73c6945

File tree

6 files changed

+91
-35
lines changed

6 files changed

+91
-35
lines changed

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "chrome",
9+
"request": "launch",
10+
"name": "Launch Chrome against localhost",
11+
"url": "http://localhost:3000",
12+
"webRoot": "${workspaceFolder}"
13+
}
14+
]
15+
}

app/(protected)/borrows/[id]/page.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ import {
3030
CreditCard,
3131
Gavel,
3232
Library,
33+
Pen,
3334
Tally5,
3435
User,
3536
UserCog,
3637
} from 'lucide-react'
3738
import clsx from 'clsx'
3839
import { differenceInDays } from 'date-fns'
3940
import { Borrow } from '@/lib/types/borrow'
41+
import { Button } from '@/components/ui/button'
4042

4143
export default async function BorrowDetailsPage({
4244
params,
@@ -125,14 +127,15 @@ export default async function BorrowDetailsPage({
125127
<CardHeader>
126128
<CardTitle>Book Information</CardTitle>
127129
</CardHeader>
128-
<CardContent className="grid place-self-center md:grid-cols-2 gap-4">
130+
<CardContent className="grid place-self-center md:place-self-auto md:grid-cols-2 gap-4">
129131
<Link href={`/books/${borrowRes.data.book.id}`}>
130132
<Image
131133
src={borrowRes.data.book?.cover ?? '/book-placeholder.svg'}
132134
alt={borrowRes.data.book.title + "'s cover"}
133135
width={256}
134136
height={256}
135137
className="rounded-md w-56 h-auto hover:shadow-md hover:scale-105 transition-transform"
138+
priority
136139
/>
137140
</Link>
138141
<div>
@@ -349,23 +352,29 @@ export default async function BorrowDetailsPage({
349352
alt={b.book.title + "'s cover"}
350353
width={160}
351354
height={240}
352-
className="shadow-md rounded-lg w-40 h-64 place-self-center object-cover"
355+
className="shadow-md rounded-lg w-40 h-60 place-self-center object-cover"
353356
/>
354357
</Link>
355358
))}
356359
</CardContent>
357360
</Card>
358361
)}
359362

360-
{!borrowRes.data.returning && (
361-
<div className="bottom-0 sticky py-2">
363+
<div className="bottom-0 sticky py-2 grid md:grid-cols-2 gap-4">
364+
{!borrowRes.data.returning && (
362365
<BtnReturnBook
363366
variant="outline"
364367
className="w-full"
365368
borrow={borrowRes.data}
366369
/>
367-
</div>
368-
)}
370+
)}
371+
<Button asChild>
372+
<Link href={`/borrows/${borrowRes.data.id}/edit`} className="w-full">
373+
<Pen />
374+
Edit
375+
</Link>
376+
</Button>
377+
</div>
369378
</div>
370379
)
371380
}

app/(protected)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default function ProtectedLayout({
22
children,
33
}: Readonly<{ children: React.ReactNode }>) {
4-
return <div className="container mx-auto px-4">{children}</div>
4+
return <div className="container mx-auto px-4 my-4">{children}</div>
55
}

components/borrows/BtnReturnBorrow.tsx

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,75 @@
11
'use client'
22

33
import { Borrow } from '@/lib/types/borrow'
4-
import { useState } from 'react'
4+
import { useTransition, useState } from 'react'
55
import { Button, ButtonProps } from '../ui/button'
6-
import { Lock, Unlock } from 'lucide-react'
7-
import { ReturnBorrow } from '@/lib/actions/return-borrow'
6+
import { Lock, Unlock, Loader } from 'lucide-react'
7+
import { actionReturnBorrow } from '@/lib/actions/return-borrow'
88
import { formatDate } from '@/lib/utils'
9+
import { toast } from '../hooks/use-toast'
910

1011
export const BtnReturnBook: React.FC<
1112
ButtonProps & {
1213
borrow: Borrow
1314
}
1415
> = ({ borrow, ...props }) => {
15-
const [confirm, setConfirm] = useState<NodeJS.Timeout>()
16+
const [confirmTimeout, setConfirmTimeout] = useState<NodeJS.Timeout>()
17+
const [clientBorrow, setClientBorrow] = useState<Borrow>(borrow)
18+
const [isPending, startTransition] = useTransition()
1619

1720
const onUnlock = () => {
1821
const timerId = setTimeout(() => {
19-
setConfirm(undefined)
22+
setConfirmTimeout(undefined)
2023
}, 3_000)
21-
setConfirm(timerId)
24+
setConfirmTimeout(timerId)
2225
}
2326

2427
const onClick = () => {
25-
ReturnBorrow(borrow.id)
26-
if (confirm) clearTimeout(confirm)
28+
startTransition(async () => {
29+
clearTimeout(confirmTimeout)
30+
const res = await actionReturnBorrow(borrow.id)
31+
if ('error' in res) {
32+
toast({
33+
title: 'Failed to return book',
34+
description: res.error,
35+
variant: 'destructive',
36+
})
37+
return
38+
}
39+
// optimistic update
40+
setClientBorrow((prev) => ({
41+
...prev,
42+
returning: {
43+
returned_at: new Date().toISOString(),
44+
} as Borrow['returning'],
45+
}))
46+
toast({
47+
title: 'Success',
48+
description: 'Book returned successfully',
49+
variant: 'default',
50+
})
51+
})
2752
}
2853

29-
if (borrow.returning)
54+
if (clientBorrow.returning)
3055
return (
3156
<Button {...props} variant="secondary" disabled>
32-
{formatDate(borrow.returning.returned_at)}
57+
{formatDate(clientBorrow.returning.returned_at)}
3358
</Button>
3459
)
3560

36-
if (!confirm) {
61+
if (!confirmTimeout) {
3762
return (
3863
<Button onClick={onUnlock} {...props}>
3964
<Lock />
4065
Return
4166
</Button>
4267
)
4368
}
69+
4470
return (
45-
<Button onClick={onClick} {...props} variant="default">
46-
<Unlock />
71+
<Button onClick={onClick} {...props} variant="default" disabled={isPending}>
72+
{isPending ? <Loader className="animate-spin" /> : <Unlock />}
4773
Confirm
4874
</Button>
4975
)

lib/actions/return-borrow.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@ import { returnBorrow } from '../api/borrow'
44
import { Verify } from '../firebase/firebase'
55

66
// server action to return a borrow
7-
export async function ReturnBorrow(id: string) {
7+
export async function actionReturnBorrow(id: string) {
88
const headers = await Verify({
99
from: '/borrows',
1010
})
1111

12-
returnBorrow(
13-
{
14-
id,
15-
returned_at: new Date().toISOString(),
16-
},
17-
{
18-
headers,
12+
try {
13+
const res = await returnBorrow(
14+
{
15+
id: id,
16+
returned_at: new Date().toISOString(),
17+
},
18+
{
19+
headers,
20+
}
21+
)
22+
return res
23+
} catch (e) {
24+
if (e instanceof Object && 'error' in e) {
25+
return { error: e.error as string }
1926
}
20-
)
21-
22-
revalidatePath('/borrows')
27+
return { error: 'failed to return borrow' }
28+
} finally {
29+
revalidatePath('/borrows')
30+
}
2331
}

lib/api/borrow.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Borrow, BorrowDetail } from '../types/borrow'
1+
import { Borrow, BorrowDetail, Return } from '../types/borrow'
22
import { QueryParams, ResList, ResSingle } from '../types/common'
33
import { BASE_URL } from './common'
44

@@ -61,9 +61,7 @@ export const createBorrow = async (
6161
}
6262

6363
export const returnBorrow = async (
64-
data: Pick<Borrow, 'id'> & {
65-
returned_at: string
66-
},
64+
data: Pick<Borrow, 'id'> & Partial<Pick<Return, 'returned_at' | 'fine'>>,
6765
init?: RequestInit
6866
): GetBorrowResponse => {
6967
const headers = new Headers(init?.headers)

0 commit comments

Comments
 (0)