fix(sirsoft-gdpr): 관리자 환경설정 카테고리 목록 id 중복 + 다크모드 배지 색상 누락 수정
- cookie_categories_rows / blocked_domains_taginputs iteration 컨테이너의 정적 id를 index_var(categoryIdx)로 동적화 — 카테고리가 여러 개일 때 동일 HTML id가 중복 렌더되던 W3C id 유일성 위반 수정 (audit layout-iteration-static-id) - 카테고리 「필수」 배지 dark:bg-red-800 / text-danger-soft 클래스 사이 공백 누락으로 다크 모드 배경·글자색이 미적용되던 오타 수정 이슈 작업 중 audit-branch 전수 검사에서 발견한 기존 결함 (이번 이슈 기능과 무관, )
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
|
||||
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
|
||||
|
||||
## [1.0.2] - 2026-07-14
|
||||
## [1.0.2] - 2026-07-27
|
||||
|
||||
### Added
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
- 「동의하지 않고 계속하기」를 눌러도 필수 쿠키가 「동의함」으로 기록되지 않도록 바로잡았습니다 — 필수 쿠키는 서비스 이용에 반드시 필요하여 동의 대상이 아닙니다.
|
||||
- 거부 시 안내 문구를 「동의하지 않고 계속합니다」로 바로잡았습니다 (이전에는 「동의가 저장되었습니다」로 잘못 표시).
|
||||
|
||||
### Fixed
|
||||
|
||||
- 관리자 환경설정의 쿠키 카테고리·차단 도메인 목록에서 항목이 여러 개일 때 화면 요소 식별자가 중복되던 문제를 바로잡았습니다.
|
||||
- 관리자 환경설정의 카테고리 「필수」 배지가 다크 모드에서 배경·글자색이 적용되지 않던 문제를 바로잡았습니다.
|
||||
- 관리자 환경설정의 「운영 주체 / 개인정보처리방침」 섹션 제목 영역이 다른 카드와 다르게 표시되던 문제를 바로잡았습니다.
|
||||
- 관리자 환경설정의 저장/취소 버튼을 다른 관리자 화면과 동일하게 화면 하단 고정 위치로 옮기고, 상단 탭 영역의 불필요한 구분선과 좌우 여백 어긋남을 바로잡았습니다.
|
||||
|
||||
## [1.0.1] - 2026-07-16
|
||||
|
||||
### Changed
|
||||
|
||||
+14
-14
@@ -140,7 +140,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
* 운영자가 카드에서 required 값을 변경하는 위젯/액션은 존재하지 않는다.
|
||||
*/
|
||||
it('카테고리 카드 영역에 Toggle/setState(required) 위젯이 없다', () => {
|
||||
const rows = findById(root, 'cookie_categories_rows');
|
||||
const rows = findById(root, 'cookie_categories_rows_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(rows);
|
||||
|
||||
expect(serialized).not.toContain('"name":"Toggle"');
|
||||
@@ -149,7 +149,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('카테고리 카드 헤더에 「필수」/「선택」 배지가 required 값으로 분기 렌더된다', () => {
|
||||
const rows = findById(root, 'cookie_categories_rows');
|
||||
const rows = findById(root, 'cookie_categories_rows_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(rows);
|
||||
|
||||
expect(serialized).toContain('category_required_badge');
|
||||
@@ -167,7 +167,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
*/
|
||||
it('Phase 1: cookie_categories 영역에 functional 분기 (아이콘 + scope + tools) 존재', () => {
|
||||
// 실제 카테고리 카드는 cookie_categories_rows iteration 노드에 위치 (subsection 헤더 별개)
|
||||
const rows = findById(root, 'cookie_categories_rows');
|
||||
const rows = findById(root, 'cookie_categories_rows_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(rows);
|
||||
|
||||
// 1. 카테고리 아이콘 분기 (fa-sliders / emerald 색상)
|
||||
@@ -186,7 +186,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
// 법령 인용 (ePrivacy Art.5(3) / GDPR Art.6(1)(a) 등) 은 운영자 의사결정에 불필요하여
|
||||
// 펼치기 영역에서 제거되었다. 정책 버전 snapshot 에 카테고리 본문이 영속되므로
|
||||
// 감사 자료는 그쪽에서 확인 가능.
|
||||
const rows = findById(root, 'cookie_categories_rows');
|
||||
const rows = findById(root, 'cookie_categories_rows_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(rows);
|
||||
|
||||
expect(serialized).not.toContain('cookie_categories.info.necessary.legal_basis');
|
||||
@@ -197,7 +197,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('Phase 1: 도메인 차단 섹션의 카테고리 아이콘에 functional 분기 존재', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(wrapper);
|
||||
|
||||
// functional 카테고리 분기 (sliders 아이콘 + emerald 색상)
|
||||
@@ -331,7 +331,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('blocked_domains_taginputs iteration 이 cookie_categories 에서 necessary 제외하고 렌더', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
expect(wrapper).toBeTruthy();
|
||||
expect(wrapper?.iteration?.source).toBe(
|
||||
"(_local.form?.cookie_categories ?? []).filter(c => c?.key !== 'necessary')"
|
||||
@@ -340,7 +340,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('TagInput 가 카테고리별 도메인 입력으로 사용되며 creatable=true 자유 입력 허용', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(wrapper);
|
||||
// TagInput composite 사용
|
||||
expect(serialized).toContain('"name":"TagInput"');
|
||||
@@ -353,7 +353,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('TagInput change 이벤트가 $event.target.value 에서 string 배열을 추출하여 form.blocked_domains 정적 키로 setState', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(wrapper);
|
||||
// 정적 setState 키 — form.blocked_domains 만 사용 (CRITICAL 규정 준수)
|
||||
expect(serialized).toContain('"form.blocked_domains"');
|
||||
@@ -368,7 +368,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('카탈로그 도메인 일괄 추가 링크 미존재 — 신규 설치 시 카탈로그가 이미 기본값으로 채워져 있고, 카탈로그 갱신 빈도 낮음. 운영자가 의도적으로 제외한 도메인이 재추가되는 위험 회피 (검토 #9)', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(wrapper);
|
||||
// 일괄 추가 링크 라벨/툴팁 키 사용 금지 (lang 키 자체 제거됨)
|
||||
expect(serialized).not.toContain('blocked_domains.add_catalog_link');
|
||||
@@ -392,7 +392,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
});
|
||||
|
||||
it('422 검증 에러 시 카테고리 카드 외곽이 빨간 테두리로 강조 + 카드 내부에 첫 잘못된 도메인 에러 메시지 Span 노출 (검토 #10 옵션 B — 카테고리 내 어느 인덱스든 매칭)', () => {
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs');
|
||||
const wrapper = findById(root, 'blocked_domains_taginputs_{{categoryIdx}}');
|
||||
const serialized = JSON.stringify(wrapper);
|
||||
// 카드 외곽 동적 className — 카테고리 내 *어느 인덱스든* 에러 키가 있으면 true (some)
|
||||
expect(serialized).toContain("Object.keys(_local.errors ?? {}).some(k => k.startsWith('blocked_domains.' + category.key + '.'))");
|
||||
@@ -406,8 +406,8 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
expect(serialized).toContain('"form.blocked_domains"');
|
||||
});
|
||||
|
||||
it('header_save_button onError 토스트가 422 응답의 첫 번째 검증 에러 메시지를 노출 + fallback 일반화 메시지 유지 (검토 #10 옵션 A 기반)', () => {
|
||||
const saveBtn = findById(root, 'header_save_button');
|
||||
it('footer_save_button onError 토스트가 422 응답의 첫 번째 검증 에러 메시지를 노출 + fallback 일반화 메시지 유지 (검토 #10 옵션 A 기반)', () => {
|
||||
const saveBtn = findById(root, 'footer_save_button');
|
||||
const serialized = JSON.stringify(saveBtn);
|
||||
// 첫 번째 검증 에러 메시지 추출 표현식
|
||||
expect(serialized).toContain('Object.values(error.errors ?? {})[0]');
|
||||
@@ -551,8 +551,8 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
|
||||
expect(Object.keys(params ?? {})).not.toContain('policyGuideOpen');
|
||||
});
|
||||
|
||||
it('상단 저장 sequence 는 단순 save_success 토스트만 노출 — published_version 분기 / 정책 버전 refetch 제거 (수동 발행 모델)', () => {
|
||||
const btn = findById(root, 'header_save_button');
|
||||
it('하단 저장 sequence 는 단순 save_success 토스트만 노출 — published_version 분기 / 정책 버전 refetch 제거 (수동 발행 모델)', () => {
|
||||
const btn = findById(root, 'footer_save_button');
|
||||
const serialized = JSON.stringify(btn);
|
||||
// 옛 자동 발행 흐름의 분기 / refetch 제거 — 저장은 정책 버전을 발행하지 않으므로 분기 불필요
|
||||
expect(serialized).not.toContain('response?.data?.published_version');
|
||||
|
||||
@@ -92,14 +92,14 @@
|
||||
"dataKey": "form",
|
||||
"trackChanges": true,
|
||||
"props": {
|
||||
"className": "bg-gray-50 dark:bg-gray-900 min-h-screen"
|
||||
"className": "admin-page-content-responsive-fluid"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "sticky_header",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"comment": "헤더 + 탭을 sticky 컨테이너로 묶음 (게시판 설정과 동일한 구조)",
|
||||
"comment": "헤더 + 탭을 sticky 컨테이너로 묶음 (게시판 설정과 동일한 구조 — root 가 admin-page-content-responsive-fluid 로 좌우 패딩을 이미 갖고 있으므로 header_content 는 자체 좌우 패딩을 두지 않는다. 그래야 sticky_header 의 border-b 폭이 아래 카드 폭과 정확히 일치한다).",
|
||||
"props": {
|
||||
"className": "sticky-section-header"
|
||||
},
|
||||
@@ -109,16 +109,14 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": {
|
||||
"className": "px-4 sm:px-6 lg:px-8 pt-4 pb-0"
|
||||
"className": "pt-4 pb-0"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "header_top_row",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": {
|
||||
"className": "panel-header-row"
|
||||
},
|
||||
"comment": "제목 wrapper — 게시판 설정(page_header_wrapper)과 동일하게 별도 className 없음. panel-header-row(border-b 포함) 를 쓰면 탭 위에 불필요한 구분선이 하나 더 생긴다.",
|
||||
"children": [
|
||||
{
|
||||
"id": "page_header",
|
||||
@@ -130,163 +128,6 @@
|
||||
"backPath": "/admin/plugins",
|
||||
"backLabel": "$t:admin.plugins.back_to_list"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "header_save_buttons",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": {
|
||||
"className": "flex-center gap-3 flex-shrink-0 pt-1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"if": "{{_local.hasChanges}}",
|
||||
"props": {
|
||||
"className": "flex-center gap-1.5 text-sm text-amber-600 dark:text-amber-400"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"props": {
|
||||
"name": "circle-dot",
|
||||
"size": "xs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "$t:sirsoft-gdpr.settings.unsaved_changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "header_cancel_button",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"type": "button",
|
||||
"className": "btn btn-secondary text-sm"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "navigate",
|
||||
"params": {
|
||||
"path": "/admin/plugins"
|
||||
}
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "$t:common.cancel"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "header_save_button",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"type": "button",
|
||||
"disabled": "{{!_local.hasChanges || _local.isSaving}}",
|
||||
"className": "btn btn-primary flex-center gap-1.5 text-sm"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "sequence",
|
||||
"actions": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": true,
|
||||
"errors": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "apiCall",
|
||||
"auth_required": true,
|
||||
"target": "/api/plugins/sirsoft-gdpr/admin/settings",
|
||||
"params": {
|
||||
"method": "PUT",
|
||||
"body": "{{_local.form}}"
|
||||
},
|
||||
"onSuccess": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": false,
|
||||
"hasChanges": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "refetchDataSource",
|
||||
"params": {
|
||||
"dataSourceId": "gdprSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "toast",
|
||||
"params": {
|
||||
"type": "success",
|
||||
"message": "$t:sirsoft-gdpr.settings.save_success"
|
||||
}
|
||||
}
|
||||
],
|
||||
"onError": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": false,
|
||||
"errors": "{{error.errors}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "toast",
|
||||
"params": {
|
||||
"type": "error",
|
||||
"message": "{{Object.values(error.errors ?? {})[0]?.[0] ?? '$t:sirsoft-gdpr.settings.save_error'}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"if": "{{_local.isSaving}}",
|
||||
"props": {
|
||||
"name": "spinner",
|
||||
"size": "sm",
|
||||
"spin": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{!_local.isSaving}}",
|
||||
"text": "$t:common.save"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{_local.isSaving}}",
|
||||
"text": "$t:common.saving"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -294,7 +135,7 @@
|
||||
"id": "settings_tab_navigation",
|
||||
"type": "composite",
|
||||
"name": "TabNavigationScroll",
|
||||
"comment": "상단 탭 스크롤 — 클릭 시 해당 카드로 자동 스크롤 + 스크롤 시 활성 탭 자동 갱신",
|
||||
"comment": "상단 탭 스크롤 — settings_main_content(p-4 sm:p-6 lg:p-8) 안 카드들과 좌우 정렬을 맞추기 위해 header_content 패딩을 그대로 물려받는다(자체 좌우 패딩 없음). 클릭 시 해당 카드로 자동 스크롤 + 스크롤 시 활성 탭 자동 갱신.",
|
||||
"props": {
|
||||
"tabs": [
|
||||
{
|
||||
@@ -326,9 +167,9 @@
|
||||
"id": "settings_main_content",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"comment": "5 카드 컨테이너 (transition_overlay 타겟)",
|
||||
"comment": "5 카드 컨테이너 (transition_overlay 타겟). root(admin-page-content-responsive-fluid) 가 좌우 패딩을 이미 가지므로 여기서는 세로 여백만 둔다. 상단 pt-6 은 게시판 설정(.tab-content — sticky 탭과 콘텐츠 사이 표준 24px)과 동일 값.",
|
||||
"props": {
|
||||
"className": "p-4 sm:p-6 lg:p-8"
|
||||
"className": "pt-6 pb-6"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
@@ -411,7 +252,7 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": {
|
||||
"className": "flex-start justify-between gap-4 mb-3"
|
||||
"className": "panel-header-row"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
@@ -1364,7 +1205,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cookie_categories_rows",
|
||||
"id": "cookie_categories_rows_{{categoryIdx}}",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"comment": "카드 구조: 헤더(아이콘+이름+key+필수/선택 배지) + 운영 정보 펼침. 카드는 정보 패널이며, 카테고리·필수 여부는 CookieCategory enum 이 SSoT.",
|
||||
@@ -1488,7 +1329,7 @@
|
||||
"name": "Span",
|
||||
"if": "{{category?.required === true}}",
|
||||
"props": {
|
||||
"className": "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-800text-danger-soft"
|
||||
"className": "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-800 text-danger-soft"
|
||||
},
|
||||
"text": "$t:sirsoft-gdpr.settings.fields.cookie_categories.category_required_badge"
|
||||
},
|
||||
@@ -1832,7 +1673,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blocked_domains_taginputs",
|
||||
"id": "blocked_domains_taginputs_{{categoryIdx}}",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"comment": "카테고리별 TagInput iteration — necessary 제외. options 에 카탈로그 도메인을 추천 노출 (creatable=true 로 자유 입력 가능)",
|
||||
@@ -1944,6 +1785,164 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "footer_buttons",
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"comment": "하단 sticky 저장/취소 바 — 게시판 설정(admin_board_settings.json footer_buttons)과 동일 패턴으로 통일 (상단 헤더 배치는 스크롤 시 밀리는 것으로 오인되어 재배치).",
|
||||
"props": {
|
||||
"className": "sticky-footer-buttons"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"if": "{{_local.hasChanges}}",
|
||||
"props": {
|
||||
"className": "flex-center gap-1.5 text-sm text-amber-600 dark:text-amber-400 mr-auto"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"props": {
|
||||
"name": "circle-dot",
|
||||
"size": "xs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "$t:sirsoft-gdpr.settings.unsaved_changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "footer_cancel_button",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"type": "button",
|
||||
"className": "btn btn-secondary text-sm"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "navigate",
|
||||
"params": {
|
||||
"path": "/admin/plugins"
|
||||
}
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "$t:common.cancel"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "footer_save_button",
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": {
|
||||
"type": "button",
|
||||
"disabled": "{{!_local.hasChanges || _local.isSaving}}",
|
||||
"className": "btn btn-primary flex-center gap-1.5 text-sm"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "sequence",
|
||||
"actions": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": true,
|
||||
"errors": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "apiCall",
|
||||
"auth_required": true,
|
||||
"target": "/api/plugins/sirsoft-gdpr/admin/settings",
|
||||
"params": {
|
||||
"method": "PUT",
|
||||
"body": "{{_local.form}}"
|
||||
},
|
||||
"onSuccess": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": false,
|
||||
"hasChanges": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "refetchDataSource",
|
||||
"params": {
|
||||
"dataSourceId": "gdprSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "toast",
|
||||
"params": {
|
||||
"type": "success",
|
||||
"message": "$t:sirsoft-gdpr.settings.save_success"
|
||||
}
|
||||
}
|
||||
],
|
||||
"onError": [
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"isSaving": false,
|
||||
"errors": "{{error.errors}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "toast",
|
||||
"params": {
|
||||
"type": "error",
|
||||
"message": "{{Object.values(error.errors ?? {})[0]?.[0] ?? '$t:sirsoft-gdpr.settings.save_error'}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"if": "{{_local.isSaving}}",
|
||||
"props": {
|
||||
"name": "spinner",
|
||||
"size": "sm",
|
||||
"spin": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{!_local.isSaving}}",
|
||||
"text": "$t:common.save"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{_local.isSaving}}",
|
||||
"text": "$t:common.saving"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user