@redacto.io/consent-sdk-react 9.2.0 → 9.3.0-beta.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.
- package/.turbo/turbo-build.log +25 -26
- package/.turbo/turbo-test.log +203 -0
- package/.turbo/turbo-verify-pack.log +6 -0
- package/CHANGELOG.md +12 -0
- package/dist/privacy-center.js +3242 -2956
- package/dist/privacy-center.mjs +3068 -2774
- package/package.json +3 -3
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -3
- package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +36 -5
- package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +3 -2
- package/src/RedactoPrivacyCenter/components/Form.tsx +127 -8
- package/src/RedactoPrivacyCenter/components/PrivacyCenterEmptyState.tsx +76 -0
- package/src/RedactoPrivacyCenter/components/ReceiptsList.tsx +3 -41
- package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.tsx +169 -0
- package/src/RedactoPrivacyCenter/locales/as/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/en/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/or/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/te/translation.json +2 -0
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +2 -0
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1 -1
- package/tests/DataAvailabilityContext.test.tsx +123 -0
- package/tests/Form.test.tsx +93 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { fetchUserConsents, fetchActivities, fetchCaseHistory } from '../lib/api';
|
|
12
|
+
import { useCurrentLanguage } from '../lib/useCurrentLanguage';
|
|
13
|
+
import { useOrganization } from './OrganizationContext';
|
|
14
|
+
import { useAuth } from './AuthContext';
|
|
15
|
+
import { useBaseUrl } from './BaseUrlContext';
|
|
16
|
+
|
|
17
|
+
export type DataAvailabilityStatus = 'checking' | 'has-data' | 'empty';
|
|
18
|
+
|
|
19
|
+
type DataAvailabilityContextValue = {
|
|
20
|
+
status: DataAvailabilityStatus;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DataAvailabilityContext = createContext<DataAvailabilityContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines whether the authenticated user has ANY data in the Privacy Center
|
|
27
|
+
* (consents, data-request cases, or activity). When all three are empty we
|
|
28
|
+
* surface a dedicated empty state instead of dropping the user into a shell
|
|
29
|
+
* full of empty tables.
|
|
30
|
+
*
|
|
31
|
+
* Fail-open by design: the underlying fetchers return `null` on non-auth
|
|
32
|
+
* errors (and may reject if a post-refresh retry throws). A `null`/rejected
|
|
33
|
+
* signal is treated as "has data" so a transient or auth error never wrongly
|
|
34
|
+
* locks a user out — the empty state shows ONLY when all three signals
|
|
35
|
+
* conclusively resolve to empty.
|
|
36
|
+
*/
|
|
37
|
+
export const DataAvailabilityProvider = ({ children }: { children: ReactNode }) => {
|
|
38
|
+
const { organization } = useOrganization();
|
|
39
|
+
const { accessToken, refreshToken, onError, updateTokens } = useAuth();
|
|
40
|
+
const { consentClient } = useBaseUrl();
|
|
41
|
+
const { langParam } = useCurrentLanguage();
|
|
42
|
+
|
|
43
|
+
const organizationId = organization?.uuid;
|
|
44
|
+
const workspaceId = organization?.workspaces[0]?.uuid;
|
|
45
|
+
|
|
46
|
+
// Memoized to keep the fetch identity stable across renders (mirrors the
|
|
47
|
+
// canonical pattern in ActivitiesList / AuthContext).
|
|
48
|
+
const authContext = useMemo(
|
|
49
|
+
() => ({ accessToken, refreshToken, onError, updateTokens }),
|
|
50
|
+
[accessToken, refreshToken, onError, updateTokens],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const [status, setStatus] = useState<DataAvailabilityStatus>('checking');
|
|
54
|
+
|
|
55
|
+
// Monotonic run id guards against a stale in-flight check (org/token changed)
|
|
56
|
+
// overwriting a newer one; mountedRef guards against setState after unmount.
|
|
57
|
+
const runIdRef = useRef(0);
|
|
58
|
+
const mountedRef = useRef(true);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
mountedRef.current = true;
|
|
61
|
+
return () => {
|
|
62
|
+
mountedRef.current = false;
|
|
63
|
+
};
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
const runCheck = useCallback(async () => {
|
|
67
|
+
if (!organizationId || !workspaceId) return;
|
|
68
|
+
|
|
69
|
+
const runId = ++runIdRef.current;
|
|
70
|
+
setStatus('checking');
|
|
71
|
+
|
|
72
|
+
// Cases require the user's email (same source the request Form reads).
|
|
73
|
+
const contact =
|
|
74
|
+
typeof window !== 'undefined' ? sessionStorage.getItem('email') : null;
|
|
75
|
+
|
|
76
|
+
// Each signal resolves to a boolean "is empty". A `null` result (the
|
|
77
|
+
// fetchers' non-auth error path) or a rejection (rare post-refresh retry
|
|
78
|
+
// throw) resolves to `false` so the signal counts as "has data" — the
|
|
79
|
+
// empty state shows ONLY when all three conclusively resolve to empty.
|
|
80
|
+
const consentsEmptyP: Promise<boolean> = fetchUserConsents({
|
|
81
|
+
organizationId,
|
|
82
|
+
workspaceId,
|
|
83
|
+
limit: 1,
|
|
84
|
+
nominatedLimit: 1,
|
|
85
|
+
language: langParam,
|
|
86
|
+
authContext,
|
|
87
|
+
consentClient,
|
|
88
|
+
})
|
|
89
|
+
.then(
|
|
90
|
+
(v) =>
|
|
91
|
+
!!v &&
|
|
92
|
+
v.status_summary?.total_purposes === 0 &&
|
|
93
|
+
(v.direct?.length ?? 0) === 0 &&
|
|
94
|
+
(v.nominated?.length ?? 0) === 0 &&
|
|
95
|
+
(v.nominators?.length ?? 0) === 0,
|
|
96
|
+
)
|
|
97
|
+
.catch(() => false);
|
|
98
|
+
|
|
99
|
+
// No contact => we can't check cases, so treat as empty and rely on the
|
|
100
|
+
// other two signals' fail-open. With a contact, a null/rejected result
|
|
101
|
+
// fails open (not empty).
|
|
102
|
+
const casesEmptyP: Promise<boolean> = contact
|
|
103
|
+
? fetchCaseHistory(
|
|
104
|
+
organizationId,
|
|
105
|
+
workspaceId,
|
|
106
|
+
contact,
|
|
107
|
+
0,
|
|
108
|
+
1,
|
|
109
|
+
authContext,
|
|
110
|
+
consentClient,
|
|
111
|
+
langParam,
|
|
112
|
+
)
|
|
113
|
+
.then((v) => !!v && (v.data?.length ?? 0) === 0)
|
|
114
|
+
.catch(() => false)
|
|
115
|
+
: Promise.resolve(true);
|
|
116
|
+
|
|
117
|
+
const activitiesEmptyP: Promise<boolean> = fetchActivities(
|
|
118
|
+
organizationId,
|
|
119
|
+
workspaceId,
|
|
120
|
+
0,
|
|
121
|
+
1,
|
|
122
|
+
authContext,
|
|
123
|
+
consentClient,
|
|
124
|
+
langParam,
|
|
125
|
+
)
|
|
126
|
+
.then((v) => !!v && (v.pagination?.total_count ?? 0) === 0)
|
|
127
|
+
.catch(() => false);
|
|
128
|
+
|
|
129
|
+
const [consentsEmpty, casesEmpty, activitiesEmpty] = await Promise.all([
|
|
130
|
+
consentsEmptyP,
|
|
131
|
+
casesEmptyP,
|
|
132
|
+
activitiesEmptyP,
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// Ignore if a newer check superseded this one or we unmounted.
|
|
136
|
+
if (!mountedRef.current || runId !== runIdRef.current) return;
|
|
137
|
+
|
|
138
|
+
setStatus(
|
|
139
|
+
consentsEmpty && casesEmpty && activitiesEmpty ? 'empty' : 'has-data',
|
|
140
|
+
);
|
|
141
|
+
}, [organizationId, workspaceId, authContext, consentClient, langParam]);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (organizationId && workspaceId && accessToken) {
|
|
145
|
+
void runCheck();
|
|
146
|
+
}
|
|
147
|
+
}, [organizationId, workspaceId, accessToken, runCheck]);
|
|
148
|
+
|
|
149
|
+
const value = useMemo<DataAvailabilityContextValue>(
|
|
150
|
+
() => ({ status }),
|
|
151
|
+
[status],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<DataAvailabilityContext.Provider value={value}>
|
|
156
|
+
{children}
|
|
157
|
+
</DataAvailabilityContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const useDataAvailability = (): DataAvailabilityContextValue => {
|
|
162
|
+
const ctx = useContext(DataAvailabilityContext);
|
|
163
|
+
if (!ctx) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
'useDataAvailability must be used within a DataAvailabilityProvider',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return ctx;
|
|
169
|
+
};
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "অভিযোগৰ প্ৰকাৰ",
|
|
183
183
|
"close": "বন্ধ কৰক",
|
|
184
184
|
"noData": "ডেটা নাই",
|
|
185
|
+
"noDataTitle": "এতিয়ালৈকে দেখুৱাবলৈ একো নাই",
|
|
186
|
+
"noDataDescription": "আপোনাৰ একাউণ্টৰ সৈতে সংযুক্ত কোনো সন্মতি, অনুৰোধ বা কাৰ্য্যাৱলী আমি এতিয়ালৈকে বিচাৰি পোৱা নাই। উপলব্ধ হোৱাৰ লগে লগে আপোনাৰ গোপনীয়তা ৰেকৰ্ডসমূহ ইয়াত দেখা যাব।",
|
|
185
187
|
"rowsPerPage": "পৃষ্ঠাত শাৰী:",
|
|
186
188
|
"pageOf": "পৃষ্ঠা {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}}-ৰ {{count}} শাৰী নিৰ্বাচিত",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "शिकायत प्रकार",
|
|
183
183
|
"close": "बन्द करीं",
|
|
184
184
|
"noData": "कोनो डेटा नै",
|
|
185
|
+
"noDataTitle": "अबहीं देखावे खातिर कुछ नइखे",
|
|
186
|
+
"noDataDescription": "रउरा खाता से जुड़ल कवनो सहमति, अनुरोध, या गतिविधि अबहीं तक ना मिलल। रउरा गोपनीयता रिकॉर्ड उपलब्ध होते ई इहाँ देखाई दिही।",
|
|
185
187
|
"rowsPerPage": "पृष्ठ प्रति पंक्ति:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} में से {{count}} पंक्ति चुनल गइल",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "অভিযোগের ধরন",
|
|
183
183
|
"close": "বন্ধ করুন",
|
|
184
184
|
"noData": "কোনো ডেটা নেই",
|
|
185
|
+
"noDataTitle": "এখনো দেখানোর মতো কিছু নেই",
|
|
186
|
+
"noDataDescription": "আপনার অ্যাকাউন্টের সাথে সম্পর্কিত কোনো সম্মতি, অনুরোধ বা ক্রিয়াকলাপ আমরা এখনো খুঁজে পাইনি। উপলব্ধ হওয়ার সাথে সাথেই আপনার গোপনীয়তা রেকর্ডগুলি এখানে প্রদর্শিত হবে।",
|
|
185
187
|
"rowsPerPage": "প্রতি পৃষ্ঠায় সারি:",
|
|
186
188
|
"pageOf": "পৃষ্ঠা {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}}-এর মধ্যে {{count}} সারি নির্বাচিত",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "गुनासो थाखो",
|
|
183
183
|
"close": "बन्द खालाम",
|
|
184
184
|
"noData": "डेटा नंगौ",
|
|
185
|
+
"noDataTitle": "दानो दिनथिनो थाखाय जेबो गैया",
|
|
186
|
+
"noDataDescription": "दानो सम नोंथांनि एकाउन्टजों सोमोन्दो थानाय जेबो सहमति, हाबाय एबा खामानि जों मोननो हायाखै। नोंथांनि गोपनीयथा रिकर्डफोर मोननो हानाय बादि बेयावनो दिनथिजागोन।",
|
|
185
187
|
"rowsPerPage": "पृष्ठनि पंक्ति:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} निफ्राय {{count}} पंक्ति सायख 'खै",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "शिकायत प्रकार",
|
|
183
183
|
"close": "बंद करो",
|
|
184
184
|
"noData": "कोनो डेटा नै",
|
|
185
|
+
"noDataTitle": "अजें दिक्खाने आस्तै किछ नेईं",
|
|
186
|
+
"noDataDescription": "तुंदे खाते कन्नै जुड़ी दी कोई बी सहमति, बेनती जां गतिविधि अजें तक नेईं मिली। तुंदे गोपनीयता रिकॉर्ड उपलब्ध होंदे गै इत्थें दिक्खने गी मिलङन।",
|
|
185
187
|
"rowsPerPage": "पृष्ठ प्रति पंक्ति:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} मध्ये {{count}} पंक्ति चुनल गेल",
|
|
@@ -195,6 +195,8 @@
|
|
|
195
195
|
"grievanceTypes": "Grievance Types",
|
|
196
196
|
"close": "Close",
|
|
197
197
|
"noData": "No Data",
|
|
198
|
+
"noDataTitle": "Nothing to show yet",
|
|
199
|
+
"noDataDescription": "We couldn't find any consents, requests, or activity linked to your account yet. Your privacy records will appear here once they're available.",
|
|
198
200
|
"rowsPerPage": "Rows per page:",
|
|
199
201
|
"pageOf": "Page {{current}} of {{total}}",
|
|
200
202
|
"rowsSelected": "{{count}} of {{total}} row(s) selected",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "तक्रार प्रकार",
|
|
183
183
|
"close": "बंद करात",
|
|
184
184
|
"noData": "डेटा ना",
|
|
185
|
+
"noDataTitle": "अजून दाखोवपाक कांय ना",
|
|
186
|
+
"noDataDescription": "तुमच्या खात्याकडेन जोडिल्ली कसलीच संमती, मागण्यो वा कृती आमकां अजून मेळूंक नात. उपलब्ध जातकच तुमच्यो गोपनीयता नोंदी हांगा दिसतल्यो.",
|
|
185
187
|
"rowsPerPage": "पानाक पंक्ती:",
|
|
186
188
|
"pageOf": "पान {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} मध्ये {{count}} पंक्ती निवडली",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ફરિયાદ પ્રકારો",
|
|
183
183
|
"close": "બંધ કરો",
|
|
184
184
|
"noData": "ડેટા નથી",
|
|
185
|
+
"noDataTitle": "હજુ સુધી બતાવવા માટે કંઈ નથી",
|
|
186
|
+
"noDataDescription": "તમારા ખાતા સાથે જોડાયેલી કોઈ સંમતિઓ, વિનંતીઓ અથવા પ્રવૃત્તિ અમને હજુ સુધી મળી નથી. તમારા ગોપનીયતા રેકોર્ડ ઉપલબ્ધ થતાં જ અહીં દેખાશે.",
|
|
185
187
|
"rowsPerPage": "દર પૃષ્ઠ પંક્તિઓ:",
|
|
186
188
|
"pageOf": "પૃષ્ઠ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} માંથી {{count}} પંક્તિ પસંદ",
|
|
@@ -185,6 +185,8 @@
|
|
|
185
185
|
"grievanceTypes": "शिकायत प्रकार",
|
|
186
186
|
"close": "बंद करें",
|
|
187
187
|
"noData": "कोई डेटा नहीं",
|
|
188
|
+
"noDataTitle": "अभी दिखाने के लिए कुछ नहीं",
|
|
189
|
+
"noDataDescription": "हमें अभी तक आपके खाते से जुड़ी कोई सहमति, अनुरोध या गतिविधि नहीं मिली। उपलब्ध होते ही आपके गोपनीयता रिकॉर्ड यहाँ दिखाई देंगे।",
|
|
188
190
|
"rowsPerPage": "प्रति पृष्ठ पंक्तियाँ:",
|
|
189
191
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
190
192
|
"rowsSelected": "कुल {{total}} में से {{count}} पंक्तियाँ चयनित",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ಪರಾತಿ ವಿಧಗಳು",
|
|
183
183
|
"close": "ಮುಚ್ಚಿ",
|
|
184
184
|
"noData": "ಡೇಟಾ ಇಲ್ಲ",
|
|
185
|
+
"noDataTitle": "ಇನ್ನೂ ತೋರಿಸಲು ಏನೂ ಇಲ್ಲ",
|
|
186
|
+
"noDataDescription": "ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಬಂಧಿಸಿದ ಯಾವುದೇ ಸಮ್ಮತಿಗಳು, ವಿನಂತಿಗಳು ಅಥವಾ ಚಟುವಟಿಕೆ ನಮಗೆ ಇನ್ನೂ ಕಂಡುಬಂದಿಲ್ಲ. ನಿಮ್ಮ ಗೌಪ್ಯತಾ ದಾಖಲೆಗಳು ಲಭ್ಯವಾದ ತಕ್ಷಣ ಅವು ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ.",
|
|
185
187
|
"rowsPerPage": "ಪುಟಕ್ಕೆ ಸಾಲುಗಳು:",
|
|
186
188
|
"pageOf": "ಪುಟ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} ರಲ್ಲಿ {{count}} ಸಾಲು ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "شکایت قسم",
|
|
183
183
|
"close": "بند کرو",
|
|
184
184
|
"noData": "ڈیٹا کانہہ",
|
|
185
|
+
"noDataTitle": "ابٕ تام ہاوُن خٲطرٕ کانٛہہ نٕ",
|
|
186
|
+
"noDataDescription": "اسہِ تُہنٛدِ اکاؤنٹَس سٕتۍ جُڑٕمٕژ کانٛہہِ رضامندی، درخواستٕ یا سرگرمی ابٕ تام نٕ لَب۔ تُہنٛد رازداری ریکارڈ یہِ دستیاب سپدِتھ یِتہِ کنہِ وُچھنہٕ یِیِہٕ۔",
|
|
185
187
|
"rowsPerPage": "صفحہ فی قطاراں:",
|
|
186
188
|
"pageOf": "صفحہ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} منز {{count}} قطار چُنِت",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "शिकायत प्रकार",
|
|
183
183
|
"close": "बन्द करू",
|
|
184
184
|
"noData": "कोनो डेटा नै",
|
|
185
|
+
"noDataTitle": "अखन तक देखाबय लेल किछु नहि",
|
|
186
|
+
"noDataDescription": "अहाँक खाता सँ जुड़ल कोनो सहमति, अनुरोध, वा गतिविधि अखन तक नहि भेटल। उपलब्ध होइते अहाँक गोपनीयता रिकॉर्ड एतय देखाएत।",
|
|
185
187
|
"rowsPerPage": "पृष्ठ प्रति पंक्ति:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} मध्ये {{count}} पंक्ति चुनल गेल",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "പരാതി തരങ്ങൾ",
|
|
183
183
|
"close": "അടയ്ക്കുക",
|
|
184
184
|
"noData": "ഡാറ്റയില്ല",
|
|
185
|
+
"noDataTitle": "ഇതുവരെ കാണിക്കാൻ ഒന്നുമില്ല",
|
|
186
|
+
"noDataDescription": "നിങ്ങളുടെ അക്കൗണ്ടുമായി ബന്ധിപ്പിച്ച സമ്മതങ്ങളോ അഭ്യർത്ഥനകളോ പ്രവൃത്തിയോ ഇതുവരെ ഞങ്ങൾക്ക് കണ്ടെത്താനായില്ല. നിങ്ങളുടെ സ്വകാര്യതാ രേഖകൾ ലഭ്യമാകുമ്പോൾ അവ ഇവിടെ ദൃശ്യമാകും.",
|
|
185
187
|
"rowsPerPage": "പേജിന് വരികൾ:",
|
|
186
188
|
"pageOf": "പേജ് {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}}-ൽ {{count}} വരി തിരഞ്ഞെടുത്തു",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ꯁꯤꯕꯒꯤ ꯄꯣꯠꯁꯤꯡ",
|
|
183
183
|
"close": "ꯁꯥꯕ",
|
|
184
184
|
"noData": "ꯗꯦꯇꯥ ꯂꯩꯇꯦ",
|
|
185
|
+
"noDataTitle": "ꯍꯧꯖꯤꯛ ꯐꯥꯎꯕ ꯎꯠꯅꯕ ꯀꯔꯤꯁꯨ ꯂꯩꯇꯦ",
|
|
186
|
+
"noDataDescription": "ꯍꯧꯖꯤꯛ ꯐꯥꯎꯕ ꯑꯗꯣꯝꯒꯤ ꯑꯀꯥꯎꯟꯇꯀ ꯁꯝꯅꯗꯨꯅ ꯀꯅꯁꯦꯟꯇ, ꯍꯧꯔꯛꯐꯝ, ꯅꯠꯇ꯭ꯔꯒ ꯈꯨꯗꯝꯕꯤꯠ ꯑꯃꯠꯇ ꯐꯪꯗꯔꯦ꯫ ꯑꯗꯣꯝꯒꯤ ꯄ꯭ꯔꯥꯏꯚꯦꯁꯤ ꯔꯤꯀꯣꯔꯗꯁꯤꯡ ꯐꯪꯂꯛꯄ ꯃꯇꯃꯗ ꯃꯐꯝ ꯑꯁꯤꯗ ꯎꯔꯛꯀꯅꯤ꯫",
|
|
185
187
|
"rowsPerPage": "ꯂꯦꯡꯒꯤ ꯃꯔꯨꯑꯣꯏꯕ:",
|
|
186
188
|
"pageOf": "ꯂꯦꯡ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} ꯒꯤ ꯃꯔꯨꯑꯣꯏꯕ {{count}} ꯁꯤꯄ ꯈꯨꯟꯁꯥꯖꯈꯔꯦ",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "तक्रार प्रकार",
|
|
183
183
|
"close": "बंद करा",
|
|
184
184
|
"noData": "डेटा नाही",
|
|
185
|
+
"noDataTitle": "अद्याप दाखवण्यासारखे काही नाही",
|
|
186
|
+
"noDataDescription": "तुमच्या खात्याशी संबंधित कोणत्याही संमती, विनंत्या किंवा क्रियाकलाप अद्याप आम्हाला आढळले नाहीत. उपलब्ध होताच तुमच्या गोपनीयता नोंदी येथे दिसतील.",
|
|
185
187
|
"rowsPerPage": "प्रति पृष्ठ पंक्ती:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} पैकी {{count}} पंक्ती निवडली",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "गुनासो प्रकारहरू",
|
|
183
183
|
"close": "बन्द गर्नुहोस्",
|
|
184
184
|
"noData": "डेटा छैन",
|
|
185
|
+
"noDataTitle": "अहिलेसम्म देखाउने केही छैन",
|
|
186
|
+
"noDataDescription": "तपाईंको खातासँग जोडिएका कुनै सहमति, अनुरोध वा गतिविधि हामीले अहिलेसम्म फेला पारेनौं। उपलब्ध भएपछि तपाईंका गोपनीयता रेकर्डहरू यहाँ देखिनेछन्।",
|
|
185
187
|
"rowsPerPage": "पृष्ठ प्रति पङ्क्तिहरू:",
|
|
186
188
|
"pageOf": "पृष्ठ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} मध्ये {{count}} पङ्क्ति चयन गरियो",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ଅଭିଯୋଗ ପ୍ରକାର",
|
|
183
183
|
"close": "ବନ୍ଦ କରନ୍ତୁ",
|
|
184
184
|
"noData": "ତଥ୍ୟ ନାହିଁ",
|
|
185
|
+
"noDataTitle": "ଏପର୍ଯ୍ୟନ୍ତ ଦେଖାଇବାକୁ କିଛି ନାହିଁ",
|
|
186
|
+
"noDataDescription": "ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ସହିତ ସଂଯୁକ୍ତ କୌଣସି ସମ୍ମତି, ଅନୁରୋଧ କିମ୍ବା କାର୍ଯ୍ୟକଳାପ ଆମେ ଏପର୍ଯ୍ୟନ୍ତ ପାଇଲୁ ନାହିଁ। ଉପଲବ୍ଧ ହେବା ମାତ୍ରେ ଆପଣଙ୍କ ଗୋପନୀୟତା ରେକର୍ଡ ଏଠାରେ ଦେଖାଯିବ।",
|
|
185
187
|
"rowsPerPage": "ପୃଷ୍ଠାକୁ ଧାଡ଼ି:",
|
|
186
188
|
"pageOf": "ପୃଷ୍ଠା {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} ରୁ {{count}} ଧାଡ଼ି ବାଛିଲେ",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ਸ਼ਿਕਾਇਤ ਕਿਸਮਾਂ",
|
|
183
183
|
"close": "ਬੰਦ ਕਰੋ",
|
|
184
184
|
"noData": "ਕੋਈ ਡੇਟਾ ਨਹੀਂ",
|
|
185
|
+
"noDataTitle": "ਅਜੇ ਦਿਖਾਉਣ ਲਈ ਕੁਝ ਨਹੀਂ",
|
|
186
|
+
"noDataDescription": "ਅਸੀਂ ਤੁਹਾਡੇ ਖਾਤੇ ਨਾਲ ਜੁੜੀ ਕੋਈ ਸਹਿਮਤੀ, ਬੇਨਤੀ ਜਾਂ ਗਤੀਵਿਧੀ ਅਜੇ ਨਹੀਂ ਲੱਭ ਸਕੇ। ਤੁਹਾਡੇ ਗੋਪਨੀਯਤਾ ਰਿਕਾਰਡ ਉਪਲਬਧ ਹੁੰਦੇ ਹੀ ਇੱਥੇ ਦਿਖਾਈ ਦੇਣਗੇ।",
|
|
185
187
|
"rowsPerPage": "ਪੰਨੇ ਪ੍ਰਤੀ ਕਤਾਰਾਂ:",
|
|
186
188
|
"pageOf": "ਪੰਨਾ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} ਵਿੱਚੋਂ {{count}} ਕਤਾਰ ਚੁਣੀ ਗਈ",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "अभियोगप्रकाराः",
|
|
183
183
|
"close": "समापय",
|
|
184
184
|
"noData": "डेटा नास्ति",
|
|
185
|
+
"noDataTitle": "अद्यापि किमपि दर्शयितुं नास्ति",
|
|
186
|
+
"noDataDescription": "भवतः खातया सह सम्बद्धाः काश्चन सम्मतयः, अनुरोधाः, कार्यकलापाः वा अद्यापि अस्माभिः न प्राप्ताः। उपलब्धाः सति भवतः गोपनीयता-अभिलेखाः अत्र दर्शयिष्यन्ते।",
|
|
185
187
|
"rowsPerPage": "पृष्ठे पंक्तयः:",
|
|
186
188
|
"pageOf": "पृष्ठम् {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} मध्ये {{count}} पंक्तिः चिता",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ᱩᱯᱟᱹᱭ ᱞᱮᱠᱟᱱ",
|
|
183
183
|
"close": "ᱵᱚᱸᱫᱚ",
|
|
184
184
|
"noData": "ᱰᱮᱴᱟ ᱵᱟᱹᱱᱩᱜᱼᱟ",
|
|
185
|
+
"noDataTitle": "ᱱᱤᱛ ᱛᱟᱠ ᱡᱟᱦᱟᱸ ᱫᱮᱠᱷᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱝ",
|
|
186
|
+
"noDataDescription": "ᱟᱞᱮ ᱱᱤᱛ ᱛᱟᱠ ᱟᱢᱟᱜ ᱮᱠᱟᱣᱩᱱᱴ ᱥᱟᱞᱟᱜ ᱡᱚᱲᱟᱣ ᱡᱟᱦᱟᱸ ᱥᱚᱦᱚᱢᱚᱛᱤ, ᱦᱟᱵᱟᱭ, ᱟᱨᱵᱟᱝ ᱠᱟᱹᱢᱤ ᱵᱟᱝ ᱧᱟᱢ ᱫᱟᱲᱮᱭᱟᱫᱼᱟ᱾ ᱟᱢᱟᱜ ᱯᱨᱟᱭᱣᱮᱥᱤ ᱨᱤᱠᱚᱨᱰ ᱠᱚ ᱧᱟᱢᱚᱜ ᱠᱷᱟᱱ ᱱᱚᱸᱰᱮ ᱫᱮᱠᱷᱟᱣᱚᱜᱼᱟ᱾",
|
|
185
187
|
"rowsPerPage": "ᱯᱮᱡᱽ ᱯᱤᱨᱤ ᱪᱷᱩᱴᱟᱹ:",
|
|
186
188
|
"pageOf": "ᱯᱮᱡᱽ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} ᱠᱷᱚᱱ {{count}} ᱪᱷᱩᱴᱟᱹ ᱵᱟᱪᱷᱟᱣ",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "شڪايت قسمن",
|
|
183
183
|
"close": "بند ڪريو",
|
|
184
184
|
"noData": "ڊيٽا ڪانهي",
|
|
185
|
+
"noDataTitle": "اڃا ڏيکارڻ لاءِ ڪجهه ناهي",
|
|
186
|
+
"noDataDescription": "اسان کي اڃا تائين توهان جي اڪائونٽ سان لاڳاپيل ڪابه رضامندي، درخواست يا سرگرمي نه ملي. توهان جا رازداري رڪارڊ موجود ٿيندي ئي هتي ظاهر ٿيندا.",
|
|
185
187
|
"rowsPerPage": "صفحي في قطارون:",
|
|
186
188
|
"pageOf": "صفحو {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}} مان {{count}} قطار چونڊيل",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "முறையீட்டு வகைகள்",
|
|
183
183
|
"close": "மூடு",
|
|
184
184
|
"noData": "தரவு இல்லை",
|
|
185
|
+
"noDataTitle": "இன்னும் காட்ட எதுவும் இல்லை",
|
|
186
|
+
"noDataDescription": "உங்கள் கணக்குடன் இணைக்கப்பட்ட ஒப்புதல்கள், கோரிக்கைகள் அல்லது செயல்பாடு எதையும் இன்னும் எங்களால் கண்டறிய முடியவில்லை. உங்கள் தனியுரிமை பதிவுகள் கிடைக்கும்போது அவை இங்கே தோன்றும்.",
|
|
185
187
|
"rowsPerPage": "பக்கத்திற்கு வரிசைகள்:",
|
|
186
188
|
"pageOf": "பக்கம் {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{count}} / {{total}} வரிசைத் தேர்ந்தெடுக்கப்பட்டது",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "ఫిర్యాదు రకాలు",
|
|
183
183
|
"close": "మూసివేయండి",
|
|
184
184
|
"noData": "డేటా లేదు",
|
|
185
|
+
"noDataTitle": "చూపించడానికి ఇంకా ఏమీ లేదు",
|
|
186
|
+
"noDataDescription": "మీ ఖాతాకు అనుసంధానించబడిన ఏ సమ్మతులు, అభ్యర్థనలు లేదా కార్యాచరణ ఇంకా మాకు కనుగొనబడలేదు. అందుబాటులోకి వచ్చిన తర్వాత మీ గోప్యతా రికార్డులు ఇక్కడ కనిపిస్తాయి.",
|
|
185
187
|
"rowsPerPage": "పేజీకి వరుసలు:",
|
|
186
188
|
"pageOf": "పేజీ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{total}}లో {{count}} వరుస ఎంచుకోబడింది",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"grievanceTypes": "شکایت کی اقسام",
|
|
183
183
|
"close": "بند کریں",
|
|
184
184
|
"noData": "کوئی ڈیٹا نہیں",
|
|
185
|
+
"noDataTitle": "ابھی دکھانے کے لیے کچھ نہیں",
|
|
186
|
+
"noDataDescription": "ہمیں ابھی تک آپ کے اکاؤنٹ سے منسلک کوئی رضامندی، درخواست یا سرگرمی نہیں ملی۔ آپ کے پرائیویسی ریکارڈ دستیاب ہوتے ہی یہاں ظاہر ہو جائیں گے۔",
|
|
185
187
|
"rowsPerPage": "فی صفحہ قطاریں:",
|
|
186
188
|
"pageOf": "صفحہ {{current}} / {{total}}",
|
|
187
189
|
"rowsSelected": "{{count}} از {{total}} قطار منتخب",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// ─── API stubs ───────────────────────────────────────────────────────────────
|
|
5
|
+
// The gate calls the direct fetchers (NOT react-query hooks). Each returns
|
|
6
|
+
// `null` on error in production; we exercise both null and rejection paths.
|
|
7
|
+
const fetchUserConsents = vi.fn();
|
|
8
|
+
const fetchCaseHistory = vi.fn();
|
|
9
|
+
const fetchActivities = vi.fn();
|
|
10
|
+
vi.mock('../src/RedactoPrivacyCenter/lib/api', () => ({
|
|
11
|
+
fetchUserConsents: (...args: unknown[]) => fetchUserConsents(...args),
|
|
12
|
+
fetchCaseHistory: (...args: unknown[]) => fetchCaseHistory(...args),
|
|
13
|
+
fetchActivities: (...args: unknown[]) => fetchActivities(...args),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// ─── Language stub (avoids subscribing to the real i18n event emitter) ───────
|
|
17
|
+
vi.mock('../src/RedactoPrivacyCenter/lib/useCurrentLanguage', () => ({
|
|
18
|
+
useCurrentLanguage: () => ({ currentLanguage: 'en', langParam: undefined }),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// ─── Auth / BaseUrl / Organization context stubs ─────────────────────────────
|
|
22
|
+
vi.mock('../src/RedactoPrivacyCenter/context/AuthContext', () => {
|
|
23
|
+
const auth = {
|
|
24
|
+
accessToken: 'tok',
|
|
25
|
+
refreshToken: 'rtok',
|
|
26
|
+
onError: () => false,
|
|
27
|
+
updateTokens: () => {},
|
|
28
|
+
};
|
|
29
|
+
return { useAuth: () => auth };
|
|
30
|
+
});
|
|
31
|
+
vi.mock('../src/RedactoPrivacyCenter/context/BaseUrlContext', () => {
|
|
32
|
+
const base = { consentClient: {}, baseUrl: 'https://consent.test' };
|
|
33
|
+
return { useBaseUrl: () => base };
|
|
34
|
+
});
|
|
35
|
+
vi.mock('../src/RedactoPrivacyCenter/context/OrganizationContext', () => {
|
|
36
|
+
const org = {
|
|
37
|
+
organization: { uuid: 'org-uuid', slug: 'acme', name: 'Acme', workspaces: [{ uuid: 'ws-uuid' }] },
|
|
38
|
+
};
|
|
39
|
+
return { useOrganization: () => org };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
DataAvailabilityProvider,
|
|
44
|
+
useDataAvailability,
|
|
45
|
+
} from '../src/RedactoPrivacyCenter/context/DataAvailabilityContext';
|
|
46
|
+
|
|
47
|
+
const emptyConsents = { status_summary: { total_purposes: 0 }, direct: [], nominated: [], nominators: [] };
|
|
48
|
+
const emptyCases = { data: [] };
|
|
49
|
+
const emptyActivities = { pagination: { total_count: 0 } };
|
|
50
|
+
|
|
51
|
+
function StatusProbe() {
|
|
52
|
+
const { status } = useDataAvailability();
|
|
53
|
+
return <div data-testid="status">{status}</div>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderGate() {
|
|
57
|
+
return render(
|
|
58
|
+
<DataAvailabilityProvider>
|
|
59
|
+
<StatusProbe />
|
|
60
|
+
</DataAvailabilityProvider>,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const waitForStatus = (value: string) =>
|
|
65
|
+
waitFor(() => expect(screen.getByTestId('status').textContent).toBe(value));
|
|
66
|
+
|
|
67
|
+
describe('DataAvailabilityContext', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
sessionStorage.setItem('email', 'user@example.com');
|
|
70
|
+
// Default: everything empty.
|
|
71
|
+
fetchUserConsents.mockResolvedValue(emptyConsents);
|
|
72
|
+
fetchCaseHistory.mockResolvedValue(emptyCases);
|
|
73
|
+
fetchActivities.mockResolvedValue(emptyActivities);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
vi.clearAllMocks();
|
|
78
|
+
sessionStorage.clear();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("resolves to 'empty' only when consents, cases, and activity are all empty", async () => {
|
|
82
|
+
renderGate();
|
|
83
|
+
await waitForStatus('empty');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("resolves to 'has-data' when there are consents", async () => {
|
|
87
|
+
fetchUserConsents.mockResolvedValue({ status_summary: { total_purposes: 2 }, direct: [{}], nominated: [], nominators: [] });
|
|
88
|
+
renderGate();
|
|
89
|
+
await waitForStatus('has-data');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("resolves to 'has-data' when there are cases", async () => {
|
|
93
|
+
fetchCaseHistory.mockResolvedValue({ data: [{ uuid: 'c1' }] });
|
|
94
|
+
renderGate();
|
|
95
|
+
await waitForStatus('has-data');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("resolves to 'has-data' when there is activity", async () => {
|
|
99
|
+
fetchActivities.mockResolvedValue({ pagination: { total_count: 4 } });
|
|
100
|
+
renderGate();
|
|
101
|
+
await waitForStatus('has-data');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("fails open to 'has-data' when a signal returns null", async () => {
|
|
105
|
+
// Consents + cases empty, but activity could not be read (null).
|
|
106
|
+
fetchActivities.mockResolvedValue(null);
|
|
107
|
+
renderGate();
|
|
108
|
+
await waitForStatus('has-data');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("fails open to 'has-data' when a signal rejects", async () => {
|
|
112
|
+
fetchUserConsents.mockRejectedValue(new Error('boom'));
|
|
113
|
+
renderGate();
|
|
114
|
+
await waitForStatus('has-data');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("skips the cases fetch when no email is present, treating cases as empty", async () => {
|
|
118
|
+
sessionStorage.removeItem('email');
|
|
119
|
+
renderGate();
|
|
120
|
+
await waitForStatus('empty');
|
|
121
|
+
expect(fetchCaseHistory).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
});
|