Skip to content

Commit 0981432

Browse files
committed
feat: admin collections
1 parent b5d5732 commit 0981432

File tree

12 files changed

+1073
-29
lines changed

12 files changed

+1073
-29
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { FormCollection } from '@/components/collections/FormCollection'
2+
import {
3+
Breadcrumb,
4+
BreadcrumbItem,
5+
BreadcrumbLink,
6+
BreadcrumbList,
7+
BreadcrumbPage,
8+
BreadcrumbSeparator,
9+
} from '@/components/ui/breadcrumb'
10+
import { updateCollectionAction } from '@/lib/actions/collection'
11+
import { getCollection } from '@/lib/api/collection'
12+
13+
export default async function EditCollectionPage({
14+
params,
15+
}: {
16+
params: Promise<{ id: string }>
17+
}) {
18+
const { id } = await params
19+
20+
const [collectionRes] = await Promise.all([getCollection(id)])
21+
22+
if ('error' in collectionRes) {
23+
console.log({ libRes: collectionRes })
24+
return <div>{JSON.stringify(collectionRes.message)}</div>
25+
}
26+
27+
return (
28+
<div className="space-y-4">
29+
<h1 className="text-2xl font-semibold">New Collection</h1>
30+
<Breadcrumb>
31+
<BreadcrumbList>
32+
<BreadcrumbItem>
33+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
34+
</BreadcrumbItem>
35+
<BreadcrumbSeparator />
36+
<BreadcrumbItem>
37+
<BreadcrumbLink href="/admin/collections">
38+
Collections
39+
</BreadcrumbLink>
40+
</BreadcrumbItem>
41+
<BreadcrumbSeparator />
42+
<BreadcrumbItem>
43+
<BreadcrumbPage>{collectionRes.data.title}</BreadcrumbPage>
44+
</BreadcrumbItem>
45+
</BreadcrumbList>
46+
</Breadcrumb>
47+
48+
<FormCollection
49+
initialData={{
50+
library_id: collectionRes.data.library_id,
51+
title: collectionRes.data.title,
52+
cover: collectionRes.data.cover?.path,
53+
description: collectionRes.data.description,
54+
}}
55+
onSubmit={updateCollectionAction.bind(null, collectionRes.data.id)}
56+
/>
57+
</div>
58+
)
59+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { ListBook } from '@/components/books/ListBook'
2+
import { BtnDeleteCollection } from '@/components/collections/BtnDeleteCollection'
3+
import {
4+
Breadcrumb,
5+
BreadcrumbItem,
6+
BreadcrumbLink,
7+
BreadcrumbList,
8+
BreadcrumbPage,
9+
BreadcrumbSeparator,
10+
} from '@/components/ui/breadcrumb'
11+
import { Button } from '@/components/ui/button'
12+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
13+
import { deleteCollectionAction } from '@/lib/actions/collection'
14+
import { getCollection, getListCollectionBooks } from '@/lib/api/collection'
15+
import {
16+
BookOpen,
17+
Calendar,
18+
Edit,
19+
Library,
20+
Plus,
21+
Settings,
22+
Users,
23+
} from 'lucide-react'
24+
import Image from 'next/image'
25+
import Link from 'next/link'
26+
27+
export default async function CollectionManageBooksPage({
28+
params,
29+
}: {
30+
params: Promise<{ id: string }>
31+
}) {
32+
// const claims = await IsLoggedIn()
33+
34+
const { id } = await params
35+
36+
const [collectionRes, bookRes] = await Promise.all([
37+
getCollection(id),
38+
getListCollectionBooks(id, {
39+
include_book: 'true',
40+
}),
41+
])
42+
43+
if ('error' in collectionRes) {
44+
console.log({ libRes: collectionRes })
45+
return <div>{JSON.stringify(collectionRes.message)}</div>
46+
}
47+
48+
if ('error' in bookRes) {
49+
console.log({ bookRes })
50+
return <div>{JSON.stringify(bookRes.message)}</div>
51+
}
52+
53+
return (
54+
<div className="space-y-4">
55+
<h1 className="text-2xl font-semibold">{collectionRes.data.title}</h1>
56+
<Breadcrumb>
57+
<BreadcrumbList>
58+
<BreadcrumbItem>
59+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
60+
</BreadcrumbItem>
61+
<BreadcrumbSeparator />
62+
<BreadcrumbItem>
63+
<BreadcrumbLink href="/admin/collections">
64+
Collections
65+
</BreadcrumbLink>
66+
</BreadcrumbItem>
67+
<BreadcrumbSeparator />
68+
<BreadcrumbItem>
69+
<BreadcrumbPage>{collectionRes.data.title}</BreadcrumbPage>
70+
</BreadcrumbItem>
71+
</BreadcrumbList>
72+
</Breadcrumb>
73+
74+
{/* Books in Collection */}
75+
<div className="lg:col-span-3">
76+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
77+
{bookRes.data.map((collectionBook) => (
78+
<ListBook book={collectionBook.book!} key={collectionBook.id} />
79+
))}
80+
</div>
81+
82+
{collectionRes.data.book_count === 0 && (
83+
<Card className="p-12 text-center">
84+
<BookOpen className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
85+
<h3 className="text-lg font-medium mb-2">
86+
No books in this collection
87+
</h3>
88+
<p className="text-muted-foreground mb-4">
89+
Start building your collection by adding some books.
90+
</p>
91+
<Button asChild>
92+
<Link
93+
href={`/admin/collections/${collectionRes.data.id}/manage-books`}
94+
>
95+
<Plus className="mr-2 h-4 w-4" />
96+
Add Books
97+
</Link>
98+
</Button>
99+
</Card>
100+
)}
101+
</div>
102+
</div>
103+
)
104+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { ListBook } from '@/components/books/ListBook'
2+
import { BtnDeleteCollection } from '@/components/collections/BtnDeleteCollection'
3+
import {
4+
Breadcrumb,
5+
BreadcrumbItem,
6+
BreadcrumbLink,
7+
BreadcrumbList,
8+
BreadcrumbPage,
9+
BreadcrumbSeparator,
10+
} from '@/components/ui/breadcrumb'
11+
import { Button } from '@/components/ui/button'
12+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
13+
import { deleteCollectionAction } from '@/lib/actions/collection'
14+
import { getCollection, getListCollectionBooks } from '@/lib/api/collection'
15+
import {
16+
BookOpen,
17+
Calendar,
18+
Edit,
19+
Library,
20+
Plus,
21+
Settings,
22+
Users,
23+
} from 'lucide-react'
24+
import Image from 'next/image'
25+
import Link from 'next/link'
26+
27+
export default async function CollectionDetailsPage({
28+
params,
29+
}: {
30+
params: Promise<{ id: string }>
31+
}) {
32+
// const claims = await IsLoggedIn()
33+
34+
const { id } = await params
35+
36+
const [collectionRes, bookRes] = await Promise.all([
37+
getCollection(id),
38+
getListCollectionBooks(id, {
39+
include_book: 'true',
40+
}),
41+
])
42+
43+
if ('error' in collectionRes) {
44+
console.log({ libRes: collectionRes })
45+
return <div>{JSON.stringify(collectionRes.message)}</div>
46+
}
47+
48+
if ('error' in bookRes) {
49+
console.log({ bookRes })
50+
return <div>{JSON.stringify(bookRes.message)}</div>
51+
}
52+
53+
return (
54+
<div className="space-y-4">
55+
<h1 className="text-2xl font-semibold">{collectionRes.data.title}</h1>
56+
<Breadcrumb>
57+
<BreadcrumbList>
58+
<BreadcrumbItem>
59+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
60+
</BreadcrumbItem>
61+
<BreadcrumbSeparator />
62+
<BreadcrumbItem>
63+
<BreadcrumbLink href="/admin/collections">
64+
Collections
65+
</BreadcrumbLink>
66+
</BreadcrumbItem>
67+
<BreadcrumbSeparator />
68+
<BreadcrumbItem>
69+
<BreadcrumbPage>{collectionRes.data.title}</BreadcrumbPage>
70+
</BreadcrumbItem>
71+
</BreadcrumbList>
72+
</Breadcrumb>
73+
74+
<div className="relative aspect-[2] rounded-lg overflow-hidden mb-6">
75+
<Image
76+
src={collectionRes.data.cover?.path || '/book-placeholder.svg'}
77+
alt={collectionRes.data.title}
78+
fill
79+
className="w-full h-full object-cover rounded-t"
80+
/>
81+
<div className="absolute inset-0 bg-black/20" />
82+
<div className="absolute bottom-6 left-6 text-white">
83+
<h1 className="text-4xl font-bold mb-2">
84+
{collectionRes.data.title}
85+
</h1>
86+
<div className="flex flex-col md:flex-row md:items-center gap-4 text-sm">
87+
<div className="flex items-center gap-1">
88+
<Library className="h-4 w-4" />
89+
<span>{collectionRes.data.library?.name}</span>
90+
</div>
91+
<div className="flex items-center gap-1">
92+
<Users className="h-4 w-4" />
93+
<span>{collectionRes.data.follower_count} followers</span>
94+
</div>
95+
<div className="flex items-center gap-1">
96+
<BookOpen className="h-4 w-4" />
97+
<span>{collectionRes.data.book_count} books</span>
98+
</div>
99+
</div>
100+
</div>
101+
</div>
102+
103+
{/* Action Buttons */}
104+
<div className="flex gap-2 mb-8 flex-wrap">
105+
<Button variant="outline" asChild>
106+
<Link
107+
href={`/admin/collections/${collectionRes.data.id}/manage-books`}
108+
>
109+
<Settings className="mr-2 h-4 w-4" />
110+
Manage Books
111+
</Link>
112+
</Button>
113+
<Button variant="outline" asChild>
114+
<Link href={`/admin/collections/${collectionRes.data.id}/edit`}>
115+
<Edit className="mr-2 h-4 w-4" />
116+
Edit
117+
</Link>
118+
</Button>
119+
<BtnDeleteCollection
120+
deleteCollectionAction={deleteCollectionAction.bind(
121+
null,
122+
id,
123+
collectionRes.data.library_id
124+
)}
125+
variant="outline"
126+
/>
127+
</div>
128+
129+
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
130+
{/* Collection Info */}
131+
<div className="lg:col-span-1">
132+
<Card>
133+
<CardHeader>
134+
<CardTitle>About this Collection</CardTitle>
135+
</CardHeader>
136+
<CardContent className="space-y-4">
137+
{collectionRes.data.description && (
138+
<p className="text-sm text-muted-foreground leading-relaxed">
139+
{collectionRes.data.description}
140+
</p>
141+
)}
142+
143+
<div className="space-y-3 pt-4 border-t">
144+
<div className="flex items-center gap-2 text-sm">
145+
<Library className="h-4 w-4 text-muted-foreground" />
146+
<span>{collectionRes.data.library?.name}</span>
147+
</div>
148+
<div className="flex items-center gap-2 text-sm">
149+
<Users className="h-4 w-4 text-muted-foreground" />
150+
<span>{collectionRes.data.follower_count} followers</span>
151+
</div>
152+
<div className="flex items-center gap-2 text-sm">
153+
<BookOpen className="h-4 w-4 text-muted-foreground" />
154+
<span>{bookRes.data.length} books</span>
155+
</div>
156+
<div className="flex items-center gap-2 text-sm">
157+
<Calendar className="h-4 w-4 text-muted-foreground" />
158+
<span>
159+
Created{' '}
160+
{new Date(
161+
collectionRes.data.created_at
162+
).toLocaleDateString()}
163+
</span>
164+
</div>
165+
</div>
166+
</CardContent>
167+
</Card>
168+
</div>
169+
170+
{/* Books in Collection */}
171+
<div className="lg:col-span-3">
172+
<div className="flex justify-between items-center mb-6">
173+
<h2 className="text-2xl font-semibold">Books in Collection</h2>
174+
<Button asChild>
175+
<Link
176+
href={`/admin/collections/${collectionRes.data.id}/manage-books`}
177+
>
178+
<Plus className="mr-2 h-4 w-4" />
179+
Manage Books
180+
</Link>
181+
</Button>
182+
</div>
183+
184+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
185+
{bookRes.data.map((collectionBook) => (
186+
<ListBook book={collectionBook.book!} key={collectionBook.id} />
187+
))}
188+
</div>
189+
190+
{collectionRes.data.book_count === 0 && (
191+
<Card className="p-12 text-center">
192+
<BookOpen className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
193+
<h3 className="text-lg font-medium mb-2">
194+
No books in this collection
195+
</h3>
196+
<p className="text-muted-foreground mb-4">
197+
Start building your collection by adding some books.
198+
</p>
199+
<Button asChild>
200+
<Link
201+
href={`/admin/collections/${collectionRes.data.id}/manage-books`}
202+
>
203+
<Plus className="mr-2 h-4 w-4" />
204+
Add Books
205+
</Link>
206+
</Button>
207+
</Card>
208+
)}
209+
</div>
210+
</div>
211+
</div>
212+
)
213+
}

0 commit comments

Comments
 (0)