/** * 完成初始化頁面 * * 流程: * 1. 填寫公司基本資訊 * 2. 設定郵件網域 * 3. 設定管理員帳號 * 4. 確認並執行初始化 */ 'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' interface CompanyInfo { company_name: string company_name_en: string tenant_code: string tenant_prefix: string tax_id: string tel: string add: string } interface MailDomainInfo { domain_set: 1 | 2 domain: string } interface AdminInfo { admin_legal_name: string admin_english_name: string admin_email: string admin_phone: string password_method: 'auto' | 'manual' manual_password?: string } export default function CompleteInitialization() { const router = useRouter() const [step, setStep] = useState<'checking' | 'company' | 'maildomain' | 'admin' | 'confirm' | 'executing'>('checking') const [sessionId, setSessionId] = useState(null) const [checkingDb, setCheckingDb] = useState(true) const [dbReady, setDbReady] = useState(false) const [companyInfo, setCompanyInfo] = useState({ company_name: '', company_name_en: '', tenant_code: '', tenant_prefix: '', tax_id: '', tel: '', add: '', }) const [mailDomainInfo, setMailDomainInfo] = useState({ domain_set: 2, domain: '', }) const [adminInfo, setAdminInfo] = useState({ admin_legal_name: '', admin_english_name: '', admin_email: '', admin_phone: '', password_method: 'auto', }) const [generatedPassword, setGeneratedPassword] = useState('') const [executing, setExecuting] = useState(false) // 載入 Keycloak Realm 名稱作為租戶代碼預設值 useEffect(() => { const loadKeycloakRealm = async () => { try { const response = await fetch('http://10.1.0.245:10181/api/v1/installation/get-config/keycloak') if (response.ok) { const data = await response.json() if (data.configured && data.config && data.config.realm) { setCompanyInfo(prev => ({ ...prev, tenant_code: data.config.realm.toLowerCase(), // ⚠️ 必須小寫,與 Keycloak Realm 一致 })) } } setCheckingDb(false) setDbReady(true) setStep('company') } catch (error) { console.error('[Installation] Failed to load Keycloak config:', error) setCheckingDb(false) setDbReady(true) setStep('company') } } loadKeycloakRealm() }, []) // Step 1: 建立會話並儲存公司資訊 const handleSaveCompany = async () => { try { // 建立會話 if (!sessionId) { const sessionResponse = await fetch('http://10.1.0.245:10181/api/v1/installation/sessions', { method: 'POST', }) if (!sessionResponse.ok) { throw new Error('建立會話失敗') } const sessionData = await sessionResponse.json() setSessionId(sessionData.session_id) } setStep('maildomain') } catch (error) { alert(`錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`) } } // Step 2: 儲存郵件網域設定 const handleSaveMailDomain = async () => { try { if (!sessionId) { throw new Error('會話 ID 不存在') } // 合併公司資訊 + 郵件網域資訊 const tenantData = { ...companyInfo, ...mailDomainInfo, } const response = await fetch(`http://10.1.0.245:10181/api/v1/installation/sessions/${sessionId}/tenant-info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(tenantData), }) if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: `HTTP ${response.status}` })) throw new Error(`儲存租戶資訊失敗: ${errorData.detail || response.statusText}`) } setStep('admin') } catch (error) { console.error('[MailDomain] Save failed:', error) alert(`錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`) } } // Step 3: 設定管理員帳號 const handleSaveAdmin = async () => { try { if (!sessionId) { throw new Error('會話 ID 不存在') } const response = await fetch(`http://10.1.0.245:10181/api/v1/installation/sessions/${sessionId}/admin-setup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(adminInfo), }) const data = await response.json() if (!response.ok) { throw new Error(data.detail || '設定管理員失敗') } if (data.initial_password) { setGeneratedPassword(data.initial_password) } setStep('confirm') } catch (error) { alert(`錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`) } } // Step 4: 執行初始化 const handleExecute = async () => { try { if (!sessionId) { throw new Error('會話 ID 不存在') } setStep('executing') setExecuting(true) const response = await fetch(`http://10.1.0.245:10181/api/v1/installation/sessions/${sessionId}/execute`, { method: 'POST', }) const data = await response.json() if (!response.ok) { throw new Error(data.detail || '初始化失敗') } // 從回應中取得臨時密碼 const tempPassword = data.result?.credentials?.plain_password || '(密碼已清除,請聯絡管理員)' const username = adminInfo.admin_english_name // 顯示完成訊息和臨時密碼 alert(`初始化完成! 請使用以下資訊登入 SSO 系統: 帳號: ${username} 臨時密碼: ${tempPassword} ⚠️ 重要提醒: 1. 請立即記下或截圖這個密碼 2. 首次登入後系統會要求您變更密碼 3. 此密碼僅顯示一次,關閉後將無法再次查看 點擊「確定」後將跳轉至登入頁面。`) // 跳轉到登入頁面 window.location.href = '/auth/signin' } catch (error) { alert(`錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`) setExecuting(false) } } return (
{/* Progress Bar */}
初始化進度 {step === 'company' ? '1' : step === 'maildomain' ? '2' : step === 'admin' ? '3' : step === 'confirm' ? '4' : '5'} / 4
{/* Main Card */}

完成初始化

{step === 'company' && '填寫公司基本資訊'} {step === 'maildomain' && '設定郵件網域'} {step === 'admin' && '設定系統管理員'} {step === 'confirm' && '確認並執行'} {step === 'executing' && '正在初始化系統...'}

{/* Step 1: 公司資訊 */} {step === 'company' && (
setCompanyInfo({ ...companyInfo, company_name: e.target.value })} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="例如: 匠耘股份有限公司" />
setCompanyInfo({ ...companyInfo, company_name_en: e.target.value })} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="例如: Porsche World Co., Ltd." />
setCompanyInfo({ ...companyInfo, tax_id: e.target.value })} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="例如: 82871784" maxLength={8} />
setCompanyInfo({ ...companyInfo, tel: e.target.value })} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:ring-2 focus:ring-green-500 focus:border-transparent" placeholder="例如: 02-26262026" />