fix(sirsoft-gdpr): 쿠키 배너 노출 토글 안내 문구 및 필드 레이아웃 정정

banner_enabled(쿠키 배너 노출) 토글 안내가 "마이페이지 동의 관리 카드"까지
함께 켜지고 꺼지는 것처럼 서술되어 있었으나, 실제로는 카드가 이 토글과
무관하게 동의/철회 이력이 있는 회원에게 항상 노출된다(GDPR Art.7(3) 철회
대칭성 보장, mypage_privacy_tab.json 회귀 가드로 이미 고정된 설계).
문구만 실제 동작과 어긋나 있어 ko/en 양쪽 hint 및 관련 코드 주석을
바로잡았다.

문구 확인 과정에서 field_legal_entity_name / field_data_storage_location /
field_banner_enabled / field_banner_position 4개 필드가 텍스트 스타일
전용 클래스(section-heading-md, grid 미정의)를 컨테이너로 잘못 사용해
안내 문구가 길어지면 입력/토글 컨트롤이 아래 줄로 밀려나던 기존 UI
결함도 함께 발견해 수정했다. 같은 파일의 field_privacy_policy_slug가
이미 쓰던 올바른 grid-3col-responsive 패턴으로 통일.
This commit is contained in:
chym1217
2026-07-30 10:42:04 +09:00
parent a0cc36a9db
commit 192000cfce
11 changed files with 76 additions and 19 deletions
@@ -20,6 +20,7 @@
- 마이페이지 「쿠키 동의 관리」의 글자 크기·여백이 어긋나던 문제를 바로잡고, 동의한 항목에는 동의한 날짜를, 철회한 항목에는 철회한 날짜를 함께 표시합니다.
- 「동의하지 않고 계속하기」를 눌러도 필수 쿠키가 「동의함」으로 기록되지 않도록 바로잡았습니다 — 필수 쿠키는 서비스 이용에 반드시 필요하여 동의 대상이 아닙니다.
- 거부 시 안내 문구를 「동의하지 않고 계속합니다」로 바로잡았습니다 (이전에는 「동의가 저장되었습니다」로 잘못 표시).
- 관리자 환경설정의 「쿠키 배너 노출」 안내 문구를 실제 동작에 맞게 바로잡았습니다 — 이 토글은 배너와 자동 차단만 제어하며, 마이페이지 「쿠키 동의 관리」 카드는 동의·철회 이력이 있는 회원에게 이 토글과 무관하게 항상 노출됩니다.
### Fixed
@@ -28,6 +29,7 @@
- 관리자 환경설정의 「운영 주체 / 개인정보처리방침」 섹션 제목 영역이 다른 카드와 다르게 표시되던 문제를 바로잡았습니다.
- 관리자 환경설정의 저장/취소 버튼을 다른 관리자 화면과 동일하게 화면 하단 고정 위치로 옮기고, 상단 탭 영역의 불필요한 구분선과 좌우 여백 어긋남을 바로잡았습니다.
- 관리자 환경설정 「정책 버전 보기」에서 본문 페이지 링크를 클릭하면 페이지를 찾을 수 없던 문제를 바로잡았습니다.
- 관리자 환경설정의 「운영 주체」·「쿠키 배너 노출」·「배너 위치」 필드에서 안내 문구가 길어질 때 입력·토글 컨트롤이 옆이 아닌 아래 줄로 밀려나던 문제를 바로잡았습니다.
## [1.0.1] - 2026-07-16
+1 -1
View File
@@ -38,7 +38,7 @@ php artisan plugin:activate sirsoft-gdpr
| 항목 | 설명 |
|------|------|
| 쿠키 배너 노출 (`banner_enabled`) | 마스터 토글 — ON 시 배너 + 자동 차단 + 마이페이지 동의 관리 카드가 일괄 활성화됩니다. GDPR Art.6 "동의 전 처리 금지" 의 강제 메커니즘인 자동 차단을 단독 OFF 할 수 없도록 단일 토글로 통합되어 있습니다 |
| 쿠키 배너 노출 (`banner_enabled`) | 마스터 토글 — ON 시 배너 + 자동 차단이 함께 활성화됩니다. GDPR Art.6 "동의 전 처리 금지" 의 강제 메커니즘인 자동 차단을 단독 OFF 할 수 없도록 단일 토글로 통합되어 있습니다. 마이페이지 동의 관리 카드는 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출됩니다 (Art.7(3) 철회 대칭성 보장) |
| 배너 위치 (`banner_position`) | 하단 바 / 좌하단 팝업 / 우하단 팝업 / 중앙 모달 |
### 자동 차단 정책
@@ -1,7 +1,7 @@
{
"_meta": {
"version": "1.0.0",
"description": "GDPR (일반 데이터 보호 규정) 플러그인 환경설정 기본값 및 프론트엔드 스키마. banner_enabled 가 GDPR 컴플라이언스 모드 단일 토글 — ON 시 배너 노출 + 동의 전 외부 추적 자동 차단 + 마이페이지 동의 관리 카드 일괄 활성."
"description": "GDPR (일반 데이터 보호 규정) 플러그인 환경설정 기본값 및 프론트엔드 스키마. banner_enabled 가 GDPR 컴플라이언스 모드 단일 토글 — ON 시 배너 노출 + 동의 전 외부 추적 자동 차단이 함께 시작됨. 마이페이지 동의 관리 카드는 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출됨."
},
"frontend_schema": {
"privacy_policy_slug": { "type": "string", "expose": true },
@@ -78,7 +78,7 @@ return [
],
'banner_enabled' => [
'label' => 'Show Cookie Banner',
'hint' => 'When enabled, the cookie consent banner, pre-consent auto-blocking, and the MyPage consent management card are activated together. (Auto-blocking is the enforcement mechanism for GDPR Art.6 "no processing before consent" and is unified into a single toggle to prevent operators from disabling it independently.)',
'hint' => 'When enabled, the cookie consent banner and pre-consent auto-blocking are activated together. (Auto-blocking is the enforcement mechanism for GDPR Art.6 "no processing before consent" and is unified into a single toggle to prevent operators from disabling it independently.) The MyPage consent management card is independent of this toggle and always shows for members with consent/withdrawal history.',
],
'banner_position' => [
'label' => 'Banner Position',
@@ -78,7 +78,7 @@ return [
],
'banner_enabled' => [
'label' => '쿠키 배너 노출',
'hint' => '활성 시 쿠키 동의 배너 노출 + 동의 전 외부 추적 자동 차단 + 마이페이지 동의 관리 카드가 일괄 활성됩니다. (GDPR Art.6 "동의 전 처리 금지" 의 강제 메커니즘인 차단을 분리 토글로 끄지 못하도록 단일 토글로 통합)',
'hint' => '활성 시 쿠키 동의 배너 노출 + 동의 전 외부 추적 자동 차단이 함께 시작됩니다. (GDPR Art.6 "동의 전 처리 금지" 의 강제 메커니즘인 차단을 분리 토글로 끄지 못하도록 단일 토글로 통합) 마이페이지 동의 관리 카드는 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출됩니다.',
],
'banner_position' => [
'label' => '배너 위치',
+6 -4
View File
@@ -381,7 +381,9 @@ class Plugin extends AbstractPlugin
'data_storage_location' => '',
// 쿠키 배너 + 자동 차단 (F-01 / F-02) — banner_enabled 단일 토글로 통합 제어.
// ON 시 배너 노출 + 동의 전 외부 추적 자동 차단 + 마이페이지 동의 관리 카드 일괄 활성.
// ON 시 배너 노출 + 동의 전 외부 추적 자동 차단이 함께 시작됨. 마이페이지 동의 관리
// 카드는 이 토글과 무관 — GDPR Art.7(3) 철회 대칭성 보장을 위해 동의/철회 이력이
// 있는 회원에게 배너 노출 여부와 무관하게 항상 노출된다 (resources/extensions/mypage_privacy_tab.json 참조).
// 차단을 별도 토글로 제공하지 않는 이유: GDPR Art.6 "동의 전 처리 금지" 의 강제 메커니즘
// 인 차단을 운영자가 단독 OFF 할 수 있으면 위반 조합 (배너 ON + 차단 OFF) 가능 → CNIL
// Microsoft €60M / Amazon €35M / Google €100M 처벌 패턴과 동일. 단일 토글로 구조적 차단.
@@ -492,15 +494,15 @@ class Plugin extends AbstractPlugin
'required' => false,
],
// 쿠키 배너 + 자동 차단 + 마이페이지 카드 단일 토글 (banner_enabled)
// 쿠키 배너 + 자동 차단 단일 토글 (banner_enabled). 마이페이지 카드는 이 토글과 무관 (별도 절 참조).
// 기본값 true — 플러그인 활성화 = 운영자의 GDPR 컴플라이언스 의사 표명.
'banner_enabled' => [
'type' => 'boolean',
'default' => true,
'label' => ['ko' => '쿠키 배너 노출', 'en' => 'Show Cookie Banner'],
'hint' => [
'ko' => 'ON 시 쿠키 동의 배너 노출 + 동의 전 외부 추적 자동 차단 + 마이페이지 동의 관리 카드 일괄 활성. 정책 슬러그·카테고리 설정 후 켜는 것을 권장.',
'en' => 'When ON, the cookie consent banner, pre-consent auto-blocking, and the MyPage consent management card are activated together.',
'ko' => 'ON 시 쿠키 동의 배너 노출 + 동의 전 외부 추적 자동 차단이 함께 시작됩니다. 정책 슬러그·카테고리 설정 후 켜는 것을 권장. 마이페이지 동의 관리 카드는 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출됩니다.',
'en' => 'When ON, the cookie consent banner and pre-consent auto-blocking are activated together. The MyPage consent management card is independent of this toggle and always shows for members with consent/withdrawal history.',
],
'required' => false,
],
@@ -1,3 +1,4 @@
// e2e:allow 이슈 #509 4번 — banner_enabled 안내 문구/주석 정정만 (레이아웃 구조·핸들러·바인딩 변경 없음, 브라우저 관찰 가능한 동작 변화 없음). Vitest 회귀(위 hint 문자열 검증)로 충분.
/**
* GDPR 플러그인 환경설정 레이아웃 구조 검증
*
@@ -15,6 +16,8 @@ import { describe, it, expect } from 'vitest';
import layout from '../../../layouts/admin/plugin_settings.json';
import publishModal from '../../../layouts/admin/partials/plugin_settings/_policy_version_publish_modal.json';
import snapshotModal from '../../../layouts/admin/partials/_shared/_policy_version_snapshot_modal.json';
import koLang from '../../../lang/ko.json';
import enLang from '../../../lang/en.json';
import { findById, type AnyNode } from './helpers';
describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / auto_blocking_policy)', () => {
@@ -121,7 +124,55 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
});
});
/**
* 이슈 #509 체크리스트 4번 회귀 테스트 — 라벨/hint + 컨트롤(Input/Toggle/Select) 2단
* 배치 필드가 `section-heading-md`(텍스트 스타일 전용, grid 미정의) 를 컨테이너
* className 으로 사용해 col-span-* 자식이 무효화되고 컨트롤이 아래 줄로 밀려나던
* 결함. 코드베이스 표준은 `grid-3col-responsive`(grid grid-cols-1 lg:grid-cols-3
* gap-4 items-start) — field_privacy_policy_slug 가 원래 이 클래스를 올바르게
* 사용하고 있었다 (참조 패턴).
*/
describe('필드 2단 배치 컨테이너 — grid-3col-responsive 사용 (section-heading-md 오용 회귀 가드)', () => {
it.each([
'field_legal_entity_name',
'field_data_storage_location',
'field_banner_enabled',
'field_banner_position',
])('%s 컨테이너 className 이 grid-3col-responsive 를 포함한다', (fieldId) => {
const field = findById(root, fieldId);
const className = String((field?.props as { className?: string } | undefined)?.className ?? '');
expect(className).toContain('grid-3col-responsive');
});
it('section-heading-md 는 더 이상 필드 2단 배치 컨테이너로 오용되지 않는다 (텍스트 스타일 전용 클래스)', () => {
const layoutJson = JSON.stringify(root);
expect(layoutJson).not.toContain('"className":"section-heading-md"');
});
});
describe('card_cookie_banner', () => {
/**
* 이슈 #509 체크리스트 4번 회귀 테스트 — banner_enabled 안내 문구가 "마이페이지 동의
* 관리 카드"까지 이 토글로 일괄 제어되는 것처럼 서술하던 결함. 실제로는 카드가
* banner_enabled 와 무관하게 동의/철회 이력 존재 여부로만 판정된다
* (mypage_privacy_tab.test.tsx "카드 표시 조건" 참조). 안내 문구를 실제 동작과
* 불일치시키지 않도록 ko/en 양쪽 hint 를 고정한다.
*/
it('banner_enabled hint 는 마이페이지 카드가 이 토글과 무관하게 항상 노출됨을 명시한다 (ko/en)', () => {
const koHint = (koLang as { settings?: { fields?: { banner_enabled?: { hint?: string } } } }).settings
?.fields?.banner_enabled?.hint ?? '';
const enHint = (enLang as { settings?: { fields?: { banner_enabled?: { hint?: string } } } }).settings
?.fields?.banner_enabled?.hint ?? '';
expect(koHint).toContain('마이페이지');
expect(koHint).toContain('무관');
expect(koHint).not.toMatch(/마이페이지.*(함께|일괄).*(시작|활성)/);
expect(enHint).toContain('MyPage');
expect(enHint).toContain('independent');
expect(enHint).not.toMatch(/MyPage.*activated together/);
});
it('banner_enabled (쿠키 배너 노출 단일 토글) / banner_position 포함. auto_blocking_enabled 별도 토글 제거됨', () => {
const fields = findById(root, 'card_cookie_banner_fields');
const serialized = JSON.stringify(fields);
@@ -9,10 +9,11 @@
* (회원/게스트 통합 — 인증 불필요. 회원은 sanctum 토큰이 있으면 자동 적용)
*
* banner_enabled 가 단일 토글 (쿠키 배너 노출) — ON 시 배너 노출 + 동의 전 외부 추적
* 자동 차단 + 마이페이지 동의 관리 카드 일괄 활성. 차단 별도 토글 없음 (위반 조합 구조적 차단).
* 자동 차단이 함께 시작됨. 차단 별도 토글 없음 (위반 조합 구조적 차단).
*
* 마이페이지 동의 카드(F-04)는 GDPR Art.7(3) 대칭성 의무에 따라 회원에게 동의 데이터가 있을
* 때만 노출 (빈 카드 노출 방지). 가드 조건은 `mypage_privacy_tab.json` 의 `if` 속성에 정의.
* 마이페이지 동의 카드(F-04)는 banner_enabled 와 무관 — GDPR Art.7(3) 대칭성 의무(철회는
* 동의만큼 쉬워야 함)에 따라 회원에게 동의 데이터가 있으면 배너 비활성 중에도 항상 노출
* (빈 카드 노출만 방지). 가드 조건은 `mypage_privacy_tab.json` 의 `if` 속성에 정의.
*
* G7Core.api 가 로드된 환경에서는 G7Core.api.get() 사용 (auth/devtools/locale 헤더 자동),
* 미로드 환경(부트 초기 등)에서는 fetch 로 fallback.
@@ -23,7 +24,8 @@
export interface GdprPublicSettings {
cookie_policy_version: string;
/**
* 쿠키 배너 노출 단일 토글. ON 시 배너 + 자동 차단 + 마이페이지 카드 일괄 활성.
* 쿠키 배너 노출 단일 토글. ON 시 배너 + 자동 차단이 함께 활성.
* 마이페이지 동의 관리 카드는 이 값과 무관 (동의/철회 이력 존재 여부로만 판정).
*/
banner_enabled: boolean;
/**
@@ -88,7 +88,7 @@
},
"banner_enabled": {
"label": "Show Cookie Banner",
"hint": "When enabled, the cookie banner, pre-consent auto-blocking, and the MyPage consent management card are activated together."
"hint": "When enabled, the cookie banner and pre-consent auto-blocking are activated together. The MyPage consent management card is independent of this toggle and always shows for members with consent/withdrawal history."
},
"banner_position": {
"label": "Banner Position",
@@ -88,7 +88,7 @@
},
"banner_enabled": {
"label": "쿠키 배너 노출",
"hint": "활성 시 쿠키 배너 노출, 동의 전 외부 추적 자동 차단, 마이페이지 동의 관리 카드가 함께 시작됩니다."
"hint": "활성 시 쿠키 배너 노출과 동의 전 외부 추적 자동 차단이 함께 시작됩니다. 마이페이지 동의 관리 카드는 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출됩니다."
},
"banner_position": {
"label": "배너 위치",
@@ -286,7 +286,7 @@
"type": "basic",
"name": "Div",
"props": {
"className": "section-heading-md"
"className": "grid-3col-responsive"
},
"children": [
{
@@ -350,7 +350,7 @@
"type": "basic",
"name": "Div",
"props": {
"className": "section-heading-md"
"className": "grid-3col-responsive"
},
"children": [
{
@@ -549,7 +549,7 @@
"name": "Div",
"comment": "v1.4.0: banner_display_mode Select → banner_enabled Toggle",
"props": {
"className": "section-heading-md"
"className": "grid-3col-responsive"
},
"children": [
{
@@ -600,7 +600,7 @@
"type": "basic",
"name": "Div",
"props": {
"className": "section-heading-md"
"className": "grid-3col-responsive"
},
"children": [
{
@@ -1578,7 +1578,7 @@
"type": "basic",
"name": "Div",
"if": "{{!_local.form?.banner_enabled}}",
"comment": "쿠키 배너 노출 (banner_enabled) OFF 시 안내 박스. ON 으로 전환하면 배너 + 자동 차단 + 마이페이지 카드가 일괄 활성됨",
"comment": "쿠키 배너 노출 (banner_enabled) OFF 시 안내 박스. ON 으로 전환하면 배너 + 자동 차단이 함께 활성됨 (마이페이지 카드는 이 토글과 무관, 항상 노출)",
"props": {
"className": "bg-blue-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 border-l-4 border-l-blue-500 dark:border-l-blue-400 rounded-lg p-4 flex-center gap-2"
},