// First-Run Setup Application
// Purpose: Initializes a fresh server or imports an explicitly selected YAML configuration file through the restricted setup channel.
// Scope: Exists only while the server reports setup required; ordinary administration belongs to /admin.
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import CardFrame from '../components/CardFrame/index.jsx';
import SocketConnectionPill from '../components/SocketConnectionPill/index.jsx';
import { useSocket } from '../context/SocketContext.jsx';
import { createFirstAdministrator, getSetupStatus, importConfigurationFile } from './api.js';
export default function SetupApp() {
const socket = useSocket();
const [required, setRequired] = useState(null);
const [setupCode, setSetupCode] = useState('');
const [username, setUsername] = useState('');
const [discordId, setDiscordId] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [configurationFile, setConfigurationFile] = useState(null);
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState('');
useEffect(() => {
getSetupStatus(socket).then((response) => setRequired(response.required)).catch((error) => setMessage(error.message));
}, [socket]);
async function run(work) {
setBusy(true);
setMessage('');
try {
await work();
setRequired(false);
setMessage('Setup completed. You can now open the administration application and log in.');
} catch (error) {
const details = Array.isArray(error.validationErrors)
? ` ${error.validationErrors.map((entry) => `${entry.path}: ${entry.message}`).join('; ')}`
: '';
setMessage(`${error.message}${details}`);
} finally {
setBusy(false);
}
}
function createAdministrator(event) {
event.preventDefault();
if (password !== confirmPassword) {
setMessage('Passwords do not match.');
return;
}
run(() => createFirstAdministrator(socket, { setupCode, username, discordId, password }));
}
function importSelectedConfiguration(event) {
event.preventDefault();
if (!configurationFile) return;
run(async () => importConfigurationFile(socket, {
setupCode,
fileName: configurationFile.name,
yaml: await configurationFile.text(),
}));
}
return (
{required ?
Enter the one-time code from setup-code.txt in the server data folder, then create the first lockdown administrator or import an existing configuration.