Dmitri d4a5378adf Refactor: migrate frontend to Vite/React, add product backend modules
Frontend:
- Replace Next.js with Vite + React + TypeScript
- Add new component architecture (app-shell, sidebar, dashboard modules)
- Implement product modules: FRAME, safety protocols, walkthrough checkin,
  campus/staff attendance, personality quiz, sign language, classroom timer
- Add shadcn/ui component library with Tailwind CSS
- Remove legacy generated components, stores, and pages

Backend:
- Add product migrations: frame_entries, user_progress, safety_quiz_results,
  walkthrough_checkins, communication_events, personality_quiz_results,
  campus_attendance_config/summaries, staff_attendance_records, content_catalog
- Add corresponding models, services, and routes
- Implement cookie-based auth with refresh token rotation
- Add content catalog seeder with product content
- Migrate to ESLint flat config
- Switch from yarn to npm

Infrastructure:
- Update .gitignore for new tooling
- Add project documentation (CLAUDE.md, docs/)
- Remove deprecated config files and yarn.lock

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-09 15:18:23 +02:00

122 lines
4.5 KiB
TypeScript

import { Monitor } from 'lucide-react';
import { CircularProgress } from '@/components/classroom-timer/CircularProgress';
import { TimerControls } from '@/components/classroom-timer/TimerControls';
import { TimerParticles } from '@/components/classroom-timer/TimerParticles';
import type { ClassroomTimerActions, ClassroomTimerState } from '@/components/classroom-timer/types';
type TimerDisplayProps = {
state: ClassroomTimerState;
actions: ClassroomTimerActions;
};
export function TimerDisplay({ state, actions }: TimerDisplayProps) {
const {
selectedBackground,
isFinished,
progress,
urgencyColor,
formattedTime,
isRunning,
remainingSeconds,
totalSeconds,
soundEnabled,
displayParticles,
presets,
} = state;
if (!selectedBackground) {
return null;
}
return (
<div className="lg:col-span-2">
<div className="relative overflow-hidden rounded-2xl border border-slate-700/40 shadow-2xl shadow-black/30 min-h-[480px]">
<img
src={selectedBackground.image}
alt=""
className="absolute inset-0 w-full h-full object-cover transition-all duration-1000"
style={{ filter: isFinished ? 'brightness(0.3) saturate(0.5)' : 'brightness(0.6)' }}
/>
<div className={`absolute inset-0 bg-gradient-to-br ${selectedBackground.overlay} transition-all duration-1000`} />
<TimerParticles particles={displayParticles} />
<div className="relative z-10 flex flex-col items-center justify-center h-full py-10 px-6">
<div className="relative">
<CircularProgress
progress={progress}
size={280}
strokeWidth={8}
ringClass={selectedBackground.ringColor}
trackClass={selectedBackground.trackColor}
/>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className={`font-mono text-7xl md:text-8xl font-bold tracking-wider ${urgencyColor}`}>
{formattedTime}
</span>
{isFinished && (
<span className={`text-xl font-bold ${selectedBackground.accentColor} animate-bounce mt-1`}>
Time&apos;s Up!
</span>
)}
{!isRunning && !isFinished && remainingSeconds === totalSeconds && (
<span className={`text-sm ${selectedBackground.accentColor} opacity-60 mt-1`}>
Ready
</span>
)}
{isRunning && (
<span className={`text-sm ${selectedBackground.accentColor} opacity-60 mt-1`}>
Running...
</span>
)}
</div>
</div>
<div className="mt-8">
<TimerControls
isRunning={isRunning}
isFinished={isFinished}
remainingSeconds={remainingSeconds}
totalSeconds={totalSeconds}
soundEnabled={soundEnabled}
onStart={actions.handleStart}
onPause={actions.handlePause}
onReset={actions.handleReset}
onToggleSound={() => actions.setSoundEnabled(!soundEnabled)}
onToggleFullscreen={actions.toggleFullscreen}
/>
</div>
<div className="flex flex-wrap items-center justify-center gap-2 mt-6">
{presets.map((preset) => (
<button
type="button"
key={preset.seconds}
onClick={() => actions.handleSetTime(preset.seconds)}
className={`px-3 py-1.5 rounded-full text-xs font-medium backdrop-blur-md border transition-all ${
totalSeconds === preset.seconds
? 'bg-white/25 border-white/40 text-white shadow-lg'
: 'bg-white/8 border-white/15 text-white/50 hover:bg-white/15 hover:text-white/80'
}`}
>
{preset.label}
</button>
))}
</div>
</div>
<div className="absolute bottom-3 right-3 z-10">
<button
type="button"
onClick={actions.toggleFullscreen}
className="flex items-center gap-1.5 px-3 py-1.5 bg-black/40 backdrop-blur-md rounded-lg text-white/60 text-xs hover:text-white hover:bg-black/60 transition-all border border-white/10"
>
<Monitor size={14} />
Project on Screen
</button>
</div>
</div>
</div>
);
}