Skip to content

Commit a264aa9

Browse files
committed
feat: library create & update form - authentication
1 parent f1793f2 commit a264aa9

File tree

9 files changed

+364
-29
lines changed

9 files changed

+364
-29
lines changed

app/libraries/[id]/edit/page.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { LibraryEditForm } from '@/components/libraries/lib-edit-form'
2+
import {
3+
Breadcrumb,
4+
BreadcrumbItem,
5+
BreadcrumbLink,
6+
BreadcrumbList,
7+
BreadcrumbPage,
8+
BreadcrumbSeparator,
9+
} from '@/components/ui/breadcrumb'
10+
import { getLibrary } from '@/lib/api/library'
11+
import { Verify } from '@/lib/firebase/firebase'
12+
import { cookies } from 'next/headers'
13+
import Link from 'next/link'
14+
15+
export default async function EditPage({
16+
params,
17+
}: {
18+
params: Promise<{ id: string }>
19+
}) {
20+
const { id } = await params
21+
22+
await Verify({ from: `/libraries/${id}/edit` })
23+
24+
const [libRes] = await Promise.all([getLibrary({ id })])
25+
26+
if ('error' in libRes) {
27+
console.log({ libRes })
28+
return <div>{JSON.stringify(libRes.message)}</div>
29+
}
30+
31+
const cookieStore = await cookies()
32+
const sessionName = process.env.SESSION_COOKIE_NAME as string
33+
const session = cookieStore.get(sessionName)
34+
35+
return (
36+
<div>
37+
<h1 className="text-2xl font-semibold">{libRes.data.name}</h1>
38+
<Breadcrumb>
39+
<BreadcrumbList>
40+
<BreadcrumbItem>
41+
<Link href="/" passHref legacyBehavior>
42+
<BreadcrumbLink href="/">Home</BreadcrumbLink>
43+
</Link>
44+
</BreadcrumbItem>
45+
<BreadcrumbSeparator />
46+
<BreadcrumbItem>
47+
<Link href="/libraries" passHref legacyBehavior>
48+
<BreadcrumbLink>Libraries</BreadcrumbLink>
49+
</Link>
50+
</BreadcrumbItem>
51+
<BreadcrumbSeparator />
52+
53+
<BreadcrumbItem>
54+
<BreadcrumbPage>{libRes.data.name}</BreadcrumbPage>
55+
</BreadcrumbItem>
56+
</BreadcrumbList>
57+
</Breadcrumb>
58+
59+
<LibraryEditForm library={libRes.data} token={session?.value as string} />
60+
</div>
61+
)
62+
}

app/libraries/new/page.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import { Button } from '@/components/ui/button'
2-
import { Input } from '@/components/ui/input'
3-
import { createLibrary } from '@/lib/api/library'
4-
51
import {
62
Breadcrumb,
73
BreadcrumbItem,
@@ -11,20 +7,16 @@ import {
117
BreadcrumbSeparator,
128
} from '@/components/ui/breadcrumb'
139
import Link from 'next/link'
14-
import { redirect } from 'next/navigation'
15-
16-
export default function NewLibrary() {
17-
async function create(formData: FormData) {
18-
'use server'
19-
20-
const name = formData.get('name') as string
10+
import { LibraryCreateForm } from '@/components/libraries/lib-create-form'
11+
import { Verify } from '@/lib/firebase/firebase'
12+
import { cookies } from 'next/headers'
2113

22-
await createLibrary({
23-
name,
24-
})
14+
export default async function NewLibrary() {
15+
await Verify({ from: '/libraries/new' })
2516

26-
redirect('/libraries')
27-
}
17+
const cookieStore = await cookies()
18+
const sessionName = process.env.SESSION_COOKIE_NAME as string
19+
const session = cookieStore.get(sessionName)
2820

2921
return (
3022
<div className="space-y-4">
@@ -49,10 +41,7 @@ export default function NewLibrary() {
4941
</BreadcrumbList>
5042
</Breadcrumb>
5143

52-
<form action={create} className="space-y-4 md:max-w-[40%]">
53-
<Input name="name" placeholder="Name" required />
54-
<Button type="submit">Create</Button>
55-
</form>
44+
<LibraryCreateForm token={session?.value as string} />
5645
</div>
5746
)
5847
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
'use client'
2+
3+
import { zodResolver } from '@hookform/resolvers/zod'
4+
import { useForm } from 'react-hook-form'
5+
import { z } from 'zod'
6+
import {
7+
Form,
8+
// FormDescription,
9+
FormField,
10+
FormItem,
11+
FormLabel,
12+
FormMessage,
13+
} from '@/components/ui/form'
14+
import { Input } from '../ui/input'
15+
import { Textarea } from '../ui/textarea'
16+
import { Button } from '../ui/button'
17+
import { Library } from '@/lib/types/library'
18+
19+
export type LibraryFormValues = Pick<
20+
Library,
21+
'name' | 'logo' | 'address' | 'phone' | 'email' | 'description'
22+
>
23+
24+
type LibraryFormProps = {
25+
initialData: LibraryFormValues
26+
onSubmit(data: LibraryFormValues): void
27+
}
28+
29+
const FormSchema = z.object({
30+
name: z.string().nonempty({ message: 'Name is required' }),
31+
logo: z.string().optional(),
32+
address: z.string().optional(),
33+
phone: z.string().optional(),
34+
email: z.string().optional(),
35+
description: z.string().optional(),
36+
})
37+
38+
export const LibraryForm: React.FC<LibraryFormProps> = ({
39+
initialData,
40+
onSubmit,
41+
}) => {
42+
const form = useForm<z.infer<typeof FormSchema>>({
43+
resolver: zodResolver(FormSchema),
44+
defaultValues: initialData,
45+
})
46+
47+
return (
48+
<Form {...form}>
49+
<form onSubmit={form.handleSubmit(onSubmit)}>
50+
<div className="mx-auto max-w-sm space-y-6 md:space-y-0 md:grid md:grid-cols-2 md:gap-2">
51+
<FormField
52+
control={form.control}
53+
name="name"
54+
render={({ field }) => (
55+
<FormItem className="flex flex-col">
56+
<FormLabel>Name</FormLabel>
57+
<Input placeholder="Name" {...field} />
58+
<FormMessage />
59+
</FormItem>
60+
)}
61+
/>
62+
63+
<FormField
64+
control={form.control}
65+
name="logo"
66+
render={({ field }) => (
67+
<FormItem className="flex flex-col">
68+
<FormLabel>Logo</FormLabel>
69+
<Input placeholder="Logo URL" {...field} />
70+
<FormMessage />
71+
</FormItem>
72+
)}
73+
/>
74+
75+
<FormField
76+
control={form.control}
77+
name="address"
78+
render={({ field }) => (
79+
<FormItem className="flex flex-col col-span-2">
80+
<FormLabel>Address</FormLabel>
81+
<Input placeholder="Address" {...field} />
82+
<FormMessage />
83+
</FormItem>
84+
)}
85+
/>
86+
87+
<FormField
88+
control={form.control}
89+
name="email"
90+
render={({ field }) => (
91+
<FormItem className="flex flex-col">
92+
<FormLabel>Email</FormLabel>
93+
<Input placeholder="Email" type="email" {...field} />
94+
<FormMessage />
95+
</FormItem>
96+
)}
97+
/>
98+
99+
<FormField
100+
control={form.control}
101+
name="phone"
102+
render={({ field }) => (
103+
<FormItem className="flex flex-col">
104+
<FormLabel>Phone</FormLabel>
105+
<Input placeholder="Phone" type="tel" {...field} />
106+
<FormMessage />
107+
</FormItem>
108+
)}
109+
/>
110+
111+
<FormField
112+
control={form.control}
113+
name="description"
114+
render={({ field }) => (
115+
<FormItem className="flex flex-col col-span-2">
116+
<FormLabel>Description</FormLabel>
117+
<Textarea placeholder="About" {...field} rows={3} />
118+
<FormMessage />
119+
</FormItem>
120+
)}
121+
/>
122+
123+
<Button
124+
type="submit"
125+
className="w-full md:w-auto col-span-2 place-self-end"
126+
disabled={form.formState.isSubmitting || !form.formState.isDirty}
127+
>
128+
Submit
129+
</Button>
130+
</div>
131+
</form>
132+
</Form>
133+
)
134+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use client'
2+
3+
import { useRouter } from 'next/navigation'
4+
import { LibraryForm, LibraryFormValues } from './LibraryForm'
5+
import { createLibrary } from '@/lib/api/library'
6+
import { toast } from '@/components/hooks/use-toast'
7+
8+
const initialData: LibraryFormValues = {
9+
name: '',
10+
logo: '',
11+
address: '',
12+
phone: '',
13+
email: '',
14+
description: '',
15+
}
16+
17+
export const LibraryCreateForm: React.FC<{ token: string }> = ({ token }) => {
18+
const router = useRouter()
19+
20+
function onSubmit(data: LibraryFormValues) {
21+
createLibrary(data, {
22+
headers: {
23+
Authorization: `Bearer ${token}`,
24+
},
25+
})
26+
.then(console.log)
27+
.then(() => {
28+
toast({
29+
title: 'Library Created',
30+
})
31+
router.push('/libraries')
32+
})
33+
.catch((e) => {
34+
toast({
35+
title: 'Error',
36+
description: e?.error,
37+
variant: 'destructive',
38+
})
39+
})
40+
}
41+
return <LibraryForm initialData={initialData} onSubmit={onSubmit} />
42+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use client'
2+
3+
import { Library } from '@/lib/types/library'
4+
import { LibraryForm, LibraryFormValues } from './LibraryForm'
5+
import { updateLibrary } from '@/lib/api/library'
6+
import { toast } from '@/components/hooks/use-toast'
7+
import { useRouter } from 'next/navigation'
8+
9+
export const LibraryEditForm: React.FC<{ library: Library; token: string }> = ({
10+
library,
11+
token,
12+
}) => {
13+
const initialData = {
14+
name: library.name,
15+
logo: library.logo ?? '',
16+
address: library.address ?? '',
17+
phone: library.phone ?? '',
18+
email: library.email ?? '',
19+
description: library.description ?? '',
20+
}
21+
22+
const router = useRouter()
23+
24+
function onSubmit(data: LibraryFormValues) {
25+
updateLibrary(library.id, data, {
26+
headers: {
27+
Authorization: `Bearer ${token}`,
28+
},
29+
})
30+
.then(console.log)
31+
.then(() => {
32+
toast({
33+
title: 'Library Updated',
34+
})
35+
router.push(`/libraries/${library.id}`)
36+
})
37+
.catch((e) => {
38+
toast({
39+
title: 'Error',
40+
description: e?.error,
41+
variant: 'destructive',
42+
})
43+
})
44+
}
45+
46+
return <LibraryForm initialData={initialData} onSubmit={onSubmit} />
47+
}

components/ui/textarea.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from 'react'
2+
3+
import { cn } from '@/lib/utils'
4+
5+
const Textarea = React.forwardRef<
6+
HTMLTextAreaElement,
7+
React.ComponentProps<'textarea'>
8+
>(({ className, ...props }, ref) => {
9+
return (
10+
<textarea
11+
className={cn(
12+
'flex w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
13+
className
14+
)}
15+
ref={ref}
16+
{...props}
17+
/>
18+
)
19+
})
20+
Textarea.displayName = 'Textarea'
21+
22+
export { Textarea }

0 commit comments

Comments
 (0)