Skip to content

Commit 6f6041b

Browse files
committed
feat: book create image upload
1 parent 6814b59 commit 6f6041b

File tree

4 files changed

+130
-1
lines changed

4 files changed

+130
-1
lines changed

components/books/BookForm.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ import { Library } from '@/lib/types/library'
3333
import { useState, useEffect, useCallback } from 'react'
3434
import { Input } from '@/components/ui/input'
3535
import { Book } from '@/lib/types/book'
36+
import Image from 'next/image'
37+
import { ImageUploader } from '../common/ImageUploader'
3638

3739
export type BookFormValues = Pick<
3840
Book,
39-
'title' | 'author' | 'year' | 'code' | 'library_id'
41+
'title' | 'author' | 'year' | 'code' | 'library_id' | 'cover'
4042
>
4143

4244
type BookFormProps = {
@@ -70,6 +72,7 @@ const FormSchema = z.object({
7072
required_error: 'Please select the library.',
7173
})
7274
.uuid(),
75+
cover: z.string().optional(),
7376
})
7477

7578
export const BookForm: React.FC<BookFormProps> = ({
@@ -104,6 +107,8 @@ export const BookForm: React.FC<BookFormProps> = ({
104107
form.reset()
105108
}, [form])
106109

110+
const title = form.watch('title')
111+
107112
return (
108113
<div className="grid place-items-center">
109114
<Form {...form}>
@@ -246,6 +251,37 @@ export const BookForm: React.FC<BookFormProps> = ({
246251
)}
247252
/>
248253

254+
{initialData.cover && (
255+
<Image
256+
className="w-12 h-auto rounded col-span-2"
257+
src={initialData.cover}
258+
alt={initialData.title}
259+
width={50}
260+
height={50}
261+
/>
262+
)}
263+
264+
{title && (
265+
<FormField
266+
control={form.control}
267+
name="cover"
268+
render={({ field }) => (
269+
<FormItem className="flex flex-col">
270+
<FormLabel>Cover Image URL</FormLabel>
271+
<FormControl>
272+
<ImageUploader
273+
{...field}
274+
imageName={title}
275+
value={field.value}
276+
onChange={field.onChange}
277+
/>
278+
</FormControl>
279+
<FormMessage />
280+
</FormItem>
281+
)}
282+
/>
283+
)}
284+
249285
<Button type="reset" variant="ghost" onClick={onReset}>
250286
Reset
251287
</Button>

components/books/book-edit-form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const BookEditForm: React.FC<{ book: BookDetail }> = ({ book }) => {
1111
year: book.year,
1212
code: book.code,
1313
library_id: book.library_id,
14+
cover: book.cover,
1415
}
1516
// const router = useRouter()
1617

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { getUploadURL } from '@/lib/api/file'
2+
import { Input } from '../ui/input'
3+
import { useState } from 'react'
4+
import Image from 'next/image'
5+
6+
export const ImageUploader: React.FC<
7+
{
8+
onChange: (path: string) => void
9+
value?: string
10+
imageName: string
11+
} & Parameters<typeof Input>[0]
12+
> = ({ onChange, value, imageName, ...props }) => {
13+
const [url, setUrl] = useState<string>('')
14+
const [preview, setPreview] = useState<string>()
15+
16+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
17+
const file = event.target.files?.[0]
18+
if (file) {
19+
const reader = new FileReader()
20+
reader.onloadend = () => {
21+
setPreview(reader.result as string)
22+
onChange(imageName)
23+
}
24+
reader.readAsDataURL(file)
25+
// upload file to url with PUT request
26+
if (url) {
27+
fetch(url, {
28+
method: 'PUT',
29+
headers: {
30+
'Content-Type': file.type,
31+
},
32+
body: file,
33+
})
34+
.then((response) => {
35+
if (!response.ok) {
36+
throw new Error('Failed to upload image')
37+
}
38+
return response.text()
39+
})
40+
.then((data) => {
41+
console.log('Image uploaded successfully:', data)
42+
})
43+
.catch((error) => {
44+
console.error('Error uploading image:', error)
45+
})
46+
}
47+
}
48+
}
49+
50+
const onClick = async () => {
51+
const res = await getUploadURL({ name: imageName })
52+
setUrl(res.url)
53+
}
54+
55+
return (
56+
<>
57+
{preview && (
58+
<Image
59+
className="w-12 h-auto rounded col-span-2"
60+
src={preview}
61+
alt={imageName}
62+
width={50}
63+
height={50}
64+
/>
65+
)}
66+
<Input
67+
type="file"
68+
accept="image/*"
69+
onClick={onClick}
70+
onChange={handleFileChange}
71+
value={''}
72+
/>
73+
</>
74+
)
75+
}

lib/api/file.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { BASE_URL } from '@/lib/api/common'
2+
3+
const FILES_URL = `${BASE_URL}/files`
4+
5+
export const getUploadURL = async (query: {
6+
name: string
7+
}): Promise<{ url: string }> => {
8+
const url = new URL(`${FILES_URL}/upload`)
9+
Object.entries(query).forEach(([key, value]) => {
10+
if (value) {
11+
url.searchParams.append(key, String(value))
12+
}
13+
})
14+
15+
const response = await fetch(url.toString())
16+
return response.json()
17+
}

0 commit comments

Comments
 (0)