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