gitscope
← marzneshin / v0.3.2 / dashboard/src/features/hosts/dialogs/mutation/index.tsx
https://dev.kronoz.win/r/marzneshin.git
dashboard/src/features/hosts/dialogs/mutation/index.tsx · TypeScript
Blame Raw
  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};