@micha.bigler/ui-core-micha 3.14.4 → 4.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.
- package/CHANGELOG.md +29 -0
- package/README.md +7 -0
- package/dist/chat/kitSxRegistry.js +9 -0
- package/dist/components/charts/kitSxRegistry.js +6 -0
- package/dist/entries/charts.js +6 -0
- package/dist/entries/chat.js +8 -0
- package/dist/entries/markdown.js +1 -0
- package/dist/index.js +0 -14
- package/dist/pages/AccountPage.js +1 -2
- package/dist/pages/LoginPage.js +1 -2
- package/dist/pages/PasswordChangePage.js +1 -2
- package/dist/pages/PasswordInvitePage.js +1 -2
- package/dist/pages/PasswordResetRequestPage.js +1 -2
- package/dist/pages/SignUpPage.js +1 -2
- package/dist/pages/SignupConfirmPage.js +1 -2
- package/dist/theme/kitSxEntry.js +11 -0
- package/dist/theme/kitSxRegistry.js +2 -15
- package/package.json +31 -7
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -126
- package/DESIGN.md +0 -60
- package/WORK_ORDERS.md +0 -150
- package/dev/entries.jsx +0 -721
- package/dev/fixtures.js +0 -26
- package/dev/main.jsx +0 -75
- package/dev/mockTransport.jsx +0 -128
- package/docs/CHART-LAYOUT.md +0 -157
- package/docs/DARK-TOKENS.md +0 -140
- package/docs/TYPE-SCALE.md +0 -111
- package/docs/messaging-deviations.md +0 -118
- package/index.html +0 -12
- package/renovate.json +0 -10
- package/scripts/chart-api-census.mjs +0 -415
- package/src/auth/AuthContext.jsx +0 -158
- package/src/auth/ReauthModal.jsx +0 -82
- package/src/auth/UserMenu.jsx +0 -121
- package/src/auth/apiClient.jsx +0 -232
- package/src/auth/authApi.jsx +0 -539
- package/src/auth/authConfig.jsx +0 -34
- package/src/auth/reauth.js +0 -47
- package/src/chat/ChatComposer.jsx +0 -36
- package/src/chat/ChatList.jsx +0 -87
- package/src/chat/ChatThread.jsx +0 -128
- package/src/chat/ChatWorkspace.jsx +0 -244
- package/src/chat/MarkdownReply.jsx +0 -22
- package/src/chat/ModelPicker.jsx +0 -85
- package/src/chat/api.js +0 -18
- package/src/chat/useChatSocket.js +0 -32
- package/src/chat/wire.js +0 -7
- package/src/components/AccessCodeManager.jsx +0 -288
- package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
- package/src/components/AllowedEmailDomainsManager.jsx +0 -92
- package/src/components/AuthFactorRequirementCard.jsx +0 -95
- package/src/components/BulkInviteCsvTab.jsx +0 -198
- package/src/components/EmptyState.jsx +0 -38
- package/src/components/LoginForm.jsx +0 -159
- package/src/components/MFAComponent.jsx +0 -350
- package/src/components/MfaLoginComponent.jsx +0 -252
- package/src/components/MobileBottomNav.jsx +0 -82
- package/src/components/PasskeysComponent.jsx +0 -252
- package/src/components/PasswordChangeForm.jsx +0 -87
- package/src/components/PasswordResetRequestForm.jsx +0 -43
- package/src/components/PasswordSetForm.jsx +0 -97
- package/src/components/ProfileComponent.jsx +0 -348
- package/src/components/QrSignupManager.jsx +0 -404
- package/src/components/QrSignupValidityManager.jsx +0 -106
- package/src/components/RegistrationMethodsManager.jsx +0 -150
- package/src/components/SecurityComponent.jsx +0 -141
- package/src/components/SocialLoginButtons.jsx +0 -83
- package/src/components/SoftChip.jsx +0 -93
- package/src/components/StatTile.jsx +0 -39
- package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
- package/src/components/TurnstileWidget.jsx +0 -99
- package/src/components/UserInviteComponent.jsx +0 -76
- package/src/components/UserListComponent.jsx +0 -618
- package/src/components/charts/BarChart.jsx +0 -102
- package/src/components/charts/ChartFrame.jsx +0 -214
- package/src/components/charts/LineChart.jsx +0 -126
- package/src/components/charts/ScatterChart.jsx +0 -299
- package/src/components/charts/TimeSeriesChart.jsx +0 -300
- package/src/components/charts/chartDefaults.js +0 -644
- package/src/components/charts/chartLabels.js +0 -36
- package/src/components/charts/exportChart.js +0 -258
- package/src/components/charts/formatters.js +0 -39
- package/src/components/charts/palette.js +0 -33
- package/src/components/charts/yearTickInterval.js +0 -7
- package/src/i18n/authTranslations.ts +0 -2651
- package/src/i18n/chartsTranslations.ts +0 -91
- package/src/i18n/chatTranslations.ts +0 -55
- package/src/i18n/messagingTranslations.ts +0 -120
- package/src/i18n/notificationsTranslations.ts +0 -32
- package/src/i18n/onboardingTranslations.ts +0 -29
- package/src/i18n/sectionNavTranslations.ts +0 -20
- package/src/i18n/uiCoreTranslations.js +0 -29
- package/src/i18n/userMenuTranslations.ts +0 -5
- package/src/index.js +0 -158
- package/src/layout/PageLayout.jsx +0 -36
- package/src/layout/SectionNav.jsx +0 -320
- package/src/messaging/AttachmentList.jsx +0 -145
- package/src/messaging/Composer.jsx +0 -197
- package/src/messaging/ConversationLaunchers.jsx +0 -51
- package/src/messaging/ConversationList.jsx +0 -109
- package/src/messaging/DirectMessageLauncher.jsx +0 -112
- package/src/messaging/MessageBubble.jsx +0 -183
- package/src/messaging/MessagingProvider.jsx +0 -0
- package/src/messaging/MessagingScopeConfig.jsx +0 -24
- package/src/messaging/PollCard.jsx +0 -163
- package/src/messaging/ReactionBar.jsx +0 -43
- package/src/messaging/ReadTicks.jsx +0 -82
- package/src/messaging/Thread.jsx +0 -195
- package/src/messaging/api.js +0 -48
- package/src/messaging/composerImageCompression.js +0 -35
- package/src/messaging/linkifyText.jsx +0 -37
- package/src/notifications/NotificationBell.jsx +0 -84
- package/src/notifications/NotificationSettings.jsx +0 -330
- package/src/notifications/NotificationsProvider.jsx +0 -224
- package/src/notifications/PopupSurface.jsx +0 -105
- package/src/notifications/api.js +0 -55
- package/src/notifications/feedApi.js +0 -18
- package/src/notifications/realtime.jsx +0 -138
- package/src/notifications/serviceWorker/sw.js +0 -50
- package/src/onboarding/OnboardingProvider.jsx +0 -326
- package/src/onboarding/OnboardingWizard.jsx +0 -61
- package/src/onboarding/WizardDialogShell.jsx +0 -51
- package/src/onboarding/api.js +0 -13
- package/src/onboarding/stepSelection.js +0 -16
- package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
- package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
- package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
- package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
- package/src/pages/AccountPage.jsx +0 -394
- package/src/pages/LoginPage.jsx +0 -343
- package/src/pages/PasswordChangePage.jsx +0 -62
- package/src/pages/PasswordInvitePage.jsx +0 -177
- package/src/pages/PasswordResetRequestPage.jsx +0 -68
- package/src/pages/SignUpPage.jsx +0 -268
- package/src/pages/SignupConfirmPage.jsx +0 -155
- package/src/testing/checkKitIntegration.js +0 -233
- package/src/theme/createAppTheme.js +0 -423
- package/src/theme/fonts.js +0 -3
- package/src/theme/index.js +0 -21
- package/src/theme/kitSxRegistry.js +0 -285
- package/src/theme/themeCompleteness.js +0 -1045
- package/src/theme/tokens.js +0 -478
- package/src/utils/auth-errors.js +0 -76
- package/src/utils/authService.js +0 -223
- package/src/utils/resolveErrorText.js +0 -28
- package/src/utils/webauthn.js +0 -51
- package/tests/AccountPage.profileStatements.test.jsx +0 -162
- package/tests/AccountPage.test.jsx +0 -200
- package/tests/AuthContext.test.jsx +0 -129
- package/tests/BarChart.test.jsx +0 -112
- package/tests/BrowserPushStep.test.jsx +0 -110
- package/tests/ChartFrame.test.jsx +0 -336
- package/tests/ConversationList.test.jsx +0 -144
- package/tests/CookieConsentStep.test.jsx +0 -69
- package/tests/EmptyState.test.jsx +0 -62
- package/tests/LineChart.test.jsx +0 -80
- package/tests/MessagingProvider.test.jsx +0 -50
- package/tests/MobileBottomNav.test.jsx +0 -319
- package/tests/NotificationBell.test.jsx +0 -60
- package/tests/NotificationSettings.test.jsx +0 -276
- package/tests/NotificationsProvider.test.jsx +0 -240
- package/tests/OnboardingProvider.test.jsx +0 -294
- package/tests/OnboardingWizard.test.jsx +0 -143
- package/tests/PasswordChangeForm.test.jsx +0 -70
- package/tests/PasswordInvitePage.test.jsx +0 -142
- package/tests/PasswordSetForm.test.jsx +0 -70
- package/tests/PopupSurface.test.jsx +0 -142
- package/tests/ProfileComponent.refresh.test.jsx +0 -194
- package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
- package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
- package/tests/ScatterChart.test.jsx +0 -270
- package/tests/SectionNav.test.jsx +0 -363
- package/tests/SignUpPage.test.jsx +0 -208
- package/tests/SoftChip.test.jsx +0 -105
- package/tests/StatTile.test.jsx +0 -57
- package/tests/Thread.test.jsx +0 -151
- package/tests/TimeSeriesChart.test.jsx +0 -367
- package/tests/UserListComponent.test.jsx +0 -192
- package/tests/UserMenu.test.jsx +0 -171
- package/tests/apiClientLanguage.test.js +0 -33
- package/tests/apiClientPublicPaths.test.js +0 -178
- package/tests/authApiLogin.test.js +0 -42
- package/tests/authApiRegistration.test.js +0 -31
- package/tests/authErrorCodeRendering.test.jsx +0 -77
- package/tests/authErrors.test.js +0 -74
- package/tests/authTranslations.test.js +0 -74
- package/tests/chart-api-census.test.js +0 -366
- package/tests/chartAxisTickBand.test.jsx +0 -95
- package/tests/chartDefaults.test.js +0 -902
- package/tests/chartLabels.test.js +0 -50
- package/tests/chartsPalette.test.js +0 -99
- package/tests/chatApi.test.js +0 -48
- package/tests/chatDriverNames.test.jsx +0 -71
- package/tests/chatExports.test.js +0 -3
- package/tests/chatList.test.jsx +0 -84
- package/tests/chatPicker.test.jsx +0 -82
- package/tests/chatSocket.test.jsx +0 -26
- package/tests/chatThread.test.jsx +0 -116
- package/tests/chatTranslations.test.js +0 -47
- package/tests/chatWorkspace.test.jsx +0 -234
- package/tests/checkKitIntegration.test.jsx +0 -219
- package/tests/createAppTheme.test.js +0 -132
- package/tests/createAppThemeDark.test.js +0 -127
- package/tests/createAppThemeLightGuard.test.js +0 -40
- package/tests/createAppThemePageContrast.test.js +0 -108
- package/tests/createAppThemeTypography.test.js +0 -53
- package/tests/errorCallSiteGuard.test.js +0 -32
- package/tests/exportChart.test.js +0 -313
- package/tests/feedApi.test.js +0 -46
- package/tests/fixtures/light-theme-baseline.json +0 -2234
- package/tests/i18nAggregate.test.js +0 -120
- package/tests/i18nGermanVariant.test.js +0 -65
- package/tests/loginPageErrorResolution.test.jsx +0 -102
- package/tests/messagingApi.test.js +0 -32
- package/tests/messagingChunk6.test.jsx +0 -116
- package/tests/messagingCompactBubble.test.jsx +0 -64
- package/tests/messagingComposer.test.jsx +0 -136
- package/tests/messagingComposerChunk5.test.jsx +0 -113
- package/tests/messagingContractConformance.test.js +0 -69
- package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
- package/tests/messagingExports.test.js +0 -21
- package/tests/messagingInteractions.test.jsx +0 -136
- package/tests/messagingLastMessagePreview.test.jsx +0 -93
- package/tests/messagingLinkify.test.jsx +0 -51
- package/tests/messagingMessageActions.test.jsx +0 -163
- package/tests/messagingMsg6.test.jsx +0 -105
- package/tests/messagingMsg6f.test.jsx +0 -155
- package/tests/messagingMsg6g.test.jsx +0 -166
- package/tests/messagingMsg6h.test.jsx +0 -136
- package/tests/messagingMsg6i.test.jsx +0 -129
- package/tests/messagingPollRendering.test.jsx +0 -142
- package/tests/messagingReactionMerge.test.jsx +0 -77
- package/tests/messagingReadTicksTooltip.test.jsx +0 -36
- package/tests/messagingThreadReceipts.test.jsx +0 -178
- package/tests/messagingThreadingQuoting.test.jsx +0 -87
- package/tests/messagingUnreadLifecycle.test.jsx +0 -78
- package/tests/notificationsApi.test.js +0 -41
- package/tests/notificationsExports.test.js +0 -34
- package/tests/onboardingDescriptors.test.js +0 -132
- package/tests/packageTreeShaking.test.js +0 -126
- package/tests/realtime.test.jsx +0 -219
- package/tests/resolveErrorText.test.js +0 -72
- package/tests/stepSelection.test.js +0 -53
- package/tests/themeCompleteness.test.js +0 -626
- package/tests/timeSeriesLegend.test.js +0 -99
- package/tests/userScreenTranslations.test.jsx +0 -218
- package/tsconfig.build.json +0 -18
- package/vite.config.mjs +0 -6
- package/work-orders/AUTH-1.md +0 -74
- package/work-orders/AUTH-2.md +0 -74
- package/work-orders/AUTH-4.md +0 -183
- package/work-orders/AUTH-5.md +0 -154
- package/work-orders/AUTH-7.md +0 -181
- package/work-orders/AUTH-8.md +0 -184
- package/work-orders/CHART-1.md +0 -285
- package/work-orders/CHART-2.md +0 -222
- package/work-orders/CHART-3.md +0 -163
- package/work-orders/CHART-4.md +0 -203
- package/work-orders/CHART-5.md +0 -226
- package/work-orders/CHART-6.md +0 -428
- package/work-orders/CHART-7.md +0 -175
- package/work-orders/DX-1.md +0 -169
- package/work-orders/DX-2.md +0 -188
- package/work-orders/DX-3.md +0 -195
- package/work-orders/I18N-1.md +0 -254
- package/work-orders/I18N-3.md +0 -207
- package/work-orders/MSG-3.md +0 -375
- package/work-orders/MSG-3b.md +0 -1002
- package/work-orders/MSG-3c.md +0 -378
- package/work-orders/MSG-3d.md +0 -268
- package/work-orders/MSG-3e.md +0 -164
- package/work-orders/MSG-3f.md +0 -391
- package/work-orders/MSG-6e.md +0 -308
- package/work-orders/MSG-6f.md +0 -214
- package/work-orders/MSG-6g.md +0 -244
- package/work-orders/MSG-6h.md +0 -238
- package/work-orders/MSG-6i.md +0 -271
- package/work-orders/MSG-6j.md +0 -117
- package/work-orders/NOTIF-12.md +0 -214
- package/work-orders/NOTIF-13.md +0 -142
- package/work-orders/NOTIF-14.md +0 -151
- package/work-orders/PRIM-1.md +0 -177
- package/work-orders/SHELL-1.md +0 -255
- package/work-orders/SHELL-2.md +0 -197
- package/work-orders/SHELL-3.md +0 -359
- package/work-orders/SHELL-4.md +0 -351
- package/work-orders/SHELL-5.md +0 -345
- package/work-orders/SHELL-6.md +0 -210
- package/work-orders/THEME-1.md +0 -587
- package/work-orders/THEME-10.md +0 -303
- package/work-orders/THEME-11.md +0 -208
- package/work-orders/THEME-2.md +0 -339
- package/work-orders/THEME-3.md +0 -210
- package/work-orders/THEME-4.md +0 -271
- package/work-orders/THEME-5.md +0 -341
- package/work-orders/THEME-6.md +0 -247
- package/work-orders/THEME-7.md +0 -82
- package/work-orders/THEME-8.md +0 -211
- package/work-orders/THEME-9.md +0 -194
- package/work-orders/UCM-AUTH-10.md +0 -490
- package/work-orders/UCM-AUTH-11.md +0 -185
- package/work-orders/UCM-AUTH-12.md +0 -98
- package/work-orders/UCM-AUTH-13.md +0 -47
- package/work-orders/UCM-AUTH-9.md +0 -207
- package/work-orders/UCM-CHART-10.md +0 -150
- package/work-orders/UCM-CHART-11.md +0 -132
- package/work-orders/UCM-CHART-12.md +0 -348
- package/work-orders/UCM-CHART-13.md +0 -122
- package/work-orders/UCM-CHART-14.md +0 -198
- package/work-orders/UCM-CHART-15.md +0 -187
- package/work-orders/UCM-CHART-16.md +0 -169
- package/work-orders/UCM-CHART-17.md +0 -195
- package/work-orders/UCM-CHART-18.md +0 -200
- package/work-orders/UCM-CHART-8.md +0 -147
- package/work-orders/UCM-CHART-9.md +0 -265
- package/work-orders/UCM-CHAT-1.md +0 -190
- package/work-orders/UCM-CHAT-2.md +0 -75
- package/work-orders/UCM-CI-1.md +0 -65
- package/work-orders/UCM-DEP-1.md +0 -63
- package/work-orders/UCM-I18N-4.md +0 -226
- package/work-orders/UCM-I18N-5.md +0 -211
- package/work-orders/UCM-MSG-20.md +0 -192
- package/work-orders/UCM-PRIM-2.md +0 -114
- package/work-orders/UCM-SHELL-7.md +0 -134
- package/work-orders/UCM-SHELL-8.md +0 -125
- package/work-orders/UCM-TEST-1.md +0 -304
- package/work-orders/UCM-THEME-13.md +0 -69
- package/work-orders/UCM-THEME-14.md +0 -312
- package/work-orders/UCM-THEME-15.md +0 -356
- package/work-orders/UCM-THEME-16.md +0 -80
- package/work-orders/UCM-THEME-17.md +0 -111
- package/work-orders/UCM-THEME-18.md +0 -184
- package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
- package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
- package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
- package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
- package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
- package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
package/src/pages/SignUpPage.jsx
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useContext,
|
|
3
|
-
useEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import { useNavigate, useLocation } from 'react-router-dom';
|
|
9
|
-
import {
|
|
10
|
-
Alert,
|
|
11
|
-
Box,
|
|
12
|
-
Button,
|
|
13
|
-
Stack,
|
|
14
|
-
TextField,
|
|
15
|
-
Typography,
|
|
16
|
-
} from '@mui/material';
|
|
17
|
-
import { Helmet } from 'react-helmet';
|
|
18
|
-
import { useTranslation } from 'react-i18next';
|
|
19
|
-
import { NarrowPage } from '../layout/PageLayout';
|
|
20
|
-
import { AuthContext } from '../auth/AuthContext';
|
|
21
|
-
import { submitRegistrationRequest } from '../auth/authApi';
|
|
22
|
-
import { TurnstileWidget } from '../components/TurnstileWidget';
|
|
23
|
-
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
24
|
-
|
|
25
|
-
export const SIGN_UP_PAGE_ALERT_SX = { mb: 2 };
|
|
26
|
-
|
|
27
|
-
const TURNSTILE_SIGNUP_MODES = new Set([
|
|
28
|
-
'self_signup_open',
|
|
29
|
-
'self_signup_email_domain',
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
const MODE_LABELS = {
|
|
33
|
-
self_signup_access_code: 'Auth.SIGNUP_ACCESS_CODE_TAB',
|
|
34
|
-
self_signup_open: 'Auth.SIGNUP_OPEN_TAB',
|
|
35
|
-
self_signup_email_domain: 'Auth.SIGNUP_EMAIL_DOMAIN_TAB',
|
|
36
|
-
self_signup_qr: 'Auth.SIGNUP_QR_TAB',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const MODE_SUBTITLES = {
|
|
40
|
-
self_signup_access_code: 'Auth.PAGE_SIGNUP_SUBTITLE_ACCESS_CODE',
|
|
41
|
-
self_signup_open: 'Auth.PAGE_SIGNUP_SUBTITLE_OPEN',
|
|
42
|
-
self_signup_email_domain: 'Auth.PAGE_SIGNUP_SUBTITLE_EMAIL_DOMAIN',
|
|
43
|
-
self_signup_qr: 'Auth.PAGE_SIGNUP_SUBTITLE_QR',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export function SignUpPage() {
|
|
47
|
-
const navigate = useNavigate();
|
|
48
|
-
const location = useLocation();
|
|
49
|
-
const { t, i18n } = useTranslation();
|
|
50
|
-
const { authMethods } = useContext(AuthContext);
|
|
51
|
-
|
|
52
|
-
const signupModes = useMemo(() => {
|
|
53
|
-
const configured = Array.isArray(authMethods?.signup_modes)
|
|
54
|
-
? authMethods.signup_modes.filter(Boolean)
|
|
55
|
-
: [];
|
|
56
|
-
if (configured.length > 0) {
|
|
57
|
-
return configured;
|
|
58
|
-
}
|
|
59
|
-
return authMethods?.signup ? ['self_signup_access_code'] : [];
|
|
60
|
-
}, [authMethods]);
|
|
61
|
-
|
|
62
|
-
const query = new URLSearchParams(location.search);
|
|
63
|
-
const tokenFromUrl = query.get('rt') || '';
|
|
64
|
-
|
|
65
|
-
const initialMode = useMemo(() => {
|
|
66
|
-
if (tokenFromUrl && signupModes.includes('self_signup_qr')) {
|
|
67
|
-
return 'self_signup_qr';
|
|
68
|
-
}
|
|
69
|
-
return signupModes[0] || 'self_signup_access_code';
|
|
70
|
-
}, [signupModes, tokenFromUrl]);
|
|
71
|
-
|
|
72
|
-
const [mode, setMode] = useState(initialMode);
|
|
73
|
-
const [email, setEmail] = useState('');
|
|
74
|
-
const [accessCode, setAccessCode] = useState('');
|
|
75
|
-
const [submitting, setSubmitting] = useState(false);
|
|
76
|
-
const [successKey, setSuccessKey] = useState(null);
|
|
77
|
-
const [errorKey, setErrorKey] = useState(null);
|
|
78
|
-
const [turnstileToken, setTurnstileToken] = useState('');
|
|
79
|
-
const turnstileWidgetRef = useRef(null);
|
|
80
|
-
|
|
81
|
-
const turnstileRequired = Boolean(authMethods?.turnstile_site_key)
|
|
82
|
-
&& TURNSTILE_SIGNUP_MODES.has(mode);
|
|
83
|
-
|
|
84
|
-
const pageSubtitle = useMemo(() => {
|
|
85
|
-
if (mode === 'self_signup_qr' && tokenFromUrl) {
|
|
86
|
-
return t(
|
|
87
|
-
'Auth.PAGE_SIGNUP_SUBTITLE_QR_READY',
|
|
88
|
-
'Enter your email address to complete sign-up with the QR link you opened.',
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
return t(
|
|
92
|
-
MODE_SUBTITLES[mode] || 'Auth.PAGE_SIGNUP_SUBTITLE',
|
|
93
|
-
'Choose a sign-up method and enter the required details.',
|
|
94
|
-
);
|
|
95
|
-
}, [mode, t, tokenFromUrl]);
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
setMode(initialMode);
|
|
99
|
-
}, [initialMode]);
|
|
100
|
-
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
setTurnstileToken('');
|
|
103
|
-
}, [mode, authMethods?.turnstile_site_key]);
|
|
104
|
-
|
|
105
|
-
const handleSubmit = async (event) => {
|
|
106
|
-
event.preventDefault();
|
|
107
|
-
setSuccessKey(null);
|
|
108
|
-
setErrorKey(null);
|
|
109
|
-
|
|
110
|
-
if (!email) {
|
|
111
|
-
setErrorKey(t('Auth.EMAIL_REQUIRED'));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (mode === 'self_signup_access_code' && !accessCode) {
|
|
116
|
-
setErrorKey(t('Auth.SIGNUP_ACCESS_CODE_REQUIRED'));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (mode === 'self_signup_qr' && !tokenFromUrl) {
|
|
121
|
-
setErrorKey(t('Auth.SIGNUP_QR_INVALID'));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (turnstileRequired && !turnstileToken) return;
|
|
126
|
-
|
|
127
|
-
setSubmitting(true);
|
|
128
|
-
try {
|
|
129
|
-
const res = await submitRegistrationRequest({
|
|
130
|
-
email,
|
|
131
|
-
mode,
|
|
132
|
-
accessCode,
|
|
133
|
-
registrationContextToken: mode === 'self_signup_qr' ? tokenFromUrl : null,
|
|
134
|
-
turnstileToken: turnstileRequired ? turnstileToken : undefined,
|
|
135
|
-
});
|
|
136
|
-
// The backend's own response code is already mode-agnostic and accurate
|
|
137
|
-
// (Auth.INVITE_SENT). Falling back to the access-code-specific "if valid,
|
|
138
|
-
// an invite was sent" wording here was wrong for open/email-domain/QR
|
|
139
|
-
// signup, which have no code whose validity needs hiding.
|
|
140
|
-
setSuccessKey(res?.code || 'Auth.INVITE_SENT');
|
|
141
|
-
} catch (err) {
|
|
142
|
-
setErrorKey(resolveErrorText(i18n, err, 'Auth.INVITE_FAILED'));
|
|
143
|
-
if (turnstileRequired) {
|
|
144
|
-
setTurnstileToken('');
|
|
145
|
-
turnstileWidgetRef.current?.reset();
|
|
146
|
-
}
|
|
147
|
-
} finally {
|
|
148
|
-
setSubmitting(false);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const handleGoToLogin = () => {
|
|
153
|
-
navigate('/login');
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<NarrowPage
|
|
158
|
-
title={t('Auth.LOGIN_SIGNUP_BUTTON')}
|
|
159
|
-
subtitle={pageSubtitle}
|
|
160
|
-
>
|
|
161
|
-
<Helmet>
|
|
162
|
-
<title>
|
|
163
|
-
{t('App.NAME')} – {t('Auth.LOGIN_SIGNUP_BUTTON')}
|
|
164
|
-
</title>
|
|
165
|
-
</Helmet>
|
|
166
|
-
|
|
167
|
-
{signupModes.length === 0 ? (
|
|
168
|
-
<Alert severity="info" sx={SIGN_UP_PAGE_ALERT_SX}>
|
|
169
|
-
{t('Auth.SELF_SIGNUP_DISABLED', 'Self sign-up is currently disabled.')}
|
|
170
|
-
</Alert>
|
|
171
|
-
) : (
|
|
172
|
-
<>
|
|
173
|
-
{successKey && (
|
|
174
|
-
<Alert severity="success" sx={SIGN_UP_PAGE_ALERT_SX}>
|
|
175
|
-
{t(successKey, { email })}
|
|
176
|
-
</Alert>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
{errorKey && (
|
|
180
|
-
<Alert severity="error" sx={SIGN_UP_PAGE_ALERT_SX}>
|
|
181
|
-
{errorKey}
|
|
182
|
-
</Alert>
|
|
183
|
-
)}
|
|
184
|
-
|
|
185
|
-
{signupModes.length > 1 && (
|
|
186
|
-
<Stack spacing={1} sx={{ mb: 2 }}>
|
|
187
|
-
<Stack spacing={1}>
|
|
188
|
-
{signupModes.map((entry) => (
|
|
189
|
-
<Button
|
|
190
|
-
key={entry}
|
|
191
|
-
variant={mode === entry ? 'contained' : 'outlined'}
|
|
192
|
-
onClick={() => setMode(entry)}
|
|
193
|
-
disabled={submitting}
|
|
194
|
-
fullWidth
|
|
195
|
-
>
|
|
196
|
-
{t(MODE_LABELS[entry] || entry, entry)}
|
|
197
|
-
</Button>
|
|
198
|
-
))}
|
|
199
|
-
</Stack>
|
|
200
|
-
</Stack>
|
|
201
|
-
)}
|
|
202
|
-
|
|
203
|
-
<Box
|
|
204
|
-
component="form"
|
|
205
|
-
onSubmit={handleSubmit}
|
|
206
|
-
sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
|
|
207
|
-
>
|
|
208
|
-
<TextField
|
|
209
|
-
label={t('Auth.EMAIL_LABEL')}
|
|
210
|
-
type="email"
|
|
211
|
-
required
|
|
212
|
-
fullWidth
|
|
213
|
-
value={email}
|
|
214
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
215
|
-
disabled={submitting}
|
|
216
|
-
/>
|
|
217
|
-
|
|
218
|
-
{mode === 'self_signup_access_code' && (
|
|
219
|
-
<TextField
|
|
220
|
-
label={t('Auth.ACCESS_CODE_LABEL')}
|
|
221
|
-
type="text"
|
|
222
|
-
required
|
|
223
|
-
fullWidth
|
|
224
|
-
value={accessCode}
|
|
225
|
-
onChange={(e) => setAccessCode(e.target.value)}
|
|
226
|
-
disabled={submitting}
|
|
227
|
-
/>
|
|
228
|
-
)}
|
|
229
|
-
|
|
230
|
-
{turnstileRequired && (
|
|
231
|
-
<TurnstileWidget
|
|
232
|
-
ref={turnstileWidgetRef}
|
|
233
|
-
siteKey={authMethods.turnstile_site_key}
|
|
234
|
-
onToken={setTurnstileToken}
|
|
235
|
-
/>
|
|
236
|
-
)}
|
|
237
|
-
|
|
238
|
-
<Button
|
|
239
|
-
type="submit"
|
|
240
|
-
variant="contained"
|
|
241
|
-
disabled={submitting || (turnstileRequired && !turnstileToken)}
|
|
242
|
-
>
|
|
243
|
-
{submitting
|
|
244
|
-
? t('Auth.SIGNUP_SUBMITTING')
|
|
245
|
-
: t('Auth.SIGNUP_SUBMIT')}
|
|
246
|
-
</Button>
|
|
247
|
-
</Box>
|
|
248
|
-
</>
|
|
249
|
-
)}
|
|
250
|
-
|
|
251
|
-
{/* Always rendered, closed or open — a closed signup page must still
|
|
252
|
-
leave the person a way forward (the Envelope's own Goal), not just
|
|
253
|
-
say why the form is missing. */}
|
|
254
|
-
<Box sx={{ mt: 3 }}>
|
|
255
|
-
<Typography variant="body2">
|
|
256
|
-
{t('Auth.SIGNUP_ALREADY_HAVE_ACCOUNT')}{' '}
|
|
257
|
-
<Button
|
|
258
|
-
onClick={handleGoToLogin}
|
|
259
|
-
variant="text"
|
|
260
|
-
size="small"
|
|
261
|
-
>
|
|
262
|
-
{t('Auth.SIGNUP_GO_TO_LOGIN')}
|
|
263
|
-
</Button>
|
|
264
|
-
</Typography>
|
|
265
|
-
</Box>
|
|
266
|
-
</NarrowPage>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// src/pages/SignupConfirmPage.jsx
|
|
2
|
-
//
|
|
3
|
-
// S13: Completes a pending registration. Reads the signed pending-token from
|
|
4
|
-
// the URL (`?token=...`), asks the user for a password, and POSTs to
|
|
5
|
-
// `register_confirm`. On success, the user is sent to /login to sign in
|
|
6
|
-
// explicitly — matches the existing invite/password-reset pattern and
|
|
7
|
-
// preserves any MFA / login-event flow the app may enforce.
|
|
8
|
-
import React, { useMemo, useState } from 'react';
|
|
9
|
-
import { useLocation, useNavigate } from 'react-router-dom';
|
|
10
|
-
import {
|
|
11
|
-
Alert,
|
|
12
|
-
Box,
|
|
13
|
-
Button,
|
|
14
|
-
Stack,
|
|
15
|
-
TextField,
|
|
16
|
-
Typography,
|
|
17
|
-
} from '@mui/material';
|
|
18
|
-
import { Helmet } from 'react-helmet';
|
|
19
|
-
import { useTranslation } from 'react-i18next';
|
|
20
|
-
import { NarrowPage } from '../layout/PageLayout';
|
|
21
|
-
import { confirmRegistration } from '../auth/authApi';
|
|
22
|
-
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
23
|
-
|
|
24
|
-
export const SIGNUP_CONFIRM_PAGE_ALERT_SX = { mb: 2 };
|
|
25
|
-
|
|
26
|
-
export function SignupConfirmPage() {
|
|
27
|
-
const { t, i18n } = useTranslation();
|
|
28
|
-
const location = useLocation();
|
|
29
|
-
const navigate = useNavigate();
|
|
30
|
-
|
|
31
|
-
const tokenFromUrl = useMemo(() => {
|
|
32
|
-
const query = new URLSearchParams(location.search);
|
|
33
|
-
return query.get('token') || '';
|
|
34
|
-
}, [location.search]);
|
|
35
|
-
|
|
36
|
-
const [password, setPassword] = useState('');
|
|
37
|
-
const [confirmPassword, setConfirmPassword] = useState('');
|
|
38
|
-
const [submitting, setSubmitting] = useState(false);
|
|
39
|
-
const [errorKey, setErrorKey] = useState(null);
|
|
40
|
-
|
|
41
|
-
const handleSubmit = async (event) => {
|
|
42
|
-
event.preventDefault();
|
|
43
|
-
setErrorKey(null);
|
|
44
|
-
|
|
45
|
-
if (!tokenFromUrl) {
|
|
46
|
-
setErrorKey(t('Auth.PENDING_TOKEN_INVALID'));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (!password || password.length < 8) {
|
|
50
|
-
setErrorKey(t('Auth.PASSWORD_TOO_SHORT'));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (password !== confirmPassword) {
|
|
54
|
-
setErrorKey(t('Auth.PASSWORD_MISMATCH'));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setSubmitting(true);
|
|
59
|
-
try {
|
|
60
|
-
await confirmRegistration({ token: tokenFromUrl, password });
|
|
61
|
-
// Send the user to /login. Aligned with PasswordInvitePage; no implicit
|
|
62
|
-
// session that would bypass MFA challenges or skip the explicit login
|
|
63
|
-
// event in audit logs.
|
|
64
|
-
navigate('/login', { replace: true });
|
|
65
|
-
return;
|
|
66
|
-
} catch (err) {
|
|
67
|
-
setErrorKey(resolveErrorText(i18n, err, 'Auth.PENDING_TOKEN_INVALID'));
|
|
68
|
-
} finally {
|
|
69
|
-
setSubmitting(false);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const tokenMissing = !tokenFromUrl;
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<NarrowPage
|
|
77
|
-
title={t('Auth.SIGNUP_CONFIRM_TITLE', 'Confirm your registration')}
|
|
78
|
-
subtitle={t(
|
|
79
|
-
'Auth.SIGNUP_CONFIRM_SUBTITLE',
|
|
80
|
-
'Choose a password to finish creating your account.',
|
|
81
|
-
)}
|
|
82
|
-
>
|
|
83
|
-
<Helmet>
|
|
84
|
-
<title>
|
|
85
|
-
{t('App.NAME')} – {t('Auth.SIGNUP_CONFIRM_TITLE', 'Confirm your registration')}
|
|
86
|
-
</title>
|
|
87
|
-
</Helmet>
|
|
88
|
-
|
|
89
|
-
{tokenMissing ? (
|
|
90
|
-
<>
|
|
91
|
-
<Alert severity="error" sx={SIGNUP_CONFIRM_PAGE_ALERT_SX}>
|
|
92
|
-
{t('Auth.PENDING_TOKEN_INVALID', 'This confirmation link is invalid or expired.')}
|
|
93
|
-
</Alert>
|
|
94
|
-
<Stack direction="row" spacing={1} sx={{ mt: 1 }}>
|
|
95
|
-
<Button onClick={() => navigate('/signup')} variant="contained">
|
|
96
|
-
{t('Auth.SIGNUP_REQUEST_NEW', 'Request a new invitation')}
|
|
97
|
-
</Button>
|
|
98
|
-
<Button onClick={() => navigate('/login')} variant="text">
|
|
99
|
-
{t('Auth.SIGNUP_GO_TO_LOGIN', 'Go to login')}
|
|
100
|
-
</Button>
|
|
101
|
-
</Stack>
|
|
102
|
-
</>
|
|
103
|
-
) : (
|
|
104
|
-
<>
|
|
105
|
-
{errorKey && (
|
|
106
|
-
<Alert severity="error" sx={SIGNUP_CONFIRM_PAGE_ALERT_SX}>
|
|
107
|
-
{errorKey}
|
|
108
|
-
</Alert>
|
|
109
|
-
)}
|
|
110
|
-
|
|
111
|
-
<Box
|
|
112
|
-
component="form"
|
|
113
|
-
onSubmit={handleSubmit}
|
|
114
|
-
sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
|
|
115
|
-
>
|
|
116
|
-
<TextField
|
|
117
|
-
label={t('Auth.NEW_PASSWORD_LABEL', 'New password')}
|
|
118
|
-
type="password"
|
|
119
|
-
required
|
|
120
|
-
fullWidth
|
|
121
|
-
value={password}
|
|
122
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
123
|
-
disabled={submitting}
|
|
124
|
-
autoComplete="new-password"
|
|
125
|
-
/>
|
|
126
|
-
<TextField
|
|
127
|
-
label={t('Auth.PASSWORD_CONFIRM_LABEL', 'Confirm new password')}
|
|
128
|
-
type="password"
|
|
129
|
-
required
|
|
130
|
-
fullWidth
|
|
131
|
-
value={confirmPassword}
|
|
132
|
-
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
133
|
-
disabled={submitting}
|
|
134
|
-
autoComplete="new-password"
|
|
135
|
-
/>
|
|
136
|
-
<Button type="submit" variant="contained" disabled={submitting}>
|
|
137
|
-
{submitting
|
|
138
|
-
? t('Auth.SIGNUP_CONFIRM_SUBMITTING', 'Confirming…')
|
|
139
|
-
: t('Auth.SIGNUP_CONFIRM_SUBMIT', 'Confirm registration')}
|
|
140
|
-
</Button>
|
|
141
|
-
</Box>
|
|
142
|
-
|
|
143
|
-
<Stack direction="row" spacing={1} sx={{ mt: 3 }}>
|
|
144
|
-
<Typography variant="body2">
|
|
145
|
-
{t('Auth.SIGNUP_ALREADY_HAVE_ACCOUNT', 'Already have an account?')}
|
|
146
|
-
</Typography>
|
|
147
|
-
<Button onClick={() => navigate('/login')} variant="text" size="small">
|
|
148
|
-
{t('Auth.SIGNUP_GO_TO_LOGIN', 'Go to login')}
|
|
149
|
-
</Button>
|
|
150
|
-
</Stack>
|
|
151
|
-
</>
|
|
152
|
-
)}
|
|
153
|
-
</NarrowPage>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import React, { act } from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
-
import i18next from 'i18next';
|
|
5
|
-
|
|
6
|
-
import apiClient from '../auth/apiClient';
|
|
7
|
-
import { LoginPage } from '../pages/LoginPage';
|
|
8
|
-
import { SignUpPage } from '../pages/SignUpPage';
|
|
9
|
-
import { PasswordInvitePage } from '../pages/PasswordInvitePage';
|
|
10
|
-
import { uiCoreTranslations } from '../i18n/uiCoreTranslations';
|
|
11
|
-
|
|
12
|
-
const KIT_LANGUAGES = ['de', 'en', 'fr', 'sw'];
|
|
13
|
-
const LOGIN_PATH = '/api/auth/browser/v1/auth/login';
|
|
14
|
-
const RAW_CODE = /^[a-z][a-z0-9_]*$/;
|
|
15
|
-
|
|
16
|
-
function pageName(Page) {
|
|
17
|
-
return Page?.displayName || Page?.name || 'page';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function appLanguages(i18n) {
|
|
21
|
-
const candidates = [
|
|
22
|
-
i18n?.options?.supportedLngs,
|
|
23
|
-
Object.keys(i18n?.options?.resources || {}),
|
|
24
|
-
i18n?.languages,
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
for (const candidate of candidates) {
|
|
28
|
-
const languages = Array.isArray(candidate)
|
|
29
|
-
? candidate.filter((language) => language && language !== 'cimode')
|
|
30
|
-
: [];
|
|
31
|
-
if (languages.length > 0) return languages;
|
|
32
|
-
}
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function textNodes(container) {
|
|
37
|
-
const result = [];
|
|
38
|
-
const document = container.ownerDocument;
|
|
39
|
-
const filter = document.defaultView?.NodeFilter || NodeFilter;
|
|
40
|
-
const walker = document.createTreeWalker(container, filter.SHOW_TEXT);
|
|
41
|
-
let node;
|
|
42
|
-
while ((node = walker.nextNode())) {
|
|
43
|
-
const text = node.textContent.trim();
|
|
44
|
-
if (text) result.push(text);
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const KIT_KEYS = new Set(Object.keys(uiCoreTranslations));
|
|
50
|
-
// Every legitimate string the kit itself ships, across all locales -- a short,
|
|
51
|
-
// all-lowercase, no-space word can still be entirely legitimate rendered prose
|
|
52
|
-
// (e.g. `Auth.LOGIN_OR`'s German value is literally "oder", which the bare
|
|
53
|
-
// RAW_CODE shape below would otherwise flag as a raw backend code). Exempting
|
|
54
|
-
// anything that IS one of the kit's own translated values keeps the check
|
|
55
|
-
// looking for text that is NEITHER a key NOR a real translation -- which is
|
|
56
|
-
// exactly what a leaked backend code is, and a legitimate short word never is.
|
|
57
|
-
const KIT_VALUES = new Set(
|
|
58
|
-
Object.values(uiCoreTranslations).flatMap((entry) => Object.values(entry)),
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
function scanPage(container, name) {
|
|
62
|
-
return textNodes(container)
|
|
63
|
-
.filter((text) => (
|
|
64
|
-
KIT_KEYS.has(text)
|
|
65
|
-
|| (!KIT_VALUES.has(text) && text.length > 2 && RAW_CODE.test(text))
|
|
66
|
-
))
|
|
67
|
-
.map((text) => ({
|
|
68
|
-
check: 'render',
|
|
69
|
-
reason: `${name} rendered raw kit key or backend code: ${text}`,
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function renderPage(Page, wrapper, initialEntries = ['/']) {
|
|
74
|
-
const container = document.createElement('div');
|
|
75
|
-
const root = createRoot(container);
|
|
76
|
-
try {
|
|
77
|
-
await act(async () => {
|
|
78
|
-
root.render(React.createElement(
|
|
79
|
-
MemoryRouter,
|
|
80
|
-
{ initialEntries },
|
|
81
|
-
React.createElement(wrapper, null, React.createElement(Page)),
|
|
82
|
-
));
|
|
83
|
-
await Promise.resolve();
|
|
84
|
-
});
|
|
85
|
-
return { container, findings: scanPage(container, pageName(Page)) };
|
|
86
|
-
} finally {
|
|
87
|
-
await act(async () => {
|
|
88
|
-
root.unmount();
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function transportFailure(config) {
|
|
94
|
-
return Promise.reject(new Error(`checkKitIntegration blocked request: ${config?.url || 'unknown'}`));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function loginFailure() {
|
|
98
|
-
return Promise.reject({
|
|
99
|
-
response: {
|
|
100
|
-
status: 400,
|
|
101
|
-
data: {
|
|
102
|
-
errors: [{
|
|
103
|
-
code: 'invalid',
|
|
104
|
-
param: 'email',
|
|
105
|
-
message: 'The email address is invalid.',
|
|
106
|
-
}],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function renderFindings(Page, wrapper, initialEntries) {
|
|
113
|
-
try {
|
|
114
|
-
const rendered = await renderPage(Page, wrapper, initialEntries);
|
|
115
|
-
return rendered.findings;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return [{
|
|
118
|
-
check: 'render',
|
|
119
|
-
reason: `${pageName(Page)} threw while rendering: ${error?.message || String(error)}`,
|
|
120
|
-
}];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function errorTextFindings(i18n, wrapper) {
|
|
125
|
-
const Page = LoginPage;
|
|
126
|
-
const name = pageName(Page);
|
|
127
|
-
const container = document.createElement('div');
|
|
128
|
-
const root = createRoot(container);
|
|
129
|
-
try {
|
|
130
|
-
await act(async () => {
|
|
131
|
-
root.render(React.createElement(
|
|
132
|
-
MemoryRouter,
|
|
133
|
-
{ initialEntries: ['/login'] },
|
|
134
|
-
React.createElement(wrapper, null, React.createElement(Page)),
|
|
135
|
-
));
|
|
136
|
-
await Promise.resolve();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const inputs = Array.from(container.querySelectorAll('input'));
|
|
140
|
-
const email = inputs.find((input) => input.type === 'email');
|
|
141
|
-
const password = inputs.find((input) => input.type === 'password');
|
|
142
|
-
const form = container.querySelector('form');
|
|
143
|
-
if (!email || !password || !form) {
|
|
144
|
-
return [{ check: 'error-text', reason: `${name} did not render its login form.` }];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const setValue = (input, value) => {
|
|
148
|
-
const setter = Object.getOwnPropertyDescriptor(
|
|
149
|
-
input.ownerDocument.defaultView.HTMLInputElement.prototype,
|
|
150
|
-
'value',
|
|
151
|
-
).set;
|
|
152
|
-
setter.call(input, value);
|
|
153
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
154
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
await act(async () => {
|
|
158
|
-
setValue(email, 'not-an-email@example.com');
|
|
159
|
-
setValue(password, 'password');
|
|
160
|
-
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
161
|
-
await Promise.resolve();
|
|
162
|
-
await Promise.resolve();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const expected = i18n.t('Auth.field.email.invalid');
|
|
166
|
-
const renderedTexts = textNodes(container);
|
|
167
|
-
if (renderedTexts.includes(expected)) return [];
|
|
168
|
-
return [{
|
|
169
|
-
check: 'error-text',
|
|
170
|
-
reason: `${name} did not show ${expected} for the allauth invalid-email response.`,
|
|
171
|
-
}];
|
|
172
|
-
} catch (error) {
|
|
173
|
-
return [{
|
|
174
|
-
check: 'error-text',
|
|
175
|
-
reason: `${name} error flow failed: ${error?.message || String(error)}`,
|
|
176
|
-
}];
|
|
177
|
-
} finally {
|
|
178
|
-
await act(async () => {
|
|
179
|
-
root.unmount();
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Check that an app's i18n instance and providers wire the kit pages correctly. */
|
|
185
|
-
export async function checkKitIntegration({
|
|
186
|
-
i18n,
|
|
187
|
-
wrapper,
|
|
188
|
-
pages = [LoginPage, SignUpPage, PasswordInvitePage],
|
|
189
|
-
}) {
|
|
190
|
-
const findings = [];
|
|
191
|
-
const languages = appLanguages(i18n);
|
|
192
|
-
const sharedLanguages = KIT_LANGUAGES.filter((language) => languages.includes(language));
|
|
193
|
-
|
|
194
|
-
for (const [key] of Object.entries(uiCoreTranslations)) {
|
|
195
|
-
for (const language of sharedLanguages) {
|
|
196
|
-
// fallbackLng: false forces a true per-language check -- without it, a
|
|
197
|
-
// consumer's own fallbackLng config (e.g. "en") would let a key missing
|
|
198
|
-
// in "fr" silently resolve through the fallback and read as present.
|
|
199
|
-
if (!i18n?.exists(key, { lng: language, fallbackLng: false })) {
|
|
200
|
-
findings.push({
|
|
201
|
-
check: 'catalogue',
|
|
202
|
-
reason: `Missing kit translation key ${key} for language ${language}.`,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!i18next.isInitialized || i18n !== i18next) {
|
|
209
|
-
findings.push({
|
|
210
|
-
check: 'instance',
|
|
211
|
-
reason: 'The app i18n is not the initialized kit default instance; Accept-Language will be missing or wrong.',
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const originalAdapter = apiClient.defaults.adapter;
|
|
216
|
-
apiClient.defaults.adapter = async (config) => {
|
|
217
|
-
if (config?.url === LOGIN_PATH || String(config?.url || '').endsWith(LOGIN_PATH)) {
|
|
218
|
-
return loginFailure();
|
|
219
|
-
}
|
|
220
|
-
return transportFailure(config);
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
for (const Page of pages) {
|
|
225
|
-
findings.push(...await renderFindings(Page, wrapper));
|
|
226
|
-
}
|
|
227
|
-
findings.push(...await errorTextFindings(i18n, wrapper));
|
|
228
|
-
} finally {
|
|
229
|
-
apiClient.defaults.adapter = originalAdapter;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { findings };
|
|
233
|
-
}
|