- 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
178 lines
9.9 KiB
TypeScript
178 lines
9.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
|
|
interface Employee {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
avatar?: string;
|
|
}
|
|
|
|
interface InviteMultipleEmployeesModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onInviteSelected: (employees: Employee[]) => void;
|
|
suggestedEmployees?: Employee[];
|
|
}
|
|
|
|
const defaultSuggestedEmployees: Employee[] = [
|
|
{ id: '1', name: 'John Smith', email: 'john@company.com' },
|
|
{ id: '2', name: 'Sarah Johnson', email: 'sarah@company.com' },
|
|
{ id: '3', name: 'Mike Chen', email: 'mike@company.com' },
|
|
{ id: '4', name: 'Emily Davis', email: 'emily@company.com' },
|
|
{ id: '5', name: 'Alex Rodriguez', email: 'alex@company.com' },
|
|
];
|
|
|
|
export const InviteMultipleEmployeesModal: React.FC<InviteMultipleEmployeesModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
onInviteSelected,
|
|
suggestedEmployees = defaultSuggestedEmployees
|
|
}) => {
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedEmployees, setSelectedEmployees] = useState<Employee[]>([]);
|
|
const [showDropdown, setShowDropdown] = useState(false);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const filteredEmployees = suggestedEmployees.filter(emp =>
|
|
(emp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
emp.email.toLowerCase().includes(searchTerm.toLowerCase())) &&
|
|
!selectedEmployees.find(selected => selected.id === emp.id)
|
|
);
|
|
|
|
const handleEmployeeSelect = (employee: Employee) => {
|
|
setSelectedEmployees(prev => [...prev, employee]);
|
|
setSearchTerm('');
|
|
setShowDropdown(false);
|
|
};
|
|
|
|
const handleEmployeeRemove = (employeeId: string) => {
|
|
setSelectedEmployees(prev => prev.filter(emp => emp.id !== employeeId));
|
|
};
|
|
|
|
const handleInvite = () => {
|
|
if (selectedEmployees.length > 0) {
|
|
onInviteSelected(selectedEmployees);
|
|
setSelectedEmployees([]);
|
|
setSearchTerm('');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div className="w-[480px] bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate900 rounded-3xl shadow-[0px_25px_50px_-12px_rgba(0,0,0,0.25)] flex flex-col justify-start items-start overflow-hidden">
|
|
{/* Header */}
|
|
<div className="self-stretch p-6 inline-flex justify-between items-center">
|
|
<div className="flex justify-start items-center gap-2.5">
|
|
<div className="justify-start text-Text-Dark-950 dark:text-Neutrals-NeutralSlate50 text-lg font-semibold font-['Inter'] leading-7">
|
|
Invite multiple employees
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="w-6 h-6 flex justify-center items-center hover:bg-Text-Gray-100 dark:hover:bg-Neutrals-NeutralSlate700 rounded"
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
<path d="M13 1L1 13M1 1L13 13" stroke="#666D80" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Search Input with Dropdown */}
|
|
<div className="self-stretch px-6 flex flex-col justify-start items-start gap-4">
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-1 relative">
|
|
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">
|
|
Search employees
|
|
</div>
|
|
<div className="self-stretch h-10 px-3 py-2 bg-Neutrals-NeutralSlate0 rounded-lg border border-Outline-Outline-Gray-300 inline-flex justify-start items-center gap-2">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-Text-Gray-400">
|
|
<path d="M15 15L11.15 11.15M12.6667 7.33333C12.6667 10.2789 10.2789 12.6667 7.33333 12.6667C4.38781 12.6667 2 10.2789 2 7.33333C2 4.38781 4.38781 2 7.33333 2C10.2789 2 12.6667 4.38781 12.6667 7.33333Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
<input
|
|
type="text"
|
|
value={searchTerm}
|
|
onChange={(e) => {
|
|
setSearchTerm(e.target.value);
|
|
setShowDropdown(e.target.value.length > 0);
|
|
}}
|
|
onFocus={() => setShowDropdown(searchTerm.length > 0)}
|
|
placeholder="Type name or email to search..."
|
|
className="flex-1 text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight bg-transparent outline-none placeholder:text-Text-Gray-500"
|
|
/>
|
|
</div>
|
|
|
|
{/* Dropdown */}
|
|
{showDropdown && filteredEmployees.length > 0 && (
|
|
<div className="absolute top-full left-0 right-0 mt-1 bg-Neutrals-NeutralSlate0 border border-Outline-Outline-Gray-200 rounded-lg shadow-lg z-10 max-h-48 overflow-y-auto">
|
|
{filteredEmployees.map((employee) => (
|
|
<button
|
|
key={employee.id}
|
|
onClick={() => handleEmployeeSelect(employee)}
|
|
className="w-full px-3 py-2 text-left hover:bg-Text-Gray-50 flex items-center gap-3 border-b border-Text-Gray-100 last:border-b-0"
|
|
>
|
|
<div className="w-8 h-8 bg-Brand-Orange rounded-full flex items-center justify-center text-white text-sm font-medium">
|
|
{employee.name.charAt(0)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="text-sm font-medium text-Neutrals-NeutralSlate950">{employee.name}</div>
|
|
<div className="text-xs text-Text-Gray-500">{employee.email}</div>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Selected Employees */}
|
|
{selectedEmployees.length > 0 && (
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-2">
|
|
<div className="text-sm font-medium text-Neutrals-NeutralSlate950">
|
|
Selected ({selectedEmployees.length})
|
|
</div>
|
|
<div className="self-stretch flex flex-wrap gap-2">
|
|
{selectedEmployees.map((employee) => (
|
|
<div
|
|
key={employee.id}
|
|
className="px-3 py-1.5 bg-Brand-Orange bg-opacity-10 rounded-full flex items-center gap-2"
|
|
>
|
|
<div className="w-5 h-5 bg-Brand-Orange rounded-full flex items-center justify-center text-white text-xs font-medium">
|
|
{employee.name.charAt(0)}
|
|
</div>
|
|
<span className="text-sm text-Neutrals-NeutralSlate950">{employee.name}</span>
|
|
<button
|
|
onClick={() => handleEmployeeRemove(employee.id)}
|
|
className="w-4 h-4 flex items-center justify-center hover:bg-Brand-Orange hover:bg-opacity-20 rounded-full"
|
|
>
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
<path d="M9 3L3 9M3 3L9 9" stroke="#666D80" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="self-stretch p-6 inline-flex justify-end items-center">
|
|
<div className="flex justify-start items-start gap-3">
|
|
<button
|
|
onClick={onClose}
|
|
className="px-4 py-2 bg-Neutrals-NeutralSlate0 rounded-lg border border-Outline-Outline-Gray-300 text-Neutrals-NeutralSlate700 text-sm font-medium font-['Inter'] leading-tight hover:bg-Text-Gray-50"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleInvite}
|
|
disabled={selectedEmployees.length === 0}
|
|
className="px-4 py-2 bg-Brand-Orange rounded-lg text-Neutrals-NeutralSlate0 text-sm font-medium font-['Inter'] leading-tight hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Send Invites ({selectedEmployees.length})
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |