新增連結測驗邀請註冊功能
This commit is contained in:
@@ -1,435 +1,114 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
import { ProtectedRoute } from "@/components/protected-route"
|
import { ProtectedRoute } from "@/components/protected-route"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Copy,
|
Copy,
|
||||||
CheckCircle,
|
|
||||||
Clock,
|
|
||||||
Users,
|
|
||||||
Link as LinkIcon,
|
Link as LinkIcon,
|
||||||
Send,
|
|
||||||
RefreshCw,
|
|
||||||
Eye,
|
Eye,
|
||||||
Trash2,
|
|
||||||
Plus,
|
|
||||||
Loader2,
|
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
// 定義測驗連結類型
|
|
||||||
interface TestLink {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
type: 'logic' | 'creative' | 'combined'
|
|
||||||
url: string
|
|
||||||
createdAt: string
|
|
||||||
expiresAt?: string
|
|
||||||
isActive: boolean
|
|
||||||
totalSent: number
|
|
||||||
completedCount: number
|
|
||||||
pendingCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定義已送出連結的狀態
|
|
||||||
interface SentLinkStatus {
|
|
||||||
id: string
|
|
||||||
linkId: string
|
|
||||||
recipientName: string
|
|
||||||
recipientEmail: string
|
|
||||||
sentAt: string
|
|
||||||
status: 'pending' | 'completed' | 'expired'
|
|
||||||
completedAt?: string
|
|
||||||
testType: 'logic' | 'creative' | 'combined'
|
|
||||||
score?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TestLinksPage() {
|
export default function TestLinksPage() {
|
||||||
const [currentLinks, setCurrentLinks] = useState<TestLink[]>([])
|
const handleCopyLink = () => {
|
||||||
const [sentLinks, setSentLinks] = useState<SentLinkStatus[]>([])
|
const url = `${typeof window !== 'undefined' ? window.location.origin : ''}/test-link`
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [newLinkName, setNewLinkName] = useState("")
|
|
||||||
const [newLinkType, setNewLinkType] = useState<'logic' | 'creative' | 'combined'>('combined')
|
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false)
|
|
||||||
|
|
||||||
// 模擬數據
|
|
||||||
useEffect(() => {
|
|
||||||
const mockCurrentLinks: TestLink[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: '2024年度綜合能力測驗',
|
|
||||||
type: 'combined',
|
|
||||||
url: 'https://hr-assessment.com/test/combined/abc123',
|
|
||||||
createdAt: '2024-01-15T10:00:00Z',
|
|
||||||
expiresAt: '2024-12-31T23:59:59Z',
|
|
||||||
isActive: true,
|
|
||||||
totalSent: 45,
|
|
||||||
completedCount: 38,
|
|
||||||
pendingCount: 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: '創意能力專項測驗',
|
|
||||||
type: 'creative',
|
|
||||||
url: 'https://hr-assessment.com/test/creative/def456',
|
|
||||||
createdAt: '2024-01-20T14:30:00Z',
|
|
||||||
isActive: true,
|
|
||||||
totalSent: 23,
|
|
||||||
completedCount: 20,
|
|
||||||
pendingCount: 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockSentLinks: SentLinkStatus[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
linkId: '1',
|
|
||||||
recipientName: '張小明',
|
|
||||||
recipientEmail: 'zhang.xiaoming@company.com',
|
|
||||||
sentAt: '2024-01-15T10:30:00Z',
|
|
||||||
status: 'completed',
|
|
||||||
completedAt: '2024-01-16T09:15:00Z',
|
|
||||||
testType: 'combined',
|
|
||||||
score: 85
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
linkId: '1',
|
|
||||||
recipientName: '李美華',
|
|
||||||
recipientEmail: 'li.meihua@company.com',
|
|
||||||
sentAt: '2024-01-15T11:00:00Z',
|
|
||||||
status: 'completed',
|
|
||||||
completedAt: '2024-01-17T14:20:00Z',
|
|
||||||
testType: 'combined',
|
|
||||||
score: 92
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
linkId: '1',
|
|
||||||
recipientName: '王大偉',
|
|
||||||
recipientEmail: 'wang.dawei@company.com',
|
|
||||||
sentAt: '2024-01-15T11:30:00Z',
|
|
||||||
status: 'pending',
|
|
||||||
testType: 'combined'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
linkId: '2',
|
|
||||||
recipientName: '陳小芳',
|
|
||||||
recipientEmail: 'chen.xiaofang@company.com',
|
|
||||||
sentAt: '2024-01-20T15:00:00Z',
|
|
||||||
status: 'completed',
|
|
||||||
completedAt: '2024-01-21T10:45:00Z',
|
|
||||||
testType: 'creative',
|
|
||||||
score: 78
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setCurrentLinks(mockCurrentLinks)
|
|
||||||
setSentLinks(mockSentLinks)
|
|
||||||
setLoading(false)
|
|
||||||
}, 1000)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleCopyLink = (url: string) => {
|
|
||||||
navigator.clipboard.writeText(url)
|
navigator.clipboard.writeText(url)
|
||||||
// 這裡可以添加 toast 提示
|
alert('連結已複製到剪貼簿')
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateLink = () => {
|
|
||||||
if (!newLinkName.trim()) return
|
|
||||||
|
|
||||||
const newLink: TestLink = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
name: newLinkName,
|
|
||||||
type: newLinkType,
|
|
||||||
url: `https://hr-assessment.com/test/${newLinkType}/${Math.random().toString(36).substr(2, 9)}`,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
isActive: true,
|
|
||||||
totalSent: 0,
|
|
||||||
completedCount: 0,
|
|
||||||
pendingCount: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentLinks([...currentLinks, newLink])
|
|
||||||
setNewLinkName("")
|
|
||||||
setShowCreateForm(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusBadge = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'completed':
|
|
||||||
return <Badge variant="default" className="bg-green-500">已完成</Badge>
|
|
||||||
case 'pending':
|
|
||||||
return <Badge variant="secondary">進行中</Badge>
|
|
||||||
case 'expired':
|
|
||||||
return <Badge variant="destructive">已過期</Badge>
|
|
||||||
default:
|
|
||||||
return <Badge variant="outline">未知</Badge>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTypeName = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'logic':
|
|
||||||
return '邏輯思維'
|
|
||||||
case 'creative':
|
|
||||||
return '創意能力'
|
|
||||||
case 'combined':
|
|
||||||
return '綜合能力'
|
|
||||||
default:
|
|
||||||
return '未知'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background">
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
|
||||||
<div className="flex items-center justify-center h-64">
|
|
||||||
<Loader2 className="h-8 w-8 animate-spin" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<ProtectedRoute>
|
||||||
{/* Header */}
|
<div className="min-h-screen bg-background">
|
||||||
<header className="border-b bg-card/50 backdrop-blur-sm">
|
{/* Header */}
|
||||||
<div className="container mx-auto px-4 py-4">
|
<header className="border-b bg-card/50 backdrop-blur-sm">
|
||||||
<div className="flex items-center gap-3">
|
<div className="container mx-auto px-4 py-4">
|
||||||
<Button variant="ghost" size="sm" asChild>
|
<div className="flex items-center gap-3">
|
||||||
<Link href="/dashboard">
|
<Button variant="ghost" size="sm" asChild>
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<Link href="/dashboard">
|
||||||
返回儀表板
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
</Link>
|
返回儀表板
|
||||||
</Button>
|
</Link>
|
||||||
<div className="w-10 h-10 bg-primary rounded-lg flex items-center justify-center">
|
</Button>
|
||||||
<LinkIcon className="w-6 h-6 text-white" />
|
<div className="w-10 h-10 bg-primary rounded-lg flex items-center justify-center">
|
||||||
</div>
|
<LinkIcon className="w-6 h-6 text-white" />
|
||||||
<div>
|
</div>
|
||||||
<h1 className="text-xl font-bold text-foreground">測驗連結管理</h1>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<h1 className="text-xl font-bold text-foreground">測驗連結管理</h1>
|
||||||
管理測驗連結的發送和追蹤測試完成狀態
|
<p className="text-sm text-muted-foreground">
|
||||||
</p>
|
管理測驗連結的發送和追蹤測試完成狀態
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-8">
|
{/* Main Content */}
|
||||||
<div className="max-w-6xl mx-auto space-y-6">
|
<div className="container mx-auto px-4 py-8">
|
||||||
<Tabs defaultValue="current" className="w-full">
|
<div className="max-w-2xl mx-auto">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<Card>
|
||||||
<TabsTrigger value="current">目前測驗連結</TabsTrigger>
|
<CardHeader className="text-center">
|
||||||
<TabsTrigger value="sent">已送出連結狀態</TabsTrigger>
|
<CardTitle className="flex items-center justify-center gap-2">
|
||||||
</TabsList>
|
<LinkIcon className="w-6 h-6" />
|
||||||
|
測驗連結
|
||||||
{/* 目前測驗連結 */}
|
</CardTitle>
|
||||||
<TabsContent value="current" className="space-y-6">
|
<CardDescription>
|
||||||
<Card>
|
分享此連結給受測者,點擊後會自動收集用戶資料並開始測驗
|
||||||
<CardHeader>
|
</CardDescription>
|
||||||
<div className="flex items-center justify-between">
|
</CardHeader>
|
||||||
<div>
|
<CardContent className="space-y-6">
|
||||||
<CardTitle>目前測驗連結</CardTitle>
|
{/* 連結顯示區域 */}
|
||||||
<CardDescription>
|
<div className="space-y-3">
|
||||||
管理當前可用的測驗連結
|
<Label>測驗連結</Label>
|
||||||
</CardDescription>
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<Input
|
||||||
<Button onClick={() => setShowCreateForm(!showCreateForm)}>
|
value={`${typeof window !== 'undefined' ? window.location.origin : ''}/test-link`}
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
readOnly
|
||||||
新增連結
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const url = `${typeof window !== 'undefined' ? window.location.origin : ''}/test-link`
|
||||||
|
navigator.clipboard.writeText(url)
|
||||||
|
alert('連結已複製到剪貼簿')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
|
||||||
{/* 新增連結表單 */}
|
|
||||||
{showCreateForm && (
|
|
||||||
<Card className="mb-6">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">新增測驗連結</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="linkName">連結名稱</Label>
|
|
||||||
<Input
|
|
||||||
id="linkName"
|
|
||||||
value={newLinkName}
|
|
||||||
onChange={(e) => setNewLinkName(e.target.value)}
|
|
||||||
placeholder="請輸入連結名稱"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="linkType">測驗類型</Label>
|
|
||||||
<Select value={newLinkType} onValueChange={(value: any) => setNewLinkType(value)}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="logic">邏輯思維測驗</SelectItem>
|
|
||||||
<SelectItem value="creative">創意能力測驗</SelectItem>
|
|
||||||
<SelectItem value="combined">綜合能力測驗</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button onClick={handleCreateLink} disabled={!newLinkName.trim()}>
|
|
||||||
建立連結
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" onClick={() => setShowCreateForm(false)}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 連結列表 */}
|
{/* 功能說明 */}
|
||||||
<div className="space-y-4">
|
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
|
||||||
{currentLinks.map((link) => (
|
<h4 className="font-medium text-sm">功能說明:</h4>
|
||||||
<Card key={link.id}>
|
<ul className="text-sm text-muted-foreground space-y-1">
|
||||||
<CardContent className="p-6">
|
<li>• 受測者點擊連結後會要求填寫基本資料</li>
|
||||||
<div className="flex items-start justify-between">
|
<li>• 系統會自動創建用戶帳號(密碼:Aa123456)</li>
|
||||||
<div className="space-y-2">
|
<li>• 填寫完成後自動跳轉到測驗選擇頁面</li>
|
||||||
<div className="flex items-center gap-2">
|
<li>• 支援邏輯思維、創意能力、綜合測試三種類型</li>
|
||||||
<h3 className="text-lg font-semibold">{link.name}</h3>
|
</ul>
|
||||||
<Badge variant={link.isActive ? "default" : "secondary"}>
|
</div>
|
||||||
{link.isActive ? "啟用中" : "已停用"}
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline">{getTypeName(link.type)}</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
||||||
<span>建立時間: {new Date(link.createdAt).toLocaleString('zh-TW')}</span>
|
|
||||||
{link.expiresAt && (
|
|
||||||
<span>到期時間: {new Date(link.expiresAt).toLocaleString('zh-TW')}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-6 text-sm">
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<Users className="w-4 h-4" />
|
|
||||||
總發送: {link.totalSent}
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1 text-green-600">
|
|
||||||
<CheckCircle className="w-4 h-4" />
|
|
||||||
已完成: {link.completedCount}
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1 text-orange-600">
|
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
進行中: {link.pendingCount}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleCopyLink(link.url)}
|
|
||||||
>
|
|
||||||
<Copy className="w-4 h-4 mr-2" />
|
|
||||||
複製連結
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="sm" asChild>
|
|
||||||
<Link href={`/test/${link.type}`} target="_blank">
|
|
||||||
<Eye className="w-4 h-4 mr-2" />
|
|
||||||
預覽
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* 已送出連結狀態 */}
|
{/* 預覽按鈕 */}
|
||||||
<TabsContent value="sent" className="space-y-6">
|
<div className="flex justify-center">
|
||||||
<Card>
|
<Button
|
||||||
<CardHeader>
|
variant="outline"
|
||||||
<CardTitle>已送出連結狀態清單</CardTitle>
|
onClick={() => window.open('/test-link', '_blank')}
|
||||||
<CardDescription>
|
className="w-full sm:w-auto"
|
||||||
追蹤已送出測驗連結的完成狀態
|
>
|
||||||
</CardDescription>
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
</CardHeader>
|
預覽測驗頁面
|
||||||
<CardContent>
|
</Button>
|
||||||
<Table>
|
</div>
|
||||||
<TableHeader>
|
</CardContent>
|
||||||
<TableRow>
|
</Card>
|
||||||
<TableHead>收件人</TableHead>
|
</div>
|
||||||
<TableHead>測驗類型</TableHead>
|
|
||||||
<TableHead>發送時間</TableHead>
|
|
||||||
<TableHead>狀態</TableHead>
|
|
||||||
<TableHead>完成時間</TableHead>
|
|
||||||
<TableHead>分數</TableHead>
|
|
||||||
<TableHead>操作</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{sentLinks.map((sentLink) => (
|
|
||||||
<TableRow key={sentLink.id}>
|
|
||||||
<TableCell>
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">{sentLink.recipientName}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">{sentLink.recipientEmail}</div>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="outline">{getTypeName(sentLink.testType)}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{new Date(sentLink.sentAt).toLocaleString('zh-TW')}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{getStatusBadge(sentLink.status)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{sentLink.completedAt
|
|
||||||
? new Date(sentLink.completedAt).toLocaleString('zh-TW')
|
|
||||||
: '-'
|
|
||||||
}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{sentLink.score ? `${sentLink.score}分` : '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button variant="outline" size="sm" asChild>
|
|
||||||
<Link href={`/admin/results?user=${sentLink.recipientEmail}`}>
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
{sentLink.status === 'pending' && (
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Send className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ProtectedRoute>
|
||||||
)
|
)
|
||||||
}
|
}
|
220
app/test-link/page.tsx
Normal file
220
app/test-link/page.tsx
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||||
|
import { Loader2, User, Mail, Building, CheckCircle } from "lucide-react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
|
||||||
|
export default function TestLinkPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const [userInfo, setUserInfo] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
department: ''
|
||||||
|
})
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
const handleUserInfoChange = (field: string, value: string) => {
|
||||||
|
setUserInfo(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}))
|
||||||
|
setError('') // 清除錯誤訊息
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (!userInfo.name || !userInfo.email || !userInfo.department) {
|
||||||
|
setError('請填寫所有必填欄位')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 註冊用戶
|
||||||
|
const response = await fetch('/api/auth/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: userInfo.name,
|
||||||
|
email: userInfo.email,
|
||||||
|
department: userInfo.department,
|
||||||
|
password: 'Aa123456', // 預設密碼
|
||||||
|
role: 'user'
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// 註冊成功後自動登入
|
||||||
|
const loginResponse = await fetch('/api/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: userInfo.email,
|
||||||
|
password: 'Aa123456'
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (loginResponse.ok) {
|
||||||
|
const loginData = await loginResponse.json()
|
||||||
|
|
||||||
|
// 存儲 token 和用戶資料到 localStorage
|
||||||
|
if (loginData.accessToken && loginData.refreshToken) {
|
||||||
|
localStorage.setItem('accessToken', loginData.accessToken)
|
||||||
|
localStorage.setItem('refreshToken', loginData.refreshToken)
|
||||||
|
localStorage.setItem('hr_current_user', JSON.stringify(loginData.user))
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSuccess(true)
|
||||||
|
// 2秒後跳轉到個人專區(儀表板)
|
||||||
|
setTimeout(() => {
|
||||||
|
// 使用 window.location.href 確保頁面完全重新載入
|
||||||
|
window.location.href = '/home'
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
setError('註冊成功但自動登入失敗,請手動登入')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json()
|
||||||
|
setError(errorData.message || '註冊失敗,請稍後再試')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Registration error:', error)
|
||||||
|
setError('註冊失敗,請稍後再試')
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto">
|
||||||
|
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-green-600">註冊成功!</h2>
|
||||||
|
<p className="text-muted-foreground mt-2">
|
||||||
|
正在跳轉到個人專區...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<CardTitle className="text-2xl">測驗資料收集</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
請填寫以下資料以開始測驗
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name" className="flex items-center gap-2">
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
姓名
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
placeholder="請輸入您的姓名"
|
||||||
|
value={userInfo.name}
|
||||||
|
onChange={(e) => handleUserInfoChange('name', e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email" className="flex items-center gap-2">
|
||||||
|
<Mail className="w-4 h-4" />
|
||||||
|
電子郵件
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="請輸入電子郵件"
|
||||||
|
value={userInfo.email}
|
||||||
|
onChange={(e) => handleUserInfoChange('email', e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="department" className="flex items-center gap-2">
|
||||||
|
<Building className="w-4 h-4" />
|
||||||
|
部門
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={userInfo.department}
|
||||||
|
onValueChange={(value) => handleUserInfoChange('department', value)}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="請選擇部門" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="資訊技術部">資訊技術部</SelectItem>
|
||||||
|
<SelectItem value="人力資源部">人力資源部</SelectItem>
|
||||||
|
<SelectItem value="財務部">財務部</SelectItem>
|
||||||
|
<SelectItem value="行銷部">行銷部</SelectItem>
|
||||||
|
<SelectItem value="營運部">營運部</SelectItem>
|
||||||
|
<SelectItem value="其他">其他</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
處理中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'開始測驗'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-6 p-3 bg-muted/50 rounded-lg">
|
||||||
|
<p className="text-xs text-muted-foreground text-center">
|
||||||
|
系統將自動為您創建帳號,預設密碼為:<strong>Aa123456</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
Reference in New Issue
Block a user