This commit is contained in:
2025-03-07 17:45:17 +08:00
commit 936af0c4ec
114 changed files with 37662 additions and 0 deletions

239
extension/src/App.tsx Normal file
View File

@@ -0,0 +1,239 @@
import React, { useState, useEffect } from 'react';
import { MessageSquare, BarChart2, Send, RefreshCw, Settings as SettingsIcon, AlertCircle } from 'lucide-react';
import CommentList from './sidebar/components/CommentList';
import Analytics from './sidebar/components/Analytics';
import ReplyGenerator from './sidebar/components/ReplyGenerator';
import Settings from './sidebar/components/Settings';
import { Comment } from './types';
import mockComments from './mockData';
function App() {
const [activeTab, setActiveTab] = useState<'comments' | 'analytics' | 'reply' | 'settings'>('comments');
const [comments, setComments] = useState<Comment[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [selectedComment, setSelectedComment] = useState<Comment | null>(null);
const [mockDelay, setMockDelay] = useState<number>(1000);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Simulate loading comments with a delay
setError(null);
const timer = setTimeout(() => {
try {
setComments(mockComments);
setIsLoading(false);
} catch (err) {
setError('Error loading comments: ' + (err instanceof Error ? err.message : String(err)));
setIsLoading(false);
}
}, mockDelay);
return () => clearTimeout(timer);
}, [mockDelay]);
const refreshComments = () => {
setIsLoading(true);
setError(null);
setTimeout(() => {
try {
setComments(mockComments);
setIsLoading(false);
} catch (err) {
setError('Error refreshing comments: ' + (err instanceof Error ? err.message : String(err)));
setIsLoading(false);
}
}, mockDelay);
};
const handleSelectComment = (comment: Comment) => {
setSelectedComment(comment);
setActiveTab('reply');
};
return (
<div className="flex flex-col min-h-screen bg-gray-100">
<header className="bg-blue-600 text-white p-4">
<div className="container mx-auto flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<h1 className="text-2xl font-bold"> - </h1>
<div className="flex items-center space-x-4">
<div className="flex items-center">
<span className="text-sm mr-2">:</span>
<select
value={mockDelay}
onChange={(e) => setMockDelay(Number(e.target.value))}
className="bg-blue-700 text-white rounded px-2 py-1 text-sm"
>
<option value="0"></option>
<option value="500">0.5 </option>
<option value="1000">1 </option>
<option value="2000">2 </option>
</select>
</div>
<button
onClick={refreshComments}
className="p-2 bg-blue-700 rounded-full text-white hover:bg-blue-800 transition-colors"
title="重新載入留言"
>
<RefreshCw size={16} />
</button>
</div>
</div>
</header>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mx-auto mt-4 container" role="alert">
<div className="flex items-center">
<AlertCircle className="mr-2" size={20} />
<span className="block sm:inline">{error}</span>
</div>
</div>
)}
<main className="flex-1 container mx-auto p-4 flex flex-col md:flex-row gap-4">
{/* Sidebar Preview */}
<div className="w-full md:w-80 bg-white rounded-lg shadow-lg overflow-hidden flex flex-col h-[600px] border border-gray-200">
{/* Header */}
<div className="bg-blue-600 text-white p-4 shadow-md">
<h2 className="text-xl font-bold flex items-center">
<MessageSquare className="mr-2" size={20} />
</h2>
<p className="text-sm opacity-80"></p>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-4">
{activeTab === 'comments' && (
<CommentList
comments={comments}
isLoading={isLoading}
onSelectComment={handleSelectComment}
/>
)}
{activeTab === 'analytics' && (
<Analytics comments={comments} />
)}
{activeTab === 'reply' && (
<ReplyGenerator
comment={selectedComment}
onBack={() => setActiveTab('comments')}
/>
)}
{activeTab === 'settings' && (
<Settings />
)}
</div>
{/* Navigation */}
<nav className="bg-white border-t border-gray-200 p-2">
<div className="flex justify-around">
<button
onClick={() => setActiveTab('comments')}
className={`flex flex-col items-center p-2 rounded-md ${activeTab === 'comments' ? 'text-blue-600' : 'text-gray-600'}`}
>
<MessageSquare size={20} />
<span className="text-xs mt-1"></span>
</button>
<button
onClick={() => setActiveTab('analytics')}
className={`flex flex-col items-center p-2 rounded-md ${activeTab === 'analytics' ? 'text-blue-600' : 'text-gray-600'}`}
>
<BarChart2 size={20} />
<span className="text-xs mt-1"></span>
</button>
<button
onClick={() => setActiveTab('reply')}
className={`flex flex-col items-center p-2 rounded-md ${activeTab === 'reply' ? 'text-blue-600' : 'text-gray-600'}`}
>
<Send size={20} />
<span className="text-xs mt-1"></span>
</button>
<button
onClick={() => setActiveTab('settings')}
className={`flex flex-col items-center p-2 rounded-md ${activeTab === 'settings' ? 'text-blue-600' : 'text-gray-600'}`}
>
<SettingsIcon size={20} />
<span className="text-xs mt-1"></span>
</button>
</div>
</nav>
</div>
{/* Development Info */}
<div className="flex-1">
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold mb-4"></h2>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-gray-50 p-3 rounded border border-gray-200">
<p className="text-sm font-medium text-gray-700"></p>
<p className="text-lg">{activeTab}</p>
</div>
<div className="bg-gray-50 p-3 rounded border border-gray-200">
<p className="text-sm font-medium text-gray-700"></p>
<p className="text-lg">{comments.length}</p>
</div>
<div className="bg-gray-50 p-3 rounded border border-gray-200">
<p className="text-sm font-medium text-gray-700"></p>
<p className="text-lg">{isLoading ? '載入中' : '已載入'}</p>
</div>
<div className="bg-gray-50 p-3 rounded border border-gray-200">
<p className="text-sm font-medium text-gray-700"></p>
<p className="text-lg">{selectedComment ? `ID: ${selectedComment.id}` : '無'}</p>
</div>
</div>
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="bg-blue-50 p-4 rounded border border-blue-200">
<p className="mb-2"> Chrome </p>
<ul className="list-disc pl-5 space-y-1 text-sm">
<li></li>
<li>調</li>
<li></li>
<li>使</li>
</ul>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="bg-gray-50 p-4 rounded border border-gray-200 space-y-3">
<div>
<p className="font-medium"></p>
<code className="bg-gray-100 px-2 py-1 rounded text-sm">npm run build</code>
</div>
<div>
<p className="font-medium"></p>
<ol className="list-decimal pl-5 text-sm space-y-1">
<li> Chrome (chrome://extensions/)</li>
<li></li>
<li></li>
<li> dist </li>
</ol>
</div>
<div>
<p className="font-medium"></p>
<ol className="list-decimal pl-5 text-sm space-y-1">
<li></li>
<li></li>
<li></li>
</ol>
</div>
</div>
</div>
</div>
</div>
</main>
<footer className="bg-gray-800 text-white p-4 text-center">
<p> - © 2025</p>
</footer>
</div>
);
}
export default App;