From b2611d951bfbea50d62842e00af43cfd91004daa Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 26 Jun 2026 15:54:35 +0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D1=81=D0=B5=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D1=8B=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=20label=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D1=82=D0=B5=D1=85=D0=BD=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?;=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20VPS=20=D0=BF=D0=BE=D0=B4=D1=82?= =?UTF-8?q?=D1=8F=D0=B3=D0=B8=D0=B2=D0=B0=D0=B5=D1=82=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SelectField: обёртка над base-ui Select с items prop (Select.Value рендерит label выбранного элемента, а не raw value) - все Select в forms (vps/providers/accounts/payments/balance/settings) переписаны на SelectField - FormSheetRhf: useEffect reset(defaultValues) при открытии sheet — теперь форма редактирования заполняется текущими данными Co-authored-by: Cursor --- apps/web/src/components/form-sheet-rhf.tsx | 9 ++ apps/web/src/components/select-field.tsx | 107 +++++++++++++++++++++ apps/web/src/routes/_auth/accounts.tsx | 31 +++--- apps/web/src/routes/_auth/balance.tsx | 36 ++++--- apps/web/src/routes/_auth/payments.tsx | 40 ++++---- apps/web/src/routes/_auth/providers.tsx | 18 ++-- apps/web/src/routes/_auth/settings.tsx | 35 +++---- apps/web/src/routes/_auth/vps.tsx | 74 ++++++-------- 8 files changed, 226 insertions(+), 124 deletions(-) create mode 100644 apps/web/src/components/select-field.tsx diff --git a/apps/web/src/components/form-sheet-rhf.tsx b/apps/web/src/components/form-sheet-rhf.tsx index e52f7d0..f336e37 100644 --- a/apps/web/src/components/form-sheet-rhf.tsx +++ b/apps/web/src/components/form-sheet-rhf.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import type { ReactNode } from 'react' import { useForm, @@ -42,6 +43,14 @@ export function FormSheetRhf({ mode: 'onBlur', }) + // Re-sync form values when the sheet opens or defaultValues change (e.g. edit vs create). + useEffect(() => { + if (open) { + form.reset(defaultValues as DefaultValues) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, defaultValues]) + const submit: SubmitHandler = (values) => onSubmit(values) return ( diff --git a/apps/web/src/components/select-field.tsx b/apps/web/src/components/select-field.tsx new file mode 100644 index 0000000..8c49680 --- /dev/null +++ b/apps/web/src/components/select-field.tsx @@ -0,0 +1,107 @@ +import * as React from 'react' +import { + Select as SelectPrimitive, + type SelectRootProps, +} from '@base-ui/react/select' + +import { cn } from '@cfdm/ui/lib/utils' +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from 'lucide-react' + +export interface SelectOption { + value: string + label: React.ReactNode +} + +interface SelectFieldProps extends Omit, 'items' | 'value' | 'onValueChange'> { + options: SelectOption[] + placeholder?: string + triggerClassName?: string + triggerId?: string + size?: 'sm' | 'default' + value?: string | null + onValueChange?: (value: string | null) => void +} + +export function SelectField({ + options, + placeholder, + triggerClassName, + triggerId, + size = 'default', + value, + onValueChange, + ...props +}: SelectFieldProps) { + const items = React.useMemo(() => options.map((o) => ({ value: o.value, label: o.label })), [options]) + + return ( + + + + } + /> + + + + + + + + + {options.map((opt) => ( + + + {opt.label} + + + } + > + + + + ))} + + + + + + + + + ) +} diff --git a/apps/web/src/routes/_auth/accounts.tsx b/apps/web/src/routes/_auth/accounts.tsx index 07f30ad..3ab56c8 100644 --- a/apps/web/src/routes/_auth/accounts.tsx +++ b/apps/web/src/routes/_auth/accounts.tsx @@ -18,7 +18,7 @@ import { FormSheet } from '@/components/form-sheet' import { FormField } from '@/components/form-field' import { Input } from '@cfdm/ui/components/input' import { Textarea } from '@cfdm/ui/components/textarea' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@cfdm/ui/components/select' +import { SelectField } from '@/components/select-field' import { LoadingButton } from '@/components/loading-button' import type { ProviderAccount, BillingMode } from '@/types/entities' @@ -191,12 +191,13 @@ function AccountsPage() { submitting={saveMut.isPending} > - + setForm({ ...form, providerId: v ?? '' })} + options={(snapshot?.providers ?? []).map((p) => ({ value: p.id, label: p.name }))} + /> setForm({ ...form, name: e.target.value })} /> @@ -212,13 +213,15 @@ function AccountsPage() { setForm({ ...form, apiCredentials: e.target.value })} /> - + setForm({ ...form, billingMode: (v ?? 'monthly') as BillingMode })} + options={[ + { value: 'monthly', label: billingModeLabel('monthly') }, + { value: 'daily', label: billingModeLabel('daily') }, + ]} + />