doct-ui-auth-kit 1.0.18 → 1.0.21

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.
@@ -24,6 +24,21 @@ export interface CheckIdentifierResponse {
24
24
  exists: boolean;
25
25
  userType?: UserType;
26
26
  }
27
+ interface LinkOrMergeBaseParams {
28
+ isLinking: boolean;
29
+ name: string;
30
+ }
31
+ export type LinkOrMergeAccountParams = (LinkOrMergeBaseParams & {
32
+ conflictField: 'email';
33
+ oldEmail?: string;
34
+ newEmail: string;
35
+ }) | (LinkOrMergeBaseParams & {
36
+ conflictField: 'phone';
37
+ oldCountryCode?: string;
38
+ oldPhone?: string;
39
+ newCountryCode: string;
40
+ newPhone: string;
41
+ });
27
42
  /**
28
43
  * Adapter implemented by the consumer to talk to the central auth service.
29
44
  * All methods return promises; the SDK handles loading/error state.
@@ -52,6 +67,13 @@ export interface AuthApiAdapter {
52
67
  refreshSession?(refreshToken: string): Promise<SSOSession | null>;
53
68
  /** Optional: check identifier existence before OTP routing decisions. */
54
69
  checkIdentifier?(params: CheckIdentifierParams): Promise<CheckIdentifierResponse>;
70
+ /**
71
+ * Optional: link or merge an account after profile completion reports a
72
+ * duplicate new email/mobile. Implementations should only send the fields
73
+ * for `params.conflictField`.
74
+ */
75
+ linkOrMergeAccount?(params: LinkOrMergeAccountParams): Promise<SSOSession | void>;
55
76
  /** Optional: clear server-side cookie/session on sign out. */
56
77
  logout?(): Promise<void>;
57
78
  }
79
+ export {};
@@ -24,7 +24,7 @@ export interface SSOSession {
24
24
  /** User type for layout and OTP routing: Indian (+91) vs Foreign (non +91). */
25
25
  export type UserType = 'INDIAN' | 'FOREIGN';
26
26
  /** Flow states for the state machine. */
27
- export type AuthStep = 'CHECKING_SESSION' | 'REPEAT_LOGIN' | 'METHOD_SELECT' | 'PHONE_ENTRY' | 'EMAIL_ENTRY' | 'OTP_VERIFICATION' | 'SIGNUP_DETAILS' | 'PROVIDER_PENDING' | 'AUTHENTICATED';
27
+ export type AuthStep = 'CHECKING_SESSION' | 'REPEAT_LOGIN' | 'METHOD_SELECT' | 'PHONE_ENTRY' | 'EMAIL_ENTRY' | 'OTP_VERIFICATION' | 'SIGNUP_DETAILS' | 'LINK_OR_MERGE' | 'PROVIDER_PENDING' | 'AUTHENTICATED';
28
28
  /** Error codes from the central auth service. */
29
29
  export type AuthErrorCode = 'IDENTIFIER_CONFLICT' | 'OTP_EXPIRED' | 'OTP_INVALID' | 'OTP_RATE_LIMIT' | 'SESSION_EXPIRED' | 'PROVIDER_AUTH_FAILED' | 'NETWORK_ERROR' | 'UNKNOWN';
30
30
  /** Auth error with code and optional message / linked identifier. */
@@ -4,6 +4,20 @@
4
4
  import type { AuthStep, SSOSession, UserType } from './auth-types';
5
5
  /** Identifier type for OTP / profile steps. */
6
6
  export type IdentifierType = 'phone' | 'email';
7
+ export type LinkOrMergeConflictField = IdentifierType;
8
+ export type LinkOrMergeState = {
9
+ conflictField: 'email';
10
+ name: string;
11
+ oldEmail?: string;
12
+ newEmail: string;
13
+ } | {
14
+ conflictField: 'phone';
15
+ name: string;
16
+ oldCountryCode?: string;
17
+ oldPhone?: string;
18
+ newCountryCode: string;
19
+ newPhone: string;
20
+ };
7
21
  /** Full state for the auth flow. */
8
22
  export interface AuthFlowState {
9
23
  step: AuthStep;
@@ -28,6 +42,8 @@ export interface AuthFlowState {
28
42
  /** Foreign flow: signup form data for completeProfile after OTP verify. */
29
43
  pendingFullName: string;
30
44
  pendingEmail: string;
45
+ /** Link/merge details collected after duplicate email/mobile profile errors. */
46
+ linkOrMerge: LinkOrMergeState | null;
31
47
  }
32
48
  /** Pure reducer actions. */
33
49
  export type AuthFlowAction = {
@@ -64,7 +80,9 @@ export type AuthFlowAction = {
64
80
  identifierType?: IdentifierType;
65
81
  identifierValue?: string;
66
82
  maskedRecipient?: string;
67
- } | {
83
+ } | ({
84
+ type: 'SET_STEP_LINK_OR_MERGE';
85
+ } & LinkOrMergeState) | {
68
86
  type: 'SET_STEP_PROVIDER_PENDING';
69
87
  } | {
70
88
  type: 'SET_STEP_AUTHENTICATED';
@@ -75,6 +93,16 @@ export type AuthFlowAction = {
75
93
  type: 'RESET';
76
94
  } | {
77
95
  type: 'SIGN_OUT';
96
+ }
97
+ /**
98
+ * Replace the entire state with a previously-persisted snapshot. Used
99
+ * by the provider to rehydrate the flow on page refresh so the user
100
+ * doesn't lose their step. See `flow-state-storage.ts` and the
101
+ * CHECKING_SESSION fallback in `auth-provider.tsx`.
102
+ */
103
+ | {
104
+ type: 'RESTORE_STATE';
105
+ state: AuthFlowState;
78
106
  };
79
107
  /** Provider config slice exposed to flow for Google/Apple wiring. */
80
108
  export interface AuthFlowProviderConfig {
@@ -116,6 +144,7 @@ export interface AuthFlowActions {
116
144
  provider: 'google' | 'apple';
117
145
  credential: string;
118
146
  }) => void;
147
+ verifyLinkOrMerge: () => Promise<void>;
119
148
  goBack: () => void;
120
149
  reset: () => void;
121
150
  signOut: () => void;
@@ -2,7 +2,7 @@
2
2
  * Props and public types for auth page components.
3
3
  */
4
4
  import type { UserType } from '../auth/auth-types';
5
- import type { IdentifierType } from '../auth/flow';
5
+ import type { IdentifierType, LinkOrMergeState } from '../auth/flow';
6
6
  import type { LoginEntryMode } from './login-form';
7
7
  import type { UseMainAuthPageHandlersOptions } from './main-login';
8
8
  import type { OtpValidateFn, OtpVerificationMode } from './otp-verification';
@@ -85,6 +85,11 @@ export interface SignupPageFullProps extends SignupPageProps {
85
85
  layoutType?: 'withSlider' | 'standalone';
86
86
  variant?: 'desktop' | 'mobile';
87
87
  }
88
+ export interface LinkOrMergePageProps {
89
+ details: LinkOrMergeState;
90
+ onBack?: (() => void) | undefined;
91
+ onVerify: () => Promise<void>;
92
+ }
88
93
  /** Props for OtpVerification (inner form without layout). */
89
94
  export interface OtpVerificationProps {
90
95
  /** Mode: mobile (phone) or email OTP */
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `sessionStorage` persistence for the auth-flow state machine itself
3
+ * (step, userType, pendingEmail, etc.). Lets the user refresh on any
4
+ * step — including non-deep-linkable ones like OTP_VERIFICATION and
5
+ * SIGNUP_DETAILS — without getting bounced to METHOD_SELECT or
6
+ * REPEAT_LOGIN by the router-sync deep-link guard.
7
+ *
8
+ * Cleared on AUTHENTICATED (the provider's success effect) and
9
+ * automatically on tab close (sessionStorage default).
10
+ *
11
+ * Separate from `form-state-storage.ts` so the two concerns can be
12
+ * cleared / inspected independently — form values mirror RHF state,
13
+ * flow state mirrors the reducer.
14
+ */
15
+ import type { AuthFlowState } from '../types';
16
+ /**
17
+ * Read a persisted flow state.
18
+ *
19
+ * Returns null when storage is missing, the value is unparseable, or
20
+ * `step` is unrecognised. When the step is valid, the parsed object is
21
+ * merged on top of `defaultState(step)` — that guarantees every
22
+ * required field is populated even if the snapshot was hand-edited,
23
+ * truncated, or written by an older kit version that didn't know
24
+ * about every field. Without that merge, `RESTORE_STATE` could place
25
+ * `identifierType: undefined` / `identifierValue: undefined` / etc.
26
+ * into the reducer and crash downstream pages.
27
+ */
28
+ export declare function readFlowState(): AuthFlowState | null;
29
+ /** Persist the full flow state. No-op on quota / disabled storage. */
30
+ export declare function writeFlowState(state: AuthFlowState): void;
31
+ /** Wipe the persisted flow state. Called on successful auth. */
32
+ export declare function clearFlowState(): void;
@@ -2,5 +2,6 @@
2
2
  * Reusable utilities used in 2+ places across the auth SDK.
3
3
  */
4
4
  export { buildPhoneRecipient } from './build-phone-recipient';
5
+ export { clearFlowState, readFlowState, writeFlowState, } from './flow-state-storage';
5
6
  export { clearAllFormState, clearFormState, readFormState, writeFormState, } from './form-state-storage';
6
7
  export { setFormErrorsFromZod } from './set-form-errors-from-zod';
package/package.json CHANGED
@@ -1,110 +1,110 @@
1
- {
2
- "name": "doct-ui-auth-kit",
3
- "version": "1.0.18",
4
- "description": "Composable React auth SDK – layouts, login/signup/OTP pages, SSO provider, and auth flow hooks for Docthub",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "files": ["dist", "README.md"],
10
- "keywords": [
11
- "react",
12
- "auth",
13
- "sdk",
14
- "docthub",
15
- "login",
16
- "signup",
17
- "sso",
18
- "otp"
19
- ],
20
- "license": "UNLICENSED",
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/docthub/doct-ui-auth-kit"
24
- },
25
- "scripts": {
26
- "dev": "vite",
27
- "build": "vite build && tsc --build tsconfig.build.json --force && tsc-alias -p tsconfig.build.json",
28
- "prepublishOnly": "npm run build",
29
- "lint": "biome lint .",
30
- "preview": "vite preview",
31
- "format": "biome format --write .",
32
- "lint:biome": "biome lint .",
33
- "check": "biome check .",
34
- "check:fix": "biome check --write .",
35
- "type-check": "tsc --noEmit",
36
- "prepare": "husky",
37
- "storybook": "storybook dev -p 6006",
38
- "build-storybook": "storybook build"
39
- },
40
- "exports": {
41
- ".": {
42
- "types": "./dist/index.d.ts",
43
- "import": "./dist/index.js"
44
- },
45
- "./style.css": "./dist/doct-ui-auth-kit.css",
46
- "./pages": {
47
- "types": "./dist/pages/index.d.ts",
48
- "import": "./dist/pages.js"
49
- }
50
- },
51
- "peerDependencies": {
52
- "docthub-core-components": "2.99.0",
53
- "react": "^19.0.0",
54
- "react-dom": "^19.0.0",
55
- "react-hook-form": "^7.50.0",
56
- "react-icons": "^5.5.0",
57
- "zod": "^4.0.0"
58
- },
59
- "dependencies": {
60
- "clsx": "^2.1.1",
61
- "dayjs": "^1.11.19",
62
- "docthub-core-components": "2.99.0",
63
- "react": "^19.0.0",
64
- "react-dom": "^19.0.0",
65
- "react-hook-form": "^7.50.0",
66
- "react-icons": "^5.5.0",
67
- "react-phone-number-input": "^3.4.14",
68
- "react-router-dom": "^7.13.0",
69
- "zod": "^4.0.0"
70
- },
71
- "devDependencies": {
72
- "@biomejs/biome": "^2.3.14",
73
- "@chromatic-com/storybook": "^5.0.0",
74
- "@eslint/js": "^9.39.1",
75
- "@storybook/addon-a11y": "^10.2.6",
76
- "@storybook/addon-docs": "^10.2.6",
77
- "@storybook/addon-onboarding": "^10.2.6",
78
- "@storybook/addon-vitest": "^10.2.6",
79
- "@storybook/react-vite": "^10.2.6",
80
- "@types/node": "^24.10.1",
81
- "@types/react": "^19.2.5",
82
- "@types/react-dom": "^19.2.3",
83
- "@vitejs/plugin-react": "^5.1.1",
84
- "@vitest/browser-playwright": "^4.0.18",
85
- "@vitest/coverage-v8": "^4.0.18",
86
- "autoprefixer": "^10.4.24",
87
- "babel-plugin-react-compiler": "^1.0.0",
88
- "eslint": "^9.39.1",
89
- "eslint-plugin-react-hooks": "^7.0.1",
90
- "eslint-plugin-react-refresh": "^0.4.24",
91
- "glob": "^13.0.1",
92
- "globals": "^16.5.0",
93
- "husky": "^9.1.7",
94
- "lint-staged": "^16.2.7",
95
- "playwright": "^1.58.1",
96
- "postcss": "^8.5.6",
97
- "react": "^19.2.0",
98
- "react-dom": "^19.2.0",
99
- "storybook": "^10.2.6",
100
- "tailwind-merge": "^3.0.1",
101
- "tailwindcss": "^3.4.16",
102
- "tailwindcss-animate": "^1.0.7",
103
- "tsc-alias": "^1.8.17",
104
- "typescript": "~5.6.2",
105
- "typescript-eslint": "^8.46.4",
106
- "vite": "^7.2.4",
107
- "vite-plugin-dts": "^4.5.0",
108
- "vitest": "^4.0.18"
109
- }
110
- }
1
+ {
2
+ "name": "doct-ui-auth-kit",
3
+ "version": "1.0.21",
4
+ "description": "Composable React auth SDK – layouts, login/signup/OTP pages, SSO provider, and auth flow hooks for Docthub",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": ["dist", "README.md"],
10
+ "keywords": [
11
+ "react",
12
+ "auth",
13
+ "sdk",
14
+ "docthub",
15
+ "login",
16
+ "signup",
17
+ "sso",
18
+ "otp"
19
+ ],
20
+ "license": "UNLICENSED",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/docthub/doct-ui-auth-kit"
24
+ },
25
+ "scripts": {
26
+ "dev": "vite",
27
+ "build": "vite build && tsc --build tsconfig.build.json --force && tsc-alias -p tsconfig.build.json",
28
+ "prepublishOnly": "npm run build",
29
+ "lint": "biome lint .",
30
+ "preview": "vite preview",
31
+ "format": "biome format --write .",
32
+ "lint:biome": "biome lint .",
33
+ "check": "biome check .",
34
+ "check:fix": "biome check --write .",
35
+ "type-check": "tsc --noEmit",
36
+ "prepare": "husky",
37
+ "storybook": "storybook dev -p 6006",
38
+ "build-storybook": "storybook build"
39
+ },
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.ts",
43
+ "import": "./dist/index.js"
44
+ },
45
+ "./style.css": "./dist/doct-ui-auth-kit.css",
46
+ "./pages": {
47
+ "types": "./dist/pages/index.d.ts",
48
+ "import": "./dist/pages.js"
49
+ }
50
+ },
51
+ "peerDependencies": {
52
+ "docthub-core-components": "2.99.0",
53
+ "react": "^19.0.0",
54
+ "react-dom": "^19.0.0",
55
+ "react-hook-form": "^7.50.0",
56
+ "react-icons": "^5.5.0",
57
+ "zod": "^4.0.0"
58
+ },
59
+ "dependencies": {
60
+ "clsx": "^2.1.1",
61
+ "dayjs": "^1.11.19",
62
+ "docthub-core-components": "2.99.0",
63
+ "react": "^19.0.0",
64
+ "react-dom": "^19.0.0",
65
+ "react-hook-form": "^7.50.0",
66
+ "react-icons": "^5.5.0",
67
+ "react-phone-number-input": "^3.4.14",
68
+ "react-router-dom": "^7.13.0",
69
+ "zod": "^4.0.0"
70
+ },
71
+ "devDependencies": {
72
+ "@biomejs/biome": "^2.3.14",
73
+ "@chromatic-com/storybook": "^5.0.0",
74
+ "@eslint/js": "^9.39.1",
75
+ "@storybook/addon-a11y": "^10.2.6",
76
+ "@storybook/addon-docs": "^10.2.6",
77
+ "@storybook/addon-onboarding": "^10.2.6",
78
+ "@storybook/addon-vitest": "^10.2.6",
79
+ "@storybook/react-vite": "^10.2.6",
80
+ "@types/node": "^24.10.1",
81
+ "@types/react": "^19.2.5",
82
+ "@types/react-dom": "^19.2.3",
83
+ "@vitejs/plugin-react": "^5.1.1",
84
+ "@vitest/browser-playwright": "^4.0.18",
85
+ "@vitest/coverage-v8": "^4.0.18",
86
+ "autoprefixer": "^10.4.24",
87
+ "babel-plugin-react-compiler": "^1.0.0",
88
+ "eslint": "^9.39.1",
89
+ "eslint-plugin-react-hooks": "^7.0.1",
90
+ "eslint-plugin-react-refresh": "^0.4.24",
91
+ "glob": "^13.0.1",
92
+ "globals": "^16.5.0",
93
+ "husky": "^9.1.7",
94
+ "lint-staged": "^16.2.7",
95
+ "playwright": "^1.58.1",
96
+ "postcss": "^8.5.6",
97
+ "react": "^19.2.0",
98
+ "react-dom": "^19.2.0",
99
+ "storybook": "^10.2.6",
100
+ "tailwind-merge": "^3.0.1",
101
+ "tailwindcss": "^3.4.16",
102
+ "tailwindcss-animate": "^1.0.7",
103
+ "tsc-alias": "^1.8.17",
104
+ "typescript": "~5.6.2",
105
+ "typescript-eslint": "^8.46.4",
106
+ "vite": "^7.2.4",
107
+ "vite-plugin-dts": "^4.5.0",
108
+ "vitest": "^4.0.18"
109
+ }
110
+ }