@phygitallabs/phygital-consent 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/phygital-consent.css +31 -20
- package/package.json +1 -1
- package/src/components/CookieConsentBanner.tsx +397 -152
- package/src/env/constant.ts +6 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/sha256.ts +176 -0
- package/src/hooks/useConsent.ts +29 -8
- package/src/provider/PhygitalConsentProvider.tsx +5 -2
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { COOKIE_POLICY_URL, OTHER_INFO_URL } from "../env/constant";
|
|
4
5
|
import { setConsentPreferenceCookie } from "../helpers/cookie";
|
|
5
6
|
import { useCreateDeviceCookieConsent } from "../hooks/useConsent";
|
|
6
7
|
|
|
8
|
+
import { usePhygitalConsent } from "../provider/PhygitalConsentProvider";
|
|
9
|
+
|
|
7
10
|
const PREFERENCES = ["essential", "analytics", "advertising"] as const;
|
|
8
11
|
type PreferenceKey = (typeof PREFERENCES)[number];
|
|
9
12
|
|
|
10
13
|
const PREFERENCE_CONFIG: Record<
|
|
11
14
|
PreferenceKey,
|
|
12
|
-
{ label: string; description: string; disabled?: boolean }
|
|
15
|
+
{ label: string; description: string; labelVi: string; descriptionVi: string; disabled?: boolean }
|
|
13
16
|
> = {
|
|
14
17
|
essential: {
|
|
15
18
|
label: "Essential",
|
|
16
19
|
description: "Required for the site to work (e.g. security, preferences).",
|
|
17
|
-
|
|
20
|
+
labelVi: "Cần thiết",
|
|
21
|
+
descriptionVi: "Cần thiết cho hoạt động của trang (bảo mật, tùy chọn).",
|
|
18
22
|
},
|
|
19
23
|
analytics: {
|
|
20
24
|
label: "Analytics",
|
|
21
25
|
description: "Help us improve by collecting anonymous usage data.",
|
|
26
|
+
labelVi: "Phân tích",
|
|
27
|
+
descriptionVi: "Giúp chúng tôi cải thiện bằng cách thu thập dữ liệu sử dụng ẩn danh.",
|
|
22
28
|
},
|
|
23
29
|
advertising: {
|
|
24
30
|
label: "Advertising",
|
|
25
31
|
description: "Used to show you relevant ads and measure campaigns.",
|
|
32
|
+
labelVi: "Quảng cáo",
|
|
33
|
+
descriptionVi: "Dùng để hiển thị quảng cáo phù hợp và đo lường chiến dịch.",
|
|
26
34
|
},
|
|
27
35
|
};
|
|
28
36
|
|
|
29
|
-
const MOCK_CONTENT = {
|
|
30
|
-
title: "Cookie preferences",
|
|
31
|
-
description:
|
|
32
|
-
"We use cookies to improve your experience. Choose which categories to enable, then save.",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
37
|
export interface CookieConsentBannerProps {
|
|
36
|
-
/** Device ID (consumer app must provide; will be hashed by app before API if required). */
|
|
37
|
-
deviceId: string;
|
|
38
38
|
/** Called after consent is submitted successfully (Reject or Accept). */
|
|
39
39
|
onSubmitted?: () => void;
|
|
40
40
|
/** Called when the banner is dismissed (e.g. hide from UI). */
|
|
@@ -44,24 +44,28 @@ export interface CookieConsentBannerProps {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function CookieConsentBanner({
|
|
47
|
-
deviceId,
|
|
48
47
|
onSubmitted,
|
|
49
48
|
onDismiss,
|
|
50
49
|
className = "",
|
|
51
50
|
}: CookieConsentBannerProps) {
|
|
52
51
|
const [dismissed, setDismissed] = useState(false);
|
|
52
|
+
const [showCustomModal, setShowCustomModal] = useState(false);
|
|
53
|
+
const [expandedKey, setExpandedKey] = useState<PreferenceKey | null>(null);
|
|
53
54
|
const [preferences, setPreferences] = useState<Record<PreferenceKey, boolean>>({
|
|
54
55
|
essential: true,
|
|
55
|
-
analytics:
|
|
56
|
-
advertising:
|
|
56
|
+
analytics: true,
|
|
57
|
+
advertising: true,
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
const { deviceId } = usePhygitalConsent();
|
|
61
|
+
|
|
59
62
|
const createConsent = useCreateDeviceCookieConsent({
|
|
60
63
|
onSuccess: (_data, variables) => {
|
|
61
64
|
setConsentPreferenceCookie(deviceId, variables.selected_preferences ?? []);
|
|
62
65
|
onSubmitted?.();
|
|
63
66
|
onDismiss?.();
|
|
64
67
|
setDismissed(true);
|
|
68
|
+
setShowCustomModal(false);
|
|
65
69
|
},
|
|
66
70
|
});
|
|
67
71
|
|
|
@@ -81,163 +85,404 @@ export function CookieConsentBanner({
|
|
|
81
85
|
});
|
|
82
86
|
};
|
|
83
87
|
|
|
84
|
-
const
|
|
88
|
+
const handleAcceptAll = () => {
|
|
89
|
+
createConsent.mutate({
|
|
90
|
+
device_id: deviceId,
|
|
91
|
+
status: "ACCEPTED",
|
|
92
|
+
selected_preferences: [...PREFERENCES],
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleSaveCustom = () => {
|
|
85
97
|
const selected = getSelectedPreferences();
|
|
86
98
|
createConsent.mutate({
|
|
87
99
|
device_id: deviceId,
|
|
88
100
|
status: "ACCEPTED",
|
|
89
|
-
selected_preferences: selected.length > 0 ? selected : [],
|
|
101
|
+
selected_preferences: selected.length > 0 ? selected : ["essential"],
|
|
90
102
|
});
|
|
91
103
|
};
|
|
92
104
|
|
|
93
105
|
if (dismissed) return null;
|
|
94
106
|
|
|
95
107
|
return (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
className="consent:text-lg consent:font-semibold consent:text-gray-900"
|
|
119
|
-
style={{ fontSize: "1.125rem", fontWeight: 600, color: "#111827" }}
|
|
120
|
-
>
|
|
121
|
-
{MOCK_CONTENT.title}
|
|
122
|
-
</h2>
|
|
123
|
-
<p
|
|
124
|
-
className="consent:text-sm consent:text-gray-600"
|
|
125
|
-
style={{ fontSize: "0.875rem", color: "#4b5563" }}
|
|
108
|
+
<>
|
|
109
|
+
{/* Screen 1: Bottom banner */}
|
|
110
|
+
<div
|
|
111
|
+
role="dialog"
|
|
112
|
+
aria-label="Thông báo về cookie"
|
|
113
|
+
className={`consent:fixed consent:bottom-0 consent:left-0 consent:right-0 consent:z-50 consent:flex consent:flex-col consent:gap-4 consent:rounded-t-xl consent:border consent:border-b-0 consent:border-gray-200 consent:bg-white consent:p-4 consent:shadow-lg consent:md:left-4 consent:md:right-auto consent:md:bottom-4 consent:md:max-w-md consent:md:rounded-xl consent:md:border consent:md:border-gray-200 ${className}`}
|
|
114
|
+
style={{
|
|
115
|
+
position: "fixed",
|
|
116
|
+
bottom: 0,
|
|
117
|
+
left: 0,
|
|
118
|
+
right: 0,
|
|
119
|
+
zIndex: 50,
|
|
120
|
+
display: "flex",
|
|
121
|
+
flexDirection: "column",
|
|
122
|
+
gap: "1rem",
|
|
123
|
+
borderRadius: "0.75rem 0.75rem 0 0",
|
|
124
|
+
borderWidth: "1px 1px 0 1px",
|
|
125
|
+
borderColor: "#e5e7eb",
|
|
126
|
+
backgroundColor: "#fff",
|
|
127
|
+
padding: "1rem",
|
|
128
|
+
boxShadow: "0 -4px 6px -1px rgb(0 0 0 / 0.1)",
|
|
129
|
+
}}
|
|
126
130
|
>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
<h2
|
|
132
|
+
className="consent:text-lg consent:font-semibold consent:text-gray-900"
|
|
133
|
+
style={{ fontSize: "1.125rem", fontWeight: 600, color: "#111827" }}
|
|
134
|
+
>
|
|
135
|
+
Thông báo về cookie
|
|
136
|
+
</h2>
|
|
137
|
+
<div
|
|
138
|
+
className="consent:text-sm consent:text-gray-600"
|
|
139
|
+
style={{ fontSize: "0.875rem", color: "#4b5563", display: "flex", flexDirection: "column", gap: "0.5rem" }}
|
|
140
|
+
>
|
|
141
|
+
<p>
|
|
142
|
+
TapQuest sử dụng trình theo dõi (như Cookie hoặc SDK) cho:
|
|
143
|
+
</p>
|
|
144
|
+
<ul style={{ margin: 0, paddingLeft: "1.25rem" }}>
|
|
145
|
+
{PREFERENCES.map((key) => (
|
|
146
|
+
<li key={key}>{PREFERENCE_CONFIG[key].labelVi}</li>
|
|
147
|
+
))}
|
|
148
|
+
</ul>
|
|
149
|
+
<p>
|
|
150
|
+
Trước khi bạn đồng ý, chỉ những trình theo dõi hoàn toàn cần thiết mới được triển khai.
|
|
151
|
+
</p>
|
|
152
|
+
<p>
|
|
153
|
+
Nếu bạn chọn Chấp nhận tất cả, bạn đồng ý sử dụng tất cả trình theo dõi của chúng tôi.
|
|
154
|
+
</p>
|
|
155
|
+
<p>
|
|
156
|
+
Nếu bạn chọn Từ chối tất cả, bạn từ chối những trình theo dõi yêu cầu sự đồng ý của bạn và sẽ không có quyền truy cập vào các đề nghị hoặc nội dung được cá nhân hóa.
|
|
157
|
+
</p>
|
|
158
|
+
<p>
|
|
159
|
+
Nếu bạn chọn Tùy chọn, bạn có thể chọn xem bạn có đồng ý sử dụng trình theo dõi trên ứng dụng của chúng tôi hay không và ở mức độ nào.
|
|
160
|
+
</p>
|
|
161
|
+
<p>
|
|
162
|
+
Bạn có thể rút lại sự đồng ý của mình bất kỳ lúc nào bằng cách nhấp vào liên kết trong{" "}
|
|
163
|
+
{COOKIE_POLICY_URL ? (
|
|
164
|
+
<a
|
|
165
|
+
href={COOKIE_POLICY_URL}
|
|
166
|
+
target="_blank"
|
|
167
|
+
rel="noopener noreferrer"
|
|
168
|
+
className="consent:text-blue-600 consent:underline"
|
|
169
|
+
style={{ color: "#2563eb", textDecoration: "underline" }}
|
|
170
|
+
>
|
|
171
|
+
Chính sách cookie
|
|
172
|
+
</a>
|
|
173
|
+
) : (
|
|
174
|
+
"Chính sách cookie"
|
|
175
|
+
)}{" "}
|
|
176
|
+
của chúng tôi.
|
|
177
|
+
</p>
|
|
178
|
+
<p>
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
onClick={() => setShowCustomModal(true)}
|
|
182
|
+
className="consent:text-blue-600 consent:underline consent:bg-transparent consent:border-0 consent:p-0 consent:cursor-pointer consent:font-inherit"
|
|
136
183
|
style={{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
184
|
+
color: "#2563eb",
|
|
185
|
+
textDecoration: "underline",
|
|
186
|
+
background: "none",
|
|
187
|
+
border: "none",
|
|
188
|
+
padding: 0,
|
|
189
|
+
cursor: "pointer",
|
|
190
|
+
font: "inherit",
|
|
141
191
|
}}
|
|
142
192
|
>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<p
|
|
151
|
-
className="consent:text-xs consent:text-gray-500"
|
|
152
|
-
style={{ fontSize: "0.75rem", color: "#6b7280", margin: "0.25rem 0 0" }}
|
|
153
|
-
>
|
|
154
|
-
{config.description}
|
|
155
|
-
</p>
|
|
156
|
-
</div>
|
|
157
|
-
<button
|
|
158
|
-
type="button"
|
|
159
|
-
role="switch"
|
|
160
|
-
aria-checked={checked}
|
|
161
|
-
aria-label={`${config.label} cookies ${checked ? "on" : "off"}`}
|
|
162
|
-
disabled={config.disabled}
|
|
163
|
-
onClick={() => togglePreference(key)}
|
|
164
|
-
style={{
|
|
165
|
-
flexShrink: 0,
|
|
166
|
-
width: 44,
|
|
167
|
-
height: 24,
|
|
168
|
-
borderRadius: 12,
|
|
169
|
-
border: "none",
|
|
170
|
-
padding: 0,
|
|
171
|
-
cursor: config.disabled ? "default" : "pointer",
|
|
172
|
-
backgroundColor: config.disabled ? "#e5e7eb" : checked ? "#111827" : "#d1d5db",
|
|
173
|
-
opacity: config.disabled ? 0.7 : 1,
|
|
174
|
-
transition: "background-color 0.2s",
|
|
175
|
-
}}
|
|
176
|
-
>
|
|
177
|
-
<span
|
|
178
|
-
style={{
|
|
179
|
-
display: "block",
|
|
180
|
-
width: 20,
|
|
181
|
-
height: 20,
|
|
182
|
-
borderRadius: "50%",
|
|
183
|
-
backgroundColor: "#fff",
|
|
184
|
-
marginLeft: checked ? 22 : 2,
|
|
185
|
-
marginTop: 2,
|
|
186
|
-
transition: "margin-left 0.2s",
|
|
187
|
-
boxShadow: "0 1px 2px rgb(0 0 0 / 0.2)",
|
|
188
|
-
}}
|
|
189
|
-
/>
|
|
190
|
-
</button>
|
|
191
|
-
</div>
|
|
192
|
-
);
|
|
193
|
-
})}
|
|
194
|
-
</div>
|
|
195
|
-
<div
|
|
196
|
-
className="consent:flex consent:flex-wrap consent:gap-2"
|
|
197
|
-
style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem" }}
|
|
198
|
-
>
|
|
199
|
-
<button
|
|
200
|
-
type="button"
|
|
201
|
-
onClick={handleReject}
|
|
202
|
-
disabled={createConsent.isPending}
|
|
203
|
-
className="consent:rounded-lg consent:border consent:border-gray-300 consent:bg-white consent:px-4 consent:py-2 consent:text-sm consent:font-medium consent:text-gray-700 consent:shadow-sm hover:consent:bg-gray-50 disabled:consent:opacity-50"
|
|
204
|
-
style={{
|
|
205
|
-
borderRadius: "0.5rem",
|
|
206
|
-
border: "1px solid #d1d5db",
|
|
207
|
-
background: "#fff",
|
|
208
|
-
padding: "0.5rem 1rem",
|
|
209
|
-
fontSize: "0.875rem",
|
|
210
|
-
fontWeight: 500,
|
|
211
|
-
color: "#374151",
|
|
212
|
-
}}
|
|
193
|
+
Tuỳ chỉnh cookie.
|
|
194
|
+
</button>
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
<div
|
|
198
|
+
className="consent:flex consent:flex-col consent:gap-2"
|
|
199
|
+
style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}
|
|
213
200
|
>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
201
|
+
<div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={handleAcceptAll}
|
|
205
|
+
disabled={createConsent.isPending}
|
|
206
|
+
className="consent:rounded-lg consent:bg-gray-900 consent:px-4 consent:py-2 consent:text-sm consent:font-medium consent:text-white consent:shadow-sm hover:consent:bg-gray-800 disabled:consent:opacity-50"
|
|
207
|
+
style={{
|
|
208
|
+
borderRadius: "0.5rem",
|
|
209
|
+
background: "#111827",
|
|
210
|
+
padding: "0.5rem 1rem",
|
|
211
|
+
fontSize: "0.875rem",
|
|
212
|
+
fontWeight: 500,
|
|
213
|
+
color: "#fff",
|
|
214
|
+
border: "none",
|
|
215
|
+
cursor: "pointer",
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
Chấp nhận tất cả
|
|
219
|
+
</button>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
onClick={handleReject}
|
|
223
|
+
disabled={createConsent.isPending}
|
|
224
|
+
className="consent:rounded-lg consent:bg-gray-900 consent:px-4 consent:py-2 consent:text-sm consent:font-medium consent:text-white consent:shadow-sm hover:consent:bg-gray-800 disabled:consent:opacity-50"
|
|
225
|
+
style={{
|
|
226
|
+
borderRadius: "0.5rem",
|
|
227
|
+
background: "#111827",
|
|
228
|
+
padding: "0.5rem 1rem",
|
|
229
|
+
fontSize: "0.875rem",
|
|
230
|
+
fontWeight: 500,
|
|
231
|
+
color: "#fff",
|
|
232
|
+
border: "none",
|
|
233
|
+
cursor: "pointer",
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
Từ chối tất cả
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
onClick={() => setShowCustomModal(true)}
|
|
242
|
+
className="consent:text-blue-600 consent:underline consent:bg-transparent consent:border-0 consent:p-0 consent:text-sm consent:cursor-pointer consent:font-inherit"
|
|
243
|
+
style={{
|
|
244
|
+
color: "#2563eb",
|
|
245
|
+
textDecoration: "underline",
|
|
246
|
+
background: "none",
|
|
247
|
+
border: "none",
|
|
248
|
+
padding: 0,
|
|
249
|
+
fontSize: "0.875rem",
|
|
250
|
+
cursor: "pointer",
|
|
251
|
+
font: "inherit",
|
|
252
|
+
alignSelf: "flex-start",
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
Tuỳ chọn
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
{createConsent.isError && (
|
|
259
|
+
<p
|
|
260
|
+
className="consent:text-sm consent:text-red-600"
|
|
261
|
+
style={{ fontSize: "0.875rem", color: "#dc2626" }}
|
|
262
|
+
>
|
|
263
|
+
Đã xảy ra lỗi. Vui lòng thử lại.
|
|
264
|
+
</p>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* Screen 2: Custom preferences modal */}
|
|
269
|
+
{showCustomModal && (
|
|
270
|
+
<div
|
|
271
|
+
role="dialog"
|
|
272
|
+
aria-modal="true"
|
|
273
|
+
aria-label="Trung tâm tuỳ chọn bảo mật"
|
|
221
274
|
style={{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
275
|
+
position: "fixed",
|
|
276
|
+
inset: 0,
|
|
277
|
+
zIndex: 60,
|
|
278
|
+
display: "flex",
|
|
279
|
+
alignItems: "center",
|
|
280
|
+
justifyContent: "center",
|
|
281
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
282
|
+
padding: "1rem",
|
|
228
283
|
}}
|
|
284
|
+
onClick={(e) => e.target === e.currentTarget && setShowCustomModal(false)}
|
|
229
285
|
>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
286
|
+
<div
|
|
287
|
+
className="consent:bg-white consent:rounded-xl consent:border consent:border-gray-200 consent:shadow-lg consent:max-h-[90vh] consent:overflow-y-auto"
|
|
288
|
+
style={{
|
|
289
|
+
backgroundColor: "#fff",
|
|
290
|
+
borderRadius: "0.75rem",
|
|
291
|
+
border: "1px solid #e5e7eb",
|
|
292
|
+
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1)",
|
|
293
|
+
maxWidth: "28rem",
|
|
294
|
+
width: "100%",
|
|
295
|
+
maxHeight: "90vh",
|
|
296
|
+
overflowY: "auto",
|
|
297
|
+
padding: "1.25rem",
|
|
298
|
+
position: "relative",
|
|
299
|
+
}}
|
|
300
|
+
onClick={(e) => e.stopPropagation()}
|
|
301
|
+
>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
onClick={() => setShowCustomModal(false)}
|
|
305
|
+
aria-label="Đóng"
|
|
306
|
+
style={{
|
|
307
|
+
position: "absolute",
|
|
308
|
+
top: "1rem",
|
|
309
|
+
right: "1rem",
|
|
310
|
+
width: "2rem",
|
|
311
|
+
height: "2rem",
|
|
312
|
+
borderRadius: "0.25rem",
|
|
313
|
+
border: "none",
|
|
314
|
+
background: "transparent",
|
|
315
|
+
cursor: "pointer",
|
|
316
|
+
fontSize: "1.25rem",
|
|
317
|
+
lineHeight: 1,
|
|
318
|
+
color: "#6b7280",
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
×
|
|
322
|
+
</button>
|
|
323
|
+
<h2
|
|
324
|
+
className="consent:text-lg consent:font-semibold consent:text-gray-900"
|
|
325
|
+
style={{ fontSize: "1.125rem", fontWeight: 600, color: "#111827", marginBottom: "0.75rem", paddingRight: "2rem" }}
|
|
326
|
+
>
|
|
327
|
+
Trung tâm tuỳ chọn bảo mật
|
|
328
|
+
</h2>
|
|
329
|
+
<div
|
|
330
|
+
className="consent:text-sm consent:text-gray-600"
|
|
331
|
+
style={{ fontSize: "0.875rem", color: "#4b5563", marginBottom: "1rem" }}
|
|
332
|
+
>
|
|
333
|
+
<p style={{ marginBottom: "0.5rem" }}>
|
|
334
|
+
Khi bạn truy cập bất kỳ trang web nào, trang web đó có thể lưu trữ hoặc truy xuất thông tin trên trình duyệt của bạn, chủ yếu dưới dạng cookie. Thông tin này có thể liên quan đến bạn, tùy chọn của bạn hoặc thiết bị của bạn, và chủ yếu được sử dụng để trang web hoạt động như mong đợi.
|
|
335
|
+
</p>
|
|
336
|
+
<p style={{ marginBottom: "0.5rem" }}>
|
|
337
|
+
Thông tin này thường không trực tiếp xác định bạn nhưng có thể mang lại trải nghiệm web được cá nhân hóa hơn. Chúng tôi tôn trọng quyền riêng tư của bạn; bạn có thể chọn không cho phép một số loại cookie. Nhấp vào tiêu đề từng danh mục để tìm hiểu thêm và thay đổi cài đặt mặc định. Việc chặn một số cookie có thể ảnh hưởng đến trải nghiệm của bạn với trang web và các dịch vụ được cung cấp.
|
|
338
|
+
</p>
|
|
339
|
+
<p style={{ marginBottom: "0.5rem" }}>
|
|
340
|
+
Nếu chọn "Không theo dõi" khi lần đầu sử dụng TapQuest, chỉ cookie hoàn toàn cần thiết mới được sử dụng.
|
|
341
|
+
</p>
|
|
342
|
+
{OTHER_INFO_URL ? (
|
|
343
|
+
<a
|
|
344
|
+
href={OTHER_INFO_URL}
|
|
345
|
+
target="_blank"
|
|
346
|
+
rel="noopener noreferrer"
|
|
347
|
+
className="consent:text-blue-600 consent:underline"
|
|
348
|
+
style={{ color: "#2563eb", textDecoration: "underline" }}
|
|
349
|
+
>
|
|
350
|
+
Thông tin khác
|
|
351
|
+
</a>
|
|
352
|
+
) : (
|
|
353
|
+
<span style={{ color: "#2563eb", textDecoration: "underline" }}>Thông tin khác</span>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
<h3
|
|
357
|
+
className="consent:text-sm consent:font-semibold consent:text-gray-900"
|
|
358
|
+
style={{ fontSize: "0.875rem", fontWeight: 600, color: "#111827", marginBottom: "0.75rem" }}
|
|
359
|
+
>
|
|
360
|
+
Quản lý tuỳ chọn đồng ý
|
|
361
|
+
</h3>
|
|
362
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.25rem", marginBottom: "1.25rem" }}>
|
|
363
|
+
{PREFERENCES.map((key) => {
|
|
364
|
+
const config = PREFERENCE_CONFIG[key];
|
|
365
|
+
const checked = preferences[key];
|
|
366
|
+
const isExpanded = expandedKey === key;
|
|
367
|
+
return (
|
|
368
|
+
<div
|
|
369
|
+
key={key}
|
|
370
|
+
style={{
|
|
371
|
+
border: "1px solid #e5e7eb",
|
|
372
|
+
borderRadius: "0.5rem",
|
|
373
|
+
overflow: "hidden",
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
<div
|
|
377
|
+
style={{
|
|
378
|
+
display: "flex",
|
|
379
|
+
alignItems: "center",
|
|
380
|
+
gap: "0.5rem",
|
|
381
|
+
padding: "0.75rem",
|
|
382
|
+
cursor: "pointer",
|
|
383
|
+
backgroundColor: isExpanded ? "#f9fafb" : "transparent",
|
|
384
|
+
}}
|
|
385
|
+
onClick={() => setExpandedKey(isExpanded ? null : key)}
|
|
386
|
+
>
|
|
387
|
+
<span
|
|
388
|
+
style={{
|
|
389
|
+
flexShrink: 0,
|
|
390
|
+
width: "1.25rem",
|
|
391
|
+
height: "1.25rem",
|
|
392
|
+
display: "flex",
|
|
393
|
+
alignItems: "center",
|
|
394
|
+
justifyContent: "center",
|
|
395
|
+
fontSize: "1rem",
|
|
396
|
+
color: "#6b7280",
|
|
397
|
+
transform: isExpanded ? "rotate(45deg)" : "none",
|
|
398
|
+
transition: "transform 0.2s",
|
|
399
|
+
}}
|
|
400
|
+
>
|
|
401
|
+
+
|
|
402
|
+
</span>
|
|
403
|
+
<span
|
|
404
|
+
className="consent:font-medium consent:text-gray-900"
|
|
405
|
+
style={{ flex: 1, fontSize: "0.875rem", fontWeight: 500, color: "#111827" }}
|
|
406
|
+
>
|
|
407
|
+
{config.labelVi}
|
|
408
|
+
</span>
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
role="switch"
|
|
412
|
+
aria-checked={checked}
|
|
413
|
+
aria-label={`${config.labelVi} ${checked ? "bật" : "tắt"}`}
|
|
414
|
+
disabled={config.disabled}
|
|
415
|
+
onClick={(e) => {
|
|
416
|
+
e.stopPropagation();
|
|
417
|
+
togglePreference(key);
|
|
418
|
+
}}
|
|
419
|
+
style={{
|
|
420
|
+
flexShrink: 0,
|
|
421
|
+
width: 44,
|
|
422
|
+
height: 24,
|
|
423
|
+
borderRadius: 12,
|
|
424
|
+
border: "none",
|
|
425
|
+
padding: 0,
|
|
426
|
+
cursor: config.disabled ? "default" : "pointer",
|
|
427
|
+
backgroundColor: config.disabled ? "#e5e7eb" : checked ? "#111827" : "#d1d5db",
|
|
428
|
+
opacity: config.disabled ? 0.7 : 1,
|
|
429
|
+
transition: "background-color 0.2s",
|
|
430
|
+
}}
|
|
431
|
+
>
|
|
432
|
+
<span
|
|
433
|
+
style={{
|
|
434
|
+
display: "block",
|
|
435
|
+
width: 20,
|
|
436
|
+
height: 20,
|
|
437
|
+
borderRadius: "50%",
|
|
438
|
+
backgroundColor: "#fff",
|
|
439
|
+
marginLeft: checked ? 22 : 2,
|
|
440
|
+
marginTop: 2,
|
|
441
|
+
transition: "margin-left 0.2s",
|
|
442
|
+
boxShadow: "0 1px 2px rgb(0 0 0 / 0.2)",
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
</button>
|
|
446
|
+
</div>
|
|
447
|
+
{isExpanded && (
|
|
448
|
+
<div
|
|
449
|
+
className="consent:text-sm consent:text-gray-600"
|
|
450
|
+
style={{
|
|
451
|
+
fontSize: "0.875rem",
|
|
452
|
+
color: "#4b5563",
|
|
453
|
+
padding: "0 0.75rem 0.75rem 2.5rem",
|
|
454
|
+
borderTop: "1px solid #e5e7eb",
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
{config.descriptionVi}
|
|
458
|
+
</div>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
461
|
+
);
|
|
462
|
+
})}
|
|
463
|
+
</div>
|
|
464
|
+
<button
|
|
465
|
+
type="button"
|
|
466
|
+
onClick={handleSaveCustom}
|
|
467
|
+
disabled={createConsent.isPending}
|
|
468
|
+
className="consent:rounded-lg consent:bg-gray-900 consent:px-4 consent:py-2 consent:text-sm consent:font-medium consent:text-white consent:shadow-sm hover:consent:bg-gray-800 disabled:consent:opacity-50 consent:w-full"
|
|
469
|
+
style={{
|
|
470
|
+
borderRadius: "0.5rem",
|
|
471
|
+
background: "#111827",
|
|
472
|
+
padding: "0.5rem 1rem",
|
|
473
|
+
fontSize: "0.875rem",
|
|
474
|
+
fontWeight: 500,
|
|
475
|
+
color: "#fff",
|
|
476
|
+
border: "none",
|
|
477
|
+
cursor: "pointer",
|
|
478
|
+
width: "100%",
|
|
479
|
+
}}
|
|
480
|
+
>
|
|
481
|
+
Xác nhận lựa chọn của tôi
|
|
482
|
+
</button>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
240
485
|
)}
|
|
241
|
-
|
|
486
|
+
</>
|
|
242
487
|
);
|
|
243
488
|
}
|
package/src/env/constant.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export const CONSENT_API_BASE_URL = "";
|
|
2
|
+
|
|
3
|
+
/** URL to cookie policy page (e.g. for "Chính sách cookie" link). */
|
|
4
|
+
export const COOKIE_POLICY_URL = "";
|
|
5
|
+
|
|
6
|
+
/** URL to other information page (e.g. for "Thông tin khác" link in preferences modal). */
|
|
7
|
+
export const OTHER_INFO_URL = "";
|
package/src/helpers/index.ts
CHANGED