@sanvika/auth 1.0.19 → 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.
- package/dist/index.js +136 -325
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1,105 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// SanvikaAuthProvider.jsx
|
|
4
|
-
import {
|
|
5
|
-
createContext,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
useState,
|
|
9
|
-
useCallback,
|
|
10
|
-
useRef
|
|
11
|
-
} from "react";
|
|
4
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
12
5
|
|
|
13
6
|
// constants.js
|
|
14
7
|
var STORAGE_KEYS = {
|
|
15
8
|
ACCESS_TOKEN: "sanvika_access_token",
|
|
16
|
-
USER: "sanvika_user"
|
|
17
|
-
STATE: "sanvika_oauth_state",
|
|
18
|
-
// CSRF state for OAuth flow
|
|
19
|
-
RETURN_PATH: "sanvika_return_path"
|
|
20
|
-
// Page to redirect back after login
|
|
9
|
+
USER: "sanvika_user"
|
|
21
10
|
};
|
|
22
11
|
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`;
|
|
23
12
|
|
|
24
|
-
// tokenRefreshUtils.js
|
|
25
|
-
function decodeToken(token) {
|
|
26
|
-
try {
|
|
27
|
-
const base64Url = token.split(".")[1];
|
|
28
|
-
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
29
|
-
const jsonPayload = decodeURIComponent(
|
|
30
|
-
atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
31
|
-
);
|
|
32
|
-
return JSON.parse(jsonPayload);
|
|
33
|
-
} catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function ensureTokenFresh(token, thresholdSeconds = 300) {
|
|
38
|
-
if (!token) return null;
|
|
39
|
-
const decoded = decodeToken(token);
|
|
40
|
-
if (!decoded || !decoded.exp) return token;
|
|
41
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
42
|
-
const secondsUntilExpiry = decoded.exp - nowSeconds;
|
|
43
|
-
if (secondsUntilExpiry > thresholdSeconds) {
|
|
44
|
-
return token;
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
const refreshRes = await fetch("/api/auth/refresh", {
|
|
48
|
-
method: "POST",
|
|
49
|
-
headers: { "Content-Type": "application/json" },
|
|
50
|
-
credentials: "include"
|
|
51
|
-
// Send httpOnly refresh token cookie (same domain)
|
|
52
|
-
});
|
|
53
|
-
if (!refreshRes.ok) {
|
|
54
|
-
console.warn("[TokenRefresh] Refresh failed:", refreshRes.status);
|
|
55
|
-
return token;
|
|
56
|
-
}
|
|
57
|
-
const data = await refreshRes.json();
|
|
58
|
-
if (data.success && data.accessToken) {
|
|
59
|
-
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, data.accessToken);
|
|
60
|
-
return data.accessToken;
|
|
61
|
-
}
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.warn("[TokenRefresh] Error refreshing token:", err.message);
|
|
64
|
-
}
|
|
65
|
-
return token;
|
|
66
|
-
}
|
|
67
|
-
function scheduleTokenRefresh(token, onRefresh) {
|
|
68
|
-
if (!token) return () => {
|
|
69
|
-
};
|
|
70
|
-
const decoded = decodeToken(token);
|
|
71
|
-
if (!decoded || !decoded.exp) return () => {
|
|
72
|
-
};
|
|
73
|
-
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
74
|
-
const secondsUntilExpiry = decoded.exp - nowSeconds;
|
|
75
|
-
const refreshAt = Math.max(60, Math.floor(secondsUntilExpiry * 0.75));
|
|
76
|
-
const timeoutId = setTimeout(async () => {
|
|
77
|
-
try {
|
|
78
|
-
const freshToken = await ensureTokenFresh(token, 0);
|
|
79
|
-
if (freshToken !== token && onRefresh) {
|
|
80
|
-
onRefresh(freshToken);
|
|
81
|
-
}
|
|
82
|
-
} catch (err) {
|
|
83
|
-
console.warn("[TokenRefresh] Auto-refresh failed:", err.message);
|
|
84
|
-
}
|
|
85
|
-
}, refreshAt * 1e3);
|
|
86
|
-
return () => clearTimeout(timeoutId);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
13
|
// SanvikaAuthProvider.jsx
|
|
90
14
|
import { jsx } from "react/jsx-runtime";
|
|
91
15
|
var SA_URL = "https://accounts.sanvikaproduction.com";
|
|
92
16
|
var SanvikaAuthContext = createContext(null);
|
|
93
|
-
function SanvikaAuthProvider({
|
|
94
|
-
children,
|
|
95
|
-
clientId,
|
|
96
|
-
redirectUri,
|
|
97
|
-
dashboardPath = "/dashboard"
|
|
98
|
-
}) {
|
|
17
|
+
function SanvikaAuthProvider({ children }) {
|
|
99
18
|
const [user, setUser] = useState(null);
|
|
100
19
|
const [accessToken, setToken] = useState(null);
|
|
101
20
|
const [loading, setLoading] = useState(true);
|
|
102
|
-
const refreshCleanupRef = useRef(null);
|
|
103
21
|
useEffect(() => {
|
|
104
22
|
try {
|
|
105
23
|
const storedToken = localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
@@ -108,136 +26,69 @@ function SanvikaAuthProvider({
|
|
|
108
26
|
setToken(storedToken);
|
|
109
27
|
setUser(JSON.parse(storedUser));
|
|
110
28
|
}
|
|
111
|
-
} catch {
|
|
112
|
-
|
|
113
|
-
localStorage.removeItem(STORAGE_KEYS.USER);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error("[SanvikaAuth] Failed to load from localStorage:", e);
|
|
114
31
|
} finally {
|
|
115
32
|
setLoading(false);
|
|
116
33
|
}
|
|
117
34
|
}, []);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} catch {
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
window.addEventListener("storage", onStorage);
|
|
134
|
-
return () => window.removeEventListener("storage", onStorage);
|
|
135
|
-
}, []);
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
if (!accessToken) {
|
|
138
|
-
if (refreshCleanupRef.current) {
|
|
139
|
-
refreshCleanupRef.current();
|
|
140
|
-
refreshCleanupRef.current = null;
|
|
141
|
-
}
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const cleanup = scheduleTokenRefresh(accessToken, (freshToken) => {
|
|
145
|
-
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, freshToken);
|
|
146
|
-
setToken(freshToken);
|
|
147
|
-
window.dispatchEvent(
|
|
148
|
-
new StorageEvent("storage", {
|
|
149
|
-
key: STORAGE_KEYS.ACCESS_TOKEN,
|
|
150
|
-
newValue: freshToken,
|
|
151
|
-
oldValue: accessToken
|
|
152
|
-
})
|
|
153
|
-
);
|
|
35
|
+
const login = async ({ mobile, password, deviceId, deviceName }) => {
|
|
36
|
+
const response = await fetch(`${SA_URL}/api/auth/login`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
credentials: "include",
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
mobile,
|
|
42
|
+
password,
|
|
43
|
+
deviceId: deviceId || crypto.randomUUID(),
|
|
44
|
+
deviceName: deviceName || "Browser"
|
|
45
|
+
})
|
|
154
46
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const logout = useCallback(async () => {
|
|
167
|
-
const token = localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
168
|
-
if (token) {
|
|
169
|
-
fetch(`${SA_URL}/api/auth/logout`, {
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
if (!data.success) throw new Error(data.error);
|
|
49
|
+
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, data.accessToken);
|
|
50
|
+
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(data.user));
|
|
51
|
+
setToken(data.accessToken);
|
|
52
|
+
setUser(data.user);
|
|
53
|
+
return data;
|
|
54
|
+
};
|
|
55
|
+
const logout = async (logoutAll = false) => {
|
|
56
|
+
try {
|
|
57
|
+
await fetch(`${SA_URL}/api/auth/logout`, {
|
|
170
58
|
method: "POST",
|
|
171
59
|
headers: {
|
|
172
60
|
"Content-Type": "application/json",
|
|
173
|
-
Authorization: `Bearer ${
|
|
61
|
+
Authorization: `Bearer ${accessToken}`
|
|
174
62
|
},
|
|
175
|
-
|
|
176
|
-
|
|
63
|
+
credentials: "include",
|
|
64
|
+
body: JSON.stringify({ logout_all: logoutAll })
|
|
177
65
|
});
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error("[SanvikaAuth] Logout API error:", e);
|
|
68
|
+
} finally {
|
|
69
|
+
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
70
|
+
localStorage.removeItem(STORAGE_KEYS.USER);
|
|
71
|
+
setToken(null);
|
|
72
|
+
setUser(null);
|
|
178
73
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return merged;
|
|
190
|
-
});
|
|
191
|
-
}, []);
|
|
192
|
-
const redirectToLogin = useCallback(
|
|
193
|
-
(returnPath) => {
|
|
194
|
-
if (returnPath) {
|
|
195
|
-
localStorage.setItem(STORAGE_KEYS.RETURN_PATH, returnPath);
|
|
196
|
-
}
|
|
197
|
-
const state = Math.random().toString(36).slice(2);
|
|
198
|
-
localStorage.setItem(STORAGE_KEYS.STATE, state);
|
|
199
|
-
const url = new URL(`${SA_URL}/authorize`);
|
|
200
|
-
url.searchParams.set("client_id", clientId);
|
|
201
|
-
url.searchParams.set("redirect_uri", redirectUri);
|
|
202
|
-
url.searchParams.set("response_type", "code");
|
|
203
|
-
url.searchParams.set("state", state);
|
|
204
|
-
const appTheme = localStorage.getItem("theme");
|
|
205
|
-
if (appTheme === "light" || appTheme === "dark") {
|
|
206
|
-
url.searchParams.set("theme", appTheme);
|
|
207
|
-
}
|
|
208
|
-
window.location.href = url.toString();
|
|
209
|
-
},
|
|
210
|
-
[clientId, redirectUri]
|
|
211
|
-
);
|
|
212
|
-
return /* @__PURE__ */ jsx(
|
|
213
|
-
SanvikaAuthContext.Provider,
|
|
214
|
-
{
|
|
215
|
-
value: {
|
|
216
|
-
user,
|
|
217
|
-
accessToken,
|
|
218
|
-
loading,
|
|
219
|
-
isLoggedIn: !!user,
|
|
220
|
-
login,
|
|
221
|
-
logout,
|
|
222
|
-
updateUser,
|
|
223
|
-
redirectToLogin,
|
|
224
|
-
clientId,
|
|
225
|
-
redirectUri,
|
|
226
|
-
dashboardPath
|
|
227
|
-
},
|
|
228
|
-
children
|
|
229
|
-
}
|
|
230
|
-
);
|
|
74
|
+
};
|
|
75
|
+
const value = {
|
|
76
|
+
user,
|
|
77
|
+
accessToken,
|
|
78
|
+
loading,
|
|
79
|
+
isAuthenticated: !!user,
|
|
80
|
+
login,
|
|
81
|
+
logout
|
|
82
|
+
};
|
|
83
|
+
return /* @__PURE__ */ jsx(SanvikaAuthContext.Provider, { value, children });
|
|
231
84
|
}
|
|
232
85
|
function useSanvikaAuth() {
|
|
233
86
|
const ctx = useContext(SanvikaAuthContext);
|
|
234
|
-
if (!ctx)
|
|
235
|
-
throw new Error("useSanvikaAuth must be used inside <SanvikaAuthProvider>");
|
|
236
87
|
return ctx;
|
|
237
88
|
}
|
|
238
89
|
|
|
239
90
|
// SanvikaAccountButton.jsx
|
|
240
|
-
import { useEffect as useEffect2, useRef
|
|
91
|
+
import { useEffect as useEffect2, useRef, useState as useState2, Component } from "react";
|
|
241
92
|
|
|
242
93
|
// #style-inject:#style-inject
|
|
243
94
|
function styleInject(css, { insertAt } = {}) {
|
|
@@ -262,10 +113,26 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
262
113
|
}
|
|
263
114
|
|
|
264
115
|
// SanvikaAccountButton.css
|
|
265
|
-
styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n.snvk-skeleton {\n width: clamp(72px, 20vw, 96px);\n height: clamp(34px, 8vw, 40px);\n border-radius: 8px;\n background:\n linear-gradient(\n 90deg,\n var(--skeleton-base-color, #d0d0d0) 25%,\n var(--skeleton-highlight-color, #f0f0f0) 50%,\n var(--skeleton-base-color, #d0d0d0) 75%);\n background-size: 200% 100%;\n animation: snvk-shimmer 1.4s infinite;\n display: inline-block;\n}\n.snvk-wrapper {\n position: relative;\n display: inline-block;\n}\n.snvk-guestBtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: clamp(6px, 2vw, 8px) clamp(10px, 3vw, 16px);\n min-height: 44px;\n min-width: 44px;\n background: var(--sanvika-brand-color, #4f46e5);\n color: #ffffff;\n border: none;\n border-radius: 8px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.3s ease, color 0.3s ease;\n white-space: nowrap;\n}\n.snvk-guestBtn:hover {\n background: var(--sanvika-brand-hover, #4338ca);\n}\n.snvk-iconWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n.snvk-profileBtn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 10px 5px 5px;\n min-height: 44px;\n background: var(--muted-bg, #f5f5f5);\n border: 1px solid var(--border-color-light, #e5e7eb);\n border-radius: 99px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n color: var(--text-color, #1a1a1a);\n transition:\n background-color 0.3s ease,\n border-color 0.3s ease,\n color 0.3s ease;\n white-space: nowrap;\n outline: none;\n}\n.snvk-profileBtn:hover {\n background: var(--hover-color, #ebebeb);\n border-color: var(--border-color-dark, #d1d5db);\n}\n.snvk-profileBtn:focus-visible {\n outline: 2px solid var(--sanvika-brand-color, #4f46e5);\n outline-offset: 2px;\n}\n.snvk-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-textWrap {\n display: inline;\n}\n.snvk-hideTextOnMobile {\n display: none;\n}\n@media (min-width: 500px) {\n .snvk-hideTextOnMobile {\n display: inline;\n }\n}\n.snvk-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: clamp(200px, 60vw, 240px);\n background: var(--card-bg, #f3f3f3);\n border: 1px solid var(--border-color-light, #cccccc);\n border-radius: 12px;\n box-shadow: 0 8px 24px var(--shadow-color, rgba(0, 0, 0, 0.2));\n z-index: 9999;\n overflow: hidden;\n transition: background-color 0.3s ease, border-color 0.3s ease;\n}\n.snvk-dropdownHeader {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 16px;\n background: var(--section-bg, #ebebeb);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownAvatar {\n width: 38px;\n height: 38px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-dropdownName {\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 800;\n color: var(--text-color, #000000);\n line-height: 1.3;\n transition: color 0.3s ease;\n}\n.snvk-dropdownMobile {\n font-size: clamp(11px, 2.5vw, 12px);\n color: var(--secondary-text-color, #333333);\n margin-top: 2px;\n transition: color 0.3s ease;\n font-weight: 700;\n}\n.snvk-divider {\n height: 1px;\n background: var(--muted-border, #d8d8d8);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownItem {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 11px 16px;\n min-height: 44px;\n background: none;\n border: none;\n font-size: clamp(13px, 3vw, 14px);\n color: var(--text-color, #000000);\n font-weight: 600;\n cursor: pointer;\n text-decoration: none;\n text-align: left;\n transition: background-color 0.3s ease, color 0.3s ease;\n}\n.snvk-dropdownItem:hover {\n background: var(--hover-color, #e8e8e8);\n}\n
|
|
116
|
+
styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n.snvk-skeleton {\n width: clamp(72px, 20vw, 96px);\n height: clamp(34px, 8vw, 40px);\n border-radius: 8px;\n background:\n linear-gradient(\n 90deg,\n var(--skeleton-base-color, #d0d0d0) 25%,\n var(--skeleton-highlight-color, #f0f0f0) 50%,\n var(--skeleton-base-color, #d0d0d0) 75%);\n background-size: 200% 100%;\n animation: snvk-shimmer 1.4s infinite;\n display: inline-block;\n}\n.snvk-wrapper {\n position: relative;\n display: inline-block;\n}\n.snvk-guestBtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: clamp(6px, 2vw, 8px) clamp(10px, 3vw, 16px);\n min-height: 44px;\n min-width: 44px;\n background: var(--sanvika-brand-color, #4f46e5);\n color: #ffffff;\n border: none;\n border-radius: 8px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.3s ease, color 0.3s ease;\n white-space: nowrap;\n}\n.snvk-guestBtn:hover {\n background: var(--sanvika-brand-hover, #4338ca);\n}\n.snvk-iconWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n.snvk-profileBtn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 10px 5px 5px;\n min-height: 44px;\n background: var(--muted-bg, #f5f5f5);\n border: 1px solid var(--border-color-light, #e5e7eb);\n border-radius: 99px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n color: var(--text-color, #1a1a1a);\n transition:\n background-color 0.3s ease,\n border-color 0.3s ease,\n color 0.3s ease;\n white-space: nowrap;\n outline: none;\n}\n.snvk-profileBtn:hover {\n background: var(--hover-color, #ebebeb);\n border-color: var(--border-color-dark, #d1d5db);\n}\n.snvk-profileBtn:focus-visible {\n outline: 2px solid var(--sanvika-brand-color, #4f46e5);\n outline-offset: 2px;\n}\n.snvk-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-textWrap {\n display: inline;\n}\n.snvk-hideTextOnMobile {\n display: none;\n}\n@media (min-width: 500px) {\n .snvk-hideTextOnMobile {\n display: inline;\n }\n}\n.snvk-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: clamp(200px, 60vw, 240px);\n background: var(--card-bg, #f3f3f3);\n border: 1px solid var(--border-color-light, #cccccc);\n border-radius: 12px;\n box-shadow: 0 8px 24px var(--shadow-color, rgba(0, 0, 0, 0.2));\n z-index: 9999;\n overflow: hidden;\n transition: background-color 0.3s ease, border-color 0.3s ease;\n}\n.snvk-dropdownHeader {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 16px;\n background: var(--section-bg, #ebebeb);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownAvatar {\n width: 38px;\n height: 38px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-dropdownName {\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 800;\n color: var(--text-color, #000000);\n line-height: 1.3;\n transition: color 0.3s ease;\n}\n.snvk-dropdownMobile {\n font-size: clamp(11px, 2.5vw, 12px);\n color: var(--secondary-text-color, #333333);\n margin-top: 2px;\n transition: color 0.3s ease;\n font-weight: 700;\n}\n.snvk-divider {\n height: 1px;\n background: var(--muted-border, #d8d8d8);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownItem {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 11px 16px;\n min-height: 44px;\n background: none;\n border: none;\n font-size: clamp(13px, 3vw, 14px);\n color: var(--text-color, #000000);\n font-weight: 600;\n cursor: pointer;\n text-decoration: none;\n text-align: left;\n transition: background-color 0.3s ease, color 0.3s ease;\n}\n.snvk-dropdownItem:hover {\n background: var(--hover-color, #e8e8e8);\n}\n");
|
|
266
117
|
|
|
267
118
|
// SanvikaAccountButton.jsx
|
|
268
119
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
120
|
+
var SanvikaAccountButtonErrorBoundary = class extends Component {
|
|
121
|
+
constructor(props) {
|
|
122
|
+
super(props);
|
|
123
|
+
this.state = { hasError: false };
|
|
124
|
+
}
|
|
125
|
+
static getDerivedStateFromError(error) {
|
|
126
|
+
return { hasError: true };
|
|
127
|
+
}
|
|
128
|
+
static displayName = "SanvikaAccountButtonErrorBoundary";
|
|
129
|
+
render() {
|
|
130
|
+
if (this.state.hasError) {
|
|
131
|
+
return /* @__PURE__ */ jsx2("div", { className: "snvk-skeleton", "aria-hidden": "true" });
|
|
132
|
+
}
|
|
133
|
+
return this.props.children;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
269
136
|
function UserIcon({ size = 18 }) {
|
|
270
137
|
return /* @__PURE__ */ jsxs(
|
|
271
138
|
"svg",
|
|
@@ -324,89 +191,20 @@ function DashboardIcon() {
|
|
|
324
191
|
}
|
|
325
192
|
);
|
|
326
193
|
}
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
{
|
|
331
|
-
width: "15",
|
|
332
|
-
height: "15",
|
|
333
|
-
viewBox: "0 0 24 24",
|
|
334
|
-
fill: "none",
|
|
335
|
-
stroke: "currentColor",
|
|
336
|
-
strokeWidth: "2",
|
|
337
|
-
strokeLinecap: "round",
|
|
338
|
-
children: [
|
|
339
|
-
/* @__PURE__ */ jsx2("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
340
|
-
/* @__PURE__ */ jsx2("polyline", { points: "16 17 21 12 16 7" }),
|
|
341
|
-
/* @__PURE__ */ jsx2("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
342
|
-
]
|
|
343
|
-
}
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
function DeleteIcon() {
|
|
347
|
-
return /* @__PURE__ */ jsxs(
|
|
348
|
-
"svg",
|
|
349
|
-
{
|
|
350
|
-
width: "15",
|
|
351
|
-
height: "15",
|
|
352
|
-
viewBox: "0 0 24 24",
|
|
353
|
-
fill: "none",
|
|
354
|
-
stroke: "currentColor",
|
|
355
|
-
strokeWidth: "2",
|
|
356
|
-
strokeLinecap: "round",
|
|
357
|
-
children: [
|
|
358
|
-
/* @__PURE__ */ jsx2("polyline", { points: "3 6 5 6 21 6" }),
|
|
359
|
-
/* @__PURE__ */ jsx2("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
|
|
360
|
-
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
|
|
361
|
-
/* @__PURE__ */ jsx2("line", { x1: "14", y1: "11", x2: "14", y2: "17" })
|
|
362
|
-
]
|
|
363
|
-
}
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
function SanvikaAccountButton({
|
|
367
|
-
text = "Account",
|
|
368
|
-
hideTextOnMobile = false,
|
|
194
|
+
function SanvikaAccountButtonContent({
|
|
195
|
+
text,
|
|
196
|
+
hideTextOnMobile,
|
|
369
197
|
onLoginClick,
|
|
370
198
|
onProfileClick,
|
|
371
|
-
onboardingPath
|
|
372
|
-
className
|
|
199
|
+
onboardingPath,
|
|
200
|
+
className
|
|
373
201
|
}) {
|
|
374
|
-
var _a;
|
|
375
|
-
const
|
|
376
|
-
user,
|
|
377
|
-
loading,
|
|
378
|
-
isLoggedIn,
|
|
379
|
-
logout,
|
|
380
|
-
redirectToLogin,
|
|
381
|
-
dashboardPath,
|
|
382
|
-
accessToken,
|
|
383
|
-
updateUser
|
|
384
|
-
} = useSanvikaAuth();
|
|
202
|
+
var _a, _b;
|
|
203
|
+
const auth = useSanvikaAuth();
|
|
385
204
|
const [dropdownOpen, setDropdownOpen] = useState2(false);
|
|
386
205
|
const [imgError, setImgError] = useState2(false);
|
|
387
|
-
const [prevImage, setPrevImage] = useState2(user == null ? void 0 :
|
|
388
|
-
const wrapperRef =
|
|
389
|
-
if ((user == null ? void 0 : user.image) !== prevImage) {
|
|
390
|
-
setPrevImage(user == null ? void 0 : user.image);
|
|
391
|
-
setImgError(false);
|
|
392
|
-
}
|
|
393
|
-
useEffect2(() => {
|
|
394
|
-
if (!user) return;
|
|
395
|
-
let disposed = false;
|
|
396
|
-
const handleProfileUpdated = async () => {
|
|
397
|
-
if (disposed) return;
|
|
398
|
-
try {
|
|
399
|
-
const stored = localStorage.getItem("sanvika_user");
|
|
400
|
-
if (stored) updateUser(JSON.parse(stored));
|
|
401
|
-
} catch {
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
window.addEventListener("profileUpdated", handleProfileUpdated);
|
|
405
|
-
return () => {
|
|
406
|
-
disposed = true;
|
|
407
|
-
window.removeEventListener("profileUpdated", handleProfileUpdated);
|
|
408
|
-
};
|
|
409
|
-
}, [user, updateUser]);
|
|
206
|
+
const [prevImage, setPrevImage] = useState2((_a = auth == null ? void 0 : auth.user) == null ? void 0 : _a.image);
|
|
207
|
+
const wrapperRef = useRef(null);
|
|
410
208
|
useEffect2(() => {
|
|
411
209
|
if (!dropdownOpen) return;
|
|
412
210
|
function handleOutside(e) {
|
|
@@ -417,10 +215,38 @@ function SanvikaAccountButton({
|
|
|
417
215
|
document.addEventListener("mousedown", handleOutside);
|
|
418
216
|
return () => document.removeEventListener("mousedown", handleOutside);
|
|
419
217
|
}, [dropdownOpen]);
|
|
420
|
-
if (
|
|
421
|
-
return /* @__PURE__ */
|
|
218
|
+
if (!auth) {
|
|
219
|
+
return /* @__PURE__ */ jsxs(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
className: `snvk-guestBtn ${className}`,
|
|
223
|
+
onClick: () => {
|
|
224
|
+
if (onLoginClick) {
|
|
225
|
+
onLoginClick();
|
|
226
|
+
} else {
|
|
227
|
+
window.location.href = "https://accounts.sanvikaproduction.com";
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"aria-label": "Login or Sign Up",
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsx2("span", { className: "snvk-iconWrap", children: /* @__PURE__ */ jsx2(UserIcon, { size: 18 }) }),
|
|
233
|
+
/* @__PURE__ */ jsx2(
|
|
234
|
+
"span",
|
|
235
|
+
{
|
|
236
|
+
className: hideTextOnMobile ? "snvk-hideTextOnMobile" : "snvk-textWrap",
|
|
237
|
+
children: text
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
);
|
|
422
243
|
}
|
|
423
|
-
|
|
244
|
+
const { user, loading, isAuthenticated, logout } = auth;
|
|
245
|
+
if ((user == null ? void 0 : user.image) !== prevImage) {
|
|
246
|
+
setPrevImage(user == null ? void 0 : user.image);
|
|
247
|
+
setImgError(false);
|
|
248
|
+
}
|
|
249
|
+
if (!isAuthenticated || loading) {
|
|
424
250
|
return /* @__PURE__ */ jsxs(
|
|
425
251
|
"button",
|
|
426
252
|
{
|
|
@@ -429,7 +255,7 @@ function SanvikaAccountButton({
|
|
|
429
255
|
if (onLoginClick) {
|
|
430
256
|
onLoginClick();
|
|
431
257
|
} else {
|
|
432
|
-
|
|
258
|
+
window.location.href = "https://accounts.sanvikaproduction.com";
|
|
433
259
|
}
|
|
434
260
|
},
|
|
435
261
|
"aria-label": "Login or Sign Up",
|
|
@@ -446,15 +272,15 @@ function SanvikaAccountButton({
|
|
|
446
272
|
}
|
|
447
273
|
);
|
|
448
274
|
}
|
|
449
|
-
const displayName = user.firstName || ((
|
|
275
|
+
const displayName = user.firstName || ((_b = user.mobile) == null ? void 0 : _b.slice(-4)) || "Me";
|
|
450
276
|
const imageSrc = !imgError && user.image ? user.image : DEFAULT_AVATAR_SVG;
|
|
451
277
|
const handleProfileClick = () => {
|
|
452
278
|
if (onProfileClick) return onProfileClick();
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
279
|
+
window.location.href = "https://accounts.sanvikaproduction.com/dashboard";
|
|
280
|
+
};
|
|
281
|
+
const handleLogout = async () => {
|
|
282
|
+
await logout(false);
|
|
283
|
+
setDropdownOpen(false);
|
|
458
284
|
};
|
|
459
285
|
return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: `snvk-wrapper ${className}`, children: [
|
|
460
286
|
/* @__PURE__ */ jsxs(
|
|
@@ -509,20 +335,7 @@ function SanvikaAccountButton({
|
|
|
509
335
|
/* @__PURE__ */ jsxs(
|
|
510
336
|
"a",
|
|
511
337
|
{
|
|
512
|
-
href:
|
|
513
|
-
className: "snvk-dropdownItem",
|
|
514
|
-
role: "menuitem",
|
|
515
|
-
onClick: () => setDropdownOpen(false),
|
|
516
|
-
children: [
|
|
517
|
-
/* @__PURE__ */ jsx2(UserIcon, { size: 15 }),
|
|
518
|
-
/* @__PURE__ */ jsx2("span", { children: "View Profile" })
|
|
519
|
-
]
|
|
520
|
-
}
|
|
521
|
-
),
|
|
522
|
-
/* @__PURE__ */ jsxs(
|
|
523
|
-
"a",
|
|
524
|
-
{
|
|
525
|
-
href: onboardingPath && user.status === "onboarding" ? onboardingPath : dashboardPath,
|
|
338
|
+
href: "https://accounts.sanvikaproduction.com/dashboard",
|
|
526
339
|
className: "snvk-dropdownItem",
|
|
527
340
|
role: "menuitem",
|
|
528
341
|
onClick: () => setDropdownOpen(false),
|
|
@@ -532,33 +345,31 @@ function SanvikaAccountButton({
|
|
|
532
345
|
]
|
|
533
346
|
}
|
|
534
347
|
),
|
|
535
|
-
/* @__PURE__ */
|
|
536
|
-
"a",
|
|
537
|
-
{
|
|
538
|
-
href: `https://accounts.sanvikaproduction.com/account/delete?token=${encodeURIComponent(
|
|
539
|
-
accessToken || ""
|
|
540
|
-
)}&origin=${encodeURIComponent(window.location.origin)}`,
|
|
541
|
-
className: "snvk-dropdownItem snvk-deleteItem",
|
|
542
|
-
role: "menuitem",
|
|
543
|
-
onClick: () => setDropdownOpen(false),
|
|
544
|
-
children: [
|
|
545
|
-
/* @__PURE__ */ jsx2(DeleteIcon, {}),
|
|
546
|
-
/* @__PURE__ */ jsx2("span", { children: "Delete Account" })
|
|
547
|
-
]
|
|
548
|
-
}
|
|
549
|
-
),
|
|
348
|
+
/* @__PURE__ */ jsx2("div", { className: "snvk-divider" }),
|
|
550
349
|
/* @__PURE__ */ jsxs(
|
|
551
350
|
"button",
|
|
552
351
|
{
|
|
553
|
-
className: "snvk-dropdownItem
|
|
352
|
+
className: "snvk-dropdownItem",
|
|
554
353
|
role: "menuitem",
|
|
555
|
-
onClick:
|
|
556
|
-
setDropdownOpen(false);
|
|
557
|
-
await logout();
|
|
558
|
-
window.location.href = "/";
|
|
559
|
-
},
|
|
354
|
+
onClick: handleLogout,
|
|
560
355
|
children: [
|
|
561
|
-
/* @__PURE__ */
|
|
356
|
+
/* @__PURE__ */ jsxs(
|
|
357
|
+
"svg",
|
|
358
|
+
{
|
|
359
|
+
width: "15",
|
|
360
|
+
height: "15",
|
|
361
|
+
viewBox: "0 0 24 24",
|
|
362
|
+
fill: "none",
|
|
363
|
+
stroke: "currentColor",
|
|
364
|
+
strokeWidth: "2",
|
|
365
|
+
strokeLinecap: "round",
|
|
366
|
+
children: [
|
|
367
|
+
/* @__PURE__ */ jsx2("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
368
|
+
/* @__PURE__ */ jsx2("polyline", { points: "16 17 21 12 16 7" }),
|
|
369
|
+
/* @__PURE__ */ jsx2("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
),
|
|
562
373
|
/* @__PURE__ */ jsx2("span", { children: "Logout" })
|
|
563
374
|
]
|
|
564
375
|
}
|
|
@@ -566,14 +377,14 @@ function SanvikaAccountButton({
|
|
|
566
377
|
] })
|
|
567
378
|
] });
|
|
568
379
|
}
|
|
380
|
+
function SanvikaAccountButton(props) {
|
|
381
|
+
return /* @__PURE__ */ jsx2(SanvikaAccountButtonErrorBoundary, { children: /* @__PURE__ */ jsx2(SanvikaAccountButtonContent, { ...props }) });
|
|
382
|
+
}
|
|
569
383
|
export {
|
|
570
384
|
DEFAULT_AVATAR_SVG,
|
|
571
385
|
STORAGE_KEYS,
|
|
572
386
|
SanvikaAccountButton,
|
|
573
387
|
SanvikaAuthContext,
|
|
574
388
|
SanvikaAuthProvider,
|
|
575
|
-
decodeToken,
|
|
576
|
-
ensureTokenFresh,
|
|
577
|
-
scheduleTokenRefresh,
|
|
578
389
|
useSanvikaAuth
|
|
579
390
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanvika/auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "Sanvika Auth SDK — React components and hooks for Sanvika SSO integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"sanvika",
|
|
30
30
|
"auth",
|
|
31
31
|
"sso",
|
|
32
|
-
"oauth",
|
|
33
32
|
"react",
|
|
34
33
|
"nextjs"
|
|
35
34
|
],
|