import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; import { ChevronDown, ChevronUp, FileText, Shield, Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; import { getCurrentPolicies } from '@/lib/polices/service'; import { useTranslation } from '@/i18n/client'; // ✅ 정책 데이터 타입 개선 interface PolicyData { id: number; policyType: 'privacy_policy' | 'terms_of_service'; locale: string; // locale 필드 필요 version: string; content: string; // HTML 형태의 리치텍스트 effectiveDate: string; isCurrent: boolean; createdAt: string; } interface PolicyVersions { privacy_policy?: PolicyData; terms_of_service?: PolicyData; } interface ConsentStepProps { data: { privacy: boolean; terms: boolean; marketing: boolean; }; onChange: (updater: (prev: any) => any) => void; onNext: () => void; lng: string; // 언어 코드 추가 } export default function ConsentStep({ data, onChange, onNext, lng }: ConsentStepProps) { const { t } = useTranslation(lng, 'consent'); const [policyData, setPolicyData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showPrivacyModal, setShowPrivacyModal] = useState(false); const [showTermsModal, setShowTermsModal] = useState(false); const [expandedSections, setExpandedSections] = useState>({}); const isValid = data.privacy && data.terms; // 정책 데이터 로드 useEffect(() => { fetchPolicyData(); }, []); const fetchPolicyData = async () => { try { setLoading(true); setError(null); const result = await getCurrentPolicies(lng); if (result.success) { console.log('Policy data loaded:', result.data); setPolicyData(result.data[lng]); } else { setError(result.error || t('policyLoadError')); console.error('Failed to fetch policy data:', result.error); } } catch (error) { const errorMessage = t('policyFetchError'); setError(errorMessage); console.error('Failed to fetch policy data:', error); } finally { setLoading(false); } }; const handleConsentChange = (type: string, checked: boolean) => { onChange(prev => ({ ...prev, [type]: checked })); }; const toggleSection = (section: string) => { setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })); }; // ✅ HTML에서 텍스트만 추출하는 함수 const stripHtmlTags = (html: string): string => { if (!html) return ''; // HTML 태그 제거 return html .replace(/<[^>]*>/g, '') // 모든 HTML 태그 제거 .replace(/ /g, ' ') // non-breaking space 처리 .replace(/&/g, '&') // HTML entities 처리 .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/\s+/g, ' ') // 연속된 공백을 하나로 .trim(); }; // ✅ 정책 미리보기 텍스트 생성 const renderPolicyPreview = (policy: PolicyData, maxLength = 200): string => { if (!policy?.content) return t('noContent'); const textContent = stripHtmlTags(policy.content); return textContent.length > maxLength ? textContent.substring(0, maxLength) + '...' : textContent; }; // ✅ 로딩 상태 if (loading) { return (
{t('loadingPolicies')}
); } // ✅ 에러 상태 if (error || !policyData) { return (
{error || t('policyLoadError')}
); } // ✅ 필수 정책이 없는 경우 if (!policyData.privacy_policy || !policyData.terms_of_service) { return (
{t('policiesNotConfigured')}
{!policyData.privacy_policy && t('missingPrivacyPolicy')}
{!policyData.terms_of_service && t('missingTermsOfService')}
); } return (

{t('consentTitle')}

{t('consentDescription')}

{/* ✅ 개인정보 처리방침 */} {policyData.privacy_policy && ( } title={t('privacyPolicyTitle')} description={t('privacyPolicyDescription')} expanded={expandedSections.privacy} onToggleExpand={() => toggleSection('privacy')} onShowModal={() => setShowPrivacyModal(true)} t={t} lng={lng} /> )} {/* ✅ 이용약관 */} {policyData.terms_of_service && ( } title={t('termsOfServiceTitle')} description={t('termsOfServiceDescription')} expanded={expandedSections.terms} onToggleExpand={() => toggleSection('terms')} onShowModal={() => setShowTermsModal(true)} t={t} lng={lng} /> )} {/* ✅ 전체 동의 */}
{ const checked = e.target.checked; onChange(() => ({ privacy: checked, terms: checked, marketing: checked })); }} className="h-5 w-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
{/* ✅ 개인정보 처리방침 상세 모달 */} {showPrivacyModal && policyData.privacy_policy && ( setShowPrivacyModal(false)} onAgree={() => { onChange(prev => ({ ...prev, privacy: true })); setShowPrivacyModal(false); }} t={t} lng={lng} /> )} {/* ✅ 이용약관 상세 모달 */} {showTermsModal && policyData.terms_of_service && ( setShowTermsModal(false)} onAgree={() => { onChange(prev => ({ ...prev, terms: true })); setShowTermsModal(false); }} t={t} lng={lng} /> )}
); } // ✅ 개별 정책 동의 섹션 컴포넌트 interface PolicyConsentSectionProps { id: string; type: string; checked: boolean; onChange: (type: string, checked: boolean) => void; policy: PolicyData; isRequired: boolean; icon: React.ReactNode; title: string; description: string; expanded: boolean; onToggleExpand: () => void; onShowModal: () => void; t: (key: string, options?: any) => string; lng: string; } function PolicyConsentSection({ id, type, checked, onChange, policy, isRequired, icon, title, description, expanded, onToggleExpand, onShowModal, t, lng }: PolicyConsentSectionProps) { // ✅ HTML에서 텍스트 추출 const renderPolicyPreview = (content: string, maxLength = 300): string => { if (!content) return t('noContent'); const textContent = content .replace(/<[^>]*>/g, '') // HTML 태그 제거 .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/\s+/g, ' ') .trim(); return textContent.length > maxLength ? textContent.substring(0, maxLength) + '...' : textContent; }; return (
{/* 체크박스와 기본 정보 */}
onChange(type, e.target.checked)} className="mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
{icon}

{description}

{/* ✅ 정책 미리보기 - HTML 내용 표시 */}
{renderPolicyPreview(policy.content, expanded ? 1000 : 200)}
{/* 액션 버튼들 */}
| | {t('effectiveDate')}: {new Date(policy.effectiveDate).toLocaleDateString( lng === 'ko' ? 'ko-KR' : 'en-US' )}
); } // ✅ 정책 상세 모달 컴포넌트 interface PolicyModalProps { policy: PolicyData; onClose: () => void; onAgree: () => void; t: (key: string, options?: any) => string; lng: string; } function PolicyModal({ policy, onClose, onAgree, t, lng }: PolicyModalProps) { const getPolicyTitle = (policyType: string): string => { return policyType === 'privacy_policy' ? t('privacyPolicyTitle') : t('termsOfServiceTitle'); }; return (
{/* 헤더 */}

{getPolicyTitle(policy.policyType)}

{t('version')} {policy.version} | {t('effectiveDate')}: {new Date(policy.effectiveDate).toLocaleDateString( lng === 'ko' ? 'ko-KR' : 'en-US' )}

{/* ✅ 내용 - HTML 직접 렌더링 */}
{/* 푸터 */}
); }