From b10e0d0acd95e44f82c32cc686c8565de9b45a95 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 02:01:31 +0200 Subject: [PATCH] feat(frontend): InboundFormModal.new.tsx skeleton (Pattern A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First commit of the sibling-file modal rewrite. The new modal mounts Form.useForm, 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. --- .../pages/inbounds/InboundFormModal.new.tsx | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 frontend/src/pages/inbounds/InboundFormModal.new.tsx diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx new file mode 100644 index 00000000..030c287a --- /dev/null +++ b/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(); + 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} + +
+ + WIP — Pattern A rewrite. Tabs are not yet wired into this skeleton. + +
+
+ + ); +}