feat: add Leren page for AI-generated learning modules and update Caddyfile configuration for static routing
Some checks failed
On Push to Main / test (push) Has been cancelled
On Push to Main / publish (push) Has been cancelled
On Push to Main / deploy-dev (push) Has been cancelled

This commit is contained in:
RaymondVerhoef
2026-05-14 16:33:07 +02:00
parent 59e746a8a8
commit 42d7209773
2 changed files with 96 additions and 16 deletions

View File

@@ -1,7 +1,4 @@
:80 {
root * /srv
file_server
encode gzip zstd
header {
@@ -13,6 +10,16 @@
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
handle /api/anthropic/* {
uri strip_prefix /api/anthropic
reverse_proxy https://api.anthropic.com {
header_up Host api.anthropic.com
}
}
handle {
root * /srv
@static {
path *.css *.js *.png *.jpg *.jpeg *.gif *.webp *.svg *.woff *.woff2 *.ttf
}
@@ -23,14 +30,9 @@
}
header @html Cache-Control "public, max-age=0, must-revalidate"
handle /api/anthropic/* {
uri strip_prefix /api/anthropic
reverse_proxy https://api.anthropic.com {
header_up Host api.anthropic.com
}
}
try_files {path} /index.html
file_server
}
handle_errors {
rewrite * /index.html

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { BookOpen, CheckCircle, Loader, ArrowRight, Plus, Search, ChevronLeft } from 'lucide-react';
import { BookOpen, CheckCircle, Loader, ArrowRight, Plus, Search, ChevronLeft, MessageSquare } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Link } from 'react-router-dom';
import Card from '../components/ui/Card';
@@ -32,6 +32,11 @@ const Leren = () => {
const [weeklyDone, setWeeklyDone] = useState(false);
const [sessionDone, setSessionDone] = useState(false);
// Feedback
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
const [feedbackText, setFeedbackText] = useState('');
const [feedbackPrompted, setFeedbackPrompted] = useState(false);
useEffect(() => {
if (state.currentUser) {
const assigned = getAssignedTopic(state.currentUser.id, state.weekNumber);
@@ -48,6 +53,8 @@ const Leren = () => {
setView('detail');
setSessionDone(false);
setError(null);
setFeedbackText('');
setFeedbackPrompted(false);
const cached = getCachedContent(topic.id);
if (cached) {
setContent(cached);
@@ -90,7 +97,7 @@ const Leren = () => {
}
};
const handleComplete = () => {
const doComplete = () => {
setSessionDone(true);
if (!weeklyDone) {
setWeeklyDone(true);
@@ -98,6 +105,33 @@ const Leren = () => {
}
};
const handleComplete = () => {
if (!feedbackPrompted) {
setFeedbackPrompted(true);
setShowFeedbackModal(true);
return;
}
doComplete();
};
const handleSubmitFeedback = () => {
if (feedbackText.trim()) {
storage.set(`user:${state.currentUser.id}:feedback:${activeTopic.id}`, {
text: feedbackText.trim(),
topicId: activeTopic.id,
week: state.weekNumber,
timestamp: Date.now(),
});
}
setShowFeedbackModal(false);
doComplete();
};
const handleSkipFeedback = () => {
setShowFeedbackModal(false);
doComplete();
};
// ── Detail View ──────────────────────────────────────────
if (view === 'detail' && activeTopic) {
if (sessionDone) {
@@ -123,6 +157,50 @@ const Leren = () => {
return (
<div className="p-4 md:p-8 max-w-4xl mx-auto pb-24 md:pb-8">
<AnimatePresence>
{showFeedbackModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center p-4"
style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}
>
<motion.div
initial={{ scale: 0.95, opacity: 0, y: 16 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.95, opacity: 0, y: 16 }}
transition={{ type: 'spring', stiffness: 300, damping: 24 }}
className="w-full max-w-lg"
>
<Card className="border border-bg-warm shadow-xl">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-full bg-teal/10 flex items-center justify-center flex-shrink-0">
<MessageSquare size={20} className="text-teal" />
</div>
<h2 className="text-xl font-bold">How was this content?</h2>
</div>
<p className="text-fg-muted text-sm mb-5">
Your feedback helps improve future learning content. This is optional you can skip if you prefer.
</p>
<textarea
autoFocus
value={feedbackText}
onChange={e => setFeedbackText(e.target.value)}
placeholder="What was clear? What could be improved? Anything missing?"
rows={4}
className="w-full rounded-[var(--r-sm)] border border-bg-warm bg-bg p-3 text-sm resize-none focus:outline-none focus:border-teal transition-colors"
/>
<div className="flex justify-end gap-3 mt-4">
<Button variant="outline" onClick={handleSkipFeedback}>Skip</Button>
<Button onClick={handleSubmitFeedback}>Submit Feedback</Button>
</div>
</Card>
</motion.div>
</motion.div>
)}
</AnimatePresence>
<button onClick={() => setView('overview')} className="flex items-center gap-2 text-fg-muted hover:text-teal mb-6 transition-colors">
<ChevronLeft size={16} /> Back to overview
</button>