import React, { useState, useEffect, useCallback } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import type { SurveyQuestion, SurveyOption, } from '@/app/infra/http/BackendClient'; import { X, ChevronRight, ChevronLeft, MessageSquare } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; /** * Get i18n text from a Record based on browser locale. */ function getI18nText(obj?: Record | null): string { if (!obj) return ''; const lang = typeof navigator !== 'undefined' ? navigator.language : 'en'; if (lang.startsWith('zh')) return obj['zh_Hans'] || obj['en_US'] || Object.values(obj)[0] || ''; if (lang.startsWith('ja')) return obj['ja_JP'] || obj['en_US'] || Object.values(obj)[0] || ''; return obj['en_US'] || Object.values(obj)[0] || ''; } interface SurveyData { survey_id: string; version: number; title: Record; description: Record; questions: SurveyQuestion[]; } export default function SurveyWidget() { const [survey, setSurvey] = useState(null); const [visible, setVisible] = useState(false); const [currentStep, setCurrentStep] = useState(0); const [answers, setAnswers] = useState>({}); const [otherInputs, setOtherInputs] = useState>({}); const [submitted, setSubmitted] = useState(false); const [collapsed, setCollapsed] = useState(false); // Poll for pending survey useEffect(() => { let timer: NodeJS.Timeout; let cancelled = false; const checkSurvey = async () => { try { const resp = await httpClient.getSurveyPending(); if (!cancelled && resp?.survey) { setSurvey(resp.survey); setVisible(true); } } catch { // Silently ignore } }; // Check after 5 seconds, then every 60 seconds timer = setTimeout(() => { checkSurvey(); timer = setInterval(checkSurvey, 60000) as unknown as NodeJS.Timeout; }, 5000); return () => { cancelled = true; clearTimeout(timer); clearInterval(timer); }; }, []); const handleDismiss = useCallback(async () => { if (survey) { try { await httpClient.dismissSurvey(survey.survey_id); } catch { /* ignore */ } } setVisible(false); }, [survey]); const handleSubmit = useCallback(async () => { if (!survey) return; // Merge "other" text inputs into answers const finalAnswers = { ...answers }; for (const [qId, text] of Object.entries(otherInputs)) { if (text.trim()) { const current = finalAnswers[qId]; if (Array.isArray(current)) { // Replace 'other' with the text finalAnswers[qId] = (current as string[]).map((v) => v === 'other' ? `other:${text}` : v, ); } else if (current === 'other') { finalAnswers[qId] = `other:${text}`; } } } try { await httpClient.submitSurveyResponse( survey.survey_id, finalAnswers, true, ); setSubmitted(true); setTimeout(() => setVisible(false), 2000); } catch { /* ignore */ } }, [survey, answers, otherInputs]); const setAnswer = useCallback((qId: string, value: unknown) => { setAnswers((prev) => ({ ...prev, [qId]: value })); }, []); if (!visible || !survey) return null; const questions = survey.questions || []; const totalSteps = questions.length; const currentQuestion = questions[currentStep]; if (submitted) { return (
🎉

{getI18nText({ zh_Hans: '感谢你的反馈!', en_US: 'Thanks for your feedback!', })}

); } if (collapsed) { return ( ); } return (
{/* Header */}
{getI18nText(survey.title)}
{/* Progress */}
{questions.map((_, i) => (
))}
{currentStep + 1} / {totalSteps}
{/* Question */}

{getI18nText(currentQuestion?.title)}

{currentQuestion?.subtitle && (

{getI18nText(currentQuestion.subtitle)}

)}
{currentQuestion?.type === 'single_select' && currentQuestion.options && ( setAnswer(currentQuestion.id, v)} otherText={otherInputs[currentQuestion.id] || ''} onOtherChange={(t) => setOtherInputs((prev) => ({ ...prev, [currentQuestion.id]: t, })) } /> )} {currentQuestion?.type === 'multi_select' && currentQuestion.options && ( setAnswer(currentQuestion.id, v)} otherText={otherInputs[currentQuestion.id] || ''} onOtherChange={(t) => setOtherInputs((prev) => ({ ...prev, [currentQuestion.id]: t, })) } /> )} {currentQuestion?.type === 'text' && (