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'; // ✅ 정책 데이터 타입 개선 interface PolicyData { id: number; policyType: 'privacy_policy' | 'terms_of_service'; 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; } export default function ConsentStep({ data, onChange, onNext }: ConsentStepProps) { 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(); if (result.success) { console.log('Policy data loaded:', result.data); setPolicyData(result.data); } else { setError(result.error || '정책 데이터를 불러올 수 없습니다.'); console.error('Failed to fetch policy data:', result.error); } } catch (error) { const errorMessage = '정책 데이터를 불러오는 중 오류가 발생했습니다.'; 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 '내용 없음'; const textContent = stripHtmlTags(policy.content); return textContent.length > maxLength ? textContent.substring(0, maxLength) + '...' : textContent; }; // ✅ 로딩 상태 if (loading) { return (
정책 내용을 불러오는 중...
); } // ✅ 에러 상태 if (error || !policyData) { return (
{error || '정책 내용을 불러올 수 없습니다.'}
); } // ✅ 필수 정책이 없는 경우 if (!policyData.privacy_policy || !policyData.terms_of_service) { return (
일부 정책이 설정되지 않았습니다. 관리자에게 문의해주세요.
{!policyData.privacy_policy && '개인정보 처리방침이 없습니다.'}
{!policyData.terms_of_service && '이용약관이 없습니다.'}
); } return (

서비스 이용 약관 동의

서비스 이용을 위해 다음 약관에 동의해주세요. 각 항목을 클릭하여 상세 내용을 확인할 수 있습니다.

{/* ✅ 개인정보 처리방침 */} {policyData.privacy_policy && ( } title="개인정보 처리방침" description="개인정보 수집, 이용, 보관 및 파기에 관한 정책입니다." expanded={expandedSections.privacy} onToggleExpand={() => toggleSection('privacy')} onShowModal={() => setShowPrivacyModal(true)} /> )} {/* ✅ 이용약관 */} {policyData.terms_of_service && ( } title="이용약관" description="서비스 이용 시 준수해야 할 규칙과 조건입니다." expanded={expandedSections.terms} onToggleExpand={() => toggleSection('terms')} onShowModal={() => setShowTermsModal(true)} /> )} {/* ✅ 전체 동의 */}
{ 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); }} /> )} {/* ✅ 이용약관 상세 모달 */} {showTermsModal && policyData.terms_of_service && ( setShowTermsModal(false)} onAgree={() => { onChange(prev => ({ ...prev, terms: true })); setShowTermsModal(false); }} /> )}
); } // ✅ 개별 정책 동의 섹션 컴포넌트 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; } function PolicyConsentSection({ id, type, checked, onChange, policy, isRequired, icon, title, description, expanded, onToggleExpand, onShowModal }: PolicyConsentSectionProps) { // ✅ HTML에서 텍스트 추출 const renderPolicyPreview = (content: string, maxLength = 300): string => { if (!content) return '내용 없음'; 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)}
{/* 액션 버튼들 */}
| | 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')}
); } // ✅ 정책 상세 모달 컴포넌트 interface PolicyModalProps { policy: PolicyData; onClose: () => void; onAgree: () => void; } function PolicyModal({ policy, onClose, onAgree }: PolicyModalProps) { const getPolicyTitle = (policyType: string): string => { return policyType === 'privacy_policy' ? '개인정보 처리방침' : '이용약관'; }; return (
{/* 헤더 */}

{getPolicyTitle(policy.policyType)}

버전 {policy.version} | 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')}

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