gitscope
← marzneshin / v0.4.1 / 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 { 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};