refactor(forms): перевести service sheets на FormSheet и RHF
SubdomainEditSheet на FormSheet+RHF+Zod; FormField hint через FieldDescription; service-edit на Field/Item примитивы. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ import type { Control, FieldPath, FieldValues } from 'react-hook-form'
|
|||||||
import { Controller } from 'react-hook-form'
|
import { Controller } from 'react-hook-form'
|
||||||
import {
|
import {
|
||||||
Field,
|
Field,
|
||||||
|
FieldDescription,
|
||||||
FieldError,
|
FieldError,
|
||||||
FieldLabel,
|
FieldLabel,
|
||||||
} from '@cfdm/ui/components/field'
|
} from '@cfdm/ui/components/field'
|
||||||
@@ -13,6 +14,7 @@ interface FormFieldProps<T extends FieldValues> {
|
|||||||
control: Control<T>
|
control: Control<T>
|
||||||
label: string
|
label: string
|
||||||
htmlFor?: string
|
htmlFor?: string
|
||||||
|
hint?: string
|
||||||
className?: string
|
className?: string
|
||||||
children: (props: {
|
children: (props: {
|
||||||
id: string
|
id: string
|
||||||
@@ -29,6 +31,7 @@ export function FormField<T extends FieldValues>({
|
|||||||
control,
|
control,
|
||||||
label,
|
label,
|
||||||
htmlFor,
|
htmlFor,
|
||||||
|
hint,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
}: FormFieldProps<T>) {
|
}: FormFieldProps<T>) {
|
||||||
@@ -52,6 +55,9 @@ export function FormField<T extends FieldValues>({
|
|||||||
onBlur: field.onBlur,
|
onBlur: field.onBlur,
|
||||||
ref: field.ref,
|
ref: field.ref,
|
||||||
})}
|
})}
|
||||||
|
{hint && !fieldState.error ? (
|
||||||
|
<FieldDescription>{hint}</FieldDescription>
|
||||||
|
) : null}
|
||||||
<FieldError errors={[fieldState.error]} />
|
<FieldError errors={[fieldState.error]} />
|
||||||
</Field>
|
</Field>
|
||||||
)}
|
)}
|
||||||
@@ -80,9 +86,7 @@ export function FormFieldSimple({
|
|||||||
<Field data-invalid={!!error} className={cn(className)}>
|
<Field data-invalid={!!error} className={cn(className)}>
|
||||||
<FieldLabel htmlFor={htmlFor}>{label}</FieldLabel>
|
<FieldLabel htmlFor={htmlFor}>{label}</FieldLabel>
|
||||||
{children}
|
{children}
|
||||||
{hint && !error && (
|
{hint && !error ? <FieldDescription>{hint}</FieldDescription> : null}
|
||||||
<p className="text-xs text-muted-foreground">{hint}</p>
|
|
||||||
)}
|
|
||||||
<FieldError errors={[error]} />
|
<FieldError errors={[error]} />
|
||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,19 +19,6 @@ import type {
|
|||||||
UpdateServiceConfigInput,
|
UpdateServiceConfigInput,
|
||||||
} from '@/lib/schemas'
|
} from '@/lib/schemas'
|
||||||
import { bindingToFqdn } from '@/lib/parse-fqdn'
|
import { bindingToFqdn } from '@/lib/parse-fqdn'
|
||||||
import { AppButton } from '@/components/app-button'
|
|
||||||
import { AppInput } from '@/components/app-input'
|
|
||||||
import {
|
|
||||||
AppField,
|
|
||||||
AppFieldGroup,
|
|
||||||
AppFieldLabel,
|
|
||||||
} from '@/components/app-field'
|
|
||||||
import {
|
|
||||||
AppItem,
|
|
||||||
AppItemContent,
|
|
||||||
AppItemGroup,
|
|
||||||
} from '@/components/app-item'
|
|
||||||
import { AppSeparator } from '@/components/app-separator'
|
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -40,6 +27,14 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@cfdm/ui/components/sheet'
|
} from '@cfdm/ui/components/sheet'
|
||||||
|
import { Button } from '@cfdm/ui/components/button'
|
||||||
|
import { Field, FieldGroup, FieldLabel } from '@cfdm/ui/components/field'
|
||||||
|
import { Input } from '@cfdm/ui/components/input'
|
||||||
|
import {
|
||||||
|
Item,
|
||||||
|
ItemContent,
|
||||||
|
ItemGroup,
|
||||||
|
} from '@cfdm/ui/components/item'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { TabsContent } from '@cfdm/ui/components/tabs'
|
import { TabsContent } from '@cfdm/ui/components/tabs'
|
||||||
import {
|
import {
|
||||||
@@ -405,27 +400,27 @@ export function ServiceEditSheet({
|
|||||||
listClassName="mb-0 w-full"
|
listClassName="mb-0 w-full"
|
||||||
>
|
>
|
||||||
<TabsContent value="general" className="flex flex-col gap-4">
|
<TabsContent value="general" className="flex flex-col gap-4">
|
||||||
<AppFieldGroup className="flex flex-col gap-4">
|
<FieldGroup className="flex flex-col gap-4">
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="edit-service-name">Название</AppFieldLabel>
|
<FieldLabel htmlFor="edit-service-name">Название</FieldLabel>
|
||||||
<AppInput
|
<Input
|
||||||
id="edit-service-name"
|
id="edit-service-name"
|
||||||
value={name}
|
value={name}
|
||||||
placeholder={isCreate ? 'VPN Panel' : undefined}
|
placeholder={isCreate ? 'VPN Panel' : undefined}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="edit-service-slug">Slug</AppFieldLabel>
|
<FieldLabel htmlFor="edit-service-slug">Slug</FieldLabel>
|
||||||
<AppInput
|
<Input
|
||||||
id="edit-service-slug"
|
id="edit-service-slug"
|
||||||
value={slug}
|
value={slug}
|
||||||
placeholder={isCreate ? 'vpn-panel' : undefined}
|
placeholder={isCreate ? 'vpn-panel' : undefined}
|
||||||
onChange={(e) => setSlug(e.target.value)}
|
onChange={(e) => setSlug(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="edit-service-group">Группа сервисов</AppFieldLabel>
|
<FieldLabel htmlFor="edit-service-group">Группа сервисов</FieldLabel>
|
||||||
<Select
|
<Select
|
||||||
items={groupItems}
|
items={groupItems}
|
||||||
value={serviceGroupId}
|
value={serviceGroupId}
|
||||||
@@ -442,9 +437,9 @@ export function ServiceEditSheet({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</AppField>
|
</Field>
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="edit-service-ips">IP-адреса сервиса</AppFieldLabel>
|
<FieldLabel htmlFor="edit-service-ips">IP-адреса сервиса</FieldLabel>
|
||||||
<TaggedInput
|
<TaggedInput
|
||||||
id="edit-service-ips"
|
id="edit-service-ips"
|
||||||
value={ips}
|
value={ips}
|
||||||
@@ -452,8 +447,8 @@ export function ServiceEditSheet({
|
|||||||
placeholder="192.168.1.1"
|
placeholder="192.168.1.1"
|
||||||
validate={isValidIpv4}
|
validate={isValidIpv4}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
</AppFieldGroup>
|
</FieldGroup>
|
||||||
|
|
||||||
{groupHasDomain && (
|
{groupHasDomain && (
|
||||||
<>
|
<>
|
||||||
@@ -464,9 +459,9 @@ export function ServiceEditSheet({
|
|||||||
сервиса для общего домена группы.
|
сервиса для общего домена группы.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="service-lb-weight">Вес</AppFieldLabel>
|
<FieldLabel htmlFor="service-lb-weight">Вес</FieldLabel>
|
||||||
<AppInput
|
<Input
|
||||||
id="service-lb-weight"
|
id="service-lb-weight"
|
||||||
type="number"
|
type="number"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
@@ -477,10 +472,10 @@ export function ServiceEditSheet({
|
|||||||
setLbWeight(Math.max(1, Number(e.target.value) || 1))
|
setLbWeight(Math.max(1, Number(e.target.value) || 1))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor="service-lb-priority">Приоритет</AppFieldLabel>
|
<FieldLabel htmlFor="service-lb-priority">Приоритет</FieldLabel>
|
||||||
<AppInput
|
<Input
|
||||||
id="service-lb-priority"
|
id="service-lb-priority"
|
||||||
type="number"
|
type="number"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
@@ -491,7 +486,7 @@ export function ServiceEditSheet({
|
|||||||
setLbPriority(Math.max(1, Number(e.target.value) || 1))
|
setLbPriority(Math.max(1, Number(e.target.value) || 1))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -503,10 +498,10 @@ export function ServiceEditSheet({
|
|||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
FQDN → IP или CNAME для DNS-записей Cloudflare
|
FQDN → IP или CNAME для DNS-записей Cloudflare
|
||||||
</p>
|
</p>
|
||||||
<AppButton type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить
|
Добавить
|
||||||
</AppButton>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bindings.length === 0 ? (
|
{bindings.length === 0 ? (
|
||||||
@@ -515,14 +510,14 @@ export function ServiceEditSheet({
|
|||||||
title="Нет привязок"
|
title="Нет привязок"
|
||||||
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
description="Необязательно. Пример: newdom.ivx.su — зона ivx.su определится автоматически."
|
||||||
action={
|
action={
|
||||||
<AppButton type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
<Button type="button" variant="outline" size="sm" onClick={handleAddBinding}>
|
||||||
<PlusIcon data-icon="inline-start" />
|
<PlusIcon data-icon="inline-start" />
|
||||||
Добавить привязку
|
Добавить привязку
|
||||||
</AppButton>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<AppItemGroup className="gap-2">
|
<ItemGroup className="gap-2">
|
||||||
{bindings.map((binding, index) => {
|
{bindings.map((binding, index) => {
|
||||||
const showLbBlock =
|
const showLbBlock =
|
||||||
(binding.record_type === 'A' && binding.target_ips.length > 0) ||
|
(binding.record_type === 'A' && binding.target_ips.length > 0) ||
|
||||||
@@ -532,11 +527,11 @@ export function ServiceEditSheet({
|
|||||||
binding.target_ips.length > 1 &&
|
binding.target_ips.length > 1 &&
|
||||||
binding.lb_mode !== 'round_robin'
|
binding.lb_mode !== 'round_robin'
|
||||||
return (
|
return (
|
||||||
<AppItem key={`binding-${index}`} variant="outline" className="items-stretch">
|
<Item key={`binding-${index}`} variant="outline" className="items-stretch">
|
||||||
<AppItemContent className="w-full flex flex-col gap-3">
|
<ItemContent className="w-full flex flex-col gap-3">
|
||||||
<div className="flex items-end gap-2">
|
<div className="flex items-end gap-2">
|
||||||
<AppField className="min-w-0 flex-1">
|
<Field className="min-w-0 flex-1">
|
||||||
<AppFieldLabel htmlFor={`binding-fqdn-${index}`}>FQDN</AppFieldLabel>
|
<FieldLabel htmlFor={`binding-fqdn-${index}`}>FQDN</FieldLabel>
|
||||||
<TaggedInput
|
<TaggedInput
|
||||||
id={`binding-fqdn-${index}`}
|
id={`binding-fqdn-${index}`}
|
||||||
value={binding.fqdn ? [binding.fqdn] : []}
|
value={binding.fqdn ? [binding.fqdn] : []}
|
||||||
@@ -546,8 +541,8 @@ export function ServiceEditSheet({
|
|||||||
}
|
}
|
||||||
maxItems={1}
|
maxItems={1}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
<AppButton
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -556,10 +551,10 @@ export function ServiceEditSheet({
|
|||||||
onClick={() => handleRemoveBinding(index)}
|
onClick={() => handleRemoveBinding(index)}
|
||||||
>
|
>
|
||||||
<Trash2Icon />
|
<Trash2Icon />
|
||||||
</AppButton>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor={`binding-type-${index}`}>Тип записи</AppFieldLabel>
|
<FieldLabel htmlFor={`binding-type-${index}`}>Тип записи</FieldLabel>
|
||||||
<Select
|
<Select
|
||||||
items={[
|
items={[
|
||||||
{ label: 'A (IP)', value: 'A' },
|
{ label: 'A (IP)', value: 'A' },
|
||||||
@@ -578,22 +573,22 @@ export function ServiceEditSheet({
|
|||||||
<SelectItem value="CNAME">CNAME</SelectItem>
|
<SelectItem value="CNAME">CNAME</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</AppField>
|
</Field>
|
||||||
{binding.record_type === 'CNAME' ? (
|
{binding.record_type === 'CNAME' ? (
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor={`binding-cname-${index}`}>
|
<FieldLabel htmlFor={`binding-cname-${index}`}>
|
||||||
CNAME-цель
|
CNAME-цель
|
||||||
</AppFieldLabel>
|
</FieldLabel>
|
||||||
<AppInput
|
<Input
|
||||||
id={`binding-cname-${index}`}
|
id={`binding-cname-${index}`}
|
||||||
value={binding.target_cname}
|
value={binding.target_cname}
|
||||||
placeholder="mmsk.rkns.top"
|
placeholder="mmsk.rkns.top"
|
||||||
onChange={(event) => handleCnameChange(index, event.target.value)}
|
onChange={(event) => handleCnameChange(index, event.target.value)}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
) : (
|
) : (
|
||||||
<AppField>
|
<Field>
|
||||||
<AppFieldLabel htmlFor={`binding-ip-${index}`}>IP</AppFieldLabel>
|
<FieldLabel htmlFor={`binding-ip-${index}`}>IP</FieldLabel>
|
||||||
<ServiceBindingIpInput
|
<ServiceBindingIpInput
|
||||||
id={`binding-ip-${index}`}
|
id={`binding-ip-${index}`}
|
||||||
value={binding.target_ips}
|
value={binding.target_ips}
|
||||||
@@ -606,7 +601,7 @@ export function ServiceEditSheet({
|
|||||||
handleBindingMetaChange(index, ip, meta)
|
handleBindingMetaChange(index, ip, meta)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</AppField>
|
</Field>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showLbBlock ? (
|
{showLbBlock ? (
|
||||||
@@ -629,17 +624,17 @@ export function ServiceEditSheet({
|
|||||||
idPrefix={`binding-${index}-health`}
|
idPrefix={`binding-${index}-health`}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</AppItemContent>
|
</ItemContent>
|
||||||
</AppItem>
|
</Item>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</AppItemGroup>
|
</ItemGroup>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</CountedLineTabs>
|
</CountedLineTabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppSeparator />
|
<Separator />
|
||||||
|
|
||||||
<SheetFooter className="flex flex-row flex-wrap gap-2 border-t-0 pt-4">
|
<SheetFooter className="flex flex-row flex-wrap gap-2 border-t-0 pt-4">
|
||||||
{!isCreate ? (
|
{!isCreate ? (
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo } from 'react'
|
||||||
|
import { useForm, Controller } from 'react-hook-form'
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
|
import { z } from 'zod'
|
||||||
import type { CertMonitoring } from '@cfdm/shared'
|
import type { CertMonitoring } from '@cfdm/shared'
|
||||||
import type { ServiceView, SubdomainRecord } from '@/lib/schemas'
|
import type { ServiceView, SubdomainRecord } from '@/lib/schemas'
|
||||||
import { certMonitoringOptions } from '@/lib/cert-monitoring'
|
import { certMonitoringOptions } from '@/lib/cert-monitoring'
|
||||||
import { formatServiceGroupLabel } from '@/lib/service-utils'
|
import { formatServiceGroupLabel } from '@/lib/service-utils'
|
||||||
|
import { FormSheet } from '@/components/form-sheet'
|
||||||
import { FormFieldSimple } from '@/components/form-field'
|
import { FormFieldSimple } from '@/components/form-field'
|
||||||
import { LoadingButton } from '@/components/loading-button'
|
import { LoadingButton } from '@/components/loading-button'
|
||||||
import { AppInput } from '@/components/app-input'
|
import { FieldGroup } from '@cfdm/ui/components/field'
|
||||||
import { AppFieldDescription, AppFieldGroup } from '@/components/app-field'
|
import { Input } from '@cfdm/ui/components/input'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -14,20 +18,14 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@cfdm/ui/components/select'
|
} from '@cfdm/ui/components/select'
|
||||||
import {
|
|
||||||
Sheet,
|
|
||||||
SheetContent,
|
|
||||||
SheetDescription,
|
|
||||||
SheetFooter,
|
|
||||||
SheetHeader,
|
|
||||||
SheetTitle,
|
|
||||||
} from '@cfdm/ui/components/sheet'
|
|
||||||
|
|
||||||
export interface SubdomainEditValues {
|
const subdomainEditSchema = z.object({
|
||||||
name: string
|
name: z.string().min(1, 'Укажите имя'),
|
||||||
serviceId: string
|
serviceId: z.string(),
|
||||||
certMonitoring: CertMonitoring
|
certMonitoring: z.enum(['auto', 'required', 'skipped']),
|
||||||
}
|
})
|
||||||
|
|
||||||
|
export type SubdomainEditValues = z.infer<typeof subdomainEditSchema>
|
||||||
|
|
||||||
interface SubdomainEditSheetProps {
|
interface SubdomainEditSheetProps {
|
||||||
mode: 'create' | 'edit'
|
mode: 'create' | 'edit'
|
||||||
@@ -54,9 +52,14 @@ export function SubdomainEditSheet({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: SubdomainEditSheetProps) {
|
}: SubdomainEditSheetProps) {
|
||||||
const [name, setName] = useState('')
|
const form = useForm<SubdomainEditValues>({
|
||||||
const [serviceId, setServiceId] = useState('none')
|
resolver: zodResolver(subdomainEditSchema),
|
||||||
const [certMonitoring, setCertMonitoring] = useState<CertMonitoring>('auto')
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
serviceId: 'none',
|
||||||
|
certMonitoring: 'auto',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const serviceItems = useMemo(
|
const serviceItems = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -84,55 +87,83 @@ export function SubdomainEditSheet({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
if (mode === 'edit' && subdomain) {
|
if (mode === 'edit' && subdomain) {
|
||||||
setName(subdomain.name)
|
form.reset({
|
||||||
setServiceId(currentServiceId || 'none')
|
name: subdomain.name,
|
||||||
setCertMonitoring(subdomain.cert_monitoring)
|
serviceId: currentServiceId || 'none',
|
||||||
|
certMonitoring: subdomain.cert_monitoring,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setName('')
|
form.reset({
|
||||||
setServiceId('none')
|
name: '',
|
||||||
setCertMonitoring('auto')
|
serviceId: 'none',
|
||||||
}, [open, mode, subdomain, currentServiceId])
|
certMonitoring: 'auto',
|
||||||
|
})
|
||||||
|
}, [open, mode, subdomain, currentServiceId, form])
|
||||||
|
|
||||||
function handleSubmit(event: React.FormEvent) {
|
const certMonitoring = form.watch('certMonitoring')
|
||||||
event.preventDefault()
|
const certHint =
|
||||||
const trimmed = name.trim()
|
certMonitoringOptions.find((o) => o.value === certMonitoring)?.description
|
||||||
if (!trimmed) return
|
|
||||||
onSubmit({ name: trimmed, serviceId, certMonitoring })
|
function handleSubmit(values: SubdomainEditValues) {
|
||||||
|
onSubmit({
|
||||||
|
name: values.name.trim(),
|
||||||
|
serviceId: values.serviceId,
|
||||||
|
certMonitoring: values.certMonitoring as CertMonitoring,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
<FormSheet<SubdomainEditValues>
|
||||||
<SheetContent>
|
open={open}
|
||||||
<SheetHeader>
|
onOpenChange={onOpenChange}
|
||||||
<SheetTitle>
|
title={
|
||||||
{mode === 'create' ? 'Создать поддомен' : 'Редактировать поддомен'}
|
mode === 'create' ? 'Создать поддомен' : 'Редактировать поддомен'
|
||||||
</SheetTitle>
|
}
|
||||||
<SheetDescription>
|
description={
|
||||||
{mode === 'create'
|
mode === 'create'
|
||||||
? `Имя записи в зоне ${zoneName} (например, www или api)`
|
? `Имя записи в зоне ${zoneName} (например, www или api)`
|
||||||
: `Изменение поддомена в зоне ${zoneName}`}
|
: `Изменение поддомена в зоне ${zoneName}`
|
||||||
</SheetDescription>
|
}
|
||||||
</SheetHeader>
|
form={form}
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 px-4">
|
onSubmit={handleSubmit}
|
||||||
<AppFieldGroup>
|
footer={
|
||||||
<FormFieldSimple label="Имя" htmlFor="subdomain_name">
|
<LoadingButton
|
||||||
<AppInput
|
type="submit"
|
||||||
id="subdomain_name"
|
className="w-full"
|
||||||
placeholder="www"
|
disabled={!form.watch('name')?.trim()}
|
||||||
value={name}
|
isLoading={isSaving}
|
||||||
onChange={(event) => setName(event.target.value)}
|
loadingLabel="Сохранение…"
|
||||||
className="font-mono"
|
>
|
||||||
aria-invalid={!name.trim() && name.length > 0}
|
{mode === 'create' ? 'Создать' : 'Сохранить'}
|
||||||
/>
|
</LoadingButton>
|
||||||
</FormFieldSimple>
|
}
|
||||||
{mode === 'edit' && (
|
>
|
||||||
<>
|
<FieldGroup>
|
||||||
<FormFieldSimple label="Сервис" htmlFor="subdomain_service">
|
<FormFieldSimple
|
||||||
|
label="Имя"
|
||||||
|
htmlFor="subdomain_name"
|
||||||
|
error={form.formState.errors.name}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
id="subdomain_name"
|
||||||
|
placeholder="www"
|
||||||
|
className="font-mono"
|
||||||
|
aria-invalid={!!form.formState.errors.name}
|
||||||
|
{...form.register('name')}
|
||||||
|
/>
|
||||||
|
</FormFieldSimple>
|
||||||
|
{mode === 'edit' ? (
|
||||||
|
<>
|
||||||
|
<FormFieldSimple label="Сервис" htmlFor="subdomain_service">
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="serviceId"
|
||||||
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
items={serviceItems}
|
items={serviceItems}
|
||||||
value={serviceId}
|
value={field.value}
|
||||||
onValueChange={(value) => setServiceId(value ?? 'none')}
|
onValueChange={(value) => field.onChange(value ?? 'none')}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="subdomain_service" className="w-full">
|
<SelectTrigger id="subdomain_service" className="w-full">
|
||||||
<SelectValue placeholder="Без сервиса" />
|
<SelectValue placeholder="Без сервиса" />
|
||||||
@@ -145,19 +176,29 @@ export function SubdomainEditSheet({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormFieldSimple>
|
)}
|
||||||
<FormFieldSimple
|
/>
|
||||||
label="Мониторинг SSL"
|
</FormFieldSimple>
|
||||||
htmlFor="subdomain_cert_monitoring"
|
<FormFieldSimple
|
||||||
>
|
label="Мониторинг SSL"
|
||||||
|
htmlFor="subdomain_cert_monitoring"
|
||||||
|
hint={certHint}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="certMonitoring"
|
||||||
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
items={certMonitoringItems}
|
items={certMonitoringItems}
|
||||||
value={certMonitoring}
|
value={field.value}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setCertMonitoring((value ?? 'auto') as CertMonitoring)
|
field.onChange((value ?? 'auto') as CertMonitoring)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="subdomain_cert_monitoring" className="w-full">
|
<SelectTrigger
|
||||||
|
id="subdomain_cert_monitoring"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -168,29 +209,12 @@ export function SubdomainEditSheet({
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<AppFieldDescription>
|
)}
|
||||||
{
|
/>
|
||||||
certMonitoringOptions.find((o) => o.value === certMonitoring)
|
</FormFieldSimple>
|
||||||
?.description
|
</>
|
||||||
}
|
) : null}
|
||||||
</AppFieldDescription>
|
</FieldGroup>
|
||||||
</FormFieldSimple>
|
</FormSheet>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AppFieldGroup>
|
|
||||||
<SheetFooter>
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
className="w-full"
|
|
||||||
disabled={!name.trim()}
|
|
||||||
isLoading={isSaving}
|
|
||||||
loadingLabel="Сохранение…"
|
|
||||||
>
|
|
||||||
{mode === 'create' ? 'Создать' : 'Сохранить'}
|
|
||||||
</LoadingButton>
|
|
||||||
</SheetFooter>
|
|
||||||
</form>
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user