Files
liqa/client/src/pages/credential/CreateCredentialPage.tsx
T
ars9 1c13e068ad feat(ui): add CodeBlock and CodeEditor components with Monaco and hljs
- add CodeBlock for read-only syntax-highlighted display (hljs, js/json)
- add CodeEditor wrapping Monaco editor with theme sync, focus state, and error state
- replace code textareas in snippet, credential, and step pages with CodeEditor
- replace code <pre> blocks in snippet and credential detail pages with CodeBlock
- make form cards full-width on pages with code editors
- add resize:vertical support to CodeEditor wrapper with automaticLayout
2026-04-10 15:53:26 +03:00

107 lines
3.2 KiB
TypeScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { credentials } from '../../api';
import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui';
import styles from '../Page.module.css';
export function CreateCredentialPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const [name, setName] = useState('');
const [data, setData] = useState('');
const [nameError, setNameError] = useState('');
const [dataError, setDataError] = useState('');
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
let valid = true;
if (!name.trim()) {
setNameError(t('credentials.form_name_required'));
valid = false;
}
if (data.trim()) {
try {
JSON.parse(data.trim());
} catch {
setDataError(t('credentials.form_data_invalid_json'));
valid = false;
}
}
if (!valid) return;
setSaving(true);
setError(null);
try {
const credential = await credentials.create(name.trim(), data.trim() || undefined);
navigate(`/credentials/${credential.id}`);
} catch (err) {
setError((err as Error).message);
} finally {
setSaving(false);
}
};
return (
<div>
<div className={styles.pageToolbar}>
<Breadcrumbs
items={[
{ label: t('credentials.title'), onClick: () => navigate('/credentials') },
{ label: t('credentials.create_title') },
]}
/>
</div>
{error && <p className={styles.error}>{error}</p>}
<form onSubmit={handleSubmit} noValidate>
<Card className={styles.formCardFull}>
<div className={styles.formFields}>
<Input
label={t('credentials.form_name')}
placeholder={t('credentials.form_name_placeholder')}
value={name}
onChange={(e) => {
setName(e.target.value);
setNameError('');
}}
error={nameError || undefined}
required
autoFocus
/>
<div className={styles.formField}>
<label className={styles.fieldLabel}>
{t('credentials.form_data')}
<span className={styles.requiredMark}> *</span>
</label>
<CodeEditor
language="json"
value={data}
onChange={(v) => {
setData(v);
setDataError('');
}}
rows={6}
error={!!dataError}
/>
{dataError && <span className={styles.fieldError}>{dataError}</span>}
</div>
</div>
<div className={styles.formActions}>
<Button type="button" variant="secondary" onClick={() => navigate('/credentials')}>
{t('credentials.action_cancel')}
</Button>
<Button type="submit" loading={saving}>
{t('credentials.action_save')}
</Button>
</div>
</Card>
</form>
</div>
);
}