diff --git a/plugins/plugin-better-auth-dashboard/admin/src/components/Nav.tsx b/plugins/plugin-better-auth-dashboard/admin/src/components/Nav.tsx
new file mode 100644
index 0000000..f14a377
--- /dev/null
+++ b/plugins/plugin-better-auth-dashboard/admin/src/components/Nav.tsx
@@ -0,0 +1,96 @@
+import { Divider } from "@strapi/design-system";
+import { useIntl } from "react-intl";
+import { getTranslation } from "../utils/getTranslation";
+import { SubNav } from "./SubNav";
+
+const sections = [
+ {
+ id: "api-roles",
+ intlLabel: {
+ id: getTranslation("settings.general"),
+ defaultMessage: "General",
+ },
+ links: [
+ {
+ id: "overview",
+ intlLabel: {
+ id: getTranslation("settings.overview"),
+ defaultMessage: "Overview",
+ },
+ to: "/plugins/better-auth-dashboard/overview",
+ testid: "nav-overview",
+ },
+ {
+ id: "users",
+ intlLabel: {
+ id: getTranslation("settings.users"),
+ defaultMessage: "User Management",
+ },
+ to: "/plugins/better-auth-dashboard/users",
+ testid: "nav-users",
+ },
+ {
+ id: "organizations",
+ intlLabel: {
+ id: getTranslation("settings.organizations"),
+ defaultMessage: "Organization Management",
+ },
+ to: "/plugins/better-auth-dashboard/organizations",
+ testid: "nav-organizations",
+ },
+ ],
+ },
+];
+
+type NavProps = {
+ isFullPage?: boolean;
+ orgEnabled?: boolean;
+};
+
+const Nav = ({ isFullPage = false, orgEnabled = false }: NavProps) => {
+ const { formatMessage } = useIntl();
+
+ return (
+
+ {!isFullPage && (
+ <>
+
+
+ >
+ )}
+
+ {isFullPage && (
+ <>
+
+
+ >
+ )}
+
+ {sections.map((section) => (
+
+ {section.links
+ .filter((link) => orgEnabled || link.id !== "organizations")
+ .map((link) => {
+ return (
+
+ {formatMessage(link.intlLabel)}
+
+ );
+ })}
+
+ ))}
+
+
+
+ );
+};
+
+export { Nav };
diff --git a/plugins/plugin-better-auth-dashboard/admin/src/components/SubNav.tsx b/plugins/plugin-better-auth-dashboard/admin/src/components/SubNav.tsx
new file mode 100644
index 0000000..df7a56d
--- /dev/null
+++ b/plugins/plugin-better-auth-dashboard/admin/src/components/SubNav.tsx
@@ -0,0 +1,352 @@
+import {
+ Badge,
+ Box,
+ SubNav as DSSubNav,
+ Flex,
+ ScrollArea,
+ Typography,
+} from "@strapi/design-system";
+import { ChevronDown } from "@strapi/icons";
+import { useEffect, useId, useRef, useState } from "react";
+import { NavLink } from "react-router-dom";
+import styled from "styled-components";
+
+const HEIGHT_TOP_NAVIGATION = "6.4rem";
+const HEIGHT_TOP_NAVIGATION_MEDIUM = "5.6rem";
+const WIDTH_SIDE_NAVIGATION = "23.2rem";
+
+const MainSubNav = styled(DSSubNav)`
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background-color: ${({ theme }) => theme.colors.neutral0};
+ display: flex;
+ flex-direction: column;
+ border-right: 0;
+ box-shadow: none;
+ position: relative;
+
+ ${({ theme }) => theme.breakpoints.medium} {
+ width: ${WIDTH_SIDE_NAVIGATION};
+ position: sticky;
+ top: 0;
+ border-right: 1px solid ${({ theme }) => theme.colors.neutral150};
+ overscroll-behavior: contain;
+ }
+`;
+
+type MainProps = {
+ children: React.ReactNode;
+ isFullPage?: boolean;
+};
+
+const Main = ({ children, ...props }: MainProps) => (
+ {children}
+);
+
+const StyledLink = styled(NavLink)`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ text-decoration: none;
+ height: 32px;
+ color: ${({ theme }) => theme.colors.neutral800};
+
+ &.active > div {
+ background-color: ${({ theme }) => theme.colors.primary100};
+ color: ${({ theme }) => theme.colors.primary700};
+ font-weight: 500;
+ }
+
+ &:hover.active > div {
+ background-color: ${({ theme }) => theme.colors.primary100};
+ color: ${({ theme }) => theme.colors.primary700};
+ font-weight: 500;
+ }
+
+ &:hover > div {
+ background-color: ${({ theme }) => theme.colors.neutral100};
+ }
+
+ &:focus-visible {
+ outline-offset: -2px;
+ }
+`;
+
+const Link = (
+ props: Omit, "label"> & {
+ label: React.ReactNode;
+ endAction?: React.ReactNode;
+ handleClick?: () => void;
+ },
+) => {
+ const { to, label, endAction, handleClick, ...rest } = props;
+
+ return (
+
+
+
+
+ {label}
+
+ {endAction}
+
+
+
+ );
+};
+
+const StyledHeader = styled(Flex)`
+ flex: 0 0 ${HEIGHT_TOP_NAVIGATION};
+ height: ${HEIGHT_TOP_NAVIGATION};
+
+ ${({ theme }) => theme.breakpoints.medium} {
+ flex: 0 0 ${HEIGHT_TOP_NAVIGATION_MEDIUM};
+ height: ${HEIGHT_TOP_NAVIGATION_MEDIUM};
+ }
+`;
+
+const Header = ({ label }: { label: string }) => {
+ return (
+
+
+ {label}
+
+
+ );
+};
+
+const Sections = ({
+ children,
+ ...props
+}: {
+ children: React.ReactNode[];
+ [key: string]: unknown;
+}) => {
+ return (
+
+
+ {children.map((child, index) => {
+ return {child};
+ })}
+
+
+ );
+};
+
+const Section = ({
+ label,
+ children,
+ badgeLabel,
+}: {
+ label: string;
+ children: React.ReactNode[];
+ badgeLabel?: string;
+}) => {
+ const listId = useId();
+
+ return (
+
+
+
+
+
+
+ {label}
+
+
+
+
+ {badgeLabel && (
+
+ {badgeLabel}
+
+ )}
+
+
+
+
+ {children.map((child, index) => {
+ return {child};
+ })}
+
+
+ );
+};
+
+const SubSectionHeader = styled.button`
+ cursor: pointer;
+ width: 100%;
+ border: none;
+ padding: 0;
+ background: transparent;
+ display: flex;
+ align-items: center;
+ border-radius: ${({ theme }) => theme.borderRadius};
+ padding-left: ${({ theme }) => theme.spaces[3]};
+ padding-right: ${({ theme }) => theme.spaces[3]};
+ padding-top: ${({ theme }) => theme.spaces[2]};
+ padding-bottom: ${({ theme }) => theme.spaces[2]};
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.neutral100};
+ }
+`;
+
+const SubSectionLinkWrapper = styled.li`
+ ${StyledLink} > div {
+ padding-left: 36px;
+ }
+`;
+
+const SubSection = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode[];
+}) => {
+ const [isOpen, setOpenLinks] = useState(true);
+ const [contentHeight, setContentHeight] = useState(0);
+ const listId = useId();
+ const contentRef = useRef(null);
+
+ useEffect(() => {
+ if (!children) return;
+
+ if (contentRef.current) {
+ setContentHeight(contentRef.current.scrollHeight);
+ }
+ }, [children]);
+
+ const handleClick = () => {
+ setOpenLinks((prev) => !prev);
+ };
+
+ return (
+
+
+
+
+
+
+ {label}
+
+
+
+
+
+ {children.map((child, index) => {
+ return (
+ {child}
+ );
+ })}
+
+
+ );
+};
+
+const PageWrapper = styled(Box)`
+ width: 100%;
+
+ ${MainSubNav} {
+ background-color: transparent;
+ border-right: none;
+ }
+
+ ${({ theme }) => theme.breakpoints.medium} {
+ overflow: hidden;
+
+ ${MainSubNav} {
+ top: 0;
+ }
+ }
+`;
+
+const Content = ({ children }: { children: React.ReactNode }) => {
+ return {children};
+};
+
+export const SubNav = {
+ Main,
+ Content,
+ Header,
+ Link,
+ Sections,
+ Section,
+ SubSection,
+ PageWrapper,
+};
diff --git a/plugins/plugin-better-auth-dashboard/admin/src/index.ts b/plugins/plugin-better-auth-dashboard/admin/src/index.ts
index fd9237b..a5c5a13 100644
--- a/plugins/plugin-better-auth-dashboard/admin/src/index.ts
+++ b/plugins/plugin-better-auth-dashboard/admin/src/index.ts
@@ -44,10 +44,10 @@ export default {
Component: async () => import("./pages/Root"),
});
- // app.router.addRoute({
- // path: `/plugins/${PLUGIN_ID}`,
- // Component: async () => import("./pages/Root"),
- // });
+ app.router.addRoute({
+ path: `plugins/${PLUGIN_ID}/*`,
+ Component: async () => import("./pages/Root"),
+ });
},
bootstrap() {},
diff --git a/plugins/plugin-better-auth-dashboard/admin/src/pages/App.tsx b/plugins/plugin-better-auth-dashboard/admin/src/pages/App.tsx
index 8306e92..f44af2c 100644
--- a/plugins/plugin-better-auth-dashboard/admin/src/pages/App.tsx
+++ b/plugins/plugin-better-auth-dashboard/admin/src/pages/App.tsx
@@ -1,51 +1,16 @@
-import {
- Alert,
- Box,
- Flex,
- Loader,
- Tabs,
- Typography,
-} from "@strapi/design-system";
+import { Alert, Box, Flex, Loader } from "@strapi/design-system";
+import { BackButton, Layouts, Page } from "@strapi/strapi/admin";
import { useQuery } from "react-query";
-import styled from "styled-components";
+import { Outlet } from "react-router-dom";
import { client } from "../client";
-import { PluginIcon } from "../components/PluginIcon";
+import { Nav } from "../components/Nav";
import { hasPlugin, useDashConfig } from "../hooks/useDashConfig";
-import { OrganizationsPage } from "./Organizations";
-import { OverviewPage } from "./Overview";
-import { UsersPage } from "./Users";
-const Accent = styled.div`
- height: 3px;
- background: linear-gradient(90deg, #4945ff 0%, #7b79ff 55%, #9593ff 100%);
- flex-shrink: 0;
-`;
-
-const BrandIcon = styled.div`
- width: 30px;
- height: 30px;
- background: black;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-size: 12px;
- font-weight: 900;
- flex-shrink: 0;
- letter-spacing: -0.06em;
- user-select: none;
-`;
-
-const PathTag = styled.code`
- padding: 2px 8px;
- background: #f0f0ff;
- border: 1px solid #d9d8ff;
- border-radius: 4px;
- font-size: 11px;
- color: #4945ff;
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
- font-weight: 500;
-`;
+const RESPONSIVE_DEFAULT_SPACING = {
+ initial: 4,
+ medium: 6,
+ large: 10,
+};
export function App() {
const { data: config, isLoading, isError, error } = useDashConfig();
@@ -91,75 +56,22 @@ export function App() {
if (!config) return null;
return (
-
-
+
+
}>
+
Authentication
-
-
-
-
-
-
-
-
-
- Better Auth
-
-
-
- Authentication Dashboard
-
-
-
-
- {config.basePath}
-
-
-
- Overview
-
-
- Users
-
- {orgEnabled && (
-
- Organizations
-
- )}
-
-
+
-
-
-
-
-
-
-
- {orgEnabled && (
-
-
-
- )}
-
-
+
+
+
);
}
diff --git a/plugins/plugin-better-auth-dashboard/admin/src/pages/Root.tsx b/plugins/plugin-better-auth-dashboard/admin/src/pages/Root.tsx
index 14321ce..4c7eac1 100644
--- a/plugins/plugin-better-auth-dashboard/admin/src/pages/Root.tsx
+++ b/plugins/plugin-better-auth-dashboard/admin/src/pages/Root.tsx
@@ -1,6 +1,21 @@
import { useMemo } from "react";
import { QueryClient, QueryClientProvider } from "react-query";
+import { Navigate, Route, Routes, useOutletContext } from "react-router-dom";
+import type { DashConfig } from "../hooks/useDashConfig";
import { App } from "./App";
+import { OrganizationsPage } from "./Organizations";
+import { OverviewPage } from "./Overview";
+import { UsersPage } from "./Users";
+
+function UsersRoute() {
+ const { config } = useOutletContext<{ config: DashConfig }>();
+ return ;
+}
+
+function OrganizationsRoute() {
+ const { teamsEnabled } = useOutletContext<{ teamsEnabled: boolean }>();
+ return ;
+}
/**
* Root component that provides the React Query client.
@@ -24,7 +39,14 @@ export function Root() {
return (
-
+
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
);
}
diff --git a/plugins/plugin-better-auth-dashboard/admin/src/utils/getTranslation.ts b/plugins/plugin-better-auth-dashboard/admin/src/utils/getTranslation.ts
new file mode 100644
index 0000000..9f11aea
--- /dev/null
+++ b/plugins/plugin-better-auth-dashboard/admin/src/utils/getTranslation.ts
@@ -0,0 +1,5 @@
+import { PLUGIN_ID } from "../pluginId";
+
+const getTranslation = (id: string) => `${PLUGIN_ID}.${id}`;
+
+export { getTranslation };
diff --git a/plugins/plugin-better-auth-dashboard/admin/test/navigation.spec.ts b/plugins/plugin-better-auth-dashboard/admin/test/navigation.spec.ts
index 8cdd438..a5e067b 100644
--- a/plugins/plugin-better-auth-dashboard/admin/test/navigation.spec.ts
+++ b/plugins/plugin-better-auth-dashboard/admin/test/navigation.spec.ts
@@ -12,9 +12,7 @@ test.describe("Navigation", () => {
test("shows Better Auth branding header", async ({ page }) => {
await expect(page.getByTestId("dashboard-root")).toBeVisible();
- // Use exact: true to avoid matching the hidden SVG Better Auth Logo
- await expect(page.getByText("Better Auth", { exact: true })).toBeVisible();
- await expect(page.getByText("Authentication Dashboard")).toBeVisible();
+ await expect(page.getByText("Authentication")).toBeVisible();
});
test("shows the main navigation tab list", async ({ page }) => {
@@ -66,12 +64,12 @@ test.describe("Navigation", () => {
test("tab content area renders for overview tab", async ({ page }) => {
await expect(page.getByTestId("dashboard-root")).toBeVisible();
- await expect(page.getByTestId("tab-overview")).toBeVisible();
+ await expect(page.getByTestId("overview-page")).toBeVisible();
});
test("tab content area renders for users tab", async ({ page }) => {
await expect(page.getByTestId("dashboard-root")).toBeVisible();
await page.getByTestId("nav-users").click();
- await expect(page.getByTestId("tab-users")).toBeVisible();
+ await expect(page.getByTestId("users-page")).toBeVisible();
});
});
diff --git a/plugins/plugin-better-auth-dashboard/package.json b/plugins/plugin-better-auth-dashboard/package.json
index c119d92..09bfe54 100644
--- a/plugins/plugin-better-auth-dashboard/package.json
+++ b/plugins/plugin-better-auth-dashboard/package.json
@@ -74,7 +74,8 @@
"react-dom": "^18.0.0",
"react-intl": "^6.0.0 || ^7.0.0",
"react-query": "^3.0.0",
- "styled-components": "^6.0.0"
+ "styled-components": "^6.0.0",
+ "react-router-dom": "^6.0.0"
},
"strapi": {
"kind": "plugin",