feat: EduEasy checkpoint - plan, scaffolding, pedagogia modules, components child+parent, API routes, Prisma schema, Docker infra, Better Auth

This commit is contained in:
Renato
2026-07-20 17:58:49 +02:00
commit e69b18abaa
49 changed files with 9842 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
"use client"
import { useEffect, useRef, useState } from "react"
import { motion } from "framer-motion"
interface TemporizadorVisualProps {
durationMinutes: number
onComplete: () => void
running: boolean
}
export default function TemporizadorVisual({
durationMinutes,
onComplete,
running,
}: TemporizadorVisualProps) {
const [remaining, setRemaining] = useState(durationMinutes * 60)
const totalSec = durationMinutes * 60
const intervalRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
if (running) {
intervalRef.current = setInterval(() => {
setRemaining((prev) => {
if (prev <= 1) {
clearInterval(intervalRef.current!)
onComplete()
return 0
}
return prev - 1
})
}, 1000)
} else {
if (intervalRef.current) clearInterval(intervalRef.current)
}
return () => {
if (intervalRef.current) clearInterval(intervalRef.current)
}
}, [running, onComplete])
const fraction = remaining / totalSec
const degrees = fraction * 360
const minutes = Math.floor(remaining / 60)
const seconds = remaining % 60
return (
<div className="flex flex-col items-center gap-2">
<div className="relative w-20 h-20">
<svg className="w-full h-full -rotate-90" viewBox="0 0 100 100">
<circle
cx="50" cy="50" r="42"
fill="none"
stroke="#E8E0D8"
strokeWidth="8"
/>
<circle
cx="50" cy="50" r="42"
fill="none"
stroke={fraction > 0.3 ? "#8CB8A0" : "#D4A574"}
strokeWidth="8"
strokeDasharray="264"
strokeDashoffset={264 * (1 - fraction)}
strokeLinecap="round"
className="transition-all duration-1000 ease-linear"
/>
</svg>
<span className="absolute inset-0 flex items-center justify-center text-lg font-display font-bold text-foreground">
{minutes}:{seconds.toString().padStart(2, "0")}
</span>
</div>
</div>
)
}