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