1
2
3import {
4 DialogTitle,
5 DialogContent,
6 Dialog,
7 DialogHeader,
8 Form,
9 FormItem,
10 FormControl,
11 FormMessage,
12 FormLabel,
13 Input,
14 FormField,
15 Button,
16 Checkbox,
17 Badge
18} from '@marzneshin/components'
19import { FC } from 'react'
20import {
21 ServiceType,
22 useServicesCreationMutation,
23 useServicesUpdateMutation,
24} from '@marzneshin/features/services'
25import { useInboundsQuery } from '@marzneshin/features/inbounds'
26import { useTranslation } from 'react-i18next'
27import { z } from 'zod'
28import { cn } from '@marzneshin/utils'
29import { Box, GlobeLock } from 'lucide-react'
30import { useMutationDialog } from '@marzneshin/hooks'
31
32export const ServiceSchema = z.object({
33 id: z.number().optional(),
34 inbounds: z.array(z.number()).refine((value) => value.some((item) => item), {
35 message: "You have to select at least one inbound.",
36 }),
37 name: z.string().trim().min(1)
38})
39type ServiceCreateType = z.infer<typeof ServiceSchema>
40
41interface MutationDialogProps {
42 entity: ServiceType | null
43 open: boolean
44 onOpenChange: (state: boolean) => void
45}
46
47const getDefaultValues = (): ServiceCreateType => ({
48 name: '',
49 inbounds: [],
50})
51
52export const MutationDialog: FC<MutationDialogProps> = ({ entity, open, onOpenChange }) => {
53 const isEditing = entity !== null
54 const updateMutation = useServicesUpdateMutation();
55 const createMutation = useServicesCreationMutation();
56 const { data: inbounds } = useInboundsQuery()
57 const { form, handleSubmit } = useMutationDialog({
58 entity,
59 updateMutation,
60 createMutation,
61 getDefaultValue: getDefaultValues,
62 schema: ServiceSchema,
63 onOpenChange,
64 })
65 const { t } = useTranslation()
66 return (
67 <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
68 <DialogContent>
69 <DialogHeader>
70 <DialogTitle className="text-primary">
71 {isEditing ?
72 t('page.services.dialogs.edition.title') :
73 t('page.services.dialogs.creation.title')}
74 </DialogTitle>
75 </DialogHeader>
76 <Form {...form} >
77 <form onSubmit={handleSubmit} >
78 <FormField
79 control={form.control}
80 name="name"
81 render={({ field }) => (
82 <FormItem>
83 <FormLabel>{t('name')}</FormLabel>
84 <FormControl>
85 <Input {...field} />
86 </FormControl>
87 <FormMessage />
88 </FormItem>
89 )}
90 />
91 <FormField
92 control={form.control}
93 name="inbounds"
94 render={() => (
95 <FormItem>
96 <FormLabel>{t('inbounds')}</FormLabel>
97 {inbounds.map((inbound) => (
98 <FormField
99 key={inbound.id}
100 control={form.control}
101 name="inbounds"
102 render={({ field }) => (
103 <FormItem key={inbound.id} >
104 <FormControl>
105 <div
106 className={cn("flex flex-row items-center p-3 space-y-0 space-x-3 rounded-md border", { "bg-secondary": field.value?.includes(inbound.id) })}>
107 <Checkbox
108 checked={field.value?.includes(inbound.id)}
109 onCheckedChange={(checked) => {
110 return checked
111 ? field.onChange([...field.value, inbound.id])
112 : field.onChange(
113 field.value?.filter(
114 (value: number) => value !== inbound.id
115 )
116 )
117 }}
118 />
119 <FormLabel className="flex flex-row justify-between items-center w-full">
120 {inbound.tag}
121 <div className="flex gap-2">
122 <Badge className="py-1 px-2"> <GlobeLock className="mr-1 w-4" /> {inbound.protocol} </Badge>
123 <Badge className="py-1 px-2"> <Box className="mr-1 w-4" /> {inbound.node.name} </Badge>
124 </div>
125 </FormLabel>
126 </div>
127 </FormControl>
128 </FormItem>
129 )}
130 />
131 ))}
132 <FormMessage />
133 </FormItem>
134 )}
135 />
136 <Button
137 className="mt-3 w-full font-semibold"
138 type="submit"
139 disabled={form.formState.isSubmitting}>
140 {t('submit')}
141 </Button>
142 </form>
143 </Form>
144 </DialogContent>
145 </Dialog >
146 )
147}