flowquizbot / webapp /src /components /QuestionScreen.tsx
yuk1qwe's picture
Devin AI
Deploy FlowQuizBot to HF Spaces
99a369b
Raw History Blame Contribute Delete
6.77 kB
import { useState } from 'react'
import type { Question } from '../types/quiz'
import { ProgressBar } from './ProgressBar'
interface QuestionScreenProps {
question: Question
current: number
total: number
onAnswer: (answer: string | string[]) => void
}
export function QuestionScreen({ question, current, total, onAnswer }: QuestionScreenProps) {
const [selected, setSelected] = useState<string[]>([])
const [textValue, setTextValue] = useState('')
const handleSingleSelect = (optionId: string) => {
setSelected([optionId])
setTimeout(() => onAnswer(optionId), 400)
}
const handleMultiToggle = (optionId: string) => {
setSelected(prev =>
prev.includes(optionId)
? prev.filter(id => id !== optionId)
: [...prev, optionId]
)
}
const handleMultiSubmit = () => {
if (selected.length > 0) onAnswer(selected)
}
const handleTextSubmit = () => {
if (textValue.trim()) onAnswer(textValue.trim())
}
return (
<div key={question.id} className="animate-fade-in flex flex-col min-h-screen px-5 pt-14 pb-8">
<ProgressBar current={current} total={total} />
<h2 className="text-[22px] font-bold leading-snug tracking-tight text-(--color-text-primary) mb-7">
{question.text}
</h2>
{/* Single choice */}
{question.type === 'single' && (
<div className="flex flex-col gap-2">
{question.options.map(opt => (
<button
key={opt.id}
onClick={() => handleSingleSelect(opt.id)}
className={`flex items-center gap-3.5 px-4 py-3.5 bg-(--color-bg-card) border rounded-xl cursor-pointer transition-all duration-200 shadow-sm text-left
${selected.includes(opt.id) ? 'border-(--color-border-selected) bg-(--color-bg-selected)' : 'border-(--color-border) hover:bg-(--color-bg-hover) hover:border-(--color-border-hover)'}`}
>
{opt.emoji && <span className="text-[22px] w-8 text-center shrink-0">{opt.emoji}</span>}
<span className="text-[15px] font-medium text-(--color-text-primary) flex-1">{opt.text}</span>
<span className={`w-5 h-5 rounded-full border-2 shrink-0 relative transition-all duration-200
${selected.includes(opt.id) ? 'border-(--color-accent) bg-(--color-accent)' : 'border-(--color-border)'}`}>
{selected.includes(opt.id) && (
<span className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-(--color-accent-text)" />
)}
</span>
</button>
))}
</div>
)}
{/* Multi choice */}
{question.type === 'multi' && (
<>
<div className="flex flex-col gap-2">
{question.options.map(opt => (
<button
key={opt.id}
onClick={() => handleMultiToggle(opt.id)}
className={`flex items-center gap-3.5 px-4 py-3.5 bg-(--color-bg-card) border rounded-xl cursor-pointer transition-all duration-200 shadow-sm text-left
${selected.includes(opt.id) ? 'border-(--color-border-selected) bg-(--color-bg-selected)' : 'border-(--color-border) hover:bg-(--color-bg-hover) hover:border-(--color-border-hover)'}`}
>
{opt.emoji && <span className="text-[22px] w-8 text-center shrink-0">{opt.emoji}</span>}
<span className="text-[15px] font-medium text-(--color-text-primary) flex-1">{opt.text}</span>
<span className={`w-5 h-5 rounded shrink-0 border-2 flex items-center justify-center transition-all duration-200
${selected.includes(opt.id) ? 'border-(--color-accent) bg-(--color-accent)' : 'border-(--color-border)'}`}>
{selected.includes(opt.id) && (
<svg className="w-3 h-3 text-(--color-accent-text)" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)}
</span>
</button>
))}
</div>
<div className="flex-1" />
<button
onClick={handleMultiSubmit}
disabled={selected.length === 0}
className="w-full py-3.5 bg-(--color-accent) text-(--color-accent-text) rounded-xl text-[15px] font-semibold tracking-tight transition-all disabled:opacity-30 mt-6"
>
Continue
</button>
</>
)}
{/* Image cards */}
{question.type === 'image_cards' && (
<div className="grid grid-cols-2 gap-2">
{question.options.map(opt => (
<button
key={opt.id}
onClick={() => handleSingleSelect(opt.id)}
className={`flex flex-col items-center p-3 bg-(--color-bg-card) border rounded-xl cursor-pointer transition-all duration-200 shadow-sm
${selected.includes(opt.id) ? 'border-(--color-border-selected) bg-(--color-bg-selected)' : 'border-(--color-border) hover:bg-(--color-bg-hover)'}`}
>
{opt.image_url ? (
<img src={opt.image_url} alt={opt.text} className="w-full aspect-square object-cover rounded-lg mb-2" />
) : (
<div className="w-full aspect-square bg-(--color-bg-hover) rounded-lg mb-2 flex items-center justify-center text-3xl">
{opt.emoji || '📷'}
</div>
)}
<span className="text-[13px] font-medium text-(--color-text-primary) text-center">{opt.text}</span>
</button>
))}
</div>
)}
{/* Text input */}
{question.type === 'text_input' && (
<>
<input
type="text"
value={textValue}
onChange={e => setTextValue(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleTextSubmit()}
placeholder={question.placeholder || ''}
className="w-full px-3.5 py-3 text-[15px] bg-(--color-bg-card) border border-(--color-border) rounded-lg text-(--color-text-primary) outline-none transition-colors focus:border-(--color-accent) placeholder:text-(--color-text-tertiary)"
autoFocus
/>
<div className="flex-1" />
<button
onClick={handleTextSubmit}
disabled={!textValue.trim()}
className="w-full py-3.5 bg-(--color-accent) text-(--color-accent-text) rounded-xl text-[15px] font-semibold tracking-tight transition-all disabled:opacity-30 mt-6"
>
Continue
</button>
</>
)}
</div>
)
}