Spaces:
Configuration error
Configuration error
| import { type ClassValue, clsx } from "clsx" | |
| import { twMerge } from "tailwind-merge" | |
| export function cn(...inputs: ClassValue[]) { | |
| return twMerge(clsx(inputs)) | |
| } | |
| export function formatDate(date: Date | string): string { | |
| const d = new Date(date) | |
| const now = new Date() | |
| const diff = now.getTime() - d.getTime() | |
| // Less than 1 minute | |
| if (diff < 60000) { | |
| return 'Just now' | |
| } | |
| // Less than 1 hour | |
| if (diff < 3600000) { | |
| const minutes = Math.floor(diff / 60000) | |
| return `${minutes}m ago` | |
| } | |
| // Less than 24 hours | |
| if (diff < 86400000) { | |
| const hours = Math.floor(diff / 3600000) | |
| return `${hours}h ago` | |
| } | |
| // Less than 7 days | |
| if (diff < 604800000) { | |
| const days = Math.floor(diff / 86400000) | |
| return `${days}d ago` | |
| } | |
| // More than 7 days | |
| return d.toLocaleDateString() | |
| } | |
| export function formatTime(date: Date | string): string { | |
| const d = new Date(date) | |
| return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) | |
| } | |
| export function formatFileSize(bytes: number): string { | |
| if (bytes === 0) return '0 Bytes' | |
| const k = 1024 | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB'] | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)) | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] | |
| } | |
| export function getFileIcon(mimeType: string): string { | |
| if (mimeType.startsWith('image/')) return 'πΌοΈ' | |
| if (mimeType.startsWith('video/')) return 'π₯' | |
| if (mimeType.startsWith('audio/')) return 'π΅' | |
| if (mimeType.includes('pdf')) return 'π' | |
| if (mimeType.includes('word')) return 'π' | |
| if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'π' | |
| if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return 'π½οΈ' | |
| if (mimeType.includes('zip') || mimeType.includes('rar') || mimeType.includes('archive')) return 'π¦' | |
| return 'π' | |
| } | |
| export function generateAvatar(name: string): string { | |
| const colors = [ | |
| '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', | |
| '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9' | |
| ] | |
| const initials = name | |
| .split(' ') | |
| .map(word => word[0]) | |
| .join('') | |
| .toUpperCase() | |
| .slice(0, 2) | |
| const colorIndex = name.charCodeAt(0) % colors.length | |
| const color = colors[colorIndex] | |
| return `data:image/svg+xml,${encodeURIComponent(` | |
| <svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"> | |
| <circle cx="20" cy="20" r="20" fill="${color}"/> | |
| <text x="20" y="25" text-anchor="middle" fill="white" font-family="Arial" font-size="14" font-weight="bold"> | |
| ${initials} | |
| </text> | |
| </svg> | |
| `)}` | |
| } | |
| export function debounce<T extends (...args: any[]) => any>( | |
| func: T, | |
| wait: number | |
| ): (...args: Parameters<T>) => void { | |
| let timeout: NodeJS.Timeout | null = null | |
| return (...args: Parameters<T>) => { | |
| if (timeout) { | |
| clearTimeout(timeout) | |
| } | |
| timeout = setTimeout(() => { | |
| func(...args) | |
| }, wait) | |
| } | |
| } | |
| export function throttle<T extends (...args: any[]) => any>( | |
| func: T, | |
| limit: number | |
| ): (...args: Parameters<T>) => void { | |
| let inThrottle: boolean | |
| return (...args: Parameters<T>) => { | |
| if (!inThrottle) { | |
| func(...args) | |
| inThrottle = true | |
| setTimeout(() => inThrottle = false, limit) | |
| } | |
| } | |
| } | |
| export function isValidEmail(email: string): boolean { | |
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ | |
| return emailRegex.test(email) | |
| } | |
| export function isValidUsername(username: string): boolean { | |
| const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/ | |
| return usernameRegex.test(username) | |
| } | |
| export function truncateText(text: string, maxLength: number): string { | |
| if (text.length <= maxLength) return text | |
| return text.slice(0, maxLength) + '...' | |
| } | |
| export function copyToClipboard(text: string): Promise<void> { | |
| if (navigator.clipboard) { | |
| return navigator.clipboard.writeText(text) | |
| } else { | |
| // Fallback for older browsers | |
| const textArea = document.createElement('textarea') | |
| textArea.value = text | |
| document.body.appendChild(textArea) | |
| textArea.focus() | |
| textArea.select() | |
| try { | |
| document.execCommand('copy') | |
| return Promise.resolve() | |
| } catch (err) { | |
| return Promise.reject(err) | |
| } finally { | |
| document.body.removeChild(textArea) | |
| } | |
| } | |
| } | |
| export function downloadFile(url: string, filename: string): void { | |
| const link = document.createElement('a') | |
| link.href = url | |
| link.download = filename | |
| document.body.appendChild(link) | |
| link.click() | |
| document.body.removeChild(link) | |
| } | |
| export function isImageFile(file: File): boolean { | |
| return file.type.startsWith('image/') | |
| } | |
| export function isVideoFile(file: File): boolean { | |
| return file.type.startsWith('video/') | |
| } | |
| export function isAudioFile(file: File): boolean { | |
| return file.type.startsWith('audio/') | |
| } | |
| export function getInitials(name: string): string { | |
| return name | |
| .split(' ') | |
| .map(word => word[0]) | |
| .join('') | |
| .toUpperCase() | |
| .slice(0, 2) | |
| } | |