SetupPage.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import { useMutation } from '@tanstack/react-query';
  4. import { api } from '../api/client';
  5. import { useToast } from '../contexts/ToastContext';
  6. import { useTheme } from '../contexts/ThemeContext';
  7. import { useAuth } from '../contexts/AuthContext';
  8. import { Info } from 'lucide-react';
  9. export function SetupPage() {
  10. const navigate = useNavigate();
  11. const { showToast } = useToast();
  12. const { mode } = useTheme();
  13. const { refreshAuth } = useAuth();
  14. const [authEnabled, setAuthEnabled] = useState(false);
  15. const [adminUsername, setAdminUsername] = useState('');
  16. const [adminPassword, setAdminPassword] = useState('');
  17. const [confirmPassword, setConfirmPassword] = useState('');
  18. const setupMutation = useMutation({
  19. mutationFn: () =>
  20. api.setupAuth({
  21. auth_enabled: authEnabled,
  22. admin_username: authEnabled ? adminUsername : undefined,
  23. admin_password: authEnabled ? adminPassword : undefined,
  24. }),
  25. onSuccess: async (data) => {
  26. // Refresh auth status after setup
  27. await refreshAuth();
  28. if (data.auth_enabled) {
  29. if (data.admin_created) {
  30. showToast('Authentication enabled and admin user created');
  31. navigate('/login');
  32. } else {
  33. showToast('Authentication enabled using existing admin users');
  34. navigate('/login');
  35. }
  36. } else {
  37. showToast('Setup completed');
  38. navigate('/');
  39. }
  40. },
  41. onError: (error: Error) => {
  42. showToast(error.message, 'error');
  43. },
  44. });
  45. const handleSubmit = (e: React.FormEvent) => {
  46. e.preventDefault();
  47. if (authEnabled) {
  48. // Only validate if credentials are provided
  49. // If no credentials provided, backend will use existing admin users if they exist
  50. if (adminUsername || adminPassword) {
  51. if (!adminUsername || !adminPassword) {
  52. showToast('Please enter both admin username and password, or leave both empty to use existing admin users', 'error');
  53. return;
  54. }
  55. if (adminPassword !== confirmPassword) {
  56. showToast('Passwords do not match', 'error');
  57. return;
  58. }
  59. if (adminPassword.length < 6) {
  60. showToast('Password must be at least 6 characters', 'error');
  61. return;
  62. }
  63. }
  64. }
  65. setupMutation.mutate();
  66. };
  67. return (
  68. <div className="min-h-screen flex items-center justify-center bg-bambu-dark p-4">
  69. <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">
  70. <div className="text-center">
  71. <div className="flex items-center justify-center mb-6">
  72. <img
  73. src={mode === 'dark' ? '/img/bambuddy_logo_dark_transparent.png' : '/img/bambuddy_logo_light.png'}
  74. alt="Bambuddy"
  75. className="h-16"
  76. />
  77. </div>
  78. <h2 className="text-3xl font-bold text-white">
  79. Bambuddy Setup
  80. </h2>
  81. <p className="mt-2 text-sm text-bambu-gray">
  82. Configure authentication for your Bambuddy instance
  83. </p>
  84. </div>
  85. <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
  86. <div className="space-y-4">
  87. <div className="flex items-center p-4 bg-bambu-dark-secondary/50 rounded-lg border border-bambu-dark-tertiary">
  88. <input
  89. id="auth-enabled"
  90. type="checkbox"
  91. checked={authEnabled}
  92. onChange={(e) => setAuthEnabled(e.target.checked)}
  93. className="h-4 w-4 text-bambu-green focus:ring-bambu-green border-bambu-dark-tertiary rounded bg-bambu-dark-secondary"
  94. />
  95. <label htmlFor="auth-enabled" className="ml-3 block text-sm font-medium text-white">
  96. Enable Authentication
  97. </label>
  98. </div>
  99. {authEnabled && (
  100. <div className="space-y-4 mt-4">
  101. <div className="p-3 bg-bambu-dark-secondary/50 border border-bambu-dark-tertiary rounded-lg">
  102. <div className="flex items-start gap-2">
  103. <Info className="w-4 h-4 text-bambu-green mt-0.5 flex-shrink-0" />
  104. <div className="text-sm text-bambu-gray">
  105. <p className="text-white font-medium mb-1">Admin Account</p>
  106. <p>
  107. If admin users already exist, authentication will be enabled using the existing admin accounts.
  108. Leave the fields below empty to use existing admins, or enter new credentials to create a new admin user.
  109. </p>
  110. </div>
  111. </div>
  112. </div>
  113. <div>
  114. <label htmlFor="admin-username" className="block text-sm font-medium text-white mb-2">
  115. Admin Username <span className="text-bambu-gray text-xs">(optional if admin users exist)</span>
  116. </label>
  117. <input
  118. id="admin-username"
  119. type="text"
  120. value={adminUsername}
  121. onChange={(e) => setAdminUsername(e.target.value)}
  122. 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"
  123. placeholder="Enter admin username (optional)"
  124. autoComplete="username"
  125. />
  126. </div>
  127. <div>
  128. <label htmlFor="admin-password" className="block text-sm font-medium text-white mb-2">
  129. Admin Password <span className="text-bambu-gray text-xs">(optional if admin users exist)</span>
  130. </label>
  131. <input
  132. id="admin-password"
  133. type="password"
  134. value={adminPassword}
  135. onChange={(e) => setAdminPassword(e.target.value)}
  136. 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"
  137. placeholder="Enter admin password (optional)"
  138. minLength={6}
  139. autoComplete="new-password"
  140. />
  141. </div>
  142. {adminPassword && (
  143. <div>
  144. <label htmlFor="confirm-password" className="block text-sm font-medium text-white mb-2">
  145. Confirm Password
  146. </label>
  147. <input
  148. id="confirm-password"
  149. type="password"
  150. value={confirmPassword}
  151. onChange={(e) => setConfirmPassword(e.target.value)}
  152. 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"
  153. placeholder="Confirm admin password"
  154. minLength={6}
  155. autoComplete="new-password"
  156. />
  157. </div>
  158. )}
  159. </div>
  160. )}
  161. </div>
  162. <div>
  163. <button
  164. type="submit"
  165. disabled={setupMutation.isPending}
  166. 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"
  167. >
  168. {setupMutation.isPending ? 'Setting up...' : 'Complete Setup'}
  169. </button>
  170. </div>
  171. </form>
  172. </div>
  173. </div>
  174. );
  175. }