建立檔案

This commit is contained in:
2025-08-05 08:22:44 +08:00
commit 042d03aff7
122 changed files with 34763 additions and 0 deletions

7
app/admin/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
"use client"
import { AdminPanel } from "@/components/admin/admin-panel"
export default function AdminPage() {
return <AdminPanel />
}

View File

@@ -0,0 +1,195 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { CheckCircle, Edit, Loader2 } from "lucide-react"
export default function ScoringFormTestPage() {
const [showScoringForm, setShowScoringForm] = useState(false)
const [manualScoring, setManualScoring] = useState({
judgeId: "judge1",
participantId: "app1",
scores: {
"創新性": 0,
"技術性": 0,
"實用性": 0,
"展示效果": 0,
"影響力": 0
},
comments: ""
})
const [isLoading, setIsLoading] = useState(false)
const scoringRules = [
{ name: "創新性", description: "技術創新程度和獨特性", weight: 25 },
{ name: "技術性", description: "技術實現的複雜度和穩定性", weight: 20 },
{ name: "實用性", description: "實際應用價值和用戶體驗", weight: 25 },
{ name: "展示效果", description: "演示效果和表達能力", weight: 15 },
{ name: "影響力", description: "對行業和社會的潛在影響", weight: 15 }
]
const calculateTotalScore = (scores: Record<string, number>): number => {
let totalScore = 0
let totalWeight = 0
scoringRules.forEach(rule => {
const score = scores[rule.name] || 0
const weight = rule.weight || 1
totalScore += score * weight
totalWeight += weight
})
return totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0
}
const handleSubmitScore = async () => {
setIsLoading(true)
// 模擬提交
setTimeout(() => {
setIsLoading(false)
setShowScoringForm(false)
}, 2000)
}
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<h1 className="text-3xl font-bold"></h1>
<p className="text-gray-600"></p>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Button onClick={() => setShowScoringForm(true)} size="lg">
<Edit className="w-5 h-5 mr-2" />
</Button>
</CardContent>
</Card>
<Dialog open={showScoringForm} onOpenChange={setShowScoringForm}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Edit className="w-5 h-5" />
<span></span>
</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* 評分項目 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold"></h3>
{scoringRules.map((rule, index) => (
<div key={index} className="space-y-4 p-6 border rounded-lg bg-white shadow-sm">
<div className="flex justify-between items-start">
<div className="flex-1">
<Label className="text-lg font-semibold text-gray-900">{rule.name}</Label>
<p className="text-sm text-gray-600 mt-2 leading-relaxed">{rule.description}</p>
<p className="text-xs text-purple-600 mt-2 font-medium">{rule.weight}%</p>
</div>
<div className="text-right ml-4">
<span className="text-2xl font-bold text-blue-600">
{manualScoring.scores[rule.name] || 0} / 10
</span>
</div>
</div>
{/* 評分按鈕 */}
<div className="flex flex-wrap gap-3">
{Array.from({ length: 10 }, (_, i) => i + 1).map((score) => (
<button
key={score}
type="button"
onClick={() => setManualScoring({
...manualScoring,
scores: { ...manualScoring.scores, [rule.name]: score }
})}
className={`w-12 h-12 rounded-lg border-2 font-semibold text-lg transition-all duration-200 ${
(manualScoring.scores[rule.name] || 0) === score
? 'bg-blue-600 text-white border-blue-600 shadow-lg scale-105'
: 'bg-white text-gray-700 border-gray-300 hover:border-blue-400 hover:bg-blue-50 hover:scale-105'
}`}
>
{score}
</button>
))}
</div>
</div>
))}
</div>
{/* 總分顯示 */}
<div className="p-6 bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg border-2 border-blue-200">
<div className="flex justify-between items-center">
<div>
<span className="text-xl font-bold text-gray-900"></span>
<p className="text-sm text-gray-600 mt-1"></p>
</div>
<div className="flex items-center space-x-3">
<span className="text-4xl font-bold text-blue-600">
{calculateTotalScore(manualScoring.scores)}
</span>
<span className="text-xl text-gray-500 font-medium">/ 10</span>
</div>
</div>
</div>
{/* 評審意見 */}
<div className="space-y-3">
<Label className="text-lg font-semibold"> *</Label>
<Textarea
placeholder="請詳細填寫評審意見、優點分析、改進建議等..."
value={manualScoring.comments}
onChange={(e) => setManualScoring({ ...manualScoring, comments: e.target.value })}
rows={6}
className="min-h-[120px] resize-none"
/>
<p className="text-xs text-gray-500"></p>
</div>
</div>
<div className="flex justify-end space-x-4 pt-6 border-t border-gray-200">
<Button
variant="outline"
size="lg"
onClick={() => setShowScoringForm(false)}
className="px-8"
>
</Button>
<Button
onClick={handleSubmitScore}
disabled={isLoading}
size="lg"
className="px-8 bg-blue-600 hover:bg-blue-700"
>
{isLoading ? (
<>
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
...
</>
) : (
<>
<CheckCircle className="w-5 h-5 mr-2" />
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -0,0 +1,13 @@
import { ScoringManagement } from "@/components/admin/scoring-management"
export default function ScoringTestPage() {
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<h1 className="text-3xl font-bold"></h1>
<p className="text-gray-600"></p>
</div>
<ScoringManagement />
</div>
)
}

View File

@@ -0,0 +1,13 @@
import { ScoringManagement } from "@/components/admin/scoring-management"
export default function ScoringPage() {
return (
<div className="container mx-auto py-6">
<div className="mb-6">
<h1 className="text-3xl font-bold"></h1>
<p className="text-gray-600"></p>
</div>
<ScoringManagement />
</div>
)
}

572
app/competition/page.tsx Normal file
View File

@@ -0,0 +1,572 @@
"use client"
import { useState } from "react"
import { useAuth } from "@/contexts/auth-context"
import { useCompetition } from "@/contexts/competition-context"
import { Trophy, Award, Medal, Target, Users, Lightbulb, ArrowLeft, Plus, Search, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Input } from "@/components/ui/input"
import { PopularityRankings } from "@/components/competition/popularity-rankings"
import { CompetitionDetailDialog } from "@/components/competition/competition-detail-dialog"
import { AwardDetailDialog } from "@/components/competition/award-detail-dialog"
export default function CompetitionPage() {
const { user, canAccessAdmin } = useAuth()
const { competitions, awards, getAwardsByYear, getCompetitionRankings } = useCompetition()
const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all")
const [selectedMonthFilter, setSelectedMonthFilter] = useState("all")
const [selectedAwardCategory, setSelectedAwardCategory] = useState("all")
const [selectedYear, setSelectedYear] = useState(2024)
const [searchQuery, setSearchQuery] = useState("")
const [showCompetitionDetail, setShowCompetitionDetail] = useState(false)
const [selectedRanking, setSelectedRanking] = useState<any>(null)
const [selectedCompetitionType, setSelectedCompetitionType] = useState<"individual" | "team" | "proposal">(
"individual",
)
const [showAwardDetail, setShowAwardDetail] = useState(false)
const [selectedAward, setSelectedAward] = useState<any>(null)
const getCompetitionTypeIcon = (type: string) => {
switch (type) {
case "individual":
return <Target className="w-4 h-4" />
case "team":
return <Users className="w-4 h-4" />
case "proposal":
return <Lightbulb className="w-4 h-4" />
case "mixed":
return <Trophy className="w-4 h-4" />
default:
return <Trophy className="w-4 h-4" />
}
}
const getCompetitionTypeText = (type: string) => {
switch (type) {
case "individual":
return "個人賽"
case "team":
return "團隊賽"
case "proposal":
return "提案賽"
case "mixed":
return "混合賽"
default:
return "競賽"
}
}
const getCompetitionTypeColor = (type: string) => {
switch (type) {
case "individual":
return "bg-blue-100 text-blue-800 border-blue-200"
case "team":
return "bg-green-100 text-green-800 border-green-200"
case "proposal":
return "bg-purple-100 text-purple-800 border-purple-200"
case "mixed":
return "bg-gradient-to-r from-blue-100 via-green-100 to-purple-100 text-gray-800 border-gray-200"
default:
return "bg-gray-100 text-gray-800 border-gray-200"
}
}
const handleShowCompetitionDetail = (ranking: any, type: "individual" | "team" | "proposal") => {
setSelectedRanking(ranking)
setSelectedCompetitionType(type)
setShowCompetitionDetail(true)
}
const handleShowAwardDetail = (award: any) => {
setSelectedAward(award)
setShowAwardDetail(true)
}
const getFilteredAwards = () => {
let filteredAwards = getAwardsByYear(selectedYear)
// 搜索功能 - 按应用名称、创作者或奖项名称搜索
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase().trim()
filteredAwards = filteredAwards.filter((award) => {
return (
award.appName?.toLowerCase().includes(query) ||
award.creator?.toLowerCase().includes(query) ||
award.awardName?.toLowerCase().includes(query)
)
})
}
if (selectedCompetitionTypeFilter !== "all") {
filteredAwards = filteredAwards.filter((award) => award.competitionType === selectedCompetitionTypeFilter)
}
if (selectedMonthFilter !== "all") {
filteredAwards = filteredAwards.filter((award) => award.month === Number.parseInt(selectedMonthFilter))
}
if (selectedAwardCategory !== "all") {
if (selectedAwardCategory === "ranking") {
filteredAwards = filteredAwards.filter((award) => award.rank > 0 && award.rank <= 3)
} else if (selectedAwardCategory === "popular") {
filteredAwards = filteredAwards.filter((award) => award.awardType === "popular")
} else {
filteredAwards = filteredAwards.filter((award) => award.category === selectedAwardCategory)
}
}
return filteredAwards.sort((a, b) => {
// Sort by month first, then by rank
if (a.month !== b.month) return b.month - a.month
if (a.rank !== b.rank) {
if (a.rank === 0) return 1
if (b.rank === 0) return -1
return a.rank - b.rank
}
return 0
})
}
const filteredAwards = getFilteredAwards()
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50">
{/* Header */}
<header className="bg-white/80 backdrop-blur-sm border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
<Button
variant="ghost"
size="sm"
onClick={() => window.history.back()}
className="text-gray-700 hover:text-blue-600 hover:bg-blue-50"
>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
<div className="flex items-center space-x-2">
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
<Trophy className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="text-xl font-bold text-gray-900"></h1>
<p className="text-xs text-gray-500">COMPETITION CENTER</p>
</div>
</div>
</div>
<div className="flex items-center space-x-4">
</div>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Hero Section */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4">AI </h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-8">
AI 耀
</p>
</div>
<Tabs defaultValue="rankings" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-8">
<TabsTrigger value="rankings" className="flex items-center space-x-2">
<Trophy className="w-4 h-4" />
<span></span>
</TabsTrigger>
<TabsTrigger value="awards" className="flex items-center space-x-2">
<Award className="w-4 h-4" />
<span></span>
</TabsTrigger>
</TabsList>
<TabsContent value="rankings">
<PopularityRankings />
</TabsContent>
<TabsContent value="awards">
<div className="space-y-8">
{/* Enhanced Filter Section */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center space-x-2">
<Medal className="w-5 h-5 text-purple-500" />
<span></span>
</CardTitle>
<div className="flex items-center space-x-3">
<Select
value={selectedYear.toString()}
onValueChange={(value) => setSelectedYear(Number.parseInt(value))}
>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2024">2024</SelectItem>
<SelectItem value="2023">2023</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-gray-600"> {selectedYear} </p>
{searchQuery && (
<div className="text-sm text-blue-600 bg-blue-50 px-3 py-1 rounded-full">
{searchQuery}
</div>
)}
</div>
{/* Search and Filter Controls */}
<div className="space-y-4">
{/* Search Bar */}
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-4 w-4 text-gray-400" />
</div>
<Input
type="text"
placeholder="搜尋應用名稱、創作者或獎項名稱..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 pr-10 w-full md:w-96"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600"
>
<X className="h-4 w-4" />
</button>
)}
</div>
{/* Filter Controls */}
<div className="flex flex-wrap gap-4 items-center">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedCompetitionTypeFilter} onValueChange={setSelectedCompetitionTypeFilter}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="individual"></SelectItem>
<SelectItem value="team"></SelectItem>
<SelectItem value="proposal"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedMonthFilter} onValueChange={setSelectedMonthFilter}>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="1">1</SelectItem>
<SelectItem value="2">2</SelectItem>
<SelectItem value="3">3</SelectItem>
<SelectItem value="4">4</SelectItem>
<SelectItem value="5">5</SelectItem>
<SelectItem value="6">6</SelectItem>
<SelectItem value="7">7</SelectItem>
<SelectItem value="8">8</SelectItem>
<SelectItem value="9">9</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="11">11</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedAwardCategory} onValueChange={setSelectedAwardCategory}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="ranking"></SelectItem>
<SelectItem value="popular"></SelectItem>
<SelectItem value="innovation"></SelectItem>
<SelectItem value="technical"></SelectItem>
<SelectItem value="practical"></SelectItem>
</SelectContent>
</Select>
</div>
{/* Clear Filters Button */}
{(searchQuery || selectedCompetitionTypeFilter !== "all" || selectedMonthFilter !== "all" || selectedAwardCategory !== "all") && (
<div className="flex items-center">
<Button
variant="outline"
size="sm"
onClick={() => {
setSearchQuery("")
setSelectedCompetitionTypeFilter("all")
setSelectedMonthFilter("all")
setSelectedAwardCategory("all")
}}
className="text-gray-600 hover:text-gray-800"
>
<X className="w-4 h-4 mr-1" />
</Button>
</div>
)}
</div>
</div>
{/* Statistics */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="text-lg font-bold text-blue-600">{filteredAwards.length}</div>
<div className="text-xs text-blue-600"></div>
</div>
<div className="text-center p-3 bg-yellow-50 rounded-lg">
<div className="text-lg font-bold text-yellow-600">
{filteredAwards.filter((a) => a.rank > 0 && a.rank <= 3).length}
</div>
<div className="text-xs text-yellow-600"></div>
</div>
<div className="text-center p-3 bg-red-50 rounded-lg">
<div className="text-lg font-bold text-red-600">
{filteredAwards.filter((a) => a.awardType === "popular").length}
</div>
<div className="text-xs text-red-600"></div>
</div>
<div className="text-center p-3 bg-green-50 rounded-lg">
<div className="text-lg font-bold text-green-600">
{new Set(filteredAwards.map((a) => `${a.year}-${a.month}`)).size}
</div>
<div className="text-xs text-green-600"></div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Awards Grid with Enhanced Display */}
{filteredAwards.length > 0 ? (
<div className="space-y-8">
{/* Group awards by month */}
{Array.from(new Set(filteredAwards.map((award) => award.month)))
.sort((a, b) => b - a)
.map((month) => {
const monthAwards = filteredAwards.filter((award) => award.month === month)
const competition = competitions.find((c) => c.month === month && c.year === selectedYear)
return (
<div key={month} className="space-y-4">
<div className="flex items-center space-x-4">
<h3 className="text-xl font-bold text-gray-900">
{selectedYear}{month}
</h3>
{competition && (
<Badge variant="outline" className={getCompetitionTypeColor(competition.type)}>
{getCompetitionTypeIcon(competition.type)}
<span className="ml-1">{getCompetitionTypeText(competition.type)}</span>
</Badge>
)}
<Badge variant="secondary" className="bg-gray-100 text-gray-700">
{monthAwards.length}
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{monthAwards.map((award) => (
<Card
key={award.id}
className="relative overflow-hidden border-0 shadow-lg bg-gradient-to-br from-white to-gray-50 hover:shadow-xl transition-shadow cursor-pointer"
onClick={() => handleShowAwardDetail(award)}
>
{/* Rank Badge */}
{award.rank > 0 && award.rank <= 3 && (
<div className="absolute top-2 left-2 z-10">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold text-white ${
award.rank === 1
? "bg-yellow-500"
: award.rank === 2
? "bg-gray-400"
: award.rank === 3
? "bg-orange-600"
: ""
}`}
>
{award.rank}
</div>
</div>
)}
<div className="absolute top-4 right-4 text-3xl">{award.icon}</div>
<CardHeader className="pb-3 pt-12">
<div className="space-y-2">
<div className="flex flex-wrap gap-2">
<Badge
variant="secondary"
className={`w-fit ${
award.awardType === "popular"
? "bg-red-100 text-red-800 border-red-200"
: award.rank === 1
? "bg-yellow-100 text-yellow-800 border-yellow-200"
: award.rank === 2
? "bg-gray-100 text-gray-800 border-gray-200"
: award.rank === 3
? "bg-orange-100 text-orange-800 border-orange-200"
: "bg-blue-100 text-blue-800 border-blue-200"
}`}
>
{award.awardName}
</Badge>
<Badge
variant="outline"
className={getCompetitionTypeColor(award.competitionType)}
>
{getCompetitionTypeIcon(award.competitionType)}
<span className="ml-1">{getCompetitionTypeText(award.competitionType)}</span>
</Badge>
</div>
<CardTitle className="text-lg line-clamp-2">
{award.appName || award.proposalTitle || award.teamName}
</CardTitle>
<p className="text-sm text-gray-500">by {award.creator}</p>
<div className="text-xs text-gray-400">
{award.year}{award.month}
</div>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">
{award.competitionType === "proposal"
? "評審評分"
: award.awardType === "popular"
? award.competitionType === "team"
? "人氣指數"
: "收藏數"
: "評審評分"}
</span>
<span className="font-bold text-lg text-gray-900">
{award.awardType === "popular" && award.competitionType === "team"
? `${award.score}`
: award.awardType === "popular"
? `${award.score}`
: award.score}
</span>
</div>
<Button
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
onClick={(e) => {
e.stopPropagation()
handleShowAwardDetail(award)
}}
>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)
})}
</div>
) : (
<Card>
<CardContent className="text-center py-12">
<div className="space-y-4">
{searchQuery ? (
<Search className="w-16 h-16 text-gray-400 mx-auto" />
) : (
<Medal className="w-16 h-16 text-gray-400 mx-auto" />
)}
<div>
<h3 className="text-xl font-semibold text-gray-600 mb-2">
{searchQuery ? (
<>{searchQuery}</>
) : (
<>
{selectedYear}{selectedMonthFilter !== "all" ? `${selectedMonthFilter}` : ""}
</>
)}
</h3>
<p className="text-gray-500">
{searchQuery
? "嘗試使用其他關鍵字或調整篩選條件"
: "請調整篩選條件查看其他得獎作品"}
</p>
</div>
<div className="flex justify-center gap-2">
<Button
variant="outline"
className="bg-transparent"
onClick={() => {
setSearchQuery("")
setSelectedCompetitionTypeFilter("all")
setSelectedMonthFilter("all")
setSelectedAwardCategory("all")
}}
>
<X className="w-4 h-4 mr-1" />
</Button>
{searchQuery && (
<Button
variant="outline"
className="bg-transparent"
onClick={() => setSearchQuery("")}
>
</Button>
)}
</div>
</div>
</CardContent>
</Card>
)}
</div>
</TabsContent>
</Tabs>
{/* Competition Detail Dialog */}
{selectedRanking && (
<CompetitionDetailDialog
open={showCompetitionDetail}
onOpenChange={setShowCompetitionDetail}
ranking={selectedRanking}
competitionType={selectedCompetitionType}
/>
)}
{/* Award Detail Dialog */}
{selectedAward && (
<AwardDetailDialog open={showAwardDetail} onOpenChange={setShowAwardDetail} award={selectedAward} />
)}
</div>
</div>
)
}

100
app/globals.css Normal file
View File

@@ -0,0 +1,100 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 隱藏滾動條 */
.scrollbar-hide {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

384
app/judge-scoring/page.tsx Normal file
View File

@@ -0,0 +1,384 @@
"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 { Alert, AlertDescription } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Textarea } from "@/components/ui/textarea"
import { AlertTriangle, CheckCircle, User, Trophy, LogIn, Loader2 } from "lucide-react"
interface Judge {
id: string
name: string
specialty: string
}
interface ScoringItem {
id: string
name: string
type: "individual" | "team"
status: "pending" | "completed"
score?: number
submittedAt?: string
}
export default function JudgeScoringPage() {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [judgeId, setJudgeId] = useState("")
const [accessCode, setAccessCode] = useState("")
const [currentJudge, setCurrentJudge] = useState<Judge | null>(null)
const [scoringItems, setScoringItems] = useState<ScoringItem[]>([])
const [selectedItem, setSelectedItem] = useState<ScoringItem | null>(null)
const [showScoringDialog, setShowScoringDialog] = useState(false)
const [scores, setScores] = useState<Record<string, number>>({})
const [comments, setComments] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
// Judge data - empty for production
const mockJudges: Judge[] = []
// Scoring items - empty for production
const mockScoringItems: ScoringItem[] = []
const handleLogin = () => {
setError("")
if (!judgeId.trim() || !accessCode.trim()) {
setError("請填寫評審ID和存取碼")
return
}
if (accessCode !== "judge2024") {
setError("存取碼錯誤")
return
}
const judge = mockJudges.find(j => j.id === judgeId)
if (!judge) {
setError("評審ID不存在")
return
}
setCurrentJudge(judge)
setScoringItems(mockScoringItems)
setIsLoggedIn(true)
setSuccess("登入成功!")
setTimeout(() => setSuccess(""), 3000)
}
const handleStartScoring = (item: ScoringItem) => {
setSelectedItem(item)
setScores({})
setComments("")
setShowScoringDialog(true)
}
const handleSubmitScore = async () => {
if (!selectedItem) return
setIsSubmitting(true)
// 模擬提交評分
setTimeout(() => {
setScoringItems(prev => prev.map(item =>
item.id === selectedItem.id
? { ...item, status: "completed", score: Object.values(scores).reduce((a, b) => a + b, 0) / Object.values(scores).length, submittedAt: new Date().toISOString() }
: item
))
setShowScoringDialog(false)
setSelectedItem(null)
setScores({})
setComments("")
setIsSubmitting(false)
setSuccess("評分提交成功!")
setTimeout(() => setSuccess(""), 3000)
}, 1000)
}
const getProgress = () => {
const total = scoringItems.length
const completed = scoringItems.filter(item => item.status === "completed").length
return { total, completed, percentage: total > 0 ? Math.round((completed / total) * 100) : 0 }
}
const progress = getProgress()
if (!isLoggedIn) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
<Trophy className="w-8 h-8 text-blue-600" />
</div>
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>
ID和存取碼進行登入
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="judgeId">ID</Label>
<Input
id="judgeId"
placeholder="例如j1"
value={judgeId}
onChange={(e) => setJudgeId(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="accessCode"></Label>
<Input
id="accessCode"
type="password"
placeholder="請輸入存取碼"
value={accessCode}
onChange={(e) => setAccessCode(e.target.value)}
/>
</div>
<Button
onClick={handleLogin}
className="w-full"
size="lg"
>
<LogIn className="w-4 h-4 mr-2" />
</Button>
<div className="text-center text-sm text-gray-500">
<p>ID範例j1, j2, j3, j4, j5</p>
<p>judge2024</p>
</div>
</CardContent>
</Card>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-6xl mx-auto space-y-6">
{/* 成功訊息 */}
{success && (
<Alert className="border-green-200 bg-green-50">
<CheckCircle className="h-4 w-4 text-green-600" />
<AlertDescription className="text-green-800">{success}</AlertDescription>
</Alert>
)}
{/* 評審資訊 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<Avatar className="w-12 h-12">
<AvatarFallback className="text-lg font-semibold">
{currentJudge?.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<h1 className="text-2xl font-bold">{currentJudge?.name}</h1>
<p className="text-gray-600">{currentJudge?.specialty}</p>
</div>
</div>
<Button
variant="outline"
onClick={() => setIsLoggedIn(false)}
>
</Button>
</div>
</CardHeader>
</Card>
{/* 評分進度 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between text-sm">
<span></span>
<span>{progress.completed} / {progress.total}</span>
</div>
<Progress value={progress.percentage} className="h-2" />
<div className="text-center">
<span className="text-2xl font-bold text-blue-600">{progress.percentage}%</span>
</div>
</div>
</CardContent>
</Card>
{/* 評分項目列表 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{scoringItems.map((item) => (
<div
key={item.id}
className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 transition-colors"
>
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
{item.type === "individual" ? (
<User className="w-4 h-4 text-blue-600" />
) : (
<div className="flex space-x-1">
<User className="w-4 h-4 text-green-600" />
<User className="w-4 h-4 text-green-600" />
</div>
)}
<span className="font-medium">{item.name}</span>
<Badge variant="outline">
{item.type === "individual" ? "個人" : "團隊"}
</Badge>
</div>
</div>
<div className="flex items-center space-x-4">
{item.status === "completed" ? (
<div className="text-center">
<div className="text-lg font-bold text-green-600">{item.score}</div>
<div className="text-xs text-gray-500">/ 10</div>
<div className="text-xs text-gray-500">{item.submittedAt}</div>
</div>
) : (
<Button
onClick={() => handleStartScoring(item)}
variant="outline"
size="sm"
>
</Button>
)}
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* 評分對話框 */}
<Dialog open={showScoringDialog} onOpenChange={setShowScoringDialog}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{selectedItem?.name}</DialogTitle>
<DialogDescription>
滿10
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* 評分項目 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold"></h3>
{[
{ name: "創新性", description: "創新程度和獨特性" },
{ name: "技術性", description: "技術實現的複雜度和品質" },
{ name: "實用性", description: "實際應用價值和用戶體驗" },
{ name: "展示效果", description: "展示的清晰度和吸引力" },
{ name: "影響力", description: "對行業或社會的潛在影響" }
].map((criterion, index) => (
<div key={index} className="space-y-2">
<Label>{criterion.name}</Label>
<p className="text-sm text-gray-600">{criterion.description}</p>
<div className="flex space-x-2">
{Array.from({ length: 10 }, (_, i) => i + 1).map((score) => (
<button
key={score}
type="button"
onClick={() => setScores(prev => ({ ...prev, [criterion.name]: score }))}
className={`w-10 h-10 rounded border-2 font-semibold transition-all ${
scores[criterion.name] === score
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-700 border-gray-300 hover:border-blue-400'
}`}
>
{score}
</button>
))}
</div>
</div>
))}
</div>
{/* 評審意見 */}
<div className="space-y-2">
<Label></Label>
<Textarea
placeholder="請詳細填寫評審意見、優點分析、改進建議等..."
value={comments}
onChange={(e) => setComments(e.target.value)}
rows={4}
/>
</div>
{/* 總分顯示 */}
<div className="p-4 bg-blue-50 rounded-lg">
<div className="flex justify-between items-center">
<span className="font-semibold"></span>
<span className="text-2xl font-bold text-blue-600">
{Object.values(scores).length > 0
? Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / Object.values(scores).length)
: 0
} / 10
</span>
</div>
</div>
</div>
<div className="flex justify-end space-x-4 pt-6 border-t">
<Button
variant="outline"
onClick={() => setShowScoringDialog(false)}
>
</Button>
<Button
onClick={handleSubmitScore}
disabled={isSubmitting || Object.keys(scores).length < 5 || !comments.trim()}
>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
"提交評分"
)}
</Button>
</div>
</DialogContent>
</Dialog>
</div>
)
}

33
app/layout.tsx Normal file
View File

@@ -0,0 +1,33 @@
import type React from "react"
import { Inter } from "next/font/google"
import "./globals.css"
import { AuthProvider } from "@/contexts/auth-context"
import { CompetitionProvider } from "@/contexts/competition-context"
import { Toaster } from "@/components/ui/toaster"
import { ChatBot } from "@/components/chat-bot"
const inter = Inter({ subsets: ["latin"] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-TW">
<body className={inter.className}>
<AuthProvider>
<CompetitionProvider>
{children}
<Toaster />
<ChatBot />
</CompetitionProvider>
</AuthProvider>
</body>
</html>
)
}
export const metadata = {
generator: 'v0.dev'
};

7
app/loading.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function Loading() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-600"></div>
</div>
)
}

1017
app/page.tsx Normal file

File diff suppressed because it is too large Load Diff

3
app/register/loading.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

408
app/register/page.tsx Normal file
View File

@@ -0,0 +1,408 @@
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { useAuth } from "@/contexts/auth-context"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Badge } from "@/components/ui/badge"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Separator } from "@/components/ui/separator"
import { Brain, User, Mail, Building, Lock, Loader2, CheckCircle, AlertTriangle, Shield, Code } from "lucide-react"
export default function RegisterPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { register, isLoading } = useAuth()
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
confirmPassword: "",
department: "",
})
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
// 從 URL 參數獲取邀請資訊
const invitationToken = searchParams.get("token")
const invitedEmail = searchParams.get("email")
const invitedRole = searchParams.get("role") || "user"
const isInvitedUser = !!(invitationToken && invitedEmail)
useEffect(() => {
if (isInvitedUser) {
setFormData((prev) => ({
...prev,
email: decodeURIComponent(invitedEmail),
}))
}
}, [isInvitedUser, invitedEmail])
const handleInputChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
setError("")
}
const getRoleText = (role: string) => {
switch (role) {
case "admin":
return "管理員"
case "developer":
return "開發者"
case "user":
return "一般用戶"
default:
return role
}
}
const getRoleIcon = (role: string) => {
switch (role) {
case "admin":
return <Shield className="w-4 h-4 text-purple-600" />
case "developer":
return <Code className="w-4 h-4 text-green-600" />
case "user":
return <User className="w-4 h-4 text-blue-600" />
default:
return <User className="w-4 h-4 text-blue-600" />
}
}
const getRoleColor = (role: string) => {
switch (role) {
case "admin":
return "bg-purple-100 text-purple-800 border-purple-200"
case "developer":
return "bg-green-100 text-green-800 border-green-200"
case "user":
return "bg-blue-100 text-blue-800 border-blue-200"
default:
return "bg-gray-100 text-gray-800 border-gray-200"
}
}
const getRoleDescription = (role: string) => {
switch (role) {
case "admin":
return "可以訪問管理後台,管理用戶和審核應用"
case "developer":
return "可以提交 AI 應用申請,參與平台建設"
case "user":
return "可以瀏覽和收藏應用,參與評價互動"
default:
return ""
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setIsSubmitting(true)
// 表單驗證
if (!formData.name || !formData.email || !formData.password || !formData.department) {
setError("請填寫所有必填欄位")
setIsSubmitting(false)
return
}
if (formData.password !== formData.confirmPassword) {
setError("密碼確認不一致")
setIsSubmitting(false)
return
}
if (formData.password.length < 6) {
setError("密碼長度至少需要 6 個字符")
setIsSubmitting(false)
return
}
try {
const success = await register({
name: formData.name,
email: formData.email,
password: formData.password,
department: formData.department,
})
if (success) {
setSuccess("註冊成功!正在跳轉...")
setTimeout(() => {
router.push("/")
}, 2000)
} else {
setError("註冊失敗,請檢查資料或聯繫管理員")
}
} catch (err) {
setError("註冊過程中發生錯誤,請稍後再試")
}
setIsSubmitting(false)
}
if (success) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardContent className="text-center py-8">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-600 mb-4"> AI </p>
<p className="text-sm text-gray-500">...</p>
</CardContent>
</Card>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center space-x-2 mb-4">
<div className="w-10 h-10 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
<Brain className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold text-gray-900"> AI </h1>
</div>
</div>
{isInvitedUser ? (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"></h2>
<p className="text-gray-600"></p>
</div>
) : (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"></h2>
<p className="text-gray-600"> AI </p>
</div>
)}
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
{isInvitedUser ? "請填寫您的個人資訊完成註冊" : "請填寫以下資訊建立您的帳戶"}
</CardDescription>
</CardHeader>
<CardContent>
{/* Invitation Info */}
{isInvitedUser && (
<div className="mb-6">
<div className="bg-blue-50 rounded-lg p-4">
<div className="flex items-start space-x-3">
<CheckCircle className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="flex-1">
<h4 className="font-medium text-blue-900 mb-2"></h4>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm text-blue-700"></span>
<span className="text-sm font-medium text-blue-900">{invitedEmail}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-blue-700"></span>
<Badge variant="outline" className={getRoleColor(invitedRole)}>
<div className="flex items-center space-x-1">
{getRoleIcon(invitedRole)}
<span>{getRoleText(invitedRole)}</span>
</div>
</Badge>
</div>
<div className="mt-3 pt-3 border-t border-blue-200">
<p className="text-xs text-blue-600">{getRoleDescription(invitedRole)}</p>
</div>
</div>
</div>
</div>
</div>
</div>
)}
{/* Error/Success Messages */}
{error && (
<Alert variant="destructive" className="mb-4">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name"> *</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="name"
type="text"
value={formData.name}
onChange={(e) => handleInputChange("name", e.target.value)}
placeholder="請輸入您的姓名"
className="pl-10"
disabled={isSubmitting}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email"> *</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="請輸入電子郵件"
className="pl-10"
disabled={isSubmitting || isInvitedUser}
readOnly={isInvitedUser}
/>
</div>
{isInvitedUser && <p className="text-xs text-gray-500"></p>}
</div>
<div className="space-y-2">
<Label htmlFor="department"> *</Label>
<div className="relative">
<Building className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4 z-10" />
<Select
value={formData.department}
onValueChange={(value) => handleInputChange("department", value)}
disabled={isSubmitting}
>
<SelectTrigger className="pl-10">
<SelectValue placeholder="請選擇您的部門" />
</SelectTrigger>
<SelectContent>
<SelectItem value="HQBU">HQBU</SelectItem>
<SelectItem value="ITBU">ITBU</SelectItem>
<SelectItem value="MBU1">MBU1</SelectItem>
<SelectItem value="SBU">SBU</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Separator />
<div className="space-y-2">
<Label htmlFor="password"> *</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => handleInputChange("password", e.target.value)}
placeholder="請輸入密碼(至少 6 個字符)"
className="pl-10"
disabled={isSubmitting}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword"> *</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={(e) => handleInputChange("confirmPassword", e.target.value)}
placeholder="請再次輸入密碼"
className="pl-10"
disabled={isSubmitting}
/>
</div>
</div>
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
disabled={isSubmitting || isLoading}
>
{isSubmitting || isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
"完成註冊"
)}
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{" "}
<Button
variant="link"
className="p-0 h-auto font-normal text-blue-600 hover:text-blue-700"
onClick={() => router.push("/")}
>
</Button>
</p>
</div>
</CardContent>
</Card>
{/* Role Information */}
{!isInvitedUser && (
<Card className="mt-6">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-3">
<div className="flex items-start space-x-3 p-3 bg-blue-50 rounded-lg">
<User className="w-5 h-5 text-blue-600 mt-0.5" />
<div>
<h4 className="font-medium text-blue-900"></h4>
<p className="text-sm text-blue-700"></p>
</div>
</div>
<div className="flex items-start space-x-3 p-3 bg-green-50 rounded-lg">
<Code className="w-5 h-5 text-green-600 mt-0.5" />
<div>
<h4 className="font-medium text-green-900"></h4>
<p className="text-sm text-green-700"> AI </p>
</div>
</div>
<div className="flex items-start space-x-3 p-3 bg-purple-50 rounded-lg">
<Shield className="w-5 h-5 text-purple-600 mt-0.5" />
<div>
<h4 className="font-medium text-purple-900"></h4>
<p className="text-sm text-purple-700"></p>
</div>
</div>
</div>
<div className="bg-gray-50 p-3 rounded-lg">
<p className="text-xs text-gray-600">
<strong></strong>調
</p>
</div>
</CardContent>
</Card>
)}
</div>
</div>
)
}