Skip to content

Commit 1d44213

Browse files
committed
fix(clerk-js): Refactor encoding/decoding methods in util file
fix(clerk-js): Refactor set state params helper fix(clerk-js): Bump bundle size fix(clerk-js): Change redirect url fix(clerk-js): Move urlStateParams to BaseRouter
1 parent c924290 commit 1d44213

12 files changed

+101
-46
lines changed

packages/clerk-js/src/ui/Components.tsx

+9-21
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { createRoot } from 'react-dom/client';
1515

1616
import { PRESERVED_QUERYSTRING_PARAMS } from '../core/constants';
1717
import { clerkUIErrorDOMElementNotFound } from '../core/errors';
18-
import { getClerkQueryParam, removeClerkQueryParam } from '../utils';
1918
import { CreateOrganization, CreateOrganizationModal } from './components/CreateOrganization';
2019
import { ImpersonationFab } from './components/ImpersonationFab';
2120
import { OrganizationProfile, OrganizationProfileModal } from './components/OrganizationProfile';
@@ -28,7 +27,7 @@ import { EnvironmentProvider, OptionsProvider } from './contexts';
2827
import { CoreClerkContextWrapper } from './contexts/CoreClerkContextWrapper';
2928
import { AppearanceProvider } from './customizables';
3029
import { FlowMetadataProvider, Modal } from './elements';
31-
import { useSafeLayoutEffect } from './hooks';
30+
import { useReadParamState, useSafeLayoutEffect } from './hooks';
3231
import Portal from './portal';
3332
import { VirtualRouter } from './router';
3433
import { InternalThemeProvider } from './styledSystem';
@@ -135,14 +134,13 @@ export type MountComponentRenderer = typeof mountComponentRenderer;
135134

136135
const componentsControls = {} as ComponentControls;
137136

138-
// TODO: move this elsewhere
139137
const componentNodes = Object.freeze({
140138
SignUp: 'signUpModal',
141139
SignIn: 'signInModal',
142140
UserProfile: 'userProfileModal',
143141
OrganizationProfile: 'organizationProfileModal',
144142
CreateOrganization: 'createOrganizationModal',
145-
});
143+
}) as any;
146144

147145
const Components = (props: ComponentsProps) => {
148146
const [state, setState] = React.useState<ComponentsState>({
@@ -158,19 +156,10 @@ const Components = (props: ComponentsProps) => {
158156
const { signInModal, signUpModal, userProfileModal, organizationProfileModal, createOrganizationModal, nodes } =
159157
state;
160158

161-
const [urlState, setUrlState] = React.useState(null);
162-
163-
const readAndRemoveStateParam = () => {
164-
const urlClerkState = getClerkQueryParam('__clerk_state') ?? '';
165-
removeClerkQueryParam('__clerk_state');
166-
return urlClerkState ? JSON.parse(atob(urlClerkState)) : null;
167-
};
168-
169-
const decodedRedirectParams = readAndRemoveStateParam() ?? '';
159+
const { urlStateParam, clearUrlStateParam, decodedRedirectParams } = useReadParamState();
170160

171161
useSafeLayoutEffect(() => {
172162
if (decodedRedirectParams) {
173-
setUrlState(decodedRedirectParams);
174163
setState(s => ({
175164
...s,
176165
[componentNodes[decodedRedirectParams.componentName]]: true,
@@ -207,7 +196,7 @@ const Components = (props: ComponentsProps) => {
207196
};
208197

209198
componentsControls.closeModal = name => {
210-
setUrlState(null);
199+
clearUrlStateParam();
211200
setState(s => ({ ...s, [name + 'Modal']: null }));
212201
};
213202

@@ -230,7 +219,7 @@ const Components = (props: ComponentsProps) => {
230219
<VirtualRouter
231220
preservedParams={PRESERVED_QUERYSTRING_PARAMS}
232221
onExternalNavigate={() => componentsControls.closeModal('signIn')}
233-
startPath={state.options?.startPath || '/sign-in'}
222+
startPath={'/sign-in' + urlStateParam?.path}
234223
>
235224
<SignInModal {...signInModal} />
236225
<SignUpModal
@@ -257,7 +246,7 @@ const Components = (props: ComponentsProps) => {
257246
<VirtualRouter
258247
preservedParams={PRESERVED_QUERYSTRING_PARAMS}
259248
onExternalNavigate={() => componentsControls.closeModal('signUp')}
260-
startPath={state.options?.startPath || '/sign-up'}
249+
startPath={'/sign-up' + urlStateParam?.path}
261250
>
262251
<SignInModal
263252
afterSignInUrl={signUpModal?.afterSignInUrl}
@@ -288,8 +277,7 @@ const Components = (props: ComponentsProps) => {
288277
<VirtualRouter
289278
preservedParams={PRESERVED_QUERYSTRING_PARAMS}
290279
onExternalNavigate={() => componentsControls.closeModal('userProfile')}
291-
// startPath={state.options?.startPath || '/user'}
292-
startPath={`/user${urlState?.path || ''}`}
280+
startPath={'/user' + urlStateParam?.path}
293281
>
294282
<UserProfileModal />
295283
</VirtualRouter>
@@ -315,7 +303,7 @@ const Components = (props: ComponentsProps) => {
315303
<VirtualRouter
316304
preservedParams={PRESERVED_QUERYSTRING_PARAMS}
317305
onExternalNavigate={() => componentsControls.closeModal('organizationProfile')}
318-
startPath={state.options?.startPath || '/organization'}
306+
startPath={'/organization' + urlStateParam?.path}
319307
>
320308
<OrganizationProfileModal {...organizationProfileModal} />
321309
</VirtualRouter>
@@ -341,7 +329,7 @@ const Components = (props: ComponentsProps) => {
341329
<VirtualRouter
342330
preservedParams={PRESERVED_QUERYSTRING_PARAMS}
343331
onExternalNavigate={() => componentsControls.closeModal('createOrganization')}
344-
startPath={state.options?.startPath || '/create-organization'}
332+
startPath={'/create-organization' + urlStateParam?.path}
345333
>
346334
<CreateOrganizationModal {...createOrganizationModal} />
347335
</VirtualRouter>

packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsPage.tsx

+6-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ExternalAccountResource, OAuthStrategy } from '@clerk/types';
22
import React from 'react';
33

4+
import { serializeAndAppendModalState } from '../../../utils';
45
import { useWizard, Wizard } from '../../common';
56
import { useCoreUser, useUserProfileContext } from '../../contexts';
67
import { Col, Image, localizationKeys, Text } from '../../customizables';
@@ -46,24 +47,8 @@ const AddConnectedAccount = () => {
4647
const user = useCoreUser();
4748
const { navigate } = useNavigate();
4849
const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders();
49-
const { currentPath } = useRouter();
50-
const isModal = useUserProfileContext().mode === 'modal';
51-
const componentName = useUserProfileContext().componentName;
52-
53-
const serializeAndAppendModalState = (url: string, isModal: boolean) => {
54-
if (!isModal) {
55-
return url;
56-
}
57-
58-
const redirectParams = {
59-
path: currentPath.replace('/CLERK-ROUTER/VIRTUAL/user', '') || '',
60-
componentName,
61-
};
62-
63-
const encodedRedirectParams = btoa(JSON.stringify(redirectParams));
64-
65-
return `${window.location.href}?__clerk_state=${encodedRedirectParams}`;
66-
};
50+
const { componentName, mode } = useUserProfileContext();
51+
const isModal = mode === 'modal';
6752

6853
const enabledStrategies = strategies.filter(s => s.startsWith('oauth')) as OAuthStrategy[];
6954
const connectedStrategies = user.verifiedExternalAccounts.map(a => 'oauth_' + a.provider) as OAuthStrategy[];
@@ -79,7 +64,9 @@ const AddConnectedAccount = () => {
7964
user
8065
.createExternalAccount({
8166
strategy: strategy,
82-
redirect_url: serializeAndAppendModalState(window.location.href, isModal),
67+
redirect_url: isModal
68+
? serializeAndAppendModalState({ url: window.location.href, currentPath: '#/user', componentName })
69+
: window.location.href,
8370
})
8471
.then(res => {
8572
if (res.verification?.externalVerificationRedirectURL) {

packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OAuthStrategy } from '@clerk/types';
22
import type { ExternalAccountResource } from '@clerk/types/src';
33

4+
import { useRouter } from '../../../ui/router';
45
import { useCoreUser } from '../../contexts';
56
import { Badge, Col, descriptors, Flex, Image, localizationKeys } from '../../customizables';
67
import { ProfileSection, useCardState, UserPreview } from '../../elements';
@@ -43,12 +44,16 @@ const ConnectedAccountAccordion = ({ account }: { account: ExternalAccountResour
4344
const card = useCardState();
4445
const user = useCoreUser();
4546
const { navigate } = useNavigate();
47+
const router = useRouter();
4648
const { providerToDisplayData } = useEnabledThirdPartyProviders();
4749
const error = account.verification?.error?.longMessage;
4850
const label = account.username || account.emailAddress;
51+
const defaultOpen = !!router.urlStateParam.path;
4952

53+
// console.log(defaultOpen, 'defaultOpen ')
5054
return (
5155
<UserProfileAccordion
56+
defaultOpen={defaultOpen}
5257
icon={
5358
<Image
5459
elementDescriptor={[descriptors.providerIcon]}

packages/clerk-js/src/ui/elements/Accordion.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,20 @@ export const AccordionItem = (props: AccordionItemProps) => {
2525
}
2626
};
2727

28-
React.useLayoutEffect(() => {
28+
React.useEffect(() => {
29+
setTimeout(() => {
30+
setIsOpen(defaultOpen);
31+
}, 0);
32+
}, [defaultOpen]);
33+
34+
React.useEffect(() => {
2935
if (scrollOnOpen && isOpen && contentRef.current) {
3036
contentRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
3137
}
32-
}, [isOpen]);
38+
}, [isOpen, scrollOnOpen, contentRef.current]);
3339

3440
return (
35-
<Col>
41+
<Col ref={contentRef}>
3642
<ArrowBlockButton
3743
elementDescriptor={descriptors.accordionTriggerButton}
3844
variant='ghost'
@@ -60,7 +66,6 @@ export const AccordionItem = (props: AccordionItemProps) => {
6066
{isOpen && (
6167
<Col
6268
elementDescriptor={descriptors.accordionContent}
63-
ref={contentRef}
6469
sx={t => ({
6570
animation: `${animations.blockBigIn} ${t.transitionDuration.$slow} ease`,
6671
padding: `${t.space.$4} ${t.space.$8}`,

packages/clerk-js/src/ui/hooks/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './useSearchInput';
1111
export * from './useSafeState';
1212
export * from './useScrollLock';
1313
export * from './useDeepEqualMemo';
14+
export * from './useReadParamState';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
3+
import { readAndRemoveStateParam, removeClerkQueryParam } from '../../utils';
4+
5+
export const useReadParamState = () => {
6+
const contentRef = React.useRef({ path: '', componentName: '' });
7+
const decodedRedirectParams = readAndRemoveStateParam();
8+
9+
React.useLayoutEffect(() => {
10+
if (decodedRedirectParams) {
11+
contentRef.current = decodedRedirectParams;
12+
}
13+
}, []);
14+
15+
const clearUrlStateParam = () => {
16+
contentRef.current = { path: '', componentName: '' };
17+
};
18+
19+
const removeQueryParam = () => removeClerkQueryParam('__clerk_state');
20+
21+
return { urlStateParam: contentRef.current, decodedRedirectParams, clearUrlStateParam, removeQueryParam };
22+
};

packages/clerk-js/src/ui/router/BaseRouter.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33

44
import { getQueryParams, trimTrailingSlash } from '../../utils';
55
import { useCoreClerk } from '../contexts';
6-
import { useWindowEventListener } from '../hooks';
6+
import { useReadParamState, useWindowEventListener } from '../hooks';
77
import { newPaths } from './newPaths';
88
import { match } from './pathToRegexp';
99
import { Route } from './Route';
@@ -33,6 +33,8 @@ export const BaseRouter = ({
3333
children,
3434
}: BaseRouterProps): JSX.Element => {
3535
const { navigate: externalNavigate } = useCoreClerk();
36+
const { urlStateParam, removeQueryParam } = useReadParamState();
37+
3638
const [routeParts, setRouteParts] = React.useState({
3739
path: getPath(),
3840
queryString: getQueryString(),
@@ -78,6 +80,7 @@ export const BaseRouter = ({
7880
}, [currentPath, currentQueryString, getPath, getQueryString]);
7981

8082
useWindowEventListener(refreshEvents, refresh);
83+
removeQueryParam();
8184

8285
// TODO: Look into the real possible types of globalNavigate
8386
const baseNavigate = async (toURL: URL | undefined): Promise<unknown> => {
@@ -129,6 +132,7 @@ export const BaseRouter = ({
129132
resolve: resolve.bind(this),
130133
refresh: refresh.bind(this),
131134
params: {},
135+
urlStateParam: urlStateParam,
132136
}}
133137
>
134138
<Route path={basePath}>{children}</Route>

packages/clerk-js/src/ui/router/Route.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function Route(props: RouteProps): JSX.Element | null {
106106
},
107107
refresh: router.refresh,
108108
params: paramsDict,
109+
urlStateParam: router.urlStateParam,
109110
}}
110111
>
111112
{props.canActivate ? (

packages/clerk-js/src/ui/router/RouteContext.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface RouteContextValue {
1717
queryParams: ParsedQs;
1818
preservedParams?: string[];
1919
getMatchData: (path?: string, index?: boolean) => false | object;
20+
urlStateParam: { path: string; componentName: string };
2021
}
2122

2223
export const RouteContext = React.createContext<RouteContextValue | null>(null);

packages/clerk-js/src/ui/router/__tests__/VirtualRouter.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const ShowPreserved = () => {
3737
const Tester = () => (
3838
<VirtualRouter
3939
preservedParams={['preserved']}
40-
startPath='/'
40+
startPath={'/' + ''}
4141
>
4242
<Route index>
4343
<div id='index'>Index</div>

packages/clerk-js/src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './url';
2222
export * from './web3';
2323
export * from './windowNavigate';
2424
export * from './componentGuards';
25+
export * from './queryStateParams';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getClerkQueryParam } from '../utils';
2+
3+
export const decodeBase64Json = (value: string) => {
4+
return JSON.parse(atob(value));
5+
};
6+
7+
export const encodeBase64Json = (value: { path: string; componentName: string }) => {
8+
return btoa(JSON.stringify(value));
9+
};
10+
11+
export const readAndRemoveStateParam = () => {
12+
const urlClerkState = getClerkQueryParam('__clerk_state') ?? '';
13+
14+
return urlClerkState ? decodeBase64Json(urlClerkState) : null;
15+
};
16+
17+
type SerializeAndAppendModalStateProps = { url: string; currentPath: string; componentName: string };
18+
export const serializeAndAppendModalState = ({
19+
url,
20+
currentPath,
21+
componentName,
22+
}: SerializeAndAppendModalStateProps) => {
23+
const regexPattern = /CLERK-ROUTER\/VIRTUAL\/.*\//;
24+
25+
const redirectParams = {
26+
path: currentPath.replace(regexPattern, '') || '',
27+
componentName,
28+
};
29+
30+
const encodedRedirectParams = encodeBase64Json(redirectParams);
31+
32+
const urlWithParams = new URL(url);
33+
const searchParams = urlWithParams.searchParams;
34+
35+
searchParams.set('__clerk_state', encodedRedirectParams);
36+
37+
urlWithParams.search = searchParams.toString();
38+
39+
return urlWithParams.toString();
40+
};

0 commit comments

Comments
 (0)