Spaces:
Paused
Paused
| import { CircleAlert, Loader2, PlusCircle } from 'lucide-react'; | |
| import { useState, useEffect } from 'react'; | |
| import { credentialApi } from '@/api'; | |
| import type { CredentialSchema } from '@/api'; | |
| import { SchemaForm, type SchemaFormValue } from '@/components/form/SchemaForm'; | |
| import { Button } from '@/components/ui/button'; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogHeader, | |
| DialogTitle, | |
| DialogDescription, | |
| DialogFooter, | |
| } from '@/components/ui/dialog'; | |
| import { Field, FieldGroup, FieldLabel } from '@/components/ui/field.tsx'; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from '@/components/ui/select'; | |
| import { useCredentials } from '@/hooks/useCredentials'; | |
| import { useTranslation } from '@/i18n/useI18n.ts'; | |
| interface Props { | |
| open: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| onCreated?: () => void; | |
| defaultType?: string; | |
| } | |
| export function CreateCredentialDialog({ open, onOpenChange, onCreated, defaultType }: Props) { | |
| const { create } = useCredentials(); | |
| const { t } = useTranslation(); | |
| const [schemas, setSchemas] = useState<CredentialSchema[]>([]); | |
| const [loadingSchemas, setLoadingSchemas] = useState(false); | |
| const [selectedType, setSelectedType] = useState(''); | |
| const [values, setValues] = useState<Record<string, SchemaFormValue>>({}); | |
| const [submitting, setSubmitting] = useState(false); | |
| useEffect(() => { | |
| if (!open) return; | |
| setLoadingSchemas(true); | |
| credentialApi | |
| .schemas() | |
| .then((res) => { | |
| setSchemas(res.schemas); | |
| if (res.schemas.length > 0) { | |
| const first = (res.schemas[0].properties.type?.const as string) ?? ''; | |
| setSelectedType(defaultType ?? first); | |
| } | |
| }) | |
| .finally(() => setLoadingSchemas(false)); | |
| }, [open, defaultType]); | |
| const selectedSchema = schemas.find( | |
| (s) => (s.properties.type?.const as string) === selectedType, | |
| ); | |
| const handleTypeChange = (type: string) => { | |
| setSelectedType(type); | |
| setValues({}); | |
| }; | |
| const handleSubmit = async () => { | |
| if (!selectedSchema) return; | |
| setSubmitting(true); | |
| try { | |
| const data: Record<string, unknown> = { type: selectedType }; | |
| for (const [key, prop] of Object.entries(selectedSchema.properties)) { | |
| if (key === 'id' || key === 'type' || prop.const !== undefined) continue; | |
| const val = values[key]; | |
| if (val !== undefined && val !== '') data[key] = val; | |
| } | |
| await create({ data }); | |
| onOpenChange(false); | |
| onCreated?.(); | |
| } finally { | |
| setSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <Dialog open={open} onOpenChange={onOpenChange}> | |
| <DialogContent className="!w-[500px] !max-w-[500px]"> | |
| <DialogHeader> | |
| <DialogTitle>{t('dialog-credential-create.title')}</DialogTitle> | |
| <DialogDescription> | |
| {t('dialog-credential-create.description')} | |
| </DialogDescription> | |
| </DialogHeader> | |
| <FieldGroup> | |
| <Field> | |
| <FieldLabel>{t('dialog-credential-create.selectType')}</FieldLabel> | |
| <Select | |
| value={selectedType} | |
| onValueChange={handleTypeChange} | |
| disabled={loadingSchemas} | |
| > | |
| <SelectTrigger> | |
| <SelectValue | |
| placeholder={ | |
| loadingSchemas | |
| ? t('common.loading') | |
| : t('dialog-credential-create.selectTypePlaceholder') | |
| } | |
| /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {schemas.map((s) => ( | |
| <SelectItem | |
| key={s.properties.type?.const as string} | |
| value={s.properties.type?.const as string} | |
| > | |
| {s.title} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </Field> | |
| {selectedSchema && ( | |
| <SchemaForm | |
| schema={selectedSchema} | |
| values={values} | |
| onChange={(key, val) => setValues((prev) => ({ ...prev, [key]: val }))} | |
| /> | |
| )} | |
| </FieldGroup> | |
| <DialogFooter> | |
| <Button | |
| variant="ghost" | |
| onClick={() => onOpenChange(false)} | |
| disabled={submitting} | |
| > | |
| <CircleAlert className="size-3.5" /> | |
| {t('common.cancel')} | |
| </Button> | |
| <Button onClick={handleSubmit} disabled={submitting || !selectedSchema}> | |
| {submitting ? ( | |
| <Loader2 className="size-3.5 animate-spin" /> | |
| ) : ( | |
| <PlusCircle className="size-3.5" /> | |
| )} | |
| {submitting ? t('common.creating') : t('common.create')} | |
| </Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| } | |