Spaces:
Sleeping
Sleeping
| // web/src/components/RightPanel.tsx | |
| import React, { useState } from 'react'; | |
| import { Button } from './ui/button'; | |
| import { Input } from './ui/input'; | |
| import { Label } from './ui/label'; | |
| import { Card } from './ui/card'; | |
| import { Separator } from './ui/separator'; | |
| import { Textarea } from './ui/textarea'; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; | |
| import { LogIn, LogOut, FileText, MessageSquare, Download, ClipboardList, Sparkles } from 'lucide-react'; | |
| import type { User } from '../App'; | |
| import { toast } from 'sonner'; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogDescription, | |
| DialogHeader, | |
| DialogTitle, | |
| DialogFooter, | |
| } from './ui/dialog'; | |
| interface RightPanelProps { | |
| user: User | null; | |
| onLogin: (name: string, emailOrId: string) => void; | |
| onLogout: () => void; | |
| isLoggedIn: boolean; | |
| onClose?: () => void; | |
| exportResult: string; | |
| setExportResult: (result: string) => void; | |
| resultType: 'export' | 'quiz' | 'summary' | null; | |
| setResultType: (type: 'export' | 'quiz' | 'summary' | null) => void; | |
| // ✅ Actions buttons callbacks (来自 App.tsx) | |
| onExport: () => void; | |
| onQuiz: () => void; | |
| onSummary: () => void; | |
| } | |
| export function RightPanel({ | |
| user, | |
| onLogin, | |
| onLogout, | |
| isLoggedIn, | |
| exportResult, | |
| setExportResult, | |
| resultType, | |
| setResultType, | |
| onExport, | |
| onQuiz, | |
| onSummary, | |
| }: RightPanelProps) { | |
| const [showLoginForm, setShowLoginForm] = useState(false); | |
| const [name, setName] = useState(''); | |
| const [emailOrId, setEmailOrId] = useState(''); | |
| const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false); | |
| const [feedbackText, setFeedbackText] = useState(''); | |
| const [feedbackCategory, setFeedbackCategory] = useState<'general' | 'bug' | 'feature'>('general'); | |
| const handleLoginClick = () => { | |
| if (!name.trim() || !emailOrId.trim()) { | |
| toast.error('Please fill in all fields'); | |
| return; | |
| } | |
| onLogin(name.trim(), emailOrId.trim()); | |
| setShowLoginForm(false); | |
| setName(''); | |
| setEmailOrId(''); | |
| }; | |
| const handleLogoutClick = () => { | |
| onLogout(); | |
| setShowLoginForm(false); | |
| }; | |
| const handleFeedbackSubmit = () => { | |
| if (!feedbackText.trim()) { | |
| toast.error('Please provide feedback text'); | |
| return; | |
| } | |
| console.log('Feedback submitted:', feedbackText, feedbackCategory); | |
| setFeedbackDialogOpen(false); | |
| setFeedbackText(''); | |
| toast.success('Feedback submitted!'); | |
| }; | |
| return ( | |
| <div className="flex-1 overflow-auto p-4 space-y-4"> | |
| {/* Account */} | |
| <Card className="p-4"> | |
| {!isLoggedIn ? ( | |
| <div className="space-y-4"> | |
| <div className="flex flex-col items-center py-4"> | |
| <h3 className="mb-2">Welcome to Clare!</h3> | |
| <p className="text-sm text-muted-foreground text-center mb-4"> | |
| Log in to start your learning session | |
| </p> | |
| </div> | |
| {!showLoginForm ? ( | |
| <Button onClick={() => setShowLoginForm(true)} className="w-full gap-2"> | |
| <LogIn className="h-4 w-4" /> | |
| Student Login | |
| </Button> | |
| ) : ( | |
| <div className="space-y-3"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="name">Name</Label> | |
| <Input | |
| id="name" | |
| value={name} | |
| onChange={(e) => setName(e.target.value)} | |
| placeholder="Enter your name" | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="emailOrId">Email / Student ID</Label> | |
| <Input | |
| id="emailOrId" | |
| value={emailOrId} | |
| onChange={(e) => setEmailOrId(e.target.value)} | |
| placeholder="Enter your email or ID" | |
| /> | |
| </div> | |
| <div className="flex gap-2"> | |
| <Button onClick={handleLoginClick} className="flex-1"> | |
| Enter | |
| </Button> | |
| <Button variant="outline" onClick={() => setShowLoginForm(false)}> | |
| Cancel | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ) : ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center gap-3"> | |
| <div className="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-blue-500 flex items-center justify-center text-white"> | |
| {user?.name?.charAt(0).toUpperCase()} | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <h4 className="truncate">{user?.name}</h4> | |
| <p className="text-sm text-muted-foreground truncate">{user?.email}</p> | |
| <p className="text-xs text-muted-foreground truncate">user_id: {user?.user_id}</p> | |
| </div> | |
| </div> | |
| <Button variant="destructive" onClick={handleLogoutClick} className="w-full gap-2"> | |
| <LogOut className="h-4 w-4" /> | |
| Log Out | |
| </Button> | |
| </div> | |
| )} | |
| </Card> | |
| {/* ✅ Actions:三按钮并排(同宽) */} | |
| {/* Actions */} | |
| {/* Actions */} | |
| <Card className="p-4"> | |
| <div className="flex items-center justify-between mb-3"> | |
| <h3 className="text-base font-medium">Actions TEST 123</h3> | |
| </div> | |
| {/* 强制一排三个:不用 grid,直接 flex,最稳 */} | |
| <div className="flex items-stretch gap-2"> | |
| <Button | |
| variant="outline" | |
| onClick={onExport} | |
| disabled={!isLoggedIn} | |
| title="Export" | |
| className="flex-1 h-10 p-0 flex items-center justify-center rounded-xl" | |
| > | |
| <Download className="h-4 w-4" /> | |
| </Button> | |
| <Button | |
| variant="outline" | |
| onClick={onQuiz} | |
| disabled={!isLoggedIn} | |
| title="Quiz" | |
| className="flex-1 h-10 p-0 flex items-center justify-center rounded-xl" | |
| > | |
| <ClipboardList className="h-4 w-4" /> | |
| </Button> | |
| <Button | |
| variant="outline" | |
| onClick={onSummary} | |
| disabled={!isLoggedIn} | |
| title="Summary" | |
| className="flex-1 h-10 p-0 flex items-center justify-center rounded-xl" | |
| > | |
| <Sparkles className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| </Card> | |
| <Separator /> | |
| {/* Results */} | |
| <div className="space-y-3"> | |
| <h3> | |
| {resultType === 'export' && 'Exported Conversation'} | |
| {resultType === 'quiz' && 'Micro-Quiz'} | |
| {resultType === 'summary' && 'Summarization'} | |
| {!resultType && 'Results'} | |
| </h3> | |
| <Card className="p-4 min-h-[200px] bg-muted/30"> | |
| {exportResult ? ( | |
| <div className="space-y-3"> | |
| <div className="flex items-center justify-between"> | |
| <FileText className="h-4 w-4 text-muted-foreground" /> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => { | |
| navigator.clipboard.writeText(exportResult); | |
| toast.success('Copied to clipboard!'); | |
| }} | |
| > | |
| Copy | |
| </Button> | |
| </div> | |
| <div className="text-sm whitespace-pre-wrap text-foreground">{exportResult}</div> | |
| </div> | |
| ) : ( | |
| <div className="flex items-center justify-center h-full text-sm text-muted-foreground text-left"> | |
| Results (export / summary / quiz) will appear here after actions run | |
| </div> | |
| )} | |
| </Card> | |
| </div> | |
| <Separator /> | |
| {/* Feedback */} | |
| <div className="space-y-3"> | |
| <h3>Feedback</h3> | |
| <Button | |
| variant="outline" | |
| className="w-full justify-start gap-2" | |
| onClick={() => setFeedbackDialogOpen(true)} | |
| > | |
| <MessageSquare className="h-4 w-4" /> | |
| Provide Feedback | |
| </Button> | |
| </div> | |
| <Dialog open={feedbackDialogOpen} onOpenChange={setFeedbackDialogOpen}> | |
| <DialogContent className="sm:max-w-[425px]"> | |
| <DialogHeader> | |
| <DialogTitle>Provide Feedback</DialogTitle> | |
| <DialogDescription>Help us improve Clare by sharing your thoughts and suggestions.</DialogDescription> | |
| </DialogHeader> | |
| <div className="space-y-3"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="feedbackCategory">Category</Label> | |
| <Select value={feedbackCategory} onValueChange={(v) => setFeedbackCategory(v as any)}> | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select a category" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="general">General Feedback</SelectItem> | |
| <SelectItem value="bug">Bug Report</SelectItem> | |
| <SelectItem value="feature">Feature Request</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="feedbackText">Feedback</Label> | |
| <Textarea | |
| id="feedbackText" | |
| value={feedbackText} | |
| onChange={(e) => setFeedbackText(e.target.value)} | |
| placeholder="Enter your feedback here..." | |
| className="min-h-[100px]" | |
| /> | |
| </div> | |
| </div> | |
| <DialogFooter> | |
| <Button variant="outline" onClick={() => setFeedbackDialogOpen(false)}> | |
| Cancel | |
| </Button> | |
| <Button onClick={handleFeedbackSubmit}>Submit</Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| </div> | |
| ); | |
| } | |