Files
MOPC-Portal/src/lib/utils.ts
Matt 0277768ed7 Add notification bell system and MOPC onboarding form
Notification System:
- Add InAppNotification and NotificationEmailSetting database models
- Create notification service with 60+ notification types for all user roles
- Add notification router with CRUD endpoints
- Build NotificationBell UI component with dropdown and unread count
- Integrate bell into admin, jury, mentor, and observer navs
- Add notification email settings admin UI in Settings > Notifications
- Add notification triggers to filtering router (complete/failed)
- Add sendNotificationEmail function to email library
- Add formatRelativeTime utility function

MOPC Onboarding Form:
- Create /apply landing page with auto-redirect for single form
- Create seed script for MOPC 2026 application form (6 steps)
- Create seed script for default notification email settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 21:30:25 +01:00

84 lines
2.3 KiB
TypeScript

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 {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(date))
}
export function formatDateOnly(date: Date | string): string {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'long',
}).format(new Date(date))
}
export function truncate(str: string, length: number): string {
if (str.length <= length) return str
return str.slice(0, length) + '...'
}
export function getInitials(name: string): string {
return name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
.slice(0, 2)
}
export function slugify(str: string): string {
return str
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '')
}
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 formatEnumLabel(value: string): string {
return value
.replace(/_/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase())
}
export function daysUntil(date: Date | string): number {
const target = new Date(date)
const now = new Date()
return Math.ceil((target.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
}
export function formatRelativeTime(date: Date | string): string {
const now = new Date()
const target = new Date(date)
const diffMs = now.getTime() - target.getTime()
const diffSec = Math.floor(diffMs / 1000)
const diffMin = Math.floor(diffSec / 60)
const diffHour = Math.floor(diffMin / 60)
const diffDay = Math.floor(diffHour / 24)
const diffWeek = Math.floor(diffDay / 7)
if (diffSec < 60) return 'just now'
if (diffMin < 60) return `${diffMin}m ago`
if (diffHour < 24) return `${diffHour}h ago`
if (diffDay < 7) return `${diffDay}d ago`
if (diffWeek < 4) return `${diffWeek}w ago`
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
}).format(target)
}