Skip to content

Commit 29f6259

Browse files
committed
feat: separate admin portal
1 parent 0dbf0af commit 29f6259

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+875
-227
lines changed
File renamed without changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {
2+
Breadcrumb,
3+
BreadcrumbItem,
4+
BreadcrumbLink,
5+
BreadcrumbList,
6+
BreadcrumbPage,
7+
BreadcrumbSeparator,
8+
} from '@/components/ui/breadcrumb'
9+
import { getBook } from '@/lib/api/book'
10+
import { BookDown, Calendar, Hash, Library } from 'lucide-react'
11+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
12+
import { ThreeDBook } from '@/components/books/three-d-book'
13+
import { Button } from '@/components/ui/button'
14+
import { Badge } from '@/components/ui/badge'
15+
import Link from 'next/link'
16+
17+
export default async function BookDetailsPage({
18+
params,
19+
}: {
20+
params: Promise<{ id: string }>
21+
}) {
22+
const { id } = await params
23+
24+
const [bookRes] = await Promise.all([getBook({ id })])
25+
26+
if ('error' in bookRes) {
27+
console.log({ libRes: bookRes })
28+
return <div>{JSON.stringify(bookRes.message)}</div>
29+
}
30+
31+
return (
32+
<div className="space-y-4">
33+
<h1 className="text-2xl font-semibold">{bookRes.data.title}</h1>
34+
<Breadcrumb>
35+
<BreadcrumbList>
36+
<BreadcrumbItem>
37+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
38+
</BreadcrumbItem>
39+
<BreadcrumbSeparator />
40+
<BreadcrumbItem>
41+
<BreadcrumbLink href="/admin/books">Books</BreadcrumbLink>
42+
</BreadcrumbItem>
43+
<BreadcrumbSeparator />
44+
<BreadcrumbItem>
45+
<BreadcrumbPage>{bookRes.data.title}</BreadcrumbPage>
46+
</BreadcrumbItem>
47+
</BreadcrumbList>
48+
</Breadcrumb>
49+
50+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
51+
{/* Book Cover */}
52+
<div className="lg:col-span-1 grid place-items-center gap-4">
53+
<ThreeDBook book={bookRes.data} />
54+
<Button className="w-full" disabled={true}>
55+
{true ? (
56+
<>
57+
<BookDown className="mr-2 h-4 w-4" />
58+
Borrow Book
59+
</>
60+
) : (
61+
'Currently Borrowed'
62+
)}
63+
</Button>
64+
<Button variant="outline" className="w-full bg-transparent">
65+
Add to Wishlist
66+
</Button>
67+
</div>
68+
69+
{/* Book Information */}
70+
<div className="lg:col-span-2 space-y-4">
71+
<div>
72+
<h1 className="text-3xl font-bold mb-2">{bookRes.data.title}</h1>
73+
<p className="text-xl text-muted-foreground mb-4">
74+
{bookRes.data.author}
75+
</p>
76+
<div className="flex flex-wrap gap-2 mb-4">
77+
<Badge variant={true ? 'default' : 'destructive'}>
78+
{true ? 'Available' : 'Borrowed'}
79+
</Badge>
80+
<Badge variant="outline">bookRes.data.genre</Badge>
81+
</div>
82+
</div>
83+
84+
<Card>
85+
<CardHeader>
86+
<CardTitle>Book Information</CardTitle>
87+
</CardHeader>
88+
<CardContent className="grid gap-2 grid-cols-[max-content_1fr] md:grid-cols-[max-content_1fr_max-content_1fr] items-center">
89+
<Hash className="size-4" />
90+
<p>
91+
<span className="font-medium">Code:&nbsp;</span>
92+
{bookRes.data.code}
93+
</p>
94+
<Calendar className="size-4" />
95+
<p>
96+
<span className="font-medium">Year:&nbsp;</span>
97+
{bookRes.data.year}
98+
</p>
99+
<Library className="size-4" />
100+
<p>
101+
<span className="font-medium">Library:&nbsp;</span>
102+
<Link href={`/libraries/${bookRes.data.library.id}`}>
103+
{bookRes.data.library.name}
104+
</Link>
105+
</p>
106+
</CardContent>
107+
</Card>
108+
109+
<Card>
110+
<CardHeader>
111+
<CardTitle>Description</CardTitle>
112+
</CardHeader>
113+
<CardContent>
114+
<p className="text-sm leading-relaxed">
115+
bookRes.data.description
116+
</p>
117+
</CardContent>
118+
</Card>
119+
</div>
120+
</div>
121+
122+
{/* <div className="place-self-center text-center pt-4 border-t">
123+
<p className="text-gray-600">{bookRes.data.author}</p>
124+
<p className="text-sm text-gray-500">{bookRes.data.code}</p>
125+
</div> */}
126+
</div>
127+
)
128+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {
2+
Breadcrumb,
3+
BreadcrumbItem,
4+
BreadcrumbLink,
5+
BreadcrumbList,
6+
BreadcrumbPage,
7+
BreadcrumbSeparator,
8+
} from '@/components/ui/breadcrumb'
9+
import { Button } from '@/components/ui/button'
10+
import {
11+
Pagination,
12+
PaginationContent,
13+
PaginationItem,
14+
PaginationNext,
15+
PaginationPrevious,
16+
} from '@/components/ui/pagination'
17+
import { getListBooks } from '@/lib/api/book'
18+
import Link from 'next/link'
19+
import type { Metadata } from 'next'
20+
import { SITE_NAME } from '@/lib/consts'
21+
import { Search } from 'lucide-react'
22+
import { DebouncedInput } from '@/components/common/DebouncedInput'
23+
import { Badge } from '@/components/ui/badge'
24+
import { ListBook } from '@/components/books/ListBook'
25+
import { cookies } from 'next/headers'
26+
import { Verify } from '@/lib/firebase/firebase'
27+
28+
export const metadata: Metadata = {
29+
title: `Books · ${SITE_NAME}`,
30+
}
31+
32+
export default async function Books({
33+
searchParams,
34+
}: {
35+
searchParams: Promise<{
36+
skip?: number
37+
limit?: number
38+
title?: string
39+
}>
40+
}) {
41+
const sp = await searchParams
42+
const skip = Number(sp?.skip ?? 0)
43+
const limit = Number(sp?.limit ?? 20)
44+
45+
await Verify({ from: '/admin/books' })
46+
47+
const cookieStore = await cookies()
48+
const cookieName = process.env.LIBRARY_COOKIE_NAME as string
49+
const libID = cookieStore.get(cookieName)?.value
50+
51+
const res = await getListBooks({
52+
sort_by: 'created_at',
53+
sort_in: 'desc',
54+
limit: limit,
55+
skip: skip,
56+
title: sp?.title,
57+
library_id: libID,
58+
})
59+
60+
if ('error' in res) {
61+
console.log(res)
62+
return <div>{JSON.stringify(res.message)}</div>
63+
}
64+
65+
const prevSkip = skip - limit > 0 ? skip - limit : 0
66+
67+
const nextURL = `/admin/books?skip=${skip + limit}&limit=${limit}`
68+
const prevURL = `/admin/books?skip=${prevSkip}&limit=${limit}`
69+
70+
return (
71+
<div className="space-y-4">
72+
<nav className="backdrop-blur-sm sticky top-0 z-10">
73+
<h1 className="text-2xl font-semibold">Books</h1>
74+
<div className="flex justify-between items-center">
75+
<Breadcrumb>
76+
<BreadcrumbList>
77+
<BreadcrumbItem>
78+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
79+
</BreadcrumbItem>
80+
<BreadcrumbSeparator />
81+
82+
<BreadcrumbItem>
83+
<BreadcrumbPage>
84+
Books{' '}
85+
<Badge className="ml-4" variant="outline">
86+
{res.meta.total}
87+
</Badge>
88+
</BreadcrumbPage>
89+
</BreadcrumbItem>
90+
</BreadcrumbList>
91+
</Breadcrumb>
92+
<Button asChild>
93+
<Link href="/admin/books/new">Register New Book</Link>
94+
</Button>
95+
</div>
96+
</nav>
97+
98+
<div className="relative flex-1">
99+
<Search className="absolute left-3 top-3 size-4 text-muted-foreground" />
100+
101+
<DebouncedInput
102+
name="title"
103+
placeholder="Search by title"
104+
className="pl-8 max-w-md"
105+
/>
106+
</div>
107+
108+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
109+
{res.data.map((book) => (
110+
<Link key={book.id} href={`/admin/books/${book.id}`} passHref>
111+
<ListBook book={book} />
112+
</Link>
113+
))}
114+
</div>
115+
<Pagination>
116+
<PaginationContent>
117+
{res.meta.skip > 0 && (
118+
<PaginationItem>
119+
<PaginationPrevious href={prevURL} />
120+
</PaginationItem>
121+
)}
122+
{res.meta.limit <= res.data.length && (
123+
<PaginationItem>
124+
<PaginationNext href={nextURL} />
125+
</PaginationItem>
126+
)}
127+
</PaginationContent>
128+
</Pagination>
129+
</div>
130+
)
131+
}

app/(protected)/borrows/[id]/@edit/(.)edit/page.tsx renamed to app/(protected)/admin/borrows/[id]/@edit/(.)edit/page.tsx

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)