@redacto.io/consent-sdk-react 9.3.0 → 9.4.0-beta.0
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 +10 -10
- package/CHANGELOG.md +6 -0
- package/README.md +93 -12
- package/dist/index.d.mts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +4454 -334
- package/dist/index.mjs +4454 -335
- package/dist/privacy-center.d.mts +4 -1
- package/dist/privacy-center.d.ts +4 -1
- package/dist/privacy-center.js +3905 -3660
- package/dist/privacy-center.mjs +4172 -3921
- package/package.json +1 -1
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.test.tsx +847 -0
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.tsx +2133 -0
- package/src/RedactoNoticeAssisted/api/index.ts +269 -0
- package/src/RedactoNoticeAssisted/api/types.ts +180 -0
- package/src/RedactoNoticeAssisted/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeAssisted/constants.ts +51 -0
- package/src/RedactoNoticeAssisted/i18n.ts +1463 -0
- package/src/RedactoNoticeAssisted/index.ts +1 -0
- package/src/RedactoNoticeAssisted/injectStyles.ts +251 -0
- package/src/RedactoNoticeAssisted/styles.ts +730 -0
- package/src/RedactoNoticeAssisted/types.ts +24 -0
- package/src/RedactoNoticeAssisted/useMediaQuery.ts +36 -0
- package/src/RedactoPrivacyCenter/NotificationsPage.tsx +8 -0
- package/src/RedactoPrivacyCenter/api/actions.ts +6 -11
- package/src/RedactoPrivacyCenter/api/client.ts +15 -3
- package/src/RedactoPrivacyCenter/api/fetcher.ts +80 -1
- package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +193 -213
- package/src/RedactoPrivacyCenter/components/ActivityTimeline.tsx +239 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +3 -1
- package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +8 -1
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +23 -17
- package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +24 -10
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +16 -3
- package/src/RedactoPrivacyCenter/components/constant.ts +25 -0
- package/src/RedactoPrivacyCenter/components/types.ts +32 -1
- package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +25 -23
- package/src/RedactoPrivacyCenter/lib/constants.ts +23 -10
- package/src/RedactoPrivacyCenter/lib/queries.ts +9 -2
- package/src/RedactoPrivacyCenter/lib/utils.refresh-circuit.test.ts +127 -0
- package/src/RedactoPrivacyCenter/lib/utils.refresh-storage.test.ts +85 -0
- package/src/RedactoPrivacyCenter/lib/utils.ts +213 -63
- package/src/RedactoPrivacyCenter/locales/en/translation.json +3 -0
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +16 -0
- package/src/index.ts +2 -0
- package/src/shared/api-errors.ts +9 -0
- package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +0 -141
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback, useMemo, useRef, type CSSProperties } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import { pcStyles } from '../styles/pcStyles';
|
|
5
|
-
import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
|
3
|
+
import { History } from 'lucide-react';
|
|
6
4
|
import { fetchActivities } from '../lib/api';
|
|
7
5
|
import { useOrganization } from '../context/OrganizationContext';
|
|
8
6
|
import { useAuth } from '../context/AuthContext';
|
|
9
7
|
import { useBaseUrl } from '../context/BaseUrlContext';
|
|
10
8
|
import type { Activity } from '../lib/types';
|
|
11
|
-
import { useActivitiesColumns } from './activitiesColumns';
|
|
12
9
|
import { useCurrentLanguage } from '../lib/useCurrentLanguage';
|
|
13
10
|
import { toast } from '../utils/toast';
|
|
11
|
+
import { getVar } from '../lib/utils';
|
|
12
|
+
import { ACTIVITY_PAGE_SIZE } from '../lib/constants';
|
|
13
|
+
import { PCText, PCLoader, PCEmpty } from '../ui';
|
|
14
|
+
import ActivityTimeline from './ActivityTimeline';
|
|
15
|
+
import { ACTIVITY_FETCH_ERROR_MESSAGE } from './constant';
|
|
14
16
|
|
|
15
|
-
export default function ActivitiesList({
|
|
16
|
-
variant,
|
|
17
|
-
}: {
|
|
18
|
-
variant?: 'modern' | 'minimal';
|
|
19
|
-
}) {
|
|
17
|
+
export default function ActivitiesList() {
|
|
20
18
|
const { t } = useTranslation();
|
|
21
19
|
const { organization } = useOrganization();
|
|
22
20
|
const { accessToken, refreshToken, onError, updateTokens } = useAuth();
|
|
@@ -35,235 +33,217 @@ export default function ActivitiesList({
|
|
|
35
33
|
// Track language changes for localized API calls
|
|
36
34
|
const { langParam } = useCurrentLanguage();
|
|
37
35
|
|
|
38
|
-
const [
|
|
36
|
+
const [activities, setActivities] = useState<Activity[]>([]);
|
|
39
37
|
const [totalCount, setTotalCount] = useState(0);
|
|
40
|
-
const [
|
|
41
|
-
const [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
38
|
+
const [isLoadingInitial, setIsLoadingInitial] = useState(true);
|
|
39
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
40
|
+
|
|
41
|
+
const hasMore = activities.length < totalCount;
|
|
42
|
+
|
|
43
|
+
// Refs let the IntersectionObserver callback read fresh values without
|
|
44
|
+
// re-subscribing on every state change, and guard against double-fetch.
|
|
45
|
+
const isFetchingRef = useRef(false);
|
|
46
|
+
// Bumped on every reset; an in-flight loadMore from a prior org/workspace/
|
|
47
|
+
// language context checks it before appending so stale pages can't leak in.
|
|
48
|
+
const requestVersionRef = useRef(0);
|
|
49
|
+
const hasMoreRef = useRef(hasMore);
|
|
50
|
+
hasMoreRef.current = hasMore;
|
|
51
|
+
const countRef = useRef(0);
|
|
52
|
+
countRef.current = activities.length;
|
|
53
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
|
|
55
|
+
const fetchPage = useCallback(
|
|
56
|
+
async (offset: number) => {
|
|
57
|
+
if (!organizationId || !workspaceId) return null;
|
|
58
|
+
return fetchActivities(
|
|
59
|
+
organizationId,
|
|
60
|
+
workspaceId,
|
|
61
|
+
offset,
|
|
62
|
+
ACTIVITY_PAGE_SIZE,
|
|
63
|
+
authContext,
|
|
64
|
+
consentClient,
|
|
65
|
+
langParam
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
[organizationId, workspaceId, authContext, consentClient, langParam]
|
|
69
|
+
);
|
|
48
70
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
// Initial load (and reset) whenever org / workspace / language changes.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
let cancelled = false;
|
|
74
|
+
const requestVersion = ++requestVersionRef.current;
|
|
75
|
+
isFetchingRef.current = false;
|
|
76
|
+
setIsLoadingInitial(true);
|
|
77
|
+
setActivities([]);
|
|
78
|
+
setTotalCount(0);
|
|
79
|
+
|
|
80
|
+
(async () => {
|
|
52
81
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
organizationId,
|
|
57
|
-
workspaceId,
|
|
58
|
-
offset,
|
|
59
|
-
size,
|
|
60
|
-
authContext,
|
|
61
|
-
consentClient,
|
|
62
|
-
langParam
|
|
63
|
-
);
|
|
64
|
-
if (!response) {
|
|
65
|
-
throw new Error('Failed to fetch activity data');
|
|
66
|
-
}
|
|
67
|
-
setActivityData(response.data ?? []);
|
|
68
|
-
setTotalCount(response.pagination?.total_count ?? 0);
|
|
69
|
-
if (page !== currentPage) setCurrentPage(page);
|
|
70
|
-
if (size !== pageSize) setPageSize(size);
|
|
82
|
+
const response = await fetchPage(0);
|
|
83
|
+
if (cancelled || requestVersion !== requestVersionRef.current) {
|
|
84
|
+
return;
|
|
71
85
|
}
|
|
86
|
+
if (organizationId && workspaceId && !response) {
|
|
87
|
+
throw new Error(ACTIVITY_FETCH_ERROR_MESSAGE);
|
|
88
|
+
}
|
|
89
|
+
setActivities(response?.data ?? []);
|
|
90
|
+
setTotalCount(response?.pagination?.total_count ?? 0);
|
|
72
91
|
} catch (error) {
|
|
92
|
+
if (cancelled || requestVersion !== requestVersionRef.current) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
73
95
|
console.error(error);
|
|
74
|
-
|
|
96
|
+
setActivities([]);
|
|
75
97
|
setTotalCount(0);
|
|
76
|
-
setPreviousData([]);
|
|
77
|
-
setPreviousTotalCount(0);
|
|
78
98
|
toast.error(t('failedToFetchActivityData'));
|
|
79
99
|
} finally {
|
|
80
|
-
|
|
100
|
+
if (!cancelled && requestVersion === requestVersionRef.current) {
|
|
101
|
+
setIsLoadingInitial(false);
|
|
102
|
+
}
|
|
81
103
|
}
|
|
82
|
-
}
|
|
83
|
-
[organizationId, workspaceId, currentPage, pageSize, authContext, consentClient, langParam]
|
|
84
|
-
);
|
|
104
|
+
})();
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
return () => {
|
|
107
|
+
cancelled = true;
|
|
108
|
+
};
|
|
109
|
+
}, [fetchPage, organizationId, workspaceId, t]);
|
|
110
|
+
|
|
111
|
+
const loadMore = useCallback(async () => {
|
|
112
|
+
if (isFetchingRef.current || !hasMoreRef.current) {
|
|
113
|
+
return;
|
|
91
114
|
}
|
|
92
|
-
|
|
115
|
+
const requestVersion = requestVersionRef.current;
|
|
116
|
+
isFetchingRef.current = true;
|
|
117
|
+
setIsLoadingMore(true);
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetchPage(countRef.current);
|
|
120
|
+
if (requestVersion !== requestVersionRef.current) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!response) {
|
|
124
|
+
throw new Error(ACTIVITY_FETCH_ERROR_MESSAGE);
|
|
125
|
+
}
|
|
126
|
+
setActivities((prev) => [...prev, ...(response.data ?? [])]);
|
|
127
|
+
setTotalCount((prev) => response.pagination?.total_count ?? prev);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (requestVersion !== requestVersionRef.current) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.error(error);
|
|
133
|
+
toast.error(t('failedToFetchActivityData'));
|
|
134
|
+
} finally {
|
|
135
|
+
if (requestVersion === requestVersionRef.current) {
|
|
136
|
+
isFetchingRef.current = false;
|
|
137
|
+
setIsLoadingMore(false);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}, [fetchPage, t]);
|
|
93
141
|
|
|
142
|
+
// Auto-load as the sentinel nears the viewport. Re-subscribing when the
|
|
143
|
+
// loaded count changes lets a short list keep filling (the observer fires its
|
|
144
|
+
// initial callback on observe) until the viewport is full or all are loaded.
|
|
94
145
|
useEffect(() => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const displayData = isLoading && activityData.length === 0 ? previousData : activityData;
|
|
100
|
-
const displayTotalCount = isLoading && totalCount === 0 ? previousTotalCount : totalCount;
|
|
146
|
+
const node = sentinelRef.current;
|
|
147
|
+
if (!node || isLoadingInitial) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
101
150
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
pageCount: Math.ceil(displayTotalCount / pageSize) || 0,
|
|
108
|
-
state: {
|
|
109
|
-
pagination: {
|
|
110
|
-
pageIndex: currentPage - 1,
|
|
111
|
-
pageSize: pageSize,
|
|
151
|
+
const observer = new IntersectionObserver(
|
|
152
|
+
(entries) => {
|
|
153
|
+
if (entries[0]?.isIntersecting) {
|
|
154
|
+
void loadMore();
|
|
155
|
+
}
|
|
112
156
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
/>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
{ root: null, rootMargin: '600px 0px' }
|
|
158
|
+
);
|
|
159
|
+
observer.observe(node);
|
|
160
|
+
return () => observer.disconnect();
|
|
161
|
+
}, [loadMore, isLoadingInitial, activities.length]);
|
|
162
|
+
|
|
163
|
+
let content;
|
|
164
|
+
if (isLoadingInitial) {
|
|
165
|
+
content = <ActivityTimelineSkeleton />;
|
|
166
|
+
} else if (activities.length === 0) {
|
|
167
|
+
content = (
|
|
168
|
+
<PCEmpty
|
|
169
|
+
icon={<History size={28} aria-hidden />}
|
|
170
|
+
title={t('noActivitiesYet')}
|
|
171
|
+
description={t('noActivitiesDescription')}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
content = (
|
|
176
|
+
<>
|
|
177
|
+
<ActivityTimeline activities={activities} />
|
|
178
|
+
|
|
179
|
+
{/* Fixed-height footer: the sentinel stays mounted and the spinner
|
|
180
|
+
occupies reserved space, so loading never shifts the layout. */}
|
|
181
|
+
{hasMore ? (
|
|
182
|
+
<div style={{
|
|
183
|
+
position: 'relative',
|
|
184
|
+
minHeight: '2.75rem',
|
|
185
|
+
display: 'flex',
|
|
186
|
+
alignItems: 'center',
|
|
187
|
+
justifyContent: 'center',
|
|
188
|
+
}}>
|
|
189
|
+
<div
|
|
190
|
+
ref={sentinelRef}
|
|
191
|
+
aria-hidden
|
|
192
|
+
style={{ position: 'absolute', bottom: 0, left: 0, height: 1, width: 1 }}
|
|
193
|
+
/>
|
|
194
|
+
{isLoadingMore && <PCLoader size="sm" />}
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<div style={{
|
|
198
|
+
display: 'flex',
|
|
199
|
+
alignItems: 'center',
|
|
200
|
+
justifyContent: 'center',
|
|
201
|
+
minHeight: '2.75rem',
|
|
202
|
+
}}>
|
|
203
|
+
<PCText as="span" size="xs" color="tertiary">
|
|
204
|
+
{t('allCaughtUp')}
|
|
205
|
+
</PCText>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
137
211
|
|
|
138
|
-
|
|
139
|
-
<div style={{
|
|
140
|
-
...pcStyles.flex,
|
|
141
|
-
...pcStyles.flexCol,
|
|
142
|
-
...pcStyles.itemsCenter,
|
|
143
|
-
...pcStyles.justifyBetween,
|
|
144
|
-
...pcStyles.gapSm,
|
|
145
|
-
marginTop: '0.25rem',
|
|
146
|
-
}} className="pc-flex-responsive">
|
|
147
|
-
<AdvancedTablePagination table={table} />
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
);
|
|
212
|
+
return <div style={{ width: '100%' }}>{content}</div>;
|
|
151
213
|
}
|
|
152
214
|
|
|
153
|
-
// Skeleton loader
|
|
154
|
-
function
|
|
155
|
-
const
|
|
215
|
+
// Skeleton loader mirroring the timeline layout (time bar + small disc + content bars).
|
|
216
|
+
function ActivityTimelineSkeleton() {
|
|
217
|
+
const pulse = (extra: CSSProperties): CSSProperties => ({
|
|
218
|
+
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
219
|
+
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
220
|
+
borderRadius: getVar('--pc-border-radius-md'),
|
|
221
|
+
...extra,
|
|
222
|
+
});
|
|
156
223
|
|
|
157
224
|
return (
|
|
158
|
-
<div style={{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
backgroundColor: getVar('--pc-bg-primary'),
|
|
162
|
-
}}>
|
|
163
|
-
{/* Table Header */}
|
|
164
|
-
<div style={{
|
|
165
|
-
borderBottom: `1px solid ${getVar('--pc-border-color')}`,
|
|
166
|
-
backgroundColor: getVar('--pc-bg-secondary'),
|
|
167
|
-
}}>
|
|
168
|
-
<div style={{
|
|
169
|
-
display: 'grid',
|
|
170
|
-
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
171
|
-
gap: getVar('--pc-spacing-sm'),
|
|
172
|
-
paddingLeft: getVar('--pc-spacing-md'),
|
|
173
|
-
paddingRight: getVar('--pc-spacing-md'),
|
|
174
|
-
paddingTop: '0.625rem',
|
|
175
|
-
paddingBottom: '0.625rem',
|
|
176
|
-
}}>
|
|
177
|
-
{labels.map((_, i) => (
|
|
178
|
-
<div
|
|
179
|
-
key={i}
|
|
180
|
-
style={{
|
|
181
|
-
height: '1rem',
|
|
182
|
-
width: '6rem',
|
|
183
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
184
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
185
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
186
|
-
}}
|
|
187
|
-
/>
|
|
188
|
-
))}
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
{/* Table Rows */}
|
|
193
|
-
{Array.from({ length: 4 }).map((_, index) => (
|
|
225
|
+
<div style={{ width: '100%' }}>
|
|
226
|
+
<div style={{ ...pulse({ height: '0.75rem', width: '7rem' }), marginBottom: getVar('--pc-spacing-md') }} />
|
|
227
|
+
{Array.from({ length: 6 }).map((_, index) => (
|
|
194
228
|
<div
|
|
195
229
|
key={index}
|
|
196
230
|
style={{
|
|
197
|
-
|
|
198
|
-
|
|
231
|
+
display: 'grid',
|
|
232
|
+
gridTemplateColumns: '64px 34px 1fr',
|
|
233
|
+
columnGap: '12px',
|
|
234
|
+
marginBottom: getVar('--pc-spacing-sm'),
|
|
199
235
|
}}
|
|
200
236
|
>
|
|
201
|
-
<div style={{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
<div style={{ ...pcStyles.flex, ...pcStyles.flexCol, ...pcStyles.gapXs }}>
|
|
212
|
-
<div style={{
|
|
213
|
-
height: '1rem',
|
|
214
|
-
width: '8rem',
|
|
215
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
216
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
217
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
218
|
-
}} />
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
{/* Activity Type */}
|
|
222
|
-
<div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter }}>
|
|
223
|
-
<div style={{
|
|
224
|
-
height: '1.5rem',
|
|
225
|
-
width: '5rem',
|
|
226
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
227
|
-
borderRadius: '9999px',
|
|
228
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
229
|
-
}} />
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{/* Description */}
|
|
233
|
-
<div style={{ ...pcStyles.flex, ...pcStyles.flexCol, ...pcStyles.gapXs }}>
|
|
234
|
-
<div style={{
|
|
235
|
-
height: '0.75rem',
|
|
236
|
-
width: '10rem',
|
|
237
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
238
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
239
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
240
|
-
}} />
|
|
241
|
-
<div style={{
|
|
242
|
-
height: '0.75rem',
|
|
243
|
-
width: '8rem',
|
|
244
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
245
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
246
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
247
|
-
}} />
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
{/* Timestamp */}
|
|
251
|
-
<div style={{ ...pcStyles.flex, ...pcStyles.flexCol, ...pcStyles.gapXs }}>
|
|
252
|
-
<div style={{
|
|
253
|
-
height: '1rem',
|
|
254
|
-
width: '6rem',
|
|
255
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
256
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
257
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
258
|
-
}} />
|
|
259
|
-
<div style={{
|
|
260
|
-
height: '0.75rem',
|
|
261
|
-
width: '5rem',
|
|
262
|
-
animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
263
|
-
borderRadius: getVar('--pc-border-radius-md'),
|
|
264
|
-
backgroundColor: getVar('--pc-bg-tertiary'),
|
|
265
|
-
}} />
|
|
266
|
-
</div>
|
|
237
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '0.25rem', paddingTop: '0.5rem' }}>
|
|
238
|
+
<div style={pulse({ height: '0.75rem', width: '3rem' })} />
|
|
239
|
+
<div style={pulse({ height: '0.625rem', width: '2.25rem' })} />
|
|
240
|
+
</div>
|
|
241
|
+
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: '0.5rem' }}>
|
|
242
|
+
<div style={pulse({ width: 30, height: 30, borderRadius: '9999px' })} />
|
|
243
|
+
</div>
|
|
244
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.4375rem', padding: '0.5rem 0.625rem' }}>
|
|
245
|
+
<div style={pulse({ height: '0.875rem', width: '40%' })} />
|
|
246
|
+
<div style={pulse({ height: '0.75rem', width: '72%' })} />
|
|
267
247
|
</div>
|
|
268
248
|
</div>
|
|
269
249
|
))}
|