@checkstack/auth-frontend 0.1.0 → 0.3.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.
- package/CHANGELOG.md +155 -0
- package/package.json +1 -1
- package/src/api.ts +2 -2
- package/src/components/ApplicationsTab.tsx +89 -90
- package/src/components/AuthSettingsPage.tsx +87 -100
- package/src/components/LoginPage.tsx +15 -22
- package/src/components/RegisterPage.tsx +17 -28
- package/src/components/RoleDialog.tsx +49 -53
- package/src/components/RolesTab.tsx +60 -47
- package/src/components/StrategiesTab.tsx +90 -96
- package/src/components/TeamAccessEditor.tsx +131 -123
- package/src/components/TeamsTab.tsx +180 -162
- package/src/components/UsersTab.tsx +43 -32
- package/src/hooks/useAccessRules.ts +32 -0
- package/src/hooks/useEnabledStrategies.ts +10 -41
- package/src/index.test.tsx +83 -37
- package/src/index.tsx +29 -51
- package/src/lib/auth-client.ts +17 -22
- package/src/hooks/usePermissions.ts +0 -43
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import React, { useEffect, useState, useMemo } from "react";
|
|
2
2
|
import { useSearchParams } from "react-router-dom";
|
|
3
|
-
import { useApi, permissionApiRef, rpcApiRef } from "@checkstack/frontend-api";
|
|
4
|
-
import { PageLayout, useToast, Tabs, TabPanel } from "@checkstack/ui";
|
|
5
|
-
import { authApiRef, AuthUser, Role, AuthStrategy, Permission } from "../api";
|
|
6
3
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
useApi,
|
|
5
|
+
accessApiRef,
|
|
6
|
+
usePluginClient,
|
|
7
|
+
} from "@checkstack/frontend-api";
|
|
8
|
+
import { PageLayout, Tabs, TabPanel } from "@checkstack/ui";
|
|
9
|
+
import {
|
|
10
|
+
authApiRef,
|
|
11
|
+
AuthUser,
|
|
12
|
+
Role,
|
|
13
|
+
AuthStrategy,
|
|
14
|
+
AccessRuleEntry,
|
|
15
|
+
} from "../api";
|
|
16
|
+
import { authAccess, AuthApi } from "@checkstack/auth-common";
|
|
10
17
|
import { Shield, Settings2, Users, Key, Users2 } from "lucide-react";
|
|
11
18
|
import { UsersTab } from "./UsersTab";
|
|
12
19
|
import { RolesTab } from "./RolesTab";
|
|
@@ -16,10 +23,8 @@ import { TeamsTab } from "./TeamsTab";
|
|
|
16
23
|
|
|
17
24
|
export const AuthSettingsPage: React.FC = () => {
|
|
18
25
|
const authApi = useApi(authApiRef);
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const permissionApi = useApi(permissionApiRef);
|
|
22
|
-
const toast = useToast();
|
|
26
|
+
const authClient = usePluginClient(AuthApi);
|
|
27
|
+
const accessApi = useApi(accessApiRef);
|
|
23
28
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
24
29
|
|
|
25
30
|
const session = authApi.useSession();
|
|
@@ -27,15 +32,8 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
27
32
|
const [activeTab, setActiveTab] = useState<
|
|
28
33
|
"users" | "roles" | "teams" | "strategies" | "applications"
|
|
29
34
|
>("users");
|
|
30
|
-
const [users, setUsers] = useState<(AuthUser & { roles: string[] })[]>([]);
|
|
31
|
-
const [roles, setRoles] = useState<Role[]>([]);
|
|
32
|
-
const [permissions, setPermissions] = useState<Permission[]>([]);
|
|
33
|
-
const [strategies, setStrategies] = useState<AuthStrategy[]>([]);
|
|
34
|
-
const [loading, setLoading] = useState(true);
|
|
35
35
|
|
|
36
|
-
const canReadUsers =
|
|
37
|
-
authPermissions.usersRead.id
|
|
38
|
-
);
|
|
36
|
+
const canReadUsers = accessApi.useAccess(authAccess.users.read);
|
|
39
37
|
|
|
40
38
|
// Handle ?tab= URL parameters (from command palette)
|
|
41
39
|
useEffect(() => {
|
|
@@ -58,62 +56,69 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
58
56
|
}
|
|
59
57
|
}, [searchParams, setSearchParams]);
|
|
60
58
|
|
|
61
|
-
const canManageUsers =
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
59
|
+
const canManageUsers = accessApi.useAccess(authAccess.users.manage);
|
|
60
|
+
const canCreateUsers = accessApi.useAccess(authAccess.users.create);
|
|
61
|
+
const canReadRoles = accessApi.useAccess(authAccess.roles.read);
|
|
62
|
+
const canCreateRoles = accessApi.useAccess(authAccess.roles.create);
|
|
63
|
+
const canUpdateRoles = accessApi.useAccess(authAccess.roles.update);
|
|
64
|
+
const canDeleteRoles = accessApi.useAccess(authAccess.roles.delete);
|
|
65
|
+
const canManageRoles = accessApi.useAccess(authAccess.roles.manage);
|
|
66
|
+
const canManageStrategies = accessApi.useAccess(authAccess.strategies);
|
|
67
|
+
const canManageRegistration = accessApi.useAccess(authAccess.registration);
|
|
68
|
+
const canManageApplications = accessApi.useAccess(authAccess.applications);
|
|
69
|
+
const canReadTeams = accessApi.useAccess(authAccess.teams.read);
|
|
70
|
+
const canManageTeams = accessApi.useAccess(authAccess.teams.manage);
|
|
71
|
+
|
|
72
|
+
// Queries: Fetch data from API
|
|
73
|
+
const {
|
|
74
|
+
data: users = [],
|
|
75
|
+
isLoading: usersLoading,
|
|
76
|
+
refetch: refetchUsers,
|
|
77
|
+
} = authClient.getUsers.useQuery({}, { enabled: canReadUsers.allowed });
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
data: roles = [],
|
|
81
|
+
isLoading: rolesLoading,
|
|
82
|
+
refetch: refetchRoles,
|
|
83
|
+
} = authClient.getRoles.useQuery({}, { enabled: canReadRoles.allowed });
|
|
84
|
+
|
|
85
|
+
const {
|
|
86
|
+
data: accessRuleEntries = [],
|
|
87
|
+
isLoading: rulesLoading,
|
|
88
|
+
refetch: refetchRules,
|
|
89
|
+
} = authClient.getAccessRules.useQuery({}, { enabled: canReadRoles.allowed });
|
|
90
|
+
|
|
91
|
+
const {
|
|
92
|
+
data: strategies = [],
|
|
93
|
+
isLoading: strategiesLoading,
|
|
94
|
+
refetch: refetchStrategies,
|
|
95
|
+
} = authClient.getStrategies.useQuery(
|
|
96
|
+
{},
|
|
97
|
+
{ enabled: canManageStrategies.allowed }
|
|
96
98
|
);
|
|
97
99
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
+
const dataLoading =
|
|
101
|
+
usersLoading || rolesLoading || rulesLoading || strategiesLoading;
|
|
102
|
+
|
|
103
|
+
const accessRulesLoading =
|
|
104
|
+
dataLoading ||
|
|
100
105
|
canReadUsers.loading ||
|
|
101
106
|
canReadRoles.loading ||
|
|
102
107
|
canManageStrategies.loading ||
|
|
103
108
|
canManageApplications.loading ||
|
|
104
109
|
canReadTeams.loading;
|
|
105
110
|
|
|
106
|
-
const
|
|
111
|
+
const hasAnyAccess =
|
|
107
112
|
canReadUsers.allowed ||
|
|
108
113
|
canReadRoles.allowed ||
|
|
109
114
|
canManageStrategies.allowed ||
|
|
110
115
|
canManageApplications.allowed ||
|
|
111
116
|
canReadTeams.allowed;
|
|
112
117
|
|
|
113
|
-
// Special case: if user is not logged in, show
|
|
114
|
-
const isAllowed = session.data?.user ?
|
|
118
|
+
// Special case: if user is not logged in, show access denied
|
|
119
|
+
const isAllowed = session.data?.user ? hasAnyAccess : false;
|
|
115
120
|
|
|
116
|
-
// Compute visible tabs based on
|
|
121
|
+
// Compute visible tabs based on access rules
|
|
117
122
|
const visibleTabs = useMemo(() => {
|
|
118
123
|
const tabs: Array<{
|
|
119
124
|
id: "users" | "roles" | "teams" | "strategies" | "applications";
|
|
@@ -129,7 +134,7 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
129
134
|
if (canReadRoles.allowed)
|
|
130
135
|
tabs.push({
|
|
131
136
|
id: "roles",
|
|
132
|
-
label: "Roles &
|
|
137
|
+
label: "Roles & Access Rules",
|
|
133
138
|
icon: <Shield size={18} />,
|
|
134
139
|
});
|
|
135
140
|
if (canReadTeams.allowed)
|
|
@@ -169,42 +174,24 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
169
174
|
}
|
|
170
175
|
}, [visibleTabs, activeTab]);
|
|
171
176
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const permissionsData = await authClient.getPermissions();
|
|
180
|
-
const strategiesData = await authClient.getStrategies();
|
|
181
|
-
setUsers(usersData);
|
|
182
|
-
setRoles(rolesData);
|
|
183
|
-
setPermissions(permissionsData);
|
|
184
|
-
setStrategies(strategiesData);
|
|
185
|
-
} catch (error: unknown) {
|
|
186
|
-
const message =
|
|
187
|
-
error instanceof Error ? error.message : "Failed to fetch data";
|
|
188
|
-
toast.error(message);
|
|
189
|
-
console.error(error);
|
|
190
|
-
} finally {
|
|
191
|
-
setLoading(false);
|
|
192
|
-
}
|
|
177
|
+
const handleDataChange = async () => {
|
|
178
|
+
await Promise.all([
|
|
179
|
+
refetchUsers(),
|
|
180
|
+
refetchRoles(),
|
|
181
|
+
refetchRules(),
|
|
182
|
+
refetchStrategies(),
|
|
183
|
+
]);
|
|
193
184
|
};
|
|
194
185
|
|
|
195
|
-
// Initial data fetch
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
fetchData();
|
|
198
|
-
}, []);
|
|
199
|
-
|
|
200
186
|
// Get current user's role IDs for the RolesTab
|
|
187
|
+
const typedUsers = users as (AuthUser & { roles: string[] })[];
|
|
201
188
|
const currentUserRoleIds =
|
|
202
|
-
|
|
189
|
+
typedUsers.find((u) => u.id === session.data?.user?.id)?.roles || [];
|
|
203
190
|
|
|
204
191
|
return (
|
|
205
192
|
<PageLayout
|
|
206
193
|
title="Authentication Settings"
|
|
207
|
-
loading={
|
|
194
|
+
loading={accessRulesLoading}
|
|
208
195
|
allowed={isAllowed}
|
|
209
196
|
>
|
|
210
197
|
<Tabs
|
|
@@ -220,52 +207,52 @@ export const AuthSettingsPage: React.FC = () => {
|
|
|
220
207
|
|
|
221
208
|
<TabPanel id="users" activeTab={activeTab}>
|
|
222
209
|
<UsersTab
|
|
223
|
-
users={
|
|
224
|
-
roles={roles}
|
|
225
|
-
strategies={strategies}
|
|
210
|
+
users={typedUsers}
|
|
211
|
+
roles={roles as Role[]}
|
|
212
|
+
strategies={strategies as AuthStrategy[]}
|
|
226
213
|
currentUserId={session.data?.user?.id}
|
|
227
214
|
canReadUsers={canReadUsers.allowed}
|
|
228
215
|
canCreateUsers={canCreateUsers.allowed}
|
|
229
216
|
canManageUsers={canManageUsers.allowed}
|
|
230
217
|
canManageRoles={canManageRoles.allowed}
|
|
231
|
-
onDataChange={
|
|
218
|
+
onDataChange={handleDataChange}
|
|
232
219
|
/>
|
|
233
220
|
</TabPanel>
|
|
234
221
|
|
|
235
222
|
<TabPanel id="roles" activeTab={activeTab}>
|
|
236
223
|
<RolesTab
|
|
237
|
-
roles={roles}
|
|
238
|
-
|
|
224
|
+
roles={roles as Role[]}
|
|
225
|
+
accessRulesList={accessRuleEntries as AccessRuleEntry[]}
|
|
239
226
|
userRoleIds={currentUserRoleIds}
|
|
240
227
|
canReadRoles={canReadRoles.allowed}
|
|
241
228
|
canCreateRoles={canCreateRoles.allowed}
|
|
242
229
|
canUpdateRoles={canUpdateRoles.allowed}
|
|
243
230
|
canDeleteRoles={canDeleteRoles.allowed}
|
|
244
|
-
onDataChange={
|
|
231
|
+
onDataChange={handleDataChange}
|
|
245
232
|
/>
|
|
246
233
|
</TabPanel>
|
|
247
234
|
|
|
248
235
|
<TabPanel id="teams" activeTab={activeTab}>
|
|
249
236
|
<TeamsTab
|
|
250
|
-
users={
|
|
237
|
+
users={typedUsers}
|
|
251
238
|
canReadTeams={canReadTeams.allowed}
|
|
252
239
|
canManageTeams={canManageTeams.allowed}
|
|
253
|
-
onDataChange={
|
|
240
|
+
onDataChange={handleDataChange}
|
|
254
241
|
/>
|
|
255
242
|
</TabPanel>
|
|
256
243
|
|
|
257
244
|
<TabPanel id="strategies" activeTab={activeTab}>
|
|
258
245
|
<StrategiesTab
|
|
259
|
-
strategies={strategies}
|
|
246
|
+
strategies={strategies as AuthStrategy[]}
|
|
260
247
|
canManageStrategies={canManageStrategies.allowed}
|
|
261
248
|
canManageRegistration={canManageRegistration.allowed}
|
|
262
|
-
onDataChange={
|
|
249
|
+
onDataChange={handleDataChange}
|
|
263
250
|
/>
|
|
264
251
|
</TabPanel>
|
|
265
252
|
|
|
266
253
|
<TabPanel id="applications" activeTab={activeTab}>
|
|
267
254
|
<ApplicationsTab
|
|
268
|
-
roles={roles}
|
|
255
|
+
roles={roles as Role[]}
|
|
269
256
|
canManageApplications={canManageApplications.allowed}
|
|
270
257
|
/>
|
|
271
258
|
</TabPanel>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { Link
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
3
|
import { LogIn, LogOut, AlertCircle } from "lucide-react";
|
|
4
4
|
import {
|
|
5
5
|
useApi,
|
|
6
6
|
ExtensionSlot,
|
|
7
7
|
pluginRegistry,
|
|
8
|
-
|
|
8
|
+
usePluginClient,
|
|
9
9
|
UserMenuItemsSlot,
|
|
10
10
|
UserMenuItemsBottomSlot,
|
|
11
11
|
UserMenuItemsContext,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
} from "@checkstack/ui";
|
|
39
39
|
import { authApiRef } from "../api";
|
|
40
40
|
import { useEnabledStrategies } from "../hooks/useEnabledStrategies";
|
|
41
|
-
import {
|
|
41
|
+
import { useAccessRules } from "../hooks/useAccessRules";
|
|
42
42
|
import { useAuthClient } from "../lib/auth-client";
|
|
43
43
|
import { SocialProviderButton } from "./SocialProviderButton";
|
|
44
44
|
import { useEffect } from "react";
|
|
@@ -47,24 +47,16 @@ export const LoginPage = () => {
|
|
|
47
47
|
const [email, setEmail] = useState("");
|
|
48
48
|
const [password, setPassword] = useState("");
|
|
49
49
|
const [loading, setLoading] = useState(false);
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
const authApi = useApi(authApiRef);
|
|
52
|
-
const
|
|
53
|
-
const authRpcClient = rpcApi.forPlugin(AuthApi);
|
|
52
|
+
const authClient = usePluginClient(AuthApi);
|
|
54
53
|
const { strategies, loading: strategiesLoading } = useEnabledStrategies();
|
|
55
|
-
const [registrationAllowed, setRegistrationAllowed] = useState<boolean>(true);
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
.catch((error: Error) => {
|
|
64
|
-
console.error("Failed to check registration status:", error);
|
|
65
|
-
setRegistrationAllowed(true);
|
|
66
|
-
});
|
|
67
|
-
}, [authRpcClient]);
|
|
55
|
+
// Query: Registration status
|
|
56
|
+
const { data: registrationData } = authClient.getRegistrationStatus.useQuery(
|
|
57
|
+
{}
|
|
58
|
+
);
|
|
59
|
+
const registrationAllowed = registrationData?.allowRegistration ?? true;
|
|
68
60
|
|
|
69
61
|
const handleCredentialLogin = async (e: React.FormEvent) => {
|
|
70
62
|
e.preventDefault();
|
|
@@ -74,7 +66,8 @@ export const LoginPage = () => {
|
|
|
74
66
|
if (error) {
|
|
75
67
|
console.error("Login failed:", error);
|
|
76
68
|
} else {
|
|
77
|
-
|
|
69
|
+
// Use full page navigation to ensure session/permissions state refreshes
|
|
70
|
+
globalThis.location.href = "/";
|
|
78
71
|
}
|
|
79
72
|
} finally {
|
|
80
73
|
setLoading(false);
|
|
@@ -273,7 +266,7 @@ export const LogoutMenuItem = (_props: UserMenuItemsContext) => {
|
|
|
273
266
|
export const LoginNavbarAction = () => {
|
|
274
267
|
const authApi = useApi(authApiRef);
|
|
275
268
|
const { data: session, isPending } = authApi.useSession();
|
|
276
|
-
const {
|
|
269
|
+
const { accessRules, loading: accessRulesLoading } = useAccessRules();
|
|
277
270
|
const authClient = useAuthClient();
|
|
278
271
|
const [hasCredentialAccount, setHasCredentialAccount] =
|
|
279
272
|
useState<boolean>(false);
|
|
@@ -295,7 +288,7 @@ export const LoginNavbarAction = () => {
|
|
|
295
288
|
});
|
|
296
289
|
}, [session?.user, authClient]);
|
|
297
290
|
|
|
298
|
-
if (isPending ||
|
|
291
|
+
if (isPending || accessRulesLoading || credentialLoading) {
|
|
299
292
|
return <div className="w-20 h-9 bg-muted animate-pulse rounded-full" />;
|
|
300
293
|
}
|
|
301
294
|
|
|
@@ -306,7 +299,7 @@ export const LoginNavbarAction = () => {
|
|
|
306
299
|
);
|
|
307
300
|
const hasBottomItems = bottomExtensions.length > 0;
|
|
308
301
|
const menuContext: UserMenuItemsContext = {
|
|
309
|
-
|
|
302
|
+
accessRules,
|
|
310
303
|
hasCredentialAccount,
|
|
311
304
|
};
|
|
312
305
|
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Link
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
3
|
import { AlertCircle } from "lucide-react";
|
|
4
|
-
import { useApi,
|
|
4
|
+
import { useApi, usePluginClient } from "@checkstack/frontend-api";
|
|
5
5
|
import { authApiRef } from "../api";
|
|
6
|
-
import {
|
|
7
|
-
AuthApi,
|
|
8
|
-
authRoutes,
|
|
9
|
-
passwordSchema,
|
|
10
|
-
} from "@checkstack/auth-common";
|
|
6
|
+
import { AuthApi, authRoutes, passwordSchema } from "@checkstack/auth-common";
|
|
11
7
|
import { resolveRoute } from "@checkstack/common";
|
|
12
8
|
import {
|
|
13
9
|
Button,
|
|
@@ -35,14 +31,11 @@ export const RegisterPage = () => {
|
|
|
35
31
|
const [password, setPassword] = useState("");
|
|
36
32
|
const [loading, setLoading] = useState(false);
|
|
37
33
|
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
|
38
|
-
|
|
34
|
+
|
|
39
35
|
const authApi = useApi(authApiRef);
|
|
40
|
-
const
|
|
41
|
-
const authRpcClient = rpcApi.forPlugin(AuthApi);
|
|
36
|
+
const authClient = usePluginClient(AuthApi);
|
|
42
37
|
const { strategies, loading: strategiesLoading } = useEnabledStrategies();
|
|
43
|
-
const
|
|
44
|
-
const [checkingRegistration, setCheckingRegistration] = useState(true);
|
|
45
|
-
const authClient = useAuthClient();
|
|
38
|
+
const authBetterClient = useAuthClient();
|
|
46
39
|
|
|
47
40
|
// Validate password on change
|
|
48
41
|
useEffect(() => {
|
|
@@ -58,19 +51,10 @@ export const RegisterPage = () => {
|
|
|
58
51
|
}
|
|
59
52
|
}, [password]);
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
setRegistrationAllowed(allowRegistration);
|
|
66
|
-
})
|
|
67
|
-
.catch((error: Error) => {
|
|
68
|
-
console.error("Failed to check registration status:", error);
|
|
69
|
-
// Default to allowed on error to avoid blocking
|
|
70
|
-
setRegistrationAllowed(true);
|
|
71
|
-
})
|
|
72
|
-
.finally(() => setCheckingRegistration(false));
|
|
73
|
-
}, [authRpcClient]);
|
|
54
|
+
// Query: Registration status
|
|
55
|
+
const { data: registrationData, isLoading: checkingRegistration } =
|
|
56
|
+
authClient.getRegistrationStatus.useQuery({});
|
|
57
|
+
const registrationAllowed = registrationData?.allowRegistration ?? true;
|
|
74
58
|
|
|
75
59
|
const handleCredentialRegister = async (e: React.FormEvent) => {
|
|
76
60
|
e.preventDefault();
|
|
@@ -83,11 +67,16 @@ export const RegisterPage = () => {
|
|
|
83
67
|
|
|
84
68
|
setLoading(true);
|
|
85
69
|
try {
|
|
86
|
-
const res = await
|
|
70
|
+
const res = await authBetterClient.signUp.email({
|
|
71
|
+
name,
|
|
72
|
+
email,
|
|
73
|
+
password,
|
|
74
|
+
});
|
|
87
75
|
if (res.error) {
|
|
88
76
|
console.error("Registration failed:", res.error);
|
|
89
77
|
} else {
|
|
90
|
-
|
|
78
|
+
// Use full page navigation to ensure session state refreshes in navbar
|
|
79
|
+
globalThis.location.href = "/";
|
|
91
80
|
}
|
|
92
81
|
} catch (error) {
|
|
93
82
|
console.error("Registration failed:", error);
|