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