@sanvika/auth 2.9.5 → 2.10.0

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.
Files changed (2) hide show
  1. package/dist/index.js +518 -70
  2. package/package.json +8 -2
package/dist/index.js CHANGED
@@ -16,11 +16,224 @@ var STORAGE_KEYS = {
16
16
  USER: "sanvika_user"
17
17
  };
18
18
  var DEFAULT_AVATAR_SVG = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='20' fill='%23e5e7eb'/%3E%3Ccircle cx='20' cy='15' r='7' fill='%23adb5bd'/%3E%3Cellipse cx='20' cy='35' rx='12' ry='8' fill='%23adb5bd'/%3E%3C/svg%3E`;
19
+ var AUTH_API = Object.freeze({
20
+ CHECK_MOBILE: "/api/auth/check-mobile",
21
+ LOGIN: "/api/auth/login",
22
+ LOGOUT: "/api/auth/logout",
23
+ VERIFY_TOKEN: "/api/auth/verify-token",
24
+ SEND_OTP: "/api/auth/send-otp",
25
+ VERIFY_OTP: "/api/auth/verify-otp",
26
+ AUTHORIZE: "/api/auth/authorize"
27
+ });
28
+ var CLIENT_MOBILE_POLICIES = Object.freeze({
29
+ freeadpostkaro: {
30
+ format: "SOUTH_ASIA_E164",
31
+ defaultCountryCode: "91",
32
+ allowedDialCodes: ["91", "92", "880"]
33
+ },
34
+ venerationlove: {
35
+ format: "E164",
36
+ defaultCountryCode: "91"
37
+ },
38
+ IN_10: {
39
+ format: "IN_10",
40
+ defaultCountryCode: "91",
41
+ allowedDialCodes: ["91"]
42
+ }
43
+ });
44
+
45
+ // mobilePolicy.js
46
+ var MOBILE_FORMAT = Object.freeze({
47
+ IN_10: "IN_10",
48
+ SOUTH_ASIA_E164: "SOUTH_ASIA_E164",
49
+ E164: "E164"
50
+ });
51
+ var DEFAULT_DIAL_CODES_SOUTH_ASIA = Object.freeze(["91", "92", "880"]);
52
+ var DEFAULT_MOBILE_POLICY = Object.freeze({
53
+ format: MOBILE_FORMAT.IN_10,
54
+ defaultCountryCode: "91",
55
+ allowedDialCodes: ["91"]
56
+ });
57
+ var E164_MIN_DIGITS = 7;
58
+ var E164_MAX_DIGITS = 15;
59
+ function digitsOnly(raw) {
60
+ return String(raw ?? "").replace(/\D/g, "");
61
+ }
62
+ function isValidIndiaNational(n10) {
63
+ return /^[6-9]\d{9}$/.test(n10);
64
+ }
65
+ function extractIndiaNational(d) {
66
+ let n = d;
67
+ if (n.startsWith("91") && n.length >= 12) n = n.slice(2);
68
+ if (n.length === 11 && n.startsWith("0")) n = n.slice(1);
69
+ if (n.length > 10) n = n.slice(-10);
70
+ if (!isValidIndiaNational(n)) return null;
71
+ return n;
72
+ }
73
+ function extractPakistanNational(d) {
74
+ let n = d;
75
+ if (n.startsWith("92") && n.length >= 12) n = n.slice(2);
76
+ if (n.length === 11 && n.startsWith("0")) n = n.slice(1);
77
+ if (n.length > 10) n = n.slice(-10);
78
+ if (n.length !== 10 || n[0] !== "3") return null;
79
+ return n;
80
+ }
81
+ function extractBangladeshNational(d) {
82
+ let n = d;
83
+ if (n.startsWith("880")) n = n.slice(3);
84
+ if (n.length === 11 && n.startsWith("0")) n = n.slice(1);
85
+ if (n.length > 10) n = n.slice(-10);
86
+ if (n.length !== 10 || n[0] !== "1") return null;
87
+ return n;
88
+ }
89
+ function mergeMobilePolicy(raw) {
90
+ if (!raw || typeof raw !== "object") {
91
+ return { ...DEFAULT_MOBILE_POLICY };
92
+ }
93
+ const format = Object.values(MOBILE_FORMAT).includes(raw.format) ? raw.format : DEFAULT_MOBILE_POLICY.format;
94
+ const explicitCodes = Array.isArray(raw.allowedDialCodes) ? raw.allowedDialCodes.map(String).filter(Boolean) : null;
95
+ const allowedDialCodes = (explicitCodes == null ? void 0 : explicitCodes.length) > 0 ? explicitCodes : format === MOBILE_FORMAT.SOUTH_ASIA_E164 ? [...DEFAULT_DIAL_CODES_SOUTH_ASIA] : format === MOBILE_FORMAT.E164 ? [] : ["91"];
96
+ const defaultCountryCode = raw.defaultCountryCode != null ? String(raw.defaultCountryCode).replace(/\D/g, "") : allowedDialCodes[0] || "91";
97
+ return {
98
+ format,
99
+ defaultCountryCode,
100
+ allowedDialCodes
101
+ };
102
+ }
103
+ function normalizeIn10(raw) {
104
+ return extractIndiaNational(digitsOnly(raw));
105
+ }
106
+ function normalizeSouthAsiaE164(raw, policy) {
107
+ var _a;
108
+ let d = digitsOnly(raw);
109
+ if (!d) return null;
110
+ if (d.startsWith("00") && d.length > 4) d = d.slice(2);
111
+ const allowed = ((_a = policy.allowedDialCodes) == null ? void 0 : _a.length) > 0 ? policy.allowedDialCodes : DEFAULT_DIAL_CODES_SOUTH_ASIA;
112
+ const byCode = [...allowed].sort((a, b) => b.length - a.length);
113
+ for (const code of byCode) {
114
+ if (!d.startsWith(code)) continue;
115
+ const rest = d.slice(code.length);
116
+ if (code === "91") {
117
+ const n = extractIndiaNational(rest.length >= 10 ? rest : d);
118
+ if (n) return `91${n}`;
119
+ }
120
+ if (code === "92") {
121
+ const n = extractPakistanNational(rest.length >= 10 ? rest : d);
122
+ if (n) return `92${n}`;
123
+ }
124
+ if (code === "880") {
125
+ const n = extractBangladeshNational(rest.length >= 6 ? rest : d);
126
+ if (n) return `880${n}`;
127
+ }
128
+ }
129
+ if (allowed.includes("91")) {
130
+ const n = extractIndiaNational(d);
131
+ if (n) return `91${n}`;
132
+ }
133
+ if (allowed.includes("92")) {
134
+ const n = extractPakistanNational(d);
135
+ if (n) return `92${n}`;
136
+ }
137
+ if (allowed.includes("880")) {
138
+ const n = extractBangladeshNational(d);
139
+ if (n) return `880${n}`;
140
+ }
141
+ return null;
142
+ }
143
+ function normalizeE164(raw) {
144
+ let d = digitsOnly(raw);
145
+ if (!d) return null;
146
+ if (d.startsWith("00") && d.length > 4) d = d.slice(2);
147
+ if (d.length >= E164_MIN_DIGITS && d.length <= E164_MAX_DIGITS) return d;
148
+ return null;
149
+ }
150
+ function normalizeMobileWithPolicy(raw, policy = DEFAULT_MOBILE_POLICY) {
151
+ const p = mergeMobilePolicy(policy);
152
+ switch (p.format) {
153
+ case MOBILE_FORMAT.SOUTH_ASIA_E164:
154
+ return normalizeSouthAsiaE164(raw, p);
155
+ case MOBILE_FORMAT.E164:
156
+ return normalizeE164(raw);
157
+ case MOBILE_FORMAT.IN_10:
158
+ default:
159
+ return normalizeIn10(raw);
160
+ }
161
+ }
162
+ function isValidMobileWithPolicy(raw, policy = DEFAULT_MOBILE_POLICY) {
163
+ return normalizeMobileWithPolicy(raw, policy) != null;
164
+ }
165
+ function getMobileLookupValues(normalized, policy = DEFAULT_MOBILE_POLICY) {
166
+ if (!normalized) return [];
167
+ const p = mergeMobilePolicy(policy);
168
+ const values = /* @__PURE__ */ new Set([normalized]);
169
+ if (p.format !== MOBILE_FORMAT.IN_10) {
170
+ if (normalized.startsWith("91") && normalized.length === 12) {
171
+ values.add(normalized.slice(2));
172
+ }
173
+ if (/^\d{10}$/.test(normalized)) {
174
+ values.add(`91${normalized}`);
175
+ }
176
+ }
177
+ return [...values];
178
+ }
179
+ function formatMobileForDisplay(normalized, policy = DEFAULT_MOBILE_POLICY) {
180
+ if (!normalized) return "";
181
+ const p = mergeMobilePolicy(policy);
182
+ if (p.format === MOBILE_FORMAT.IN_10) {
183
+ return `+91 ${normalized}`;
184
+ }
185
+ return `+${normalized}`;
186
+ }
187
+ function invalidMobileMessage(policy = DEFAULT_MOBILE_POLICY) {
188
+ const p = mergeMobilePolicy(policy);
189
+ if (p.format === MOBILE_FORMAT.IN_10) {
190
+ return "Valid 10-digit mobile number required.";
191
+ }
192
+ if (p.format === MOBILE_FORMAT.SOUTH_ASIA_E164) {
193
+ return "Valid mobile number required (India, Pakistan, or Bangladesh with country code).";
194
+ }
195
+ return "Valid mobile number with country code required.";
196
+ }
197
+ function mobilePolicyToClientConfig(policy = DEFAULT_MOBILE_POLICY) {
198
+ const p = mergeMobilePolicy(policy);
199
+ return {
200
+ format: p.format,
201
+ defaultCountryCode: p.defaultCountryCode,
202
+ allowedDialCodes: p.allowedDialCodes
203
+ };
204
+ }
205
+ function isLegacyIndianIn10(mobile) {
206
+ const d = digitsOnly(mobile);
207
+ return /^[6-9]\d{9}$/.test(d);
208
+ }
209
+ function toIndiaE164FromLegacyIn10(mobile) {
210
+ const d = digitsOnly(mobile);
211
+ if (!isLegacyIndianIn10(d)) return null;
212
+ if (d.length === 12 && d.startsWith("91")) return d;
213
+ return `91${d}`;
214
+ }
215
+ function mobilesMatchIdentity(stored, candidate) {
216
+ const policy = { format: MOBILE_FORMAT.E164 };
217
+ const a = digitsOnly(stored);
218
+ const b = digitsOnly(candidate);
219
+ if (!a || !b) return false;
220
+ if (a === b) return true;
221
+ const aliasA = new Set(getMobileLookupValues(a, policy));
222
+ return getMobileLookupValues(b, policy).some((v) => aliasA.has(v));
223
+ }
19
224
 
20
225
  // authFlow.js
21
226
  var DEFAULT_AUTH_URL = "https://auth.sanvikaproduction.com";
22
227
  var DEVICE_ID_STORAGE_KEY = "sanvika_deviceId";
23
228
  var MOBILE_DEVICE_ID_STORAGE_KEY = "deviceId";
229
+ function authApiUrl(path, authBaseUrl = DEFAULT_AUTH_URL) {
230
+ return `${String(authBaseUrl).replace(/\/$/, "")}${path}`;
231
+ }
232
+ function resolveMobileForApi(mobile, mobilePolicy) {
233
+ const policy = mergeMobilePolicy(mobilePolicy || DEFAULT_MOBILE_POLICY);
234
+ const raw = String(mobile ?? "").trim();
235
+ return normalizeMobileWithPolicy(raw, policy) || raw;
236
+ }
24
237
  function randomDeviceId() {
25
238
  try {
26
239
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -66,13 +279,15 @@ async function checkMobile({
66
279
  mobile,
67
280
  deviceId,
68
281
  clientId,
69
- callbackUrl
282
+ callbackUrl,
283
+ mobilePolicy
70
284
  }) {
71
- const res = await fetch(`${authBaseUrl}/api/auth/check-mobile`, {
285
+ const normalized = resolveMobileForApi(mobile, mobilePolicy);
286
+ const res = await fetch(authApiUrl(AUTH_API.CHECK_MOBILE, authBaseUrl), {
72
287
  method: "POST",
73
288
  headers: { "Content-Type": "application/json" },
74
289
  body: JSON.stringify({
75
- mobile: String(mobile).trim(),
290
+ mobile: normalized,
76
291
  deviceId,
77
292
  clientId,
78
293
  ...callbackUrl ? { callbackUrl } : {}
@@ -93,10 +308,11 @@ async function postLogin({
93
308
  deviceId,
94
309
  userAgent,
95
310
  clientId,
96
- deviceName
311
+ deviceName,
312
+ mobilePolicy
97
313
  }) {
98
314
  const body = {
99
- mobile: String(mobile).trim(),
315
+ mobile: resolveMobileForApi(mobile, mobilePolicy),
100
316
  deviceId,
101
317
  clientId,
102
318
  userAgent: userAgent || deviceName || "Sanvika App"
@@ -104,7 +320,7 @@ async function postLogin({
104
320
  if (password) {
105
321
  body.password = password;
106
322
  }
107
- const res = await fetch(`${authBaseUrl}/api/auth/login`, {
323
+ const res = await fetch(authApiUrl(AUTH_API.LOGIN, authBaseUrl), {
108
324
  method: "POST",
109
325
  headers: { "Content-Type": "application/json" },
110
326
  body: JSON.stringify(body)
@@ -126,6 +342,7 @@ async function deviceAwareLogin({
126
342
  deviceName,
127
343
  clientId,
128
344
  callbackUrl,
345
+ mobilePolicy,
129
346
  resolveDeviceId
130
347
  }) {
131
348
  const resolvedDeviceId = deviceId || (typeof resolveDeviceId === "function" ? await resolveDeviceId() : getOrCreateWebDeviceId());
@@ -134,7 +351,8 @@ async function deviceAwareLogin({
134
351
  mobile,
135
352
  deviceId: resolvedDeviceId,
136
353
  clientId,
137
- callbackUrl
354
+ callbackUrl,
355
+ mobilePolicy
138
356
  });
139
357
  if (!check.exists) {
140
358
  const err = new Error("Mobile number not registered.");
@@ -154,7 +372,8 @@ async function deviceAwareLogin({
154
372
  deviceId: resolvedDeviceId,
155
373
  userAgent,
156
374
  deviceName,
157
- clientId
375
+ clientId,
376
+ mobilePolicy
158
377
  });
159
378
  }
160
379
 
@@ -184,7 +403,9 @@ function SanvikaAuthProvider({
184
403
  embeddedLogin = false,
185
404
  dashboardPath,
186
405
  authBaseUrl = DEFAULT_AUTH_URL,
187
- persistence: persistenceProp
406
+ persistence: persistenceProp,
407
+ /** Client mobilePolicy — E164 / SOUTH_ASIA_E164 / IN_10 (see CLIENT_MOBILE_POLICIES). */
408
+ mobileConfig
188
409
  }) {
189
410
  const apiCallbackUrl = embeddedLogin ? void 0 : redirectUri;
190
411
  const persistence = useMemo(() => {
@@ -235,10 +456,11 @@ function SanvikaAuthProvider({
235
456
  mobile,
236
457
  deviceId: resolvedDeviceId,
237
458
  clientId,
238
- callbackUrl: apiCallbackUrl
459
+ callbackUrl: apiCallbackUrl,
460
+ mobilePolicy: mobileConfig
239
461
  });
240
462
  },
241
- [authBaseUrl, clientId, apiCallbackUrl]
463
+ [authBaseUrl, clientId, apiCallbackUrl, mobileConfig]
242
464
  );
243
465
  const login = async ({
244
466
  mobile,
@@ -258,7 +480,8 @@ function SanvikaAuthProvider({
258
480
  deviceId: deviceId || randomDeviceId(),
259
481
  userAgent: userAgent || deviceName,
260
482
  clientId,
261
- deviceName
483
+ deviceName,
484
+ mobilePolicy: mobileConfig
262
485
  });
263
486
  } else {
264
487
  data = await deviceAwareLogin({
@@ -270,6 +493,7 @@ function SanvikaAuthProvider({
270
493
  deviceName,
271
494
  clientId,
272
495
  callbackUrl: apiCallbackUrl,
496
+ mobilePolicy: mobileConfig,
273
497
  resolveDeviceId
274
498
  });
275
499
  }
@@ -288,7 +512,7 @@ function SanvikaAuthProvider({
288
512
  const logout = async () => {
289
513
  try {
290
514
  const deviceId = await resolveLogoutDeviceId(persistence);
291
- await fetch(`${authBaseUrl}/api/auth/logout`, {
515
+ await fetch(`${authBaseUrl}${AUTH_API.LOGOUT}`, {
292
516
  method: "POST",
293
517
  headers: {
294
518
  "Content-Type": "application/json",
@@ -348,7 +572,8 @@ function SanvikaAuthProvider({
348
572
  clientId,
349
573
  redirectUri,
350
574
  dashboardPath,
351
- authBaseUrl
575
+ authBaseUrl,
576
+ mobileConfig
352
577
  };
353
578
  return /* @__PURE__ */ jsx(SanvikaAuthContext.Provider, { value, children });
354
579
  }
@@ -386,7 +611,6 @@ styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;
386
611
 
387
612
  // SanvikaAccountButton.jsx
388
613
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
389
- var S_AUTH_URL = "https://auth.sanvikaproduction.com";
390
614
  var SanvikaAccountButtonErrorBoundary = class extends Component {
391
615
  constructor(props) {
392
616
  super(props);
@@ -470,7 +694,7 @@ function SanvikaAccountButtonContent({
470
694
  className,
471
695
  layout = "default"
472
696
  }) {
473
- var _a, _b;
697
+ var _a;
474
698
  const isNavbarChip = layout === "navbarChip";
475
699
  const guestBtnClass = `snvk-guestBtn${isNavbarChip ? " snvk-layout-navbarChip" : ""}${className ? ` ${className}` : ""}`;
476
700
  const wrapperClass = `snvk-wrapper${isNavbarChip ? " snvk-layout-navbarChip" : ""}${className ? ` ${className}` : ""}`;
@@ -523,7 +747,7 @@ function SanvikaAccountButtonContent({
523
747
  if (!isAuthenticated || loading) {
524
748
  const { clientId } = auth;
525
749
  const redirectUri = auth.redirectUri || (typeof window !== "undefined" && window.location ? window.location.origin + "/auth/callback" : "");
526
- const authorizeUrl = clientId && redirectUri ? `${S_AUTH_URL}/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}` : `${S_AUTH_URL}/authorize`;
750
+ const authorizeUrl = clientId && redirectUri ? `${DEFAULT_AUTH_URL}/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}` : `${DEFAULT_AUTH_URL}/authorize`;
527
751
  return /* @__PURE__ */ jsxs(
528
752
  "button",
529
753
  {
@@ -549,7 +773,9 @@ function SanvikaAccountButtonContent({
549
773
  }
550
774
  );
551
775
  }
552
- const displayName = user.firstName || ((_b = user.mobile) == null ? void 0 : _b.slice(-4)) || "Me";
776
+ const mobileDigits = String(user.mobile || "").replace(/\D/g, "");
777
+ const displayName = user.firstName || mobileDigits.slice(-4) || "Me";
778
+ const mobileLabel = user.mobile ? formatMobileForDisplay(user.mobile, { format: MOBILE_FORMAT.E164 }) : "";
553
779
  const imageSrc = !imgError && user.image ? user.image : DEFAULT_AVATAR_SVG;
554
780
  const handleProfileClick = () => {
555
781
  if (onProfileClick) return onProfileClick();
@@ -602,10 +828,7 @@ function SanvikaAccountButtonContent({
602
828
  ),
603
829
  /* @__PURE__ */ jsxs("div", { children: [
604
830
  /* @__PURE__ */ jsx2("div", { className: "snvk-dropdownName", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
605
- /* @__PURE__ */ jsxs("div", { className: "snvk-dropdownMobile", children: [
606
- "+91 ",
607
- user.mobile
608
- ] })
831
+ /* @__PURE__ */ jsx2("div", { className: "snvk-dropdownMobile", children: mobileLabel })
609
832
  ] })
610
833
  ] }),
611
834
  /* @__PURE__ */ jsx2("div", { className: "snvk-divider" }),
@@ -661,9 +884,196 @@ function SanvikaAccountButton(props) {
661
884
  // SanvikaAdminLogin.jsx
662
885
  import { useState as useState3, useEffect as useEffect3 } from "react";
663
886
  import { useRouter } from "next/navigation";
664
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
665
- var AUTH_BASE_URL = "https://auth.sanvikaproduction.com";
887
+
888
+ // MobilePolicyPhoneInput.jsx
889
+ import PhoneInput, { formatPhoneNumberIntl } from "react-phone-number-input";
890
+ import flags from "react-phone-number-input/flags";
891
+ import en from "react-phone-number-input/locale/en.json";
892
+ import { parsePhoneNumber } from "libphonenumber-js/min";
893
+
894
+ // phoneInputPolicy.js
895
+ var DIAL_CODE_TO_COUNTRY = Object.freeze({
896
+ "1": "US",
897
+ "44": "GB",
898
+ "91": "IN",
899
+ "92": "PK",
900
+ "880": "BD",
901
+ "971": "AE",
902
+ "61": "AU",
903
+ "49": "DE",
904
+ "33": "FR"
905
+ });
906
+ var SOUTH_ASIA_DEFAULTS = Object.freeze(["IN", "PK", "BD"]);
907
+ function countriesForPolicy(config) {
908
+ var _a;
909
+ const p = mergeMobilePolicy(config);
910
+ if (p.format === MOBILE_FORMAT.IN_10) return ["IN"];
911
+ if (p.format === MOBILE_FORMAT.SOUTH_ASIA_E164) {
912
+ const codes = ((_a = p.allowedDialCodes) == null ? void 0 : _a.length) > 0 ? p.allowedDialCodes : ["91", "92", "880"];
913
+ const iso = codes.map((c) => DIAL_CODE_TO_COUNTRY[String(c).replace(/\D/g, "")]).filter(Boolean);
914
+ return iso.length ? iso : [...SOUTH_ASIA_DEFAULTS];
915
+ }
916
+ return void 0;
917
+ }
918
+ function defaultCountryForPolicy(config) {
919
+ const p = mergeMobilePolicy(config);
920
+ const dc = String(p.defaultCountryCode || "91").replace(/\D/g, "");
921
+ return DIAL_CODE_TO_COUNTRY[dc] || "IN";
922
+ }
923
+ function isCountryLocked(config) {
924
+ const list = countriesForPolicy(config);
925
+ return Array.isArray(list) && list.length === 1;
926
+ }
927
+ function toPhoneInputValue(raw, config) {
928
+ if (!raw) return void 0;
929
+ const s = String(raw).trim();
930
+ if (s.startsWith("+")) return s;
931
+ const dc = defaultCountryForPolicy(config);
932
+ const digits = s.replace(/\D/g, "");
933
+ if (!digits) return void 0;
934
+ return `+${digits}`;
935
+ }
936
+
937
+ // MobilePolicyPhoneInput.jsx
938
+ import "react-phone-number-input/style.css";
939
+
940
+ // MobilePolicyPhoneInput.css
941
+ styleInject(".sa-phone-field {\n margin-bottom: 16px;\n}\n.sa-phone-input {\n display: block;\n}\n.sa-phone-input .PhoneInput {\n display: flex;\n align-items: stretch;\n border-radius: 8px;\n overflow: hidden;\n border: 1px solid #dddddd;\n background: #ffffff;\n}\n.sa-phone-input--dark .PhoneInput {\n border-color: #333333;\n background: #222222;\n}\n.sa-phone-input .PhoneInputCountry {\n align-self: stretch;\n display: flex;\n align-items: center;\n padding: 0 10px;\n margin: 0;\n border-right: 1px solid #dddddd;\n background: #f5f5f5;\n}\n.sa-phone-input--dark .PhoneInputCountry {\n border-right-color: #333333;\n background: #2a2a2a;\n}\n.sa-phone-input .PhoneInputCountrySelect {\n font-size: 14px;\n cursor: pointer;\n color: #222222;\n max-width: 120px;\n}\n.sa-phone-input--dark .PhoneInputCountrySelect {\n color: #eeeeee;\n background: #2a2a2a;\n}\n.sa-phone-input .PhoneInputCountrySelect:disabled {\n cursor: default;\n opacity: 0.85;\n}\n.sa-phone-input .PhoneInputCountryIcon {\n width: 1.5em;\n height: 1em;\n box-shadow: none;\n border: none;\n}\n.sa-phone-input .PhoneInputCountryIcon--border {\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);\n}\n.sa-phone-input .PhoneInputInput {\n flex: 1;\n min-width: 0;\n border: none;\n outline: none;\n padding: 10px 12px;\n font-size: 15px;\n background: transparent;\n color: #222222;\n}\n.sa-phone-input--dark .PhoneInputInput {\n color: #ffffff;\n}\n.sa-phone-input .PhoneInputInput::placeholder {\n color: #999999;\n}\n.sa-phone-input--dark .PhoneInputInput::placeholder {\n color: #666666;\n}\n.sa-phone-preview {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 10px;\n margin-top: 8px;\n padding: 8px 10px;\n border-radius: 6px;\n font-size: 12px;\n background: #f0f7ff;\n border: 1px solid #cce4ff;\n}\n.sa-phone-preview--dark {\n background: #1a2433;\n border-color: #2a3f5f;\n}\n.sa-phone-preview__label {\n font-weight: 600;\n color: #555555;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-size: 10px;\n}\n.sa-phone-preview--dark .sa-phone-preview__label {\n color: #8899aa;\n}\n.sa-phone-preview__value {\n color: #0984e3;\n font-weight: 500;\n}\n.sa-phone-preview--dark .sa-phone-preview__value {\n color: #88c5ff;\n}\n.sa-phone-preview__e164 {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n monospace;\n color: #333333;\n background: rgba(0, 0, 0, 0.05);\n padding: 2px 6px;\n border-radius: 4px;\n}\n.sa-phone-preview--dark .sa-phone-preview__e164 {\n color: #cccccc;\n background: rgba(255, 255, 255, 0.06);\n}\n.sa-phone-input--modal .PhoneInputCountrySelect {\n max-width: 100px;\n}\n");
942
+
943
+ // MobilePolicyPhoneInput.jsx
944
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
945
+ function MobilePolicyPhoneInput({
946
+ config,
947
+ value,
948
+ onChange,
949
+ theme = "dark",
950
+ inputRef,
951
+ id = "mobile-phone-input",
952
+ placeholder = "Enter phone number"
953
+ }) {
954
+ const policy = mergeMobilePolicy(config);
955
+ const countries = countriesForPolicy(config);
956
+ const defaultCountry = defaultCountryForPolicy(config);
957
+ const countryLocked = isCountryLocked(config);
958
+ let intlFormatted = "";
959
+ let e164Digits = "";
960
+ if (value) {
961
+ try {
962
+ intlFormatted = formatPhoneNumberIntl(value) || value;
963
+ const parsed = parsePhoneNumber(value);
964
+ if (parsed == null ? void 0 : parsed.isValid()) {
965
+ e164Digits = parsed.number.replace("+", "");
966
+ } else {
967
+ e164Digits = normalizeMobileWithPolicy(value, policy) || String(value).replace(/\D/g, "");
968
+ }
969
+ } catch {
970
+ intlFormatted = value;
971
+ }
972
+ }
973
+ return /* @__PURE__ */ jsxs2("div", { className: "sa-phone-field", children: [
974
+ /* @__PURE__ */ jsx3(
975
+ "div",
976
+ {
977
+ className: `sa-phone-input sa-phone-input--${theme}`,
978
+ "data-theme": theme,
979
+ children: /* @__PURE__ */ jsx3(
980
+ PhoneInput,
981
+ {
982
+ id,
983
+ international: true,
984
+ countryCallingCodeEditable: false,
985
+ defaultCountry,
986
+ countries,
987
+ flags,
988
+ labels: en,
989
+ value,
990
+ onChange,
991
+ placeholder,
992
+ smartCaret: true,
993
+ countrySelectProps: {
994
+ unicodeFlags: true,
995
+ ...countryLocked ? { disabled: true } : {}
996
+ },
997
+ numberInputProps: {
998
+ ref: inputRef,
999
+ autoComplete: "tel",
1000
+ inputMode: "tel",
1001
+ "aria-label": "Phone number"
1002
+ }
1003
+ }
1004
+ )
1005
+ }
1006
+ ),
1007
+ value && /* @__PURE__ */ jsxs2("div", { className: `sa-phone-preview sa-phone-preview--${theme}`, children: [
1008
+ /* @__PURE__ */ jsx3("span", { className: "sa-phone-preview__label", children: "Formatted" }),
1009
+ /* @__PURE__ */ jsx3("span", { className: "sa-phone-preview__value", children: intlFormatted }),
1010
+ e164Digits ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1011
+ /* @__PURE__ */ jsx3("span", { className: "sa-phone-preview__label", children: "E.164" }),
1012
+ /* @__PURE__ */ jsx3("span", { className: "sa-phone-preview__e164", children: e164Digits })
1013
+ ] }) : null
1014
+ ] })
1015
+ ] });
1016
+ }
1017
+
1018
+ // mobileInput.js
1019
+ var DIAL_CODE_LABELS = Object.freeze({
1020
+ "91": "+91 India",
1021
+ "92": "+92 Pakistan",
1022
+ "880": "+880 Bangladesh"
1023
+ });
1024
+ function dialCodeOptions(config) {
1025
+ const p = mergeMobilePolicy(config);
1026
+ return (p.allowedDialCodes || ["91"]).map((code) => ({
1027
+ code,
1028
+ label: DIAL_CODE_LABELS[code] || `+${code}`
1029
+ }));
1030
+ }
1031
+ function buildRawMobile({ config, national, dialCode, e164Input }) {
1032
+ const p = mergeMobilePolicy(config);
1033
+ if (p.format === MOBILE_FORMAT.E164) {
1034
+ return String(e164Input ?? national ?? "").trim();
1035
+ }
1036
+ if (p.format === MOBILE_FORMAT.SOUTH_ASIA_E164) {
1037
+ const n = String(national ?? "").replace(/\D/g, "");
1038
+ const dc = String(dialCode || p.defaultCountryCode || "91").replace(/\D/g, "");
1039
+ return `${dc}${n}`;
1040
+ }
1041
+ return String(national ?? "").replace(/\D/g, "");
1042
+ }
1043
+ function validateMobileInput(config, raw) {
1044
+ return isValidMobileWithPolicy(raw, config);
1045
+ }
1046
+ function normalizeForSubmit(config, raw) {
1047
+ return normalizeMobileWithPolicy(raw, config);
1048
+ }
1049
+ function displayMobileLabel(config, rawOrNational) {
1050
+ const p = mergeMobilePolicy(config);
1051
+ if (p.format === MOBILE_FORMAT.IN_10) {
1052
+ return formatMobileForDisplay(rawOrNational, config);
1053
+ }
1054
+ const normalized = normalizeMobileWithPolicy(
1055
+ buildRawMobile({ config, national: rawOrNational, dialCode: p.defaultCountryCode }),
1056
+ config
1057
+ );
1058
+ return formatMobileForDisplay(normalized || rawOrNational, config);
1059
+ }
1060
+ function nationalMaxLength(config) {
1061
+ const p = mergeMobilePolicy(config);
1062
+ if (p.format === MOBILE_FORMAT.IN_10) return 10;
1063
+ if (p.format === MOBILE_FORMAT.SOUTH_ASIA_E164) return 11;
1064
+ return 15;
1065
+ }
1066
+ function mobilePlaceholder(config) {
1067
+ const p = mergeMobilePolicy(config);
1068
+ if (p.format === MOBILE_FORMAT.IN_10) return "Enter mobile number";
1069
+ if (p.format === MOBILE_FORMAT.SOUTH_ASIA_E164) return "National number";
1070
+ return "Include country code, e.g. +1 415 555 0100";
1071
+ }
1072
+
1073
+ // SanvikaAdminLogin.jsx
1074
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
666
1075
  var DEVICE_ID_KEY = "sanvika_admin_device_id";
1076
+ var ADMIN_MOBILE_POLICY = CLIENT_MOBILE_POLICIES.IN_10;
667
1077
  function getDeviceId() {
668
1078
  var _a;
669
1079
  if (typeof window === "undefined") return "";
@@ -701,11 +1111,12 @@ function friendlyError(code) {
701
1111
  function SanvikaAdminLogin({
702
1112
  serviceName = "Sanvika",
703
1113
  dashboardPath = "/dashboard/admin",
704
- homePath = "/"
1114
+ homePath = "/",
1115
+ authBaseUrl = DEFAULT_AUTH_URL
705
1116
  }) {
706
1117
  const router = useRouter();
707
- const { isAuthenticated, loading, user, setAuth } = useSanvikaAuth();
708
- const [mobile, setMobile] = useState3("");
1118
+ const { isAuthenticated, loading, user, setAuth, clientId } = useSanvikaAuth();
1119
+ const [phoneValue, setPhoneValue] = useState3(void 0);
709
1120
  const [password, setPassword] = useState3("");
710
1121
  const [error, setError] = useState3("");
711
1122
  const [submitting, setSubmitting] = useState3(false);
@@ -722,24 +1133,26 @@ function SanvikaAdminLogin({
722
1133
  var _a;
723
1134
  e.preventDefault();
724
1135
  setError("");
1136
+ if (!phoneValue || !validateMobileInput(ADMIN_MOBILE_POLICY, phoneValue)) {
1137
+ setError(invalidMobileMessage(ADMIN_MOBILE_POLICY));
1138
+ return;
1139
+ }
1140
+ if (!password) {
1141
+ setError("Please enter your password.");
1142
+ return;
1143
+ }
725
1144
  setSubmitting(true);
726
1145
  try {
727
- const res = await fetch(`${AUTH_BASE_URL}/api/auth/login`, {
728
- method: "POST",
729
- headers: { "Content-Type": "application/json" },
730
- body: JSON.stringify({
731
- mobile,
732
- password,
733
- deviceId: getDeviceId(),
734
- deviceName: "Browser"
735
- })
1146
+ const mobile = normalizeForSubmit(ADMIN_MOBILE_POLICY, phoneValue);
1147
+ const data = await postLogin({
1148
+ authBaseUrl,
1149
+ mobile,
1150
+ password,
1151
+ deviceId: getDeviceId(),
1152
+ deviceName: "Browser",
1153
+ clientId,
1154
+ mobilePolicy: ADMIN_MOBILE_POLICY
736
1155
  });
737
- const data = await res.json().catch(() => ({}));
738
- if (!data.success) {
739
- setError(friendlyError(data.error));
740
- setPassword("");
741
- return;
742
- }
743
1156
  if (((_a = data.user) == null ? void 0 : _a.role) !== "superadmin") {
744
1157
  setError("This account does not have admin access.");
745
1158
  setPassword("");
@@ -747,8 +1160,9 @@ function SanvikaAdminLogin({
747
1160
  }
748
1161
  setAuth(data.accessToken, data.user);
749
1162
  router.replace(dashboardPath);
750
- } catch {
751
- setError("Could not reach the auth service. Please try again.");
1163
+ } catch (err) {
1164
+ setError(friendlyError(err == null ? void 0 : err.code) || (err == null ? void 0 : err.message) || "Login failed.");
1165
+ setPassword("");
752
1166
  } finally {
753
1167
  setSubmitting(false);
754
1168
  }
@@ -766,7 +1180,7 @@ function SanvikaAdminLogin({
766
1180
  card: {
767
1181
  position: "relative",
768
1182
  width: "100%",
769
- maxWidth: "380px",
1183
+ maxWidth: "420px",
770
1184
  background: "#1e293b",
771
1185
  borderRadius: "16px",
772
1186
  padding: "36px 32px",
@@ -828,34 +1242,39 @@ function SanvikaAdminLogin({
828
1242
  }
829
1243
  };
830
1244
  if (loading || !ready) {
831
- return /* @__PURE__ */ jsx3("div", { style: S.page, children: /* @__PURE__ */ jsx3("div", { style: S.card, children: /* @__PURE__ */ jsx3("p", { style: S.subtitle, children: "Loading\u2026" }) }) });
1245
+ return /* @__PURE__ */ jsx4("div", { style: S.page, children: /* @__PURE__ */ jsx4("div", { style: S.card, children: /* @__PURE__ */ jsx4("p", { style: S.subtitle, children: "Loading\u2026" }) }) });
832
1246
  }
833
- return /* @__PURE__ */ jsx3("div", { style: S.page, children: /* @__PURE__ */ jsxs2("div", { style: S.card, children: [
834
- /* @__PURE__ */ jsx3("button", { style: S.closeBtn, onClick: () => router.push(homePath), "aria-label": "Close", children: "\u2715" }),
835
- /* @__PURE__ */ jsx3("div", { style: S.logo, children: "\u{1F6E1}\uFE0F" }),
836
- /* @__PURE__ */ jsxs2("h1", { style: S.title, children: [
1247
+ return /* @__PURE__ */ jsx4("div", { style: S.page, children: /* @__PURE__ */ jsxs3("div", { style: S.card, children: [
1248
+ /* @__PURE__ */ jsx4(
1249
+ "button",
1250
+ {
1251
+ style: S.closeBtn,
1252
+ onClick: () => router.push(homePath),
1253
+ "aria-label": "Close",
1254
+ children: "\u2715"
1255
+ }
1256
+ ),
1257
+ /* @__PURE__ */ jsx4("div", { style: S.logo, children: "\u{1F6E1}\uFE0F" }),
1258
+ /* @__PURE__ */ jsxs3("h1", { style: S.title, children: [
837
1259
  serviceName,
838
1260
  " Admin"
839
1261
  ] }),
840
- /* @__PURE__ */ jsx3("p", { style: S.subtitle, children: "SuperAdmin access \u2014 Sanvika Accounts SSO" }),
841
- /* @__PURE__ */ jsxs2("form", { onSubmit: handleLogin, style: S.form, autoComplete: "off", children: [
842
- /* @__PURE__ */ jsx3("label", { style: S.label, children: "Mobile Number" }),
843
- /* @__PURE__ */ jsx3(
844
- "input",
1262
+ /* @__PURE__ */ jsx4("p", { style: S.subtitle, children: "SuperAdmin access \u2014 Sanvika Accounts SSO" }),
1263
+ /* @__PURE__ */ jsxs3("form", { onSubmit: handleLogin, style: S.form, autoComplete: "off", children: [
1264
+ /* @__PURE__ */ jsx4("label", { style: S.label, htmlFor: "admin-mobile-phone", children: "Mobile Number" }),
1265
+ /* @__PURE__ */ jsx4(
1266
+ MobilePolicyPhoneInput,
845
1267
  {
846
- type: "tel",
847
- value: mobile,
848
- onChange: (e) => setMobile(e.target.value.replace(/\D/g, "").slice(0, 10)),
849
- placeholder: "10-digit mobile",
850
- style: S.input,
851
- maxLength: 10,
852
- required: true,
853
- autoFocus: true,
854
- autoComplete: "off"
1268
+ config: ADMIN_MOBILE_POLICY,
1269
+ value: phoneValue,
1270
+ onChange: setPhoneValue,
1271
+ theme: "dark",
1272
+ id: "admin-mobile-phone",
1273
+ placeholder: "Enter phone number"
855
1274
  }
856
1275
  ),
857
- /* @__PURE__ */ jsx3("label", { style: S.label, children: "Password" }),
858
- /* @__PURE__ */ jsx3(
1276
+ /* @__PURE__ */ jsx4("label", { style: S.label, children: "Password" }),
1277
+ /* @__PURE__ */ jsx4(
859
1278
  "input",
860
1279
  {
861
1280
  type: "password",
@@ -867,19 +1286,20 @@ function SanvikaAdminLogin({
867
1286
  autoComplete: "new-password"
868
1287
  }
869
1288
  ),
870
- /* @__PURE__ */ jsx3(
1289
+ /* @__PURE__ */ jsx4(
871
1290
  "button",
872
1291
  {
873
1292
  type: "submit",
874
1293
  style: S.button,
875
- disabled: submitting || mobile.length !== 10 || !password,
1294
+ disabled: submitting || !phoneValue || !password,
876
1295
  children: submitting ? "Signing in\u2026" : "Sign in"
877
1296
  }
878
1297
  )
879
1298
  ] }),
880
- error && /* @__PURE__ */ jsx3("p", { style: S.error, children: error })
1299
+ error && /* @__PURE__ */ jsx4("p", { style: S.error, children: error })
881
1300
  ] }) });
882
1301
  }
1302
+ var ADMIN_LOGIN_API_URL = authApiUrl(AUTH_API.LOGIN, DEFAULT_AUTH_URL);
883
1303
 
884
1304
  // authFetchResult.js
885
1305
  function isAuthFetchOk(response) {
@@ -904,24 +1324,52 @@ function getAuthFetchData(response) {
904
1324
  return payload;
905
1325
  }
906
1326
  export {
1327
+ AUTH_API,
1328
+ CLIENT_MOBILE_POLICIES,
907
1329
  DEFAULT_AUTH_URL,
908
1330
  DEFAULT_AVATAR_SVG,
1331
+ DEFAULT_MOBILE_POLICY,
909
1332
  DEVICE_ID_STORAGE_KEY,
1333
+ DIAL_CODE_TO_COUNTRY,
910
1334
  MOBILE_DEVICE_ID_STORAGE_KEY,
1335
+ MOBILE_FORMAT,
1336
+ MobilePolicyPhoneInput,
911
1337
  STORAGE_KEYS,
912
1338
  SanvikaAccountButton,
913
1339
  SanvikaAdminLogin,
914
1340
  SanvikaAuthContext,
915
1341
  SanvikaAuthProvider,
1342
+ authApiUrl,
1343
+ buildRawMobile,
916
1344
  checkMobile,
1345
+ countriesForPolicy,
1346
+ defaultCountryForPolicy,
917
1347
  deviceAwareLogin,
1348
+ dialCodeOptions,
1349
+ displayMobileLabel,
1350
+ formatMobileForDisplay,
918
1351
  getAuthFetchData,
919
1352
  getAuthFetchMessage,
920
1353
  getAuthFetchPayload,
921
1354
  getOrCreateWebDeviceId,
1355
+ invalidMobileMessage,
922
1356
  isAuthFetchOk,
1357
+ isCountryLocked,
1358
+ isLegacyIndianIn10,
1359
+ isValidMobileWithPolicy,
1360
+ mergeMobilePolicy,
1361
+ mobilePlaceholder,
1362
+ mobilePolicyToClientConfig,
1363
+ mobilesMatchIdentity,
1364
+ nationalMaxLength,
1365
+ normalizeForSubmit,
1366
+ normalizeMobileWithPolicy,
923
1367
  postLogin,
924
1368
  randomDeviceId,
925
1369
  resolveLogoutDeviceId,
926
- useSanvikaAuth
1370
+ resolveMobileForApi,
1371
+ toIndiaE164FromLegacyIn10,
1372
+ toPhoneInputValue,
1373
+ useSanvikaAuth,
1374
+ validateMobileInput
927
1375
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanvika/auth",
3
- "version": "2.9.5",
3
+ "version": "2.10.0",
4
4
  "description": "Sanvika Auth SDK — React components/hooks + server-side token verification and user proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,9 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "build": "tsup",
17
- "prepublishOnly": "npm run build"
17
+ "prepublishOnly": "npm run build",
18
+ "test:curl:local": "bash ../../scripts/sdk-curl-auth-test.sh local",
19
+ "test:curl:production": "bash ../../scripts/sdk-curl-auth-test.sh production"
18
20
  },
19
21
  "publishConfig": {
20
22
  "access": "public"
@@ -28,6 +30,10 @@
28
30
  "optional": true
29
31
  }
30
32
  },
33
+ "dependencies": {
34
+ "libphonenumber-js": "^1.13.6",
35
+ "react-phone-number-input": "^3.4.17"
36
+ },
31
37
  "devDependencies": {
32
38
  "tsup": "^8.5.1"
33
39
  },