Selaa lähdekoodia

feat(frontend): InboundFormModal.new.tsx skeleton (Pattern A)

First commit of the sibling-file modal rewrite. The new modal mounts
Form.useForm<InboundFormValues>, hydrates via rawInboundToFormValues on
open (edit) or buildAddModeValues (add), runs validateFields + safeParse
on submit, and posts the formValuesToWirePayload result. No tabs yet —
the modal body shows a WIP placeholder.

The file is not imported anywhere; the existing InboundFormModal.tsx
remains the one InboundsPage renders. Build, lint, and 280 tests stay
green. Subsequent commits add the basic / sniffing / protocol / stream /
security / advanced / fallbacks sections; the atomic import swap in
InboundsPage.tsx lands last.
MHSanaei 21 tuntia sitten
vanhempi
sitoutus
b10e0d0acd
1 muutettua tiedostoa jossa 142 lisäystä ja 0 poistoa
  1. 142 0
      frontend/src/pages/inbounds/InboundFormModal.new.tsx

+ 142 - 0
frontend/src/pages/inbounds/InboundFormModal.new.tsx

@@ -0,0 +1,142 @@
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Form, Modal, Typography, message } from 'antd';
+
+import { HttpUtil, RandomUtil } from '@/utils';
+import {
+  rawInboundToFormValues,
+  formValuesToWirePayload,
+} from '@/lib/xray/inbound-form-adapter';
+import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults';
+import { InboundFormSchema, type InboundFormValues } from '@/schemas/forms/inbound-form';
+import type { DBInbound } from '@/models/dbinbound';
+import type { NodeRecord } from '@/api/queries/useNodesQuery';
+
+// Pattern A rewrite of InboundFormModal. Built as a sibling file so the
+// build stays green while the rewrite progresses section by section. The
+// old InboundFormModal.tsx continues to be the one InboundsPage renders
+// until the atomic swap at the end of the rewrite (per Core Decision 7 in
+// the architecture spec).
+//
+// Current state: skeleton only. The form holds the full InboundFormValues
+// shape via setFieldsValue on open; validateFields + safeParse + adapter
+// produce the wire payload on submit. Tabs are not yet wired — the modal
+// body shows a WIP placeholder.
+
+const { Text } = Typography;
+
+interface InboundFormModalProps {
+  open: boolean;
+  onClose: () => void;
+  onSaved: () => void;
+  mode: 'add' | 'edit';
+  dbInbound: DBInbound | null;
+  dbInbounds: DBInbound[];
+  availableNodes?: NodeRecord[];
+}
+
+function buildAddModeValues(): InboundFormValues {
+  const settings = createDefaultInboundSettings('vless') ?? undefined;
+  return rawInboundToFormValues({
+    protocol: 'vless',
+    settings,
+    streamSettings: { network: 'tcp', security: 'none' },
+    sniffing: {},
+    port: RandomUtil.randomInteger(10000, 60000),
+    listen: '',
+    tag: '',
+    enable: true,
+    trafficReset: 'never',
+  });
+}
+
+export default function InboundFormModalNew({
+  open,
+  onClose,
+  onSaved,
+  mode,
+  dbInbound,
+}: InboundFormModalProps) {
+  const { t } = useTranslation();
+  const [messageApi, messageContextHolder] = message.useMessage();
+  const [form] = Form.useForm<InboundFormValues>();
+  const [saving, setSaving] = useState(false);
+
+  useEffect(() => {
+    if (!open) return;
+    const initial = mode === 'edit' && dbInbound
+      ? rawInboundToFormValues(dbInbound)
+      : buildAddModeValues();
+    form.setFieldsValue(initial);
+  }, [open, mode, dbInbound, form]);
+
+  const submit = async () => {
+    let values: InboundFormValues;
+    try {
+      values = await form.validateFields();
+    } catch {
+      return;
+    }
+    const parsed = InboundFormSchema.safeParse(values);
+    if (!parsed.success) {
+      const issue = parsed.error.issues[0];
+      messageApi.error(
+        t(issue?.message ?? 'somethingWentWrong', {
+          defaultValue: issue?.message ?? 'invalid',
+        }),
+      );
+      return;
+    }
+    setSaving(true);
+    try {
+      const payload = formValuesToWirePayload(parsed.data);
+      const url = mode === 'edit' && dbInbound
+        ? `/panel/api/inbounds/update/${dbInbound.id}`
+        : '/panel/api/inbounds/add';
+      const msg = await HttpUtil.post(url, payload);
+      if (msg?.success) {
+        onSaved();
+        onClose();
+      }
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  const title = mode === 'edit'
+    ? t('pages.inbounds.modifyInbound')
+    : t('pages.inbounds.addInbound');
+
+  const okText = mode === 'edit'
+    ? t('pages.clients.submitEdit')
+    : t('create');
+
+  return (
+    <>
+      {messageContextHolder}
+      <Modal
+        open={open}
+        title={title}
+        okText={okText}
+        cancelText={t('close')}
+        confirmLoading={saving}
+        mask={{ closable: false }}
+        width={780}
+        onOk={submit}
+        onCancel={onClose}
+        destroyOnHidden
+      >
+        <Form
+          form={form}
+          colon={false}
+          labelCol={{ sm: { span: 8 } }}
+          wrapperCol={{ sm: { span: 14 } }}
+        >
+          <Text type="secondary">
+            WIP — Pattern A rewrite. Tabs are not yet wired into this skeleton.
+          </Text>
+        </Form>
+      </Modal>
+    </>
+  );
+}