feat: add mail service for managing email configurations, generating accounts, and fetching emails with verification code support.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Menu, Moon, Sun } from 'lucide-react'
|
||||
import { Menu, Moon, Sun, Database, UserPlus } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTeamStatus } from '../../hooks/useTeamStatus'
|
||||
|
||||
interface HeaderProps {
|
||||
onMenuClick: () => void
|
||||
@@ -8,6 +9,7 @@ interface HeaderProps {
|
||||
|
||||
export default function Header({ onMenuClick, isConnected = false }: HeaderProps) {
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
const teamStatus = useTeamStatus()
|
||||
|
||||
useEffect(() => {
|
||||
// Check for saved theme preference or system preference
|
||||
@@ -55,6 +57,23 @@ export default function Header({ onMenuClick, isConnected = false }: HeaderProps
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Team Status Indicators */}
|
||||
{teamStatus.isProcessing && (
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium bg-blue-50 text-blue-700 border border-blue-200 dark:bg-blue-900/20 dark:text-blue-400 dark:border-blue-900/50">
|
||||
<Database className="h-4 w-4 animate-pulse" />
|
||||
<span className="hidden sm:inline">入库中</span>
|
||||
<span className="font-bold">{teamStatus.processingTeams}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{teamStatus.isRegistering && (
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium bg-purple-50 text-purple-700 border border-purple-200 dark:bg-purple-900/20 dark:text-purple-400 dark:border-purple-900/50">
|
||||
<UserPlus className="h-4 w-4 animate-pulse" />
|
||||
<span className="hidden sm:inline">注册中</span>
|
||||
<span className="font-bold">{teamStatus.registeringTeams}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connection status */}
|
||||
<div
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${isConnected
|
||||
|
||||
90
frontend/src/hooks/useTeamStatus.ts
Normal file
90
frontend/src/hooks/useTeamStatus.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
interface TeamProcessStatus {
|
||||
running: boolean
|
||||
total_teams: number
|
||||
completed: number
|
||||
}
|
||||
|
||||
interface TeamRegStatus {
|
||||
running: boolean
|
||||
config?: {
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
interface TeamStatus {
|
||||
// 入库状态
|
||||
processingTeams: number // 正在入库的team数量
|
||||
processedTeams: number // 已完成入库的team数量
|
||||
isProcessing: boolean // 是否正在入库
|
||||
// 注册状态
|
||||
registeringTeams: number // 正在注册的team数量
|
||||
isRegistering: boolean // 是否正在注册
|
||||
}
|
||||
|
||||
export function useTeamStatus(pollInterval = 3000) {
|
||||
const [status, setStatus] = useState<TeamStatus>({
|
||||
processingTeams: 0,
|
||||
processedTeams: 0,
|
||||
isProcessing: false,
|
||||
registeringTeams: 0,
|
||||
isRegistering: false,
|
||||
})
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
// 并行获取两个状态
|
||||
const [processRes, regRes] = await Promise.all([
|
||||
fetch('/api/team/status').catch(() => null),
|
||||
fetch('/api/team-reg/status').catch(() => null),
|
||||
])
|
||||
|
||||
let newStatus: TeamStatus = {
|
||||
processingTeams: 0,
|
||||
processedTeams: 0,
|
||||
isProcessing: false,
|
||||
registeringTeams: 0,
|
||||
isRegistering: false,
|
||||
}
|
||||
|
||||
// 解析入库状态
|
||||
if (processRes?.ok) {
|
||||
const data = await processRes.json()
|
||||
if (data.code === 0 && data.data) {
|
||||
const processData = data.data as TeamProcessStatus
|
||||
newStatus.isProcessing = processData.running
|
||||
newStatus.processingTeams = processData.running
|
||||
? processData.total_teams - processData.completed
|
||||
: 0
|
||||
newStatus.processedTeams = processData.completed
|
||||
}
|
||||
}
|
||||
|
||||
// 解析注册状态
|
||||
if (regRes?.ok) {
|
||||
const regData = await regRes.json() as TeamRegStatus
|
||||
newStatus.isRegistering = regData.running
|
||||
newStatus.registeringTeams = regData.running && regData.config
|
||||
? regData.config.count
|
||||
: 0
|
||||
}
|
||||
|
||||
setStatus(newStatus)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch team status:', error)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// 立即获取一次
|
||||
fetchStatus()
|
||||
|
||||
// 定时轮询
|
||||
const interval = setInterval(fetchStatus, pollInterval)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [fetchStatus, pollInterval])
|
||||
|
||||
return status
|
||||
}
|
||||
Reference in New Issue
Block a user