Skip to content

Commit be99136

Browse files
LekoArtsbrkalow
andauthored
feat(elements): Remove hardcoded /sign-in path (#2725)
* feat(shared): Add joinURL helper * chore(elements): Example fixes * feat(elements): Do not hardcode /sign-in Co-authored-by: Bryce Kalow <br.kalow@gmail.com> * chore(elements): Chores * chore(repo): Add empty changeset * chore(elements): Bump package version * chore(elements): Bump package version * chore(repo): Add changeset * chore(repo): Delete changeset+ * Update thirty-kings-trade.md * chore(elements): Use SocialProviderIcon from correct imports * chore(elements): Allow passing required prop to CustomField * fix(elements): Use OTPInput when name="code" * chore(elements): Add some JSDoc comments --------- Co-authored-by: Bryce Kalow <br.kalow@gmail.com>
1 parent 5d6937c commit be99136

File tree

17 files changed

+241
-83
lines changed

17 files changed

+241
-83
lines changed

.changeset/thirty-kings-trade.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/shared': patch
3+
---
4+
5+
Add `joinURL` helper to `@clerk/shared/url`

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/elements/examples/nextjs/app/otp-playground/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AnimatePresence, motion } from 'framer-motion';
77

88
export default function Page() {
99
return (
10-
<SignIn>
10+
<SignIn path='/otp-playground'>
1111
<Start>
1212
<div className='h-dvh flex items-center justify-center bg-neutral-800'>
1313
<Field name='code'>

packages/elements/examples/nextjs/app/page.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ export default function Home() {
5858
<p className='m-0 max-w-[30ch] text-sm opacity-50'>Sign up using Elements</p>
5959
</Link>
6060

61+
<Link
62+
href='/otp-playground'
63+
className='group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30'
64+
>
65+
<h2 className='mb-3 text-2xl font-semibold'>
66+
OTP{' '}
67+
<span className='inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none'>
68+
-&gt;
69+
</span>
70+
</h2>
71+
<p className='m-0 max-w-[30ch] text-sm opacity-50'>OTP Playground</p>
72+
</Link>
73+
6174
<a
6275
href='https://clerk.com/docs/custom-flows/overview#sign-in-flow'
6376
className='group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30'

packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
'use client';
22

33
import { GlobalError, Submit } from '@clerk/elements/common';
4-
import { Continue, Factor, SignIn, SocialProvider, Start, Verification } from '@clerk/elements/sign-in';
4+
import {
5+
Continue,
6+
Factor,
7+
SignIn,
8+
SocialProvider,
9+
SocialProviderIcon,
10+
Start,
11+
Verification,
12+
} from '@clerk/elements/sign-in';
513

614
import { H1, H2, H3, HR as Hr, P } from '@/components/design';
715
import { CustomField, CustomSubmit } from '@/components/form';
816
import { SignInDebug } from '@/components/sign-in-debug';
9-
import { SocialProviderIcon } from '@/components/social-providers';
1017

1118
export default function SignInPage() {
1219
return (

packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
'use client';
22

33
import { GlobalError } from '@clerk/elements/common';
4-
import { Continue, SignUp, SocialProvider, Start, Verification, Verify } from '@clerk/elements/sign-up';
4+
import {
5+
Continue,
6+
SignUp,
7+
SocialProvider,
8+
SocialProviderIcon,
9+
Start,
10+
Verification,
11+
Verify,
12+
} from '@clerk/elements/sign-up';
513

614
import { H1, HR as Hr } from '@/components/design';
715
import { CustomField, CustomSubmit } from '@/components/form';
816
import { SignUpDebug } from '@/components/sign-up-debug';
9-
import { SocialProviderIcon } from '@/components/social-providers';
1017

1118
export default function SignUpPage() {
1219
return (

packages/elements/examples/nextjs/components/form.tsx

+32-30
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,40 @@ function OTPInputSegment({ value, status }: any) {
3232
);
3333
}
3434

35-
export const CustomField = forwardRef<typeof Input, { name: string; label: string }>(function CustomField(
36-
{ name, label },
37-
forwardedRef,
38-
) {
39-
const inputProps =
40-
name === 'code'
41-
? {
42-
render: OTPInputSegment,
43-
}
44-
: {
45-
className: 'bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500',
46-
ref: forwardedRef,
47-
};
35+
export const CustomField = forwardRef<typeof Input, { name: string; label: string; required?: boolean }>(
36+
function CustomField({ name, label, required = false }, forwardedRef) {
37+
const inputProps =
38+
name === 'code'
39+
? {
40+
render: OTPInputSegment,
41+
className: 'flex gap-3',
42+
required,
43+
}
44+
: {
45+
className: 'bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500',
46+
ref: forwardedRef,
47+
required,
48+
};
4849

49-
return (
50-
<ElementsField
51-
name={name}
52-
className='flex flex-col gap-4'
53-
>
54-
<div className='flex gap-4 justify-between items-center'>
55-
<Label>{label}</Label>
56-
<Input
57-
name={name}
58-
{...inputProps}
59-
/>
60-
</div>
50+
return (
51+
<ElementsField
52+
name={name}
53+
className='flex flex-col gap-4'
54+
>
55+
<div className='flex gap-4 justify-between items-center'>
56+
<Label>{label}</Label>
57+
<Input
58+
name={name}
59+
{...inputProps}
60+
/>
61+
</div>
6162

62-
<FieldError className='block text-red-400 font-mono' />
63-
<FieldState>{({ state }) => <pre className='opacity-60 text-xs'>Field state: {state}</pre>}</FieldState>
64-
</ElementsField>
65-
);
66-
});
63+
<FieldError className='block text-red-400 font-mono' />
64+
<FieldState>{({ state }) => <pre className='opacity-60 text-xs'>Field state: {state}</pre>}</FieldState>
65+
</ElementsField>
66+
);
67+
},
68+
);
6769

6870
export const CustomSubmit = forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<'button'>>(
6971
function CustomButton(props, forwardedRef) {

packages/elements/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clerk/elements",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "Clerk Elements",
55
"keywords": [
66
"clerk",

packages/elements/src/internals/machines/sign-in/sign-in.machine.ts

+30-39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ClerkAPIResponseError } from '@clerk/shared/error';
22
import { isClerkAPIResponseError } from '@clerk/shared/error';
3+
import { joinURL } from '@clerk/shared/url';
34
import type {
45
LoadedClerk,
56
OAuthStrategy,
@@ -41,12 +42,14 @@ export interface SignInMachineContext extends MachineContext {
4142
resource: SignInResource | null;
4243
router: ClerkRouter;
4344
thirdPartyProviders: EnabledThirdPartyProviders;
45+
signUpPath: string;
4446
}
4547

4648
export interface SignInMachineInput {
4749
clerk: LoadedClerk;
4850
form: ActorRefFrom<typeof FormMachine>;
4951
router: ClerkRouter;
52+
signUpPath: string;
5053
}
5154

5255
export type SignInMachineTags = 'state:start' | 'state:first-factor' | 'state:second-factor' | 'external';
@@ -115,7 +118,11 @@ export const SignInMachine = setup({
115118
console.error(event.error);
116119
},
117120
navigateTo({ context }, { path }: { path: string }) {
118-
context.router.replace(path);
121+
const resolvedPath = joinURL(context.router.basePath, path);
122+
context.router.replace(resolvedPath);
123+
},
124+
navigateToSignUp({ context }) {
125+
context.router.push(context.signUpPath);
119126
},
120127
raiseFailure: raise(({ event }) => {
121128
assertActorEventError(event);
@@ -143,8 +150,7 @@ export const SignInMachine = setup({
143150
isCurrentFactorPassword: ({ context }) => context.currentFactor?.strategy === 'password',
144151
isCurrentFactorTOTP: ({ context }) => context.currentFactor?.strategy === 'totp',
145152
isCurrentPath: ({ context }, params: { path: string }) => {
146-
const path = params?.path;
147-
return path ? context.router.pathname() === path : false;
153+
return context.router.match(params?.path);
148154
},
149155
isLoggedIn: ({ context }) => Boolean(context.clerk.user),
150156
isSignInComplete: ({ context }) => context?.resource?.status === 'complete',
@@ -179,6 +185,7 @@ export const SignInMachine = setup({
179185
resource: null,
180186
router: input.router,
181187
thirdPartyProviders: getEnabledThirdPartyProviders(input.clerk.__unstable__environment),
188+
signUpPath: input.signUpPath,
182189
}),
183190
initial: 'Init',
184191
on: {
@@ -198,22 +205,22 @@ export const SignInMachine = setup({
198205
},
199206
{
200207
description: 'If the SignIn resource is empty, invoke the sign-in start flow',
201-
guard: or([not('hasSignInResource'), { type: 'isCurrentPath', params: { path: '/sign-in' } }]),
208+
guard: or([not('hasSignInResource'), { type: 'isCurrentPath', params: { path: '/' } }]),
202209
target: 'Start',
203210
},
204211
{
205212
description: 'Go to FirstFactor flow state',
206-
guard: and(['needsFirstFactor', { type: 'isCurrentPath', params: { path: '/sign-in/continue' } }]),
213+
guard: and(['needsFirstFactor', { type: 'isCurrentPath', params: { path: '/continue' } }]),
207214
target: 'FirstFactor',
208215
},
209216
{
210217
description: 'Go to SecondFactor flow state',
211-
guard: and(['needsSecondFactor', { type: 'isCurrentPath', params: { path: '/sign-in/continue' } }]),
218+
guard: and(['needsSecondFactor', { type: 'isCurrentPath', params: { path: '/continue' } }]),
212219
target: 'SecondFactor',
213220
},
214221
{
215222
description: 'Go to SSO Callback state',
216-
guard: { type: 'isCurrentPath', params: { path: '/sign-in/sso-callback' } },
223+
guard: { type: 'isCurrentPath', params: { path: '/sso-callback' } },
217224
target: 'SSOCallback',
218225
},
219226
{
@@ -241,12 +248,20 @@ export const SignInMachine = setup({
241248
Start: {
242249
id: 'Start',
243250
tags: 'state:start',
244-
description: 'The intial state of the sign-in flow.',
251+
description: 'The initial state of the sign-in flow.',
245252
initial: 'AwaitingInput',
246253
on: {
247254
'AUTHENTICATE.OAUTH': '#SignIn.AuthenticatingWithRedirect',
248255
'AUTHENTICATE.SAML': '#SignIn.AuthenticatingWithRedirect',
249256
},
257+
entry: [
258+
{
259+
type: 'navigateTo',
260+
params: {
261+
path: '/',
262+
},
263+
},
264+
],
250265
onDone: [
251266
{
252267
guard: 'isSignInComplete',
@@ -297,7 +312,7 @@ export const SignInMachine = setup({
297312
FirstFactor: {
298313
tags: 'state:first-factor',
299314
initial: 'DeterminingState',
300-
entry: 'assignStartingFirstFactor',
315+
entry: [{ type: 'navigateTo', params: { path: '/continue' } }, 'assignStartingFirstFactor'],
301316
onDone: [
302317
{
303318
guard: 'isSignInComplete',
@@ -385,7 +400,7 @@ export const SignInMachine = setup({
385400
SecondFactor: {
386401
tags: 'state:second-factor',
387402
initial: 'DeterminingState',
388-
entry: 'assignStartingSecondFactor',
403+
entry: [{ type: 'navigateTo', params: { path: '/continue' } }, 'assignStartingSecondFactor'],
389404
onDone: [
390405
{
391406
guard: 'isSignInComplete',
@@ -507,48 +522,24 @@ export const SignInMachine = setup({
507522
'CLERKJS.NAVIGATE.RESET_PASSWORD': '#SignIn.NotImplemented',
508523
'CLERKJS.NAVIGATE.SIGN_IN': {
509524
actions: [
510-
log('Navigating to sign in'),
525+
log('Navigating to sign in root'),
511526
{
512527
type: 'navigateTo',
513528
params: {
514-
path: '/sign-in',
529+
path: '/',
515530
},
516531
},
517532
],
518533
},
519534
'CLERKJS.NAVIGATE.SIGN_UP': {
520-
actions: [
521-
log('Navigating to sign in'),
522-
{
523-
type: 'navigateTo',
524-
params: {
525-
path: '/sign-up',
526-
},
527-
},
528-
],
535+
actions: [log('Navigating to sign up'), 'navigateToSignUp'],
529536
},
530537
'CLERKJS.NAVIGATE.VERIFICATION': {
531-
actions: [
532-
log('Navigating to sign in'),
533-
{
534-
type: 'navigateTo',
535-
params: {
536-
path: '/sign-up',
537-
},
538-
},
539-
],
538+
actions: [log('Navigating to sign in'), 'navigateToSignUp'],
540539
},
541540
'CLERKJS.NAVIGATE.CONTINUE': {
542541
description: 'Redirect to the sign-up flow',
543-
actions: [
544-
log('Navigating to sign up'),
545-
{
546-
type: 'navigateTo',
547-
params: {
548-
path: '/sign-up',
549-
},
550-
},
551-
],
542+
actions: [log('Navigating to sign up'), 'navigateToSignUp'],
552543
},
553544
'CLERKJS.NAVIGATE.*': {
554545
target: '#SignIn.Start',

packages/elements/src/internals/machines/sign-up/sign-up.actors.ts

-2
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ function fieldsToSignUpParams<T extends SignUpCreateParams | SignUpUpdateParams>
118118
): Pick<T, SignUpAdditionalKeys> {
119119
const params: SignUpUpdateParams = {};
120120

121-
// TODO: Determine what takes priority
122-
123121
fields.forEach(({ value }, key) => {
124122
if (isSignUpParam(key) && value !== undefined) {
125123
params[key] = value as string;

packages/elements/src/internals/machines/sign-up/sign-up.machine.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ export const SignUpMachine = setup({
329329
Start: {
330330
id: 'Start',
331331
tags: 'state:start',
332-
description: 'The intial state of the sign-in flow.',
332+
description: 'The intial state of the sign-up flow.',
333333
entry: 'assignThirdPartyProviders',
334334
initial: 'AwaitingInput',
335335
on: {

packages/elements/src/react/common/form/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ const useInput = ({ name: inputName, value: initialValue, type: inputType, ...pa
148148
const shouldBeHidden = false;
149149
const type = inputType ?? determineInputTypeFromName(name);
150150

151-
const Element = inputType === 'otp' ? OTPInput : RadixControl;
151+
const Element = type === 'otp' ? OTPInput : RadixControl;
152152

153153
let props = {};
154154
if (inputType === 'otp') {

packages/elements/src/react/router/router.ts

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export type ClerkHostRouter = {
1414
* Internal Clerk router, used by Clerk components to interact with the host's router.
1515
*/
1616
export type ClerkRouter = {
17+
/**
18+
* The basePath the router is currently mounted on.
19+
*/
20+
basePath: string;
1721
/**
1822
* Creates a child router instance scoped to the provided base path.
1923
*/
@@ -108,5 +112,6 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/
108112
replace,
109113
pathname: router.pathname,
110114
searchParams: router.searchParams,
115+
basePath: normalizedBasePath,
111116
};
112117
}

0 commit comments

Comments
 (0)