+
+ Welcome back! We've sent a temporary code to
+
+
Verify your email
- Please check your email for a verification code...
+
+ We've sent a verification code to .
+
diff --git a/packages/elements/package.json b/packages/elements/package.json
index 9445c801376..c5f51638a90 100644
--- a/packages/elements/package.json
+++ b/packages/elements/package.json
@@ -1,6 +1,6 @@
{
"name": "@clerk/elements",
- "version": "0.1.22",
+ "version": "0.1.23",
"description": "Clerk Elements",
"keywords": [
"clerk",
diff --git a/packages/elements/src/internals/machines/sign-in/selectors/sign-in.selectors.ts b/packages/elements/src/internals/machines/sign-in/selectors/sign-in.selectors.ts
new file mode 100644
index 00000000000..b5e799a9444
--- /dev/null
+++ b/packages/elements/src/internals/machines/sign-in/selectors/sign-in.selectors.ts
@@ -0,0 +1,16 @@
+import type { SignInRouterSnapshot } from '~/internals/machines/sign-in/types';
+import { formatSalutation } from '~/internals/machines/utils/formatters';
+
+export function SignInSafeIdentifierSelector(s: SignInRouterSnapshot): string {
+ return s.context.clerk?.client.signIn.identifier || '';
+}
+
+export function SignInSalutationSelector(s: SignInRouterSnapshot): string {
+ const signIn = s.context.clerk?.client.signIn;
+
+ return formatSalutation({
+ firstName: signIn?.userData?.firstName,
+ identifier: signIn?.identifier,
+ lastName: signIn?.userData?.lastName,
+ });
+}
diff --git a/packages/elements/src/internals/machines/sign-in/types/router.types.ts b/packages/elements/src/internals/machines/sign-in/types/router.types.ts
index e8d0ef3431f..b4efae5b10e 100644
--- a/packages/elements/src/internals/machines/sign-in/types/router.types.ts
+++ b/packages/elements/src/internals/machines/sign-in/types/router.types.ts
@@ -1,5 +1,5 @@
import type { SignInResource } from '@clerk/types';
-import type { AnyActorLogic } from 'xstate';
+import type { AnyActorLogic, MachineSnapshot } from 'xstate';
import type { SignInVerificationFactorUpdateEvent } from '~/internals/machines/sign-in/types';
import type {
@@ -93,3 +93,18 @@ export interface SignInRouterSchema {
events: SignInRouterEvents;
tags: SignInRouterTags;
}
+
+// ---------------------------------- Schema ---------------------------------- //
+
+export type SignInChildren = any; // TODO: Update
+export type SignInOuptut = any; // TODO: Update
+export type SignInStateValue = any; // TODO: Update
+
+export type SignInRouterSnapshot = MachineSnapshot<
+ SignInRouterContext,
+ SignInRouterEvents,
+ SignInChildren,
+ SignInStateValue,
+ SignInRouterTags,
+ SignInOuptut
+>;
diff --git a/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts b/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts
new file mode 100644
index 00000000000..b1ee63bc791
--- /dev/null
+++ b/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts
@@ -0,0 +1,47 @@
+import { formatName, formatSalutation } from '../formatters';
+
+describe('formatName', () => {
+ test('returns undefined when no arguments are provided', () => {
+ expect(formatName()).toBeUndefined();
+ });
+
+ test('returns the titleized version of the single argument', () => {
+ expect(formatName('john')).toBe('John');
+ });
+
+ test('returns the titleized version of multiple arguments joined by space', () => {
+ expect(formatName('john', 'doe')).toBe('John Doe');
+ });
+
+ test('ignores undefined arguments and returns the titleized version of the rest', () => {
+ expect(formatName(undefined, 'john', undefined, 'doe')).toBe('John Doe');
+ });
+});
+
+describe('formatSalutation', () => {
+ test('returns the formatted salutation based on firstName', () => {
+ expect(formatSalutation({ firstName: 'John', lastName: undefined, identifier: undefined })).toBe('John');
+ });
+
+ test('returns the formatted salutation based on lastName', () => {
+ expect(formatSalutation({ firstName: undefined, lastName: 'Doe', identifier: undefined })).toBe('Doe');
+ });
+
+ test('returns the formatted salutation based on identifier', () => {
+ expect(formatSalutation({ firstName: undefined, lastName: undefined, identifier: 'test@clerk.dev' })).toBe(
+ 'test@clerk.dev',
+ );
+ });
+
+ test('returns an empty string when no arguments are provided', () => {
+ expect(formatSalutation({ firstName: undefined, lastName: undefined, identifier: undefined })).toBe('');
+ });
+
+ test('returns the formatted salutation based on firstName and lastName', () => {
+ expect(formatSalutation({ firstName: 'John', lastName: 'Doe', identifier: undefined })).toBe('John');
+ });
+
+ test('returns the formatted salutation based on firstName, lastName, and identifier', () => {
+ expect(formatSalutation({ firstName: 'John', lastName: 'Doe', identifier: 'test@clerk.dev' })).toBe('John');
+ });
+});
diff --git a/packages/elements/src/internals/machines/utils/formatters.ts b/packages/elements/src/internals/machines/utils/formatters.ts
new file mode 100644
index 00000000000..13b41c8b957
--- /dev/null
+++ b/packages/elements/src/internals/machines/utils/formatters.ts
@@ -0,0 +1,26 @@
+import { titleize } from '@clerk/shared';
+
+// TODO: ideally the derivation of these values lives in FAPI and comes back directly from the API
+
+export function formatName(...args: (string | undefined)[]): string | undefined {
+ switch (args.length) {
+ case 0:
+ return undefined;
+ case 1:
+ return titleize(args[0]);
+ default:
+ return args.filter(Boolean).map(titleize).join(' ');
+ }
+}
+
+export function formatSalutation({
+ firstName,
+ lastName,
+ identifier,
+}: {
+ firstName: string | undefined;
+ lastName: string | undefined;
+ identifier: string | undefined | null;
+}): string {
+ return (firstName && formatName(firstName)) || (lastName && formatName(lastName)) || identifier || '';
+}
diff --git a/packages/elements/src/react/sign-in/identifiers.tsx b/packages/elements/src/react/sign-in/identifiers.tsx
new file mode 100644
index 00000000000..64d4509de8f
--- /dev/null
+++ b/packages/elements/src/react/sign-in/identifiers.tsx
@@ -0,0 +1,14 @@
+import {
+ SignInSafeIdentifierSelector,
+ SignInSalutationSelector,
+} from '~/internals/machines/sign-in/selectors/sign-in.selectors';
+
+import { SignInRouterCtx } from './context';
+
+export function SignInSafeIdentifier(): string {
+ return SignInRouterCtx.useSelector(SignInSafeIdentifierSelector);
+}
+
+export function SignInSalutation(): string {
+ return SignInRouterCtx.useSelector(SignInSalutationSelector);
+}
diff --git a/packages/elements/src/react/sign-in/index.ts b/packages/elements/src/react/sign-in/index.ts
index b5568e92aa5..3b0d62212fe 100644
--- a/packages/elements/src/react/sign-in/index.ts
+++ b/packages/elements/src/react/sign-in/index.ts
@@ -14,6 +14,8 @@ export {
SignInVerification as Verification,
} from './verifications';
+export { SignInSafeIdentifier as SafeIdentifier, SignInSalutation as Salutation } from './identifiers';
+
export { useIsLoading_unstable } from './hooks/use-loading.hook';
/** @internal Internal use only */