@sanvika/auth 2.9.4 → 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 +561 -72
  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",
@@ -314,8 +538,22 @@ function SanvikaAuthProvider({
314
538
  ...opts.headers
315
539
  };
316
540
  const res = await fetch(url, { ...opts, headers });
317
- const data = await res.json();
318
- return { data, success: data.success };
541
+ let data;
542
+ try {
543
+ data = await res.json();
544
+ } catch {
545
+ data = {
546
+ success: false,
547
+ message: `Invalid JSON response (HTTP ${res.status})`
548
+ };
549
+ }
550
+ const bodySuccess = (data == null ? void 0 : data.success) === true;
551
+ return {
552
+ data,
553
+ success: bodySuccess,
554
+ ok: res.ok && (data == null ? void 0 : data.success) !== false,
555
+ status: res.status
556
+ };
319
557
  },
320
558
  [persistence]
321
559
  );
@@ -334,7 +572,8 @@ function SanvikaAuthProvider({
334
572
  clientId,
335
573
  redirectUri,
336
574
  dashboardPath,
337
- authBaseUrl
575
+ authBaseUrl,
576
+ mobileConfig
338
577
  };
339
578
  return /* @__PURE__ */ jsx(SanvikaAuthContext.Provider, { value, children });
340
579
  }
@@ -372,7 +611,6 @@ styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;
372
611
 
373
612
  // SanvikaAccountButton.jsx
374
613
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
375
- var S_AUTH_URL = "https://auth.sanvikaproduction.com";
376
614
  var SanvikaAccountButtonErrorBoundary = class extends Component {
377
615
  constructor(props) {
378
616
  super(props);
@@ -456,7 +694,7 @@ function SanvikaAccountButtonContent({
456
694
  className,
457
695
  layout = "default"
458
696
  }) {
459
- var _a, _b;
697
+ var _a;
460
698
  const isNavbarChip = layout === "navbarChip";
461
699
  const guestBtnClass = `snvk-guestBtn${isNavbarChip ? " snvk-layout-navbarChip" : ""}${className ? ` ${className}` : ""}`;
462
700
  const wrapperClass = `snvk-wrapper${isNavbarChip ? " snvk-layout-navbarChip" : ""}${className ? ` ${className}` : ""}`;
@@ -509,7 +747,7 @@ function SanvikaAccountButtonContent({
509
747
  if (!isAuthenticated || loading) {
510
748
  const { clientId } = auth;
511
749
  const redirectUri = auth.redirectUri || (typeof window !== "undefined" && window.location ? window.location.origin + "/auth/callback" : "");
512
- 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`;
513
751
  return /* @__PURE__ */ jsxs(
514
752
  "button",
515
753
  {
@@ -535,7 +773,9 @@ function SanvikaAccountButtonContent({
535
773
  }
536
774
  );
537
775
  }
538
- 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 }) : "";
539
779
  const imageSrc = !imgError && user.image ? user.image : DEFAULT_AVATAR_SVG;
540
780
  const handleProfileClick = () => {
541
781
  if (onProfileClick) return onProfileClick();
@@ -588,10 +828,7 @@ function SanvikaAccountButtonContent({
588
828
  ),
589
829
  /* @__PURE__ */ jsxs("div", { children: [
590
830
  /* @__PURE__ */ jsx2("div", { className: "snvk-dropdownName", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
591
- /* @__PURE__ */ jsxs("div", { className: "snvk-dropdownMobile", children: [
592
- "+91 ",
593
- user.mobile
594
- ] })
831
+ /* @__PURE__ */ jsx2("div", { className: "snvk-dropdownMobile", children: mobileLabel })
595
832
  ] })
596
833
  ] }),
597
834
  /* @__PURE__ */ jsx2("div", { className: "snvk-divider" }),
@@ -647,9 +884,196 @@ function SanvikaAccountButton(props) {
647
884
  // SanvikaAdminLogin.jsx
648
885
  import { useState as useState3, useEffect as useEffect3 } from "react";
649
886
  import { useRouter } from "next/navigation";
650
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
651
- 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";
652
1075
  var DEVICE_ID_KEY = "sanvika_admin_device_id";
1076
+ var ADMIN_MOBILE_POLICY = CLIENT_MOBILE_POLICIES.IN_10;
653
1077
  function getDeviceId() {
654
1078
  var _a;
655
1079
  if (typeof window === "undefined") return "";
@@ -687,11 +1111,12 @@ function friendlyError(code) {
687
1111
  function SanvikaAdminLogin({
688
1112
  serviceName = "Sanvika",
689
1113
  dashboardPath = "/dashboard/admin",
690
- homePath = "/"
1114
+ homePath = "/",
1115
+ authBaseUrl = DEFAULT_AUTH_URL
691
1116
  }) {
692
1117
  const router = useRouter();
693
- const { isAuthenticated, loading, user, setAuth } = useSanvikaAuth();
694
- const [mobile, setMobile] = useState3("");
1118
+ const { isAuthenticated, loading, user, setAuth, clientId } = useSanvikaAuth();
1119
+ const [phoneValue, setPhoneValue] = useState3(void 0);
695
1120
  const [password, setPassword] = useState3("");
696
1121
  const [error, setError] = useState3("");
697
1122
  const [submitting, setSubmitting] = useState3(false);
@@ -708,24 +1133,26 @@ function SanvikaAdminLogin({
708
1133
  var _a;
709
1134
  e.preventDefault();
710
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
+ }
711
1144
  setSubmitting(true);
712
1145
  try {
713
- const res = await fetch(`${AUTH_BASE_URL}/api/auth/login`, {
714
- method: "POST",
715
- headers: { "Content-Type": "application/json" },
716
- body: JSON.stringify({
717
- mobile,
718
- password,
719
- deviceId: getDeviceId(),
720
- deviceName: "Browser"
721
- })
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
722
1155
  });
723
- const data = await res.json().catch(() => ({}));
724
- if (!data.success) {
725
- setError(friendlyError(data.error));
726
- setPassword("");
727
- return;
728
- }
729
1156
  if (((_a = data.user) == null ? void 0 : _a.role) !== "superadmin") {
730
1157
  setError("This account does not have admin access.");
731
1158
  setPassword("");
@@ -733,8 +1160,9 @@ function SanvikaAdminLogin({
733
1160
  }
734
1161
  setAuth(data.accessToken, data.user);
735
1162
  router.replace(dashboardPath);
736
- } catch {
737
- 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("");
738
1166
  } finally {
739
1167
  setSubmitting(false);
740
1168
  }
@@ -752,7 +1180,7 @@ function SanvikaAdminLogin({
752
1180
  card: {
753
1181
  position: "relative",
754
1182
  width: "100%",
755
- maxWidth: "380px",
1183
+ maxWidth: "420px",
756
1184
  background: "#1e293b",
757
1185
  borderRadius: "16px",
758
1186
  padding: "36px 32px",
@@ -814,34 +1242,39 @@ function SanvikaAdminLogin({
814
1242
  }
815
1243
  };
816
1244
  if (loading || !ready) {
817
- 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" }) }) });
818
1246
  }
819
- return /* @__PURE__ */ jsx3("div", { style: S.page, children: /* @__PURE__ */ jsxs2("div", { style: S.card, children: [
820
- /* @__PURE__ */ jsx3("button", { style: S.closeBtn, onClick: () => router.push(homePath), "aria-label": "Close", children: "\u2715" }),
821
- /* @__PURE__ */ jsx3("div", { style: S.logo, children: "\u{1F6E1}\uFE0F" }),
822
- /* @__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: [
823
1259
  serviceName,
824
1260
  " Admin"
825
1261
  ] }),
826
- /* @__PURE__ */ jsx3("p", { style: S.subtitle, children: "SuperAdmin access \u2014 Sanvika Accounts SSO" }),
827
- /* @__PURE__ */ jsxs2("form", { onSubmit: handleLogin, style: S.form, autoComplete: "off", children: [
828
- /* @__PURE__ */ jsx3("label", { style: S.label, children: "Mobile Number" }),
829
- /* @__PURE__ */ jsx3(
830
- "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,
831
1267
  {
832
- type: "tel",
833
- value: mobile,
834
- onChange: (e) => setMobile(e.target.value.replace(/\D/g, "").slice(0, 10)),
835
- placeholder: "10-digit mobile",
836
- style: S.input,
837
- maxLength: 10,
838
- required: true,
839
- autoFocus: true,
840
- 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"
841
1274
  }
842
1275
  ),
843
- /* @__PURE__ */ jsx3("label", { style: S.label, children: "Password" }),
844
- /* @__PURE__ */ jsx3(
1276
+ /* @__PURE__ */ jsx4("label", { style: S.label, children: "Password" }),
1277
+ /* @__PURE__ */ jsx4(
845
1278
  "input",
846
1279
  {
847
1280
  type: "password",
@@ -853,34 +1286,90 @@ function SanvikaAdminLogin({
853
1286
  autoComplete: "new-password"
854
1287
  }
855
1288
  ),
856
- /* @__PURE__ */ jsx3(
1289
+ /* @__PURE__ */ jsx4(
857
1290
  "button",
858
1291
  {
859
1292
  type: "submit",
860
1293
  style: S.button,
861
- disabled: submitting || mobile.length !== 10 || !password,
1294
+ disabled: submitting || !phoneValue || !password,
862
1295
  children: submitting ? "Signing in\u2026" : "Sign in"
863
1296
  }
864
1297
  )
865
1298
  ] }),
866
- error && /* @__PURE__ */ jsx3("p", { style: S.error, children: error })
1299
+ error && /* @__PURE__ */ jsx4("p", { style: S.error, children: error })
867
1300
  ] }) });
868
1301
  }
1302
+ var ADMIN_LOGIN_API_URL = authApiUrl(AUTH_API.LOGIN, DEFAULT_AUTH_URL);
1303
+
1304
+ // authFetchResult.js
1305
+ function isAuthFetchOk(response) {
1306
+ var _a;
1307
+ if (!response || typeof response !== "object") return false;
1308
+ if (response.ok === true) return true;
1309
+ return Boolean(response.success) || ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.success) === true;
1310
+ }
1311
+ function getAuthFetchMessage(response, fallback = "Request failed") {
1312
+ var _a;
1313
+ const msg = (_a = response == null ? void 0 : response.data) == null ? void 0 : _a.message;
1314
+ return typeof msg === "string" && msg.trim() ? msg.trim() : fallback;
1315
+ }
1316
+ function getAuthFetchPayload(response) {
1317
+ return (response == null ? void 0 : response.data) ?? null;
1318
+ }
1319
+ function getAuthFetchData(response) {
1320
+ const payload = getAuthFetchPayload(response);
1321
+ if (payload && typeof payload === "object" && payload.data !== void 0) {
1322
+ return payload.data;
1323
+ }
1324
+ return payload;
1325
+ }
869
1326
  export {
1327
+ AUTH_API,
1328
+ CLIENT_MOBILE_POLICIES,
870
1329
  DEFAULT_AUTH_URL,
871
1330
  DEFAULT_AVATAR_SVG,
1331
+ DEFAULT_MOBILE_POLICY,
872
1332
  DEVICE_ID_STORAGE_KEY,
1333
+ DIAL_CODE_TO_COUNTRY,
873
1334
  MOBILE_DEVICE_ID_STORAGE_KEY,
1335
+ MOBILE_FORMAT,
1336
+ MobilePolicyPhoneInput,
874
1337
  STORAGE_KEYS,
875
1338
  SanvikaAccountButton,
876
1339
  SanvikaAdminLogin,
877
1340
  SanvikaAuthContext,
878
1341
  SanvikaAuthProvider,
1342
+ authApiUrl,
1343
+ buildRawMobile,
879
1344
  checkMobile,
1345
+ countriesForPolicy,
1346
+ defaultCountryForPolicy,
880
1347
  deviceAwareLogin,
1348
+ dialCodeOptions,
1349
+ displayMobileLabel,
1350
+ formatMobileForDisplay,
1351
+ getAuthFetchData,
1352
+ getAuthFetchMessage,
1353
+ getAuthFetchPayload,
881
1354
  getOrCreateWebDeviceId,
1355
+ invalidMobileMessage,
1356
+ isAuthFetchOk,
1357
+ isCountryLocked,
1358
+ isLegacyIndianIn10,
1359
+ isValidMobileWithPolicy,
1360
+ mergeMobilePolicy,
1361
+ mobilePlaceholder,
1362
+ mobilePolicyToClientConfig,
1363
+ mobilesMatchIdentity,
1364
+ nationalMaxLength,
1365
+ normalizeForSubmit,
1366
+ normalizeMobileWithPolicy,
882
1367
  postLogin,
883
1368
  randomDeviceId,
884
1369
  resolveLogoutDeviceId,
885
- useSanvikaAuth
1370
+ resolveMobileForApi,
1371
+ toIndiaE164FromLegacyIn10,
1372
+ toPhoneInputValue,
1373
+ useSanvikaAuth,
1374
+ validateMobileInput
886
1375
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanvika/auth",
3
- "version": "2.9.4",
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
  },