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:
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 };
|
||||
Reference in New Issue
Block a user