diff --git a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md index 557662e9..38201236 100644 --- a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md @@ -17,7 +17,7 @@ - 쿠키 배너 버튼을 정리했습니다 — 「필수만 사용」을 제거하고, 「환경설정」을 「선택 동의」로, 펼침 패널의 저장 버튼을 「선택 저장」으로 변경했습니다. - 마이페이지 「쿠키 동의 관리」를 상태 배지 + 동의/철회 버튼 방식으로 개선했습니다 — 각 항목이 「동의함/거부함/항상 적용」 상태로 표시되고, 버튼을 누르면 동의·철회 여부를 한 번 더 확인합니다. 항목명도 「기능 쿠키」처럼 알아보기 쉽게 표기합니다. -- 마이페이지 「쿠키 동의 관리」의 글자 크기·여백이 어긋나던 문제를 바로잡고, 동의한 항목에는 동의한 날짜를 함께 표시합니다. +- 마이페이지 「쿠키 동의 관리」의 글자 크기·여백이 어긋나던 문제를 바로잡고, 동의한 항목에는 동의한 날짜를, 철회한 항목에는 철회한 날짜를 함께 표시합니다. - 「동의하지 않고 계속하기」를 눌러도 필수 쿠키가 「동의함」으로 기록되지 않도록 바로잡았습니다 — 필수 쿠키는 서비스 이용에 반드시 필요하여 동의 대상이 아닙니다. - 거부 시 안내 문구를 「동의하지 않고 계속합니다」로 바로잡았습니다 (이전에는 「동의가 저장되었습니다」로 잘못 표시). diff --git a/plugins/_bundled/sirsoft-gdpr/resources/extensions/mypage_privacy_tab.json b/plugins/_bundled/sirsoft-gdpr/resources/extensions/mypage_privacy_tab.json index 8f5ca815..439b0c6e 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/extensions/mypage_privacy_tab.json +++ b/plugins/_bundled/sirsoft-gdpr/resources/extensions/mypage_privacy_tab.json @@ -592,11 +592,11 @@ "text": "{{consent?.consent_description ?? ''}}" }, { - "comment": "이슈 #430 (3안) — 라벨 아래 이력 줄: '날짜 + 동의'. 상태는 이미 라벨 옆 배지가 말하므로 여기선 시점만 강조. 서버가 동의한 항목만 status_label·status_at_formatted 를 채워 내려주므로(거부/미설정은 null) 둘 다 있을 때만 렌더.", + "comment": "이슈 #430 (3안) + #509 16번 — 라벨 아래 이력 줄: '날짜 + 동의/철회'. 동의=초록, 철회=빨강으로 둘 다 강조해 균형을 맞춘다 (배지의 동의함=green-700/green-300, 관리자 「GDPR 동의 이력」의 revoked=red 관례와 동일 톤). 서버가 동의·철회 항목만 status_label·status_at_formatted 를 채워 내려주므로(거부/미설정은 null) 둘 다 있을 때만 렌더.", "type": "basic", "name": "Div", "if": "{{!!consent?.status_label && !!consent?.status_at_formatted}}", - "props": { "className": "mt-1.5 text-xs text-gray-400 dark:text-gray-500" }, + "props": { "className": "mt-1.5 text-xs font-medium {{consent?.status === 'revoked' ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'}}" }, "children": [ { "type": "basic", "name": "Span", "text": "{{consent?.status_at_formatted ?? ''}}" }, { "type": "basic", "name": "Span", "props": { "className": "ml-1" }, "text": "{{consent?.status_label ?? ''}}" } diff --git a/plugins/_bundled/sirsoft-gdpr/resources/js/__tests__/layouts/mypage_privacy_tab.test.tsx b/plugins/_bundled/sirsoft-gdpr/resources/js/__tests__/layouts/mypage_privacy_tab.test.tsx index 2d452db5..79e3e349 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/js/__tests__/layouts/mypage_privacy_tab.test.tsx +++ b/plugins/_bundled/sirsoft-gdpr/resources/js/__tests__/layouts/mypage_privacy_tab.test.tsx @@ -311,17 +311,33 @@ describe('extensions/mypage_privacy_tab.json — 마이페이지 GDPR 동의 매 * @scenario entry=reject, subject=member, category=optional * @effects mypage_status_date_line_consented_only */ - it('이슈 #430 — 동의한 항목만 날짜 줄 노출 (서버가 동의 상태에만 status_at_formatted 제공)', () => { - // PO 결정(2026-07-20): 날짜는 "동의한 항목만". 거부·철회·미설정은 서버가 status_at_formatted 를 - // null 로 내려주므로 날짜 줄이 렌더되지 않는다. 프론트는 status_label && status_at_formatted 가 둘 다 - // 있을 때만 날짜 줄을 그린다. + it('이슈 #430/#509 — 동의·철회 이력이 있는 항목만 날짜 줄 노출 (서버가 status_at_formatted 제공)', () => { + // PO 결정(2026-07-20) + 이슈 #509 16번 갱신: 날짜는 "동의한 항목" + "철회 이력이 있는 항목". + // 거부·신규 미설정은 서버가 status_at_formatted 를 null 로 내려주므로 날짜 줄이 렌더되지 않는다. + // 프론트는 status_label && status_at_formatted 가 둘 다 있을 때만 날짜 줄을 그리며, 값 자체는 + // 서버가 상태별(동의일/철회일)로 계산해 내려주므로 프론트 조건식은 상태 무관 동일하게 유지된다. const text = serializeForSearch(section); expect(text).toContain('consent?.status_label'); expect(text).toContain('consent?.status_at_formatted'); - // 날짜 줄은 둘 다 있을 때만 (거부/미설정은 null → 미노출) + // 날짜 줄은 둘 다 있을 때만 (거부/미설정은 null → 미노출, 철회는 이제 값이 채워짐) expect(text).toContain('!!consent?.status_label && !!consent?.status_at_formatted'); }); + /** + * @scenario entry=reject, subject=member, category=optional + * @effects mypage_status_date_line_revoked_color_distinction + */ + it('이슈 #509 16번 — 동의·철회 날짜 줄을 서로 다른 색으로 균형있게 강조 (동의=초록/철회=빨강, 배지·관리자 이력 화면과 동일 톤)', () => { + // 철회만 강조되고 동의는 밋밋해 보인다는 PO 피드백 반영 — 동의도 철회와 대등하게 강조한다. + // status === 'revoked' 면 text-red-600/dark:text-red-400, 그 외(동의)는 text-green-600/dark:text-green-400. + const text = serializeForSearch(section); + expect(text).toContain("consent?.status === 'revoked'"); + expect(text).toContain('text-red-600'); + expect(text).toContain('dark:text-red-400'); + expect(text).toContain('text-green-600'); + expect(text).toContain('dark:text-green-400'); + }); + /** * @scenario entry=reject, subject=member, category=required * @effects mypage_required_shows_locked_button diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Resources/GdprUserConsentResource.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Resources/GdprUserConsentResource.php index 42c3ee4e..d781aac0 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Resources/GdprUserConsentResource.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Resources/GdprUserConsentResource.php @@ -53,15 +53,22 @@ class GdprUserConsentResource extends BaseApiResource } else { $status = 'none'; } - // 표시 날짜/라벨은 "동의한 항목만" 노출한다 (PO 결정, 2026-07-20). - // 동의 상태(required·consented)만 동의일 + '동의' 라벨을 내려주고, - // 거부·철회·미선택은 status_label·status_at_formatted 를 null 로 두어 프론트가 날짜 줄을 렌더하지 않는다. - // (거부일·철회일은 사용자 화면 확인 가치가 낮고, 감사 로그·관리자 화면에서 확인 가능) + // 표시 날짜/라벨은 "동의한 항목" + "철회 이력이 있는 항목"만 노출한다 (PO 결정, 2026-07-20 / 이슈 #509 16번 갱신). + // 동의 상태(required·consented)는 동의일 + '동의' 라벨, 철회(revoked)는 철회일 + '철회' 라벨을 내려준다. + // 거부·신규 미선택은 status_label·status_at_formatted 를 null 로 두어 프론트가 날짜 줄을 렌더하지 않는다. + // (거부일은 사용자 화면 확인 가치가 낮고, 감사 로그·관리자 화면에서 확인 가능. 철회일은 유저 본인의 + // 철회권 행사 시점 확인 필요성이 있어 노출 — 이슈 #509 16번) $isConsentState = $status === 'required' || $status === 'consented'; - $statusRawDate = $isConsentState ? $this->consented_at : null; - $statusLabel = $isConsentState - ? __('sirsoft-gdpr::messages.mypage.privacy.status.granted') - : null; + $statusRawDate = match (true) { + $isConsentState => $this->consented_at, + $status === 'revoked' => $this->revoked_at, + default => null, + }; + $statusLabel = match (true) { + $isConsentState => __('sirsoft-gdpr::messages.mypage.privacy.status.granted'), + $status === 'revoked' => __('sirsoft-gdpr::messages.mypage.privacy.status.revoked'), + default => null, + }; // 이슈 #430 (버튼 재설계) — 상태 배지 문구. 토글 대신 배지+단일버튼 UI 에서 // 오른쪽 배지에 노출한다. 철회 이력(revoked)과 신규 미선택(none)은 '미설정'으로 통합. $statusBadgeKey = match ($status) { diff --git a/plugins/_bundled/sirsoft-gdpr/tests/Unit/Http/Resources/GdprUserConsentResourceTest.php b/plugins/_bundled/sirsoft-gdpr/tests/Unit/Http/Resources/GdprUserConsentResourceTest.php index d642424d..b728a7e3 100644 --- a/plugins/_bundled/sirsoft-gdpr/tests/Unit/Http/Resources/GdprUserConsentResourceTest.php +++ b/plugins/_bundled/sirsoft-gdpr/tests/Unit/Http/Resources/GdprUserConsentResourceTest.php @@ -84,9 +84,10 @@ class GdprUserConsentResourceTest extends PluginTestCase $this->assertSame(__('sirsoft-gdpr::messages.mypage.privacy.badge.rejected'), $result['status_badge_label']); } - public function test_revoked_optional_maps_to_revoked_status_with_no_date_label(): void + public function test_revoked_optional_maps_to_revoked_status_with_revoked_date_label(): void { - // 철회도 거부와 동일 — 동의 상태가 아니므로 날짜/라벨 미노출. + // 이슈 #509 16번 — 철회 이력이 있는 항목은 철회일 + '철회' 라벨을 노출한다 + // (배지는 신규 미선택과 함께 '미설정'(unset) 유지 — 배지 통합과 날짜 줄 노출은 별개). $consent = new GdprUserConsent([ 'consent_key' => 'cookie_marketing', 'is_consented' => false, @@ -98,9 +99,8 @@ class GdprUserConsentResourceTest extends PluginTestCase $result = $this->toArray($consent); $this->assertSame('revoked', $result['status']); - $this->assertNull($result['status_label']); - $this->assertNull($result['status_at_formatted']); - // 철회는 신규 미선택과 함께 '미설정'(unset) 배지로 통합. + $this->assertSame(__('sirsoft-gdpr::messages.mypage.privacy.status.revoked'), $result['status_label']); + $this->assertNotNull($result['status_at_formatted']); $this->assertSame(__('sirsoft-gdpr::messages.mypage.privacy.badge.unset'), $result['status_badge_label']); }