| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- import { useState } from 'react';
- import { useNavigate } from 'react-router-dom';
- import { useMutation } from '@tanstack/react-query';
- import { useTranslation } from 'react-i18next';
- import { api } from '../api/client';
- import { useToast } from '../contexts/ToastContext';
- import { useTheme } from '../contexts/ThemeContext';
- import { useAuth } from '../contexts/AuthContext';
- import { Info } from 'lucide-react';
- export function SetupPage() {
- const navigate = useNavigate();
- const { t } = useTranslation();
- const { showToast } = useToast();
- const { mode } = useTheme();
- const { refreshAuth } = useAuth();
- const [authEnabled, setAuthEnabled] = useState(false);
- const [adminUsername, setAdminUsername] = useState('');
- const [adminPassword, setAdminPassword] = useState('');
- const [confirmPassword, setConfirmPassword] = useState('');
- const setupMutation = useMutation({
- mutationFn: () =>
- api.setupAuth({
- auth_enabled: authEnabled,
- admin_username: authEnabled ? adminUsername : undefined,
- admin_password: authEnabled ? adminPassword : undefined,
- }),
- onSuccess: async (data) => {
- // Refresh auth status after setup
- await refreshAuth();
- if (data.auth_enabled) {
- if (data.admin_created) {
- showToast(t('setup.toast.authEnabledAdminCreated'));
- navigate('/login');
- } else {
- showToast(t('setup.toast.authEnabledExistingAdmins'));
- navigate('/login');
- }
- } else {
- showToast(t('setup.toast.setupCompleted'));
- navigate('/');
- }
- },
- onError: (error: Error) => {
- showToast(error.message, 'error');
- },
- });
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- if (authEnabled) {
- // Only validate if credentials are provided
- // If no credentials provided, backend will use existing admin users if they exist
- if (adminUsername || adminPassword) {
- if (!adminUsername || !adminPassword) {
- showToast(t('setup.toast.enterBothCredentials'), 'error');
- return;
- }
- if (adminPassword !== confirmPassword) {
- showToast(t('setup.toast.passwordsDoNotMatch'), 'error');
- return;
- }
- if (adminPassword.length < 6) {
- showToast(t('setup.toast.passwordTooShort'), 'error');
- return;
- }
- }
- }
- setupMutation.mutate();
- };
- return (
- <div className="min-h-screen flex items-center justify-center bg-bambu-dark p-4">
- <div className="max-w-md w-full space-y-8 p-8 bg-gradient-to-br from-bambu-card to-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary shadow-lg">
- <div className="text-center">
- <div className="flex items-center justify-center mb-6">
- <img
- src={mode === 'dark' ? '/img/bambuddy_logo_dark_transparent.png' : '/img/bambuddy_logo_light.png'}
- alt="Bambuddy"
- className="h-16"
- />
- </div>
- <h2 className="text-3xl font-bold text-white">
- {t('setup.title')}
- </h2>
- <p className="mt-2 text-sm text-bambu-gray">
- {t('setup.subtitle')}
- </p>
- </div>
- <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
- <div className="space-y-4">
- <div className="flex items-center p-4 bg-bambu-dark-secondary/50 rounded-lg border border-bambu-dark-tertiary">
- <input
- id="auth-enabled"
- type="checkbox"
- checked={authEnabled}
- onChange={(e) => setAuthEnabled(e.target.checked)}
- className="h-4 w-4 text-bambu-green focus:ring-bambu-green border-bambu-dark-tertiary rounded bg-bambu-dark-secondary"
- />
- <label htmlFor="auth-enabled" className="ml-3 block text-sm font-medium text-white">
- {t('setup.enableAuth')}
- </label>
- </div>
- {authEnabled && (
- <div className="space-y-4 mt-4">
- <div className="p-3 bg-bambu-dark-secondary/50 border border-bambu-dark-tertiary rounded-lg">
- <div className="flex items-start gap-2">
- <Info className="w-4 h-4 text-bambu-green mt-0.5 flex-shrink-0" />
- <div className="text-sm text-bambu-gray">
- <p className="text-white font-medium mb-1">{t('setup.adminAccount')}</p>
- <p>
- {t('setup.adminAccountDesc')}
- </p>
- </div>
- </div>
- </div>
- <div>
- <label htmlFor="admin-username" className="block text-sm font-medium text-white mb-2">
- {t('setup.adminUsername')} <span className="text-bambu-gray text-xs">{t('setup.optionalIfAdminExists')}</span>
- </label>
- <input
- id="admin-username"
- type="text"
- value={adminUsername}
- onChange={(e) => setAdminUsername(e.target.value)}
- className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
- placeholder={t('setup.adminUsernamePlaceholder')}
- autoComplete="username"
- />
- </div>
- <div>
- <label htmlFor="admin-password" className="block text-sm font-medium text-white mb-2">
- {t('setup.adminPassword')} <span className="text-bambu-gray text-xs">{t('setup.optionalIfAdminExists')}</span>
- </label>
- <input
- id="admin-password"
- type="password"
- value={adminPassword}
- onChange={(e) => setAdminPassword(e.target.value)}
- className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
- placeholder={t('setup.adminPasswordPlaceholder')}
- minLength={6}
- autoComplete="new-password"
- />
- </div>
- {adminPassword && (
- <div>
- <label htmlFor="confirm-password" className="block text-sm font-medium text-white mb-2">
- {t('setup.confirmPassword')}
- </label>
- <input
- id="confirm-password"
- type="password"
- value={confirmPassword}
- onChange={(e) => setConfirmPassword(e.target.value)}
- className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
- placeholder={t('setup.confirmPasswordPlaceholder')}
- minLength={6}
- autoComplete="new-password"
- />
- </div>
- )}
- </div>
- )}
- </div>
- <div>
- <button
- type="submit"
- disabled={setupMutation.isPending}
- className="w-full flex justify-center py-3 px-4 bg-bambu-green hover:bg-bambu-green-light text-white font-medium rounded-lg shadow-lg shadow-bambu-green/20 hover:shadow-bambu-green/30 focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:ring-offset-2 focus:ring-offset-bambu-dark-secondary transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-bambu-green"
- >
- {setupMutation.isPending ? t('setup.settingUp') : t('setup.completeSetup')}
- </button>
- </div>
- </form>
- </div>
- </div>
- );
- }
|