Skip to content

Commit 597fee7

Browse files
committed
feat: membership page
1 parent 72d72d9 commit 597fee7

File tree

4 files changed

+548
-1
lines changed

4 files changed

+548
-1
lines changed
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
'use client'
2+
3+
import { zodResolver } from '@hookform/resolvers/zod'
4+
import { Check, ChevronsUpDown } from 'lucide-react'
5+
import { useForm } from 'react-hook-form'
6+
import { z } from 'zod'
7+
8+
import { cn } from '@/lib/utils'
9+
import { toast } from '@/components/hooks/use-toast'
10+
import { Button } from '@/components/ui/button'
11+
import {
12+
Command,
13+
CommandGroup,
14+
CommandInput,
15+
CommandItem,
16+
CommandList,
17+
} from '@/components/ui/command'
18+
import {
19+
Form,
20+
FormControl,
21+
FormField,
22+
FormItem,
23+
FormLabel,
24+
FormMessage,
25+
} from '@/components/ui/form'
26+
import {
27+
Popover,
28+
PopoverContent,
29+
PopoverTrigger,
30+
} from '@/components/ui/popover'
31+
import {
32+
Breadcrumb,
33+
BreadcrumbItem,
34+
BreadcrumbLink,
35+
BreadcrumbList,
36+
BreadcrumbPage,
37+
BreadcrumbSeparator,
38+
} from '@/components/ui/breadcrumb'
39+
import Link from 'next/link'
40+
import { useCallback, useEffect, useState } from 'react'
41+
import { getListLibraries } from '@/lib/api/library'
42+
import { getListMemberships } from '@/lib/api/membership'
43+
import { useRouter } from 'next/navigation'
44+
import { Library } from '@/lib/types/library'
45+
import { User } from '@/lib/types/user'
46+
import { getListUsers } from '@/lib/api/user'
47+
import { Membership } from '@/lib/types/membership'
48+
import { createSubscription } from '@/lib/api/subscription'
49+
50+
const FormSchema = z.object({
51+
user_id: z
52+
.string({
53+
required_error: 'Please select user.',
54+
})
55+
.uuid(),
56+
library_id: z
57+
.string({
58+
required_error: 'Please select a library.',
59+
})
60+
.uuid(),
61+
membership_id: z
62+
.string({
63+
required_error: 'Please select a membership.',
64+
})
65+
.uuid(),
66+
})
67+
68+
export default function ComboboxForm() {
69+
const router = useRouter()
70+
const form = useForm<z.infer<typeof FormSchema>>({
71+
resolver: zodResolver(FormSchema),
72+
defaultValues: {
73+
user_id: '',
74+
membership_id: '',
75+
library_id: '',
76+
},
77+
})
78+
79+
function onSubmit(data: z.infer<typeof FormSchema>) {
80+
createSubscription(data)
81+
.then(console.log)
82+
.then(() => {
83+
toast({
84+
title: 'Purchased Membership',
85+
})
86+
router.push('/subscriptions')
87+
})
88+
.catch((e) => {
89+
toast({
90+
title: 'Error',
91+
description: e?.error,
92+
variant: 'destructive',
93+
})
94+
})
95+
}
96+
97+
const onReset = useCallback(() => {
98+
form.reset()
99+
}, [form])
100+
101+
const [userQ, setUserQ] = useState('')
102+
const [users, setUsers] = useState<User[]>([])
103+
104+
useEffect(() => {
105+
getListUsers({
106+
limit: 20,
107+
name: userQ,
108+
}).then((res) => {
109+
if ('error' in res) {
110+
toast({
111+
title: 'Error',
112+
description: res.message,
113+
})
114+
return
115+
}
116+
setUsers(res.data)
117+
})
118+
}, [userQ])
119+
120+
const [libQ, setLibQ] = useState('')
121+
const [libs, setLibs] = useState<Library[]>([])
122+
123+
useEffect(() => {
124+
getListLibraries({
125+
limit: 20,
126+
name: libQ,
127+
}).then((res) => {
128+
if ('error' in res) {
129+
toast({
130+
title: 'Error',
131+
description: res.message,
132+
})
133+
return
134+
}
135+
setLibs(res.data)
136+
})
137+
}, [libQ])
138+
139+
const [memQ, setMemQ] = useState('')
140+
const [mems, setMems] = useState<Membership[]>([])
141+
142+
const libID = form.getValues('library_id')
143+
144+
useEffect(() => {
145+
getListMemberships({
146+
limit: 20,
147+
name: memQ,
148+
library_ids: libID,
149+
}).then((res) => {
150+
if ('error' in res) {
151+
toast({
152+
title: 'Error',
153+
description: res.message,
154+
})
155+
return
156+
}
157+
setMems(res.data)
158+
})
159+
}, [memQ, libID])
160+
161+
return (
162+
<div className="grid grid-rows-2">
163+
<h1 className="text-2xl font-semibold">Purchase a Membership</h1>
164+
<Breadcrumb>
165+
<BreadcrumbList>
166+
<BreadcrumbItem>
167+
<Link href="/" passHref legacyBehavior>
168+
<BreadcrumbLink>Home</BreadcrumbLink>
169+
</Link>
170+
</BreadcrumbItem>
171+
<BreadcrumbSeparator />
172+
<BreadcrumbItem>
173+
<Link href="/subscriptions" passHref legacyBehavior>
174+
<BreadcrumbLink>Subscriptions</BreadcrumbLink>
175+
</Link>
176+
</BreadcrumbItem>
177+
<BreadcrumbSeparator />
178+
179+
<BreadcrumbItem>
180+
<BreadcrumbPage>New</BreadcrumbPage>
181+
</BreadcrumbItem>
182+
</BreadcrumbList>
183+
</Breadcrumb>
184+
<div className="grid place-items-center">
185+
<Form {...form}>
186+
<form
187+
onSubmit={form.handleSubmit(onSubmit)}
188+
className="space-y-6 md:space-y-0 md:grid md:grid-cols-2 md:gap-2"
189+
>
190+
<FormField
191+
control={form.control}
192+
name="user_id"
193+
render={({ field }) => (
194+
<FormItem className="flex flex-col col-span-2">
195+
<FormLabel>User</FormLabel>
196+
<Popover>
197+
<PopoverTrigger asChild>
198+
<FormControl>
199+
<Button
200+
variant="outline"
201+
role="combobox"
202+
className={cn(
203+
'w-full justify-between',
204+
!field.value && 'text-muted-foreground'
205+
)}
206+
>
207+
{field.value
208+
? users.find((user) => user.id === field.value)
209+
?.name
210+
: 'Select user'}
211+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
212+
</Button>
213+
</FormControl>
214+
</PopoverTrigger>
215+
<PopoverContent className="w-[200px] p-0">
216+
<Command>
217+
<CommandInput
218+
onValueChange={setUserQ}
219+
value={userQ}
220+
placeholder="Search user name..."
221+
/>
222+
<CommandList>
223+
{/* <CommandEmpty>No user found.</CommandEmpty> */}
224+
<CommandGroup forceMount>
225+
{users.map((user) => (
226+
<CommandItem
227+
value={user.id}
228+
key={user.id}
229+
onSelect={() => {
230+
form.setValue('user_id', user.id)
231+
}}
232+
>
233+
{user.name}
234+
<Check
235+
className={cn(
236+
'ml-auto',
237+
user.id === field.value
238+
? 'opacity-100'
239+
: 'opacity-0'
240+
)}
241+
/>
242+
</CommandItem>
243+
))}
244+
</CommandGroup>
245+
</CommandList>
246+
</Command>
247+
</PopoverContent>
248+
</Popover>
249+
{/* <FormDescription>
250+
This is the language that will be used in the dashboard.
251+
</FormDescription> */}
252+
<FormMessage />
253+
</FormItem>
254+
)}
255+
/>
256+
257+
<FormField
258+
control={form.control}
259+
name="library_id"
260+
render={({ field }) => (
261+
<FormItem className="flex flex-col">
262+
<FormLabel>Library</FormLabel>
263+
<Popover>
264+
<PopoverTrigger asChild>
265+
<FormControl>
266+
<Button
267+
variant="outline"
268+
role="combobox"
269+
className={cn(
270+
'w-full justify-between',
271+
!field.value && 'text-muted-foreground'
272+
)}
273+
>
274+
{field.value
275+
? libs.find((lib) => lib.id === field.value)?.name
276+
: 'Select Library'}
277+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
278+
</Button>
279+
</FormControl>
280+
</PopoverTrigger>
281+
<PopoverContent className="w-full p-0">
282+
<Command>
283+
<CommandInput
284+
onValueChange={setLibQ}
285+
value={libQ}
286+
placeholder="Search library name..."
287+
/>
288+
<CommandList>
289+
{/* <CommandEmpty>No user found.</CommandEmpty> */}
290+
<CommandGroup forceMount>
291+
{libs.map((lib) => (
292+
<CommandItem
293+
value={lib.id}
294+
key={lib.id}
295+
onSelect={() => {
296+
form.setValue('library_id', lib.id)
297+
}}
298+
>
299+
{lib.name}
300+
<Check
301+
className={cn(
302+
'ml-auto',
303+
lib.id === field.value
304+
? 'opacity-100'
305+
: 'opacity-0'
306+
)}
307+
/>
308+
</CommandItem>
309+
))}
310+
</CommandGroup>
311+
</CommandList>
312+
</Command>
313+
</PopoverContent>
314+
</Popover>
315+
{/* <FormDescription>
316+
This is the language that will be used in the dashboard.
317+
</FormDescription> */}
318+
<FormMessage />
319+
</FormItem>
320+
)}
321+
/>
322+
323+
<FormField
324+
control={form.control}
325+
name="membership_id"
326+
render={({ field }) => (
327+
<FormItem className="flex flex-col">
328+
<FormLabel>Membership</FormLabel>
329+
<Popover>
330+
<PopoverTrigger asChild>
331+
<FormControl>
332+
<Button
333+
variant="outline"
334+
role="combobox"
335+
className={cn(
336+
'w-[200px] justify-between',
337+
!field.value && 'text-muted-foreground'
338+
)}
339+
>
340+
{field.value
341+
? mems.find((mem) => mem.id === field.value)?.name
342+
: 'Select membership'}
343+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
344+
</Button>
345+
</FormControl>
346+
</PopoverTrigger>
347+
<PopoverContent className="w-[200px] p-0">
348+
<Command>
349+
<CommandInput
350+
onValueChange={setMemQ}
351+
value={memQ}
352+
placeholder="Search membership name..."
353+
/>
354+
<CommandList>
355+
{/* <CommandEmpty>No user found.</CommandEmpty> */}
356+
<CommandGroup forceMount>
357+
{mems.map((mem) => (
358+
<CommandItem
359+
value={mem.id}
360+
key={mem.id}
361+
onSelect={() => {
362+
form.setValue('membership_id', mem.id)
363+
}}
364+
>
365+
{mem.name}
366+
<Check
367+
className={cn(
368+
'ml-auto',
369+
mem.id === field.value
370+
? 'opacity-100'
371+
: 'opacity-0'
372+
)}
373+
/>
374+
</CommandItem>
375+
))}
376+
</CommandGroup>
377+
</CommandList>
378+
</Command>
379+
</PopoverContent>
380+
</Popover>
381+
{/* <FormDescription>
382+
This is the language that will be used in the dashboard.
383+
</FormDescription> */}
384+
<FormMessage />
385+
</FormItem>
386+
)}
387+
/>
388+
389+
<Button type="reset" variant="ghost" onClick={onReset}>
390+
Reset
391+
</Button>
392+
<Button type="submit">Create</Button>
393+
</form>
394+
</Form>
395+
</div>
396+
</div>
397+
)
398+
}

0 commit comments

Comments
 (0)