diff --git a/package-lock.json b/package-lock.json index 6938938a..b652843f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26724,16 +26724,14 @@ "name": "@corbado/shared-ui", "version": "0.1.0", "license": "ISC", - "dependencies": { - "@corbado/web-core": "*", - "i18next": "23.5.1" - }, "devDependencies": { "@corbado/types": "*", + "@corbado/web-core": "*", "@svgr/webpack": "^8.1.0", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "file-loader": "^6.2.0", + "i18next": "23.5.1", "image-webpack-loader": "^8.1.0", "style-loader": "^3.3.3", "svgo-loader": "^4.0.0", diff --git a/packages/react-sdk/src/contexts/CorbadoContext.tsx b/packages/react-sdk/src/contexts/CorbadoContext.tsx index 392a3dba..d4a586c8 100644 --- a/packages/react-sdk/src/contexts/CorbadoContext.tsx +++ b/packages/react-sdk/src/contexts/CorbadoContext.tsx @@ -4,7 +4,8 @@ import type { AuthMethodsListError, CompleteLoginWithEmailOTPError, CompleteSignupWithEmailOTPError, - CorbadoAppParams, GetProjectConfigError, + CorbadoAppParams, + GetProjectConfigError, InitLoginWithEmailOTPError, InitSignUpWithEmailOTPError, LoginWithPasskeyError, @@ -12,9 +13,9 @@ import type { RecoverableError, SignUpWithPasskeyError, } from '@corbado/web-core'; +import type { CorbadoApp } from '@corbado/web-core'; import { createContext, type PropsWithChildren } from 'react'; import type { Result } from 'ts-results'; -import {CorbadoApp} from "@corbado/web-core"; export type AppProviderParams = PropsWithChildren; diff --git a/packages/react-sdk/src/contexts/CorbadoProvider.tsx b/packages/react-sdk/src/contexts/CorbadoProvider.tsx index d0552106..8a7b1315 100644 --- a/packages/react-sdk/src/contexts/CorbadoProvider.tsx +++ b/packages/react-sdk/src/contexts/CorbadoProvider.tsx @@ -1,6 +1,6 @@ import type { SessionUser } from '@corbado/types'; -import type { NonRecoverableError} from '@corbado/web-core'; -import {CorbadoApp} from '@corbado/web-core'; +import type { NonRecoverableError } from '@corbado/web-core'; +import { CorbadoApp } from '@corbado/web-core'; import type { FC } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; diff --git a/packages/react-sdk/src/index.ts b/packages/react-sdk/src/index.ts index a56ba221..35805023 100644 --- a/packages/react-sdk/src/index.ts +++ b/packages/react-sdk/src/index.ts @@ -1,7 +1,7 @@ import { InvalidEmailError, - InvalidPasskeyError, InvalidFullnameError, + InvalidPasskeyError, NoPasskeyAvailableError, PasskeyChallengeCancelledError, UnknownUserError, diff --git a/packages/react/src/components/EmailOtpScreenWrapper.tsx b/packages/react/src/components/EmailOtpScreenWrapper.tsx index be6866b2..8f171525 100644 --- a/packages/react/src/components/EmailOtpScreenWrapper.tsx +++ b/packages/react/src/components/EmailOtpScreenWrapper.tsx @@ -1,13 +1,13 @@ import type { FC, FormEvent, ReactNode } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; +import useFlowHandler from '../hooks/useFlowHandler'; import { Body } from './Body'; import { Button } from './Button'; import { Header } from './Header'; import { IconLink } from './IconLink'; import { Gmail, Outlook, Yahoo } from './icons'; import { OtpInputGroup } from './OtpInputGroup'; -import useFlowHandler from '../hooks/useFlowHandler'; export interface EmailOtpScreenProps { header: ReactNode; @@ -30,7 +30,6 @@ export const EmailOtpScreenWrapper: FC = ({ }) => { const [otp, setOTP] = useState(''); const { currentUserState } = useFlowHandler(); - const [isOtpValid] = useState(false); const [loading, setLoading] = useState(false); const submitButtonRef = useRef(null); @@ -91,7 +90,7 @@ export const EmailOtpScreenWrapper: FC = ({ ref={submitButtonRef} variant='primary' isLoading={loading} - disabled={!isOtpValid || loading} + disabled={!loading} > {verificationButtonText} diff --git a/packages/react/src/contexts/FlowHandlerContext.ts b/packages/react/src/contexts/FlowHandlerContext.ts index 958b9a8e..0bc825f7 100644 --- a/packages/react/src/contexts/FlowHandlerContext.ts +++ b/packages/react/src/contexts/FlowHandlerContext.ts @@ -14,10 +14,9 @@ export interface FlowHandlerContextProps { currentScreen: ScreenNames; currentUserState: UserState; initialized: boolean; - navigateNext: (event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) => Promise; navigateBack: () => ScreenNames; changeFlow: (flowType: FlowType) => void; - emitEvent: (event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) => Promise; + emitEvent: (event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) => Promise | undefined; } export const initialContext: FlowHandlerContextProps = { @@ -25,7 +24,6 @@ export const initialContext: FlowHandlerContextProps = { currentScreen: CommonScreens.Start, currentUserState: {}, initialized: false, - navigateNext: () => Promise.resolve(), navigateBack: () => CommonScreens.Start, changeFlow: () => void 0, emitEvent: () => Promise.reject(), diff --git a/packages/react/src/contexts/FlowHandlerProvider.tsx b/packages/react/src/contexts/FlowHandlerProvider.tsx index 794f2a2b..5949dffd 100644 --- a/packages/react/src/contexts/FlowHandlerProvider.tsx +++ b/packages/react/src/contexts/FlowHandlerProvider.tsx @@ -27,13 +27,13 @@ export const FlowHandlerProvider: FC> = ({ children, .. ? LoginFlowNames.PasskeyLoginWithEmailOTPFallback : SignUpFlowNames.PasskeySignupWithEmailOTPFallback, ); - const initialized = useRef(false); + const [initialized, setInitialized] = useState(false); const onScreenChangeCbId = useRef(0); const onFlowChangeCbId = useRef(0); const onUserStateChangeCbId = useRef(0); useEffect(() => { - if (initialized.current) { + if (initialized) { return; } @@ -55,7 +55,7 @@ export const FlowHandlerProvider: FC> = ({ children, .. void flowHandler.init(corbadoApp); setFlowHandler(flowHandler); - initialized.current = true; + setInitialized(true); })(); return () => { @@ -66,20 +66,13 @@ export const FlowHandlerProvider: FC> = ({ children, .. }, []); useEffect(() => { - if (!initialized.current || !user) { + if (!initialized || !user) { return; } flowHandler?.updateUser(user); }, [initialized, user]); - const navigateNext = useCallback( - async (event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) => { - (await flowHandler?.navigateNext(event, eventOptions)) ?? CommonScreens.End; - }, - [flowHandler], - ); - const navigateBack = useCallback(() => { return flowHandler?.navigateBack() ?? CommonScreens.Start; }, [flowHandler]); @@ -93,7 +86,7 @@ export const FlowHandlerProvider: FC> = ({ children, .. const emitEvent = useCallback( (event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) => { - return flowHandler!.handleStateUpdate(event, eventOptions); + return flowHandler?.handleStateUpdate(event, eventOptions); }, [flowHandler], ); @@ -103,21 +96,12 @@ export const FlowHandlerProvider: FC> = ({ children, .. currentFlow, currentScreen, currentUserState, - initialized: initialized.current, - navigateNext, + initialized, navigateBack, changeFlow, emitEvent, }), - [ - currentFlow, - currentScreen, - currentUserState, - initialized.current, - navigateNext, - navigateBack, - changeFlow, - ], + [currentFlow, currentScreen, currentUserState, initialized, navigateBack, changeFlow], ); return {children}; diff --git a/packages/react/src/flows/PasskeyLoginWithEmailOTPFallbackFlow.tsx b/packages/react/src/flows/PasskeyLoginWithEmailOTPFallbackFlow.tsx index 314dc222..d7b1bf28 100644 --- a/packages/react/src/flows/PasskeyLoginWithEmailOTPFallbackFlow.tsx +++ b/packages/react/src/flows/PasskeyLoginWithEmailOTPFallbackFlow.tsx @@ -3,8 +3,8 @@ import { PasskeyLoginWithEmailOtpFallbackScreens } from '@corbado/shared-ui'; import { EmailOTP } from '../screens/login/EmailOTP'; import { InitiateLogin } from '../screens/login/InitiateLogin'; import { PasskeyAppend } from '../screens/login/PasskeyAppend'; -import { PasskeyError } from '../screens/login/PasskeyError'; import { PasskeyBenefits } from '../screens/login/PasskeyBenefits'; +import { PasskeyError } from '../screens/login/PasskeyError'; export const PasskeyLoginWithEmailOTPFallbackFlow = { [PasskeyLoginWithEmailOtpFallbackScreens.Start]: InitiateLogin, diff --git a/packages/react/src/screens/ErrorBoundary.tsx b/packages/react/src/screens/ErrorBoundary.tsx index 141ceae5..cc73fe7e 100644 --- a/packages/react/src/screens/ErrorBoundary.tsx +++ b/packages/react/src/screens/ErrorBoundary.tsx @@ -2,6 +2,7 @@ import type { NonRecoverableError, RecoverableError } from '@corbado/web-core'; import React from 'react'; import NonRecoverableErrorComponent from './errors/NonRecoverableError'; +import NonRecoverableErrorForCustomer from './errors/NonRecoverableErrorForCustomer'; export type ErrorBoundaryProps = React.PropsWithChildren<{ globalError: NonRecoverableError | undefined; @@ -29,26 +30,7 @@ export class ErrorBoundary extends React.Component -
-
Something went wrong
-
-
We’re sorry that our service is currently not available.
-
- Please try again in a few moments and if the issue persists - {this.props.customerSupportEmail ? `, please contact ${this.props.customerSupportEmail}.` : '.'} -
-
- -
- - ); + return ; } if (this.props.globalError) { diff --git a/packages/react/src/screens/LoadingScreen.tsx b/packages/react/src/screens/LoadingScreen.tsx deleted file mode 100644 index 43133252..00000000 --- a/packages/react/src/screens/LoadingScreen.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const LoadingScreen = () => { - return
Loading...
; -}; diff --git a/packages/react/src/screens/ScreenFlow.tsx b/packages/react/src/screens/ScreenFlow.tsx index 1f3c9b8d..ca67192e 100644 --- a/packages/react/src/screens/ScreenFlow.tsx +++ b/packages/react/src/screens/ScreenFlow.tsx @@ -3,11 +3,11 @@ import { CommonScreens } from '@corbado/shared-ui'; import type { FC } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Spinner } from '../components'; import type { ScreenMap } from '../flows'; import { flowScreensMap } from '../flows'; import useFlowHandler from '../hooks/useFlowHandler'; import { ErrorBoundary } from './ErrorBoundary'; -import {LoadingScreen} from "./LoadingScreen"; interface ScreenFlowProps { isDevMode: boolean; @@ -39,7 +39,7 @@ export const ScreensFlow: FC = ({ isDevMode, customerSupportEma isDevMode={isDevMode} customerSupportEmail={customerSupportEmail} > - {initialized ? ScreenComponent ? : : } + {initialized ? ScreenComponent ? : : } ); }; diff --git a/packages/react/src/screens/errors/NonRecoverableErrorForCustomer.tsx b/packages/react/src/screens/errors/NonRecoverableErrorForCustomer.tsx new file mode 100644 index 00000000..dc7205a2 --- /dev/null +++ b/packages/react/src/screens/errors/NonRecoverableErrorForCustomer.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +const NonRecoverableErrorForCustomer = (customerSupportEmail: { customerSupportEmail: string }) => { + const { t } = useTranslation('translation', { keyPrefix: 'authenticationFlows.unexpectedError' }); + return ( +
+
+
{t('header')}
+
+
{t('body_introduction')}
+
+ {customerSupportEmail + ? t('body_explanationCustomerSupport', customerSupportEmail) + : t('body_explanationNoCustomerSupport')} +
+
+ +
+
+ ); +}; + +export default NonRecoverableErrorForCustomer; diff --git a/packages/react/src/screens/login/EmailOTP.tsx b/packages/react/src/screens/login/EmailOTP.tsx index 233e37be..3706457e 100644 --- a/packages/react/src/screens/login/EmailOTP.tsx +++ b/packages/react/src/screens/login/EmailOTP.tsx @@ -8,7 +8,6 @@ import useFlowHandler from '../../hooks/useFlowHandler'; export const EmailOTP = () => { const { t } = useTranslation('translation', { keyPrefix: 'authenticationFlows.login.emailOtp' }); - const { t: tErrors } = useTranslation('translation', { keyPrefix: 'errors' }); const { emitEvent, currentUserState } = useFlowHandler(); const header = t('header'); @@ -19,7 +18,6 @@ export const EmailOTP = () => { {t('body_text2')} ); - const validationError = tErrors('serverError_invalidOtp'); const verificationButtonText = t('button_verify'); const backButtonText = t('button_back'); @@ -38,7 +36,6 @@ export const EmailOTP = () => { () => ({ header, body, - validationError, verificationButtonText, backButtonText, onVerificationButtonClick: handleOTPVerification, @@ -51,7 +48,6 @@ export const EmailOTP = () => { handleCancel, header, body, - validationError, verificationButtonText, backButtonText, ], diff --git a/packages/react/src/screens/signup/EmailOTP.tsx b/packages/react/src/screens/signup/EmailOTP.tsx index 5c2c427e..7e5dd023 100644 --- a/packages/react/src/screens/signup/EmailOTP.tsx +++ b/packages/react/src/screens/signup/EmailOTP.tsx @@ -24,9 +24,9 @@ export const EmailOTP = () => { const handleCancel = useCallback(() => emitEvent(FlowHandlerEvents.SecondaryButton), []); const handleOTPVerification: EmailOtpScreenProps['onVerificationButtonClick'] = useCallback( - (otp: string, setLoading) => { + async (otp: string, setLoading) => { setLoading(true); - return emitEvent(FlowHandlerEvents.PrimaryButton, { emailOTPCode: otp }); + await emitEvent(FlowHandlerEvents.PrimaryButton, { emailOTPCode: otp }); }, [], ); diff --git a/packages/react/src/screens/signup/PasskeyWelcome.tsx b/packages/react/src/screens/signup/PasskeyWelcome.tsx index ef2fc6b7..a952e248 100644 --- a/packages/react/src/screens/signup/PasskeyWelcome.tsx +++ b/packages/react/src/screens/signup/PasskeyWelcome.tsx @@ -1,10 +1,10 @@ +import { FlowHandlerEvents } from '@corbado/shared-ui'; import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import type { PasskeyScreensWrapperProps } from '../../components'; import { PasskeyScreensWrapper } from '../../components'; import useFlowHandler from '../../hooks/useFlowHandler'; -import { FlowHandlerEvents } from '@corbado/shared-ui'; export const PasskeyWelcome = () => { const { t } = useTranslation('translation', { keyPrefix: 'authenticationFlows.signup.passkeySuccess' }); diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json index 18e32fd2..28fa1c02 100644 --- a/packages/shared-ui/package.json +++ b/packages/shared-ui/package.json @@ -30,12 +30,10 @@ "bugs": { "url": "https://github.com/corbado/javascript/issues" }, - "dependencies": { - "i18next": "23.5.1", - "@corbado/web-core": "*" - }, "devDependencies": { + "i18next": "23.5.1", "@corbado/types": "*", + "@corbado/web-core": "*", "@svgr/webpack": "^8.1.0", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", diff --git a/packages/shared-ui/src/flowHandler/flowHandler.ts b/packages/shared-ui/src/flowHandler/flowHandler.ts index 6a045204..d1063f50 100644 --- a/packages/shared-ui/src/flowHandler/flowHandler.ts +++ b/packages/shared-ui/src/flowHandler/flowHandler.ts @@ -1,12 +1,12 @@ import type { ProjectConfig, SessionUser } from '@corbado/types'; -import type { CorbadoApp, RecoverableError } from '@corbado/web-core'; +import type { CorbadoApp } from '@corbado/web-core'; import type { i18n } from 'i18next'; import { canUsePasskeys } from '../utils'; import type { FlowHandlerEvents } from './constants'; import { CommonScreens, FlowType, LoginFlowNames, SignUpFlowNames } from './constants'; -import { flows } from './flows'; import { FlowHandlerState } from './flowHandlerState'; +import { flows } from './flows'; import type { Flow, FlowHandlerConfig, @@ -155,29 +155,14 @@ export class FlowHandler { this.#onUserStateChangeCallbacks.splice(cbId, 1); } - /** - * @deprecated - * Method to navigate to the next screen. - * It calls the step function of the current screen with the project configuration, the flow handler configuration, and the user input. - * If the next screen is the End screen, it redirects to a specified URL. - * It adds the current screen to the screen history, sets the current screen to the next screen, and calls any registered onScreenUpdate callbacks with the new current screen. - * - * @param event The event that triggered the navigation. - * @param eventOptions The event options. - * @returns The new current screen. - */ - navigateNext(event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) { - return this.handleStateUpdate(event, eventOptions); - } - - async handleStateUpdate(event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions, _?: RecoverableError) { + async handleStateUpdate(event?: FlowHandlerEvents, eventOptions?: FlowHandlerEventOptions) { const stateUpdater = this.#currentFlow[this.#currentScreen]; if (!stateUpdater) { throw new Error('Invalid screen'); } const flowUpdate = await stateUpdater(this.#state, event, eventOptions); - if (flowUpdate !== undefined && flowUpdate?.nextFlow !== null) { + if (flowUpdate && flowUpdate?.nextFlow !== null) { this.changeFlow(flowUpdate.nextFlow); } @@ -251,11 +236,11 @@ export class FlowHandler { return this.#currentScreen; } - #changeState = (update: FlowHandlerStateUpdate) => { + #changeState(update: FlowHandlerStateUpdate) { this.#state.update(update); this.#onUserStateChangeCallbacks.forEach(cb => cb(this.#state.userState)); - }; + } updateUser(user: SessionUser) { this.#changeState({ user: user }); diff --git a/packages/shared-ui/src/flowHandler/flowHandlerState.ts b/packages/shared-ui/src/flowHandler/flowHandlerState.ts index 3b893a4b..95adc18c 100644 --- a/packages/shared-ui/src/flowHandler/flowHandlerState.ts +++ b/packages/shared-ui/src/flowHandler/flowHandlerState.ts @@ -14,6 +14,27 @@ const defaultErrors = { * Internal state of the FlowHandler. */ export class FlowHandlerState { + #flowOptions: FlowOptions; + #userState: UserState; + #passkeysSupported: boolean; + #user?: SessionUser; + #corbadoApp: CorbadoApp; + #i18next: i18n; + + constructor( + flowOptions: FlowOptions, + userState: UserState, + passkeysSupported: boolean, + corbadoApp: CorbadoApp, + i18next: i18n, + ) { + this.#flowOptions = flowOptions; + this.#userState = userState; + this.#passkeysSupported = passkeysSupported; + this.#corbadoApp = corbadoApp; + this.#i18next = i18next; + } + get user(): SessionUser | undefined { return this.#user; } @@ -32,21 +53,6 @@ export class FlowHandlerState { return this.#corbadoApp; } - #flowOptions: FlowOptions; - #userState: UserState; - #passkeysSupported: boolean; - #user?: SessionUser; - #corbadoApp: CorbadoApp; - #i18next: i18n; - - constructor(flowOptions: FlowOptions, userState: UserState, passkeysSupported: boolean, corbadoApp: CorbadoApp, i18next: i18n) { - this.#flowOptions = flowOptions; - this.#userState = userState; - this.#passkeysSupported = passkeysSupported; - this.#corbadoApp = corbadoApp; - this.#i18next = i18next; - } - /** * Allows to update the internal state of the FlowHandler. * @param update diff --git a/packages/shared-ui/src/flowHandler/flows/passkeyLoginWithOtpFlow.ts b/packages/shared-ui/src/flowHandler/flows/passkeyLoginWithOtpFlow.ts index 446ef632..fd8804af 100644 --- a/packages/shared-ui/src/flowHandler/flows/passkeyLoginWithOtpFlow.ts +++ b/packages/shared-ui/src/flowHandler/flows/passkeyLoginWithOtpFlow.ts @@ -2,7 +2,8 @@ import type { AuthService } from '@corbado/web-core'; import { InvalidEmailError, InvalidOtpInputError, - NoPasskeyAvailableError, PasskeyChallengeCancelledError, + NoPasskeyAvailableError, + PasskeyChallengeCancelledError, UnknownError, UnknownUserError, } from '@corbado/web-core'; @@ -13,7 +14,7 @@ import { PasskeyLoginWithEmailOtpFallbackScreens, PasskeySignupWithEmailOtpFallbackScreens, } from '../constants'; -import type {FlowHandlerState} from "../flowHandlerState"; +import type { FlowHandlerState } from '../flowHandlerState'; import { FlowUpdate } from '../flowUpdate'; import type { Flow, UserState } from '../types'; @@ -156,17 +157,17 @@ export const PasskeyLoginWithEmailOTPFallbackFlow: Flow = { } return FlowUpdate.state({}); }, - [PasskeyLoginWithEmailOtpFallbackScreens.PasskeyAppend]: async (_, event) => { + [PasskeyLoginWithEmailOtpFallbackScreens.PasskeyAppend]: async (_, event): Promise => { switch (event) { case FlowHandlerEvents.PrimaryButton: - return FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.PasskeyBenefits); + return Promise.resolve(FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.PasskeyBenefits)); case FlowHandlerEvents.SecondaryButton: - return FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.End); + return Promise.resolve(FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.End)); case FlowHandlerEvents.ShowBenefits: - return FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.PasskeyBenefits); + return Promise.resolve(FlowUpdate.navigate(PasskeyLoginWithEmailOtpFallbackScreens.PasskeyBenefits)); } - return undefined; + return Promise.resolve(undefined); }, [PasskeyLoginWithEmailOtpFallbackScreens.PasskeyError]: async (state, event) => { diff --git a/packages/shared-ui/src/flowHandler/flows/passkeySignupWithOtpFlow.ts b/packages/shared-ui/src/flowHandler/flows/passkeySignupWithOtpFlow.ts index 2573d318..77eac6ba 100644 --- a/packages/shared-ui/src/flowHandler/flows/passkeySignupWithOtpFlow.ts +++ b/packages/shared-ui/src/flowHandler/flows/passkeySignupWithOtpFlow.ts @@ -212,13 +212,13 @@ export const PasskeySignupWithEmailOTPFallbackFlow: Flow = { return; }, - [PasskeySignupWithEmailOtpFallbackScreens.PasskeyWelcome]: async (_, event) => { + [PasskeySignupWithEmailOtpFallbackScreens.PasskeyWelcome]: (_, event): Promise => { switch (event) { case FlowHandlerEvents.PrimaryButton: - return FlowUpdate.navigate(PasskeySignupWithEmailOtpFallbackScreens.End); + return Promise.resolve(FlowUpdate.navigate(PasskeySignupWithEmailOtpFallbackScreens.End)); } - return; + return Promise.resolve(undefined); }, [PasskeySignupWithEmailOtpFallbackScreens.PasskeyError]: async (state, event) => { diff --git a/packages/shared-ui/src/i18n/de.json b/packages/shared-ui/src/i18n/de.json index adf39b3d..b8fafd95 100644 --- a/packages/shared-ui/src/i18n/de.json +++ b/packages/shared-ui/src/i18n/de.json @@ -100,6 +100,13 @@ "button_start": "Passkey erstellen", "button_skip": "Vielleicht später" } + }, + "unexpectedError": { + "header": "Hier ist etwas schief gelaufen", + "body_introduction": "Dieser Service ist aktuell leider nicht verfügbar", + "body_explanationCustomerSupport": "Bitte versuchen Sie es später noch einmal. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an {{customerSupportEmail}}.", + "body_explanationNoCustomerSupport": "Bitte versuchen Sie es später noch einmal.", + "cta": "Seite neu laden" } }, "errors": { diff --git a/packages/shared-ui/src/i18n/en.json b/packages/shared-ui/src/i18n/en.json index 8970ba89..57667293 100644 --- a/packages/shared-ui/src/i18n/en.json +++ b/packages/shared-ui/src/i18n/en.json @@ -100,6 +100,13 @@ "button_start": "Create passkey", "button_skip": "Maybe later" } + }, + "unexpectedError": { + "header": "Something went wrong", + "body_introduction": "We’re sorry that our service is currently not available.", + "body_explanationCustomerSupport": "Please try again in a few moments and if the issue persists, please contact {{customerSupportEmail}}.", + "body_explanationNoCustomerSupport": "Please try again in a few moments.", + "cta": "Refresh Page" } }, "errors": { diff --git a/packages/shared-ui/src/styles/error_page.css b/packages/shared-ui/src/styles/error_page.css index 3ddb7d5c..02928c66 100644 --- a/packages/shared-ui/src/styles/error_page.css +++ b/packages/shared-ui/src/styles/error_page.css @@ -21,7 +21,7 @@ display: flex; flex-direction: column; justify-content: space-between; - gap: .5rem; + gap: 0.5rem; } .error-detail-row { diff --git a/packages/types/README.md b/packages/types/README.md index 8fe93c7d..d9f11ca3 100644 --- a/packages/types/README.md +++ b/packages/types/README.md @@ -1,6 +1,7 @@ # Corbado Shared Types -This package provides shared TypeScript type definitions for Corbado. It's designed to give developers the power of strong typing and enhance the development experience with autocompletion and IntelliSense support in various IDEs. +This package provides shared TypeScript type definitions for Corbado. It's designed to give developers the power of +strong typing and enhance the development experience with autocompletion and IntelliSense support in various IDEs. ## Installation diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index d73257fc..2dbd74af 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -9,6 +9,10 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true }, - "include": ["src"], - "exclude": ["node_modules"] + "include": [ + "src" + ], + "exclude": [ + "node_modules" + ] } diff --git a/packages/web-core/src/api/base.ts b/packages/web-core/src/api/base.ts index 7b196134..1bfca55e 100644 --- a/packages/web-core/src/api/base.ts +++ b/packages/web-core/src/api/base.ts @@ -12,24 +12,23 @@ * Do not edit the class manually. */ - -import type {Configuration} from './configuration'; +import type { Configuration } from './configuration'; // Some imports not used depending on template conditions // @ts-ignore -import type {AxiosPromise, AxiosInstance, AxiosRequestConfig} from 'axios'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; import globalAxios from 'axios'; -export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); +export const BASE_PATH = 'http://localhost'.replace(/\/+$/, ''); /** * * @export */ export const COLLECTION_FORMATS = { - csv: ",", - ssv: " ", - tsv: "\t", - pipes: "|", + csv: ',', + ssv: ' ', + tsv: '\t', + pipes: '|', }; /** @@ -50,13 +49,17 @@ export interface RequestArgs { export class BaseAPI { protected configuration: Configuration | undefined; - constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + constructor( + configuration?: Configuration, + protected basePath: string = BASE_PATH, + protected axios: AxiosInstance = globalAxios, + ) { if (configuration) { this.configuration = configuration; this.basePath = configuration.basePath || this.basePath; } } -}; +} /** * @@ -65,8 +68,11 @@ export class BaseAPI { * @extends {Error} */ export class RequiredError extends Error { - constructor(public field: string, msg?: string) { + constructor( + public field: string, + msg?: string, + ) { super(msg); - this.name = "RequiredError" + this.name = 'RequiredError'; } } diff --git a/packages/web-core/src/api/common.ts b/packages/web-core/src/api/common.ts index 91bf4de5..5360e97e 100644 --- a/packages/web-core/src/api/common.ts +++ b/packages/web-core/src/api/common.ts @@ -12,17 +12,16 @@ * Do not edit the class manually. */ - -import type {Configuration} from "./configuration"; -import type {RequestArgs} from "./base"; -import type {AxiosInstance, AxiosResponse} from 'axios'; -import {RequiredError} from "./base"; +import type { Configuration } from './configuration'; +import type { RequestArgs } from './base'; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from './base'; /** * * @export */ -export const DUMMY_BASE_URL = 'https://example.com' +export const DUMMY_BASE_URL = 'https://example.com'; /** * @@ -31,9 +30,12 @@ export const DUMMY_BASE_URL = 'https://example.com' */ export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { if (paramValue === null || paramValue === undefined) { - throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + throw new RequiredError( + paramName, + `Required parameter ${paramName} was null or undefined when calling ${functionName}.`, + ); } -} +}; /** * @@ -41,12 +43,13 @@ export const assertParamExists = function (functionName: string, paramName: stri */ export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? await configuration.apiKey(keyParamName) - : await configuration.apiKey; + const localVarApiKeyValue = + typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; object[keyParamName] = localVarApiKeyValue; } -} +}; /** * @@ -54,9 +57,9 @@ export const setApiKeyToObject = async function (object: any, keyParamName: stri */ export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { if (configuration && (configuration.username || configuration.password)) { - object["auth"] = {username: configuration.username, password: configuration.password}; + object['auth'] = { username: configuration.username, password: configuration.password }; } -} +}; /** * @@ -64,34 +67,41 @@ export const setBasicAuthToObject = function (object: any, configuration?: Confi */ export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { if (configuration && configuration.accessToken) { - const accessToken = typeof configuration.accessToken === 'function' - ? await configuration.accessToken() - : await configuration.accessToken; - object["Authorization"] = "Bearer " + accessToken; + const accessToken = + typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object['Authorization'] = 'Bearer ' + accessToken; } -} +}; /** * * @export */ -export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { +export const setOAuthToObject = async function ( + object: any, + name: string, + scopes: string[], + configuration?: Configuration, +) { if (configuration && configuration.accessToken) { - const localVarAccessTokenValue = typeof configuration.accessToken === 'function' - ? await configuration.accessToken(name, scopes) - : await configuration.accessToken; - object["Authorization"] = "Bearer " + localVarAccessTokenValue; + const localVarAccessTokenValue = + typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object['Authorization'] = 'Bearer ' + localVarAccessTokenValue; } -} +}; -function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ''): void { if (parameter == null) return; - if (typeof parameter === "object") { + if (typeof parameter === 'object') { if (Array.isArray(parameter)) { (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); } else { Object.keys(parameter).forEach(currentKey => - setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`), ); } } else { @@ -111,7 +121,7 @@ export const setSearchParams = function (url: URL, ...objects: any[]) { const searchParams = new URLSearchParams(url.search); setFlattenedQueryParams(searchParams, objects); url.search = searchParams.toString(); -} +}; /** * @@ -119,32 +129,36 @@ export const setSearchParams = function (url: URL, ...objects: any[]) { */ export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { const nonString = typeof value !== 'string'; - const needsSerialization = nonString && configuration && configuration.isJsonMime - ? configuration.isJsonMime(requestOptions.headers['Content-Type']) - : nonString; - return needsSerialization - ? JSON.stringify(value !== undefined ? value : {}) - : (value || ""); -} + const needsSerialization = + nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization ? JSON.stringify(value !== undefined ? value : {}) : value || ''; +}; /** * * @export */ export const toPathString = function (url: URL) { - return url.pathname + url.search + url.hash -} + return url.pathname + url.search + url.hash; +}; /** * * @export */ -export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { +export const createRequestFunction = function ( + axiosArgs: RequestArgs, + globalAxios: AxiosInstance, + BASE_PATH: string, + configuration?: Configuration, +) { return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs = { ...axiosArgs.options, - url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url + url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url, }; return axios.request(axiosRequestArgs); }; -} +}; diff --git a/packages/web-core/src/api/configuration.ts b/packages/web-core/src/api/configuration.ts index 00f0c9b3..f860344c 100644 --- a/packages/web-core/src/api/configuration.ts +++ b/packages/web-core/src/api/configuration.ts @@ -13,12 +13,15 @@ * Do not edit the class manually. */ - export interface ConfigurationParameters { apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); username?: string; password?: string; - accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string) + | ((name?: string, scopes?: string[]) => Promise); basePath?: string; baseOptions?: any; formDataCtor?: new () => any; @@ -51,7 +54,11 @@ export class Configuration { * @param scopes oauth2 scope * @memberof Configuration */ - accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string) + | ((name?: string, scopes?: string[]) => Promise); /** * override base path * @@ -96,7 +103,7 @@ export class Configuration { * @return True if the given MIME is JSON, false otherwise. */ public isJsonMime(mime: string): boolean { - const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + const jsonMime: RegExp = new RegExp('^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); } } diff --git a/packages/web-core/src/api/index.ts b/packages/web-core/src/api/index.ts index 14c82621..65bdda20 100644 --- a/packages/web-core/src/api/index.ts +++ b/packages/web-core/src/api/index.ts @@ -12,7 +12,5 @@ * Do not edit the class manually. */ - -export * from "./api"; -export * from "./configuration"; - +export * from './api'; +export * from './configuration'; diff --git a/packages/web-core/src/services/ApiService.ts b/packages/web-core/src/services/ApiService.ts index 17d8db85..6ef52c0d 100644 --- a/packages/web-core/src/services/ApiService.ts +++ b/packages/web-core/src/services/ApiService.ts @@ -1,6 +1,7 @@ import type { ProjectConfig, UserAuthMethods } from '@corbado/types'; import type { AxiosError, AxiosInstance } from 'axios'; import axios from 'axios'; +import log from 'loglevel'; import type { Subject } from 'rxjs'; import { Err, Result } from 'ts-results'; @@ -19,7 +20,6 @@ import type { SignUpWithPasskeyError, } from '../utils'; import { CorbadoError, NonRecoverableError } from '../utils'; -import log from "loglevel"; // TODO: does this work also without npm start? (e.g. vite js) const packageVersion = '0'; @@ -251,12 +251,7 @@ export class ApiService { Result > { if (emailCodeId === '') { - return Err( - CorbadoError.illegalState( - 'email OTP challenge has not been started', - '', - ), - ); + return Err(CorbadoError.illegalState('email OTP challenge has not been started', '')); } return Result.wrapAsync(async () => { diff --git a/packages/web-core/src/services/AuthService.ts b/packages/web-core/src/services/AuthService.ts index 510d537e..61805261 100644 --- a/packages/web-core/src/services/AuthService.ts +++ b/packages/web-core/src/services/AuthService.ts @@ -1,11 +1,11 @@ -import type {SessionUser} from '@corbado/types'; +import type { SessionUser } from '@corbado/types'; import log from 'loglevel'; -import {Subject} from 'rxjs'; -import type {Result} from 'ts-results'; -import {Ok} from 'ts-results'; +import { Subject } from 'rxjs'; +import type { Result } from 'ts-results'; +import { Ok } from 'ts-results'; -import type {AuthenticationResponse} from '../models/auth'; -import type {ShortSession} from '../models/session'; +import type { AuthenticationResponse } from '../models/auth'; +import type { ShortSession } from '../models/session'; import type { AppendPasskeyError, CompleteLoginWithEmailOTPError, @@ -16,10 +16,10 @@ import type { RecoverableError, SignUpWithPasskeyError, } from '../utils'; -import {AuthState} from '../utils'; -import type {ApiService} from './ApiService'; -import type {SessionService} from './SessionService'; -import type {WebAuthnService} from './WebAuthnService'; +import { AuthState } from '../utils'; +import type { ApiService } from './ApiService'; +import type { SessionService } from './SessionService'; +import type { WebAuthnService } from './WebAuthnService'; /** * AuthService is a class that handles authentication related operations. diff --git a/packages/web-core/src/services/ProjectService.ts b/packages/web-core/src/services/ProjectService.ts index f3e057ce..d68663f9 100644 --- a/packages/web-core/src/services/ProjectService.ts +++ b/packages/web-core/src/services/ProjectService.ts @@ -1,8 +1,9 @@ import type { ProjectConfig } from '@corbado/types'; +import type { Result } from 'ts-results'; +import { Ok } from 'ts-results'; +import type { GetProjectConfigError } from '../utils'; import type { ApiService } from './ApiService'; -import {GetProjectConfigError} from "../utils"; -import {Ok, Result} from "ts-results"; /** * ProjectService is responsible for managing the project configuration. diff --git a/packages/web-core/src/services/WebAuthnService.ts b/packages/web-core/src/services/WebAuthnService.ts index 8d12adfb..1e0cf0c7 100644 --- a/packages/web-core/src/services/WebAuthnService.ts +++ b/packages/web-core/src/services/WebAuthnService.ts @@ -4,8 +4,7 @@ import type { Subject } from 'rxjs'; import type { Result } from 'ts-results'; import { Err, Ok } from 'ts-results'; -import {CorbadoError, NonRecoverableError} from '../utils'; - +import { CorbadoError, NonRecoverableError } from '../utils'; /** * AuthenticatorService handles all interactions with webAuthn platform authenticators. diff --git a/packages/web-core/src/services/index.ts b/packages/web-core/src/services/index.ts index 698b9a27..a973e105 100644 --- a/packages/web-core/src/services/index.ts +++ b/packages/web-core/src/services/index.ts @@ -1,6 +1,6 @@ import { Subject } from 'rxjs'; -import {defaultTimeout, NonRecoverableError} from '../utils'; +import { defaultTimeout, NonRecoverableError } from '../utils'; import { ApiService } from './ApiService'; import { AuthService } from './AuthService'; import { ProjectService } from './ProjectService'; diff --git a/packages/web-core/src/utils/errors/errors.ts b/packages/web-core/src/utils/errors/errors.ts index 7b7590e0..366daf3a 100644 --- a/packages/web-core/src/utils/errors/errors.ts +++ b/packages/web-core/src/utils/errors/errors.ts @@ -201,6 +201,7 @@ export class NonRecoverableError extends CorbadoError { } static invalidConfig(message: string) { + // TODO: add link to docs return NonRecoverableError.client(message, 'https://docs.corbado.com'); } diff --git a/playground/react-example/src/pages/AuthPage.tsx b/playground/react-example/src/pages/AuthPage.tsx index 6050f805..7df8d9c1 100644 --- a/playground/react-example/src/pages/AuthPage.tsx +++ b/playground/react-example/src/pages/AuthPage.tsx @@ -18,7 +18,7 @@ const AuthPage = () => { fr: frenchTranslations, en: englishTranslations, }} - isDevMode={isDevMode} + isDevMode={false} darkMode='off' customerSupportEmail='dev@test.com' />