Add Onboarding Schema Editor, replacing the deprecated stub
All checks were successful
build-and-release / build (push) Successful in 50s
All checks were successful
build-and-release / build (push) Successful in 50s
Lets an admin define each role's verification wizard steps and fields (with drag-to-reorder), which fields lock permanently once approved, the portfolio persistence model, and whether the wizard is enabled at all for that role — writing to onboarding_configs.schema_json via the (now admin-gated) config API. This is the admin surface for the new runtime-config-driven wizard the frontend/backend now consume, so nothing about a role's onboarding flow needs a code change to adjust. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
cb99b90774
commit
5316811b45
5 changed files with 434 additions and 42 deletions
|
|
@ -1,36 +0,0 @@
|
|||
import { A } from '@solidjs/router';
|
||||
|
||||
export default function OnboardingDeprecatedPage() {
|
||||
return (
|
||||
<div class="w-full space-y-4">
|
||||
<div>
|
||||
<h1 class="text-[28px] font-bold leading-tight text-[#111827]">Onboarding Management Deprecated</h1>
|
||||
<p class="mt-1 text-[14px] text-[#6B7280]">
|
||||
Legacy onboarding schema management is no longer used in the active platform flow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="border-radius:12px;border:1px solid #FDE68A;background:#FFFBEB;padding:16px">
|
||||
<p style="margin:0;color:#92400E;font-size:14px;line-height:1.6">
|
||||
Current flow: user signs up with intent, lands on role dashboard, completes My Profile/My Portfolio,
|
||||
then enters Verification and final Approval workflows.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||
<A
|
||||
href="/admin/external-dashboard-management"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;height:38px;border-radius:10px;background:#0D0D2A;color:white;padding:0 16px;font-size:13px;font-weight:700;text-decoration:none"
|
||||
>
|
||||
Open External Dashboard Management
|
||||
</A>
|
||||
<A
|
||||
href="/admin/external-roles"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;height:38px;border-radius:10px;border:1px solid #E5E7EB;background:white;color:#374151;padding:0 16px;font-size:13px;font-weight:600;text-decoration:none"
|
||||
>
|
||||
Open External Role Management
|
||||
</A>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
428
src/components/admin/OnboardingSchemaEditor.tsx
Normal file
428
src/components/admin/OnboardingSchemaEditor.tsx
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
import { For, Show, createEffect, createResource, createSignal } from 'solid-js';
|
||||
|
||||
const API = '';
|
||||
|
||||
type FieldType = 'text' | 'textarea' | 'select' | 'number' | 'email' | 'tel' | 'url' | 'date' | 'file';
|
||||
type StepType = 'basic' | 'documents' | 'portfolio' | 'review';
|
||||
type PortfolioModel = 'none' | 'custom_data' | 'professional';
|
||||
|
||||
type SchemaField = {
|
||||
id: string;
|
||||
label: string;
|
||||
type: FieldType;
|
||||
required: boolean;
|
||||
lockAfterApproval: boolean;
|
||||
options?: string; // comma-separated, editable as plain text; only meaningful for type "select"
|
||||
};
|
||||
|
||||
type SchemaStep = {
|
||||
id: string;
|
||||
title: string;
|
||||
type: StepType;
|
||||
fields: SchemaField[];
|
||||
};
|
||||
|
||||
type Role = { id: string; key: string; name: string };
|
||||
|
||||
function authHeaders() {
|
||||
const token = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' : '';
|
||||
return { Accept: 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
|
||||
}
|
||||
|
||||
async function fetchRoles(): Promise<Role[]> {
|
||||
const res = await fetch(`${API}/api/admin/roles?audience=EXTERNAL`, { headers: authHeaders() });
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json().catch(() => []);
|
||||
const rows = Array.isArray(data) ? data : (data.roles || []);
|
||||
return rows.map((r: any) => ({ id: String(r.id), key: String(r.key), name: String(r.name || r.key) }));
|
||||
}
|
||||
|
||||
function emptyField(): SchemaField {
|
||||
return { id: '', label: '', type: 'text', required: false, lockAfterApproval: false };
|
||||
}
|
||||
|
||||
function emptyStep(type: StepType): SchemaStep {
|
||||
const id = `${type}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
const titles: Record<StepType, string> = {
|
||||
basic: 'Basic Information',
|
||||
documents: 'Documents',
|
||||
portfolio: 'My Portfolio',
|
||||
review: 'Review and Submit',
|
||||
};
|
||||
return { id, title: titles[type], type, fields: [] };
|
||||
}
|
||||
|
||||
function parseSchema(schema_json: any): { portfolioModel: PortfolioModel; enableWizardFlow: boolean; steps: SchemaStep[] } {
|
||||
const steps: SchemaStep[] = Array.isArray(schema_json?.steps)
|
||||
? schema_json.steps.map((s: any) => ({
|
||||
id: String(s.id || `step_${Math.random().toString(36).slice(2, 8)}`),
|
||||
title: String(s.title || ''),
|
||||
type: (['basic', 'documents', 'portfolio', 'review'].includes(s.type) ? s.type : 'basic') as StepType,
|
||||
fields: Array.isArray(s.fields)
|
||||
? s.fields.map((f: any) => ({
|
||||
id: String(f.id || ''),
|
||||
label: String(f.label || ''),
|
||||
type: (f.type || 'text') as FieldType,
|
||||
required: Boolean(f.required),
|
||||
lockAfterApproval: Boolean(f.lockAfterApproval),
|
||||
options: Array.isArray(f.options)
|
||||
? f.options.map((o: any) => (typeof o === 'string' ? o : o?.label ?? o?.value ?? '')).join(', ')
|
||||
: '',
|
||||
}))
|
||||
: [],
|
||||
}))
|
||||
: [];
|
||||
return {
|
||||
portfolioModel: (['none', 'custom_data', 'professional'].includes(schema_json?.portfolioModel) ? schema_json.portfolioModel : 'none') as PortfolioModel,
|
||||
enableWizardFlow: Boolean(schema_json?.enableWizardFlow),
|
||||
steps,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeSchema(portfolioModel: PortfolioModel, enableWizardFlow: boolean, steps: SchemaStep[]) {
|
||||
return {
|
||||
portfolioModel,
|
||||
enableWizardFlow,
|
||||
steps: steps.map((s) => ({
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
type: s.type,
|
||||
fields: s.fields.map((f) => ({
|
||||
id: f.id,
|
||||
label: f.label,
|
||||
type: f.type,
|
||||
required: f.required,
|
||||
...(f.lockAfterApproval ? { lockAfterApproval: true } : {}),
|
||||
...(f.type === 'select' && f.options?.trim()
|
||||
? { options: f.options.split(',').map((o) => o.trim()).filter(Boolean).map((o) => ({ label: o, value: o })) }
|
||||
: {}),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export default function OnboardingSchemaEditor() {
|
||||
const [roles] = createResource(fetchRoles);
|
||||
const [roleId, setRoleId] = createSignal('');
|
||||
const [loadedVersion, setLoadedVersion] = createSignal<number | null>(null);
|
||||
const [portfolioModel, setPortfolioModel] = createSignal<PortfolioModel>('none');
|
||||
const [enableWizardFlow, setEnableWizardFlow] = createSignal(false);
|
||||
const [steps, setSteps] = createSignal<SchemaStep[]>([]);
|
||||
const [expandedStep, setExpandedStep] = createSignal<string | null>(null);
|
||||
const [dragStepId, setDragStepId] = createSignal<string | null>(null);
|
||||
const [dragField, setDragField] = createSignal<{ stepId: string; fieldIdx: number } | null>(null);
|
||||
const [saving, setSaving] = createSignal(false);
|
||||
const [message, setMessage] = createSignal('');
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
|
||||
const loadSchemaForRole = async (id: string) => {
|
||||
if (!id) return;
|
||||
setLoading(true);
|
||||
setMessage('');
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/onboarding-config/${id}`, { headers: authHeaders() });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const parsed = parseSchema(data.schema_json);
|
||||
setPortfolioModel(parsed.portfolioModel);
|
||||
setEnableWizardFlow(parsed.enableWizardFlow);
|
||||
setSteps(parsed.steps);
|
||||
setLoadedVersion(typeof data.version === 'number' ? data.version : null);
|
||||
} else {
|
||||
// No active schema yet for this role - start from a blank template.
|
||||
setPortfolioModel('none');
|
||||
setEnableWizardFlow(false);
|
||||
setSteps([emptyStep('basic'), emptyStep('documents'), emptyStep('review')]);
|
||||
setLoadedVersion(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const id = roleId();
|
||||
if (id) void loadSchemaForRole(id);
|
||||
});
|
||||
|
||||
const addStep = (type: StepType) => setSteps((prev) => [...prev, emptyStep(type)]);
|
||||
const removeStep = (id: string) => setSteps((prev) => prev.filter((s) => s.id !== id));
|
||||
const updateStep = (id: string, patch: Partial<SchemaStep>) =>
|
||||
setSteps((prev) => prev.map((s) => (s.id === id ? { ...s, ...patch } : s)));
|
||||
const moveStep = (movingId: string, targetId: string) => {
|
||||
if (!movingId || !targetId || movingId === targetId) return;
|
||||
setSteps((prev) => {
|
||||
const from = prev.findIndex((s) => s.id === movingId);
|
||||
const to = prev.findIndex((s) => s.id === targetId);
|
||||
if (from === -1 || to === -1) return prev;
|
||||
const next = [...prev];
|
||||
const [item] = next.splice(from, 1);
|
||||
next.splice(to, 0, item);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const addField = (stepId: string) =>
|
||||
setSteps((prev) => prev.map((s) => (s.id === stepId ? { ...s, fields: [...s.fields, emptyField()] } : s)));
|
||||
const removeField = (stepId: string, idx: number) =>
|
||||
setSteps((prev) => prev.map((s) => (s.id === stepId ? { ...s, fields: s.fields.filter((_, i) => i !== idx) } : s)));
|
||||
const updateField = (stepId: string, idx: number, patch: Partial<SchemaField>) =>
|
||||
setSteps((prev) =>
|
||||
prev.map((s) =>
|
||||
s.id === stepId ? { ...s, fields: s.fields.map((f, i) => (i === idx ? { ...f, ...patch } : f)) } : s
|
||||
)
|
||||
);
|
||||
const moveField = (stepId: string, from: number, to: number) => {
|
||||
if (from === to) return;
|
||||
setSteps((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== stepId) return s;
|
||||
const next = [...s.fields];
|
||||
const [item] = next.splice(from, 1);
|
||||
next.splice(to, 0, item);
|
||||
return { ...s, fields: next };
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const id = roleId();
|
||||
if (!id) return;
|
||||
setSaving(true);
|
||||
setMessage('');
|
||||
try {
|
||||
const schema_json = serializeSchema(portfolioModel(), enableWizardFlow(), steps());
|
||||
const res = await fetch(`${API}/api/admin/onboarding-config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||||
body: JSON.stringify({ role_id: id, schema_json }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body?.error || `Save failed (${res.status})`);
|
||||
}
|
||||
const saved = await res.json();
|
||||
setLoadedVersion(typeof saved.version === 'number' ? saved.version : null);
|
||||
setMessage('Saved — this is now the active schema for this role.');
|
||||
} catch (e: any) {
|
||||
setMessage(e?.message || 'Save failed.');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const FIELD_TYPES: FieldType[] = ['text', 'textarea', 'select', 'number', 'email', 'tel', 'url', 'date', 'file'];
|
||||
const STEP_TYPES: StepType[] = ['basic', 'documents', 'portfolio', 'review'];
|
||||
|
||||
return (
|
||||
<div class="w-full space-y-4">
|
||||
<div>
|
||||
<h1 class="text-[28px] font-bold leading-tight text-[#111827]">Onboarding Schema Editor</h1>
|
||||
<p class="mt-1 text-[14px] text-[#6B7280]">
|
||||
Defines the verification wizard's steps and fields per role — which fields are required, and which
|
||||
become permanently locked once the profile is approved. This replaces per-role hardcoding in the
|
||||
frontend.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="border-radius:12px;border:1px solid #E5E7EB;background:white;padding:16px;display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;min-width:240px">
|
||||
<span style="font-size:11px;font-weight:800;color:#6B7280;text-transform:uppercase">Role</span>
|
||||
<select
|
||||
value={roleId()}
|
||||
onChange={(e) => setRoleId(e.currentTarget.value)}
|
||||
style="height:36px;border-radius:8px;border:1px solid #E5E7EB;padding:0 10px;font-size:13px"
|
||||
>
|
||||
<option value="">Select a role...</option>
|
||||
<For each={roles() || []}>{(r) => <option value={r.id}>{r.name} ({r.key})</option>}</For>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<Show when={roleId()}>
|
||||
<label style="display:flex;flex-direction:column;gap:4px">
|
||||
<span style="font-size:11px;font-weight:800;color:#6B7280;text-transform:uppercase">Portfolio Model</span>
|
||||
<select
|
||||
value={portfolioModel()}
|
||||
onChange={(e) => setPortfolioModel(e.currentTarget.value as PortfolioModel)}
|
||||
style="height:36px;border-radius:8px;border:1px solid #E5E7EB;padding:0 10px;font-size:13px"
|
||||
>
|
||||
<option value="none">None (no portfolio step)</option>
|
||||
<option value="custom_data">Custom data (job-seeker style)</option>
|
||||
<option value="professional">Professional (flat fields + showcase items)</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label style="display:flex;align-items:center;gap:8px;height:36px;font-size:13px;color:#374151">
|
||||
<input type="checkbox" checked={enableWizardFlow()} onChange={(e) => setEnableWizardFlow(e.currentTarget.checked)} />
|
||||
Enable verification wizard flow for this role
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving() || loading()}
|
||||
onClick={handleSave}
|
||||
style={`height:36px;border-radius:8px;border:none;background:#0D0D2A;color:white;padding:0 16px;font-size:13px;font-weight:700;cursor:pointer;opacity:${saving() || loading() ? 0.6 : 1}`}
|
||||
>
|
||||
{saving() ? 'Saving...' : 'Save Schema'}
|
||||
</button>
|
||||
|
||||
<Show when={loadedVersion() !== null}>
|
||||
<span style="font-size:12px;color:#6B7280">Current version: {loadedVersion()}</span>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={message()}>
|
||||
<div style="border-radius:10px;border:1px solid #E5E7EB;background:#F9FAFB;padding:10px 14px;font-size:13px;color:#374151">
|
||||
{message()}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={roleId() && !loading()}>
|
||||
<div style="display:flex;flex-direction:column;gap:12px">
|
||||
<For each={steps()}>
|
||||
{(step) => (
|
||||
<div
|
||||
draggable
|
||||
onDragStart={() => setDragStepId(step.id)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
moveStep(dragStepId() || '', step.id);
|
||||
setDragStepId(null);
|
||||
}}
|
||||
style={`border-radius:12px;border:1px solid #E5E7EB;background:${dragStepId() === step.id ? '#FFF7ED' : 'white'};overflow:hidden`}
|
||||
>
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:12px 16px;cursor:grab;background:#FAFAFA;border-bottom:1px solid #F3F4F6">
|
||||
<span style="font-size:14px;color:#9CA3AF">::</span>
|
||||
<input
|
||||
value={step.title}
|
||||
onInput={(e) => updateStep(step.id, { title: e.currentTarget.value })}
|
||||
style="flex:1;height:32px;border-radius:6px;border:1px solid #E5E7EB;padding:0 10px;font-size:13px;font-weight:700"
|
||||
/>
|
||||
<select
|
||||
value={step.type}
|
||||
onChange={(e) => updateStep(step.id, { type: e.currentTarget.value as StepType })}
|
||||
style="height:32px;border-radius:6px;border:1px solid #E5E7EB;padding:0 8px;font-size:12px"
|
||||
>
|
||||
<For each={STEP_TYPES}>{(t) => <option value={t}>{t}</option>}</For>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpandedStep(expandedStep() === step.id ? null : step.id)}
|
||||
style="height:32px;border-radius:6px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;cursor:pointer"
|
||||
>
|
||||
{expandedStep() === step.id ? 'Collapse' : `${step.fields.length} field(s)`}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeStep(step.id)}
|
||||
style="height:32px;border-radius:6px;border:1px solid #FECACA;background:#FEF2F2;color:#B91C1C;padding:0 10px;font-size:12px;font-weight:700;cursor:pointer"
|
||||
>
|
||||
Remove Step
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={expandedStep() === step.id}>
|
||||
<div style="padding:12px 16px;display:flex;flex-direction:column;gap:8px">
|
||||
<For each={step.fields}>
|
||||
{(field, idx) => (
|
||||
<div
|
||||
draggable
|
||||
onDragStart={() => setDragField({ stepId: step.id, fieldIdx: idx() })}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
const from = dragField();
|
||||
if (from && from.stepId === step.id) moveField(step.id, from.fieldIdx, idx());
|
||||
setDragField(null);
|
||||
}}
|
||||
style="display:grid;grid-template-columns:1fr 1fr 110px 70px 90px 1fr 70px;gap:8px;align-items:center;border:1px solid #E5E7EB;border-radius:8px;padding:8px;cursor:grab"
|
||||
>
|
||||
<input
|
||||
placeholder="field id (e.g. first_name)"
|
||||
value={field.id}
|
||||
onInput={(e) => updateField(step.id, idx(), { id: e.currentTarget.value })}
|
||||
style="height:30px;border-radius:6px;border:1px solid #E5E7EB;padding:0 8px;font-size:12px"
|
||||
/>
|
||||
<input
|
||||
placeholder="label"
|
||||
value={field.label}
|
||||
onInput={(e) => updateField(step.id, idx(), { label: e.currentTarget.value })}
|
||||
style="height:30px;border-radius:6px;border:1px solid #E5E7EB;padding:0 8px;font-size:12px"
|
||||
/>
|
||||
<select
|
||||
value={field.type}
|
||||
onChange={(e) => updateField(step.id, idx(), { type: e.currentTarget.value as FieldType })}
|
||||
style="height:30px;border-radius:6px;border:1px solid #E5E7EB;font-size:12px"
|
||||
>
|
||||
<For each={FIELD_TYPES}>{(t) => <option value={t}>{t}</option>}</For>
|
||||
</select>
|
||||
<label style="display:flex;align-items:center;gap:4px;font-size:11px;color:#374151">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={field.required}
|
||||
onChange={(e) => updateField(step.id, idx(), { required: e.currentTarget.checked })}
|
||||
/>
|
||||
Req
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:4px;font-size:11px;color:#B45309">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={field.lockAfterApproval}
|
||||
onChange={(e) => updateField(step.id, idx(), { lockAfterApproval: e.currentTarget.checked })}
|
||||
/>
|
||||
Lock
|
||||
</label>
|
||||
<input
|
||||
placeholder="options (comma-separated, select only)"
|
||||
value={field.options || ''}
|
||||
onInput={(e) => updateField(step.id, idx(), { options: e.currentTarget.value })}
|
||||
style="height:30px;border-radius:6px;border:1px solid #E5E7EB;padding:0 8px;font-size:12px"
|
||||
disabled={field.type !== 'select'}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeField(step.id, idx())}
|
||||
style="height:30px;border-radius:6px;border:1px solid #FECACA;background:#FEF2F2;color:#B91C1C;font-size:11px;font-weight:700;cursor:pointer"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addField(step.id)}
|
||||
style="align-self:flex-start;height:30px;border-radius:6px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;cursor:pointer"
|
||||
>
|
||||
+ Add Field
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<For each={STEP_TYPES}>
|
||||
{(t) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => addStep(t)}
|
||||
style="height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:700;cursor:pointer"
|
||||
>
|
||||
+ Add {t} step
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={loading()}>
|
||||
<p style="font-size:13px;color:#6B7280">Loading schema...</p>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import OnboardingDeprecatedPage from '~/components/admin/OnboardingDeprecatedPage';
|
||||
import OnboardingSchemaEditor from '~/components/admin/OnboardingSchemaEditor';
|
||||
|
||||
export default function OnboardingSchemasDetailRoute() {
|
||||
return <OnboardingDeprecatedPage />;
|
||||
return <OnboardingSchemaEditor />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import OnboardingDeprecatedPage from '~/components/admin/OnboardingDeprecatedPage';
|
||||
import OnboardingSchemaEditor from '~/components/admin/OnboardingSchemaEditor';
|
||||
|
||||
export default function OnboardingSchemasIndexRoute() {
|
||||
return <OnboardingDeprecatedPage />;
|
||||
return <OnboardingSchemaEditor />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import OnboardingDeprecatedPage from '~/components/admin/OnboardingDeprecatedPage';
|
||||
import OnboardingSchemaEditor from '~/components/admin/OnboardingSchemaEditor';
|
||||
|
||||
export default function OnboardingSchemasNewRoute() {
|
||||
return <OnboardingDeprecatedPage />;
|
||||
return <OnboardingSchemaEditor />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue