@strapi-community/plugin-better-auth-dashboard 1.0.0-alpha.9 → 1.0.0-beta.2
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/README.md +65 -0
- package/dist/admin/Avatar-CdT7eBCh.js +39 -0
- package/dist/admin/Avatar-zlMTAZh3.mjs +40 -0
- package/dist/admin/Root-CgVBJuoM.mjs +365 -0
- package/dist/admin/Root-CsiOnzlB.js +367 -0
- package/dist/admin/cs-C_jSgeON.js +416 -0
- package/dist/admin/cs-wtnknvBC.mjs +416 -0
- package/dist/admin/de-Cg5HyoCY.js +416 -0
- package/dist/admin/de-DnnVdUxk.mjs +416 -0
- package/dist/admin/en-B3utEgMT.mjs +416 -0
- package/dist/admin/en-Cz1IJEvp.js +416 -0
- package/dist/admin/es-CGA0Tslt.js +416 -0
- package/dist/admin/es-RoCEriJL.mjs +416 -0
- package/dist/admin/fr-Ca74t21t.js +416 -0
- package/dist/admin/fr-CtmuSfiQ.mjs +416 -0
- package/dist/admin/index-BCBu-0nr.js +457 -0
- package/dist/admin/index-BI2WIsds.mjs +458 -0
- package/dist/admin/index-CPSoAbZt.js +2321 -0
- package/dist/admin/index-D9ttdrT3.js +2716 -0
- package/dist/admin/index-DQdn5s8q.js +1184 -0
- package/dist/admin/index-DvfwvCj0.mjs +1182 -0
- package/dist/admin/index-VyHXoQ6p.mjs +2321 -0
- package/dist/admin/index-gFqHlMLP.mjs +2716 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/admin/it-BAB9hDdp.mjs +416 -0
- package/dist/admin/it-dYT2_ERV.js +416 -0
- package/dist/admin/nl-CVqSbUU9.js +416 -0
- package/dist/admin/nl-CYv26PHv.mjs +416 -0
- package/dist/admin/pl-Bc0cS2mD.mjs +416 -0
- package/dist/admin/pl-HYF4BxRv.js +416 -0
- package/dist/admin/pt-DDUGyLtb.js +416 -0
- package/dist/admin/pt-DZyyK3EG.mjs +416 -0
- package/dist/admin/useModelSchema-BogQah8f.js +756 -0
- package/dist/admin/useModelSchema-C6bxNjW4.mjs +755 -0
- package/dist/server/index.js +220 -10
- package/dist/server/index.mjs +220 -10
- package/package.json +5 -4
- package/dist/admin/Root-BYdYQBE5.mjs +0 -5742
- package/dist/admin/Root-D_vCtQoC.js +0 -5744
- package/dist/admin/index-BMTIuxZI.mjs +0 -96
- package/dist/admin/index-CkSsWnBx.js +0 -95
package/README.md
CHANGED
|
@@ -64,6 +64,23 @@ export const auth = betterAuth({
|
|
|
64
64
|
});
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
To enable the "Send verification email" and "Send password reset" actions in the user detail drawer, configure the dashboard plugin itself in `config/plugins.js`/`.ts`:
|
|
68
|
+
|
|
69
|
+
```typescript title="config/plugins.ts"
|
|
70
|
+
export default {
|
|
71
|
+
"better-auth-dashboard": {
|
|
72
|
+
config: {
|
|
73
|
+
// Absolute URL of your public-facing client app. Used as the
|
|
74
|
+
// callback destination for email verification / password reset
|
|
75
|
+
// links sent from the dashboard. Unset by default — the two
|
|
76
|
+
// actions above stay disabled until this is configured, rather
|
|
77
|
+
// than guessing at a URL (e.g. the admin panel's own origin).
|
|
78
|
+
email_callback_url: process.env.BETTER_AUTH_DASHBOARD_CALLBACK_URL,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
67
84
|
### Start Strapi
|
|
68
85
|
|
|
69
86
|
```bash
|
|
@@ -80,6 +97,54 @@ The dashboard is now available in the Strapi admin panel.
|
|
|
80
97
|
| **Users** | Searchable user table with create, edit, delete, ban, and session revoke per user |
|
|
81
98
|
| **Organizations** | Organization list with member management (requires Better Auth `organization` plugin) |
|
|
82
99
|
|
|
100
|
+
## Extending the dashboard
|
|
101
|
+
|
|
102
|
+
Other Strapi admin plugins can add sections and lazy-loaded screens through the dashboard plugin's admin API. Call the API during your plugin's `bootstrap` lifecycle:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
export default {
|
|
106
|
+
bootstrap(app) {
|
|
107
|
+
const plugin = app.getPlugin("better-auth-dashboard");
|
|
108
|
+
if (!plugin) return;
|
|
109
|
+
|
|
110
|
+
const dashboard = plugin.apis;
|
|
111
|
+
|
|
112
|
+
dashboard.addDashSection({
|
|
113
|
+
id: "my-plugin",
|
|
114
|
+
intlLabel: {
|
|
115
|
+
id: "my-plugin.dashboard.section",
|
|
116
|
+
defaultMessage: "My plugin",
|
|
117
|
+
},
|
|
118
|
+
priority: 20,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
dashboard.addDashLink("my-plugin", {
|
|
122
|
+
id: "my-plugin.audit-log",
|
|
123
|
+
intlLabel: {
|
|
124
|
+
id: "my-plugin.dashboard.audit-log",
|
|
125
|
+
defaultMessage: "Audit log",
|
|
126
|
+
},
|
|
127
|
+
to: "/audit-log",
|
|
128
|
+
Component: () => import("./pages/AuditLog"),
|
|
129
|
+
permissions: [],
|
|
130
|
+
isAvailable: async () => checkAuditLogAvailability(),
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Available APIs:
|
|
137
|
+
|
|
138
|
+
| API | Description |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `addDashSection(section)` | Creates a navigation section. `priority` controls section order. |
|
|
141
|
+
| `addDashLink(section, link)` | Registers a lazy route and adds its link to a section. Supports Strapi admin `permissions` and sync or async `isAvailable`. |
|
|
142
|
+
| `getDashLinks()` | Returns the live section and link registry. Treat the result as read-only. |
|
|
143
|
+
|
|
144
|
+
`isAvailable` defaults to `true`. Its result is cached for five minutes, combined with permission checks, and applied to both navigation and direct route access. Errors are treated as unavailable.
|
|
145
|
+
|
|
146
|
+
See the [Dashboard documentation](https://strapi-community.github.io/plugin-better-auth/docs/better-auth/dashboard#dashboard-screen-api) for the complete API reference and extension guidance.
|
|
147
|
+
|
|
83
148
|
## Requirements
|
|
84
149
|
|
|
85
150
|
- `@strapi-community/plugin-better-auth` installed and configured
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const designSystem = require("@strapi/design-system");
|
|
4
|
+
function getInitials(name) {
|
|
5
|
+
const parts = name.trim().split(/\s+/);
|
|
6
|
+
if (parts.length === 0 || !parts[0]) return "?";
|
|
7
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
8
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
9
|
+
}
|
|
10
|
+
function Avatar({
|
|
11
|
+
name,
|
|
12
|
+
src,
|
|
13
|
+
size = 32
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
16
|
+
designSystem.Avatar.Item,
|
|
17
|
+
{
|
|
18
|
+
src: src ?? void 0,
|
|
19
|
+
alt: "",
|
|
20
|
+
delayMs: 0,
|
|
21
|
+
fallback: (
|
|
22
|
+
// Block + line-height 1 escapes the DS Typography wrapper's taller line box
|
|
23
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
24
|
+
"span",
|
|
25
|
+
{
|
|
26
|
+
style: {
|
|
27
|
+
display: "block",
|
|
28
|
+
lineHeight: 1,
|
|
29
|
+
fontSize: Math.round(size * 0.38)
|
|
30
|
+
},
|
|
31
|
+
children: getInitials(name || "?")
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
style: { width: size, height: size, flexShrink: 0 }
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
exports.Avatar = Avatar;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar as Avatar$1 } from "@strapi/design-system";
|
|
3
|
+
function getInitials(name) {
|
|
4
|
+
const parts = name.trim().split(/\s+/);
|
|
5
|
+
if (parts.length === 0 || !parts[0]) return "?";
|
|
6
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
7
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
8
|
+
}
|
|
9
|
+
function Avatar({
|
|
10
|
+
name,
|
|
11
|
+
src,
|
|
12
|
+
size = 32
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
Avatar$1.Item,
|
|
16
|
+
{
|
|
17
|
+
src: src ?? void 0,
|
|
18
|
+
alt: "",
|
|
19
|
+
delayMs: 0,
|
|
20
|
+
fallback: (
|
|
21
|
+
// Block + line-height 1 escapes the DS Typography wrapper's taller line box
|
|
22
|
+
/* @__PURE__ */ jsx(
|
|
23
|
+
"span",
|
|
24
|
+
{
|
|
25
|
+
style: {
|
|
26
|
+
display: "block",
|
|
27
|
+
lineHeight: 1,
|
|
28
|
+
fontSize: Math.round(size * 0.38)
|
|
29
|
+
},
|
|
30
|
+
children: getInitials(name || "?")
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
),
|
|
34
|
+
style: { width: size, height: size, flexShrink: 0 }
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
Avatar as A
|
|
40
|
+
};
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useState, useEffect, useMemo } from "react";
|
|
3
|
+
import { useQueryClient, QueryClient, QueryClientProvider } from "react-query";
|
|
4
|
+
import { NavLink, Outlet, useMatch, Navigate } from "react-router-dom";
|
|
5
|
+
import { g as getTranslation, a as getDashLinks, P as PLUGIN_ID, b as getDashLinkAvailabilityQueryKey, r as resolveDashLinkAvailability, D as DASH_LINK_AVAILABILITY_STALE_TIME } from "./index-BI2WIsds.mjs";
|
|
6
|
+
import { Typography, ScrollArea, Box, Flex, Badge, SubNav as SubNav$1, Divider } from "@strapi/design-system";
|
|
7
|
+
import { useAuth, Layouts, Page, BackButton } from "@strapi/strapi/admin";
|
|
8
|
+
import { useIntl } from "react-intl";
|
|
9
|
+
import "@strapi/icons";
|
|
10
|
+
import styled from "styled-components";
|
|
11
|
+
const HEIGHT_TOP_NAVIGATION = "6.4rem";
|
|
12
|
+
const HEIGHT_TOP_NAVIGATION_MEDIUM = "5.6rem";
|
|
13
|
+
const WIDTH_SIDE_NAVIGATION = "23.2rem";
|
|
14
|
+
const MainSubNav = styled(SubNav$1)`
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
background-color: ${({ theme }) => theme.colors.neutral0};
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
border-right: 0;
|
|
22
|
+
box-shadow: none;
|
|
23
|
+
position: relative;
|
|
24
|
+
|
|
25
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
26
|
+
width: ${WIDTH_SIDE_NAVIGATION};
|
|
27
|
+
position: sticky;
|
|
28
|
+
top: 0;
|
|
29
|
+
border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
|
|
30
|
+
overscroll-behavior: contain;
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
const Main = ({ children, ...props }) => /* @__PURE__ */ jsx(MainSubNav, { ...props, children });
|
|
34
|
+
const StyledLink = styled(NavLink)`
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
height: 32px;
|
|
40
|
+
color: ${({ theme }) => theme.colors.neutral800};
|
|
41
|
+
|
|
42
|
+
&.active > div {
|
|
43
|
+
background-color: ${({ theme }) => theme.colors.primary100};
|
|
44
|
+
color: ${({ theme }) => theme.colors.primary700};
|
|
45
|
+
font-weight: 500;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&:hover.active > div {
|
|
49
|
+
background-color: ${({ theme }) => theme.colors.primary100};
|
|
50
|
+
color: ${({ theme }) => theme.colors.primary700};
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&:hover > div {
|
|
55
|
+
background-color: ${({ theme }) => theme.colors.neutral100};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&:focus-visible {
|
|
59
|
+
outline-offset: -2px;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const Link = (props) => {
|
|
63
|
+
const { to, label, endAction, handleClick, ...rest } = props;
|
|
64
|
+
return /* @__PURE__ */ jsx(StyledLink, { to, ...rest, onClick: handleClick, children: /* @__PURE__ */ jsx(
|
|
65
|
+
Box,
|
|
66
|
+
{
|
|
67
|
+
width: "100%",
|
|
68
|
+
paddingLeft: 3,
|
|
69
|
+
paddingRight: 3,
|
|
70
|
+
paddingTop: { initial: 1, large: 0 },
|
|
71
|
+
paddingBottom: { initial: 1, large: 0 },
|
|
72
|
+
borderRadius: 1,
|
|
73
|
+
children: /* @__PURE__ */ jsxs(
|
|
74
|
+
Flex,
|
|
75
|
+
{
|
|
76
|
+
justifyContent: "space-between",
|
|
77
|
+
width: "100%",
|
|
78
|
+
gap: { initial: 2, large: 1 },
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx(
|
|
81
|
+
Typography,
|
|
82
|
+
{
|
|
83
|
+
tag: "div",
|
|
84
|
+
lineHeight: "32px",
|
|
85
|
+
width: { initial: "80dvw", medium: "100%" },
|
|
86
|
+
overflow: "hidden",
|
|
87
|
+
style: { textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
88
|
+
children: label
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
/* @__PURE__ */ jsx(Flex, { gap: 2, children: endAction })
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
) });
|
|
97
|
+
};
|
|
98
|
+
const StyledHeader = styled(Flex)`
|
|
99
|
+
flex: 0 0 ${HEIGHT_TOP_NAVIGATION};
|
|
100
|
+
height: ${HEIGHT_TOP_NAVIGATION};
|
|
101
|
+
|
|
102
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
103
|
+
flex: 0 0 ${HEIGHT_TOP_NAVIGATION_MEDIUM};
|
|
104
|
+
height: ${HEIGHT_TOP_NAVIGATION_MEDIUM};
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
const Header = ({ label }) => {
|
|
108
|
+
return /* @__PURE__ */ jsx(
|
|
109
|
+
StyledHeader,
|
|
110
|
+
{
|
|
111
|
+
justifyContent: "space-between",
|
|
112
|
+
paddingLeft: 5,
|
|
113
|
+
paddingRight: 5,
|
|
114
|
+
children: /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: label })
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
const Sections = ({
|
|
119
|
+
children,
|
|
120
|
+
...props
|
|
121
|
+
}) => {
|
|
122
|
+
return /* @__PURE__ */ jsx(
|
|
123
|
+
Box,
|
|
124
|
+
{
|
|
125
|
+
paddingTop: { initial: 5, large: 4 },
|
|
126
|
+
paddingBottom: { initial: 5, large: 4 },
|
|
127
|
+
paddingLeft: { initial: 3, large: 0 },
|
|
128
|
+
paddingRight: { initial: 3, large: 0 },
|
|
129
|
+
maxWidth: { initial: "100%", medium: WIDTH_SIDE_NAVIGATION },
|
|
130
|
+
children: /* @__PURE__ */ jsx(Flex, { tag: "ul", gap: 6, direction: "column", alignItems: "stretch", ...props, children: children.map((child, index) => {
|
|
131
|
+
return /* @__PURE__ */ jsx("li", { children: child }, index);
|
|
132
|
+
}) })
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
const Section = ({
|
|
137
|
+
label,
|
|
138
|
+
children,
|
|
139
|
+
badgeLabel
|
|
140
|
+
}) => {
|
|
141
|
+
const listId = useId();
|
|
142
|
+
return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
Box,
|
|
145
|
+
{
|
|
146
|
+
paddingLeft: {
|
|
147
|
+
initial: 3,
|
|
148
|
+
large: 5
|
|
149
|
+
},
|
|
150
|
+
paddingRight: {
|
|
151
|
+
initial: 3,
|
|
152
|
+
large: 5
|
|
153
|
+
},
|
|
154
|
+
children: /* @__PURE__ */ jsxs(Flex, { position: "relative", justifyContent: "space-between", gap: 2, children: [
|
|
155
|
+
/* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: label }) }) }),
|
|
156
|
+
/* @__PURE__ */ jsx(Flex, { gap: 1, children: badgeLabel && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: badgeLabel }) })
|
|
157
|
+
] })
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
Flex,
|
|
162
|
+
{
|
|
163
|
+
tag: "ol",
|
|
164
|
+
id: listId,
|
|
165
|
+
direction: "column",
|
|
166
|
+
gap: { initial: 2, large: "2px" },
|
|
167
|
+
alignItems: "stretch",
|
|
168
|
+
marginLeft: {
|
|
169
|
+
initial: 0,
|
|
170
|
+
large: 2
|
|
171
|
+
},
|
|
172
|
+
marginRight: {
|
|
173
|
+
initial: 0,
|
|
174
|
+
large: 2
|
|
175
|
+
},
|
|
176
|
+
children: children.map((child, index) => {
|
|
177
|
+
return /* @__PURE__ */ jsx("li", { children: child }, index);
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
] });
|
|
182
|
+
};
|
|
183
|
+
styled.button`
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
width: 100%;
|
|
186
|
+
border: none;
|
|
187
|
+
padding: 0;
|
|
188
|
+
background: transparent;
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
border-radius: ${({ theme }) => theme.borderRadius};
|
|
192
|
+
padding-left: ${({ theme }) => theme.spaces[3]};
|
|
193
|
+
padding-right: ${({ theme }) => theme.spaces[3]};
|
|
194
|
+
padding-top: ${({ theme }) => theme.spaces[2]};
|
|
195
|
+
padding-bottom: ${({ theme }) => theme.spaces[2]};
|
|
196
|
+
|
|
197
|
+
&:hover {
|
|
198
|
+
background-color: ${({ theme }) => theme.colors.neutral100};
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
styled.li`
|
|
202
|
+
${StyledLink} > div {
|
|
203
|
+
padding-left: 36px;
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
styled(Box)`
|
|
207
|
+
width: 100%;
|
|
208
|
+
|
|
209
|
+
${MainSubNav} {
|
|
210
|
+
background-color: transparent;
|
|
211
|
+
border-right: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
|
|
217
|
+
${MainSubNav} {
|
|
218
|
+
top: 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
const Content = ({ children }) => {
|
|
223
|
+
return /* @__PURE__ */ jsx(ScrollArea, { children });
|
|
224
|
+
};
|
|
225
|
+
const SubNav = {
|
|
226
|
+
Main,
|
|
227
|
+
Content,
|
|
228
|
+
Header,
|
|
229
|
+
Link,
|
|
230
|
+
Sections,
|
|
231
|
+
Section
|
|
232
|
+
};
|
|
233
|
+
const Nav = ({ isFullPage = false }) => {
|
|
234
|
+
const { formatMessage } = useIntl();
|
|
235
|
+
const queryClient = useQueryClient();
|
|
236
|
+
const checkUserHasPermissions = useAuth(
|
|
237
|
+
"DashboardNav",
|
|
238
|
+
(state) => state.checkUserHasPermissions
|
|
239
|
+
);
|
|
240
|
+
const dashLinks = getDashLinks();
|
|
241
|
+
const headerLabel = formatMessage({
|
|
242
|
+
id: getTranslation("components.nav.header"),
|
|
243
|
+
defaultMessage: "Authentication"
|
|
244
|
+
});
|
|
245
|
+
const [sections, setSections] = useState([]);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
let isCurrent = true;
|
|
248
|
+
const loadAuthorizedSections = async () => {
|
|
249
|
+
const authorizedSections = await Promise.all(
|
|
250
|
+
Object.values(dashLinks).map(async (section) => ({
|
|
251
|
+
...section,
|
|
252
|
+
links: (await Promise.all(
|
|
253
|
+
section.links.map(async (link) => {
|
|
254
|
+
const permissions = link.permissions ?? [];
|
|
255
|
+
const [hasPermission, isAvailable] = await Promise.all([
|
|
256
|
+
permissions.length === 0 ? true : checkUserHasPermissions(permissions).then(
|
|
257
|
+
(authorizedPermissions) => authorizedPermissions.length > 0
|
|
258
|
+
),
|
|
259
|
+
queryClient.fetchQuery(
|
|
260
|
+
getDashLinkAvailabilityQueryKey(link),
|
|
261
|
+
() => resolveDashLinkAvailability(link),
|
|
262
|
+
{ staleTime: DASH_LINK_AVAILABILITY_STALE_TIME }
|
|
263
|
+
)
|
|
264
|
+
]);
|
|
265
|
+
return hasPermission && isAvailable ? link : null;
|
|
266
|
+
})
|
|
267
|
+
)).filter((link) => link !== null)
|
|
268
|
+
}))
|
|
269
|
+
);
|
|
270
|
+
if (isCurrent) {
|
|
271
|
+
setSections(
|
|
272
|
+
authorizedSections.filter((section) => section.links.length > 0).sort(
|
|
273
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0) || a.id.localeCompare(b.id)
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
loadAuthorizedSections();
|
|
279
|
+
return () => {
|
|
280
|
+
isCurrent = false;
|
|
281
|
+
};
|
|
282
|
+
}, [checkUserHasPermissions, dashLinks, queryClient]);
|
|
283
|
+
return /* @__PURE__ */ jsxs(SubNav.Main, { children: [
|
|
284
|
+
!isFullPage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
285
|
+
/* @__PURE__ */ jsx(SubNav.Header, { label: headerLabel }),
|
|
286
|
+
/* @__PURE__ */ jsx(Divider, {})
|
|
287
|
+
] }),
|
|
288
|
+
/* @__PURE__ */ jsxs(SubNav.Content, { children: [
|
|
289
|
+
isFullPage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
290
|
+
/* @__PURE__ */ jsx(SubNav.Header, { label: headerLabel }),
|
|
291
|
+
/* @__PURE__ */ jsx(Divider, {})
|
|
292
|
+
] }),
|
|
293
|
+
/* @__PURE__ */ jsx(SubNav.Sections, { "data-testid": "main-nav", children: sections.map((section) => /* @__PURE__ */ jsx(
|
|
294
|
+
SubNav.Section,
|
|
295
|
+
{
|
|
296
|
+
label: formatMessage(section.intlLabel),
|
|
297
|
+
children: section.links.map((link) => {
|
|
298
|
+
return /* @__PURE__ */ jsx(
|
|
299
|
+
SubNav.Link,
|
|
300
|
+
{
|
|
301
|
+
to: `/plugins/${PLUGIN_ID}/${link.to.replace(/^\/+/, "")}`,
|
|
302
|
+
label: formatMessage(link.intlLabel),
|
|
303
|
+
"data-testid": `nav-${link.id}`,
|
|
304
|
+
children: formatMessage(link.intlLabel)
|
|
305
|
+
},
|
|
306
|
+
link.id
|
|
307
|
+
);
|
|
308
|
+
})
|
|
309
|
+
},
|
|
310
|
+
section.id
|
|
311
|
+
)) })
|
|
312
|
+
] })
|
|
313
|
+
] });
|
|
314
|
+
};
|
|
315
|
+
const RESPONSIVE_DEFAULT_SPACING = {
|
|
316
|
+
initial: 4,
|
|
317
|
+
medium: 6,
|
|
318
|
+
large: 10
|
|
319
|
+
};
|
|
320
|
+
const App = () => {
|
|
321
|
+
const { formatMessage } = useIntl();
|
|
322
|
+
return /* @__PURE__ */ jsx(
|
|
323
|
+
"div",
|
|
324
|
+
{
|
|
325
|
+
"data-testid": "dashboard-root",
|
|
326
|
+
style: { display: "flex", flex: "1 1 auto", minHeight: "100vh" },
|
|
327
|
+
children: /* @__PURE__ */ jsxs(Layouts.Root, { sideNav: /* @__PURE__ */ jsx(Nav, {}), children: [
|
|
328
|
+
/* @__PURE__ */ jsx(Page.Title, { children: formatMessage({
|
|
329
|
+
id: getTranslation("common.page.title"),
|
|
330
|
+
defaultMessage: "Authentication"
|
|
331
|
+
}) }),
|
|
332
|
+
/* @__PURE__ */ jsx(
|
|
333
|
+
Box,
|
|
334
|
+
{
|
|
335
|
+
display: { initial: "block", medium: "none" },
|
|
336
|
+
paddingLeft: RESPONSIVE_DEFAULT_SPACING,
|
|
337
|
+
paddingRight: RESPONSIVE_DEFAULT_SPACING,
|
|
338
|
+
paddingTop: RESPONSIVE_DEFAULT_SPACING,
|
|
339
|
+
children: /* @__PURE__ */ jsx(BackButton, { fallback: "/plugins/better-auth-dashboard" })
|
|
340
|
+
}
|
|
341
|
+
),
|
|
342
|
+
/* @__PURE__ */ jsx(Outlet, {})
|
|
343
|
+
] })
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
function Root() {
|
|
348
|
+
const isDashboardIndex = useMatch({
|
|
349
|
+
path: `/plugins/${PLUGIN_ID}`,
|
|
350
|
+
end: true
|
|
351
|
+
});
|
|
352
|
+
const queryClient = useMemo(
|
|
353
|
+
() => new QueryClient({
|
|
354
|
+
defaultOptions: {
|
|
355
|
+
queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 3e4 }
|
|
356
|
+
}
|
|
357
|
+
}),
|
|
358
|
+
[]
|
|
359
|
+
);
|
|
360
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: isDashboardIndex ? /* @__PURE__ */ jsx(Navigate, { to: "overview", replace: true }) : /* @__PURE__ */ jsx(App, {}) });
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
Root,
|
|
364
|
+
Root as default
|
|
365
|
+
};
|