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