@sybilion/uilib 1.2.11 → 1.2.13

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.
@@ -7,7 +7,7 @@ import { SybilionSignInPanel } from '../SybilionSignInPanel/SybilionSignInPanel.
7
7
  const DEFAULT_TITLE = 'Sign In';
8
8
  const DEFAULT_SUBTITLE = 'To get access authenticate through google or your email.';
9
9
  function SignInPage({ title = DEFAULT_TITLE, subtitle = DEFAULT_SUBTITLE, forgotPasswordTo = '/forgot-password', releasesTo = '/releases', versionLabel, primaryButtonLabel, connectingLabel, loginRedirectOptions, logoSize, containerClassName, }) {
10
- const { error, loginWithRedirect, isLoading } = useSybilionAuth();
10
+ const { error, loginWithRedirect, isLoading, isAuthenticated } = useSybilionAuth();
11
11
  const handleSignIn = () => loginWithRedirect({
12
12
  ...loginRedirectOptions,
13
13
  authorizationParams: {
@@ -19,7 +19,7 @@ function SignInPage({ title = DEFAULT_TITLE, subtitle = DEFAULT_SUBTITLE, forgot
19
19
  ...loginRedirectOptions?.authorizationParams,
20
20
  },
21
21
  });
22
- return (jsx(SybilionAuthLayout, { title: title, subtitle: subtitle, logoSize: logoSize, containerClassName: containerClassName, children: jsx(SybilionSignInPanel, { onSignIn: handleSignIn, isSigningIn: isLoading, error: error, forgotPasswordTo: forgotPasswordTo, releasesTo: releasesTo, versionLabel: versionLabel, primaryButtonLabel: primaryButtonLabel, connectingLabel: connectingLabel }) }));
22
+ return (jsx(SybilionAuthLayout, { title: title, subtitle: subtitle, logoSize: logoSize, containerClassName: containerClassName, children: jsx(SybilionSignInPanel, { onSignIn: handleSignIn, isSigningIn: isLoading && !isAuthenticated, error: error, forgotPasswordTo: forgotPasswordTo, releasesTo: releasesTo, versionLabel: versionLabel, primaryButtonLabel: primaryButtonLabel, connectingLabel: connectingLabel }) }));
23
23
  }
24
24
 
25
25
  export { SignInPage };
@@ -0,0 +1 @@
1
+ export { useIsMobile } from './useIsMobile.js';
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { useIsMobile } from './hooks/useIsMobile.js';
1
2
  export { ThemeProvider, useTheme } from './contexts/theme-context.js';
2
3
  export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
3
4
  export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
@@ -59,6 +59,16 @@ function useSybilionApiFetch() {
59
59
  const { apiBaseUrl, getSybilionAccessToken } = useSybilionAuth();
60
60
  return useMemo(() => createSybilionApiFetch(apiBaseUrl, getSybilionAccessToken), [apiBaseUrl, getSybilionAccessToken]);
61
61
  }
62
+ function readTokenFromLs(key) {
63
+ if (typeof localStorage === 'undefined')
64
+ return null;
65
+ try {
66
+ return localStorage.getItem(key);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
62
72
  function writeLs(key, value) {
63
73
  if (typeof localStorage === 'undefined')
64
74
  return;
@@ -76,7 +86,7 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
76
86
  const auth0 = useAuth0();
77
87
  const auth0Ref = useRef(auth0);
78
88
  auth0Ref.current = auth0;
79
- const [sybilionToken, setSybilionToken] = useState(null);
89
+ const [sybilionToken, setSybilionToken] = useState(() => typeof window === 'undefined' ? null : readTokenFromLs(storageKey));
80
90
  const [exchangeLoading, setExchangeLoading] = useState(false);
81
91
  const [exchangeError, setExchangeError] = useState(null);
82
92
  const [isLoggingOut, setIsLoggingOut] = useState(false);
@@ -106,10 +116,15 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
106
116
  }, [auth0.isAuthenticated]);
107
117
  const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
108
118
  useEffect(() => {
119
+ // While Auth0 restores from localStorage, `isAuthenticated` is false — do not
120
+ // wipe the Sybilion JWT (would break reload: sign-in flash / lost session).
121
+ if (auth0.isLoading) {
122
+ return;
123
+ }
109
124
  if (!auth0.isAuthenticated || !auth0.user?.sub) {
110
125
  persistToken(null);
111
126
  }
112
- }, [auth0.isAuthenticated, auth0.user?.sub, persistToken]);
127
+ }, [auth0.isLoading, auth0.isAuthenticated, auth0.user?.sub, persistToken]);
113
128
  useEffect(() => {
114
129
  if (!auth0.isAuthenticated || !auth0.user?.sub) {
115
130
  setExchangeLoading(false);
@@ -145,13 +160,7 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
145
160
  return () => {
146
161
  cancelled = true;
147
162
  };
148
- }, [
149
- sdk,
150
- auth0.isAuthenticated,
151
- auth0.user?.sub,
152
- persistToken,
153
- doLogout,
154
- ]);
163
+ }, [sdk, auth0.isAuthenticated, auth0.user?.sub, persistToken, doLogout]);
155
164
  const getAuth0AccessToken = useCallback(async () => {
156
165
  if (!auth0.isAuthenticated)
157
166
  return undefined;
@@ -168,13 +177,21 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
168
177
  const logout = useCallback(() => {
169
178
  doLogout();
170
179
  }, [doLogout]);
171
- const isAuthenticated = Boolean(auth0.isAuthenticated && sybilionToken && !exchangeError);
172
- const isLoading = auth0.isLoading ||
173
- exchangeLoading ||
174
- (Boolean(auth0.isAuthenticated && auth0.user) &&
175
- sybilionToken === null &&
176
- exchangeError === null &&
177
- !isLoggingOut);
180
+ const hasAuth0User = Boolean(auth0.isAuthenticated && auth0.user?.sub);
181
+ /** Auth0 session is enough for app routes; Sybilion JWT is filled by exchange. */
182
+ const isAuthenticated = Boolean(!exchangeError &&
183
+ !isLoggingOut &&
184
+ (hasAuth0User ||
185
+ (Boolean(sybilionToken) && (auth0.isAuthenticated || auth0.isLoading))));
186
+ const needsFirstSybilionToken = Boolean(auth0.isAuthenticated &&
187
+ auth0.user &&
188
+ sybilionToken === null &&
189
+ exchangeError === null &&
190
+ !isLoggingOut);
191
+ /** Omit Auth0/exchange "busy" once we already have a Sybilion JWT — avoids header skeleton flicker on refresh. */
192
+ const isLoading = (auth0.isLoading && sybilionToken === null) ||
193
+ (exchangeLoading && sybilionToken === null) ||
194
+ needsFirstSybilionToken;
178
195
  const value = useMemo(() => ({
179
196
  apiBaseUrl,
180
197
  isLoading,
@@ -0,0 +1 @@
1
+ export { useIsMobile } from './useIsMobile';
@@ -1,3 +1,4 @@
1
+ export { useIsMobile } from './hooks/useIsMobile';
1
2
  export * from './contexts/theme-context';
2
3
  export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme';
3
4
  export * from './sybilion-auth';
@@ -1,6 +1,6 @@
1
1
  import { type RedirectLoginOptions } from '@auth0/auth0-react';
2
- import type { SybilionSDK } from '@sybilion/sdk';
3
2
  import type { JSX, ReactNode } from 'react';
3
+ import type { SybilionSDK } from '@sybilion/sdk';
4
4
  /** Origin (`scheme://host:port`) for paths like `/api/v1/...`; derived from SDK URL layout. */
5
5
  export declare function getSybilionApiOriginFromSdk(sdk: SybilionSDK): string;
6
6
  export type SybilionAuthProviderProps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -29,6 +29,11 @@
29
29
  "import": "./src/index.ts",
30
30
  "default": "./src/index.ts"
31
31
  },
32
+ "./hooks": {
33
+ "types": "./dist/esm/types/src/hooks/index.d.ts",
34
+ "import": "./dist/esm/hooks/index.js",
35
+ "default": "./dist/esm/hooks/index.js"
36
+ },
32
37
  "./src/*": "./src/*",
33
38
  "./vite-standalone-dev": {
34
39
  "types": "./dist/standalone/vite-sybilion-standalone-dev.d.ts",
@@ -45,7 +45,8 @@ export function SignInPage({
45
45
  logoSize,
46
46
  containerClassName,
47
47
  }: SignInPageProps) {
48
- const { error, loginWithRedirect, isLoading } = useSybilionAuth();
48
+ const { error, loginWithRedirect, isLoading, isAuthenticated } =
49
+ useSybilionAuth();
49
50
 
50
51
  const handleSignIn = () =>
51
52
  loginWithRedirect({
@@ -69,7 +70,7 @@ export function SignInPage({
69
70
  >
70
71
  <SybilionSignInPanel
71
72
  onSignIn={handleSignIn}
72
- isSigningIn={isLoading}
73
+ isSigningIn={isLoading && !isAuthenticated}
73
74
  error={error}
74
75
  forgotPasswordTo={forgotPasswordTo}
75
76
  releasesTo={releasesTo}
@@ -0,0 +1 @@
1
+ export { useIsMobile } from './useIsMobile';
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { useIsMobile } from './hooks/useIsMobile';
1
2
  export * from './contexts/theme-context';
2
3
  export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme';
3
4
  export * from './sybilion-auth';
@@ -3,7 +3,6 @@ import {
3
3
  type RedirectLoginOptions,
4
4
  useAuth0,
5
5
  } from '@auth0/auth0-react';
6
- import type { LoginTokenResponse, SybilionSDK } from '@sybilion/sdk';
7
6
  import type { JSX, ReactNode } from 'react';
8
7
  import {
9
8
  createContext,
@@ -16,6 +15,7 @@ import {
16
15
  } from 'react';
17
16
 
18
17
  import { normalizeApiBaseUrl } from '#uilib/sybilion-auth/authPaths';
18
+ import type { LoginTokenResponse, SybilionSDK } from '@sybilion/sdk';
19
19
 
20
20
  const DEFAULT_TOKEN_KEY = 'sybilion.standalone.jwt';
21
21
 
@@ -138,6 +138,15 @@ export function useSybilionApiFetch(): (
138
138
  );
139
139
  }
140
140
 
141
+ function readTokenFromLs(key: string): string | null {
142
+ if (typeof localStorage === 'undefined') return null;
143
+ try {
144
+ return localStorage.getItem(key);
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
141
150
  function writeLs(key: string, value: string | null): void {
142
151
  if (typeof localStorage === 'undefined') return;
143
152
  try {
@@ -165,7 +174,9 @@ function InnerSybilionSession({
165
174
  const auth0Ref = useRef(auth0);
166
175
  auth0Ref.current = auth0;
167
176
 
168
- const [sybilionToken, setSybilionToken] = useState<string | null>(null);
177
+ const [sybilionToken, setSybilionToken] = useState<string | null>(() =>
178
+ typeof window === 'undefined' ? null : readTokenFromLs(storageKey),
179
+ );
169
180
  const [exchangeLoading, setExchangeLoading] = useState(false);
170
181
  const [exchangeError, setExchangeError] = useState<string | null>(null);
171
182
  const [isLoggingOut, setIsLoggingOut] = useState(false);
@@ -203,10 +214,15 @@ function InnerSybilionSession({
203
214
  const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
204
215
 
205
216
  useEffect(() => {
217
+ // While Auth0 restores from localStorage, `isAuthenticated` is false — do not
218
+ // wipe the Sybilion JWT (would break reload: sign-in flash / lost session).
219
+ if (auth0.isLoading) {
220
+ return;
221
+ }
206
222
  if (!auth0.isAuthenticated || !auth0.user?.sub) {
207
223
  persistToken(null);
208
224
  }
209
- }, [auth0.isAuthenticated, auth0.user?.sub, persistToken]);
225
+ }, [auth0.isLoading, auth0.isAuthenticated, auth0.user?.sub, persistToken]);
210
226
 
211
227
  useEffect(() => {
212
228
  if (!auth0.isAuthenticated || !auth0.user?.sub) {
@@ -242,13 +258,7 @@ function InnerSybilionSession({
242
258
  return () => {
243
259
  cancelled = true;
244
260
  };
245
- }, [
246
- sdk,
247
- auth0.isAuthenticated,
248
- auth0.user?.sub,
249
- persistToken,
250
- doLogout,
251
- ]);
261
+ }, [sdk, auth0.isAuthenticated, auth0.user?.sub, persistToken, doLogout]);
252
262
 
253
263
  const getAuth0AccessToken = useCallback(async () => {
254
264
  if (!auth0.isAuthenticated) return undefined;
@@ -269,17 +279,29 @@ function InnerSybilionSession({
269
279
  doLogout();
270
280
  }, [doLogout]);
271
281
 
282
+ const hasAuth0User = Boolean(auth0.isAuthenticated && auth0.user?.sub);
283
+
284
+ /** Auth0 session is enough for app routes; Sybilion JWT is filled by exchange. */
272
285
  const isAuthenticated = Boolean(
273
- auth0.isAuthenticated && sybilionToken && !exchangeError,
286
+ !exchangeError &&
287
+ !isLoggingOut &&
288
+ (hasAuth0User ||
289
+ (Boolean(sybilionToken) && (auth0.isAuthenticated || auth0.isLoading))),
290
+ );
291
+
292
+ const needsFirstSybilionToken = Boolean(
293
+ auth0.isAuthenticated &&
294
+ auth0.user &&
295
+ sybilionToken === null &&
296
+ exchangeError === null &&
297
+ !isLoggingOut,
274
298
  );
275
299
 
300
+ /** Omit Auth0/exchange "busy" once we already have a Sybilion JWT — avoids header skeleton flicker on refresh. */
276
301
  const isLoading =
277
- auth0.isLoading ||
278
- exchangeLoading ||
279
- (Boolean(auth0.isAuthenticated && auth0.user) &&
280
- sybilionToken === null &&
281
- exchangeError === null &&
282
- !isLoggingOut);
302
+ (auth0.isLoading && sybilionToken === null) ||
303
+ (exchangeLoading && sybilionToken === null) ||
304
+ needsFirstSybilionToken;
283
305
 
284
306
  const value = useMemo(
285
307
  (): SybilionAuthContextValue => ({