@cosmicdrift/kumiko-bundled-features 0.79.3 → 0.80.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/package.json +6 -6
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -8
- package/src/user-data-rights/web/confirm-deletion-screen.tsx +15 -8
- package/src/user-data-rights/web/privacy-center-screen.tsx +54 -37
- package/src/user-data-rights/web/request-deletion-screen.tsx +15 -8
- package/src/user-profile/web/profile-screen.tsx +47 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -84,11 +84,11 @@
|
|
|
84
84
|
"./step-dispatcher": "./src/step-dispatcher/index.ts"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
88
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
89
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
90
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
91
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
87
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.80.0",
|
|
88
|
+
"@cosmicdrift/kumiko-framework": "0.80.0",
|
|
89
|
+
"@cosmicdrift/kumiko-headless": "0.80.0",
|
|
90
|
+
"@cosmicdrift/kumiko-renderer": "0.80.0",
|
|
91
|
+
"@cosmicdrift/kumiko-renderer-web": "0.80.0",
|
|
92
92
|
"@mollie/api-client": "^4.5.0",
|
|
93
93
|
"@node-rs/argon2": "^2.0.2",
|
|
94
94
|
"@types/nodemailer": "^8.0.0",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// authMutedLinkClass — Subtle-Link-Style.
|
|
16
16
|
// parseUrlToken — URL-Param-Helper (window.location.search).
|
|
17
17
|
|
|
18
|
+
import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
18
19
|
import { BareFormProvider, cn } from "@cosmicdrift/kumiko-renderer-web";
|
|
19
20
|
import { createContext, type ReactNode, useContext } from "react";
|
|
20
21
|
|
|
@@ -51,17 +52,30 @@ export type AuthCardProps = {
|
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
export function AuthCard({ title, subtitle, children }: AuthCardProps): ReactNode {
|
|
55
|
+
const { Card } = usePrimitives();
|
|
54
56
|
const shell = useAuthShell() ?? defaultAuthShell;
|
|
57
|
+
// h1 (Seiten-Hauptüberschrift) via Header-Slot erhalten — die Card-Default-
|
|
58
|
+
// Header wäre h3. padded:false = Form sitzt randlos wie bisher (bare form).
|
|
55
59
|
const card = (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
<Card
|
|
61
|
+
className="w-full max-w-sm"
|
|
62
|
+
options={{ padded: false }}
|
|
63
|
+
slots={{
|
|
64
|
+
header:
|
|
65
|
+
title !== undefined || subtitle !== undefined ? (
|
|
66
|
+
<div className="flex flex-col space-y-1.5 p-6 pb-4">
|
|
67
|
+
{title !== undefined && (
|
|
68
|
+
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
|
69
|
+
)}
|
|
70
|
+
{subtitle !== undefined && (
|
|
71
|
+
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
) : undefined,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
63
77
|
<BareFormProvider>{children}</BareFormProvider>
|
|
64
|
-
</
|
|
78
|
+
</Card>
|
|
65
79
|
);
|
|
66
80
|
return shell(card);
|
|
67
81
|
}
|
|
@@ -27,7 +27,7 @@ export function ConfirmAccountDeletionScreen({
|
|
|
27
27
|
}: ConfirmAccountDeletionScreenProps): ReactNode {
|
|
28
28
|
const t = useTranslation();
|
|
29
29
|
const dispatcher = useDispatcher();
|
|
30
|
-
const { Button, Banner } = usePrimitives();
|
|
30
|
+
const { Button, Banner, Card } = usePrimitives();
|
|
31
31
|
const [token] = useState(readToken);
|
|
32
32
|
const [phase, setPhase] = useState<Phase>(token.length > 0 ? "idle" : "missing");
|
|
33
33
|
|
|
@@ -42,12 +42,19 @@ export function ConfirmAccountDeletionScreen({
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
<Card
|
|
46
|
+
className="w-full max-w-sm mx-auto"
|
|
47
|
+
options={{ padded: false }}
|
|
48
|
+
slots={{
|
|
49
|
+
header: (
|
|
50
|
+
<div className="flex flex-col space-y-1.5 p-6 pb-4">
|
|
51
|
+
<h1 className="text-xl font-semibold tracking-tight">
|
|
52
|
+
{title ?? t("userDataRights.deletion.confirm.title")}
|
|
53
|
+
</h1>
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
51
58
|
<div className="p-6 pt-0 flex flex-col gap-4">
|
|
52
59
|
{phase === "success" ? (
|
|
53
60
|
<Banner variant="info">
|
|
@@ -83,6 +90,6 @@ export function ConfirmAccountDeletionScreen({
|
|
|
83
90
|
</>
|
|
84
91
|
)}
|
|
85
92
|
</div>
|
|
86
|
-
</
|
|
93
|
+
</Card>
|
|
87
94
|
);
|
|
88
95
|
}
|
|
@@ -130,7 +130,26 @@ function ExportSection(): ReactNode {
|
|
|
130
130
|
}, [inProgress, refetch]);
|
|
131
131
|
|
|
132
132
|
return (
|
|
133
|
-
<Section
|
|
133
|
+
<Section
|
|
134
|
+
title={t("userDataRights.privacyCenter.export.title")}
|
|
135
|
+
testId="privacy-export"
|
|
136
|
+
actions={
|
|
137
|
+
!inProgress ? (
|
|
138
|
+
<Button
|
|
139
|
+
onClick={() => void request()}
|
|
140
|
+
disabled={submitting}
|
|
141
|
+
loading={submitting}
|
|
142
|
+
testId="privacy-export-request"
|
|
143
|
+
>
|
|
144
|
+
{done
|
|
145
|
+
? t("userDataRights.privacyCenter.export.requestNew")
|
|
146
|
+
: submitting
|
|
147
|
+
? t("userDataRights.privacyCenter.export.requesting")
|
|
148
|
+
: t("userDataRights.privacyCenter.export.request")}
|
|
149
|
+
</Button>
|
|
150
|
+
) : undefined
|
|
151
|
+
}
|
|
152
|
+
>
|
|
134
153
|
<p className="text-sm text-muted-foreground">
|
|
135
154
|
{t("userDataRights.privacyCenter.export.intro")}
|
|
136
155
|
</p>
|
|
@@ -171,20 +190,6 @@ function ExportSection(): ReactNode {
|
|
|
171
190
|
</Banner>
|
|
172
191
|
)}
|
|
173
192
|
<StatusBanner status={status} />
|
|
174
|
-
{!inProgress && (
|
|
175
|
-
<Button
|
|
176
|
-
onClick={() => void request()}
|
|
177
|
-
disabled={submitting}
|
|
178
|
-
loading={submitting}
|
|
179
|
-
testId="privacy-export-request"
|
|
180
|
-
>
|
|
181
|
-
{done
|
|
182
|
-
? t("userDataRights.privacyCenter.export.requestNew")
|
|
183
|
-
: submitting
|
|
184
|
-
? t("userDataRights.privacyCenter.export.requesting")
|
|
185
|
-
: t("userDataRights.privacyCenter.export.request")}
|
|
186
|
-
</Button>
|
|
187
|
-
)}
|
|
188
193
|
</Section>
|
|
189
194
|
);
|
|
190
195
|
}
|
|
@@ -219,6 +224,17 @@ function RestrictionSection({
|
|
|
219
224
|
<Section
|
|
220
225
|
title={t("userDataRights.privacyCenter.restriction.title")}
|
|
221
226
|
testId="privacy-restriction"
|
|
227
|
+
actions={
|
|
228
|
+
restricted ? undefined : (
|
|
229
|
+
<Button
|
|
230
|
+
variant="danger"
|
|
231
|
+
onClick={() => setDialogOpen(true)}
|
|
232
|
+
testId="privacy-restriction-restrict"
|
|
233
|
+
>
|
|
234
|
+
{t("userDataRights.privacyCenter.restriction.restrict")}
|
|
235
|
+
</Button>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
222
238
|
>
|
|
223
239
|
{restricted ? (
|
|
224
240
|
<Banner variant="error" testId="privacy-restriction-active">
|
|
@@ -230,13 +246,6 @@ function RestrictionSection({
|
|
|
230
246
|
{t("userDataRights.privacyCenter.restriction.explainer")}
|
|
231
247
|
</p>
|
|
232
248
|
<StatusBanner status={status} />
|
|
233
|
-
<Button
|
|
234
|
-
variant="danger"
|
|
235
|
-
onClick={() => setDialogOpen(true)}
|
|
236
|
-
testId="privacy-restriction-restrict"
|
|
237
|
-
>
|
|
238
|
-
{t("userDataRights.privacyCenter.restriction.restrict")}
|
|
239
|
-
</Button>
|
|
240
249
|
<Dialog
|
|
241
250
|
open={dialogOpen}
|
|
242
251
|
onOpenChange={setDialogOpen}
|
|
@@ -291,7 +300,29 @@ function DeletionSection({
|
|
|
291
300
|
};
|
|
292
301
|
|
|
293
302
|
return (
|
|
294
|
-
<Section
|
|
303
|
+
<Section
|
|
304
|
+
title={t("userDataRights.privacyCenter.deletion.title")}
|
|
305
|
+
testId="privacy-deletion"
|
|
306
|
+
actions={
|
|
307
|
+
deletionRequested ? (
|
|
308
|
+
<Button
|
|
309
|
+
variant="secondary"
|
|
310
|
+
onClick={() => void cancelDeletion()}
|
|
311
|
+
testId="privacy-deletion-cancel"
|
|
312
|
+
>
|
|
313
|
+
{t("userDataRights.privacyCenter.deletion.cancel")}
|
|
314
|
+
</Button>
|
|
315
|
+
) : (
|
|
316
|
+
<Button
|
|
317
|
+
variant="danger"
|
|
318
|
+
onClick={() => setDialogOpen(true)}
|
|
319
|
+
testId="privacy-deletion-delete"
|
|
320
|
+
>
|
|
321
|
+
{t("userDataRights.privacyCenter.deletion.delete")}
|
|
322
|
+
</Button>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
>
|
|
295
326
|
{deletionRequested ? (
|
|
296
327
|
<>
|
|
297
328
|
<Banner variant="error" testId="privacy-deletion-requested">
|
|
@@ -300,13 +331,6 @@ function DeletionSection({
|
|
|
300
331
|
})}
|
|
301
332
|
</Banner>
|
|
302
333
|
<StatusBanner status={status} />
|
|
303
|
-
<Button
|
|
304
|
-
variant="secondary"
|
|
305
|
-
onClick={() => void cancelDeletion()}
|
|
306
|
-
testId="privacy-deletion-cancel"
|
|
307
|
-
>
|
|
308
|
-
{t("userDataRights.privacyCenter.deletion.cancel")}
|
|
309
|
-
</Button>
|
|
310
334
|
</>
|
|
311
335
|
) : (
|
|
312
336
|
<>
|
|
@@ -314,13 +338,6 @@ function DeletionSection({
|
|
|
314
338
|
{t("userDataRights.privacyCenter.deletion.explainer")}
|
|
315
339
|
</p>
|
|
316
340
|
<StatusBanner status={status} />
|
|
317
|
-
<Button
|
|
318
|
-
variant="danger"
|
|
319
|
-
onClick={() => setDialogOpen(true)}
|
|
320
|
-
testId="privacy-deletion-delete"
|
|
321
|
-
>
|
|
322
|
-
{t("userDataRights.privacyCenter.deletion.delete")}
|
|
323
|
-
</Button>
|
|
324
341
|
<Dialog
|
|
325
342
|
open={dialogOpen}
|
|
326
343
|
onOpenChange={setDialogOpen}
|
|
@@ -21,7 +21,7 @@ export function RequestAccountDeletionScreen({
|
|
|
21
21
|
}: RequestAccountDeletionScreenProps): ReactNode {
|
|
22
22
|
const t = useTranslation();
|
|
23
23
|
const dispatcher = useDispatcher();
|
|
24
|
-
const { Form, Field, Input, Button, Banner } = usePrimitives();
|
|
24
|
+
const { Form, Field, Input, Button, Banner, Card } = usePrimitives();
|
|
25
25
|
const [email, setEmail] = useState("");
|
|
26
26
|
const [submitting, setSubmitting] = useState(false);
|
|
27
27
|
const [done, setDone] = useState(false);
|
|
@@ -50,12 +50,19 @@ export function RequestAccountDeletionScreen({
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
<Card
|
|
54
|
+
className="w-full max-w-sm mx-auto"
|
|
55
|
+
options={{ padded: false }}
|
|
56
|
+
slots={{
|
|
57
|
+
header: (
|
|
58
|
+
<div className="flex flex-col space-y-1.5 p-6 pb-4">
|
|
59
|
+
<h1 className="text-xl font-semibold tracking-tight">
|
|
60
|
+
{title ?? t("userDataRights.deletion.request.title")}
|
|
61
|
+
</h1>
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
59
66
|
{done ? (
|
|
60
67
|
<div className="p-6 pt-0">
|
|
61
68
|
<Banner variant="info">
|
|
@@ -91,6 +98,6 @@ export function RequestAccountDeletionScreen({
|
|
|
91
98
|
</Form>
|
|
92
99
|
</div>
|
|
93
100
|
)}
|
|
94
|
-
</
|
|
101
|
+
</Card>
|
|
95
102
|
);
|
|
96
103
|
}
|
|
@@ -243,7 +243,7 @@ function DangerZoneSection({
|
|
|
243
243
|
readonly onChanged: () => void;
|
|
244
244
|
}): ReactNode {
|
|
245
245
|
const t = useTranslation();
|
|
246
|
-
const { Button, Banner, Dialog,
|
|
246
|
+
const { Button, Banner, Dialog, Card } = usePrimitives();
|
|
247
247
|
const dispatcher = useDispatcher();
|
|
248
248
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
249
249
|
const [status, setStatus] = useState<SectionStatus>({ kind: "idle" });
|
|
@@ -270,52 +270,54 @@ function DangerZoneSection({
|
|
|
270
270
|
onChanged();
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
+
const footer = deletionRequested ? (
|
|
274
|
+
<Button
|
|
275
|
+
variant="secondary"
|
|
276
|
+
onClick={() => void cancelDeletion()}
|
|
277
|
+
testId="profile-danger-cancel"
|
|
278
|
+
>
|
|
279
|
+
{t("profile.danger.cancelDeletion")}
|
|
280
|
+
</Button>
|
|
281
|
+
) : (
|
|
282
|
+
<Button variant="danger" onClick={() => setDialogOpen(true)} testId="profile-danger-delete">
|
|
283
|
+
{t("profile.danger.delete")}
|
|
284
|
+
</Button>
|
|
285
|
+
);
|
|
286
|
+
|
|
273
287
|
return (
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
className="
|
|
288
|
+
<Card
|
|
289
|
+
testId="profile-danger"
|
|
290
|
+
className="border-destructive/40"
|
|
291
|
+
slots={{ title: t("profile.danger.title"), footer }}
|
|
277
292
|
>
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<Dialog
|
|
307
|
-
open={dialogOpen}
|
|
308
|
-
onOpenChange={setDialogOpen}
|
|
309
|
-
title={t("profile.danger.dialogTitle")}
|
|
310
|
-
description={t("profile.danger.dialogDescription")}
|
|
311
|
-
variant="danger"
|
|
312
|
-
confirmLabel={t("profile.danger.delete")}
|
|
313
|
-
onConfirm={requestDeletion}
|
|
314
|
-
testId="profile-danger-dialog"
|
|
315
|
-
/>
|
|
316
|
-
</>
|
|
317
|
-
)}
|
|
318
|
-
</section>
|
|
293
|
+
<div className="flex flex-col gap-4">
|
|
294
|
+
{deletionRequested ? (
|
|
295
|
+
<>
|
|
296
|
+
<Banner variant="error" testId="profile-danger-requested">
|
|
297
|
+
{t("profile.danger.requested", {
|
|
298
|
+
date: formatDeletionDate(me.gracePeriodEnd),
|
|
299
|
+
})}
|
|
300
|
+
</Banner>
|
|
301
|
+
<StatusBanner status={status} />
|
|
302
|
+
</>
|
|
303
|
+
) : (
|
|
304
|
+
<>
|
|
305
|
+
<p className="text-sm text-muted-foreground">{t("profile.danger.explainer")}</p>
|
|
306
|
+
<StatusBanner status={status} />
|
|
307
|
+
<Dialog
|
|
308
|
+
open={dialogOpen}
|
|
309
|
+
onOpenChange={setDialogOpen}
|
|
310
|
+
title={t("profile.danger.dialogTitle")}
|
|
311
|
+
description={t("profile.danger.dialogDescription")}
|
|
312
|
+
variant="danger"
|
|
313
|
+
confirmLabel={t("profile.danger.delete")}
|
|
314
|
+
onConfirm={requestDeletion}
|
|
315
|
+
testId="profile-danger-dialog"
|
|
316
|
+
/>
|
|
317
|
+
</>
|
|
318
|
+
)}
|
|
319
|
+
</div>
|
|
320
|
+
</Card>
|
|
319
321
|
);
|
|
320
322
|
}
|
|
321
323
|
|