From 4339b7c1c9ead1cd137fc8995e71626c5e1ad7f5 Mon Sep 17 00:00:00 2001 From: Misa Munde Date: Thu, 26 Jan 2023 13:28:52 +0200 Subject: [PATCH] fix: error handling for preact-spa 422 code (#74) * fix: package.json build:preact script * fix: Error handling for 422 code (#53) * fix: Implement error handling * refactor: Replicate function flow from react * style: format * fix: format * feat: api call generic error handling * feat: add basic error page * fix: change default signupURL to /registration * fix: change /signup to default kratos /registration Co-authored-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Co-authored-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> --- examples/preact-spa/src/app.tsx | 53 ++++++--- examples/preact-spa/src/error.tsx | 43 +++++++ examples/preact-spa/src/login.tsx | 88 +++++++------- examples/preact-spa/src/main.tsx | 4 +- examples/preact-spa/src/recovery.tsx | 70 +++++++---- examples/preact-spa/src/register.tsx | 71 +++++++---- examples/preact-spa/src/sdk.ts | 143 ++++++++++++++++++++++- examples/preact-spa/src/settings.tsx | 73 ++++++++---- examples/preact-spa/src/verification.tsx | 56 ++++++++- package.json | 2 +- 10 files changed, 474 insertions(+), 129 deletions(-) create mode 100644 examples/preact-spa/src/error.tsx diff --git a/examples/preact-spa/src/app.tsx b/examples/preact-spa/src/app.tsx index 93931490d..4f97e8e79 100644 --- a/examples/preact-spa/src/app.tsx +++ b/examples/preact-spa/src/app.tsx @@ -1,6 +1,6 @@ import "./app.css" import { Typography } from "@ory/elements-preact" -import sdk from "./sdk" +import { sdk, sdkError } from "./sdk" import { useEffect, useState } from "preact/hooks" import { Session } from "@ory/client" import { useLocation } from "wouter" @@ -10,23 +10,9 @@ export const Dashboard = () => { const [logoutUrl, setLogoutUrl] = useState() const [location, setLocation] = useLocation() - useEffect(() => { - sdk - .toSession() - .then(({ data: session }) => { - setSession(session) - }) - .catch((error) => { - if (error.response?.status === 403) { - if (error.response?.data.error.id === "session_aal2_required") { - return setLocation("/login?aal2=true", { replace: true }) - } - } - return setLocation("/login", { replace: true }) - }) - }, []) + const sdkErrorHandler = sdkError(undefined, undefined, "/login") - useEffect(() => { + const createLogoutFlow = () => { sdk .createBrowserLogoutFlow(undefined, { params: { @@ -36,8 +22,37 @@ export const Dashboard = () => { .then(({ data }) => { setLogoutUrl(data.logout_url) }) - .catch((data) => { - console.error(data) + .catch(sdkErrorHandler) + } + + useEffect(() => { + sdk + .toSession() + .then(({ data: session }) => { + // we set the session data which contains the user Identifier and other traits. + setSession(session) + // Set logout flow + createLogoutFlow() + }) + .catch(sdkErrorHandler) + .catch((error) => { + // Handle all other errors like error.message "network error" if Kratos can not be connected etc. + if (error.message) { + return setLocation( + `/error?error=${encodeURIComponent(error.message)}`, + { + replace: true, + }, + ) + } + + // Just stringify error and print all data + setLocation( + `/error?error=${encodeURIComponent(JSON.stringify(error))}`, + { + replace: true, + }, + ) }) }, []) diff --git a/examples/preact-spa/src/error.tsx b/examples/preact-spa/src/error.tsx new file mode 100644 index 000000000..51f06e2ed --- /dev/null +++ b/examples/preact-spa/src/error.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "preact/hooks" +import { CodeBox, ButtonLink } from "@ory/elements-preact" +import { getSearchParam } from "./sdk" + +export const Error = () => { + const [error, setError] = useState() + + useEffect(() => { + const queryError = getSearchParam("error") + + if (queryError !== null) { + try { + setError( + JSON.stringify(JSON.parse(decodeURIComponent(queryError)), null, 2), + ) + } catch (error) { + setError(queryError) + } + } else { + setError("Undefined error") + } + }, []) + + // we check if the flow is set, if not we show a loading indicator + return ( + <> + Home + +

+ An error occurred. Please check the error information below and try + again. +

+ + {error} + + + ) +} diff --git a/examples/preact-spa/src/login.tsx b/examples/preact-spa/src/login.tsx index aec259a83..7d328c632 100644 --- a/examples/preact-spa/src/login.tsx +++ b/examples/preact-spa/src/login.tsx @@ -1,7 +1,7 @@ import { LoginFlow, UpdateLoginFlowBody } from "@ory/client" import { UserAuthCard } from "@ory/elements-preact" import { useCallback, useEffect, useState } from "preact/hooks" -import sdk from "./sdk" +import { sdk, sdkError, getSearchParam } from "./sdk" import { useLocation } from "wouter" export const Login = () => { @@ -9,33 +9,57 @@ export const Login = () => { const [location, setLocation] = useLocation() - const handleFlow = useCallback( - ({ refresh, mfa }: { refresh: boolean; mfa: boolean }) => { - return sdk - .createBrowserLoginFlow({ refresh, aal: mfa ? "aal2" : "aal1" }) - .then(({ data: flow }) => flow) - }, + // Get the flow based on the flowId in the URL (.e.g redirect to this page after flow initialized) + const getFlow = useCallback( + (flowId: string) => + sdk + .getLoginFlow({ id: flowId }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler), [], ) - useEffect(() => { - const aal2 = new URLSearchParams( - new URL(window.location.toString()).search, - ).get("aal2") + // initialize the sdkError for generic handling of errors + const sdkErrorHandler = sdkError(getFlow, setFlow, "/login", true) + + const createFlow = () => { + const aal2 = getSearchParam("aal2") + + sdk + .createBrowserLoginFlow({ refresh: true, aal: aal2 ? "aal2" : "aal1" }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler) + } - const isMFA = aal2 ? true : false - handleFlow({ refresh: true, mfa: isMFA }) - .then((flow) => setFlow(flow)) - .catch((error) => { - switch (error.response?.status) { - case 400: - setFlow(error.response.data) - break - case 410: - case 404: - return setLocation("/login", { replace: true }) - } + // submit the login form data to Ory + const submitFlow = (body: UpdateLoginFlowBody) => { + // something unexpected went wrong and the flow was not set + if (!flow) return setLocation("/login", { replace: true }) + + sdk + .updateLoginFlow({ + flow: flow.id, + updateLoginFlowBody: body as UpdateLoginFlowBody, + }) + .then(() => { + // we successfully submitted the login flow, so lets redirect to the dashboard + setLocation("/", { replace: true }) }) + .catch(sdkErrorHandler) + } + + useEffect(() => { + // we might redirect to this page after the flow is initialized, so we check for the flowId in the URL + const flowId = getSearchParam("flow") + + // the flow already exists + if (flowId) { + getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one + return + } + + // we assume there was no flow, so we create a new one + createFlow() }, []) return flow ? ( @@ -44,25 +68,11 @@ export const Login = () => { flowType={"login"} additionalProps={{ forgotPasswordURL: "/recovery", - signupURL: "/signup", + signupURL: "/registration", }} title={"Login"} includeScripts={true} - onSubmit={({ body }) => { - sdk - .updateLoginFlow({ - flow: flow.id, - updateLoginFlowBody: body as UpdateLoginFlowBody, - }) - .then(() => { - // we successfully submitted the login flow, so lets redirect to the dashboard - setLocation("/", { replace: true }) - }) - .catch((error) => { - console.error({ error }) - setFlow(error.response.data) - }) - }} + onSubmit={({ body }) => submitFlow(body as UpdateLoginFlowBody)} /> ) : (
Loading...
diff --git a/examples/preact-spa/src/main.tsx b/examples/preact-spa/src/main.tsx index 6d00bc5c7..88293ad3b 100644 --- a/examples/preact-spa/src/main.tsx +++ b/examples/preact-spa/src/main.tsx @@ -8,6 +8,7 @@ import { Recovery } from "./recovery" import { Register } from "./register" import { Settings } from "./settings" import { Verification } from "./verification" +import { Error } from "./error" // import Ory elements css import "@ory/elements-preact/style.css" @@ -18,10 +19,11 @@ const Main = () => { - + + ) diff --git a/examples/preact-spa/src/recovery.tsx b/examples/preact-spa/src/recovery.tsx index 279254fb9..354071796 100644 --- a/examples/preact-spa/src/recovery.tsx +++ b/examples/preact-spa/src/recovery.tsx @@ -1,23 +1,64 @@ import { RecoveryFlow, UpdateRecoveryFlowBody } from "@ory/client" import { UserAuthCard } from "@ory/elements-preact" -import { useEffect, useState } from "preact/hooks" +import { useCallback, useEffect, useState } from "preact/hooks" import { useLocation } from "wouter" -import sdk from "./sdk" +import { getSearchParam, sdk, sdkError } from "./sdk" export const Recovery = () => { const [flow, setFlow] = useState(null) const [location, setLocation] = useLocation() - useEffect(() => { + const getFlow = useCallback( + (flowId: string) => + sdk + .getRecoveryFlow({ id: flowId }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler), + [], + ) + + // initialize the sdkError for generic handling of errors + const sdkErrorHandler = sdkError(getFlow, setFlow, "/recovery") + + // create a new recovery flow + const createFlow = () => { sdk .createBrowserRecoveryFlow() - .then(({ data: flow }) => { - setFlow(flow) + // flow contains the form fields, error messages and csrf token + .then(({ data: flow }) => setFlow(flow)) + // something serious went wrong, so we redirect to the recovery page + .catch(sdkErrorHandler) + } + + const submitFlow = (body: UpdateRecoveryFlowBody) => { + // something unexpected went wrong and the flow was not set + if (!flow) return setLocation("/login", { replace: true }) + + sdk + .updateRecoveryFlow({ + flow: flow.id, + updateRecoveryFlowBody: body as UpdateRecoveryFlowBody, }) - .catch((error) => { - console.error(error) + .then(() => { + // we successfully submitted the login flow, so lets redirect to the dashboard + setLocation("/", { replace: true }) }) + .catch(sdkErrorHandler) + } + + useEffect(() => { + // we might redirect to this page after the flow is initialized, so we check for the flowId in the URL + const flowId = getSearchParam("flow") + + // the flow already exists + if (flowId) { + getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one + return + } + + // we assume there was no flow, so we create a new one + createFlow() }, []) return flow ? ( @@ -26,20 +67,7 @@ export const Recovery = () => { flow={flow} flowType={"recovery"} additionalProps={{ loginURL: "/login" }} - onSubmit={({ body }) => { - sdk - .updateRecoveryFlow({ - flow: flow.id, - updateRecoveryFlowBody: body as UpdateRecoveryFlowBody, - }) - .then(() => { - // we successfully submitted the login flow, so lets redirect to the dashboard - setLocation("/", { replace: true }) - }) - .catch((error) => { - setFlow(error.response.data) - }) - }} + onSubmit={({ body }) => submitFlow(body as UpdateRecoveryFlowBody)} /> ) : (
Loading...
diff --git a/examples/preact-spa/src/register.tsx b/examples/preact-spa/src/register.tsx index cdbe39a94..0eae20c42 100644 --- a/examples/preact-spa/src/register.tsx +++ b/examples/preact-spa/src/register.tsx @@ -1,7 +1,7 @@ import { RegistrationFlow, UpdateRegistrationFlowBody } from "@ory/client" import { UserAuthCard } from "@ory/elements-preact" -import { useEffect, useState } from "preact/hooks" -import sdk from "./sdk" +import { useCallback, useEffect, useState } from "preact/hooks" +import { getSearchParam, sdk, sdkError } from "./sdk" import { useLocation } from "wouter" export const Register = () => { @@ -9,13 +9,57 @@ export const Register = () => { const [location, setLocation] = useLocation() - useEffect(() => { + // Get the flow based on the flowId in the URL (.e.g redirect to this page after flow initialized) + const getFlow = useCallback( + (flowId: string) => + sdk + // the flow data contains the form fields, error messages and csrf token + .getRegistrationFlow({ id: flowId }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler), + [], + ) + + // initialize the sdkError for generic handling of errors + const sdkErrorHandler = sdkError(getFlow, setFlow, "/registration", true) + + // create a new registration flow + const createFlow = () => { sdk + // we don't need to specify the return_to here since we are building an SPA. In server-side browser flows we would need to specify the return_to .createBrowserRegistrationFlow() - .then(({ data: flow }) => { - setFlow(flow) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler) + } + + // submit the registration form data to Ory + const submitFlow = (body: UpdateRegistrationFlowBody) => { + // something unexpected went wrong and the flow was not set + if (!flow) return setLocation("/registration", { replace: true }) + + sdk + .updateRegistrationFlow({ + flow: flow.id, + updateRegistrationFlowBody: body, + }) + .then(() => { + // we successfully submitted the login flow, so lets redirect to the dashboard + setLocation("/", { replace: true }) }) - .catch((error) => console.error(error)) + .catch(sdkErrorHandler) + } + + useEffect(() => { + // we might redirect to this page after the flow is initialized, so we check for the flowId in the URL + const flowId = getSearchParam("flow") + + // the flow already exists + if (flowId) { + getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one + return + } + // we assume there was no flow, so we create a new one + createFlow() }, []) return flow ? ( @@ -27,20 +71,7 @@ export const Register = () => { loginURL: "/login", }} includeScripts={true} - onSubmit={({ body }) => { - sdk - .updateRegistrationFlow({ - flow: flow.id, - updateRegistrationFlowBody: body as UpdateRegistrationFlowBody, - }) - .then(() => { - // we successfully submitted the login flow, so lets redirect to the dashboard - setLocation("/", { replace: true }) - }) - .catch((error) => { - setFlow(error.response.data) - }) - }} + onSubmit={({ body }) => submitFlow(body as UpdateRegistrationFlowBody)} /> ) : (
Loading...
diff --git a/examples/preact-spa/src/sdk.ts b/examples/preact-spa/src/sdk.ts index 73db748e7..6e6a9fe3b 100644 --- a/examples/preact-spa/src/sdk.ts +++ b/examples/preact-spa/src/sdk.ts @@ -2,8 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { FrontendApi, Configuration } from "@ory/client" +import { AxiosError } from "axios" +import { useCallback } from "preact/hooks" +import { StateUpdater } from "preact/compat" +import { useLocation } from "wouter" -export default new FrontendApi( +export const sdk = new FrontendApi( new Configuration({ //https://vitejs.dev/guide/env-and-mode.html#env-files basePath: import.meta.env.VITE_ORY_SDK_URL, @@ -12,3 +16,140 @@ export default new FrontendApi( }, }), ) + +/** + * @param getFlow - Should be function to load a flow make it visible (Login.getFlow) + * @param setFlow - Update flow data to view (Login.setFlow) + * @param defaultNav - Default navigate target for errors + * @param fatalToDash - When true and error can not be handled, then redirect to dashboard, else rethrow error + */ +export const sdkError = ( + getFlow: ((flowId: string) => Promise) | undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setFlow: StateUpdater | undefined, + defaultNav: string | undefined, + fatalToDash = false, +) => { + const [location, setLocation] = useLocation() + + return useCallback( + (error: AxiosError): Promise => { + const responseData = error.response?.data || {} + + switch (error.response?.status) { + case 400: { + // the request could contain invalid parameters which would set error messages in the flow + if (setFlow !== undefined) { + console.warn("sdkError 400: update flow data") + setFlow(responseData) + return Promise.resolve() + } + break + } + case 401: { + console.warn("sdkError 401: Navigate to /login") + setLocation("/login", { replace: true }) + return Promise.resolve() + } + case 403: { + // the user might have a session, but would require 2FA (Two-Factor Authentication) + if (responseData.error?.id === "session_aal2_required") { + setLocation("/login?aal2=true", { replace: true }) + return Promise.resolve() + } + + if ( + responseData.error?.id === "session_refresh_required" && + responseData.redirect_browser_to + ) { + console.warn("sdkError 403: Redirect browser to") + window.location = responseData.redirect_browser_to + return Promise.resolve() + } + break + } + case 404: { + if (defaultNav !== undefined) { + console.warn("sdkError 404: Navigate to Error") + const errorMsg = error.response?.data || error + errorMsg.url = window.location.href + setLocation( + `/error?error=${encodeURIComponent(JSON.stringify(errorMsg))}`, + { + replace: true, + }, + ) + return Promise.resolve() + } + break + } + case 410: { + if (getFlow !== undefined && responseData.use_flow_id !== undefined) { + console.warn("sdkError 410: Update flow") + return getFlow(responseData.use_flow_id).catch((error) => { + // Something went seriously wrong - log and redirect to defaultNav if possible + console.error(error) + + if (defaultNav !== undefined) { + setLocation(defaultNav, { replace: true }) + } else { + // Rethrow error when can't navigate and let caller handle + throw error + } + }) + } else if (defaultNav !== undefined) { + console.warn("sdkError 410: Navigate to", defaultNav) + setLocation(defaultNav, { replace: true }) + return Promise.resolve() + } + break + } + case 422: { + if (responseData.redirect_browser_to !== undefined) { + // for webauthn we need to reload the flow + const flowId = new URL( + responseData.redirect_browser_to, + ).searchParams.get("flow") + + if (flowId != null && getFlow !== undefined) { + // get new flow data based on the flow id in the redirect url + console.warn("sdkError 422: Update flow") + return getFlow(flowId).catch((error) => { + // Something went seriously wrong - log and redirect to defaultNav if possible + console.error(error) + + if (defaultNav !== undefined) { + setLocation(defaultNav, { replace: true }) + } else { + // Rethrow error when can't navigate and let caller handle + throw error + } + }) + } else { + console.warn("sdkError 422: Redirect browser to") + window.location = responseData.redirect_browser_to + return Promise.resolve() + } + } + } + } + + console.error(error) + + if (fatalToDash) { + console.warn("sdkError: fatal error redirect to dashboard") + setLocation("/", { replace: true }) + return Promise.resolve() + } + + throw error + }, + [setLocation, getFlow], + ) +} + +export const getSearchParam = (searchParam: string) => { + return new URLSearchParams(new URL(window.location.toString()).search).get( + searchParam, + ) +} diff --git a/examples/preact-spa/src/settings.tsx b/examples/preact-spa/src/settings.tsx index 9732f3f24..e605ca6d6 100644 --- a/examples/preact-spa/src/settings.tsx +++ b/examples/preact-spa/src/settings.tsx @@ -1,10 +1,11 @@ import { gridStyle, + NodeMessages, UserSettingsFlowType, UserSettingsCard, } from "@ory/elements-preact" import { useCallback, useEffect, useState } from "preact/hooks" -import sdk from "./sdk" +import { sdk, sdkError, getSearchParam } from "./sdk" import { useLocation } from "wouter" import { SettingsFlow, UpdateSettingsFlowBody } from "@ory/client" @@ -13,33 +14,62 @@ export const Settings = () => { const [location, setLocation] = useLocation() - const onSubmit = useCallback( - ({ flow, body }: { flow: SettingsFlow; body: UpdateSettingsFlowBody }) => + // Get the flow based on the flowId in the URL (.e.g redirect to this page after flow initialized) + const getFlow = useCallback( + (flowId: string) => sdk - .updateSettingsFlow({ - flow: flow.id, - updateSettingsFlowBody: body as UpdateSettingsFlowBody, - }) - .then(({ data: flow }) => { - setFlow(flow) - }) - .catch((error) => { - if (error.response.status === 403) { - return setLocation("/login", { replace: true }) - } - setFlow(error.response.data) - }), + // the flow data contains the form fields, error messages and csrf token + .getSettingsFlow({ id: flowId }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler), [], ) + // initialize the sdkError for generic handling of errors + const sdkErrorHandler = sdkError(getFlow, setFlow, "/settings", true) + + const createFlow = () => { + sdk + // create a new settings flow + // the flow contains the form fields, error messages and csrf token + // depending on the Ory Network project settings, the form fields returned may vary + .createBrowserSettingsFlow() + .then(({ data: flow }) => { + setFlow(flow) + }) + .catch(sdkErrorHandler) + } + + const onSubmit = (body: UpdateSettingsFlowBody) => { + // something unexpected went wrong and the flow was not set + if (!flow) return setLocation("/settings", { replace: true }) + + sdk + .updateSettingsFlow({ + flow: flow.id, + updateSettingsFlowBody: body as UpdateSettingsFlowBody, + }) + .then(({ data: flow }) => { + setFlow(flow) + }) + .catch(sdkErrorHandler) + } + useEffect(() => { - sdk.createBrowserSettingsFlow().then(({ data: flow }) => { - setFlow(flow) - }) + // we might redirect to this page after the flow is initialized, so we check for the flowId in the URL + const flowId = getSearchParam("flow") + + // the flow already exists + if (flowId) { + getFlow(flowId).catch(createFlow) // if for some reason the flow has expired, we need to get a new one + return + } + createFlow() }, []) return flow ? (
+ {( [ "profile", @@ -47,6 +77,7 @@ export const Settings = () => { "totp", "webauthn", "lookupSecret", + "oidc", ] as UserSettingsFlowType[] ).map((flowType: UserSettingsFlowType, index) => ( { flow={flow} flowType={flowType} includeScripts={true} - onSubmit={({ body }) => { - onSubmit({ flow, body }) - }} + onSubmit={({ body }) => onSubmit(body)} /> ))}
diff --git a/examples/preact-spa/src/verification.tsx b/examples/preact-spa/src/verification.tsx index ce09714c4..599a169c8 100644 --- a/examples/preact-spa/src/verification.tsx +++ b/examples/preact-spa/src/verification.tsx @@ -1,18 +1,62 @@ -import { VerificationFlow } from "@ory/client" +import { UpdateVerificationFlowBody, VerificationFlow } from "@ory/client" import { UserAuthCard } from "@ory/elements-preact" -import { useEffect, useState } from "preact/hooks" -import sdk from "./sdk" +import { useCallback, useEffect, useState } from "preact/hooks" +import { getSearchParam, sdk, sdkError } from "./sdk" +import { useLocation } from "wouter" export const Verification = () => { const [flow, setFlow] = useState(null) + const [location, setLocation] = useLocation() - useEffect(() => { + // Get the flow based on the flowId in the URL (.e.g redirect to this page after flow initialized) + const getFlow = useCallback( + (flowId: string) => + sdk + // the flow data contains the form fields, error messages and csrf token + .getVerificationFlow({ id: flowId }) + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler), + [], + ) + + // initialize the sdkError for generic handling of errors + const sdkErrorHandler = sdkError(getFlow, setFlow, "/verification", true) + + // create a new verification flow + const createFlow = () => { sdk .createBrowserVerificationFlow() + // flow contains the form fields, error messages and csrf token + .then(({ data: flow }) => setFlow(flow)) + .catch(sdkErrorHandler) + } + + // submit the verification form data to Ory + const submitFlow = (body: UpdateVerificationFlowBody) => { + // something unexpected went wrong and the flow was not set + if (!flow) return setLocation("/verification", { replace: true }) + + sdk + .updateVerificationFlow({ + flow: flow.id, + updateVerificationFlowBody: body, + }) .then(({ data: flow }) => { setFlow(flow) }) - .catch((error) => console.error(error)) + .catch(sdkErrorHandler) + } + + useEffect(() => { + // it could happen that we are redirected here with an existing flow + const flowId = getSearchParam("flow") + + if (flowId) { + // if the flow failed to get since it could be expired or invalid, we create a new one + getFlow(flowId).catch(createFlow) + return + } + createFlow() }, []) return flow ? ( @@ -23,6 +67,8 @@ export const Verification = () => { loginURL: "/login", }} title="Verification" + // submit the verification form data to Ory + onSubmit={({ body }) => submitFlow(body as UpdateVerificationFlowBody)} /> ) : (
Loading...
diff --git a/package.json b/package.json index 9e9906748..cc6c5fc95 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:clean": "lerna run build --skip-nx-cache", "build": "lerna run build --stream", "build:react": "lerna run build --stream --scope=@ory/elements", - "build:preact": "lerna run build --stream --scope@ory/elements-preact", + "build:preact": "lerna run build --stream --scope=@ory/elements-preact", "build:markup": "lerna run build --stream --scope=@ory/elements-markup", "version": "lerna version --no-private --no-git-tag-version --no-changelog --message 'chore(release): bump version to %s' --yes", "test": "playwright test -c playwright-ct.config.ts",