@thunderid/react 1.0.5 → 1.0.7
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/dist/cjs/components/presentation/auth/Recovery/BaseRecovery.cjs +1 -1
- package/dist/cjs/components/presentation/auth/Recovery/BaseRecovery.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/SignIn/BaseSignIn.cjs +6 -3
- package/dist/cjs/components/presentation/auth/SignIn/BaseSignIn.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/SignIn/SignIn.cjs +1 -0
- package/dist/cjs/components/presentation/auth/SignIn/SignIn.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/SignUp/BaseSignUp.cjs +1 -1
- package/dist/cjs/components/presentation/auth/SignUp/BaseSignUp.cjs.map +1 -1
- package/dist/cjs/contexts/FlowMeta/FlowMetaProvider.cjs +11 -7
- package/dist/cjs/contexts/FlowMeta/FlowMetaProvider.cjs.map +1 -1
- package/dist/cjs/contexts/ThunderID/ThunderIDProvider.cjs +2 -1
- package/dist/cjs/contexts/ThunderID/ThunderIDProvider.cjs.map +1 -1
- package/dist/cjs/hooks/useForm.cjs +42 -5
- package/dist/cjs/hooks/useForm.cjs.map +1 -1
- package/dist/components/presentation/auth/Recovery/BaseRecovery.d.ts.map +1 -1
- package/dist/components/presentation/auth/Recovery/BaseRecovery.js +1 -1
- package/dist/components/presentation/auth/Recovery/BaseRecovery.js.map +1 -1
- package/dist/components/presentation/auth/SignIn/BaseSignIn.d.ts.map +1 -1
- package/dist/components/presentation/auth/SignIn/BaseSignIn.js +6 -3
- package/dist/components/presentation/auth/SignIn/BaseSignIn.js.map +1 -1
- package/dist/components/presentation/auth/SignIn/SignIn.d.ts.map +1 -1
- package/dist/components/presentation/auth/SignIn/SignIn.js +1 -0
- package/dist/components/presentation/auth/SignIn/SignIn.js.map +1 -1
- package/dist/components/presentation/auth/SignUp/BaseSignUp.js +1 -1
- package/dist/components/presentation/auth/SignUp/BaseSignUp.js.map +1 -1
- package/dist/contexts/FlowMeta/FlowMetaProvider.d.ts +2 -0
- package/dist/contexts/FlowMeta/FlowMetaProvider.d.ts.map +1 -1
- package/dist/contexts/FlowMeta/FlowMetaProvider.js +11 -7
- package/dist/contexts/FlowMeta/FlowMetaProvider.js.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.d.ts.map +1 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.js +2 -1
- package/dist/contexts/ThunderID/ThunderIDProvider.js.map +1 -1
- package/dist/hooks/useForm.d.ts +5 -0
- package/dist/hooks/useForm.d.ts.map +1 -1
- package/dist/hooks/useForm.js +43 -6
- package/dist/hooks/useForm.js.map +1 -1
- package/dist/models/config.d.ts +2 -0
- package/dist/models/config.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -33,7 +33,7 @@ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
|
|
|
33
33
|
* </FlowMetaProvider>
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
|
-
const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = null }) => {
|
|
36
|
+
const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = null, namespace }) => {
|
|
37
37
|
const { baseUrl, endpoints, applicationId, isInitialized } = require_useThunderID.default();
|
|
38
38
|
const i18nContext = require_useI18n.default();
|
|
39
39
|
const [meta, setMeta] = (0, react.useState)(initialMeta);
|
|
@@ -62,6 +62,7 @@ const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = n
|
|
|
62
62
|
id: applicationId,
|
|
63
63
|
type: __thunderid_browser.FlowMetaType.App
|
|
64
64
|
} : {},
|
|
65
|
+
...namespace ? { namespace } : {},
|
|
65
66
|
language: i18nContext?.currentLanguage
|
|
66
67
|
}));
|
|
67
68
|
} catch (err) {
|
|
@@ -76,7 +77,8 @@ const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = n
|
|
|
76
77
|
applicationId,
|
|
77
78
|
isInitialized,
|
|
78
79
|
i18nContext?.currentLanguage,
|
|
79
|
-
fetchMeta
|
|
80
|
+
fetchMeta,
|
|
81
|
+
namespace
|
|
80
82
|
]);
|
|
81
83
|
const switchLanguage = (0, react.useCallback)(async (language) => {
|
|
82
84
|
if (!enabled) return;
|
|
@@ -93,13 +95,14 @@ const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = n
|
|
|
93
95
|
id: applicationId,
|
|
94
96
|
type: __thunderid_browser.FlowMetaType.App
|
|
95
97
|
} : {},
|
|
98
|
+
...namespace ? { namespace } : {},
|
|
96
99
|
language
|
|
97
100
|
});
|
|
98
101
|
if (result.i18n?.translations && i18nContext?.injectBundles) {
|
|
99
102
|
const flatTranslations = {};
|
|
100
|
-
Object.entries(result.i18n.translations).forEach(([namespace, keys]) => {
|
|
103
|
+
Object.entries(result.i18n.translations).forEach(([namespace$1, keys]) => {
|
|
101
104
|
Object.entries(keys).forEach(([key, value$1]) => {
|
|
102
|
-
flatTranslations[`${namespace}.${key}`] = value$1;
|
|
105
|
+
flatTranslations[`${namespace$1}.${key}`] = value$1;
|
|
103
106
|
});
|
|
104
107
|
});
|
|
105
108
|
const bundle = { translations: flatTranslations };
|
|
@@ -118,7 +121,8 @@ const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = n
|
|
|
118
121
|
endpoints,
|
|
119
122
|
applicationId,
|
|
120
123
|
i18nContext,
|
|
121
|
-
fetchMeta
|
|
124
|
+
fetchMeta,
|
|
125
|
+
namespace
|
|
122
126
|
]);
|
|
123
127
|
(0, react.useEffect)(() => {
|
|
124
128
|
if (pendingLanguage && i18nContext?.setLanguage) {
|
|
@@ -142,9 +146,9 @@ const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = n
|
|
|
142
146
|
if (!meta?.i18n?.translations || !i18nContext?.injectBundles) return;
|
|
143
147
|
const metaLanguage = meta.i18n.language || __thunderid_browser.TranslationBundleConstants.FALLBACK_LOCALE;
|
|
144
148
|
const flatTranslations = {};
|
|
145
|
-
Object.entries(meta.i18n.translations).forEach(([namespace, keys]) => {
|
|
149
|
+
Object.entries(meta.i18n.translations).forEach(([namespace$1, keys]) => {
|
|
146
150
|
Object.entries(keys).forEach(([key, value$1]) => {
|
|
147
|
-
flatTranslations[`${namespace}.${key}`] = value$1;
|
|
151
|
+
flatTranslations[`${namespace$1}.${key}`] = value$1;
|
|
148
152
|
});
|
|
149
153
|
});
|
|
150
154
|
const bundle = { translations: flatTranslations };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlowMetaProvider.cjs","names":["FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>>","useThunderID","i18nContext: I18nContextValue","useI18n","lastFetchedRef: RefObject<(() => Promise<void>) | null>","initialMetaConsumedRef: RefObject<boolean>","fetchFlowMeta: () => Promise<void>","FlowMetaType","err: unknown","switchLanguage: (language: string) => Promise<void>","result: FlowMetadataResponse","flatTranslations: Record<string, string>","value","bundle: I18nBundle","metaLanguage: string","TranslationBundleConstants","bundlesToInject: Record<string, I18nBundle>","value: any","FlowMetaContext"],"sources":["../../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n FlowMetadataResponse,\n FlowMetaType,\n getFlowMeta,\n I18nBundle,\n resolveResourceEndpoint,\n TranslationBundleConstants,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, RefObject, useCallback, useEffect, useRef, useState} from 'react';\nimport FlowMetaContext from './FlowMetaContext';\nimport {I18nContextValue} from '../I18n/I18nContext';\nimport useI18n from '../I18n/useI18n';\nimport useThunderID from '../ThunderID/useThunderID';\n\nexport interface FlowMetaProviderProps {\n /**\n * When false the provider skips fetching and provides null meta.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Overrides how flow metadata is fetched, routing the request through a caller-supplied\n * function (e.g. a Next.js Server Action) instead of the provider's default direct\n * browser-to-`baseUrl` `fetch()`. Use this when the ThunderID server's `baseUrl` is a\n * different origin than the app and CORS isn't (or can't be) configured for it — the\n * override runs server-side, so the browser never talks to `baseUrl` directly.\n *\n * Called for both the initial fetch and `switchLanguage()`.\n */\n fetchMeta?: (params: {applicationId?: string; language?: string}) => Promise<FlowMetadataResponse>;\n\n /**\n * Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed\n * the provider's state. When present, the provider skips its own initial client-side fetch —\n * avoiding a redundant request and the flash of untranslated i18n keys while that fetch is in\n * flight — but still fetches normally on subsequent changes (e.g. an explicit language switch).\n */\n initialMeta?: FlowMetadataResponse | null;\n}\n\n/**\n * FlowMetaProvider fetches flow metadata from the `GET /flow/meta` endpoint\n * (v2 API) and makes it available to child components through `FlowMetaContext`.\n *\n * It is designed to be used in v2 embedded-flow scenarios and integrates with\n * `ThemeProvider` so that theme settings (colors, direction, typography, …)\n * from the server-side design configuration are applied automatically.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider\n * config={{\n * baseUrl: 'https://localhost:8090',\n * type: FlowMetaType.App,\n * id: 'your-app-id',\n * }}\n * >\n * <ThemeProvider>\n * <App />\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n */\nconst FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>> = ({\n children,\n enabled = true,\n fetchMeta,\n initialMeta = null,\n}: PropsWithChildren<FlowMetaProviderProps>): ReactElement => {\n const {baseUrl, endpoints, applicationId, isInitialized} = useThunderID();\n const i18nContext: I18nContextValue = useI18n();\n\n const [meta, setMeta] = useState<FlowMetadataResponse | null>(initialMeta);\n const [isLoading, setIsLoading] = useState<boolean>(!initialMeta);\n const [error, setError] = useState<Error | null>(null);\n const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);\n\n // Track the last fetchFlowMeta reference that was actually dispatched.\n // This prevents two classes of double-fetch:\n // 1. React StrictMode simulates unmount+remount — the re-mount fires the\n // effect again with the same fetchFlowMeta reference; without this guard\n // the else-branch would issue a redundant second network request.\n // 2. Rapid dependency changes (e.g. baseUrl stabilising) that produce two\n // effect firings before the first fetch completes.\n const lastFetchedRef: RefObject<(() => Promise<void>) | null> = useRef<(() => Promise<void>) | null>(null);\n const initialMetaConsumedRef: RefObject<boolean> = useRef(false);\n const fetchFlowMeta: () => Promise<void> = useCallback(async (): Promise<void> => {\n if (!enabled) {\n setMeta(null);\n setIsLoading(false);\n return;\n }\n\n // Defer until ThunderID finishes initializing (e.g. loading applicationId\n // from storage on refresh). Once initialized, proceed even if applicationId\n // is absent — some flows (e.g. AcceptInvite) have no applicationId by design.\n if (!isInitialized && !applicationId) {\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language: i18nContext?.currentLanguage})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language: i18nContext?.currentLanguage,\n });\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, baseUrl, endpoints, applicationId, isInitialized, i18nContext?.currentLanguage, fetchMeta]);\n\n const switchLanguage: (language: string) => Promise<void> = useCallback(\n async (language: string): Promise<void> => {\n if (!enabled) return;\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language,\n });\n\n // Inject translations for the new language before switching\n if (result.i18n?.translations && i18nContext?.injectBundles) {\n const flatTranslations: Record<string, string> = {};\n Object.entries(result.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n i18nContext.injectBundles({[language]: bundle});\n }\n\n // Defer setLanguage to the next effect cycle so injectBundles state\n // is committed before I18nProvider's setLanguage checks mergedBundles.\n setPendingLanguage(language);\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n },\n [enabled, baseUrl, endpoints, applicationId, i18nContext, fetchMeta],\n );\n\n // After injectBundles + setPendingLanguage are batched and committed, this\n // effect fires with the updated i18nContext (mergedBundles now includes the\n // new language), so setLanguage succeeds on the first switch.\n useEffect(() => {\n if (pendingLanguage && i18nContext?.setLanguage) {\n i18nContext.setLanguage(pendingLanguage);\n setPendingLanguage(null);\n }\n }, [pendingLanguage, i18nContext?.setLanguage]);\n\n useEffect(() => {\n if (!initialMetaConsumedRef.current) {\n initialMetaConsumedRef.current = true;\n\n if (initialMeta) {\n // Seeded from SSR (or another caller) — skip the redundant first client-side fetch.\n // Later dependency changes (e.g. an explicit language switch) still fetch normally.\n lastFetchedRef.current = fetchFlowMeta;\n return;\n }\n }\n\n if (lastFetchedRef.current === fetchFlowMeta) {\n // Same reference as the last dispatch — this is a StrictMode re-mount\n // or an effect re-fire with unchanged deps. Skip to avoid a duplicate fetch.\n return;\n }\n\n lastFetchedRef.current = fetchFlowMeta;\n fetchFlowMeta();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetchFlowMeta]);\n\n // When meta loads with i18n translations, inject them into the i18n system.\n // Meta translations act as the base layer — prop-provided bundles still take precedence.\n useEffect(() => {\n if (!meta?.i18n?.translations || !i18nContext?.injectBundles) {\n return;\n }\n\n const metaLanguage: string = meta.i18n.language || TranslationBundleConstants.FALLBACK_LOCALE;\n\n // Flatten namespace-keyed translations to dot-path keys:\n // { \"signin\": { \"heading\": \"Sign In\" } } → { \"signin.heading\": \"Sign In\" }\n const flatTranslations: Record<string, string> = {};\n Object.entries(meta.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n\n // Inject under the meta language code and the i18n current language so\n // lookups succeed regardless of whether the system uses \"en\" or \"en-US\".\n const bundlesToInject: Record<string, I18nBundle> = {[metaLanguage]: bundle};\n if (i18nContext.currentLanguage && i18nContext.currentLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.currentLanguage] = bundle;\n }\n if (i18nContext.fallbackLanguage && i18nContext.fallbackLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.fallbackLanguage] = bundle;\n }\n\n i18nContext.injectBundles(bundlesToInject);\n }, [meta?.i18n?.translations, i18nContext?.injectBundles]);\n\n const value: any = {\n error,\n fetchFlowMeta,\n isLoading,\n meta,\n switchLanguage,\n };\n\n return <FlowMetaContext.Provider value={value}>{children}</FlowMetaContext.Provider>;\n};\n\nexport default FlowMetaProvider;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FlowMetaProvider.cjs","names":["FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>>","useThunderID","i18nContext: I18nContextValue","useI18n","lastFetchedRef: RefObject<(() => Promise<void>) | null>","initialMetaConsumedRef: RefObject<boolean>","fetchFlowMeta: () => Promise<void>","FlowMetaType","err: unknown","switchLanguage: (language: string) => Promise<void>","result: FlowMetadataResponse","flatTranslations: Record<string, string>","namespace","value","bundle: I18nBundle","metaLanguage: string","TranslationBundleConstants","bundlesToInject: Record<string, I18nBundle>","value: any","FlowMetaContext"],"sources":["../../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n FlowMetadataResponse,\n FlowMetaType,\n getFlowMeta,\n I18nBundle,\n resolveResourceEndpoint,\n TranslationBundleConstants,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, RefObject, useCallback, useEffect, useRef, useState} from 'react';\nimport FlowMetaContext from './FlowMetaContext';\nimport {I18nContextValue} from '../I18n/I18nContext';\nimport useI18n from '../I18n/useI18n';\nimport useThunderID from '../ThunderID/useThunderID';\n\nexport interface FlowMetaProviderProps {\n /**\n * When false the provider skips fetching and provides null meta.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Overrides how flow metadata is fetched, routing the request through a caller-supplied\n * function (e.g. a Next.js Server Action) instead of the provider's default direct\n * browser-to-`baseUrl` `fetch()`. Use this when the ThunderID server's `baseUrl` is a\n * different origin than the app and CORS isn't (or can't be) configured for it — the\n * override runs server-side, so the browser never talks to `baseUrl` directly.\n *\n * Called for both the initial fetch and `switchLanguage()`.\n */\n fetchMeta?: (params: {applicationId?: string; language?: string}) => Promise<FlowMetadataResponse>;\n\n /**\n * Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed\n * the provider's state. When present, the provider skips its own initial client-side fetch —\n * avoiding a redundant request and the flash of untranslated i18n keys while that fetch is in\n * flight — but still fetches normally on subsequent changes (e.g. an explicit language switch).\n */\n initialMeta?: FlowMetadataResponse | null;\n\n /** Sent as the flow-meta request's `namespace`, e.g. to scope namespace-keyed i18n data. */\n namespace?: string;\n}\n\n/**\n * FlowMetaProvider fetches flow metadata from the `GET /flow/meta` endpoint\n * (v2 API) and makes it available to child components through `FlowMetaContext`.\n *\n * It is designed to be used in v2 embedded-flow scenarios and integrates with\n * `ThemeProvider` so that theme settings (colors, direction, typography, …)\n * from the server-side design configuration are applied automatically.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider\n * config={{\n * baseUrl: 'https://localhost:8090',\n * type: FlowMetaType.App,\n * id: 'your-app-id',\n * }}\n * >\n * <ThemeProvider>\n * <App />\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n */\nconst FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>> = ({\n children,\n enabled = true,\n fetchMeta,\n initialMeta = null,\n namespace,\n}: PropsWithChildren<FlowMetaProviderProps>): ReactElement => {\n const {baseUrl, endpoints, applicationId, isInitialized} = useThunderID();\n const i18nContext: I18nContextValue = useI18n();\n\n const [meta, setMeta] = useState<FlowMetadataResponse | null>(initialMeta);\n const [isLoading, setIsLoading] = useState<boolean>(!initialMeta);\n const [error, setError] = useState<Error | null>(null);\n const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);\n\n // Track the last fetchFlowMeta reference that was actually dispatched.\n // This prevents two classes of double-fetch:\n // 1. React StrictMode simulates unmount+remount — the re-mount fires the\n // effect again with the same fetchFlowMeta reference; without this guard\n // the else-branch would issue a redundant second network request.\n // 2. Rapid dependency changes (e.g. baseUrl stabilising) that produce two\n // effect firings before the first fetch completes.\n const lastFetchedRef: RefObject<(() => Promise<void>) | null> = useRef<(() => Promise<void>) | null>(null);\n const initialMetaConsumedRef: RefObject<boolean> = useRef(false);\n const fetchFlowMeta: () => Promise<void> = useCallback(async (): Promise<void> => {\n if (!enabled) {\n setMeta(null);\n setIsLoading(false);\n return;\n }\n\n // Defer until ThunderID finishes initializing (e.g. loading applicationId\n // from storage on refresh). Once initialized, proceed even if applicationId\n // is absent — some flows (e.g. AcceptInvite) have no applicationId by design.\n if (!isInitialized && !applicationId) {\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language: i18nContext?.currentLanguage})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n ...(namespace ? {namespace} : {}),\n language: i18nContext?.currentLanguage,\n });\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, baseUrl, endpoints, applicationId, isInitialized, i18nContext?.currentLanguage, fetchMeta, namespace]);\n\n const switchLanguage: (language: string) => Promise<void> = useCallback(\n async (language: string): Promise<void> => {\n if (!enabled) return;\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n ...(namespace ? {namespace} : {}),\n language,\n });\n\n // Inject translations for the new language before switching\n if (result.i18n?.translations && i18nContext?.injectBundles) {\n const flatTranslations: Record<string, string> = {};\n Object.entries(result.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n i18nContext.injectBundles({[language]: bundle});\n }\n\n // Defer setLanguage to the next effect cycle so injectBundles state\n // is committed before I18nProvider's setLanguage checks mergedBundles.\n setPendingLanguage(language);\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n },\n [enabled, baseUrl, endpoints, applicationId, i18nContext, fetchMeta, namespace],\n );\n\n // After injectBundles + setPendingLanguage are batched and committed, this\n // effect fires with the updated i18nContext (mergedBundles now includes the\n // new language), so setLanguage succeeds on the first switch.\n useEffect(() => {\n if (pendingLanguage && i18nContext?.setLanguage) {\n i18nContext.setLanguage(pendingLanguage);\n setPendingLanguage(null);\n }\n }, [pendingLanguage, i18nContext?.setLanguage]);\n\n useEffect(() => {\n if (!initialMetaConsumedRef.current) {\n initialMetaConsumedRef.current = true;\n\n if (initialMeta) {\n // Seeded from SSR (or another caller) — skip the redundant first client-side fetch.\n // Later dependency changes (e.g. an explicit language switch) still fetch normally.\n lastFetchedRef.current = fetchFlowMeta;\n return;\n }\n }\n\n if (lastFetchedRef.current === fetchFlowMeta) {\n // Same reference as the last dispatch — this is a StrictMode re-mount\n // or an effect re-fire with unchanged deps. Skip to avoid a duplicate fetch.\n return;\n }\n\n lastFetchedRef.current = fetchFlowMeta;\n fetchFlowMeta();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetchFlowMeta]);\n\n // When meta loads with i18n translations, inject them into the i18n system.\n // Meta translations act as the base layer — prop-provided bundles still take precedence.\n useEffect(() => {\n if (!meta?.i18n?.translations || !i18nContext?.injectBundles) {\n return;\n }\n\n const metaLanguage: string = meta.i18n.language || TranslationBundleConstants.FALLBACK_LOCALE;\n\n // Flatten namespace-keyed translations to dot-path keys:\n // { \"signin\": { \"heading\": \"Sign In\" } } → { \"signin.heading\": \"Sign In\" }\n const flatTranslations: Record<string, string> = {};\n Object.entries(meta.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n\n // Inject under the meta language code and the i18n current language so\n // lookups succeed regardless of whether the system uses \"en\" or \"en-US\".\n const bundlesToInject: Record<string, I18nBundle> = {[metaLanguage]: bundle};\n if (i18nContext.currentLanguage && i18nContext.currentLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.currentLanguage] = bundle;\n }\n if (i18nContext.fallbackLanguage && i18nContext.fallbackLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.fallbackLanguage] = bundle;\n }\n\n i18nContext.injectBundles(bundlesToInject);\n }, [meta?.i18n?.translations, i18nContext?.injectBundles]);\n\n const value: any = {\n error,\n fetchFlowMeta,\n isLoading,\n meta,\n switchLanguage,\n };\n\n return <FlowMetaContext.Provider value={value}>{children}</FlowMetaContext.Provider>;\n};\n\nexport default FlowMetaProvider;\n"],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
|
|
|
20
20
|
|
|
21
21
|
//#region src/contexts/ThunderID/ThunderIDProvider.tsx
|
|
22
22
|
const logger = (0, __thunderid_browser.createPackageComponentLogger)("@thunderid/react", "ThunderIDProvider");
|
|
23
|
-
const ThunderIDProvider = ({ afterSignInUrl, afterSignOutUrl, baseUrl: initialBaseUrl, clientId, children, extensions, scopes, preferences, signInUrl, signUpUrl, organizationHandle, applicationId, signInOptions, tokenRequest, syncSession, instanceId = 0, organizationChain, cspNonce, vendor,...rest }) => {
|
|
23
|
+
const ThunderIDProvider = ({ afterSignInUrl, afterSignOutUrl, baseUrl: initialBaseUrl, clientId, children, extensions, scopes, preferences, signInUrl, signUpUrl, organizationHandle, applicationId, signInOptions, tokenRequest, syncSession, instanceId = 0, organizationChain, cspNonce, vendor, namespace,...rest }) => {
|
|
24
24
|
require_emotion.configureEmotionNonce(cspNonce, vendor);
|
|
25
25
|
const reRenderCheckRef = (0, react.useRef)(false);
|
|
26
26
|
const client = (0, react.useMemo)(() => new require_ThunderIDReactClient.default(instanceId), [instanceId]);
|
|
@@ -359,6 +359,7 @@ const ThunderIDProvider = ({ afterSignInUrl, afterSignOutUrl, baseUrl: initialBa
|
|
|
359
359
|
vendor: (0, __thunderid_browser.getVendorPrefix)(config.vendor),
|
|
360
360
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_FlowMetaProvider.default, {
|
|
361
361
|
enabled: preferences?.resolveFromMeta !== false,
|
|
362
|
+
namespace,
|
|
362
363
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ThemeProvider.default, {
|
|
363
364
|
theme: {
|
|
364
365
|
...preferences?.theme?.overrides,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThunderIDProvider.cjs","names":["logger: ReturnType<typeof createPackageComponentLogger>","ThunderIDProvider: FC<PropsWithChildren<ThunderIDProviderProps>>","reRenderCheckRef: RefObject<boolean>","client: ThunderIDReactClient","ThunderIDReactClient","storageManagerRef: any","useBrowserUrl","pendingSessionUpdateRef: RefObject<Promise<void> | null>","resolvedBaseUrl: string","decodedToken: IdToken","shouldFetchProfile: boolean","claims: User","currentSignInStatus: boolean","getUsersMe","getUsersMeMeta","arg1: any","isV2FlowRequest: boolean","ThunderIDRuntimeError","currentUrl: URL","urlParams: URLSearchParams","code: string | null","executionIdFromUrl: string | null","storedExecutionId: string | null","interval: NodeJS.Timeout","status: boolean","getDecodedIdToken: () => Promise<IdToken>","getIdToken: () => Promise<string>","getAccessToken: () => Promise<string>","getStorageManager: () => Promise<any>","storageManager: any","request: (...args: any[]) => Promise<any>","requestAll: (...args: any[]) => Promise<any>","exchangeToken: (exchangeConfig: any) => Promise<any>","signOut: (...args: any[]) => Promise<any>","recover: (payload: any) => Promise<any>","signUp: (...args: any[]) => Promise<any>","clearSession: (...args: any[]) => Promise<any>","reInitialize: (reInitConfig: any) => Promise<any>","result: any","reinitializedConfig: ThunderIDReactConfig","value: any","ThunderIDContext","I18nProvider","FlowMetaProvider","ThemeProvider","FlowProvider","UserProvider","ComponentRendererProvider"],"sources":["../../../../src/contexts/ThunderID/ThunderIDProvider.tsx"],"sourcesContent":["// Copyright 2025-2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n ThunderIDRuntimeError,\n generateFlattenedUserProfile,\n OIDCDiscoveryApiResponse,\n SignInOptions,\n User,\n UserProfile,\n IdToken,\n extractUserClaimsFromIdToken,\n EmbeddedSignInFlowResponse,\n createPackageComponentLogger,\n getVendorPrefix,\n resolveResourceEndpoint,\n AttributeSchema,\n} from '@thunderid/browser';\nimport {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react';\nimport ThunderIDContext from './ThunderIDContext';\nimport useBrowserUrl from '../../hooks/useBrowserUrl';\nimport {ThunderIDReactConfig} from '../../models/config';\nimport {configureEmotionNonce} from '../../styles/emotion';\nimport ThunderIDReactClient from '../../ThunderIDReactClient';\nimport ComponentRendererProvider from '../ComponentRenderer/ComponentRendererProvider';\nimport FlowProvider from '../Flow/FlowProvider';\nimport FlowMetaProvider from '../FlowMeta/FlowMetaProvider';\nimport I18nProvider from '../I18n/I18nProvider';\nimport ThemeProvider from '../Theme/ThemeProvider';\nimport UserProvider from '../User/UserProvider';\nimport getUsersMe from '../../api/getUsersMe';\nimport getUsersMeMeta from '../../api/getUsersMeMeta';\n\nconst logger: ReturnType<typeof createPackageComponentLogger> = createPackageComponentLogger(\n '@thunderid/react',\n 'ThunderIDProvider',\n);\n\n/**\n * Props interface of {@link ThunderIDProvider}\n */\nexport type ThunderIDProviderProps = ThunderIDReactConfig;\n\nconst ThunderIDProvider: FC<PropsWithChildren<ThunderIDProviderProps>> = ({\n afterSignInUrl,\n afterSignOutUrl,\n baseUrl: initialBaseUrl,\n clientId,\n children,\n extensions,\n scopes,\n preferences,\n signInUrl,\n signUpUrl,\n organizationHandle,\n applicationId,\n signInOptions,\n tokenRequest,\n syncSession,\n instanceId = 0,\n organizationChain,\n cspNonce,\n vendor,\n ...rest\n}: PropsWithChildren<ThunderIDProviderProps>): ReactElement => {\n // Must run synchronously here, in the render body, before any descendant's css()/cx()/\n // keyframes() calls execute during this same render pass - see the doc comment on\n // `configureEmotionNonce` for why a `useEffect` would be too late.\n configureEmotionNonce(cspNonce, vendor);\n\n const reRenderCheckRef: RefObject<boolean> = useRef(false);\n const client: ThunderIDReactClient = useMemo(() => new ThunderIDReactClient(instanceId), [instanceId]);\n const storageManagerRef: any = useRef<any>(null);\n const {hasAuthParams, hasCalledForThisInstance} = useBrowserUrl();\n const [user, setUser] = useState<any | null>(null);\n const [isSignedInSync, setIsSignedInSync] = useState<boolean>(false);\n const [isInitializedSync, setIsInitializedSync] = useState<boolean>(false);\n const [isLoadingSync, setIsLoadingSync] = useState<boolean>(true);\n\n const [userProfile, setUserProfile] = useState<UserProfile | null>(null);\n const [userSchema, setUserSchema] = useState<Record<string, AttributeSchema> | null>(null);\n const [baseUrl, setBaseUrl] = useState<string>(initialBaseUrl ?? '');\n const [config, setConfig] = useState<ThunderIDReactConfig>({\n afterSignInUrl: afterSignInUrl ?? window.location.origin,\n afterSignOutUrl: afterSignOutUrl ?? window.location.origin,\n applicationId,\n baseUrl,\n clientId,\n organizationChain,\n organizationHandle,\n scopes,\n signInOptions,\n tokenRequest,\n signInUrl,\n signUpUrl,\n syncSession,\n vendor,\n ...rest,\n });\n\n const [isUpdatingSession, setIsUpdatingSession] = useState<boolean>(false);\n const [wellKnown, setWellKnown] = useState<OIDCDiscoveryApiResponse | null>(null);\n\n // Holds the in-flight `updateSession()` promise so concurrent callers coalesce onto a single\n // `/users/me` request instead of issuing one each.\n const pendingSessionUpdateRef: RefObject<Promise<void> | null> = useRef<Promise<void> | null>(null);\n\n useEffect(() => {\n setBaseUrl(initialBaseUrl ?? '');\n }, [initialBaseUrl]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n await client.initialize(config);\n const initializedConfig: ThunderIDReactConfig = await client.getConfiguration();\n setConfig(initializedConfig);\n setWellKnown(await client.getDiscoveryResponse());\n })();\n }, []);\n\n async function performSessionUpdate(): Promise<void> {\n try {\n // Set flag to prevent loading state tracking from interfering\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n let resolvedBaseUrl: string = baseUrl;\n\n const decodedToken: IdToken = await client.getDecodedIdToken();\n\n // If there's a `user_org` claim in the ID token,\n // Treat this login as a organization login.\n if (decodedToken?.['user_org']) {\n resolvedBaseUrl = `${(await client.getConfiguration()).baseUrl}/o`;\n setBaseUrl(resolvedBaseUrl);\n }\n\n const shouldFetchProfile: boolean = preferences?.user?.fetchUserProfile !== false;\n const claims: User = extractUserClaimsFromIdToken(decodedToken) as User;\n let profileData = claims;\n const currentSignInStatus: boolean = await client.isSignedIn();\n\n if (currentSignInStatus && shouldFetchProfile) {\n try {\n const fetchedProfile = await getUsersMe({\n baseUrl: resolvedBaseUrl,\n url: resolveResourceEndpoint('usersMe', config),\n instanceId,\n });\n profileData = {...claims, ...fetchedProfile};\n } catch (err) {\n logger.warn('Failed to fetch user profile from /users/me:', err);\n }\n\n try {\n const metaRes = await getUsersMeMeta({\n baseUrl: resolvedBaseUrl,\n url: resolveResourceEndpoint('usersMeMeta', config),\n instanceId,\n });\n if (metaRes?.schema) {\n setUserSchema(metaRes.schema);\n } else {\n setUserSchema(null);\n }\n } catch (err) {\n setUserSchema(null);\n logger.warn('Failed to fetch user schema metadata from /users/me/meta:', err);\n }\n } else {\n setUserSchema(null);\n }\n\n setUser(profileData);\n setUserProfile({\n flattenedProfile: generateFlattenedUserProfile(profileData, []),\n profile: profileData,\n });\n\n // CRITICAL: Update sign-in status BEFORE setting loading to false\n // This prevents the race condition where ProtectedRoute sees isLoading=false but isSignedIn=false\n setIsSignedInSync(currentSignInStatus);\n } catch (error) {\n // TODO: Add an error log.\n } finally {\n // Clear the flag and set final loading state\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n }\n\n /**\n * Single-flight wrapper around {@link performSessionUpdate}.\n *\n * Concurrent callers share the in-flight request instead of each issuing their own `/users/me`.\n * This is not a cache: once the request settles the ref is cleared, so a later caller (e.g.\n * revalidation after a profile update) fetches again.\n */\n async function updateSession(): Promise<void> {\n if (pendingSessionUpdateRef.current) {\n return pendingSessionUpdateRef.current;\n }\n\n pendingSessionUpdateRef.current = performSessionUpdate().finally(() => {\n pendingSessionUpdateRef.current = null;\n });\n\n return pendingSessionUpdateRef.current;\n }\n\n async function signIn(...args: any): Promise<User | EmbeddedSignInFlowResponse> {\n // Check if this is a V2 embedded flow request BEFORE calling signIn\n // This allows us to skip session checks entirely for V2 flows\n const arg1: any = args[0];\n const isV2FlowRequest: boolean =\n typeof arg1 === 'object' && arg1 !== null && ('executionId' in arg1 || 'applicationId' in arg1);\n\n try {\n if (!isV2FlowRequest) {\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n }\n\n return await client.signIn(...args);\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signIn-Error',\n 'react',\n 'An error occurred while trying to sign in.',\n );\n } finally {\n if (!isV2FlowRequest) {\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n }\n }\n\n /**\n * Try signing in when the component is mounted.\n */\n useEffect(() => {\n // React 18.x Strict.Mode has a new check for `Ensuring reusable state` to facilitate an upcoming react feature.\n // https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state\n // This will remount all the useEffects to ensure that there are no unexpected side effects.\n // When react remounts the signIn hook of the AuthProvider, it will cause a race condition. Hence, we have to\n // prevent the re-render of this hook as suggested in the following discussion.\n // https://github.com/reactwg/react-18/discussions/18#discussioncomment-795623\n if (reRenderCheckRef.current) {\n return;\n }\n\n reRenderCheckRef.current = true;\n\n (async (): Promise<void> => {\n // Sync session state whenever sign-in completes (both redirect and embedded V2 flows).\n // Pass the user returned by the SDK's sign-in flow so users/me result is not discarded.\n await client.on('sign-in', async () => {\n await updateSession();\n });\n\n // Start auto-refresh with a soft failure. This also covers the case where the access token\n // expired while the refresh token is still valid — startAutoRefreshToken() calls\n // refreshAccessToken() immediately when timeUntilRefresh <= 0 — so the sign-in check below\n // sees the refreshed state.\n try {\n await client.startAutoRefreshToken();\n } catch (error) {\n logger.warn('Failed to schedule automatic token refresh.', error);\n }\n\n // User is already authenticated. Restore the session and skip the sign-in path.\n if (await client.isSignedIn()) {\n await updateSession();\n\n return;\n }\n\n const currentUrl: URL = new URL(window.location.href);\n const hasAuthParamsResult: boolean =\n hasAuthParams(currentUrl, config.afterSignInUrl!) && hasCalledForThisInstance(currentUrl, instanceId ?? 0);\n\n if (hasAuthParamsResult) {\n try {\n const urlParams: URLSearchParams = currentUrl.searchParams;\n const code: string | null = urlParams.get('code');\n const executionIdFromUrl: string | null = urlParams.get('executionId');\n const storedExecutionId: string | null = sessionStorage.getItem(\n `${getVendorPrefix(config.vendor)}_execution_id`,\n );\n\n if (code && !executionIdFromUrl && !storedExecutionId) {\n await signIn();\n }\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signIn-Error',\n 'react',\n 'An error occurred while trying to sign in.',\n );\n }\n }\n })();\n }, []);\n\n /**\n * Check if the user is signed in and update the state accordingly.\n * This will also set an interval to check for the sign-in status every second\n * until the user is signed in.\n */\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n (async (): Promise<void> => {\n try {\n const status: boolean = await client.isSignedIn();\n\n setIsSignedInSync(status);\n\n if (!status) {\n interval = setInterval(async () => {\n const newStatus: boolean = await client.isSignedIn();\n\n if (newStatus) {\n setIsSignedInSync(true);\n clearInterval(interval);\n }\n }, 1000);\n } else {\n // TODO: Add a debug log to indicate that the user is already signed in.\n }\n } catch (error) {\n setIsSignedInSync(false);\n }\n })();\n\n return (): void => {\n if (interval) {\n clearInterval(interval);\n }\n };\n }, [client]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n try {\n const status: boolean = await client.isInitialized();\n\n setIsInitializedSync(status);\n } catch (error) {\n setIsInitializedSync(false);\n }\n })();\n }, [client]);\n\n /**\n * Track loading state changes from the ThunderID client\n */\n useEffect(() => {\n const checkLoadingState = (): void => {\n // Don't override loading state during critical session updates\n if (isUpdatingSession) {\n return;\n }\n\n // Don't set loading=false while auth params are in the URL and user isn't signed in yet.\n // This prevents ProtectedRoute from redirecting before the sign-in effect processes the auth code.\n const currentUrl: URL = new URL(window.location.href);\n if (!isSignedInSync && hasAuthParams(currentUrl, config.afterSignInUrl!)) {\n return;\n }\n\n setIsLoadingSync(client.isLoading());\n };\n\n // Initial check\n checkLoadingState();\n\n // Set up an interval to check for loading state changes\n const interval: NodeJS.Timeout = setInterval(checkLoadingState, 100);\n\n return (): void => {\n clearInterval(interval);\n };\n }, [client, isLoadingSync, isSignedInSync, isUpdatingSession]);\n\n const signInSilently = async (options?: SignInOptions): Promise<User | boolean> => {\n try {\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n return await client.signInSilently(options);\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Error while signing in silently: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signInSilently-Error',\n 'react',\n 'An error occurred while trying to sign in silently.',\n );\n } finally {\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n };\n\n const handleProfileUpdate = (payload: User): void => {\n setUser(payload);\n setUserProfile((prev: UserProfile | null) => ({\n flattenedProfile: generateFlattenedUserProfile(payload),\n profile: payload,\n }));\n };\n\n const getDecodedIdToken: () => Promise<IdToken> = useCallback(\n async (): Promise<IdToken> => client.getDecodedIdToken(),\n [client],\n );\n\n const getIdToken: () => Promise<string> = useCallback(async (): Promise<string> => client.getIdToken(), [client]);\n\n const getAccessToken: () => Promise<string> = useCallback(\n async (): Promise<string> => client.getAccessToken(),\n [client],\n );\n\n const getStorageManager: () => Promise<any> = useCallback(async (): Promise<any> => {\n const storageManager: any = storageManagerRef.current ?? (await client.getStorageManager());\n if (storageManager) {\n storageManagerRef.current = storageManager;\n }\n return storageManager;\n }, [client]);\n\n const request: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.request(...args),\n [client],\n );\n\n const requestAll: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.requestAll(...args),\n [client],\n );\n\n const exchangeToken: (exchangeConfig: any) => Promise<any> = useCallback(\n async (exchangeConfig: any): Promise<any> => client.exchangeToken(exchangeConfig),\n [client],\n );\n\n const signOut: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.signOut(...args),\n [client],\n );\n\n const recover: (payload: any) => Promise<any> = useCallback(\n async (payload: any): Promise<any> => client.recover(payload),\n [client],\n );\n\n const signUp: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.signUp(...args),\n [client],\n );\n\n const clearSession: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.clearSession(...args),\n [client],\n );\n\n const reInitialize: (reInitConfig: any) => Promise<any> = useCallback(\n async (reInitConfig: any): Promise<any> => {\n const result: any = await client.reInitialize(reInitConfig);\n\n // Refresh React state from the reinitialized client so the context (config.endpoints,\n // baseUrl, discovery) reflects the new configuration instead of the pre-reinitialization state.\n const reinitializedConfig: ThunderIDReactConfig = await client.getConfiguration();\n setConfig(reinitializedConfig);\n setBaseUrl(reinitializedConfig.baseUrl ?? '');\n setWellKnown(await client.getDiscoveryResponse());\n\n return result;\n },\n [client],\n );\n\n const value: any = useMemo(\n () => ({\n afterSignInUrl: config.afterSignInUrl,\n applicationId: config.applicationId,\n baseUrl,\n endpoints: config.endpoints,\n scopes: config.scopes,\n clearSession,\n clientId,\n discovery: {\n wellKnown,\n },\n exchangeToken,\n getAccessToken,\n getDecodedIdToken,\n getIdToken,\n getStorageManager,\n http: {\n request,\n requestAll,\n },\n instanceId,\n isInitialized: isInitializedSync,\n isLoading: isLoadingSync,\n isSignedIn: isSignedInSync,\n organizationChain,\n organizationHandle: config?.organizationHandle,\n preferences,\n reInitialize,\n recover,\n signIn,\n signInOptions,\n tokenRequest,\n signInSilently,\n signInUrl,\n signOut,\n signUp,\n signUpUrl,\n syncSession,\n user,\n userSchema,\n vendor: getVendorPrefix(config.vendor),\n }),\n [\n applicationId,\n config?.organizationHandle,\n config.vendor,\n config.afterSignInUrl,\n config.endpoints,\n config.scopes,\n signInUrl,\n signUpUrl,\n baseUrl,\n clientId,\n wellKnown,\n isInitializedSync,\n isLoadingSync,\n isSignedInSync,\n signIn,\n signInSilently,\n user,\n userSchema,\n client,\n signInOptions,\n tokenRequest,\n syncSession,\n getDecodedIdToken,\n clearSession,\n exchangeToken,\n getAccessToken,\n getStorageManager,\n instanceId,\n organizationChain,\n preferences,\n recover,\n reInitialize,\n request,\n requestAll,\n signOut,\n signUp,\n ],\n );\n\n return (\n <ThunderIDContext.Provider value={value}>\n <I18nProvider preferences={preferences?.i18n} vendor={getVendorPrefix(config.vendor)}>\n <FlowMetaProvider enabled={preferences?.resolveFromMeta !== false}>\n <ThemeProvider\n theme={{\n ...preferences?.theme?.overrides,\n direction: preferences?.theme?.direction,\n }}\n >\n <FlowProvider>\n <UserProvider\n profile={userProfile as UserProfile}\n userSchema={userSchema}\n onUpdateProfile={handleProfileUpdate}\n >\n <ComponentRendererProvider renderers={(extensions?.components?.renderers ?? {}) as any}>\n {children}\n </ComponentRendererProvider>\n </UserProvider>\n </FlowProvider>\n </ThemeProvider>\n </FlowMetaProvider>\n </I18nProvider>\n </ThunderIDContext.Provider>\n );\n};\n\nexport default ThunderIDProvider;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ThunderIDProvider.cjs","names":["logger: ReturnType<typeof createPackageComponentLogger>","ThunderIDProvider: FC<PropsWithChildren<ThunderIDProviderProps>>","reRenderCheckRef: RefObject<boolean>","client: ThunderIDReactClient","ThunderIDReactClient","storageManagerRef: any","useBrowserUrl","pendingSessionUpdateRef: RefObject<Promise<void> | null>","resolvedBaseUrl: string","decodedToken: IdToken","shouldFetchProfile: boolean","claims: User","currentSignInStatus: boolean","getUsersMe","getUsersMeMeta","arg1: any","isV2FlowRequest: boolean","ThunderIDRuntimeError","currentUrl: URL","urlParams: URLSearchParams","code: string | null","executionIdFromUrl: string | null","storedExecutionId: string | null","interval: NodeJS.Timeout","status: boolean","getDecodedIdToken: () => Promise<IdToken>","getIdToken: () => Promise<string>","getAccessToken: () => Promise<string>","getStorageManager: () => Promise<any>","storageManager: any","request: (...args: any[]) => Promise<any>","requestAll: (...args: any[]) => Promise<any>","exchangeToken: (exchangeConfig: any) => Promise<any>","signOut: (...args: any[]) => Promise<any>","recover: (payload: any) => Promise<any>","signUp: (...args: any[]) => Promise<any>","clearSession: (...args: any[]) => Promise<any>","reInitialize: (reInitConfig: any) => Promise<any>","result: any","reinitializedConfig: ThunderIDReactConfig","value: any","ThunderIDContext","I18nProvider","FlowMetaProvider","ThemeProvider","FlowProvider","UserProvider","ComponentRendererProvider"],"sources":["../../../../src/contexts/ThunderID/ThunderIDProvider.tsx"],"sourcesContent":["// Copyright 2025-2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n ThunderIDRuntimeError,\n generateFlattenedUserProfile,\n OIDCDiscoveryApiResponse,\n SignInOptions,\n User,\n UserProfile,\n IdToken,\n extractUserClaimsFromIdToken,\n EmbeddedSignInFlowResponse,\n createPackageComponentLogger,\n getVendorPrefix,\n resolveResourceEndpoint,\n AttributeSchema,\n} from '@thunderid/browser';\nimport {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react';\nimport ThunderIDContext from './ThunderIDContext';\nimport useBrowserUrl from '../../hooks/useBrowserUrl';\nimport {ThunderIDReactConfig} from '../../models/config';\nimport {configureEmotionNonce} from '../../styles/emotion';\nimport ThunderIDReactClient from '../../ThunderIDReactClient';\nimport ComponentRendererProvider from '../ComponentRenderer/ComponentRendererProvider';\nimport FlowProvider from '../Flow/FlowProvider';\nimport FlowMetaProvider from '../FlowMeta/FlowMetaProvider';\nimport I18nProvider from '../I18n/I18nProvider';\nimport ThemeProvider from '../Theme/ThemeProvider';\nimport UserProvider from '../User/UserProvider';\nimport getUsersMe from '../../api/getUsersMe';\nimport getUsersMeMeta from '../../api/getUsersMeMeta';\n\nconst logger: ReturnType<typeof createPackageComponentLogger> = createPackageComponentLogger(\n '@thunderid/react',\n 'ThunderIDProvider',\n);\n\n/**\n * Props interface of {@link ThunderIDProvider}\n */\nexport type ThunderIDProviderProps = ThunderIDReactConfig;\n\nconst ThunderIDProvider: FC<PropsWithChildren<ThunderIDProviderProps>> = ({\n afterSignInUrl,\n afterSignOutUrl,\n baseUrl: initialBaseUrl,\n clientId,\n children,\n extensions,\n scopes,\n preferences,\n signInUrl,\n signUpUrl,\n organizationHandle,\n applicationId,\n signInOptions,\n tokenRequest,\n syncSession,\n instanceId = 0,\n organizationChain,\n cspNonce,\n vendor,\n namespace,\n ...rest\n}: PropsWithChildren<ThunderIDProviderProps>): ReactElement => {\n // Must run synchronously here, in the render body, before any descendant's css()/cx()/\n // keyframes() calls execute during this same render pass - see the doc comment on\n // `configureEmotionNonce` for why a `useEffect` would be too late.\n configureEmotionNonce(cspNonce, vendor);\n\n const reRenderCheckRef: RefObject<boolean> = useRef(false);\n const client: ThunderIDReactClient = useMemo(() => new ThunderIDReactClient(instanceId), [instanceId]);\n const storageManagerRef: any = useRef<any>(null);\n const {hasAuthParams, hasCalledForThisInstance} = useBrowserUrl();\n const [user, setUser] = useState<any | null>(null);\n const [isSignedInSync, setIsSignedInSync] = useState<boolean>(false);\n const [isInitializedSync, setIsInitializedSync] = useState<boolean>(false);\n const [isLoadingSync, setIsLoadingSync] = useState<boolean>(true);\n\n const [userProfile, setUserProfile] = useState<UserProfile | null>(null);\n const [userSchema, setUserSchema] = useState<Record<string, AttributeSchema> | null>(null);\n const [baseUrl, setBaseUrl] = useState<string>(initialBaseUrl ?? '');\n const [config, setConfig] = useState<ThunderIDReactConfig>({\n afterSignInUrl: afterSignInUrl ?? window.location.origin,\n afterSignOutUrl: afterSignOutUrl ?? window.location.origin,\n applicationId,\n baseUrl,\n clientId,\n organizationChain,\n organizationHandle,\n scopes,\n signInOptions,\n tokenRequest,\n signInUrl,\n signUpUrl,\n syncSession,\n vendor,\n ...rest,\n });\n\n const [isUpdatingSession, setIsUpdatingSession] = useState<boolean>(false);\n const [wellKnown, setWellKnown] = useState<OIDCDiscoveryApiResponse | null>(null);\n\n // Holds the in-flight `updateSession()` promise so concurrent callers coalesce onto a single\n // `/users/me` request instead of issuing one each.\n const pendingSessionUpdateRef: RefObject<Promise<void> | null> = useRef<Promise<void> | null>(null);\n\n useEffect(() => {\n setBaseUrl(initialBaseUrl ?? '');\n }, [initialBaseUrl]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n await client.initialize(config);\n const initializedConfig: ThunderIDReactConfig = await client.getConfiguration();\n setConfig(initializedConfig);\n setWellKnown(await client.getDiscoveryResponse());\n })();\n }, []);\n\n async function performSessionUpdate(): Promise<void> {\n try {\n // Set flag to prevent loading state tracking from interfering\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n let resolvedBaseUrl: string = baseUrl;\n\n const decodedToken: IdToken = await client.getDecodedIdToken();\n\n // If there's a `user_org` claim in the ID token,\n // Treat this login as a organization login.\n if (decodedToken?.['user_org']) {\n resolvedBaseUrl = `${(await client.getConfiguration()).baseUrl}/o`;\n setBaseUrl(resolvedBaseUrl);\n }\n\n const shouldFetchProfile: boolean = preferences?.user?.fetchUserProfile !== false;\n const claims: User = extractUserClaimsFromIdToken(decodedToken) as User;\n let profileData = claims;\n const currentSignInStatus: boolean = await client.isSignedIn();\n\n if (currentSignInStatus && shouldFetchProfile) {\n try {\n const fetchedProfile = await getUsersMe({\n baseUrl: resolvedBaseUrl,\n url: resolveResourceEndpoint('usersMe', config),\n instanceId,\n });\n profileData = {...claims, ...fetchedProfile};\n } catch (err) {\n logger.warn('Failed to fetch user profile from /users/me:', err);\n }\n\n try {\n const metaRes = await getUsersMeMeta({\n baseUrl: resolvedBaseUrl,\n url: resolveResourceEndpoint('usersMeMeta', config),\n instanceId,\n });\n if (metaRes?.schema) {\n setUserSchema(metaRes.schema);\n } else {\n setUserSchema(null);\n }\n } catch (err) {\n setUserSchema(null);\n logger.warn('Failed to fetch user schema metadata from /users/me/meta:', err);\n }\n } else {\n setUserSchema(null);\n }\n\n setUser(profileData);\n setUserProfile({\n flattenedProfile: generateFlattenedUserProfile(profileData, []),\n profile: profileData,\n });\n\n // CRITICAL: Update sign-in status BEFORE setting loading to false\n // This prevents the race condition where ProtectedRoute sees isLoading=false but isSignedIn=false\n setIsSignedInSync(currentSignInStatus);\n } catch (error) {\n // TODO: Add an error log.\n } finally {\n // Clear the flag and set final loading state\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n }\n\n /**\n * Single-flight wrapper around {@link performSessionUpdate}.\n *\n * Concurrent callers share the in-flight request instead of each issuing their own `/users/me`.\n * This is not a cache: once the request settles the ref is cleared, so a later caller (e.g.\n * revalidation after a profile update) fetches again.\n */\n async function updateSession(): Promise<void> {\n if (pendingSessionUpdateRef.current) {\n return pendingSessionUpdateRef.current;\n }\n\n pendingSessionUpdateRef.current = performSessionUpdate().finally(() => {\n pendingSessionUpdateRef.current = null;\n });\n\n return pendingSessionUpdateRef.current;\n }\n\n async function signIn(...args: any): Promise<User | EmbeddedSignInFlowResponse> {\n // Check if this is a V2 embedded flow request BEFORE calling signIn\n // This allows us to skip session checks entirely for V2 flows\n const arg1: any = args[0];\n const isV2FlowRequest: boolean =\n typeof arg1 === 'object' && arg1 !== null && ('executionId' in arg1 || 'applicationId' in arg1);\n\n try {\n if (!isV2FlowRequest) {\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n }\n\n return await client.signIn(...args);\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signIn-Error',\n 'react',\n 'An error occurred while trying to sign in.',\n );\n } finally {\n if (!isV2FlowRequest) {\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n }\n }\n\n /**\n * Try signing in when the component is mounted.\n */\n useEffect(() => {\n // React 18.x Strict.Mode has a new check for `Ensuring reusable state` to facilitate an upcoming react feature.\n // https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state\n // This will remount all the useEffects to ensure that there are no unexpected side effects.\n // When react remounts the signIn hook of the AuthProvider, it will cause a race condition. Hence, we have to\n // prevent the re-render of this hook as suggested in the following discussion.\n // https://github.com/reactwg/react-18/discussions/18#discussioncomment-795623\n if (reRenderCheckRef.current) {\n return;\n }\n\n reRenderCheckRef.current = true;\n\n (async (): Promise<void> => {\n // Sync session state whenever sign-in completes (both redirect and embedded V2 flows).\n // Pass the user returned by the SDK's sign-in flow so users/me result is not discarded.\n await client.on('sign-in', async () => {\n await updateSession();\n });\n\n // Start auto-refresh with a soft failure. This also covers the case where the access token\n // expired while the refresh token is still valid — startAutoRefreshToken() calls\n // refreshAccessToken() immediately when timeUntilRefresh <= 0 — so the sign-in check below\n // sees the refreshed state.\n try {\n await client.startAutoRefreshToken();\n } catch (error) {\n logger.warn('Failed to schedule automatic token refresh.', error);\n }\n\n // User is already authenticated. Restore the session and skip the sign-in path.\n if (await client.isSignedIn()) {\n await updateSession();\n\n return;\n }\n\n const currentUrl: URL = new URL(window.location.href);\n const hasAuthParamsResult: boolean =\n hasAuthParams(currentUrl, config.afterSignInUrl!) && hasCalledForThisInstance(currentUrl, instanceId ?? 0);\n\n if (hasAuthParamsResult) {\n try {\n const urlParams: URLSearchParams = currentUrl.searchParams;\n const code: string | null = urlParams.get('code');\n const executionIdFromUrl: string | null = urlParams.get('executionId');\n const storedExecutionId: string | null = sessionStorage.getItem(\n `${getVendorPrefix(config.vendor)}_execution_id`,\n );\n\n if (code && !executionIdFromUrl && !storedExecutionId) {\n await signIn();\n }\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signIn-Error',\n 'react',\n 'An error occurred while trying to sign in.',\n );\n }\n }\n })();\n }, []);\n\n /**\n * Check if the user is signed in and update the state accordingly.\n * This will also set an interval to check for the sign-in status every second\n * until the user is signed in.\n */\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n (async (): Promise<void> => {\n try {\n const status: boolean = await client.isSignedIn();\n\n setIsSignedInSync(status);\n\n if (!status) {\n interval = setInterval(async () => {\n const newStatus: boolean = await client.isSignedIn();\n\n if (newStatus) {\n setIsSignedInSync(true);\n clearInterval(interval);\n }\n }, 1000);\n } else {\n // TODO: Add a debug log to indicate that the user is already signed in.\n }\n } catch (error) {\n setIsSignedInSync(false);\n }\n })();\n\n return (): void => {\n if (interval) {\n clearInterval(interval);\n }\n };\n }, [client]);\n\n useEffect(() => {\n (async (): Promise<void> => {\n try {\n const status: boolean = await client.isInitialized();\n\n setIsInitializedSync(status);\n } catch (error) {\n setIsInitializedSync(false);\n }\n })();\n }, [client]);\n\n /**\n * Track loading state changes from the ThunderID client\n */\n useEffect(() => {\n const checkLoadingState = (): void => {\n // Don't override loading state during critical session updates\n if (isUpdatingSession) {\n return;\n }\n\n // Don't set loading=false while auth params are in the URL and user isn't signed in yet.\n // This prevents ProtectedRoute from redirecting before the sign-in effect processes the auth code.\n const currentUrl: URL = new URL(window.location.href);\n if (!isSignedInSync && hasAuthParams(currentUrl, config.afterSignInUrl!)) {\n return;\n }\n\n setIsLoadingSync(client.isLoading());\n };\n\n // Initial check\n checkLoadingState();\n\n // Set up an interval to check for loading state changes\n const interval: NodeJS.Timeout = setInterval(checkLoadingState, 100);\n\n return (): void => {\n clearInterval(interval);\n };\n }, [client, isLoadingSync, isSignedInSync, isUpdatingSession]);\n\n const signInSilently = async (options?: SignInOptions): Promise<User | boolean> => {\n try {\n setIsUpdatingSession(true);\n setIsLoadingSync(true);\n return await client.signInSilently(options);\n } catch (error) {\n throw new ThunderIDRuntimeError(\n `Error while signing in silently: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,\n 'thunderid-signInSilently-Error',\n 'react',\n 'An error occurred while trying to sign in silently.',\n );\n } finally {\n setIsUpdatingSession(false);\n setIsLoadingSync(client.isLoading());\n }\n };\n\n const handleProfileUpdate = (payload: User): void => {\n setUser(payload);\n setUserProfile((prev: UserProfile | null) => ({\n flattenedProfile: generateFlattenedUserProfile(payload),\n profile: payload,\n }));\n };\n\n const getDecodedIdToken: () => Promise<IdToken> = useCallback(\n async (): Promise<IdToken> => client.getDecodedIdToken(),\n [client],\n );\n\n const getIdToken: () => Promise<string> = useCallback(async (): Promise<string> => client.getIdToken(), [client]);\n\n const getAccessToken: () => Promise<string> = useCallback(\n async (): Promise<string> => client.getAccessToken(),\n [client],\n );\n\n const getStorageManager: () => Promise<any> = useCallback(async (): Promise<any> => {\n const storageManager: any = storageManagerRef.current ?? (await client.getStorageManager());\n if (storageManager) {\n storageManagerRef.current = storageManager;\n }\n return storageManager;\n }, [client]);\n\n const request: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.request(...args),\n [client],\n );\n\n const requestAll: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.requestAll(...args),\n [client],\n );\n\n const exchangeToken: (exchangeConfig: any) => Promise<any> = useCallback(\n async (exchangeConfig: any): Promise<any> => client.exchangeToken(exchangeConfig),\n [client],\n );\n\n const signOut: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.signOut(...args),\n [client],\n );\n\n const recover: (payload: any) => Promise<any> = useCallback(\n async (payload: any): Promise<any> => client.recover(payload),\n [client],\n );\n\n const signUp: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.signUp(...args),\n [client],\n );\n\n const clearSession: (...args: any[]) => Promise<any> = useCallback(\n async (...args: any[]): Promise<any> => client.clearSession(...args),\n [client],\n );\n\n const reInitialize: (reInitConfig: any) => Promise<any> = useCallback(\n async (reInitConfig: any): Promise<any> => {\n const result: any = await client.reInitialize(reInitConfig);\n\n // Refresh React state from the reinitialized client so the context (config.endpoints,\n // baseUrl, discovery) reflects the new configuration instead of the pre-reinitialization state.\n const reinitializedConfig: ThunderIDReactConfig = await client.getConfiguration();\n setConfig(reinitializedConfig);\n setBaseUrl(reinitializedConfig.baseUrl ?? '');\n setWellKnown(await client.getDiscoveryResponse());\n\n return result;\n },\n [client],\n );\n\n const value: any = useMemo(\n () => ({\n afterSignInUrl: config.afterSignInUrl,\n applicationId: config.applicationId,\n baseUrl,\n endpoints: config.endpoints,\n scopes: config.scopes,\n clearSession,\n clientId,\n discovery: {\n wellKnown,\n },\n exchangeToken,\n getAccessToken,\n getDecodedIdToken,\n getIdToken,\n getStorageManager,\n http: {\n request,\n requestAll,\n },\n instanceId,\n isInitialized: isInitializedSync,\n isLoading: isLoadingSync,\n isSignedIn: isSignedInSync,\n organizationChain,\n organizationHandle: config?.organizationHandle,\n preferences,\n reInitialize,\n recover,\n signIn,\n signInOptions,\n tokenRequest,\n signInSilently,\n signInUrl,\n signOut,\n signUp,\n signUpUrl,\n syncSession,\n user,\n userSchema,\n vendor: getVendorPrefix(config.vendor),\n }),\n [\n applicationId,\n config?.organizationHandle,\n config.vendor,\n config.afterSignInUrl,\n config.endpoints,\n config.scopes,\n signInUrl,\n signUpUrl,\n baseUrl,\n clientId,\n wellKnown,\n isInitializedSync,\n isLoadingSync,\n isSignedInSync,\n signIn,\n signInSilently,\n user,\n userSchema,\n client,\n signInOptions,\n tokenRequest,\n syncSession,\n getDecodedIdToken,\n clearSession,\n exchangeToken,\n getAccessToken,\n getStorageManager,\n instanceId,\n organizationChain,\n preferences,\n recover,\n reInitialize,\n request,\n requestAll,\n signOut,\n signUp,\n ],\n );\n\n return (\n <ThunderIDContext.Provider value={value}>\n <I18nProvider preferences={preferences?.i18n} vendor={getVendorPrefix(config.vendor)}>\n <FlowMetaProvider enabled={preferences?.resolveFromMeta !== false} namespace={namespace}>\n <ThemeProvider\n theme={{\n ...preferences?.theme?.overrides,\n direction: preferences?.theme?.direction,\n }}\n >\n <FlowProvider>\n <UserProvider\n profile={userProfile as UserProfile}\n userSchema={userSchema}\n onUpdateProfile={handleProfileUpdate}\n >\n <ComponentRendererProvider renderers={(extensions?.components?.renderers ?? {}) as any}>\n {children}\n </ComponentRendererProvider>\n </UserProvider>\n </FlowProvider>\n </ThemeProvider>\n </FlowMetaProvider>\n </I18nProvider>\n </ThunderIDContext.Provider>\n );\n};\n\nexport default ThunderIDProvider;\n"],"mappings":"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"}
|
|
@@ -65,6 +65,7 @@ const useForm = (config = {}) => {
|
|
|
65
65
|
const [touched, setFormTouched] = (0, react.useState)({});
|
|
66
66
|
const [errors, setFormErrors] = (0, react.useState)({});
|
|
67
67
|
const [isSubmitted, setIsSubmitted] = (0, react.useState)(false);
|
|
68
|
+
const clientErrorFieldRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
68
69
|
const getFieldConfig = (0, react.useCallback)((name) => fields.find((field) => field.name === name), [fields]);
|
|
69
70
|
const validateField = (0, react.useCallback)((name) => {
|
|
70
71
|
return computeFieldError(values[name] || "", getFieldConfig(name), requiredMessage);
|
|
@@ -73,6 +74,27 @@ const useForm = (config = {}) => {
|
|
|
73
74
|
getFieldConfig,
|
|
74
75
|
requiredMessage
|
|
75
76
|
]);
|
|
77
|
+
const validateFieldRef = (0, react.useRef)(validateField);
|
|
78
|
+
validateFieldRef.current = validateField;
|
|
79
|
+
const revalidateTouchedFields = (0, react.useCallback)(() => {
|
|
80
|
+
setFormErrors((prevErrors) => {
|
|
81
|
+
if (Object.keys(prevErrors).length === 0) return prevErrors;
|
|
82
|
+
const newErrors = { ...prevErrors };
|
|
83
|
+
let changed = false;
|
|
84
|
+
Object.keys(prevErrors).forEach((name) => {
|
|
85
|
+
if (!clientErrorFieldRef.current.has(name)) return;
|
|
86
|
+
const freshError = validateFieldRef.current(name);
|
|
87
|
+
if (freshError === prevErrors[name]) return;
|
|
88
|
+
changed = true;
|
|
89
|
+
if (freshError) newErrors[name] = freshError;
|
|
90
|
+
else {
|
|
91
|
+
delete newErrors[name];
|
|
92
|
+
clientErrorFieldRef.current.delete(name);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return changed ? newErrors : prevErrors;
|
|
96
|
+
});
|
|
97
|
+
}, []);
|
|
76
98
|
const validateForm = (0, react.useCallback)(() => {
|
|
77
99
|
const newErrors = {};
|
|
78
100
|
fields.forEach((field) => {
|
|
@@ -105,8 +127,13 @@ const useForm = (config = {}) => {
|
|
|
105
127
|
const error = computeFieldError(value, getFieldConfig(name), requiredMessage);
|
|
106
128
|
setFormErrors((prev) => {
|
|
107
129
|
const newErrors = { ...prev };
|
|
108
|
-
if (error)
|
|
109
|
-
|
|
130
|
+
if (error) {
|
|
131
|
+
newErrors[name] = error;
|
|
132
|
+
clientErrorFieldRef.current.add(name);
|
|
133
|
+
} else {
|
|
134
|
+
delete newErrors[name];
|
|
135
|
+
clientErrorFieldRef.current.delete(name);
|
|
136
|
+
}
|
|
110
137
|
return newErrors;
|
|
111
138
|
});
|
|
112
139
|
}, [
|
|
@@ -131,8 +158,13 @@ const useForm = (config = {}) => {
|
|
|
131
158
|
const error = validateField(name);
|
|
132
159
|
setFormErrors((prev) => {
|
|
133
160
|
const newErrors = { ...prev };
|
|
134
|
-
if (error)
|
|
135
|
-
|
|
161
|
+
if (error) {
|
|
162
|
+
newErrors[name] = error;
|
|
163
|
+
clientErrorFieldRef.current.add(name);
|
|
164
|
+
} else {
|
|
165
|
+
delete newErrors[name];
|
|
166
|
+
clientErrorFieldRef.current.delete(name);
|
|
167
|
+
}
|
|
136
168
|
return newErrors;
|
|
137
169
|
});
|
|
138
170
|
}
|
|
@@ -148,7 +180,9 @@ const useForm = (config = {}) => {
|
|
|
148
180
|
acc[field.name] = true;
|
|
149
181
|
return acc;
|
|
150
182
|
}, {}));
|
|
151
|
-
|
|
183
|
+
const validation = validateForm();
|
|
184
|
+
setFormErrors(validation.errors);
|
|
185
|
+
clientErrorFieldRef.current = new Set(Object.keys(validation.errors));
|
|
152
186
|
}, [fields, validateForm]);
|
|
153
187
|
const setError = (0, react.useCallback)((name, error) => {
|
|
154
188
|
setFormErrors((prev) => ({
|
|
@@ -164,12 +198,14 @@ const useForm = (config = {}) => {
|
|
|
164
198
|
}, []);
|
|
165
199
|
const clearErrors = (0, react.useCallback)(() => {
|
|
166
200
|
setFormErrors({});
|
|
201
|
+
clientErrorFieldRef.current.clear();
|
|
167
202
|
}, []);
|
|
168
203
|
const reset = (0, react.useCallback)(() => {
|
|
169
204
|
setFormValues({ ...initialValues });
|
|
170
205
|
setFormTouched({});
|
|
171
206
|
setFormErrors({});
|
|
172
207
|
setIsSubmitted(false);
|
|
208
|
+
clientErrorFieldRef.current.clear();
|
|
173
209
|
}, [initialValues]);
|
|
174
210
|
const handleSubmit = (0, react.useCallback)((onSubmit) => async (e) => {
|
|
175
211
|
if (e) e.preventDefault();
|
|
@@ -207,6 +243,7 @@ const useForm = (config = {}) => {
|
|
|
207
243
|
isSubmitted,
|
|
208
244
|
isValid,
|
|
209
245
|
reset,
|
|
246
|
+
revalidateTouchedFields,
|
|
210
247
|
setError,
|
|
211
248
|
setErrors,
|
|
212
249
|
setTouched,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useForm.cjs","names":["fieldError: string | null","getFieldConfig: (name: keyof T) => FormField | undefined","validateField: (name: keyof T) => string | null","validateForm: () => ValidationResult","newErrors: Record<keyof T, string>","error: string | null","globalErrors: Record<string, string>","isValid: boolean","setValue: (name: keyof T, value: string) => void","setValues: (newValues: Partial<T>) => void","setTouched: (name: keyof T, isTouched?: boolean) => void","setTouchedFields: (touchedFields: Partial<Record<keyof T, boolean>>) => void","touchAllFields: () => void","setError: (name: keyof T, error: string) => void","setErrors: (newErrors: Partial<Record<keyof T, string>>) => void","clearErrors: () => void","reset: () => void","handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void>","fieldConfig: FormField | undefined"],"sources":["../../../src/hooks/useForm.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {useState, useCallback, FormEvent} from 'react';\n\n/**\n * Generic form field configuration\n */\nexport interface FormField {\n initialValue?: string;\n name: string;\n required?: boolean;\n validator?: (value: string) => string | null;\n}\n\n/**\n * Form validation result\n */\nexport interface ValidationResult {\n errors: Record<string, string>;\n isValid: boolean;\n}\n\n/**\n * Configuration for the useForm hook\n */\nexport interface UseFormConfig<T extends Record<string, string>> {\n /**\n * Form field definitions\n */\n fields?: FormField[];\n /**\n * Initial form values\n */\n initialValues?: Partial<T>;\n /**\n * Custom required field validation message\n */\n requiredMessage?: string;\n /**\n * When a field has been touched (blurred at least once), re-run validation on every\n * subsequent change so a rendered error clears the moment the value becomes valid.\n *\n * Independent of `validateOnChange`: this only affects fields that have already been\n * blurred, so users don't see errors while initially typing. Default `false` to\n * preserve prior behavior; enable per-form for a \"correct-as-you-type\" UX.\n */\n revalidateOnChangeAfterBlur?: boolean;\n /**\n * Whether to validate on blur (default: true)\n */\n validateOnBlur?: boolean;\n /**\n * Whether to validate on change (default: false). When true, every keystroke\n * triggers validation — including the first, which surfaces errors before the user\n * has had a chance to finish typing. Prefer `revalidateOnChangeAfterBlur` for the\n * common \"clear the error as the user corrects\" UX.\n */\n validateOnChange?: boolean;\n /**\n * Global form validator function\n */\n validator?: (values: T) => Record<string, string>;\n}\n\n/**\n * Pure per-field validator. Kept outside `useForm` so `setValue` can validate against\n * the value it just wrote — inline validation via the closure-based `validateField`\n * inside the hook would read stale `values` (state is committed asynchronously).\n */\nconst computeFieldError = (\n value: string,\n fieldConfig: FormField | undefined,\n requiredMessage: string,\n): string | null => {\n if (fieldConfig?.required && (!value || value.trim() === '')) {\n return requiredMessage;\n }\n if (fieldConfig?.validator) {\n const fieldError: string | null = fieldConfig.validator(value);\n if (fieldError) return fieldError;\n }\n return null;\n};\n\n/**\n * Return type for the useForm hook\n */\nexport interface UseFormReturn<T extends Record<string, string>> {\n /**\n * Clear all errors\n */\n clearErrors: () => void;\n /**\n * Current validation errors\n */\n errors: Record<keyof T, string>;\n /**\n * Get field props for easy integration with form components\n */\n getFieldProps: (name: keyof T) => {\n error: string | undefined;\n name: keyof T;\n onBlur: () => void;\n onChange: (value: string) => void;\n required: boolean;\n touched: boolean;\n value: string;\n };\n /**\n * Handle form submission\n */\n handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void>;\n /**\n * Whether the form has been submitted\n */\n isSubmitted: boolean;\n /**\n * Whether the form is currently valid\n */\n isValid: boolean;\n /**\n * Reset the form to initial values\n */\n reset: () => void;\n /**\n * Set a field error\n */\n setError: (name: keyof T, error: string) => void;\n /**\n * Set multiple field errors\n */\n setErrors: (errors: Partial<Record<keyof T, string>>) => void;\n /**\n * Mark a field as touched\n */\n setTouched: (name: keyof T, touched?: boolean) => void;\n /**\n * Mark multiple fields as touched\n */\n setTouchedFields: (touched: Partial<Record<keyof T, boolean>>) => void;\n /**\n * Set a single field value\n */\n setValue: (name: keyof T, value: string) => void;\n /**\n * Set multiple field values\n */\n setValues: (values: Partial<T>) => void;\n /**\n * Mark all fields as touched\n */\n touchAllFields: () => void;\n /**\n * Fields that have been touched by the user\n */\n touched: Record<keyof T, boolean>;\n /**\n * Validate a single field\n */\n validateField: (name: keyof T) => string | null;\n /**\n * Validate all fields\n */\n validateForm: () => ValidationResult;\n /**\n * Current form values\n */\n values: T;\n}\n\n/**\n * Generic form hook that provides comprehensive form state management and validation.\n *\n * @template T - The type of form values (must extend Record<string, string>)\n * @param config - Configuration options for the form\n * @returns Form state and methods\n *\n * @example\n * ```tsx\n * interface LoginForm {\n * username: string;\n * password: string;\n * }\n *\n * const {\n * values,\n * touched,\n * errors,\n * isValid,\n * setValue,\n * handleSubmit,\n * getFieldProps\n * } = useForm<LoginForm>({\n * initialValues: { username: '', password: '' },\n * fields: [\n * { name: 'username', required: true },\n * { name: 'password', required: true }\n * ]\n * });\n *\n * const onSubmit = handleSubmit((values) => {\n * console.log('Form submitted:', values);\n * });\n *\n * return (\n * <form onSubmit={onSubmit}>\n * <input {...getFieldProps('username')} />\n * <input {...getFieldProps('password')} type=\"password\" />\n * <button type=\"submit\" disabled={!isValid}>Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends Record<string, string>>(config: UseFormConfig<T> = {}): UseFormReturn<T> => {\n const {\n initialValues = {} as T,\n fields = [],\n validator,\n validateOnChange = false,\n validateOnBlur = true,\n revalidateOnChangeAfterBlur = false,\n requiredMessage = 'This field is required',\n } = config;\n\n // Initialize form state\n const [values, setFormValues] = useState<T>({...initialValues} as T);\n const [touched, setFormTouched] = useState<Record<keyof T, boolean>>({} as Record<keyof T, boolean>);\n const [errors, setFormErrors] = useState<Record<keyof T, string>>({} as Record<keyof T, string>);\n const [isSubmitted, setIsSubmitted] = useState(false);\n\n // Get field configuration by name\n const getFieldConfig: (name: keyof T) => FormField | undefined = useCallback(\n (name: keyof T): FormField | undefined => fields.find((field: FormField) => field.name === name),\n [fields],\n );\n\n // Validate a single field against currently-committed state. Used by the blur path\n // (where `values` is already up-to-date) and by `validateForm`.\n const validateField: (name: keyof T) => string | null = useCallback(\n (name: keyof T): string | null => {\n const value: string = values[name] || '';\n const fieldConfig: FormField | undefined = getFieldConfig(name);\n return computeFieldError(value, fieldConfig, requiredMessage);\n },\n [values, getFieldConfig, requiredMessage],\n );\n\n // Validate the entire form\n const validateForm: () => ValidationResult = useCallback((): ValidationResult => {\n const newErrors: Record<keyof T, string> = {} as Record<keyof T, string>;\n\n // Validate individual fields\n fields.forEach((field: FormField) => {\n const error: string | null = validateField(field.name as keyof T);\n if (error) {\n newErrors[field.name as keyof T] = error;\n }\n });\n\n // Run global validator if provided\n if (validator) {\n const globalErrors: Record<string, string> = validator(values);\n Object.keys(globalErrors).forEach((key: string) => {\n if (globalErrors[key]) {\n newErrors[key as keyof T] = globalErrors[key];\n }\n });\n }\n\n return {\n errors: newErrors,\n isValid: Object.keys(newErrors).length === 0,\n };\n }, [fields, validateField, validator, values]);\n\n // Check if form is currently valid\n const isValid: boolean = Object.keys(errors).length === 0;\n\n // Set a single field value.\n //\n // Validation policy:\n // - `validateOnChange: true` → validate on every keystroke.\n // - `revalidateOnChangeAfterBlur: true` → validate only if the field has\n // already been touched (blurred once),\n // so errors clear as the user corrects\n // without appearing while first typing.\n // - both false → no on-change validation.\n //\n // Validation runs against the NEXT value (the string being written) rather than\n // going through `validateField` which reads the stale closed-over `values`. This\n // prevents the one-keystroke-lag bug where the error clears one character late.\n const setValue: (name: keyof T, value: string) => void = useCallback(\n (name: keyof T, value: string): void => {\n setFormValues((prev: T) => ({\n ...prev,\n [name]: value,\n }));\n\n const shouldValidate: boolean = validateOnChange || (revalidateOnChangeAfterBlur && touched[name] === true);\n if (!shouldValidate) {\n return;\n }\n\n const error: string | null = computeFieldError(value, getFieldConfig(name), requiredMessage);\n setFormErrors((prev: Record<keyof T, string>) => {\n const newErrors: Record<keyof T, string> = {...prev};\n if (error) {\n newErrors[name] = error;\n } else {\n delete newErrors[name];\n }\n return newErrors;\n });\n },\n [validateOnChange, revalidateOnChangeAfterBlur, touched, getFieldConfig, requiredMessage],\n );\n\n // Set multiple field values\n const setValues: (newValues: Partial<T>) => void = useCallback((newValues: Partial<T>): void => {\n setFormValues((prev: T) => ({\n ...prev,\n ...newValues,\n }));\n }, []);\n\n // Mark a field as touched\n const setTouched: (name: keyof T, isTouched?: boolean) => void = useCallback(\n (name: keyof T, isTouched = true): void => {\n setFormTouched((prev: Record<keyof T, boolean>) => ({\n ...prev,\n [name]: isTouched,\n }));\n\n // Validate on blur if enabled and field is touched\n if (validateOnBlur && isTouched) {\n const error: string | null = validateField(name);\n setFormErrors((prev: Record<keyof T, string>) => {\n const newErrors: Record<keyof T, string> = {...prev};\n if (error) {\n newErrors[name] = error;\n } else {\n delete newErrors[name];\n }\n return newErrors;\n });\n }\n },\n [validateField, validateOnBlur],\n );\n\n // Set multiple touched fields\n const setTouchedFields: (touchedFields: Partial<Record<keyof T, boolean>>) => void = useCallback(\n (touchedFields: Partial<Record<keyof T, boolean>>): void => {\n setFormTouched((prev: Record<keyof T, boolean>) => ({\n ...prev,\n ...touchedFields,\n }));\n },\n [],\n );\n\n // Mark all fields as touched\n const touchAllFields: () => void = useCallback((): void => {\n const allTouched: Record<keyof T, boolean> = fields.reduce(\n (acc: Record<keyof T, boolean>, field: FormField) => {\n acc[field.name as keyof T] = true;\n return acc;\n },\n {} as Record<keyof T, boolean>,\n );\n\n setFormTouched(allTouched);\n\n // Validate all fields\n const validation: ValidationResult = validateForm();\n setFormErrors(validation.errors as Record<keyof T, string>);\n }, [fields, validateForm]);\n\n // Set a field error\n const setError: (name: keyof T, error: string) => void = useCallback((name: keyof T, error: string): void => {\n setFormErrors((prev: Record<keyof T, string>) => ({\n ...prev,\n [name]: error,\n }));\n }, []);\n\n // Set multiple field errors\n const setErrors: (newErrors: Partial<Record<keyof T, string>>) => void = useCallback(\n (newErrors: Partial<Record<keyof T, string>>): void => {\n setFormErrors((prev: Record<keyof T, string>) => ({\n ...prev,\n ...newErrors,\n }));\n },\n [],\n );\n\n // Clear all errors\n const clearErrors: () => void = useCallback((): void => {\n setFormErrors({} as Record<keyof T, string>);\n }, []);\n\n // Reset form to initial state\n const reset: () => void = useCallback((): void => {\n setFormValues({...initialValues} as T);\n setFormTouched({} as Record<keyof T, boolean>);\n setFormErrors({} as Record<keyof T, string>);\n setIsSubmitted(false);\n }, [initialValues]);\n\n // Handle form submission\n const handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void> = useCallback(\n (onSubmit: (values: T) => void | Promise<void>) =>\n async (e?: FormEvent): Promise<void> => {\n if (e) {\n e.preventDefault();\n }\n\n setIsSubmitted(true);\n touchAllFields();\n\n const validation: ValidationResult = validateForm();\n\n if (validation.isValid) {\n await onSubmit(values);\n }\n },\n [values, touchAllFields, validateForm],\n );\n\n // Get field props for easy integration\n const getFieldProps: (name: keyof T) => any = useCallback(\n (name: keyof T) => {\n const fieldConfig: FormField | undefined = getFieldConfig(name);\n\n return {\n error: touched[name] ? errors[name] : undefined,\n name,\n onBlur: (): void => setTouched(name, true),\n onChange: (value: string): void => setValue(name, value),\n required: fieldConfig?.required || false,\n touched: touched[name] || false,\n value: values[name] || '',\n };\n },\n [values, errors, touched, setValue, setTouched, getFieldConfig],\n );\n\n return {\n clearErrors,\n errors,\n getFieldProps,\n handleSubmit,\n isSubmitted,\n isValid,\n reset,\n setError,\n setErrors,\n setTouched,\n setTouchedFields,\n setValue,\n setValues,\n touchAllFields,\n touched,\n validateField,\n validateForm,\n values,\n };\n};\n\nexport default useForm;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useForm.cjs","names":["fieldError: string | null","getFieldConfig: (name: keyof T) => FormField | undefined","validateField: (name: keyof T) => string | null","revalidateTouchedFields: () => void","newErrors: Record<keyof T, string>","freshError: string | null","validateForm: () => ValidationResult","error: string | null","globalErrors: Record<string, string>","isValid: boolean","setValue: (name: keyof T, value: string) => void","setValues: (newValues: Partial<T>) => void","setTouched: (name: keyof T, isTouched?: boolean) => void","setTouchedFields: (touchedFields: Partial<Record<keyof T, boolean>>) => void","touchAllFields: () => void","validation: ValidationResult","setError: (name: keyof T, error: string) => void","setErrors: (newErrors: Partial<Record<keyof T, string>>) => void","clearErrors: () => void","reset: () => void","handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void>","fieldConfig: FormField | undefined"],"sources":["../../../src/hooks/useForm.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {useState, useCallback, useRef, FormEvent} from 'react';\n\n/**\n * Generic form field configuration\n */\nexport interface FormField {\n initialValue?: string;\n name: string;\n required?: boolean;\n validator?: (value: string) => string | null;\n}\n\n/**\n * Form validation result\n */\nexport interface ValidationResult {\n errors: Record<string, string>;\n isValid: boolean;\n}\n\n/**\n * Configuration for the useForm hook\n */\nexport interface UseFormConfig<T extends Record<string, string>> {\n /**\n * Form field definitions\n */\n fields?: FormField[];\n /**\n * Initial form values\n */\n initialValues?: Partial<T>;\n /**\n * Custom required field validation message\n */\n requiredMessage?: string;\n /**\n * When a field has been touched (blurred at least once), re-run validation on every\n * subsequent change so a rendered error clears the moment the value becomes valid.\n *\n * Independent of `validateOnChange`: this only affects fields that have already been\n * blurred, so users don't see errors while initially typing. Default `false` to\n * preserve prior behavior; enable per-form for a \"correct-as-you-type\" UX.\n */\n revalidateOnChangeAfterBlur?: boolean;\n /**\n * Whether to validate on blur (default: true)\n */\n validateOnBlur?: boolean;\n /**\n * Whether to validate on change (default: false). When true, every keystroke\n * triggers validation — including the first, which surfaces errors before the user\n * has had a chance to finish typing. Prefer `revalidateOnChangeAfterBlur` for the\n * common \"clear the error as the user corrects\" UX.\n */\n validateOnChange?: boolean;\n /**\n * Global form validator function\n */\n validator?: (values: T) => Record<string, string>;\n}\n\n/**\n * Pure per-field validator. Kept outside `useForm` so `setValue` can validate against\n * the value it just wrote — inline validation via the closure-based `validateField`\n * inside the hook would read stale `values` (state is committed asynchronously).\n */\nconst computeFieldError = (\n value: string,\n fieldConfig: FormField | undefined,\n requiredMessage: string,\n): string | null => {\n if (fieldConfig?.required && (!value || value.trim() === '')) {\n return requiredMessage;\n }\n if (fieldConfig?.validator) {\n const fieldError: string | null = fieldConfig.validator(value);\n if (fieldError) return fieldError;\n }\n return null;\n};\n\n/**\n * Return type for the useForm hook\n */\nexport interface UseFormReturn<T extends Record<string, string>> {\n /**\n * Clear all errors\n */\n clearErrors: () => void;\n /**\n * Current validation errors\n */\n errors: Record<keyof T, string>;\n /**\n * Get field props for easy integration with form components\n */\n getFieldProps: (name: keyof T) => {\n error: string | undefined;\n name: keyof T;\n onBlur: () => void;\n onChange: (value: string) => void;\n required: boolean;\n touched: boolean;\n value: string;\n };\n /**\n * Handle form submission\n */\n handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void>;\n /**\n * Whether the form has been submitted\n */\n isSubmitted: boolean;\n /**\n * Whether the form is currently valid\n */\n isValid: boolean;\n /**\n * Reset the form to initial values\n */\n reset: () => void;\n /**\n * Set a field error\n */\n setError: (name: keyof T, error: string) => void;\n /**\n * Set multiple field errors\n */\n setErrors: (errors: Partial<Record<keyof T, string>>) => void;\n /**\n * Mark a field as touched\n */\n setTouched: (name: keyof T, touched?: boolean) => void;\n /**\n * Mark multiple fields as touched\n */\n setTouchedFields: (touched: Partial<Record<keyof T, boolean>>) => void;\n /**\n * Set a single field value\n */\n setValue: (name: keyof T, value: string) => void;\n /**\n * Set multiple field values\n */\n setValues: (values: Partial<T>) => void;\n /**\n * Mark all fields as touched\n */\n touchAllFields: () => void;\n /**\n * Fields that have been touched by the user\n */\n touched: Record<keyof T, boolean>;\n /**\n * Validate a single field\n */\n validateField: (name: keyof T) => string | null;\n /**\n * Validate all fields\n */\n validateForm: () => ValidationResult;\n /**\n * Re-run validation for all touched fields that have a client-side config,\n * refreshing stored error strings to the current language.\n */\n revalidateTouchedFields: () => void;\n /**\n * Current form values\n */\n values: T;\n}\n\n/**\n * Generic form hook that provides comprehensive form state management and validation.\n *\n * @template T - The type of form values (must extend Record<string, string>)\n * @param config - Configuration options for the form\n * @returns Form state and methods\n *\n * @example\n * ```tsx\n * interface LoginForm {\n * username: string;\n * password: string;\n * }\n *\n * const {\n * values,\n * touched,\n * errors,\n * isValid,\n * setValue,\n * handleSubmit,\n * getFieldProps\n * } = useForm<LoginForm>({\n * initialValues: { username: '', password: '' },\n * fields: [\n * { name: 'username', required: true },\n * { name: 'password', required: true }\n * ]\n * });\n *\n * const onSubmit = handleSubmit((values) => {\n * console.log('Form submitted:', values);\n * });\n *\n * return (\n * <form onSubmit={onSubmit}>\n * <input {...getFieldProps('username')} />\n * <input {...getFieldProps('password')} type=\"password\" />\n * <button type=\"submit\" disabled={!isValid}>Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends Record<string, string>>(config: UseFormConfig<T> = {}): UseFormReturn<T> => {\n const {\n initialValues = {} as T,\n fields = [],\n validator,\n validateOnChange = false,\n validateOnBlur = true,\n revalidateOnChangeAfterBlur = false,\n requiredMessage = 'This field is required',\n } = config;\n\n // Initialize form state\n const [values, setFormValues] = useState<T>({...initialValues} as T);\n const [touched, setFormTouched] = useState<Record<keyof T, boolean>>({} as Record<keyof T, boolean>);\n const [errors, setFormErrors] = useState<Record<keyof T, string>>({} as Record<keyof T, string>);\n const [isSubmitted, setIsSubmitted] = useState(false);\n\n // Ref to track which fields have client-side validation rules. Errors injected via\n // serErrors are not added here, so revalidateTouchedFields preserves server-side errors.\n const clientErrorFieldRef = useRef(new Set<keyof T>());\n // Get field configuration by name\n const getFieldConfig: (name: keyof T) => FormField | undefined = useCallback(\n (name: keyof T): FormField | undefined => fields.find((field: FormField) => field.name === name),\n [fields],\n );\n\n // Validate a single field against currently-committed state. Used by the blur path\n // (where `values` is already up-to-date) and by `validateForm`.\n const validateField: (name: keyof T) => string | null = useCallback(\n (name: keyof T): string | null => {\n const value: string = values[name] || '';\n const fieldConfig: FormField | undefined = getFieldConfig(name);\n return computeFieldError(value, fieldConfig, requiredMessage);\n },\n [values, getFieldConfig, requiredMessage],\n );\n\n // \"Latest value\" ref so revalidateTouchedFields can read fresh validators without\n // appearing in any effect dep array (avoids exhaustive-deps violations at call sites).\n const validateFieldRef = useRef(validateField);\n validateFieldRef.current = validateField;\n\n // Re-translate all, client-validated error strings on language change.\n // Stable identity ([] deps): uses only refs and the stable state setter.\n const revalidateTouchedFields: () => void = useCallback((): void => {\n setFormErrors((prevErrors: Record<keyof T, string>) => {\n if (Object.keys(prevErrors).length === 0) return prevErrors;\n\n const newErrors: Record<keyof T, string> = {...prevErrors};\n let changed = false;\n\n (Object.keys(prevErrors) as Array<keyof T>).forEach((name: keyof T) => {\n // Skip errors that were not produced by client-side validation - preserve server messages.\n if (!clientErrorFieldRef.current.has(name)) return;\n\n const freshError: string | null = validateFieldRef.current(name);\n if (freshError === prevErrors[name]) return;\n\n changed = true;\n if (freshError) {\n newErrors[name] = freshError;\n } else {\n delete newErrors[name];\n clientErrorFieldRef.current.delete(name);\n }\n });\n\n return changed ? newErrors : prevErrors;\n });\n }, []);\n\n // Validate the entire form\n const validateForm: () => ValidationResult = useCallback((): ValidationResult => {\n const newErrors: Record<keyof T, string> = {} as Record<keyof T, string>;\n\n // Validate individual fields\n fields.forEach((field: FormField) => {\n const error: string | null = validateField(field.name as keyof T);\n if (error) {\n newErrors[field.name as keyof T] = error;\n }\n });\n\n // Run global validator if provided\n if (validator) {\n const globalErrors: Record<string, string> = validator(values);\n Object.keys(globalErrors).forEach((key: string) => {\n if (globalErrors[key]) {\n newErrors[key as keyof T] = globalErrors[key];\n }\n });\n }\n\n return {\n errors: newErrors,\n isValid: Object.keys(newErrors).length === 0,\n };\n }, [fields, validateField, validator, values]);\n\n // Check if form is currently valid\n const isValid: boolean = Object.keys(errors).length === 0;\n\n // Set a single field value.\n //\n // Validation policy:\n // - `validateOnChange: true` → validate on every keystroke.\n // - `revalidateOnChangeAfterBlur: true` → validate only if the field has\n // already been touched (blurred once),\n // so errors clear as the user corrects\n // without appearing while first typing.\n // - both false → no on-change validation.\n //\n // Validation runs against the NEXT value (the string being written) rather than\n // going through `validateField` which reads the stale closed-over `values`. This\n // prevents the one-keystroke-lag bug where the error clears one character late.\n const setValue: (name: keyof T, value: string) => void = useCallback(\n (name: keyof T, value: string): void => {\n setFormValues((prev: T) => ({\n ...prev,\n [name]: value,\n }));\n\n const shouldValidate: boolean = validateOnChange || (revalidateOnChangeAfterBlur && touched[name] === true);\n if (!shouldValidate) {\n return;\n }\n\n const error: string | null = computeFieldError(value, getFieldConfig(name), requiredMessage);\n setFormErrors((prev: Record<keyof T, string>) => {\n const newErrors: Record<keyof T, string> = {...prev};\n if (error) {\n newErrors[name] = error;\n clientErrorFieldRef.current.add(name);\n } else {\n delete newErrors[name];\n clientErrorFieldRef.current.delete(name);\n }\n return newErrors;\n });\n },\n [validateOnChange, revalidateOnChangeAfterBlur, touched, getFieldConfig, requiredMessage],\n );\n\n // Set multiple field values\n const setValues: (newValues: Partial<T>) => void = useCallback((newValues: Partial<T>): void => {\n setFormValues((prev: T) => ({\n ...prev,\n ...newValues,\n }));\n }, []);\n\n // Mark a field as touched\n const setTouched: (name: keyof T, isTouched?: boolean) => void = useCallback(\n (name: keyof T, isTouched = true): void => {\n setFormTouched((prev: Record<keyof T, boolean>) => ({\n ...prev,\n [name]: isTouched,\n }));\n\n // Validate on blur if enabled and field is touched\n if (validateOnBlur && isTouched) {\n const error: string | null = validateField(name);\n setFormErrors((prev: Record<keyof T, string>) => {\n const newErrors: Record<keyof T, string> = {...prev};\n if (error) {\n newErrors[name] = error;\n clientErrorFieldRef.current.add(name);\n } else {\n delete newErrors[name];\n clientErrorFieldRef.current.delete(name);\n }\n return newErrors;\n });\n }\n },\n [validateField, validateOnBlur],\n );\n\n // Set multiple touched fields\n const setTouchedFields: (touchedFields: Partial<Record<keyof T, boolean>>) => void = useCallback(\n (touchedFields: Partial<Record<keyof T, boolean>>): void => {\n setFormTouched((prev: Record<keyof T, boolean>) => ({\n ...prev,\n ...touchedFields,\n }));\n },\n [],\n );\n\n // Mark all fields as touched\n const touchAllFields: () => void = useCallback((): void => {\n const allTouched: Record<keyof T, boolean> = fields.reduce(\n (acc: Record<keyof T, boolean>, field: FormField) => {\n acc[field.name as keyof T] = true;\n return acc;\n },\n {} as Record<keyof T, boolean>,\n );\n\n setFormTouched(allTouched);\n\n // Validate all fields\n const validation: ValidationResult = validateForm();\n setFormErrors(validation.errors as Record<keyof T, string>);\n clientErrorFieldRef.current = new Set(Object.keys(validation.errors) as Array<keyof T>);\n }, [fields, validateForm]);\n\n // Set a field error\n const setError: (name: keyof T, error: string) => void = useCallback((name: keyof T, error: string): void => {\n setFormErrors((prev: Record<keyof T, string>) => ({\n ...prev,\n [name]: error,\n }));\n }, []);\n\n // Set multiple field errors\n const setErrors: (newErrors: Partial<Record<keyof T, string>>) => void = useCallback(\n (newErrors: Partial<Record<keyof T, string>>): void => {\n setFormErrors((prev: Record<keyof T, string>) => ({\n ...prev,\n ...newErrors,\n }));\n },\n [],\n );\n\n // Clear all errors\n const clearErrors: () => void = useCallback((): void => {\n setFormErrors({} as Record<keyof T, string>);\n clientErrorFieldRef.current.clear();\n }, []);\n\n // Reset form to initial state\n const reset: () => void = useCallback((): void => {\n setFormValues({...initialValues} as T);\n setFormTouched({} as Record<keyof T, boolean>);\n setFormErrors({} as Record<keyof T, string>);\n setIsSubmitted(false);\n clientErrorFieldRef.current.clear();\n }, [initialValues]);\n\n // Handle form submission\n const handleSubmit: (onSubmit: (values: T) => void | Promise<void>) => (e?: FormEvent) => Promise<void> = useCallback(\n (onSubmit: (values: T) => void | Promise<void>) =>\n async (e?: FormEvent): Promise<void> => {\n if (e) {\n e.preventDefault();\n }\n\n setIsSubmitted(true);\n touchAllFields();\n\n const validation: ValidationResult = validateForm();\n\n if (validation.isValid) {\n await onSubmit(values);\n }\n },\n [values, touchAllFields, validateForm],\n );\n\n // Get field props for easy integration\n const getFieldProps: (name: keyof T) => any = useCallback(\n (name: keyof T) => {\n const fieldConfig: FormField | undefined = getFieldConfig(name);\n\n return {\n error: touched[name] ? errors[name] : undefined,\n name,\n onBlur: (): void => setTouched(name, true),\n onChange: (value: string): void => setValue(name, value),\n required: fieldConfig?.required || false,\n touched: touched[name] || false,\n value: values[name] || '',\n };\n },\n [values, errors, touched, setValue, setTouched, getFieldConfig],\n );\n\n return {\n clearErrors,\n errors,\n getFieldProps,\n handleSubmit,\n isSubmitted,\n isValid,\n reset,\n revalidateTouchedFields,\n setError,\n setErrors,\n setTouched,\n setTouchedFields,\n setValue,\n setValues,\n touchAllFields,\n touched,\n validateField,\n validateForm,\n values,\n };\n};\n\nexport default useForm;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseRecovery.d.ts","sourceRoot":"","sources":["../../../../../src/components/presentation/auth/Recovery/BaseRecovery.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAM5B,WAAW,EACX,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAgB,SAAS,EAAgE,MAAM,OAAO,CAAC;AAgBjH,OAAsB,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAOvE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAC5C,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,MAAM;QAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC;IAC5E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,4BAA4B,KAAK,IAAI,CAAC;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,4BAA4B,KAAK,IAAI,CAAC;IAChE,YAAY,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,2BAA2B,KAAK,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAChG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC3F;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"BaseRecovery.d.ts","sourceRoot":"","sources":["../../../../../src/components/presentation/auth/Recovery/BaseRecovery.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAM5B,WAAW,EACX,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAgB,SAAS,EAAgE,MAAM,OAAO,CAAC;AAgBjH,OAAsB,EAAC,SAAS,EAAC,MAAM,+BAA+B,CAAC;AAOvE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAC5C,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,MAAM;QAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC;IAC5E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,4BAA4B,KAAK,IAAI,CAAC;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,4BAA4B,KAAK,IAAI,CAAC;IAChE,YAAY,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,2BAA2B,KAAK,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAChG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC3F;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;AAugBD;;;GAGG;AACH,QAAA,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAwBvC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -83,11 +83,11 @@ const BaseRecoveryContent = ({ onInitialize, onSubmit, onError, onFlowChange, on
|
|
|
83
83
|
required: component.required || false,
|
|
84
84
|
validator: (value) => {
|
|
85
85
|
if (component.required && (!value || value.trim() === "")) return t("validations.required.field.error");
|
|
86
|
-
if ((component.type === EmbeddedFlowComponentType.EmailInput || component.variant === "EMAIL") && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return t("field.email.invalid");
|
|
87
86
|
if (ruleValidator && value) {
|
|
88
87
|
const ruleMessage = ruleValidator(value);
|
|
89
88
|
if (ruleMessage) return t(ruleMessage);
|
|
90
89
|
}
|
|
90
|
+
if ((component.type === EmbeddedFlowComponentType.EmailInput || component.variant === "EMAIL") && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return t("field.email.invalid");
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
});
|