Skip to content

Commit 3a7a499

Browse files
authored
Merge pull request #107 from edalvb/main
Implementar sistema de evaluación (quiz) para los posts
2 parents 817ec3a + 7930ecb commit 3a7a499

File tree

6 files changed

+818
-0
lines changed

6 files changed

+818
-0
lines changed

app/[post]/page.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Link from 'next/link.js'
44
import { fetchPost, listPosts } from '../../utils/posts.js'
55
import { Pill } from '../components/Pill.jsx'
66
import { ButtonRead } from '../components/ButtonRead.jsx'
7+
import { PostActionsSheet } from '../components/PostActionsSheet.jsx'
78

89
export async function generateStaticParams() {
910
return listPosts()
@@ -46,6 +47,7 @@ export default async function Post(props) {
4647
className='prose max-w-none pb-4 [&>hr]:hidden [&>h1]:text-3xl [&>h1]:font-bold [&>h1]:text-blue-900 [&>h1]:pb-8 [&>p]:pb-6 [&>p]:text-lg md:[&>p]:text-xl [&>p>strong]:bg-yellow-50 [&_a]:text-blue-700 [&_a:hover]:underline [&>ul>li]:list-disc [&>ul]:text-lg md:[&>ul]:text-xl [&>ul]:text-blue-900 dark:[&>ul]:text-blue-200 [&>ul]:pb-4 [&>ul>li]:ml-5 [&>ul]:space-y-3 [&>pre]:overflow-x-auto [&>pre]:rounded-xl [&>pre]:text-white [&>pre]:mb-8 [&>pre]:p-8 [&>pre]:bg-slate-800'
4748
dangerouslySetInnerHTML={{ __html: content }}
4849
/>
50+
<PostActionsSheet postId={post} />
4951
<footer className='py-12 clear-both text-center [&>a]:leading-snug [&>a]:hover:underline [&>a]:block [&>a]:my-2'>
5052
{prev && (
5153
<Link className='lg:float-left' href={`/${prev.id}/#content`}>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use client'
2+
3+
import { useState, useEffect, useCallback, useRef } from 'react'
4+
import { createPortal } from 'react-dom'
5+
import { Quiz } from './Quiz.jsx'
6+
7+
export function PostActionsSheet({ postId }) {
8+
const [open, setOpen] = useState(null) // 'practicar' | 'evaluar' | null
9+
const [mounted, setMounted] = useState(false)
10+
const sheetRef = useRef(null)
11+
12+
useEffect(() => {
13+
setMounted(true)
14+
}, [])
15+
16+
const close = useCallback(() => setOpen(null), [])
17+
18+
useEffect(() => {
19+
if (!open) return
20+
const onKey = e => {
21+
if (e.key === 'Escape') close()
22+
}
23+
window.addEventListener('keydown', onKey)
24+
return () => window.removeEventListener('keydown', onKey)
25+
}, [open, close])
26+
27+
useEffect(() => {
28+
if (!open) return
29+
const handleClickOutside = e => {
30+
if (sheetRef.current && !sheetRef.current.contains(e.target)) {
31+
close()
32+
}
33+
}
34+
document.addEventListener('mousedown', handleClickOutside)
35+
return () => document.removeEventListener('mousedown', handleClickOutside)
36+
}, [open, close])
37+
38+
const openSheet = type => setOpen(type)
39+
40+
const baseBtn =
41+
'border dark:border-white uppercase mix rounded-[4px] font-bold inline-block px-2 py-[3px] text-[10px] bg-white dark:bg-secondry hover:bg-blue-50 dark:hover:bg-blue-900 transition-colors'
42+
43+
const sheet = (
44+
<div
45+
aria-hidden={!open}
46+
className={`fixed inset-0 z-40 ${open ? 'pointer-events-auto' : 'pointer-events-none'}`}
47+
>
48+
<div
49+
className={`absolute inset-0 bg-black/30 backdrop-blur-sm transition-opacity ${open ? 'opacity-100' : 'opacity-0'}`}
50+
onClick={close}
51+
/>
52+
<div
53+
ref={sheetRef}
54+
role='dialog'
55+
aria-modal='true'
56+
aria-label={open === 'practicar' ? 'Prácticar' : 'Evaluar'}
57+
className={`absolute bottom-0 left-0 right-0 mx-auto max-w-3xl rounded-t-xl border border-blue-200 dark:border-blue-800 bg-white dark:bg-secondry shadow-xl p-6 transition-transform duration-300 ${open ? 'translate-y-0' : 'translate-y-full'}`}
58+
>
59+
<div className='flex items-start justify-between pb-4'>
60+
<h2 className='text-base font-bold text-blue-900 dark:text-blue-100'>
61+
{open === 'practicar' ? 'Prácticar' : 'Evaluar'}
62+
</h2>
63+
<button
64+
onClick={close}
65+
className='text-xs font-semibold px-2 py-1 rounded hover:bg-blue-100 dark:hover:bg-blue-900'
66+
>
67+
Cerrar
68+
</button>
69+
</div>
70+
<div className='prose dark:prose-invert max-w-none text-sm md:text-base'>
71+
{open === 'practicar' && (
72+
<div className='space-y-4'>
73+
<p>
74+
Próximamente: área de práctica con mini retos o tarjetas
75+
(flashcards).
76+
</p>
77+
<p className='text-xs opacity-60'>
78+
Indica qué dinámica quieres y la añado.
79+
</p>
80+
</div>
81+
)}
82+
{open === 'evaluar' && (
83+
<div className='mt-2'>
84+
<Quiz slug={postId} />
85+
</div>
86+
)}
87+
</div>
88+
</div>
89+
</div>
90+
)
91+
92+
return (
93+
<div className='pt-4 flex gap-2 justify-center'>
94+
{/* <button onClick={() => openSheet('practicar')} className={baseBtn}>
95+
prácticar
96+
</button> */}
97+
<button onClick={() => openSheet('evaluar')} className={baseBtn}>
98+
evaluar
99+
</button>
100+
{mounted && createPortal(sheet, document.body)}
101+
</div>
102+
)
103+
}

0 commit comments

Comments
 (0)