diff options
| author | 0-Zz-ang <s1998319@gmail.com> | 2025-09-25 15:57:18 +0900 |
|---|---|---|
| committer | 0-Zz-ang <s1998319@gmail.com> | 2025-09-25 15:57:18 +0900 |
| commit | d6d3bbc55a557acde7d1f07c3bd4813e367d4e86 (patch) | |
| tree | db94f330abda7fee103c050fea3e880309bcef98 /components | |
| parent | 4c2d4c235bd80368e31cae9c375e9a585f6a6844 (diff) | |
(박서영)동의관련 언어설정 추가
Diffstat (limited to 'components')
| -rw-r--r-- | components/polices/policy-editor.tsx | 75 | ||||
| -rw-r--r-- | components/polices/policy-management-client.tsx | 18 | ||||
| -rw-r--r-- | components/polices/policy-page-client.tsx | 281 | ||||
| -rw-r--r-- | components/signup/conset-step.tsx | 5 |
4 files changed, 366 insertions, 13 deletions
diff --git a/components/polices/policy-editor.tsx b/components/polices/policy-editor.tsx index d58831e0..bf99b659 100644 --- a/components/polices/policy-editor.tsx +++ b/components/polices/policy-editor.tsx @@ -17,6 +17,7 @@ interface PolicyEditorProps { onCancel: () => void onPreview: (policyType: string, content: string, version: string) => void isLoading?: boolean + currentLocale?: 'ko' | 'en' } export function PolicyEditor({ @@ -25,7 +26,8 @@ export function PolicyEditor({ onSave, onCancel, onPreview, - isLoading = false + isLoading = false, + currentLocale = 'ko' }: PolicyEditorProps) { const [version, setVersion] = useState('') const [content, setContent] = useState('') @@ -52,13 +54,14 @@ export function PolicyEditor({ setContent(currentPolicy.content || '') } else { setVersion('1.0') - setContent(getDefaultPolicyContent(policyType)) + setContent(getDefaultPolicyContent(policyType, currentLocale)) } }, [currentPolicy, policyType]) - const getDefaultPolicyContent = (type: string) => { + const getDefaultPolicyContent = (type: string, locale: 'ko' | 'en') => { if (type === 'privacy_policy') { - return `<h1>개인정보 처리방침</h1> + if (locale === 'ko') { + return `<h1>개인정보 처리방침</h1> <h2>제1조 (목적)</h2> <p>본 개인정보 처리방침은 eVCP(이하 "회사")가 개인정보 보호법 등 관련 법령에 따라 정보주체의 개인정보를 보호하고 이와 관련된 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같은 처리방침을 수립·공개합니다.</p> @@ -90,8 +93,43 @@ export function PolicyEditor({ <li>개인정보 정정·삭제요구</li> <li>개인정보 처리정지요구</li> </ul>` + } else { + return `<h1>Privacy Policy</h1> + +<h2>Article 1 (Purpose)</h2> +<p>This Privacy Policy is established and disclosed by eVCP (hereinafter "the Company") in order to protect personal information of data subjects and promptly and smoothly handle grievances related thereto in accordance with the Personal Information Protection Act and other relevant laws.</p> + +<h2>Article 2 (Purpose of Collection and Use of Personal Information)</h2> +<p>The Company processes personal information for the following purposes:</p> +<ul> + <li>Member registration and management</li> + <li>Service provision and contract fulfillment</li> + <li>Customer consultation and complaint handling</li> +</ul> + +<h2>Article 3 (Items of Personal Information Collected)</h2> +<p><strong>Required items:</strong></p> +<ul> + <li>Email address</li> + <li>Phone number</li> + <li>Company name</li> +</ul> + +<h2>Article 4 (Retention and Use Period of Personal Information)</h2> +<p>The Company processes and retains personal information within the period of retention and use of personal information according to laws or the period of retention and use of personal information agreed upon when collecting personal information from data subjects.</p> + +<h2>Article 5 (Rights of Data Subjects)</h2> +<p>Data subjects may exercise the following rights related to personal information protection against the Company at any time:</p> +<ul> + <li>Request for notification of personal information processing status</li> + <li>Request for access to personal information</li> + <li>Request for correction or deletion of personal information</li> + <li>Request for suspension of processing of personal information</li> +</ul>` + } } else { - return `<h1>이용약관</h1> + if (locale === 'ko') { + return `<h1>이용약관</h1> <h2>제1조 (목적)</h2> <p>본 약관은 eVCP(이하 "회사")가 제공하는 서비스의 이용조건 및 절차, 회사와 회원 간의 권리, 의무 및 책임사항을 규정함을 목적으로 합니다.</p> @@ -116,6 +154,33 @@ export function PolicyEditor({ <li>문서 관리 서비스</li> <li>견적 제출 서비스</li> </ul>` + } else { + return `<h1>Terms of Service</h1> + +<h2>Article 1 (Purpose)</h2> +<p>These Terms of Service are established to regulate the terms and procedures for using the services provided by eVCP (hereinafter "the Company"), and the rights, obligations, and responsibilities between the Company and its members.</p> + +<h2>Article 2 (Definitions)</h2> +<ul> + <li><strong>"Service"</strong> means all services provided by the Company.</li> + <li><strong>"Member"</strong> means a person who agrees to these Terms of Service and enters into a service use contract with the Company.</li> + <li><strong>"Partner"</strong> means a corporation or individual business operator registered as a partner of the Company.</li> +</ul> + +<h2>Article 3 (Effectiveness and Amendment of Terms)</h2> +<p>These Terms of Service take effect for all members who intend to use the service.</p> + +<h2>Article 4 (Member Registration)</h2> +<p>Member registration is concluded when an applicant agrees to the contents of these Terms of Service, applies for member registration, and the Company approves such application.</p> + +<h2>Article 5 (Provision of Services)</h2> +<p>The Company provides the following services to members:</p> +<ul> + <li>Partner registration and management services</li> + <li>Document management services</li> + <li>Quotation submission services</li> +</ul>` + } } } diff --git a/components/polices/policy-management-client.tsx b/components/polices/policy-management-client.tsx index eecb82ff..24a0a97c 100644 --- a/components/polices/policy-management-client.tsx +++ b/components/polices/policy-management-client.tsx @@ -35,9 +35,10 @@ interface PolicyManagementClientProps { allPolicies: Record<string, any[]> stats: any } + currentLocale?: 'ko' | 'en' } -export function PolicyManagementClient({ initialData }: PolicyManagementClientProps) { +export function PolicyManagementClient({ initialData, currentLocale = 'ko' }: PolicyManagementClientProps) { const [currentTab, setCurrentTab] = useState('privacy_policy') const [editingPolicy, setEditingPolicy] = useState<string | null>(null) const [viewingHistory, setViewingHistory] = useState<string | null>(null) @@ -57,6 +58,7 @@ export function PolicyManagementClient({ initialData }: PolicyManagementClientPr const { toast } = useToast(); const router = useRouter() + const policyTypes = [ { key: 'privacy_policy', @@ -91,11 +93,13 @@ export function PolicyManagementClient({ initialData }: PolicyManagementClientPr startTransition(async () => { try { - const history = await getPolicyHistory(policyType) - setPolicies(prev => ({ - ...prev, - [policyType]: history || [] // ✅ null/undefined 방지 - })) + const result = await getPolicyHistory(policyType, currentLocale) + if (result.success) { + setPolicies(prev => ({ + ...prev, + [policyType]: result.data || [] + })) + } } catch (error) { console.error('Policy history error:', error) toast({ @@ -134,6 +138,7 @@ export function PolicyManagementClient({ initialData }: PolicyManagementClientPr const result = await createPolicyVersion({ policyType: policyType as 'privacy_policy' | 'terms_of_service', + locale: currentLocale, version, content, effectiveDate: new Date() @@ -249,6 +254,7 @@ export function PolicyManagementClient({ initialData }: PolicyManagementClientPr onCancel={() => setEditingPolicy(null)} onPreview={handlePreview} isLoading={isPending} + currentLocale={currentLocale} /> ) } diff --git a/components/polices/policy-page-client.tsx b/components/polices/policy-page-client.tsx new file mode 100644 index 00000000..ccc87c31 --- /dev/null +++ b/components/polices/policy-page-client.tsx @@ -0,0 +1,281 @@ +'use client' + +import { useState } from 'react' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { FileText, Shield, Clock, Calendar, User } from 'lucide-react' +import { PolicyManagementClient } from '@/components/polices/policy-management-client' + +interface Policy { + id: number + policyType: string + locale: string + version: string + content: string + effectiveDate: string + isCurrent: boolean + createdAt: string +} + +interface PolicyPageClientProps { + data: { + currentPolicies: { + ko: Record<string, Policy> + en: Record<string, Policy> + } + allPolicies: { + ko: { + privacy_policy: Policy[] + terms_of_service: Policy[] + } + en: { + privacy_policy: Policy[] + terms_of_service: Policy[] + } + } + stats: { + totalVersions: number + koVersions: { + privacy: number + terms: number + } + enVersions: { + privacy: number + terms: number + } + lastUpdate: string | null + } + } +} + +// 선택된 locale의 가장 최근 업데이트 날짜를 가져오는 함수 +function getLastUpdateForLocale(data: PolicyPageClientProps['data'], locale: 'ko' | 'en'): string | null { + const allPolicies = [ + ...(data.allPolicies[locale]?.privacy_policy || []), + ...(data.allPolicies[locale]?.terms_of_service || []) + ] + + if (allPolicies.length === 0) return null + + // 가장 최근 createdAt 날짜 찾기 + const sortedPolicies = allPolicies.sort((a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ) + + return sortedPolicies[0]?.createdAt || null +} + +export function PolicyPageClient({ data }: PolicyPageClientProps) { + const [selectedLocale, setSelectedLocale] = useState<'ko' | 'en'>('ko') + + // 선택된 locale에 맞는 데이터 필터링 + const filteredData = { + currentPolicies: { + privacy_policy: data.currentPolicies[selectedLocale]?.privacy_policy || null, + terms_of_service: data.currentPolicies[selectedLocale]?.terms_of_service || null + }, + allPolicies: { + privacy_policy: data.allPolicies[selectedLocale]?.privacy_policy || [], + terms_of_service: data.allPolicies[selectedLocale]?.terms_of_service || [] + }, + stats: { + totalVersions: data.stats[`${selectedLocale}Versions`].privacy + data.stats[`${selectedLocale}Versions`].terms, + privacyVersions: data.stats[`${selectedLocale}Versions`].privacy, + termsVersions: data.stats[`${selectedLocale}Versions`].terms, + lastUpdate: getLastUpdateForLocale(data, selectedLocale) + } + } + + return ( + <div className="container mx-auto py-6 space-y-6"> + {/* 헤더 */} + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight">정책 관리</h2> + <p className="text-muted-foreground"> + 개인정보 처리방침과 이용약관을 버전별로 관리합니다 + </p> + </div> + + {/* 전역 locale 토글 */} + <div className="flex items-center gap-3"> + <div className="flex bg-muted rounded-lg p-1"> + <button + className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${ + selectedLocale === 'ko' + ? 'bg-background shadow-sm' + : 'text-muted-foreground hover:text-foreground' + }`} + onClick={() => setSelectedLocale('ko')} + > + KO + </button> + <button + className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${ + selectedLocale === 'en' + ? 'bg-background shadow-sm' + : 'text-muted-foreground hover:text-foreground' + }`} + onClick={() => setSelectedLocale('en')} + > + EN + </button> + </div> + </div> + </div> + + {/* 통계 카드들 */} + <div className="grid gap-4 md:grid-cols-4"> + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium">총 버전 수</CardTitle> + <FileText className="h-4 w-4 text-muted-foreground" /> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold">{filteredData.stats.totalVersions}</div> + <p className="text-xs text-muted-foreground"> + 전체 정책 버전 + </p> + </CardContent> + </Card> + + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium">개인정보 정책</CardTitle> + <Shield className="h-4 w-4 text-muted-foreground" /> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold">{filteredData.stats.privacyVersions}</div> + <p className="text-xs text-muted-foreground"> + 버전 수 + </p> + </CardContent> + </Card> + + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium">이용약관</CardTitle> + <FileText className="h-4 w-4 text-muted-foreground" /> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold">{filteredData.stats.termsVersions}</div> + <p className="text-xs text-muted-foreground"> + 버전 수 + </p> + </CardContent> + </Card> + + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium">최근 업데이트</CardTitle> + <Clock className="h-4 w-4 text-muted-foreground" /> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold"> + {filteredData.stats.lastUpdate + ? new Date(filteredData.stats.lastUpdate).toLocaleDateString('ko-KR').split('.').slice(1, 3).join('.') + : 'N/A' + } + </div> + <p className="text-xs text-muted-foreground"> + 마지막 정책 변경 + </p> + </CardContent> + </Card> + </div> + + {/* 현재 활성 정책들 */} + <div className="grid gap-6 md:grid-cols-2"> + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <Shield className="h-5 w-5" /> + 개인정보 처리방침 + </CardTitle> + <CardDescription> + {filteredData.currentPolicies.privacy_policy ? + `현재 활성 정책 • 시행일: ${new Date(filteredData.currentPolicies.privacy_policy.effectiveDate).toLocaleDateString('ko-KR')}` : + `아직 등록된 ${selectedLocale === 'ko' ? '한국어' : '영어'} 개인정보 정책이 없습니다` + } + </CardDescription> + </CardHeader> + <CardContent> + <div className="space-y-3"> + <div className="text-sm"> + <strong>정책 내용 미리보기:</strong> + </div> + <div className="bg-muted/50 p-3 rounded-md text-sm max-h-32 overflow-hidden"> + <div className="line-clamp-4"> + {filteredData.currentPolicies.privacy_policy ? + filteredData.currentPolicies.privacy_policy.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200) + '...' : + `아직 등록된 ${selectedLocale === 'ko' ? '한국어' : '영어'} 개인정보 정책이 없습니다` + } + </div> + </div> + + {/* 메타 정보 */} + <div className="flex items-center justify-between text-xs text-muted-foreground"> + <div className="flex items-center gap-1"> + <Calendar className="h-3 w-3" /> + 생성: {filteredData.currentPolicies.privacy_policy?.createdAt ? new Date(filteredData.currentPolicies.privacy_policy.createdAt).toLocaleDateString('ko-KR') : 'N/A'} + </div> + <div className="flex items-center gap-1"> + <User className="h-3 w-3" /> + 관리자 + </div> + </div> + </div> + </CardContent> + </Card> + + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <FileText className="h-5 w-5" /> + 이용약관 + </CardTitle> + <CardDescription> + {filteredData.currentPolicies.terms_of_service ? + `현재 활성 정책 • 시행일: ${new Date(filteredData.currentPolicies.terms_of_service.effectiveDate).toLocaleDateString('ko-KR')}` : + `아직 등록된 ${selectedLocale === 'ko' ? '한국어' : '영어'} 이용약관이 없습니다` + } + </CardDescription> + </CardHeader> + <CardContent> + <div className="space-y-3"> + <div className="text-sm"> + <strong>정책 내용 미리보기:</strong> + </div> + <div className="bg-muted/50 p-3 rounded-md text-sm max-h-32 overflow-hidden"> + <div className="line-clamp-4"> + {filteredData.currentPolicies.terms_of_service ? + filteredData.currentPolicies.terms_of_service.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200) + '...' : + `아직 등록된 ${selectedLocale === 'ko' ? '한국어' : '영어'} 이용약관이 없습니다` + } + </div> + </div> + + {/* 메타 정보 */} + <div className="flex items-center justify-between text-xs text-muted-foreground"> + <div className="flex items-center gap-1"> + <Calendar className="h-3 w-3" /> + 생성: {filteredData.currentPolicies.terms_of_service?.createdAt ? new Date(filteredData.currentPolicies.terms_of_service.createdAt).toLocaleDateString('ko-KR') : 'N/A'} + </div> + <div className="flex items-center gap-1"> + <User className="h-3 w-3" /> + 관리자 + </div> + </div> + </div> + </CardContent> + </Card> + </div> + + {/* 정책 편집 부분 - 선택된 locale의 데이터만 표시 */} + <PolicyManagementClient + key={selectedLocale} + initialData={filteredData} + currentLocale={selectedLocale} + /> + </div> + ) +} diff --git a/components/signup/conset-step.tsx b/components/signup/conset-step.tsx index 4d0a544f..3a145ef1 100644 --- a/components/signup/conset-step.tsx +++ b/components/signup/conset-step.tsx @@ -11,6 +11,7 @@ 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; @@ -56,11 +57,11 @@ export default function ConsentStep({ data, onChange, onNext, lng }: ConsentStep setLoading(true); setError(null); - const result = await getCurrentPolicies(); + const result = await getCurrentPolicies(lng); if (result.success) { console.log('Policy data loaded:', result.data); - setPolicyData(result.data); + setPolicyData(result.data[lng]); } else { setError(result.error || t('policyLoadError')); console.error('Failed to fetch policy data:', result.error); |
