Skip to content

Commit 42ef0ce

Browse files
catomeanclaude
andcommitted
refactor: split WorkspaceDashboard into focused sub-components
Extract WorkspaceDashboard.tsx (642→219 lines) into workspace/ directory: - WorkspaceHeader: header with lawyer info and close button - WorkspaceSidebar: navigation with stats - OverviewView: welcome banner, case summary, quick actions - FilesView: file management with categories - ChatView: live chat interface - TimelineView: activity timeline - SettingsView: privacy and notification settings - workspaceUtils: helper functions (formatFileSize, getAIResponse) Part of P2 - Large Component Breakdown Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9bc89ad commit 42ef0ce

11 files changed

Lines changed: 656 additions & 466 deletions

File tree

app/bots/legal-expert/components/demo/WorkspaceDashboard.tsx

Lines changed: 41 additions & 466 deletions
Large diffs are not rendered by default.

app/bots/legal-expert/components/demo/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,23 @@ export interface WorkspaceSection {
4646
files: UploadedFile[];
4747
aiSuggestion?: string;
4848
}
49+
50+
// Workspace Dashboard types
51+
export interface ChatMessage {
52+
id: string;
53+
sender: string;
54+
senderName: string;
55+
avatar: string;
56+
content: string;
57+
timestamp: Date;
58+
}
59+
60+
export interface CategoryWithFiles {
61+
id: string;
62+
title: string;
63+
icon: string;
64+
files: UploadedFile[];
65+
count: number;
66+
}
67+
68+
export type WorkspaceViewMode = 'overview' | 'files' | 'chat' | 'timeline' | 'settings';
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { type FC } from 'react';
2+
import { type ChatMessage, type LawyerProfile } from '../types';
3+
4+
interface ChatViewProps {
5+
messages: ChatMessage[];
6+
inputMessage: string;
7+
setInputMessage: (msg: string) => void;
8+
handleSendMessage: () => void;
9+
isTyping: boolean;
10+
lawyer: LawyerProfile;
11+
}
12+
13+
export const ChatView: FC<ChatViewProps> = ({
14+
messages,
15+
inputMessage,
16+
setInputMessage,
17+
handleSendMessage,
18+
isTyping,
19+
lawyer,
20+
}) => {
21+
return (
22+
<div className="h-full flex flex-col bg-slate-800/50 backdrop-blur-xl rounded-2xl border border-slate-700">
23+
<div className="px-6 py-4 border-b border-slate-700">
24+
<h2 className="text-xl font-bold text-white">💬 Live Chat</h2>
25+
<p className="text-sm text-slate-400">AI + {lawyer.username} • No appointments needed</p>
26+
</div>
27+
28+
<div className="flex-1 overflow-auto p-6 space-y-4">
29+
{messages.map((msg) => (
30+
<div key={msg.id} className={`flex gap-3 ${msg.sender === 'user' ? 'justify-end' : ''}`}>
31+
{msg.sender !== 'user' && (
32+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center text-white flex-shrink-0">
33+
{msg.avatar}
34+
</div>
35+
)}
36+
<div
37+
className={`max-w-md ${msg.sender === 'user' ? 'bg-blue-600' : 'bg-slate-700'} rounded-2xl px-4 py-3`}
38+
>
39+
<p className="text-xs text-slate-300 mb-1">{msg.senderName}</p>
40+
<p className="text-white text-sm">{msg.content}</p>
41+
<p className="text-xs text-slate-400 mt-1">{msg.timestamp.toLocaleTimeString()}</p>
42+
</div>
43+
</div>
44+
))}
45+
{isTyping && (
46+
<div className="flex gap-3">
47+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center">
48+
🤖
49+
</div>
50+
<div className="bg-slate-700 rounded-2xl px-4 py-3">
51+
<div className="flex gap-1">
52+
<span className="w-2 h-2 bg-slate-400 rounded-full animate-bounce" />
53+
<span className="w-2 h-2 bg-slate-400 rounded-full animate-bounce delay-100" />
54+
<span className="w-2 h-2 bg-slate-400 rounded-full animate-bounce delay-200" />
55+
</div>
56+
</div>
57+
</div>
58+
)}
59+
</div>
60+
61+
<div className="p-4 border-t border-slate-700">
62+
<div className="flex gap-2">
63+
<input
64+
type="text"
65+
value={inputMessage}
66+
onChange={(e) => setInputMessage(e.target.value)}
67+
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
68+
placeholder="Ask AI or your lawyer anything..."
69+
className="flex-1 bg-slate-900 text-white px-4 py-3 rounded-lg border border-slate-700 focus:border-blue-500 focus:outline-none"
70+
/>
71+
<button
72+
onClick={handleSendMessage}
73+
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg font-medium hover:shadow-lg transition-all"
74+
>
75+
Send
76+
</button>
77+
</div>
78+
</div>
79+
</div>
80+
);
81+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { type FC } from 'react';
2+
import { type UploadedFile, type CategoryWithFiles } from '../types';
3+
import { formatFileSize } from './workspaceUtils';
4+
5+
interface FilesViewProps {
6+
categoriesWithFiles: CategoryWithFiles[];
7+
selectedFile: string | null;
8+
setSelectedFile: (id: string | null) => void;
9+
onFileDelete: (fileId: string) => void;
10+
onFileVisibilityChange: (fileId: string, visibility: string) => void;
11+
}
12+
13+
export const FilesView: FC<FilesViewProps> = ({
14+
categoriesWithFiles,
15+
selectedFile,
16+
setSelectedFile,
17+
onFileDelete,
18+
onFileVisibilityChange,
19+
}) => {
20+
return (
21+
<div className="space-y-6 animate-fadeIn">
22+
<div className="flex items-center justify-between">
23+
<h2 className="text-2xl font-bold text-white">📁 File Management</h2>
24+
<button className="px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg font-medium hover:shadow-lg transition-all">
25+
+ Upload Files
26+
</button>
27+
</div>
28+
29+
{categoriesWithFiles.map((category) => (
30+
<div
31+
key={category.id}
32+
className="bg-slate-800/50 backdrop-blur-xl rounded-2xl border border-slate-700 overflow-hidden"
33+
>
34+
<div className="px-6 py-4 bg-slate-900/50 border-b border-slate-700">
35+
<div className="flex items-center justify-between">
36+
<div className="flex items-center gap-3">
37+
<span className="text-2xl">{category.icon}</span>
38+
<h3 className="text-lg font-bold text-white">{category.title}</h3>
39+
<span className="text-xs bg-slate-700 text-slate-300 px-2 py-1 rounded-full">
40+
{category.count} files
41+
</span>
42+
</div>
43+
</div>
44+
</div>
45+
46+
<div className="p-4 space-y-2">
47+
{category.files.map((file: UploadedFile) => (
48+
<div
49+
key={file.id}
50+
className={`flex items-center gap-4 p-4 rounded-lg border transition-all ${
51+
selectedFile === file.id
52+
? 'bg-blue-600/20 border-blue-500'
53+
: 'bg-slate-900/30 border-slate-700 hover:border-slate-600'
54+
}`}
55+
>
56+
<div className="text-3xl">📄</div>
57+
<div className="flex-1 min-w-0">
58+
<p className="text-white font-medium truncate">{file.name}</p>
59+
<div className="flex items-center gap-4 mt-1">
60+
<span className="text-xs text-slate-400">{formatFileSize(file.size)}</span>
61+
<select
62+
value={file.visibility || 'private'}
63+
onChange={(e) => onFileVisibilityChange(file.id, e.target.value)}
64+
className="text-xs bg-slate-700 text-white px-2 py-1 rounded border border-slate-600"
65+
>
66+
<option value="private">🔒 Private</option>
67+
<option value="lawyer">👨‍⚖️ Lawyer Only</option>
68+
<option value="team">👥 Team</option>
69+
<option value="public">🌐 Public</option>
70+
</select>
71+
</div>
72+
</div>
73+
<div className="flex items-center gap-2">
74+
<button
75+
onClick={() => setSelectedFile(file.id)}
76+
className="p-2 hover:bg-slate-700 rounded-lg text-slate-400 hover:text-white transition-colors"
77+
>
78+
👁️
79+
</button>
80+
<button
81+
onClick={() => onFileDelete(file.id)}
82+
className="p-2 hover:bg-red-600/20 rounded-lg text-slate-400 hover:text-red-400 transition-colors"
83+
>
84+
🗑️
85+
</button>
86+
</div>
87+
</div>
88+
))}
89+
</div>
90+
</div>
91+
))}
92+
</div>
93+
);
94+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { type FC } from 'react';
2+
import { type UploadedFile, type CategoryWithFiles, type WorkspaceViewMode } from '../types';
3+
4+
interface OverviewViewProps {
5+
files: UploadedFile[];
6+
categoriesWithFiles: CategoryWithFiles[];
7+
caseDescription: string;
8+
setViewMode: (mode: WorkspaceViewMode) => void;
9+
}
10+
11+
const QUICK_ACTIONS = [
12+
{ icon: '📁', title: 'Files', color: 'from-blue-500 to-cyan-500', view: 'files' as const },
13+
{ icon: '💬', title: 'Chat', color: 'from-purple-500 to-pink-500', view: 'chat' as const },
14+
{
15+
icon: '📅',
16+
title: 'Timeline',
17+
color: 'from-green-500 to-emerald-500',
18+
view: 'timeline' as const,
19+
},
20+
{
21+
icon: '⚙️',
22+
title: 'Settings',
23+
color: 'from-orange-500 to-red-500',
24+
view: 'settings' as const,
25+
},
26+
] as const;
27+
28+
export const OverviewView: FC<OverviewViewProps> = ({
29+
files,
30+
categoriesWithFiles,
31+
caseDescription,
32+
setViewMode,
33+
}) => {
34+
const getActionCount = (view: WorkspaceViewMode): string | number => {
35+
switch (view) {
36+
case 'files':
37+
return files.length;
38+
case 'chat':
39+
return '24/7';
40+
case 'timeline':
41+
return 'Live';
42+
case 'settings':
43+
return 'Manage';
44+
default:
45+
return '';
46+
}
47+
};
48+
49+
return (
50+
<div className="space-y-6 animate-fadeIn">
51+
{/* Welcome banner */}
52+
<div className="bg-gradient-to-r from-blue-600 to-cyan-600 rounded-2xl p-6 text-white">
53+
<h2 className="text-2xl font-bold mb-2">Welcome to Your Data Room 🚀</h2>
54+
<p className="text-blue-100">
55+
Your secure collaborative workspace is ready. All files are encrypted end-to-end.
56+
</p>
57+
</div>
58+
59+
{/* Case summary */}
60+
<div className="bg-slate-800/50 backdrop-blur-xl rounded-2xl p-6 border border-slate-700">
61+
<h3 className="text-lg font-bold text-white mb-3">📋 Case Summary</h3>
62+
<p className="text-slate-300 text-sm leading-relaxed">{caseDescription}</p>
63+
</div>
64+
65+
{/* Quick actions */}
66+
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
67+
{QUICK_ACTIONS.map((action) => (
68+
<button
69+
key={action.title}
70+
onClick={() => setViewMode(action.view)}
71+
className="bg-slate-800/50 backdrop-blur-xl rounded-xl p-6 border border-slate-700 hover:border-slate-600 transition-all hover:scale-105 group"
72+
>
73+
<div
74+
className={`text-4xl mb-3 bg-gradient-to-br ${action.color} bg-clip-text text-transparent`}
75+
>
76+
{action.icon}
77+
</div>
78+
<h4 className="text-white font-bold mb-1">{action.title}</h4>
79+
<p className="text-slate-400 text-sm">{getActionCount(action.view)}</p>
80+
</button>
81+
))}
82+
</div>
83+
84+
{/* File categories preview */}
85+
<div className="bg-slate-800/50 backdrop-blur-xl rounded-2xl p-6 border border-slate-700">
86+
<h3 className="text-lg font-bold text-white mb-4">📂 File Categories</h3>
87+
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
88+
{categoriesWithFiles.map((cat) => (
89+
<div key={cat.id} className="bg-slate-900/50 rounded-lg p-4 border border-slate-700">
90+
<div className="flex items-center justify-between mb-2">
91+
<span className="text-2xl">{cat.icon}</span>
92+
<span className="text-xs bg-blue-600 text-white px-2 py-1 rounded-full">
93+
{cat.count}
94+
</span>
95+
</div>
96+
<p className="text-sm text-white font-medium">{cat.title}</p>
97+
</div>
98+
))}
99+
</div>
100+
</div>
101+
</div>
102+
);
103+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { type FC } from 'react';
2+
import { type LawyerProfile } from '../types';
3+
4+
interface SettingsViewProps {
5+
lawyer: LawyerProfile;
6+
}
7+
8+
interface TeamMember {
9+
role: string;
10+
access: string;
11+
icon: string;
12+
}
13+
14+
interface NotificationSetting {
15+
label: string;
16+
enabled: boolean;
17+
}
18+
19+
export const SettingsView: FC<SettingsViewProps> = ({ lawyer }) => {
20+
const teamMembers: TeamMember[] = [
21+
{ role: 'Owner (You)', access: 'Full Access', icon: '👤' },
22+
{ role: `Attorney (${lawyer.username})`, access: 'Full Access', icon: lawyer.avatar },
23+
{ role: 'Paralegal', access: 'Limited Access', icon: '📝' },
24+
{ role: 'Advisor', access: 'Read Only', icon: '👥' },
25+
];
26+
27+
const notificationSettings: NotificationSetting[] = [
28+
{ label: 'Email notifications', enabled: true },
29+
{ label: 'Push notifications', enabled: true },
30+
{ label: 'SMS alerts', enabled: false },
31+
];
32+
33+
return (
34+
<div className="space-y-6 animate-fadeIn">
35+
<h2 className="text-2xl font-bold text-white">⚙️ Workspace Settings</h2>
36+
37+
<div className="bg-slate-800/50 backdrop-blur-xl rounded-2xl p-6 border border-slate-700">
38+
<h3 className="text-lg font-bold text-white mb-4">🔐 Privacy & Access Control</h3>
39+
<div className="space-y-4">
40+
{teamMembers.map((member) => (
41+
<div
42+
key={member.role}
43+
className="flex items-center justify-between p-4 bg-slate-900/50 rounded-lg"
44+
>
45+
<div className="flex items-center gap-3">
46+
<span className="text-2xl">{member.icon}</span>
47+
<div>
48+
<p className="text-white font-medium">{member.role}</p>
49+
<p className="text-sm text-slate-400">{member.access}</p>
50+
</div>
51+
</div>
52+
<button className="text-sm text-blue-400 hover:text-blue-300">Edit</button>
53+
</div>
54+
))}
55+
</div>
56+
</div>
57+
58+
<div className="bg-slate-800/50 backdrop-blur-xl rounded-2xl p-6 border border-slate-700">
59+
<h3 className="text-lg font-bold text-white mb-4">🔔 Notifications</h3>
60+
<div className="space-y-3">
61+
{notificationSettings.map((setting) => (
62+
<div
63+
key={setting.label}
64+
className="flex items-center justify-between p-3 bg-slate-900/50 rounded-lg"
65+
>
66+
<span className="text-white">{setting.label}</span>
67+
<div
68+
className={`w-12 h-6 rounded-full transition-colors ${setting.enabled ? 'bg-blue-600' : 'bg-slate-600'}`}
69+
>
70+
<div
71+
className={`w-5 h-5 bg-white rounded-full mt-0.5 transition-transform ${setting.enabled ? 'ml-6' : 'ml-0.5'}`}
72+
/>
73+
</div>
74+
</div>
75+
))}
76+
</div>
77+
</div>
78+
</div>
79+
);
80+
};

0 commit comments

Comments
 (0)