@visns-studio/visns-components 6.5.3 → 6.5.5

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/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "react-dom": "^17.0.0 || ^18.0.0"
92
92
  },
93
93
  "name": "@visns-studio/visns-components",
94
- "version": "6.5.3",
94
+ "version": "6.5.5",
95
95
  "description": "Various packages to assist in the development of our Custom Applications.",
96
96
  "main": "src/index.js",
97
97
  "files": [
@@ -146,12 +146,12 @@ const Login = ({
146
146
  if (result.previous && result.previous !== '') {
147
147
  window.location.href = result.previous;
148
148
  } else {
149
+ // GenericAuth owns the transition from here:
150
+ // its curtain drops over this screen and its
151
+ // profile check performs the one navigation
152
+ // to '/'. Navigating here as well produced a
153
+ // double route change and a visible flicker.
149
154
  setSystemAuth(true);
150
- // Move now, with the profile the server just
151
- // returned — waiting for the background
152
- // re-check to navigate leaves the login form
153
- // on screen for a beat after success.
154
- navigate('/', { replace: true });
155
155
  }
156
156
  }
157
157
  } else {
@@ -26,7 +26,7 @@ import ClientOTPVerify from '../auth/ClientOTPVerify';
26
26
  // blank white page between the sign-in redirect and the first authenticated
27
27
  // paint. Self-contained: SMIL animation, no stylesheet dependency, and the
28
28
  // consuming app's design tokens with neutral fallbacks.
29
- const AuthLoading = () => (
29
+ const AuthLoading = ({ label }) => (
30
30
  <div
31
31
  role="status"
32
32
  aria-label="Loading"
@@ -34,9 +34,13 @@ const AuthLoading = () => (
34
34
  position: 'fixed',
35
35
  inset: 0,
36
36
  display: 'flex',
37
+ flexDirection: 'column',
38
+ gap: '1rem',
37
39
  alignItems: 'center',
38
40
  justifyContent: 'center',
39
41
  background: 'var(--bg-color, #f4f4f5)',
42
+ // Above every app surface — this is a curtain, not a page.
43
+ zIndex: 2000,
40
44
  }}
41
45
  >
42
46
  <svg width="42" height="42" viewBox="0 0 42 42" fill="none">
@@ -63,13 +67,27 @@ const AuthLoading = () => (
63
67
  />
64
68
  </path>
65
69
  </svg>
70
+ {label ? (
71
+ <p
72
+ style={{
73
+ margin: 0,
74
+ fontFamily: 'var(--font-family, inherit)',
75
+ fontSize: 'var(--font-size-md, 0.9375rem)',
76
+ color: 'var(--paragraph-color, #52525b)',
77
+ }}
78
+ >
79
+ {label}
80
+ </p>
81
+ ) : null}
66
82
  </div>
67
83
  );
68
84
 
69
85
  const ProtectedRoute = ({ user, loading, redirectPath = '/login' }) => {
70
86
  const location = useLocation();
71
87
 
72
- return loading ? <AuthLoading /> : !user || !user.id ? (
88
+ // The full-screen curtain in GenericAuth covers the loading state; this
89
+ // just refrains from redirecting until the check settles.
90
+ return loading ? null : !user || !user.id ? (
73
91
  <Navigate
74
92
  to={redirectPath}
75
93
  replace
@@ -121,14 +139,16 @@ const GenericAuth = ({
121
139
  const [isClientAuthenticated, setIsClientAuthenticated] = useState(false);
122
140
 
123
141
  const updateAuthStatus = async () => {
124
- // Guards must not act while the FIRST profile check is in flight:
125
- // evaluated against empty state they bounced dashboard → /login →
126
- // dashboard after sign-in. But only block when there is no usable
127
- // profile — raising loading over an already-authenticated tree swaps
128
- // the whole page for a spinner on every background re-check, which
129
- // is its own flicker. With a profile in hand the re-check is
130
- // invisible.
131
- if (!userProfile.id) {
142
+ // The loading curtain covers two moments and stays down for neither
143
+ // more nor less: the first profile check of a page load (no usable
144
+ // profile yet), and the sign-in transition (still on the login
145
+ // screen) — the user asked for "everything behind a please-wait
146
+ // screen, then the dashboard", not a settled page interrupted by a
147
+ // background re-check.
148
+ if (
149
+ !userProfile.id ||
150
+ window.location.pathname.split('/')[1] === 'login'
151
+ ) {
132
152
  setIsLoading(true);
133
153
  }
134
154
 
@@ -141,7 +161,12 @@ const GenericAuth = ({
141
161
  }
142
162
  setIsLoading(false);
143
163
 
144
- if (location.pathname.split('/')[1] === 'login') {
164
+ // The live pathname, not the router hook's: this resolves after
165
+ // Login has already navigated, and the closure still holds
166
+ // '/login' from the render that created the effect — firing a
167
+ // second navigate('/') over the settled dashboard, whose route
168
+ // re-render is the sub-second flicker after sign-in.
169
+ if (window.location.pathname.split('/')[1] === 'login') {
145
170
  navigate('/', { replace: true });
146
171
  }
147
172
  } catch (error) {
@@ -227,6 +252,10 @@ const GenericAuth = ({
227
252
 
228
253
  return (
229
254
  <>
255
+ {/* One curtain over everything while auth settles — the login
256
+ screen included, so sign-in reads: submit → please wait →
257
+ dashboard, with no intermediate paint. */}
258
+ {isLoading ? <AuthLoading label="Please wait…" /> : null}
230
259
  <Routes>
231
260
  <Route
232
261
  path="login"