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:
Ra
2025-08-20 03:30:04 -07:00
parent 1a9e92d7bd
commit cf565df13e
47 changed files with 6654 additions and 2007 deletions

View 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 };