diff --git a/package.json b/package.json index c14048f..51346ea 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ ], "private": false, "dependencies": { - "@kinde/js-utils": "0.29.0" + "@kinde/js-utils": "0.29.1-5" }, "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" } diff --git a/src/state/KindeProvider.tsx b/src/state/KindeProvider.tsx index 780caaf..4710eb6 100644 --- a/src/state/KindeProvider.tsx +++ b/src/state/KindeProvider.tsx @@ -225,8 +225,15 @@ export const KindeProvider = ({ activityTimeout, refreshOnFocus = false, }: KindeProviderProps) => { - const mergedCallbacks = { ...defaultCallbacks, ...callbacks }; + // Check for invitation_code synchronously before any hooks/rendering + const params = new URLSearchParams(window.location.search); + const hasInvitationCode = params.has("invitation_code"); + const invitationCodeRef = useRef( + hasInvitationCode ? params.get("invitation_code") : null, + ); + const isRedirectingRef = useRef(hasInvitationCode); + const mergedCallbacks = { ...defaultCallbacks, ...callbacks }; // Track if activity tracking is currently enabled const [isActivityTrackingEnabled, setIsActivityTrackingEnabled] = useState(false); @@ -241,7 +248,11 @@ export const KindeProvider = ({ const handleLocationChange = useCallback( // eslint-disable-next-line @typescript-eslint/no-unused-vars (_loc: Location) => { - if (isActivityTrackingEnabled && activityTimeout && state.isAuthenticated) { + if ( + isActivityTrackingEnabled && + activityTimeout && + state.isAuthenticated + ) { updateActivityTimestamp(); } }, @@ -361,6 +372,8 @@ export const KindeProvider = ({ storageSettings.useInsecureForRefreshToken = useInsecureForRefreshToken; const initRef = useRef(false); + const loginRef = useRef(null); + const redirectInitiatedRef = useRef(false); const login = useCallback( async ( @@ -419,6 +432,29 @@ export const KindeProvider = ({ ], ); + // Store login in ref for immediate access + loginRef.current = login; + + // Handle invitation_code redirect immediately, before any render + useEffect(() => { + if ( + isRedirectingRef.current && + invitationCodeRef.current && + login && + !redirectInitiatedRef.current + ) { + redirectInitiatedRef.current = true; + login({ + prompt: PromptTypes.create, + invitationCode: invitationCodeRef.current, + }).catch((error) => { + console.error("Error processing invitation code:", error); + isRedirectingRef.current = false; + redirectInitiatedRef.current = false; + }); + } + }, [login]); // Include login to ensure it's ready when it becomes available + const register = useCallback( async ( options: LoginMethodParams & { state?: Record } = {}, @@ -749,6 +785,13 @@ export const KindeProvider = ({ const init = useCallback(async () => { if (initRef.current) return; try { + const params = new URLSearchParams(window.location.search); + + // Skip initialization if redirecting for invitation (handled in useEffect above) + if (isRedirectingRef.current) { + return; + } + try { initRef.current = true; await checkAuth({ domain, clientId }); @@ -756,7 +799,6 @@ export const KindeProvider = ({ console.warn("checkAuth failed:", err); setState((v: ProviderState) => ({ ...v, isLoading: false })); } - const params = new URLSearchParams(window.location.search); if (params.has("error")) { const errorCode = params.get("error"); @@ -845,6 +887,11 @@ export const KindeProvider = ({ }; }, [init]); + // Don't render children if redirecting for invitation + if (isRedirectingRef.current && !forceChildrenRender) { + return <>; + } + return forceChildrenRender || initRef.current ? ( {children}