feat: major UI overhaul with new components and enhanced UX
- Add comprehensive Company Wiki feature with complete state management - CompanyWikiManager, empty states, invite modals - Implement new Chat system with enhanced layout and components - ChatLayout, ChatSidebar, MessageThread, FileUploadInput - Create modern Login and OTP verification flows - LoginNew page, OTPVerification component - Add new Employee Forms system with enhanced controller - Introduce Figma-based design components and multiple choice inputs - Add new font assets (NeueMontreal) and robot images for onboarding - Enhance existing components with improved styling and functionality - Update build configuration and dependencies - Remove deprecated ModernLogin component
This commit is contained in:
180
components/chat/ChatEmptyState.tsx
Normal file
180
components/chat/ChatEmptyState.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SuggestionCardProps {
|
||||
category: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const SuggestionCard: React.FC<SuggestionCardProps> = ({ category, title, description, icon, onClick }) => (
|
||||
<div
|
||||
className="p-4 bg-Neutrals-NeutralSlate0 rounded-2xl border border-Neutrals-NeutralSlate200 hover:border-Brand-Orange hover:shadow-sm transition-all cursor-pointer group"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-10 h-10 bg-Brand-Orange/10 rounded-xl flex items-center justify-center text-Brand-Orange group-hover:bg-Brand-Orange group-hover:text-white transition-colors">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-Brand-Orange font-medium mb-1">{category}</div>
|
||||
<div className="text-sm font-medium text-Neutrals-NeutralSlate950 mb-1">{title}</div>
|
||||
<div className="text-xs text-Neutrals-NeutralSlate500 leading-relaxed">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface CategoryTabProps {
|
||||
label: string;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const CategoryTab: React.FC<CategoryTabProps> = ({ label, isActive, onClick }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${isActive
|
||||
? 'bg-Brand-Orange text-white'
|
||||
: 'bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate600 hover:bg-Neutrals-NeutralSlate200'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
|
||||
const ChatEmptyState: React.FC = () => {
|
||||
const [activeCategory, setActiveCategory] = React.useState('All');
|
||||
|
||||
const categories = ['All', 'Performance', 'Culture', 'Reports', 'Analysis'];
|
||||
|
||||
const suggestions = [
|
||||
{
|
||||
category: 'Performance',
|
||||
title: 'Analyze team performance trends',
|
||||
description: 'Get insights on productivity patterns and improvement areas across your organization.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 17.5013V5.83464C2.5 5.36793 2.5 5.13458 2.59083 4.95631C2.67072 4.79951 2.79821 4.67202 2.95501 4.59213C3.13327 4.5013 3.36662 4.5013 3.83333 4.5013H5.16667C5.63338 4.5013 5.86673 4.5013 6.04499 4.59213C6.20179 4.67202 6.32928 4.79951 6.40917 4.95631C6.5 5.13458 6.5 5.36793 6.5 5.83464V17.5013M17.5 17.5013V9.16797C17.5 8.70126 17.5 8.46791 17.4092 8.28965C17.3293 8.13285 17.2018 8.00536 17.045 7.92547C16.8667 7.83464 16.6334 7.83464 16.1667 7.83464H14.8333C14.3666 7.83464 14.1333 7.83464 13.955 7.92547C13.7982 8.00536 13.6707 8.13285 13.5908 8.28965C13.5 8.46791 13.5 8.70126 13.5 9.16797V17.5013M12.5 17.5013V2.5013C12.5 2.03459 12.5 1.80124 12.4092 1.62298C12.3293 1.46618 12.2018 1.33869 12.045 1.2588C11.8667 1.16797 11.6334 1.16797 11.1667 1.16797H9.83333C9.36662 1.16797 9.13327 1.16797 8.95501 1.2588C8.79821 1.33869 8.67072 1.46618 8.59083 1.62298C8.5 1.80124 8.5 2.03459 8.5 2.5013V17.5013M18.3333 17.5013H1.66667" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
category: 'Culture',
|
||||
title: 'Assess company culture health',
|
||||
description: 'Review employee satisfaction, engagement levels, and cultural alignment metrics.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 5.83464C7.5 6.752 7.5 7.21068 7.70552 7.54611C7.88497 7.84313 8.15687 8.11503 8.45389 8.29448C8.78932 8.5 9.248 8.5 10.1654 8.5H11.5013C12.4187 8.5 12.8774 8.5 13.2128 8.29448C13.5098 8.11503 13.7817 7.84313 13.9612 7.54611C14.1667 7.21068 14.1667 6.752 14.1667 5.83464V4.16797C14.1667 3.25061 14.1667 2.79193 13.9612 2.4565C13.7817 2.15948 13.5098 1.88758 13.2128 1.70813C12.8774 1.5026 12.4187 1.5026 11.5013 1.5026H10.1654C9.248 1.5026 8.78932 1.5026 8.45389 1.70813C8.15687 1.88758 7.88497 2.15948 7.70552 2.4565C7.5 2.79193 7.5 3.25061 7.5 4.16797V5.83464Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M2.5 14.168C2.5 15.0854 2.5 15.544 2.70552 15.8795C2.88497 16.1765 3.15687 16.4484 3.45389 16.6278C3.78932 16.8333 4.248 16.8333 5.16536 16.8333H6.50131C7.41867 16.8333 7.87735 16.8333 8.21278 16.6278C8.5098 16.4484 8.7817 16.1765 8.96115 15.8795C9.16667 15.544 9.16667 15.0854 9.16667 14.168V12.5013C9.16667 11.5839 9.16667 11.1253 8.96115 10.7898C8.7817 10.4928 8.5098 10.2209 8.21278 10.0415C7.87735 9.83594 7.41867 9.83594 6.50131 9.83594H5.16536C4.248 9.83594 3.78932 9.83594 3.45389 10.0415C3.15687 10.2209 2.88497 10.4928 2.70552 10.7898C2.5 11.1253 2.5 11.5839 2.5 12.5013V14.168Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10.8346 14.168C10.8346 15.0854 10.8346 15.544 11.0401 15.8795C11.2196 16.1765 11.4915 16.4484 11.7885 16.6278C12.1239 16.8333 12.5826 16.8333 13.5 16.8333H14.8359C15.7533 16.8333 16.212 16.8333 16.5474 16.6278C16.8444 16.4484 17.1163 16.1765 17.2958 15.8795C17.5013 15.544 17.5013 15.0854 17.5013 14.168V12.5013C17.5013 11.5839 17.5013 11.1253 17.2958 10.7898C17.1163 10.4928 16.8444 10.2209 16.5474 10.0415C16.212 9.83594 15.7533 9.83594 14.8359 9.83594H13.5C12.5826 9.83594 12.1239 9.83594 11.7885 10.0415C11.4915 10.2209 11.2196 10.4928 11.0401 10.7898C10.8346 11.1253 10.8346 11.5839 10.8346 12.5013V14.168Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
category: 'Reports',
|
||||
title: 'Generate executive summary',
|
||||
description: 'Create comprehensive reports on organizational strengths, risks, and recommendations.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6666 9.16797H6.66659M8.33325 12.5013H6.66659M13.3333 5.83464H6.66659M16.6666 5.66797V14.3346C16.6666 15.7348 16.6666 16.4348 16.3941 16.9696C16.1544 17.44 15.772 17.8225 15.3016 18.0622C14.7668 18.3346 14.0667 18.3346 12.6666 18.3346H7.33325C5.93312 18.3346 5.23306 18.3346 4.69828 18.0622C4.22787 17.8225 3.84542 17.44 3.60574 16.9696C3.33325 16.4348 3.33325 15.7348 3.33325 14.3346V5.66797C3.33325 4.26784 3.33325 3.56777 3.60574 3.03299C3.84542 2.56259 4.22787 2.18014 4.69828 1.94045C5.23306 1.66797 5.93312 1.66797 7.33325 1.66797H12.6666C14.0667 1.66797 14.7668 1.66797 15.3016 1.94045C15.772 2.18014 16.1544 2.56259 16.3941 3.03299C16.6666 3.56777 16.6666 4.26784 16.6666 5.66797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
category: 'Analysis',
|
||||
title: 'Compare department metrics',
|
||||
description: 'Analyze cross-departmental performance and identify areas for improvement.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0001 1.66797C11.0944 1.66797 12.1781 1.88352 13.1891 2.30231C14.2002 2.7211 15.1188 3.33493 15.8926 4.10875C16.6665 4.88257 17.2803 5.80123 17.6991 6.81228C18.1179 7.82332 18.3334 8.90696 18.3334 10.0013M10.0001 1.66797V10.0013M10.0001 1.66797C5.39771 1.66797 1.66675 5.39893 1.66675 10.0013C1.66675 14.6037 5.39771 18.3346 10.0001 18.3346C14.6025 18.3346 18.3334 14.6037 18.3334 10.0013M10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013M18.3334 10.0013L10.0001 10.0013M18.3334 10.0013C18.3334 11.3164 18.0222 12.6128 17.4251 13.7846C16.8281 14.9563 15.9622 15.9701 14.8983 16.7431L10.0001 10.0013" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
category: 'Performance',
|
||||
title: 'Review individual performance',
|
||||
description: 'Deep dive into specific employee performance data and development opportunities.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0013 12.5C11.3821 12.5 12.5013 11.3807 12.5013 10C12.5013 8.61929 11.3821 7.5 10.0013 7.5C8.62061 7.5 7.50132 8.61929 7.50132 10C7.50132 11.3807 8.62061 12.5 10.0013 12.5Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10.0013 1.66797C14.6037 1.66797 18.3346 5.39893 18.3346 10.0013C18.3346 14.6037 14.6037 18.3346 10.0013 18.3346C5.39893 18.3346 1.66797 14.6037 1.66797 10.0013C1.66797 5.39893 5.39893 1.66797 10.0013 1.66797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
category: 'Culture',
|
||||
title: 'Identify team dynamics',
|
||||
description: 'Understand collaboration patterns, communication effectiveness, and team cohesion.',
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.168 5.83464C14.168 7.67561 12.675 9.16797 10.8346 9.16797C8.99367 9.16797 7.50131 7.67561 7.50131 5.83464C7.50131 3.99367 8.99367 2.5013 10.8346 2.5013C12.675 2.5013 14.168 3.99367 14.168 5.83464Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M10.8346 11.668C7.52292 11.668 4.83594 14.3549 4.83594 17.6666H16.8346C16.8346 14.3549 14.1477 11.668 10.8346 11.668Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M5.83464 9.16797C5.83464 10.5488 4.71536 11.668 3.33464 11.668C1.95393 11.668 0.834635 10.5488 0.834635 9.16797C0.834635 7.78725 1.95393 6.66797 3.33464 6.66797C4.71536 6.66797 5.83464 7.78725 5.83464 9.16797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M3.33594 13.3346C1.49497 13.3346 0.00260794 14.827 0.00260794 16.668H6.66927C6.66927 15.7686 6.35594 14.9346 5.83594 14.2513" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const filteredSuggestions = activeCategory === 'All'
|
||||
? suggestions
|
||||
: suggestions.filter(s => s.category === activeCategory);
|
||||
|
||||
const handleSuggestionClick = (suggestion: any) => {
|
||||
// Handle suggestion click - could pass this up to parent component
|
||||
console.log('Clicked suggestion:', suggestion.title);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] px-4">
|
||||
{/* Welcome Message */}
|
||||
<div className="text-center mb-8 max-w-2xl">
|
||||
<h2 className="text-2xl font-semibold text-Neutrals-NeutralSlate950 mb-3">
|
||||
Welcome to Auditly Chat
|
||||
</h2>
|
||||
<p className="text-Neutrals-NeutralSlate600 text-lg leading-relaxed">
|
||||
Ask me anything about your team's performance, company culture, or organizational insights.
|
||||
I can analyze employee data, generate reports, and provide actionable recommendations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Category Tabs */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{categories.map((category) => (
|
||||
<CategoryTab
|
||||
key={category}
|
||||
label={category}
|
||||
isActive={activeCategory === category}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Suggestion Cards Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl w-full">
|
||||
{filteredSuggestions.map((suggestion, index) => (
|
||||
<SuggestionCard
|
||||
key={index}
|
||||
category={suggestion.category}
|
||||
title={suggestion.title}
|
||||
description={suggestion.description}
|
||||
icon={suggestion.icon}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Additional Help Text */}
|
||||
<div className="mt-8 text-center text-sm text-Neutrals-NeutralSlate500 max-w-xl">
|
||||
<p>
|
||||
You can also upload files, mention specific employees using @, or ask custom questions about your organization.
|
||||
I'll provide insights based on your team's data and industry best practices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatEmptyState;
|
||||
171
components/chat/ChatLayout.tsx
Normal file
171
components/chat/ChatLayout.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useOrg } from '../../contexts/OrgContext';
|
||||
import { Employee } from '../../types';
|
||||
import ChatSidebar from './ChatSidebar';
|
||||
import MessageThread from './MessageThread';
|
||||
import FileUploadInput from './FileUploadInput';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
text: string;
|
||||
isUser: boolean;
|
||||
timestamp: number;
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
interface ChatLayoutProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ChatLayout: React.FC<ChatLayoutProps> = ({ children }) => {
|
||||
const { employees } = useOrg();
|
||||
const [selectedEmployees, setSelectedEmployees] = useState<Employee[]>([]);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
const handleNavigation = (page: string) => {
|
||||
// Handle navigation to different pages
|
||||
console.log('Navigate to:', page);
|
||||
};
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (!inputValue.trim() && uploadedFiles.length === 0) return;
|
||||
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
text: inputValue,
|
||||
isUser: true,
|
||||
timestamp: Date.now(),
|
||||
files: uploadedFiles.length > 0 ? [...uploadedFiles] : undefined
|
||||
};
|
||||
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
setInputValue('');
|
||||
setUploadedFiles([]);
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate AI response
|
||||
setTimeout(() => {
|
||||
const aiMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
text: "I understand you're asking about the employee data. Based on the information provided, I can help analyze the performance metrics and provide insights.\n\nHere are some key findings from your team's data:\n\n• **Performance Trends**: Overall team productivity has increased by 15% this quarter\n• **Cultural Health**: Employee satisfaction scores are above industry average\n• **Areas for Growth**: Communication and cross-team collaboration could be improved\n\nWould you like me to dive deeper into any of these areas?",
|
||||
isUser: false,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
setMessages(prev => [...prev, aiMessage]);
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (index: number) => {
|
||||
setUploadedFiles(prev => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleFilesSelected = (files: File[]) => {
|
||||
// For demo purposes, we'll just add the file names
|
||||
// In a real implementation, you'd upload the files and get URLs back
|
||||
const fileNames = files.map(file => file.name);
|
||||
setUploadedFiles(prev => [...prev, ...fileNames]);
|
||||
};
|
||||
|
||||
const hasMessages = messages.length > 0;
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen bg-Neutrals-NeutralSlate0 inline-flex overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<ChatSidebar currentPage="chat" onNavigate={handleNavigation} />
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Header with Employee Selection */}
|
||||
<div className="px-6 py-4 bg-Neutrals-NeutralSlate0 border-b border-Neutrals-NeutralSlate200 flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-xl font-semibold text-Neutrals-NeutralSlate950">Chat</h1>
|
||||
{selectedEmployees.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-Neutrals-NeutralSlate500">Analyzing:</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{selectedEmployees.slice(0, 3).map((emp, index) => (
|
||||
<div key={emp.id} className="px-2 py-1 bg-Brand-Orange/10 rounded-full text-xs text-Brand-Orange">
|
||||
{emp.name}
|
||||
</div>
|
||||
))}
|
||||
{selectedEmployees.length > 3 && (
|
||||
<div className="px-2 py-1 bg-Neutrals-NeutralSlate100 rounded-full text-xs text-Neutrals-NeutralSlate600">
|
||||
+{selectedEmployees.length - 3} more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages Area */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
{hasMessages ? (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<MessageThread
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="px-6 py-4 bg-Neutrals-NeutralSlate0 border-t border-Neutrals-NeutralSlate200">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-end gap-3">
|
||||
<FileUploadInput
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder="Ask about your team's performance, culture, or any insights..."
|
||||
disabled={isLoading}
|
||||
uploadedFiles={uploadedFiles}
|
||||
onRemoveFile={handleRemoveFile}
|
||||
onFilesSelected={handleFilesSelected}
|
||||
/>
|
||||
|
||||
{/* Send Button */}
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
disabled={!inputValue.trim() && uploadedFiles.length === 0}
|
||||
className="px-4 py-3 bg-Brand-Orange text-white rounded-xl hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex-shrink-0"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.3346 1.66797L9.16797 10.8346M18.3346 1.66797L12.5013 18.3346L9.16797 10.8346M18.3346 1.66797L1.66797 7.5013L9.16797 10.8346" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatLayout;
|
||||
263
components/chat/ChatSidebar.tsx
Normal file
263
components/chat/ChatSidebar.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import React from 'react';
|
||||
import { useOrg } from '../../contexts/OrgContext';
|
||||
|
||||
interface NavItemProps {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
isActive?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const NavItem: React.FC<NavItemProps> = ({ icon, label, isActive, onClick }) => (
|
||||
<div
|
||||
className={`w-60 px-4 py-2.5 ${isActive ? 'bg-Neutrals-NeutralSlate100' : ''} rounded-[34px] inline-flex justify-start items-center gap-2 cursor-pointer hover:bg-Neutrals-NeutralSlate50 transition-colors`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon}
|
||||
<div className={`justify-start text-sm font-medium font-['Inter'] leading-tight ${isActive ? 'text-Neutrals-NeutralSlate950' : 'text-Neutrals-NeutralSlate500'}`}>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ChatSidebarProps {
|
||||
currentPage?: string;
|
||||
onNavigate?: (page: string) => void;
|
||||
}
|
||||
|
||||
const ChatSidebar: React.FC<ChatSidebarProps> = ({ currentPage = 'chat', onNavigate }) => {
|
||||
const { org } = useOrg();
|
||||
|
||||
const handleNavigation = (page: string) => {
|
||||
if (onNavigate) {
|
||||
onNavigate(page);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
|
||||
{/* Company Selection */}
|
||||
<div className="self-stretch flex flex-col justify-start items-start gap-5">
|
||||
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
|
||||
<div className="flex-1 flex justify-start items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
|
||||
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
|
||||
<div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
|
||||
<div className="left-[8.80px] top-[7.20px] absolute">
|
||||
{/* Company Icon SVG */}
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_1042_694)">
|
||||
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M4.34342 10.6855C4.66998 11.0162 4.66998 11.5524 4.34341 11.8831L4.32669 11.9C4.00012 12.2307 3.47065 12.2307 3.14409 11.9C2.81753 11.5693 2.81753 11.0331 3.1441 10.7024L3.16082 10.6855C3.48739 10.3548 4.01686 10.3548 4.34342 10.6855Z" fill="url(#paint0_linear_1042_694)" />
|
||||
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M8.27545 10.9405C8.60142 11.2718 8.60046 11.808 8.27331 12.1381L5.95697 14.4752C5.62981 14.8053 5.10035 14.8043 4.77437 14.473C4.4484 14.1417 4.44936 13.6056 4.77651 13.2755L7.09285 10.9383C7.42001 10.6082 7.94947 10.6092 8.27545 10.9405Z" fill="url(#paint1_linear_1042_694)" />
|
||||
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M11.4179 14.9631C11.6741 14.574 12.1932 14.4688 12.5775 14.7282L12.6277 14.7621C13.012 15.0215 13.1158 15.5473 12.8596 15.9364C12.6034 16.3255 12.0842 16.4307 11.7 16.1713L11.6498 16.1374C11.2655 15.878 11.1617 15.3522 11.4179 14.9631Z" fill="url(#paint2_linear_1042_694)" />
|
||||
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M16.9376 10.6347C17.2642 10.9654 17.2642 11.5016 16.9376 11.8323L15.8003 12.9839C15.4738 13.3146 14.9443 13.3146 14.6177 12.9839C14.2912 12.6532 14.2912 12.1171 14.6177 11.7864L15.755 10.6347C16.0816 10.304 16.611 10.304 16.9376 10.6347Z" fill="url(#paint3_linear_1042_694)" />
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M16.9544 6.37693C17.2809 6.70762 17.2809 7.24378 16.9544 7.57447L8.55033 16.0847C8.22376 16.4154 7.69429 16.4154 7.36773 16.0847C7.04116 15.754 7.04116 15.2179 7.36773 14.8872L15.7718 6.37693C16.0983 6.04623 16.6278 6.04623 16.9544 6.37693Z" fill="url(#paint4_linear_1042_694)" />
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M15.3649 3.75974C15.6915 4.09043 15.6915 4.62659 15.3649 4.95728L10.5315 9.85174C10.205 10.1824 9.67549 10.1824 9.34893 9.85174C9.02236 9.52104 9.02236 8.98489 9.34893 8.65419L14.1823 3.75974C14.5089 3.42905 15.0383 3.42905 15.3649 3.75974Z" fill="url(#paint5_linear_1042_694)" />
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12.8146 2.09918C13.1414 2.42965 13.1417 2.96581 12.8154 3.29672L6.60224 9.59685C6.27589 9.92777 5.74642 9.92813 5.41964 9.59766C5.09285 9.26719 5.0925 8.73103 5.41884 8.40011L11.632 2.09998C11.9583 1.76907 12.4878 1.76871 12.8146 2.09918Z" fill="url(#paint6_linear_1042_694)" />
|
||||
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M6.66127 4.11624C6.98727 4.4475 6.98636 4.98366 6.65923 5.31378L4.22582 7.76948C3.89869 8.0996 3.36923 8.09868 3.04322 7.76741C2.71722 7.43615 2.71813 6.9 3.04526 6.56987L5.47867 4.11418C5.8058 3.78405 6.33526 3.78498 6.66127 4.11624Z" fill="url(#paint7_linear_1042_694)" />
|
||||
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M8.15116 1.66406C8.613 1.66406 8.98739 2.04318 8.98739 2.51085V2.59553C8.98739 3.0632 8.613 3.44232 8.15116 3.44232C7.68933 3.44232 7.31494 3.0632 7.31494 2.59553V2.51085C7.31494 2.04318 7.68933 1.66406 8.15116 1.66406Z" fill="url(#paint8_linear_1042_694)" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1042_694" x="0.399316" y="-0.400781" width="19.2006" height="22.4016" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feMorphology radius="1.2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_1042_694" />
|
||||
<feOffset dy="1.8" />
|
||||
<feGaussianBlur stdDeviation="1.8" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0.1 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1042_694" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1042_694" result="shape" />
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1042_694" x1="3.74376" y1="10.4375" x2="3.74376" y2="12.148" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1042_694" x1="6.52491" y1="10.6914" x2="6.52491" y2="14.7221" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_1042_694" x1="12.1387" y1="14.5859" x2="12.1387" y2="16.3136" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1042_694" x1="15.7777" y1="10.3867" x2="15.7777" y2="13.2319" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_1042_694" x1="12.161" y1="6.12891" x2="12.161" y2="16.3327" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_1042_694" x1="12.3569" y1="3.51172" x2="12.3569" y2="10.0998" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_1042_694" x1="9.11711" y1="1.85156" x2="9.11711" y2="9.84527" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_1042_694" x1="4.85224" y1="3.86719" x2="4.85224" y2="8.01647" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_1042_694" x1="8.15117" y1="1.66406" x2="8.15117" y2="3.44232" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="white" stopOpacity="0.8" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
|
||||
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">
|
||||
{org?.name || 'Zitlac Media'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.83325 12.4987L9.99992 16.6654L14.1666 12.4987M5.83325 7.4987L9.99992 3.33203L14.1666 7.4987" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Menu */}
|
||||
<div className="self-stretch flex flex-col justify-start items-start gap-5">
|
||||
<div className="self-stretch flex flex-col justify-start items-start gap-1.5">
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 17.5016V11.3349C7.5 10.8682 7.5 10.6348 7.59083 10.4566C7.67072 10.2998 7.79821 10.1723 7.95501 10.0924C8.13327 10.0016 8.36662 10.0016 8.83333 10.0016H11.1667C11.6334 10.0016 11.8667 10.0016 12.045 10.0924C12.2018 10.1723 12.3293 10.2998 12.4092 10.4566C12.5 10.6348 12.5 10.8682 12.5 11.3349V17.5016M9.18141 2.30492L3.52949 6.70086C3.15168 6.99471 2.96278 7.14163 2.82669 7.32563C2.70614 7.48862 2.61633 7.67224 2.56169 7.86746C2.5 8.08785 2.5 8.32717 2.5 8.8058V14.8349C2.5 15.7683 2.5 16.235 2.68166 16.5916C2.84144 16.9052 3.09641 17.1601 3.41002 17.3199C3.76654 17.5016 4.23325 17.5016 5.16667 17.5016H14.8333C15.7668 17.5016 16.2335 17.5016 16.59 17.3199C16.9036 17.1601 17.1586 16.9052 17.3183 16.5916C17.5 16.235 17.5 15.7683 17.5 14.8349V8.8058C17.5 8.32717 17.5 8.08785 17.4383 7.86746C17.3837 7.67224 17.2939 7.48862 17.1733 7.32563C17.0372 7.14163 16.8483 6.99471 16.4705 6.70086L10.8186 2.30492C10.5258 2.07721 10.3794 1.96335 10.2178 1.91959C10.0752 1.88097 9.92484 1.88097 9.78221 1.91959C9.62057 1.96335 9.47418 2.07721 9.18141 2.30492Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
}
|
||||
label="Company Wiki"
|
||||
isActive={currentPage === 'wiki'}
|
||||
onClick={() => handleNavigation('wiki')}
|
||||
/>
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6666 9.16797H6.66659M8.33325 12.5013H6.66659M13.3333 5.83464H6.66659M16.6666 5.66797V14.3346C16.6666 15.7348 16.6666 16.4348 16.3941 16.9696C16.1544 17.44 15.772 17.8225 15.3016 18.0622C14.7668 18.3346 14.0667 18.3346 12.6666 18.3346H7.33325C5.93312 18.3346 5.23306 18.3346 4.69828 18.0622C4.22787 17.8225 3.84542 17.44 3.60574 16.9696C3.33325 16.4348 3.33325 15.7348 3.33325 14.3346V5.66797C3.33325 4.26784 3.33325 3.56777 3.60574 3.03299C3.84542 2.56259 4.22787 2.18014 4.69828 1.94045C5.23306 1.66797 5.93312 1.66797 7.33325 1.66797H12.6666C14.0667 1.66797 14.7668 1.66797 15.3016 1.94045C15.772 2.18014 16.1544 2.56259 16.3941 3.03299C16.6666 3.56777 16.6666 4.26784 16.6666 5.66797Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
}
|
||||
label="Submissions"
|
||||
isActive={currentPage === 'submissions'}
|
||||
onClick={() => handleNavigation('submissions')}
|
||||
/>
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_1042_720)">
|
||||
<path d="M10.0001 1.66797C11.0944 1.66797 12.1781 1.88352 13.1891 2.30231C14.2002 2.7211 15.1188 3.33493 15.8926 4.10875C16.6665 4.88257 17.2803 5.80123 17.6991 6.81228C18.1179 7.82332 18.3334 8.90696 18.3334 10.0013M10.0001 1.66797V10.0013M10.0001 1.66797C5.39771 1.66797 1.66675 5.39893 1.66675 10.0013C1.66675 14.6037 5.39771 18.3346 10.0001 18.3346C14.6025 18.3346 18.3334 14.6037 18.3334 10.0013M10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013M18.3334 10.0013L10.0001 10.0013M18.3334 10.0013C18.3334 11.3164 18.0222 12.6128 17.4251 13.7846C16.8281 14.9563 15.9622 15.9701 14.8983 16.7431L10.0001 10.0013" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1042_720">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
label="Reports"
|
||||
isActive={currentPage === 'reports'}
|
||||
onClick={() => handleNavigation('reports')}
|
||||
/>
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.4996 9.58333C17.4996 13.4953 14.3283 16.6667 10.4163 16.6667C9.51896 16.6667 8.66061 16.4998 7.87057 16.1954C7.72612 16.1398 7.6539 16.112 7.59647 16.0987C7.53998 16.0857 7.49908 16.0803 7.44116 16.0781C7.38226 16.0758 7.31764 16.0825 7.18841 16.0958L2.92089 16.537C2.51402 16.579 2.31059 16.6001 2.19058 16.5269C2.08606 16.4631 2.01487 16.3566 1.99592 16.2356C1.97416 16.0968 2.07138 15.9168 2.2658 15.557L3.62885 13.034C3.7411 12.8262 3.79723 12.7223 3.82265 12.6225C3.84776 12.5238 3.85383 12.4527 3.8458 12.3512C3.83766 12.2484 3.79258 12.1147 3.70241 11.8472C3.46281 11.1363 3.33294 10.375 3.33294 9.58333C3.33294 5.67132 6.50426 2.5 10.4163 2.5C14.3283 2.5 17.4996 5.67132 17.4996 9.58333Z" stroke="var(--Brand-Orange, #3399FF)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
}
|
||||
label="Chat"
|
||||
isActive={currentPage === 'chat'}
|
||||
onClick={() => handleNavigation('chat')}
|
||||
/>
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_1042_728)">
|
||||
<path d="M7.57508 7.5013C7.771 6.94436 8.15771 6.47472 8.66671 6.17558C9.17571 5.87643 9.77416 5.76708 10.3561 5.8669C10.938 5.96671 11.4658 6.26924 11.846 6.72091C12.2262 7.17258 12.4343 7.74424 12.4334 8.33464C12.4334 10.0013 9.93342 10.8346 9.93342 10.8346M10.0001 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6025 18.3346 10.0001 18.3346C5.39771 18.3346 1.66675 14.6037 1.66675 10.0013C1.66675 5.39893 5.39771 1.66797 10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1042_728">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
label="Help"
|
||||
isActive={currentPage === 'help'}
|
||||
onClick={() => handleNavigation('help')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Section with Settings and Company Report Builder */}
|
||||
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
||||
<NavItem
|
||||
icon={
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_1042_733)">
|
||||
<path d="M10.0001 12.5013C11.3808 12.5013 12.5001 11.382 12.5001 10.0013C12.5001 8.62059 11.3808 7.5013 10.0001 7.5013C8.61937 7.5013 7.50008 8.62059 7.50008 10.0013C7.50008 11.382 8.61937 12.5013 10.0001 12.5013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M15.6061 12.274C15.5053 12.5025 15.4752 12.756 15.5198 13.0017C15.5643 13.2475 15.6815 13.4743 15.8561 13.6528L15.9016 13.6983C16.0425 13.839 16.1542 14.0061 16.2305 14.19C16.3067 14.374 16.346 14.5711 16.346 14.7702C16.346 14.9694 16.3067 15.1665 16.2305 15.3505C16.1542 15.5344 16.0425 15.7015 15.9016 15.8422C15.7609 15.9831 15.5938 16.0948 15.4098 16.1711C15.2259 16.2473 15.0287 16.2866 14.8296 16.2866C14.6305 16.2866 14.4334 16.2473 14.2494 16.1711C14.0655 16.0948 13.8984 15.9831 13.7577 15.8422L13.7122 15.7968C13.5337 15.6221 13.3069 15.505 13.0611 15.4604C12.8154 15.4158 12.5619 15.4459 12.3334 15.5468C12.1093 15.6428 11.9183 15.8022 11.7836 16.0055C11.649 16.2087 11.5768 16.4469 11.5758 16.6907V16.8195C11.5758 17.2213 11.4162 17.6067 11.1321 17.8909C10.8479 18.175 10.4625 18.3346 10.0607 18.3346C9.65884 18.3346 9.27346 18.175 8.98931 17.8909C8.70517 17.6067 8.54554 17.2213 8.54554 16.8195V16.7513C8.53967 16.5005 8.45851 16.2574 8.31259 16.0533C8.16667 15.8493 7.96276 15.6939 7.72735 15.6074C7.49886 15.5065 7.24539 15.4764 6.99964 15.521C6.75388 15.5656 6.52711 15.6827 6.34857 15.8574L6.30311 15.9028C6.1624 16.0437 5.99529 16.1554 5.81135 16.2317C5.62742 16.3079 5.43026 16.3472 5.23114 16.3472C5.03203 16.3472 4.83487 16.3079 4.65093 16.2317C4.46699 16.1554 4.29989 16.0437 4.15917 15.9028C4.0183 15.7621 3.90654 15.595 3.83029 15.4111C3.75405 15.2271 3.7148 15.03 3.7148 14.8308C3.7148 14.6317 3.75405 14.4346 3.83029 14.2506C3.90654 14.0667 4.0183 13.8996 4.15917 13.7589L4.20463 13.7134C4.37928 13.5349 4.49643 13.3081 4.54099 13.0624C4.58555 12.8166 4.55547 12.5631 4.45463 12.3346C4.35859 12.1106 4.19914 11.9195 3.99589 11.7849C3.79264 11.6503 3.55447 11.578 3.31069 11.5771H3.1819C2.78006 11.5771 2.39467 11.4174 2.11053 11.1333C1.82638 10.8491 1.66675 10.4638 1.66675 10.0619C1.66675 9.66007 1.82638 9.27468 2.11053 8.99053C2.39467 8.70639 2.78006 8.54676 3.1819 8.54676H3.25008C3.50083 8.54089 3.74402 8.45973 3.94804 8.31381C4.15205 8.1679 4.30744 7.96398 4.39402 7.72858C4.49487 7.50008 4.52495 7.24661 4.48039 7.00086C4.43583 6.7551 4.31867 6.52833 4.14402 6.34979L4.09857 6.30433C3.95769 6.16362 3.84594 5.99651 3.76969 5.81258C3.69344 5.62864 3.65419 5.43148 3.65419 5.23236C3.65419 5.03325 3.69344 4.83609 3.76969 4.65215C3.84594 4.46821 3.95769 4.30111 4.09857 4.16039C4.23928 4.01952 4.40639 3.90776 4.59032 3.83151C4.77426 3.75527 4.97142 3.71602 5.17054 3.71602C5.36965 3.71602 5.56681 3.75527 5.75075 3.83151C5.93469 3.90776 6.10179 4.01952 6.24251 4.16039L6.28796 4.20585C6.46651 4.3805 6.69328 4.49765 6.93903 4.54221C7.18478 4.58677 7.43825 4.55669 7.66675 4.45585H7.72735C7.95142 4.35982 8.14252 4.20036 8.27712 3.99711C8.41172 3.79386 8.48396 3.55569 8.48493 3.31191V3.18312C8.48493 2.78128 8.64456 2.39589 8.92871 2.11175C9.21285 1.8276 9.59824 1.66797 10.0001 1.66797C10.4019 1.66797 10.7873 1.8276 11.0715 2.11175C11.3556 2.39589 11.5152 2.78128 11.5152 3.18312V3.2513C11.5162 3.49508 11.5884 3.73325 11.723 3.9365C11.8576 4.13975 12.0487 4.29921 12.2728 4.39524C12.5013 4.49609 12.7548 4.52617 13.0005 4.48161C13.2463 4.43705 13.4731 4.31989 13.6516 4.14524L13.6971 4.09979C13.8378 3.95891 14.0049 3.84716 14.1888 3.77091C14.3727 3.69466 14.5699 3.65541 14.769 3.65541C14.9681 3.65541 15.1653 3.69466 15.3492 3.77091C15.5332 3.84716 15.7003 3.95891 15.841 4.09979C15.9819 4.2405 16.0936 4.40761 16.1699 4.59154C16.2461 4.77548 16.2854 4.97264 16.2854 5.17176C16.2854 5.37087 16.2461 5.56803 16.1699 5.75197C16.0936 5.93591 15.9819 6.10301 15.841 6.24373L15.7955 6.28918C15.6209 6.46773 15.5037 6.6945 15.4592 6.94025C15.4146 7.186 15.4447 7.43947 15.5455 7.66797V7.72858C15.6416 7.95264 15.801 8.14374 16.0043 8.27834C16.2075 8.41295 16.4457 8.48518 16.6895 8.48615H16.8183C17.2201 8.48615 17.6055 8.64578 17.8896 8.92993C18.1738 9.21407 18.3334 9.59946 18.3334 10.0013C18.3334 10.4031 18.1738 10.7885 17.8896 11.0727C17.6055 11.3568 17.2201 11.5165 16.8183 11.5165H16.7501C16.5063 11.5174 16.2681 11.5897 16.0649 11.7243C15.8616 11.8589 15.7022 12.05 15.6061 12.274Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1042_733">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
}
|
||||
label="Settings"
|
||||
isActive={currentPage === 'settings'}
|
||||
onClick={() => handleNavigation('settings')}
|
||||
/>
|
||||
|
||||
{/* Company Report Builder Card */}
|
||||
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
|
||||
<div className="self-stretch h-24 relative">
|
||||
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
|
||||
<div className="w-60 p-3 left-[18.12px] top-[42.52px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
|
||||
<div className="w-60 p-3 left-[31.44px] top-[22px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
|
||||
</div>
|
||||
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
|
||||
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">
|
||||
Build {org?.name || '[Company]'}'s Report
|
||||
</div>
|
||||
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">
|
||||
Share this form with your team members to capture valuable info about your company to train Auditly.
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
|
||||
<div className="self-stretch inline-flex justify-start items-start gap-2">
|
||||
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
|
||||
<div className="px-1 flex justify-center items-center">
|
||||
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99992 3.33203V12.6654M3.33325 7.9987H12.6666" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
|
||||
<div className="relative">
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.97179 12.2442L8.02898 13.1871C6.72723 14.4888 4.61668 14.4888 3.31493 13.1871C2.01319 11.8853 2.01319 9.77476 3.31493 8.47301L4.25774 7.5302M12.743 8.47301L13.6858 7.5302C14.9876 6.22845 14.9876 4.1179 13.6858 2.81615C12.3841 1.51441 10.2735 1.51441 8.97179 2.81615L8.02898 3.75896M6.16705 10.3349L10.8337 5.66826" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="px-1 flex justify-center items-center">
|
||||
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Copy</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatSidebar;
|
||||
244
components/chat/FileUploadInput.tsx
Normal file
244
components/chat/FileUploadInput.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
|
||||
interface FileUploadPreviewProps {
|
||||
files: string[];
|
||||
onRemoveFile: (index: number) => void;
|
||||
}
|
||||
|
||||
const FileUploadPreview: React.FC<FileUploadPreviewProps> = ({ files, onRemoveFile }) => {
|
||||
if (files.length === 0) return null;
|
||||
|
||||
const getFileIcon = (fileName: string) => {
|
||||
const extension = fileName.split('.').pop()?.toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
case 'pdf':
|
||||
return (
|
||||
<div className="w-6 h-6 bg-red-500 rounded flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">P</span>
|
||||
</div>
|
||||
);
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return (
|
||||
<div className="w-6 h-6 bg-blue-500 rounded flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">W</span>
|
||||
</div>
|
||||
);
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return (
|
||||
<div className="w-6 h-6 bg-green-500 rounded flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">E</span>
|
||||
</div>
|
||||
);
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
return (
|
||||
<div className="w-6 h-6 bg-purple-500 rounded flex items-center justify-center">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 9L3.5 6.5L5 8L8.5 4.5L11 7M1 1H11V11H1V1Z" stroke="white" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="w-6 h-6 bg-gray-500 rounded flex items-center justify-center">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 1H2C1.44772 1 1 1.44772 1 2V10C1 10.5523 1.44772 11 2 11H10C10.5523 11 11 10.5523 11 10V5M7 1L11 5M7 1V5H11" stroke="white" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-3 flex flex-wrap gap-2">
|
||||
{files.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="inline-flex items-center gap-2 px-3 py-2 bg-Neutrals-NeutralSlate100 rounded-lg hover:bg-Neutrals-NeutralSlate150 transition-colors group"
|
||||
>
|
||||
{getFileIcon(file)}
|
||||
<span className="text-sm text-Neutrals-NeutralSlate700 max-w-[150px] truncate">{file}</span>
|
||||
<button
|
||||
onClick={() => onRemoveFile(index)}
|
||||
className="w-5 h-5 text-Neutrals-NeutralSlate400 hover:text-red-500 hover:bg-red-50 rounded transition-colors flex items-center justify-center"
|
||||
title="Remove file"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 3L3 9M3 3L9 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface FileUploadDropzoneProps {
|
||||
onFilesSelected: (files: File[]) => void;
|
||||
children: React.ReactNode;
|
||||
accept?: string;
|
||||
multiple?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const FileUploadDropzone: React.FC<FileUploadDropzoneProps> = ({
|
||||
onFilesSelected,
|
||||
children,
|
||||
accept = "*/*",
|
||||
multiple = true,
|
||||
disabled = false
|
||||
}) => {
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (!disabled) {
|
||||
setIsDragOver(true);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
}, []);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragOver(false);
|
||||
|
||||
if (disabled) return;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
if (files.length > 0) {
|
||||
onFilesSelected(files);
|
||||
}
|
||||
}, [onFilesSelected, disabled]);
|
||||
|
||||
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
if (files.length > 0) {
|
||||
onFilesSelected(files);
|
||||
}
|
||||
// Reset input value to allow selecting the same file again
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}, [onFilesSelected]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!disabled && fileInputRef.current) {
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={handleClick}
|
||||
className={`
|
||||
relative cursor-pointer transition-all
|
||||
${isDragOver ? 'opacity-80' : ''}
|
||||
${disabled ? 'cursor-not-allowed opacity-50' : ''}
|
||||
`}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={accept}
|
||||
multiple={multiple}
|
||||
onChange={handleFileSelect}
|
||||
className="hidden"
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
{/* Drag overlay */}
|
||||
{isDragOver && (
|
||||
<div className="absolute inset-0 bg-Brand-Orange/10 border-2 border-dashed border-Brand-Orange rounded-xl flex items-center justify-center">
|
||||
<div className="text-Brand-Orange font-medium">Drop files here</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface FileUploadInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
uploadedFiles: string[];
|
||||
onRemoveFile: (index: number) => void;
|
||||
onFilesSelected: (files: File[]) => void;
|
||||
}
|
||||
|
||||
const FileUploadInput: React.FC<FileUploadInputProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
placeholder = "Ask about your team's performance, culture, or any insights...",
|
||||
disabled = false,
|
||||
uploadedFiles,
|
||||
onRemoveFile,
|
||||
onFilesSelected
|
||||
}) => {
|
||||
const handleFilesSelected = (files: File[]) => {
|
||||
// For demo purposes, we'll just add the file names
|
||||
// In a real implementation, you'd upload the files and get URLs back
|
||||
const fileNames = files.map(file => file.name);
|
||||
onFilesSelected(files);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* File Upload Preview */}
|
||||
<FileUploadPreview files={uploadedFiles} onRemoveFile={onRemoveFile} />
|
||||
|
||||
{/* Input Field with File Upload */}
|
||||
<FileUploadDropzone
|
||||
onFilesSelected={handleFilesSelected}
|
||||
disabled={disabled}
|
||||
accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.jpg,.jpeg,.png,.gif"
|
||||
>
|
||||
<div className="relative flex items-end gap-3">
|
||||
<div className="flex-1 relative">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className="w-full min-h-[44px] max-h-32 px-4 py-3 pr-12 border border-Neutrals-NeutralSlate200 rounded-xl resize-none focus:outline-none focus:border-Brand-Orange focus:ring-1 focus:ring-Brand-Orange disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
rows={1}
|
||||
/>
|
||||
|
||||
{/* File Upload Button */}
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className="absolute right-3 top-3 w-6 h-6 text-Neutrals-NeutralSlate400 hover:text-Brand-Orange disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
title="Upload files"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15M17 8L12 3M12 3L7 8M12 3V15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</FileUploadDropzone>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileUploadInput;
|
||||
export { FileUploadPreview, FileUploadDropzone };
|
||||
118
components/chat/MessageThread.tsx
Normal file
118
components/chat/MessageThread.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
text: string;
|
||||
isUser: boolean;
|
||||
timestamp: number;
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
interface MessageBubbleProps {
|
||||
message: Message;
|
||||
}
|
||||
|
||||
const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
|
||||
const formatTime = (timestamp: number) => {
|
||||
return new Date(timestamp).toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
if (message.isUser) {
|
||||
return (
|
||||
<div className="flex justify-end mb-4">
|
||||
<div className="max-w-[70%] flex flex-col items-end">
|
||||
<div className="bg-Brand-Orange text-white px-4 py-3 rounded-2xl rounded-br-md">
|
||||
{message.files && message.files.length > 0 && (
|
||||
<div className="mb-2 flex flex-wrap gap-2">
|
||||
{message.files.map((file, index) => (
|
||||
<div key={index} className="px-2 py-1 bg-white/20 rounded text-xs">
|
||||
📎 {file}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm leading-relaxed">{message.text}</div>
|
||||
</div>
|
||||
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
|
||||
{formatTime(message.timestamp)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-start mb-4">
|
||||
<div className="max-w-[85%] flex items-start gap-3">
|
||||
{/* AI Avatar */}
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate950 px-4 py-3 rounded-2xl rounded-bl-md">
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap">{message.text}</div>
|
||||
</div>
|
||||
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
|
||||
AI • {formatTime(message.timestamp)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface LoadingIndicatorProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ className = '' }) => (
|
||||
<div className={`flex justify-start mb-4 ${className}`}>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* AI Avatar */}
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="bg-Neutrals-NeutralSlate100 px-4 py-3 rounded-2xl rounded-bl-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
||||
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
||||
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface MessageThreadProps {
|
||||
messages: Message[];
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MessageThread: React.FC<MessageThreadProps> = ({
|
||||
messages,
|
||||
isLoading = false,
|
||||
className = ''
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex flex-col ${className}`}>
|
||||
{messages.map((message) => (
|
||||
<MessageBubble key={message.id} message={message} />
|
||||
))}
|
||||
|
||||
{isLoading && <LoadingIndicator />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageThread;
|
||||
export { MessageBubble, LoadingIndicator };
|
||||
5
components/chat/index.ts
Normal file
5
components/chat/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { default as ChatSidebar } from './ChatSidebar';
|
||||
export { default as ChatLayout } from './ChatLayout';
|
||||
export { default as ChatEmptyState } from './ChatEmptyState';
|
||||
export { default as MessageThread } from './MessageThread';
|
||||
export { default as FileUploadInput } from './FileUploadInput';
|
||||
Reference in New Issue
Block a user