1import { type FC, useEffect } from "react";
2import {
3 DialogTitle,
4 DialogContent,
5 Dialog,
6 DialogHeader,
7 Form,
8 Button,
9 Accordion,
10 AccordionContent,
11 AccordionItem,
12 AccordionTrigger,
13 ScrollArea,
14 HStack,
15} from "@marzneshin/components";
16import { useForm } from "react-hook-form";
17import { zodResolver } from "@hookform/resolvers/zod";
18import { useTranslation } from "react-i18next";
19import {
20 HostSchema,
21 getDefaultValues,
22 useHostsCreationMutation,
23 useHostsUpdateMutation,
24 type HostType,
25} from "@marzneshin/features/hosts";
26import {
27 RemarkField,
28 AddressField,
29 PortField,
30 HostField,
31 MuxField,
32 FragmentField,
33 PathField,
34 SecurityFields,
35} from "./fields";
36import { useDialog, MutationDialogProps } from "@marzneshin/hooks";
37
38interface HostMutationDialogProps extends MutationDialogProps<HostType> {
39 inboundId?: number;
40}
41
42const transformFormValue = (values: HostType) => {
43 const port = values.port === "" ? null : values.port;
44 const alpn = values.alpn === "none" ? "" : values.alpn;
45 const fingerprint = values.fingerprint === "none" ? "" : values.fingerprint;
46 return { ...values, alpn, fingerprint, port };
47};
48
49export const HostsMutationDialog: FC<HostMutationDialogProps> = ({
50 inboundId,
51 entity,
52 onClose,
53}) => {
54 const [open, onOpenChange] = useDialog(true);
55 const form = useForm({
56 defaultValues: entity ? entity : getDefaultValues(),
57 resolver: zodResolver(HostSchema),
58 });
59 const updateMutation = useHostsUpdateMutation();
60 const createMutation = useHostsCreationMutation();
61 const { t } = useTranslation();
62
63 const submit = (values: HostType) => {
64 const host = transformFormValue(values);
65 if (entity && entity.id !== undefined) {
66 updateMutation.mutate({ hostId: entity.id, host });
67 onOpenChange(false);
68 } else if (inboundId !== undefined) {
69 createMutation.mutate({ inboundId, host });
70 onOpenChange(false);
71 }
72 };
73
74 useEffect(() => {
75 if (!open) onClose();
76 }, [open, onClose]);
77
78 useEffect(() => {
79 if (entity) form.reset(entity);
80 else form.reset(getDefaultValues());
81 }, [entity, form, open]);
82
83 return (
84 <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
85 <DialogContent className="min-w-full h-full md:h-auto md:min-w-[32rem]">
86 <ScrollArea className="flex flex-col h-full p-0">
87 <DialogHeader className="mb-3">
88 <DialogTitle className="text-primary">
89 {entity
90 ? t("page.hosts.dialogs.edition.title")
91 : t("page.hosts.dialogs.creation.title")}
92 </DialogTitle>
93 </DialogHeader>
94 <Form {...form}>
95 <form
96 onSubmit={form.handleSubmit(submit)}
97 className="h-full flex flex-col justify-between"
98 >
99 <RemarkField />
100 <HStack className="gap-2 items-start">
101 <AddressField />
102 <PortField />
103 </HStack>
104 <Accordion type="single" collapsible>
105 <AccordionItem value="item-1">
106 <AccordionTrigger>{t("advanced-options")}</AccordionTrigger>
107 <AccordionContent>
108 <div className="flex flex-row gap-2 items-start">
109 <HostField />
110 <PathField />
111 </div>
112 <FragmentField />
113 <MuxField />
114 <SecurityFields />
115 </AccordionContent>
116 </AccordionItem>
117 </Accordion>
118 <Button
119 className="mt-3 w-full font-semibold"
120 type="submit"
121 disabled={form.formState.isSubmitting}
122 >
123 {t("submit")}
124 </Button>
125 </form>
126 </Form>
127 </ScrollArea>
128 </DialogContent>
129 </Dialog>
130 );
131};