Files
ai-showcase-platform/components/app-submission-dialog.tsx

669 lines
28 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import type React from "react"
import { useState } from "react"
import { useAuth } from "@/contexts/auth-context"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator"
import {
Upload,
FileText,
Link,
CheckCircle,
Clock,
Info,
Lightbulb,
Target,
Zap,
AlertTriangle,
FileVideo,
X,
} from "lucide-react"
interface AppSubmissionDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}
export function AppSubmissionDialog({ open, onOpenChange }: AppSubmissionDialogProps) {
const { user, canSubmitApp } = useAuth()
const [step, setStep] = useState(1)
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSubmitted, setIsSubmitted] = useState(false)
const [formData, setFormData] = useState({
name: "",
type: "文字處理",
description: "",
appUrl: "",
demoFile: null as File | null,
sourceCodeUrl: "",
documentation: "",
features: "",
technicalDetails: "",
requestFeatured: false,
agreeTerms: false,
})
// 檢查用戶權限
if (!user || !canSubmitApp()) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<AlertTriangle className="w-5 h-5 text-orange-600" />
<span></span>
</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="text-center py-6">
<div className="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center mx-auto mb-4">
<AlertTriangle className="w-8 h-8 text-orange-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-600 mb-4">
AI {user?.role === "admin" ? "管理員" : "一般用戶"}
</p>
<div className="bg-blue-50 rounded-lg p-4 mb-4">
<div className="flex items-start space-x-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="text-left">
<p className="text-sm font-medium text-blue-900"></p>
<ul className="text-sm text-blue-700 mt-1 space-y-1">
<li> </li>
<li> </li>
<li> 調</li>
</ul>
</div>
</div>
</div>
<Button onClick={() => onOpenChange(false)} className="w-full">
</Button>
</div>
</DialogContent>
</Dialog>
)
}
const handleInputChange = (field: string, value: string | boolean | File | null) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] || null
handleInputChange("demoFile", file)
}
const removeFile = () => {
handleInputChange("demoFile", null)
}
const handleNext = () => {
if (step < 3) {
setStep(step + 1)
}
}
const handlePrevious = () => {
if (step > 1) {
setStep(step - 1)
}
}
const handleSubmit = async () => {
setIsSubmitting(true)
// 模擬提交過程
await new Promise((resolve) => setTimeout(resolve, 2000))
setIsSubmitting(false)
setIsSubmitted(true)
// 3秒後關閉對話框
setTimeout(() => {
onOpenChange(false)
setIsSubmitted(false)
setStep(1)
setFormData({
name: "",
type: "文字處理",
description: "",
appUrl: "",
demoFile: null,
sourceCodeUrl: "",
documentation: "",
features: "",
technicalDetails: "",
requestFeatured: false,
agreeTerms: false,
})
}, 3000)
}
const isStep1Valid = formData.name && formData.description && formData.appUrl
const isStep2Valid = formData.features
const isStep3Valid = formData.agreeTerms
if (isSubmitted) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<div 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"> 1-3 </p>
<div className="bg-blue-50 rounded-lg p-4 mb-4">
<div className="flex items-start space-x-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="text-left">
<p className="text-sm font-medium text-blue-900"></p>
<ul className="text-sm text-blue-700 mt-1 space-y-1">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
<p className="text-sm text-gray-500">...</p>
</div>
</DialogContent>
</Dialog>
)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Upload className="w-5 h-5 text-blue-600" />
<span> AI </span>
</DialogTitle>
<DialogDescription> AI </DialogDescription>
</DialogHeader>
{/* Progress Indicator */}
<div className="flex items-center justify-center space-x-4 mb-6">
{[1, 2, 3].map((stepNumber) => (
<div key={stepNumber} className="flex items-center">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
step >= stepNumber ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600"
}`}
>
{stepNumber}
</div>
{stepNumber < 3 && (
<div className={`w-12 h-0.5 mx-2 ${step > stepNumber ? "bg-blue-600" : "bg-gray-200"}`} />
)}
</div>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main Form */}
<div className="lg:col-span-2">
{step === 1 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<FileText className="w-5 h-5" />
<span></span>
</CardTitle>
<CardDescription> AI </CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name"> *</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => handleInputChange("name", e.target.value)}
placeholder="輸入您的應用名稱"
/>
</div>
<div className="space-y-2">
<Label htmlFor="type"> *</Label>
<Select value={formData.type} onValueChange={(value) => handleInputChange("type", value)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="文字處理"></SelectItem>
<SelectItem value="圖像生成"></SelectItem>
<SelectItem value="語音辨識"></SelectItem>
<SelectItem value="推薦系統"></SelectItem>
<SelectItem value="數據分析"></SelectItem>
<SelectItem value="其他"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description"> *</Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => handleInputChange("description", e.target.value)}
placeholder="詳細描述您的應用功能、特色和創新點..."
rows={4}
/>
<p className="text-xs text-gray-500"> 100-500 </p>
</div>
<div className="space-y-2">
<Label htmlFor="appUrl"> *</Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="appUrl"
value={formData.appUrl}
onChange={(e) => handleInputChange("appUrl", e.target.value)}
placeholder="https://your-app.example.com"
className="pl-10"
/>
</div>
<p className="text-xs text-gray-500"></p>
</div>
<div className="space-y-2">
<Label htmlFor="demoFile"></Label>
<div className="space-y-3">
{!formData.demoFile ? (
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-400 hover:bg-blue-50 transition-colors">
<input
type="file"
id="demoFile"
accept="video/*,.mp4,.avi,.mov,.wmv,.flv,.webm"
onChange={handleFileChange}
className="hidden"
/>
<label htmlFor="demoFile" className="cursor-pointer">
<div className="flex flex-col items-center space-y-2">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
<FileVideo className="w-6 h-6 text-gray-400" />
</div>
<div>
<p className="text-sm font-medium text-gray-900"></p>
<p className="text-xs text-gray-500"> MP4, AVI, MOV, WMV 100MB</p>
</div>
</div>
</label>
</div>
) : (
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex items-center space-x-3">
<FileVideo className="w-5 h-5 text-blue-600" />
<div>
<p className="text-sm font-medium text-blue-900">{formData.demoFile.name}</p>
<p className="text-xs text-blue-700">
{(formData.demoFile.size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={removeFile}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<X className="w-4 h-4" />
</Button>
</div>
)}
</div>
<p className="text-xs text-gray-500"></p>
</div>
</CardContent>
</Card>
)}
{step === 2 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Zap className="w-5 h-5" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="features"> *</Label>
<Textarea
id="features"
value={formData.features}
onChange={(e) => handleInputChange("features", e.target.value)}
placeholder="列出應用的主要功能和特色,例如:&#10;• 支援多語言對話&#10;• 上下文理解能力&#10;• 個性化回應..."
rows={4}
/>
</div>
<div className="space-y-2">
<Label htmlFor="technicalDetails"></Label>
<Textarea
id="technicalDetails"
value={formData.technicalDetails}
onChange={(e) => handleInputChange("technicalDetails", e.target.value)}
placeholder="技術架構、使用的 AI 模型、開發框架等..."
rows={3}
/>
</div>
<div className="space-y-2">
<Label htmlFor="sourceCodeUrl"></Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="sourceCodeUrl"
value={formData.sourceCodeUrl}
onChange={(e) => handleInputChange("sourceCodeUrl", e.target.value)}
placeholder="https://github.com/username/repo"
className="pl-10"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="documentation"></Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="documentation"
value={formData.documentation}
onChange={(e) => handleInputChange("documentation", e.target.value)}
placeholder="https://docs.your-app.com"
className="pl-10"
/>
</div>
</div>
</CardContent>
</Card>
)}
{step === 3 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<CheckCircle className="w-5 h-5" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Application Summary */}
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="font-medium mb-3"></h4>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-500"></span>
<span className="font-medium">{formData.name}</span>
</div>
<div>
<span className="text-gray-500"></span>
<Badge variant="outline">{formData.type}</Badge>
</div>
<div className="col-span-2">
<span className="text-gray-500"></span>
<span className="font-medium">
{user?.name} ({user?.department})
</span>
</div>
{formData.demoFile && (
<div className="col-span-2">
<span className="text-gray-500"></span>
<span className="font-medium">{formData.demoFile.name}</span>
</div>
)}
</div>
</div>
{/* Featured Request */}
<div className="flex items-center space-x-3 p-4 bg-yellow-50 rounded-lg">
<Switch
id="requestFeatured"
checked={formData.requestFeatured}
onCheckedChange={(checked) => handleInputChange("requestFeatured", checked)}
/>
<div className="flex-1">
<Label htmlFor="requestFeatured" className="font-medium">
</Label>
<p className="text-sm text-gray-600">
</p>
</div>
</div>
{/* Terms Agreement */}
<div className="space-y-4">
<div className="flex items-start space-x-3 p-4 border rounded-lg">
<Switch
id="agreeTerms"
checked={formData.agreeTerms}
onCheckedChange={(checked) => handleInputChange("agreeTerms", checked)}
/>
<div className="flex-1">
<Label htmlFor="agreeTerms" className="font-medium">
*
</Label>
<div className="text-sm text-gray-600 mt-2 space-y-1">
<p> </p>
<p> </p>
<p> 1-3 </p>
<p> 使</p>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
)}
</div>
{/* Sidebar */}
<div className="space-y-4">
{/* Progress Card */}
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className={`flex items-center space-x-3 ${step >= 1 ? "text-blue-600" : "text-gray-400"}`}>
<div
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs ${
step >= 1 ? "bg-blue-600 text-white" : "bg-gray-200"
}`}
>
1
</div>
<span className="text-sm"></span>
</div>
<div className={`flex items-center space-x-3 ${step >= 2 ? "text-blue-600" : "text-gray-400"}`}>
<div
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs ${
step >= 2 ? "bg-blue-600 text-white" : "bg-gray-200"
}`}
>
2
</div>
<span className="text-sm"></span>
</div>
<div className={`flex items-center space-x-3 ${step >= 3 ? "text-blue-600" : "text-gray-400"}`}>
<div
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs ${
step >= 3 ? "bg-blue-600 text-white" : "bg-gray-200"
}`}
>
3
</div>
<span className="text-sm"></span>
</div>
</CardContent>
</Card>
{/* Tips Card */}
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center space-x-2">
<Lightbulb className="w-4 h-4" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
{step === 1 && (
<>
<div className="flex items-start space-x-2">
<Target className="w-4 h-4 text-blue-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<Target className="w-4 h-4 text-blue-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<Target className="w-4 h-4 text-blue-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<FileVideo className="w-4 h-4 text-blue-500 mt-0.5" />
<p></p>
</div>
</>
)}
{step === 2 && (
<>
<div className="flex items-start space-x-2">
<Zap className="w-4 h-4 text-green-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<Zap className="w-4 h-4 text-green-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<Zap className="w-4 h-4 text-green-500 mt-0.5" />
<p></p>
</div>
</>
)}
{step === 3 && (
<>
<div className="flex items-start space-x-2">
<CheckCircle className="w-4 h-4 text-purple-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<CheckCircle className="w-4 h-4 text-purple-500 mt-0.5" />
<p></p>
</div>
<div className="flex items-start space-x-2">
<CheckCircle className="w-4 h-4 text-purple-500 mt-0.5" />
<p></p>
</div>
</>
)}
</CardContent>
</Card>
{/* Review Process Card */}
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="flex items-center space-x-3">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
<Upload className="w-3 h-3 text-blue-600" />
</div>
<span></span>
</div>
<div className="flex items-center space-x-3">
<div className="w-6 h-6 bg-yellow-100 rounded-full flex items-center justify-center">
<Clock className="w-3 h-3 text-yellow-600" />
</div>
<span></span>
</div>
<div className="flex items-center space-x-3">
<div className="w-6 h-6 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="w-3 h-3 text-green-600" />
</div>
<span></span>
</div>
<Separator />
<div className="bg-blue-50 p-3 rounded-lg">
<p className="text-xs text-blue-700">
<strong></strong>1-3
<br />
<strong></strong>
</p>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Action Buttons */}
<div className="flex justify-between pt-6 border-t">
<Button variant="outline" onClick={handlePrevious} disabled={step === 1}>
</Button>
<div className="flex space-x-3">
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
{step < 3 ? (
<Button
onClick={handleNext}
disabled={(step === 1 && !isStep1Valid) || (step === 2 && !isStep2Valid)}
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
>
</Button>
) : (
<Button
onClick={handleSubmit}
disabled={!isStep3Valid || isSubmitting}
className="bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700"
>
{isSubmitting ? (
<>
<Clock className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
<>
<Upload className="w-4 h-4 mr-2" />
</>
)}
</Button>
)}
</div>
</div>
</DialogContent>
</Dialog>
)
}