@redacto.io/consent-sdk-react 0.0.1

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.
@@ -0,0 +1,143 @@
1
+ import { jwtDecode } from "jwt-decode";
2
+ import type {
3
+ ConsentContent,
4
+ ConsentEventPayload,
5
+ FetchConsentContentParams,
6
+ RedactoJwtPayload,
7
+ SubmitConsentEventParams,
8
+ } from "./types";
9
+
10
+ const BASE_URL = "https://api.redacto.tech";
11
+ const BACKEND_URL = "http://localhost:3000";
12
+
13
+ export const fetchConsentContent = async ({
14
+ noticeId,
15
+ accessToken,
16
+ refreshToken,
17
+ language = "en",
18
+ }: FetchConsentContentParams): Promise<ConsentContent> => {
19
+ try {
20
+ const decodedToken = jwtDecode(accessToken) as RedactoJwtPayload;
21
+
22
+ const ORGANISATION_UUID = decodedToken?.organisation_uuid;
23
+ const WORKSPACE_UUID = decodedToken?.workspace_uuid;
24
+
25
+ const response = await fetch(
26
+ `${BASE_URL}/consent/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeId}`,
27
+ {
28
+ method: "GET",
29
+ headers: {
30
+ Authorization: `Bearer ${accessToken}`,
31
+ "Accept-Language": language,
32
+ "Content-Type": "application/json",
33
+ },
34
+ }
35
+ );
36
+
37
+ if (response.status === 401) {
38
+ try {
39
+ const response = await fetch(
40
+ `${BACKEND_URL}/api/consent/refresh-token`,
41
+ {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ },
46
+ body: JSON.stringify({
47
+ refreshToken,
48
+ }),
49
+ }
50
+ );
51
+
52
+ if (!response.ok) {
53
+ throw new Error("Failed to get consent tokens");
54
+ }
55
+
56
+ const data = await response.json();
57
+
58
+ localStorage.setItem("accessToken", data.token);
59
+ localStorage.setItem("refreshToken", data.refresh_token);
60
+ localStorage.setItem("tokenExpiresAt", data.expires_at);
61
+ } catch (error) {
62
+ console.error("Failed to setup consent:", error);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ if (response.status === 409) {
68
+ const error = new Error("User has already provided consent") as Error & {
69
+ status?: number;
70
+ };
71
+ error.status = 409;
72
+ throw error;
73
+ }
74
+
75
+ if (!response.ok) {
76
+ throw new Error(
77
+ `Failed to fetch consent content: ${response.statusText}`
78
+ );
79
+ }
80
+
81
+ const data = await response.json();
82
+ return data;
83
+ } catch (error) {
84
+ console.error("Error fetching consent content:", error);
85
+ throw error;
86
+ }
87
+ };
88
+
89
+ export const submitConsentEvent = async ({
90
+ accessToken,
91
+ noticeUuid,
92
+ purposes,
93
+ declined,
94
+ }: SubmitConsentEventParams): Promise<void> => {
95
+ try {
96
+ const primaryEmail = localStorage.getItem("userEmail");
97
+ const decodedToken = jwtDecode(accessToken) as RedactoJwtPayload;
98
+
99
+ const ORGANISATION_UUID = decodedToken.organisation_uuid;
100
+ const WORKSPACE_UUID = decodedToken.workspace_uuid;
101
+ const payload: ConsentEventPayload = {
102
+ primary_email: primaryEmail || undefined,
103
+ notice_uuid: noticeUuid,
104
+ source: "WEB",
105
+ declined,
106
+ consents: {
107
+ purposes: purposes.map((purpose) => ({
108
+ uuid: purpose.uuid,
109
+ name: purpose.name,
110
+ description: purpose.description,
111
+ industries: purpose.industries,
112
+ selected: purpose.selected,
113
+ data_elements: purpose.data_elements.map((element) => ({
114
+ uuid: element.uuid,
115
+ name: element.name,
116
+ enabled: element.enabled,
117
+ required: element.required,
118
+ selected: element.required ? true : element.selected,
119
+ })),
120
+ })),
121
+ },
122
+ };
123
+
124
+ const response = await fetch(
125
+ `${BASE_URL}/consent/public/consent-events/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/by-token`,
126
+ {
127
+ method: "POST",
128
+ headers: {
129
+ Authorization: `Bearer ${accessToken}`,
130
+ "Content-Type": "application/json",
131
+ },
132
+ body: JSON.stringify(payload),
133
+ }
134
+ );
135
+
136
+ if (!response.ok) {
137
+ throw new Error(`Failed to submit consent event: ${response.statusText}`);
138
+ }
139
+ } catch (error) {
140
+ console.error("Error submitting consent event:", error);
141
+ throw error;
142
+ }
143
+ };
@@ -0,0 +1,181 @@
1
+ export type RedactoJwtPayload = {
2
+ organisation_uuid: string;
3
+ workspace_uuid: string;
4
+ };
5
+
6
+ export type ConsentContent = {
7
+ code: number;
8
+ status: string;
9
+ detail: {
10
+ uuid: string;
11
+ name: string;
12
+ organisation_uuid: string;
13
+ workspace_uuid: string;
14
+ collection_point_uuids: string[];
15
+ collection_points: {
16
+ uuid: string;
17
+ organisation_uuid: string;
18
+ workspace_uuid: string;
19
+ name: string;
20
+ created_at: string;
21
+ updated_at: string;
22
+ }[];
23
+ active_config: {
24
+ uuid: string;
25
+ notice_uuid: string;
26
+ organisation_uuid: string;
27
+ workspace_uuid: string;
28
+ version: number;
29
+ status: string;
30
+ notice_text: string;
31
+ additional_text: string;
32
+ confirm_button_text: string;
33
+ decline_button_text: string;
34
+ logo_url: string;
35
+ privacy_policy_url: string;
36
+ sub_processors_url: string;
37
+ primary_color: string;
38
+ secondary_color: string;
39
+ font_preference: string;
40
+ purposes: {
41
+ uuid: string;
42
+ name: string;
43
+ description: string;
44
+ industries: string;
45
+ data_elements: {
46
+ uuid: string;
47
+ name: string;
48
+ description: string | null;
49
+ industries: string | null;
50
+ enabled: boolean;
51
+ required: boolean;
52
+ }[];
53
+ }[];
54
+ default_language: string;
55
+ supported_languages_and_translations: {
56
+ [key: string]: {
57
+ notice_text: string;
58
+ additional_text: string;
59
+ confirm_button_text: string;
60
+ decline_button_text: string;
61
+ privacy_policy_prefix_text: string;
62
+ vendor_list_prefix_text: string;
63
+ privacy_policy_anchor_text: string;
64
+ vendors_list_anchor_text: string;
65
+ purpose_section_heading: string;
66
+ data_elements: {
67
+ [key: string]: string;
68
+ };
69
+ purposes: {
70
+ [key: string]: string;
71
+ };
72
+ };
73
+ };
74
+ created_at: string;
75
+ updated_at: string;
76
+ deployed_at: string;
77
+ privacy_policy_prefix_text: string;
78
+ vendor_list_prefix_text: string;
79
+ privacy_policy_anchor_text: string;
80
+ vendors_list_anchor_text: string;
81
+ purpose_section_heading: string;
82
+ };
83
+ created_at: string;
84
+ updated_at: string;
85
+ };
86
+ };
87
+
88
+ export type ConsentPreference = {
89
+ id: string;
90
+ name: string;
91
+ description: string;
92
+ required: boolean;
93
+ };
94
+
95
+ export type ConsentSelection = {
96
+ id: string;
97
+ enabled: boolean;
98
+ };
99
+
100
+ export type Settings = {
101
+ button: {
102
+ accept: {
103
+ backgroundColor: string;
104
+ textColor: string;
105
+ };
106
+ decline: {
107
+ backgroundColor: string;
108
+ textColor: string;
109
+ };
110
+ language: {
111
+ backgroundColor: string;
112
+ textColor: string;
113
+ selectedBackgroundColor?: string;
114
+ selectedTextColor?: string;
115
+ };
116
+ };
117
+ link: {
118
+ color: string;
119
+ };
120
+ borderRadius?: string;
121
+ backgroundColor?: string;
122
+ headingColor?: string;
123
+ textColor?: string;
124
+ borderColor?: string;
125
+ };
126
+
127
+ export type FetchConsentContentParams = {
128
+ noticeId: string;
129
+ accessToken: string;
130
+ refreshToken: string;
131
+ language?: string;
132
+ };
133
+
134
+ export type ConsentEventPayload = {
135
+ org_user_id?: string;
136
+ primary_email?: string;
137
+ primary_mobile?: string;
138
+ notice_uuid: string;
139
+ source: string;
140
+ declined: boolean;
141
+ consents: {
142
+ purposes: Array<{
143
+ uuid: string;
144
+ name: string;
145
+ description: string;
146
+ industries: string;
147
+ selected: boolean;
148
+ data_elements: Array<{
149
+ uuid: string;
150
+ name: string;
151
+ enabled: boolean;
152
+ required: boolean;
153
+ selected: boolean;
154
+ }>;
155
+ }>;
156
+ };
157
+ };
158
+
159
+ export type DataElement = {
160
+ uuid: string;
161
+ name: string;
162
+ enabled: boolean;
163
+ required: boolean;
164
+ selected: boolean;
165
+ };
166
+
167
+ export type Purpose = {
168
+ uuid: string;
169
+ name: string;
170
+ description: string;
171
+ industries: string;
172
+ selected: boolean;
173
+ data_elements: Array<DataElement>;
174
+ };
175
+
176
+ export type SubmitConsentEventParams = {
177
+ accessToken: string;
178
+ noticeUuid: string;
179
+ purposes: Array<Purpose>;
180
+ declined: boolean;
181
+ };
@@ -0,0 +1 @@
1
+ export { RedactoNoticeConsent as default } from "./RedactoNoticeConsent";
@@ -0,0 +1,352 @@
1
+ export const styles = {
2
+ overlay: {
3
+ position: "fixed",
4
+ top: 0,
5
+ left: 0,
6
+ width: "100%",
7
+ height: "100%",
8
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
9
+ display: "flex",
10
+ alignItems: "center",
11
+ justifyContent: "center",
12
+ zIndex: 999,
13
+ animation: "overlayFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1)",
14
+ pointerEvents: "all",
15
+ },
16
+
17
+ modal: {
18
+ position: "fixed",
19
+ top: "50%",
20
+ left: "50%",
21
+ transform: "translate(-50%, -50%)",
22
+ width: "700px",
23
+ borderRadius: "8px",
24
+ backgroundColor: "#ffffff",
25
+ boxShadow: "4px 0px 4px 0px #00000040",
26
+ border: "none",
27
+ display: "flex",
28
+ flexDirection: "column",
29
+ maxHeight: "90vh",
30
+ zIndex: 1000,
31
+ },
32
+
33
+ content: {
34
+ margin: "22px",
35
+ display: "flex",
36
+ flexDirection: "column",
37
+ flexGrow: 1,
38
+ overflow: "hidden",
39
+ minHeight: 0,
40
+ },
41
+
42
+ topSection: {
43
+ display: "flex",
44
+ justifyContent: "space-between",
45
+ alignItems: "center",
46
+ paddingBottom: "15px",
47
+ borderBottom: "1px solid #e5e7eb",
48
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.03)",
49
+ backgroundColor: "#ffffff",
50
+ },
51
+
52
+ topLeft: {
53
+ display: "flex",
54
+ alignItems: "center",
55
+ gap: "10px",
56
+ },
57
+
58
+ logo: {
59
+ height: "32px",
60
+ width: "auto",
61
+ objectFit: "contain",
62
+ },
63
+
64
+ title: {
65
+ fontSize: "18px",
66
+ fontWeight: 700,
67
+ lineHeight: "150%",
68
+ letterSpacing: "0.2px",
69
+ verticalAlign: "middle",
70
+ color: "#101828",
71
+ },
72
+
73
+ topRight: {
74
+ borderRadius: "8px",
75
+ padding: "3px 9px",
76
+ border: "1px solid #d0d5dd",
77
+ display: "flex",
78
+ alignItems: "center",
79
+ justifyContent: "center",
80
+ fontWeight: 400,
81
+ fontSize: "12px",
82
+ lineHeight: "150%",
83
+ letterSpacing: "0.2px",
84
+ verticalAlign: "middle",
85
+ color: "#344054",
86
+ gap: "5px",
87
+ cursor: "pointer",
88
+ backgroundColor: "#ffffff",
89
+ },
90
+
91
+ middleSection: {
92
+ margin: "20px 0px",
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ gap: "12px",
96
+ flexGrow: 1,
97
+ overflowY: "auto",
98
+ minHeight: 0,
99
+ paddingRight: "15px",
100
+ },
101
+
102
+ privacyText: {
103
+ fontSize: "16px",
104
+ fontWeight: 400,
105
+ lineHeight: "150%",
106
+ letterSpacing: "0.2px",
107
+ color: "#344054",
108
+ },
109
+
110
+ link: {
111
+ color: "#4f87ff",
112
+ textDecoration: "none",
113
+ },
114
+
115
+ subTitle: {
116
+ fontWeight: 600,
117
+ fontSize: "16px",
118
+ lineHeight: "150%",
119
+ letterSpacing: "0.2px",
120
+ color: "#101828",
121
+ },
122
+
123
+ optionsContainer: {
124
+ display: "flex",
125
+ flexDirection: "column",
126
+ gap: "14px",
127
+ },
128
+
129
+ optionItem: {
130
+ display: "flex",
131
+ justifyContent: "space-between",
132
+ alignItems: "center",
133
+ },
134
+
135
+ optionLeft: {
136
+ display: "flex",
137
+ gap: "12px",
138
+ alignItems: "center",
139
+ cursor: "pointer",
140
+ },
141
+
142
+ optionTextContainer: {
143
+ display: "flex",
144
+ flexDirection: "column",
145
+ },
146
+
147
+ optionTitle: {
148
+ fontWeight: 500,
149
+ fontSize: "16px",
150
+ lineHeight: "150%",
151
+ letterSpacing: "0.2px",
152
+ color: "#101828",
153
+ },
154
+
155
+ optionDescription: {
156
+ fontWeight: 400,
157
+ fontSize: "12px",
158
+ lineHeight: "150%",
159
+ letterSpacing: "0.2px",
160
+ color: "#475467",
161
+ verticalAlign: "middle",
162
+ },
163
+
164
+ checkboxLarge: {
165
+ height: "20px",
166
+ width: "20px",
167
+ padding: "4px",
168
+ borderRadius: "5px",
169
+ border: "2px solid #d0d5dd",
170
+ },
171
+
172
+ dataElementsContainer: {
173
+ marginLeft: "27px",
174
+ display: "flex",
175
+ flexDirection: "column",
176
+ },
177
+
178
+ dataElementItem: {
179
+ display: "flex",
180
+ alignItems: "center",
181
+ gap: "10px",
182
+ justifyContent: "space-between",
183
+ },
184
+
185
+ dataElementText: {
186
+ fontWeight: 400,
187
+ fontSize: "14px",
188
+ lineHeight: "150%",
189
+ letterSpacing: "0.2px",
190
+ color: "#344054",
191
+ },
192
+
193
+ checkboxSmall: {
194
+ height: "16px",
195
+ width: "16px",
196
+ padding: "4px",
197
+ borderRadius: "5px",
198
+ border: "2px solid #d0d5dd",
199
+ },
200
+
201
+ bottomSection: {
202
+ display: "flex",
203
+ justifyContent: "space-between",
204
+ gap: "29px",
205
+ paddingTop: "15px",
206
+ borderTop: "1px solid #e5e7eb",
207
+ boxShadow: "0 -1px 2px rgba(0, 0, 0, 0.03)",
208
+ backgroundColor: "#ffffff",
209
+ },
210
+
211
+ button: {
212
+ width: "100%",
213
+ borderRadius: "8px",
214
+ padding: "9px 45px",
215
+ fontWeight: 400,
216
+ fontSize: "16px",
217
+ lineHeight: "150%",
218
+ letterSpacing: "0.2px",
219
+ cursor: "pointer",
220
+ },
221
+
222
+ acceptButton: {
223
+ backgroundColor: "#4f87ff",
224
+ border: "none",
225
+ color: "#ffffff",
226
+ },
227
+
228
+ cancelButton: {
229
+ border: "1px solid #d0d5dd",
230
+ backgroundColor: "#ffffff",
231
+ color: "#000000",
232
+ },
233
+
234
+ languageSelectorContainer: {
235
+ position: "relative",
236
+ },
237
+
238
+ languageDropdown: {
239
+ position: "absolute",
240
+ top: "100%",
241
+ right: 0,
242
+ backgroundColor: "#ffffff",
243
+ border: "1px solid #d0d5dd",
244
+ borderRadius: "4px",
245
+ boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.1)",
246
+ zIndex: 10,
247
+ width: "max-content",
248
+ marginTop: "4px",
249
+ },
250
+
251
+ languageItem: {
252
+ padding: "8px 12px",
253
+ cursor: "pointer",
254
+ fontSize: "12px",
255
+ color: "#344054",
256
+ backgroundColor: "#ffffff",
257
+ borderBottom: "1px solid #e5e7eb",
258
+ },
259
+
260
+ selectedLanguageItem: {
261
+ backgroundColor: "#f3f4f6",
262
+ fontWeight: 600,
263
+ },
264
+
265
+ loadingContainer: {
266
+ display: "flex",
267
+ flexDirection: "column",
268
+ alignItems: "center",
269
+ justifyContent: "center",
270
+ padding: "2rem",
271
+ minHeight: "200px",
272
+ },
273
+
274
+ loadingSpinner: {
275
+ width: "40px",
276
+ height: "40px",
277
+ border: "4px solid #f3f3f3",
278
+ borderTop: "4px solid #3498db",
279
+ borderRadius: "50%",
280
+ animation: "spin 1s linear infinite",
281
+ marginBottom: "1rem",
282
+ },
283
+
284
+ // Media query styles
285
+ "@media (max-width: 768px)": {
286
+ modal: {
287
+ width: "90%",
288
+ },
289
+ content: {
290
+ margin: "20px",
291
+ },
292
+ title: {
293
+ fontSize: "16px",
294
+ },
295
+ subTitle: {
296
+ fontSize: "14px",
297
+ },
298
+ privacyText: {
299
+ fontSize: "14px",
300
+ },
301
+ optionTitle: {
302
+ fontSize: "14px",
303
+ },
304
+ optionDescription: {
305
+ fontSize: "11px",
306
+ },
307
+ dataElementText: {
308
+ fontSize: "12px",
309
+ },
310
+ topRight: {
311
+ fontSize: "11px",
312
+ padding: "3px 6px",
313
+ height: "auto",
314
+ width: "auto",
315
+ },
316
+ languageItem: {
317
+ fontSize: "11px",
318
+ padding: "6px 10px",
319
+ },
320
+ bottomSection: {
321
+ flexDirection: "column",
322
+ gap: "12px",
323
+ },
324
+ button: {
325
+ fontSize: "14px",
326
+ padding: "10px 20px",
327
+ },
328
+ middleSection: {
329
+ paddingRight: "10px",
330
+ },
331
+ },
332
+ } as const;
333
+
334
+ // Keyframes
335
+ export const keyframes = {
336
+ overlayFadeIn: {
337
+ from: {
338
+ opacity: 0,
339
+ },
340
+ to: {
341
+ opacity: 1,
342
+ },
343
+ },
344
+ spin: {
345
+ "0%": {
346
+ transform: "rotate(0deg)",
347
+ },
348
+ "100%": {
349
+ transform: "rotate(360deg)",
350
+ },
351
+ },
352
+ } as const;
@@ -0,0 +1,41 @@
1
+ import type { Settings } from "./api/types";
2
+
3
+ export type Props = Readonly<{
4
+ noticeId: string;
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ settings?: Partial<Settings>;
8
+ language?: string;
9
+ blockUI?: boolean;
10
+ onAccept: () => void;
11
+ onDecline: () => void;
12
+ onError?: (error: Error) => void;
13
+ }>;
14
+
15
+ export type PurposeTranslations = {
16
+ [purposeId: string]: string;
17
+ };
18
+
19
+ export type DataElementTranslations = {
20
+ [elementId: string]: string;
21
+ };
22
+
23
+ export type TranslationObject = {
24
+ notice_text?: string;
25
+ additional_text?: string;
26
+ confirm_button_text?: string;
27
+ decline_button_text?: string;
28
+ purposes?: PurposeTranslations;
29
+ data_elements?: DataElementTranslations;
30
+ purpose_section_heading?: string;
31
+ privacy_policy_prefix_text?: string;
32
+ vendor_list_prefix_text?: string;
33
+ privacy_policy_anchor_text?: string;
34
+ vendors_list_anchor_text?: string;
35
+ [key: string]:
36
+ | string
37
+ | PurposeTranslations
38
+ | DataElementTranslations
39
+ | Record<string, string>
40
+ | undefined;
41
+ };