@greatapps/greatauth-ui 0.3.17 → 0.3.19
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/chunk-5XZPBJRT.js +28 -0
- package/dist/chunk-5XZPBJRT.js.map +1 -0
- package/dist/chunk-OS2C4IWR.js +1790 -0
- package/dist/chunk-OS2C4IWR.js.map +1 -0
- package/dist/index.js +390 -1849
- package/dist/index.js.map +1 -1
- package/dist/middleware.js +4 -22
- package/dist/middleware.js.map +1 -1
- package/dist/ui.js +493 -2055
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/components/app-header.tsx +2 -1
- package/src/components/app-sidebar.tsx +7 -7
- package/src/components/users/users-page.tsx +8 -5
- package/src/hooks/use-auth.ts +38 -33
- package/src/hooks/use-mobile.ts +5 -3
- package/src/hooks/use-users.ts +1 -1
package/package.json
CHANGED
|
@@ -25,6 +25,7 @@ export function AppHeader({ config }: AppHeaderProps) {
|
|
|
25
25
|
const pathname = usePathname();
|
|
26
26
|
const segments = pathname.split("/").filter(Boolean);
|
|
27
27
|
|
|
28
|
+
const routeLabelsJson = JSON.stringify(config.routeLabels);
|
|
28
29
|
const breadcrumbs = useMemo(() => {
|
|
29
30
|
const items: { label: string; href: string }[] = [];
|
|
30
31
|
for (let i = 0; i < segments.length; i++) {
|
|
@@ -35,7 +36,7 @@ export function AppHeader({ config }: AppHeaderProps) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
return items;
|
|
38
|
-
}, [segments,
|
|
39
|
+
}, [segments, routeLabelsJson]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
39
40
|
|
|
40
41
|
const isLast = (i: number) => i === breadcrumbs.length - 1;
|
|
41
42
|
const showEllipsis = breadcrumbs.length > 2;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
3
|
+
import { useState, useEffect, memo } from "react";
|
|
4
4
|
import { usePathname, useRouter } from "next/navigation";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { ChevronsUpDown, ChevronRight, LogOut } from "lucide-react";
|
|
@@ -50,7 +50,7 @@ function getUserInitials(name: string, email: string): string {
|
|
|
50
50
|
return parts[0][0].toUpperCase();
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function SimpleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
|
|
53
|
+
const SimpleMenuItem = memo(function SimpleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
|
|
54
54
|
const isActive = item.isActive ?? pathname.startsWith(item.href);
|
|
55
55
|
const Icon = item.icon;
|
|
56
56
|
|
|
@@ -69,9 +69,9 @@ function SimpleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }
|
|
|
69
69
|
</SidebarMenuButton>
|
|
70
70
|
</SidebarMenuItem>
|
|
71
71
|
);
|
|
72
|
-
}
|
|
72
|
+
});
|
|
73
73
|
|
|
74
|
-
function CollapsibleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
|
|
74
|
+
const CollapsibleMenuItem = memo(function CollapsibleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
|
|
75
75
|
const Icon = item.icon;
|
|
76
76
|
const isParentActive = pathname.startsWith(item.href);
|
|
77
77
|
const isChildActive = item.children?.some((child) =>
|
|
@@ -82,8 +82,8 @@ function CollapsibleMenuItem({ item, pathname }: { item: MenuItem; pathname: str
|
|
|
82
82
|
const [open, setOpen] = useState(isActive);
|
|
83
83
|
|
|
84
84
|
useEffect(() => {
|
|
85
|
-
if (isActive) setOpen(
|
|
86
|
-
}, [isActive]);
|
|
85
|
+
if (open !== isActive) setOpen(isActive);
|
|
86
|
+
}, [isActive]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<Collapsible open={open} onOpenChange={setOpen} className="group/collapsible">
|
|
@@ -113,7 +113,7 @@ function CollapsibleMenuItem({ item, pathname }: { item: MenuItem; pathname: str
|
|
|
113
113
|
</SidebarMenuItem>
|
|
114
114
|
</Collapsible>
|
|
115
115
|
);
|
|
116
|
-
}
|
|
116
|
+
});
|
|
117
117
|
|
|
118
118
|
export function AppSidebar({ config }: AppSidebarProps) {
|
|
119
119
|
const pathname = usePathname();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
4
|
import type { ColumnDef } from "@tanstack/react-table";
|
|
5
5
|
import type { GauthUser, GauthUserHookConfig } from "../../types/users";
|
|
6
6
|
import { useUsers, useDeleteUser, useResetPassword } from "../../hooks/use-users";
|
|
@@ -165,10 +165,13 @@ export function UsersPage({ config, renderPhones }: UsersPageProps) {
|
|
|
165
165
|
);
|
|
166
166
|
}, [users, search]);
|
|
167
167
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
const onEdit = useCallback((u: GauthUser) => { setEditUser(u); setFormOpen(true); }, []);
|
|
169
|
+
const onDelete = useCallback((id: number) => setDeleteId(id), []);
|
|
170
|
+
const onResetPassword = useCallback((u: GauthUser) => setResetUser(u), []);
|
|
171
|
+
|
|
172
|
+
const columns = useMemo(
|
|
173
|
+
() => useColumns(onEdit, onDelete, onResetPassword),
|
|
174
|
+
[onEdit, onDelete, onResetPassword],
|
|
172
175
|
);
|
|
173
176
|
|
|
174
177
|
function handleDelete() {
|
package/src/hooks/use-auth.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useMemo } from "react";
|
|
3
4
|
import { useSession } from "../auth/auth-client";
|
|
4
5
|
|
|
5
6
|
interface JwtClaimsConfig {
|
|
@@ -28,49 +29,53 @@ export function createUseAuth(config?: UseAuthConfig) {
|
|
|
28
29
|
return function useAuth() {
|
|
29
30
|
const { data: sessionData, isPending, error } = useSession();
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
|
|
32
|
+
const result = useMemo(() => {
|
|
33
|
+
const session = sessionData?.session ?? null;
|
|
34
|
+
const user = sessionData?.user ?? null;
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const sessionObj = session as Record<string, unknown> | null;
|
|
37
|
+
const userObj = user as Record<string, unknown> | null;
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// Read from session first (per-login, isolated per app), fallback to user record (backward compat)
|
|
40
|
+
const gauthToken = (sessionObj?.gauthToken ?? userObj?.gauthToken ?? null) as string | null;
|
|
41
|
+
const idAccount = (sessionObj?.idAccount ?? userObj?.idAccount ?? null) as string | null;
|
|
42
|
+
const gauthProfile = (sessionObj?.gauthProfile ?? userObj?.gauthProfile ?? null) as Record<string, unknown> | null;
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
const res: Record<string, unknown> = {
|
|
45
|
+
session,
|
|
46
|
+
user,
|
|
47
|
+
gauthToken,
|
|
48
|
+
idAccount,
|
|
49
|
+
gauthProfile,
|
|
50
|
+
isLoading: isPending,
|
|
51
|
+
isAuthenticated: !!session && !!user,
|
|
52
|
+
error: error ?? null,
|
|
53
|
+
};
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (config?.jwtClaims && gauthToken) {
|
|
56
|
+
const payload = decodeJwtPayload(gauthToken);
|
|
57
|
+
if (payload) {
|
|
58
|
+
for (const [jwtKey, fieldName] of Object.entries(config.jwtClaims)) {
|
|
59
|
+
res[fieldName] = payload[jwtKey] ?? null;
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if (config?.accountIdOverride) {
|
|
65
|
+
const overriddenId = config.accountIdOverride(user as Record<string, unknown> | null);
|
|
66
|
+
if (overriddenId !== null) {
|
|
67
|
+
res.idAccount = overriddenId;
|
|
68
|
+
}
|
|
66
69
|
}
|
|
67
|
-
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
if (config?.extraUserFields && user) {
|
|
72
|
+
for (const field of config.extraUserFields) {
|
|
73
|
+
res[field] = (user as Record<string, unknown>)[field] ?? null;
|
|
74
|
+
}
|
|
72
75
|
}
|
|
73
|
-
|
|
76
|
+
|
|
77
|
+
return res;
|
|
78
|
+
}, [sessionData, isPending, error]);
|
|
74
79
|
|
|
75
80
|
return result;
|
|
76
81
|
};
|
package/src/hooks/use-mobile.ts
CHANGED
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
|
|
5
5
|
const MOBILE_BREAKPOINT = 768;
|
|
6
|
+
const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT - 1}px)`;
|
|
6
7
|
|
|
7
8
|
export function useIsMobile() {
|
|
8
|
-
const [isMobile, setIsMobile] = useState<boolean
|
|
9
|
+
const [isMobile, setIsMobile] = useState<boolean>(false);
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
11
|
-
|
|
12
|
+
if (typeof window === "undefined") return;
|
|
13
|
+
const mql = window.matchMedia(MOBILE_QUERY);
|
|
12
14
|
const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
13
15
|
mql.addEventListener("change", onChange);
|
|
14
16
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
15
17
|
return () => mql.removeEventListener("change", onChange);
|
|
16
18
|
}, []);
|
|
17
19
|
|
|
18
|
-
return
|
|
20
|
+
return isMobile;
|
|
19
21
|
}
|
package/src/hooks/use-users.ts
CHANGED
|
@@ -29,7 +29,7 @@ async function request<T>(url: string, token: string, options?: RequestInit): Pr
|
|
|
29
29
|
|
|
30
30
|
export function useUsers(config: GauthUserHookConfig, params?: Record<string, string>) {
|
|
31
31
|
return useQuery({
|
|
32
|
-
queryKey: ["greatauth", "users", config.accountId, params],
|
|
32
|
+
queryKey: ["greatauth", "users", config.accountId, JSON.stringify(params)],
|
|
33
33
|
queryFn: () => {
|
|
34
34
|
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
35
35
|
return request<GauthUser[]>(buildUrl(config, `users${qs}`), config.token!);
|