2024-11-28 23:10:00 +08:00
|
|
|
|
import React, { createContext, useState, useEffect } from "react";
|
2024-11-30 02:15:46 +08:00
|
|
|
|
import {useHttp} from 'hooks/servicesProvider'
|
|
|
|
|
import { message} from "hooks/message";
|
|
|
|
|
import {DEFAULT_CONFIG} from "app/env"
|
|
|
|
|
|
|
|
|
|
|
2024-11-27 19:52:49 +08:00
|
|
|
|
interface SetupContextType {
|
|
|
|
|
currentStep: number;
|
|
|
|
|
setCurrentStep: (step: number) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SetupContext = createContext<SetupContextType>({
|
|
|
|
|
currentStep: 1,
|
|
|
|
|
setCurrentStep: () => {},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 步骤组件的通用属性接口
|
|
|
|
|
interface StepProps {
|
|
|
|
|
onNext: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({
|
|
|
|
|
title,
|
|
|
|
|
children,
|
|
|
|
|
}) => (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div className="mx-auto max-w-3xl">
|
|
|
|
|
<h2 className="text-xl font-medium text-custom-title-light dark:text-custom-title-dark mb-6 px-4">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
{title}
|
|
|
|
|
</h2>
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div className="space-y-6 px-4">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 通用的导航按钮组件
|
2024-11-30 02:15:46 +08:00
|
|
|
|
const NavigationButtons: React.FC<StepProps & { loading?: boolean; disabled?: boolean }> = ({
|
|
|
|
|
onNext,
|
|
|
|
|
loading = false,
|
|
|
|
|
disabled = false
|
|
|
|
|
}) => (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div className="flex justify-end mt-4">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
<button
|
|
|
|
|
onClick={onNext}
|
2024-11-30 02:15:46 +08:00
|
|
|
|
disabled={loading || disabled}
|
|
|
|
|
className={`px-6 py-2 rounded-lg transition-colors font-medium text-sm
|
|
|
|
|
${loading || disabled
|
|
|
|
|
? 'bg-gray-400 cursor-not-allowed'
|
|
|
|
|
: 'bg-blue-500 hover:bg-blue-600 text-white'
|
|
|
|
|
}`}
|
2024-11-27 19:52:49 +08:00
|
|
|
|
>
|
2024-11-30 02:15:46 +08:00
|
|
|
|
{loading ? '处理中...' : '下一步'}
|
2024-11-27 19:52:49 +08:00
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 输入框组件
|
|
|
|
|
const InputField: React.FC<{
|
|
|
|
|
label: string;
|
|
|
|
|
name: string;
|
|
|
|
|
defaultValue?: string | number;
|
|
|
|
|
hint?: string;
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required?: boolean;
|
|
|
|
|
}> = ({ label, name, defaultValue, hint, required = true }) => (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div className="mb-6">
|
|
|
|
|
<h3 className="text-base font-medium text-custom-title-light dark:text-custom-title-dark mb-2">
|
2024-11-30 02:15:46 +08:00
|
|
|
|
{label} {required && <span className="text-red-500">*</span>}
|
2024-11-27 19:52:49 +08:00
|
|
|
|
</h3>
|
|
|
|
|
<input
|
|
|
|
|
name={name}
|
|
|
|
|
defaultValue={defaultValue}
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required={required}
|
2024-11-28 23:10:00 +08:00
|
|
|
|
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
|
|
|
|
{hint && (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<p className="text-xs text-custom-p-light dark:text-custom-p-dark mt-1.5">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
{hint}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const Introduction: React.FC<StepProps> = ({ onNext }) => (
|
|
|
|
|
<StepContainer title="安装说明">
|
|
|
|
|
<div className="space-y-6">
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<p className="text-base text-custom-p-light dark:text-custom-p-dark">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
欢迎使用 Echoes
|
|
|
|
|
</p>
|
|
|
|
|
<NavigationButtons onNext={onNext} />
|
|
|
|
|
</div>
|
|
|
|
|
</StepContainer>
|
|
|
|
|
);
|
|
|
|
|
|
2024-11-28 23:10:00 +08:00
|
|
|
|
const DatabaseConfig: React.FC<StepProps> = ({ onNext }) => {
|
2024-11-27 19:52:49 +08:00
|
|
|
|
const [dbType, setDbType] = useState("postgresql");
|
2024-11-30 02:15:46 +08:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const api = useHttp();
|
|
|
|
|
|
|
|
|
|
const validateForm = () => {
|
|
|
|
|
const getRequiredFields = () => {
|
|
|
|
|
switch (dbType) {
|
|
|
|
|
case 'sqllite':
|
|
|
|
|
return ['db_prefix', 'db_name'];
|
|
|
|
|
case 'postgresql':
|
|
|
|
|
case 'mysql':
|
|
|
|
|
return ['db_host', 'db_prefix', 'db_port', 'db_user', 'db_password', 'db_name'];
|
|
|
|
|
default:
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const requiredFields = getRequiredFields();
|
|
|
|
|
const emptyFields: string[] = [];
|
|
|
|
|
|
|
|
|
|
requiredFields.forEach(field => {
|
|
|
|
|
const input = document.querySelector(`[name="${field}"]`) as HTMLInputElement;
|
|
|
|
|
if (input && (!input.value || input.value.trim() === '')) {
|
|
|
|
|
emptyFields.push(field);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (emptyFields.length > 0) {
|
|
|
|
|
const fieldNames = emptyFields.map(field => {
|
|
|
|
|
switch (field) {
|
|
|
|
|
case 'db_host': return '数据库地址';
|
|
|
|
|
case 'db_prefix': return '数据库前缀';
|
|
|
|
|
case 'db_port': return '端口';
|
|
|
|
|
case 'db_user': return '用户名';
|
|
|
|
|
case 'db_password': return '密码';
|
|
|
|
|
case 'db_name': return '数据库名';
|
|
|
|
|
default: return field;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
message.error(`请填写以下必填项:${fieldNames.join('、')}`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNext = async () => {
|
|
|
|
|
if (!validateForm()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const formData = {
|
|
|
|
|
db_type: dbType,
|
|
|
|
|
host: (document.querySelector('[name="db_host"]') as HTMLInputElement)?.value?.trim()??"",
|
|
|
|
|
db_prefix: (document.querySelector('[name="db_prefix"]') as HTMLInputElement)?.value?.trim()??"",
|
|
|
|
|
port: Number((document.querySelector('[name="db_port"]') as HTMLInputElement)?.value?.trim()??0),
|
|
|
|
|
user: (document.querySelector('[name="db_user"]') as HTMLInputElement)?.value?.trim()??"",
|
|
|
|
|
password: (document.querySelector('[name="db_password"]') as HTMLInputElement)?.value?.trim()??"",
|
|
|
|
|
db_name: (document.querySelector('[name="db_name"]') as HTMLInputElement)?.value?.trim()??"",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await api.post('/sql', formData);
|
|
|
|
|
|
|
|
|
|
let oldEnv = import.meta.env?? DEFAULT_CONFIG
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const viteEnv = Object.entries(oldEnv).reduce((acc, [key, value]) => {
|
|
|
|
|
if (key.startsWith('VITE_')) {
|
|
|
|
|
acc[key] = value;
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
}, {} as Record<string, any>);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const newEnv = {
|
|
|
|
|
...viteEnv,
|
|
|
|
|
VITE_INIT_STATUS: '2'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await api.dev("/env", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(newEnv),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Object.assign( newEnv)
|
|
|
|
|
|
|
|
|
|
message.success('数据库配置成功!');
|
|
|
|
|
setTimeout(() => onNext(), 1000);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error( error);
|
|
|
|
|
message.error(error.message );
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-11-27 19:52:49 +08:00
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<StepContainer title="数据库配置">
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div>
|
2024-11-27 19:52:49 +08:00
|
|
|
|
<div className="mb-6">
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<h3 className="text-base font-medium text-custom-title-light dark:text-custom-title-dark mb-1.5">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
数据库类型
|
|
|
|
|
</h3>
|
|
|
|
|
<select
|
|
|
|
|
value={dbType}
|
|
|
|
|
onChange={(e) => setDbType(e.target.value)}
|
2024-11-28 23:10:00 +08:00
|
|
|
|
className="w-full p-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
|
2024-11-27 19:52:49 +08:00
|
|
|
|
>
|
|
|
|
|
<option value="postgresql">PostgreSQL</option>
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<option value="mysql">MySQL</option>
|
|
|
|
|
<option value="sqllite">SQLite</option>
|
2024-11-27 19:52:49 +08:00
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{dbType === "postgresql" && (
|
|
|
|
|
<>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库地址"
|
|
|
|
|
name="db_host"
|
|
|
|
|
defaultValue="localhost"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
hint="通常使 localhost"
|
|
|
|
|
required
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<InputField
|
|
|
|
|
label="数据库前缀"
|
|
|
|
|
name="db_prefix"
|
|
|
|
|
defaultValue="echoec_"
|
|
|
|
|
hint="通常使用 echoec_"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
2024-11-27 19:52:49 +08:00
|
|
|
|
<InputField
|
|
|
|
|
label="端口"
|
|
|
|
|
name="db_port"
|
|
|
|
|
defaultValue={5432}
|
|
|
|
|
hint="PostgreSQL 默认端口为 5432"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="用户名"
|
|
|
|
|
name="db_user"
|
|
|
|
|
defaultValue="postgres"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="密码"
|
|
|
|
|
name="db_password"
|
|
|
|
|
defaultValue="postgres"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库名"
|
|
|
|
|
name="db_name"
|
|
|
|
|
defaultValue="echoes"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-27 19:52:49 +08:00
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2024-11-28 23:10:00 +08:00
|
|
|
|
{dbType === "mysql" && (
|
|
|
|
|
<>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库地址"
|
|
|
|
|
name="db_host"
|
|
|
|
|
defaultValue="localhost"
|
|
|
|
|
hint="通常使用 localhost"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库前缀"
|
|
|
|
|
name="db_prefix"
|
|
|
|
|
defaultValue="echoec_"
|
|
|
|
|
hint="通常使用 echoec_"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="端口"
|
|
|
|
|
name="db_port"
|
|
|
|
|
defaultValue={3306}
|
|
|
|
|
hint="mysql 默认端口为 3306"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="用户名"
|
|
|
|
|
name="db_user"
|
|
|
|
|
defaultValue="root"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="密码"
|
|
|
|
|
name="db_password"
|
|
|
|
|
defaultValue="mysql"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库名"
|
|
|
|
|
name="db_name"
|
|
|
|
|
defaultValue="echoes"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{dbType === "sqllite" && (
|
|
|
|
|
<>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库前缀"
|
|
|
|
|
name="db_prefix"
|
|
|
|
|
defaultValue="echoec_"
|
|
|
|
|
hint="通常使用 echoec_"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
<InputField
|
|
|
|
|
label="数据库名"
|
|
|
|
|
name="db_name"
|
|
|
|
|
defaultValue="echoes.db"
|
2024-11-30 02:15:46 +08:00
|
|
|
|
required
|
2024-11-28 23:10:00 +08:00
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2024-11-30 02:15:46 +08:00
|
|
|
|
<NavigationButtons
|
|
|
|
|
onNext={handleNext}
|
|
|
|
|
loading={loading}
|
|
|
|
|
disabled={loading}
|
|
|
|
|
/>
|
2024-11-27 19:52:49 +08:00
|
|
|
|
</div>
|
|
|
|
|
</StepContainer>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2024-11-30 02:15:46 +08:00
|
|
|
|
interface InstallReplyData {
|
|
|
|
|
token: string,
|
|
|
|
|
username: string,
|
|
|
|
|
password: string,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const AdminConfig: React.FC<StepProps> = ({ onNext }) => {
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const api = useHttp();
|
|
|
|
|
|
|
|
|
|
const handleNext = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const formData = {
|
|
|
|
|
username: (document.querySelector('[name="admin_username"]') as HTMLInputElement)?.value,
|
|
|
|
|
password: (document.querySelector('[name="admin_password"]') as HTMLInputElement)?.value,
|
|
|
|
|
email: (document.querySelector('[name="admin_email"]') as HTMLInputElement)?.value,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const response = await api.post('/administrator', formData) as InstallReplyData;
|
|
|
|
|
const data = response;
|
|
|
|
|
|
|
|
|
|
localStorage.setItem('token', data.token);
|
|
|
|
|
|
|
|
|
|
let oldEnv = import.meta.env ?? DEFAULT_CONFIG;
|
|
|
|
|
const viteEnv = Object.entries(oldEnv).reduce((acc, [key, value]) => {
|
|
|
|
|
if (key.startsWith('VITE_')) {
|
|
|
|
|
acc[key] = value;
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
}, {} as Record<string, any>);
|
|
|
|
|
|
|
|
|
|
const newEnv = {
|
|
|
|
|
...viteEnv,
|
|
|
|
|
VITE_INIT_STATUS: '3',
|
|
|
|
|
VITE_API_USERNAME:data.username,
|
|
|
|
|
VITE_API_PASSWORD:data.password
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await api.dev("/env", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(newEnv),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
message.success('管理员账号创建成功!');
|
|
|
|
|
onNext();
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
message.error(error.message);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<StepContainer title="创建管理员账号">
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<InputField label="用户名" name="admin_username" />
|
|
|
|
|
<InputField label="密码" name="admin_password" />
|
|
|
|
|
<InputField label="邮箱" name="admin_email" />
|
|
|
|
|
<NavigationButtons onNext={handleNext} loading={loading} />
|
|
|
|
|
</div>
|
|
|
|
|
</StepContainer>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SetupComplete: React.FC = () => {
|
|
|
|
|
const api = useHttp();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<StepContainer title="安装完成">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<p className="text-xl text-custom-p-light dark:text-custom-p-dark mb-4">
|
|
|
|
|
恭喜!安装已完成
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-base text-custom-p-light dark:text-custom-p-dark">
|
|
|
|
|
系统正在重启中,请稍候...
|
|
|
|
|
</p>
|
|
|
|
|
<div className="mt-4">
|
|
|
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</StepContainer>
|
|
|
|
|
);
|
|
|
|
|
};
|
2024-11-27 19:52:49 +08:00
|
|
|
|
|
2024-11-28 23:10:00 +08:00
|
|
|
|
// 修改主题切换按钮组件
|
|
|
|
|
const ThemeToggle: React.FC = () => {
|
|
|
|
|
const [isDark, setIsDark] = useState(false);
|
|
|
|
|
const [isVisible, setIsVisible] = useState(true);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const isDarkMode = document.documentElement.classList.contains('dark');
|
|
|
|
|
setIsDark(isDarkMode);
|
|
|
|
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
const currentScrollPos = window.scrollY;
|
|
|
|
|
setIsVisible(currentScrollPos < 100); // 滚动超过100px就隐藏
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const toggleTheme = () => {
|
|
|
|
|
const newIsDark = !isDark;
|
|
|
|
|
setIsDark(newIsDark);
|
|
|
|
|
document.documentElement.classList.toggle('dark');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
onClick={toggleTheme}
|
|
|
|
|
className={`absolute top-4 right-4 p-2.5 rounded-lg bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-all duration-300 ${
|
|
|
|
|
isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{isDark ? (
|
|
|
|
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
|
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" />
|
|
|
|
|
</svg>
|
|
|
|
|
) : (
|
|
|
|
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
|
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
|
|
|
|
</svg>
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-27 19:52:49 +08:00
|
|
|
|
export default function SetupPage() {
|
2024-11-30 02:15:46 +08:00
|
|
|
|
let step = Number(import.meta.env.VITE_INIT_STATUS)+1;
|
2024-11-28 23:10:00 +08:00
|
|
|
|
|
|
|
|
|
const [currentStep, setCurrentStep] = useState(step);
|
2024-11-27 19:52:49 +08:00
|
|
|
|
|
|
|
|
|
return (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<div className="relative min-h-screen w-full bg-custom-bg-light dark:bg-custom-bg-dark">
|
|
|
|
|
<ThemeToggle />
|
|
|
|
|
<div className="container mx-auto py-8">
|
2024-11-27 19:52:49 +08:00
|
|
|
|
<SetupContext.Provider value={{ currentStep, setCurrentStep }}>
|
|
|
|
|
{currentStep === 1 && (
|
|
|
|
|
<Introduction onNext={() => setCurrentStep(currentStep + 1)} />
|
|
|
|
|
)}
|
|
|
|
|
{currentStep === 2 && (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<DatabaseConfig
|
2024-11-27 19:52:49 +08:00
|
|
|
|
onNext={() => setCurrentStep(currentStep + 1)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{currentStep === 3 && (
|
2024-11-28 23:10:00 +08:00
|
|
|
|
<AdminConfig
|
2024-11-27 19:52:49 +08:00
|
|
|
|
onNext={() => setCurrentStep(currentStep + 1)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{currentStep === 4 && <SetupComplete />}
|
|
|
|
|
</SetupContext.Provider>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2024-11-27 01:02:05 +08:00
|
|
|
|
}
|