From e8b0225cd1c5b9d133c6947ff3285065d6d85568 Mon Sep 17 00:00:00 2001 From: Hugo FOYART <11079152+foyarash@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:22:02 +0100 Subject: [PATCH] feat: add user informations & logout (#193) --- .changeset/sixty-dingos-fly.md | 5 + apps/docs/pages/docs/api-docs.mdx | 11 ++ .../[locale]/admin/[[...nextadmin]]/page.tsx | 6 + .../app/[locale]/admin/custom/page.tsx | 10 +- .../pagerouter/admin/[[...nextadmin]].tsx | 14 ++- .../pages/pagerouter/admin/custom/index.tsx | 10 +- .../next-admin/src/components/MainLayout.tsx | 2 + packages/next-admin/src/components/Menu.tsx | 111 ++++++++++++++++-- .../next-admin/src/components/NextAdmin.tsx | 2 + .../src/components/radix/Dropdown.tsx | 30 +++++ packages/next-admin/src/types.ts | 12 ++ 11 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 .changeset/sixty-dingos-fly.md diff --git a/.changeset/sixty-dingos-fly.md b/.changeset/sixty-dingos-fly.md new file mode 100644 index 00000000..b158de7d --- /dev/null +++ b/.changeset/sixty-dingos-fly.md @@ -0,0 +1,5 @@ +--- +"@premieroctet/next-admin": minor +--- + +feat: add user informations & logout diff --git a/apps/docs/pages/docs/api-docs.mdx b/apps/docs/pages/docs/api-docs.mdx index 8c479aaa..9a80ed5e 100644 --- a/apps/docs/pages/docs/api-docs.mdx +++ b/apps/docs/pages/docs/api-docs.mdx @@ -146,6 +146,7 @@ import { Tabs } from "nextra/components"; - `options` used to customize the UI, like field formatters for example. Do not use with App router. - `dashboard` used to customize the rendered dashboard - `translations` used to customize some of the texts displayed in the UI. See [i18n](/docs/i18n) for more details. +- `user` used to add some user information at the bottom of the menu. See [user properties](#user-properties) for more details. > ⚠️ : Do not override these `AdminComponentProps` props, they are used internally by Next Admin. @@ -417,3 +418,13 @@ The edit page's form can display notice alerts. To do so, you can pass objects i | title | The title of the notice. This is mandatory | | id | A unique identifier for the notice that can be used to style it with the `styles` property. This is mandatory | | description | The description of the notice. This is optional | + +## User properties + +The `user` property is an object that can take the following properties: + +| Name | Description | +| ------------ | -------------------------------------------------------------------- | +| data.name | the name of the user displayed in the sidebar menu. This is required | +| data.picture | the URL of the user's avatar displayed in the sidebar menu | +| logoutUrl | an URL or path to logout the user. This is required. | diff --git a/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx b/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx index 526d2c3d..367d320f 100644 --- a/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx +++ b/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx @@ -34,6 +34,12 @@ export default async function AdminPage({ {...props} locale={params.locale as string} dashboard={Dashboard} + user={{ + data: { + name: "Example User", + }, + logoutUrl: "/", + }} /> ); } diff --git a/apps/example/app/[locale]/admin/custom/page.tsx b/apps/example/app/[locale]/admin/custom/page.tsx index 2cf9bb6c..9203c727 100644 --- a/apps/example/app/[locale]/admin/custom/page.tsx +++ b/apps/example/app/[locale]/admin/custom/page.tsx @@ -12,7 +12,15 @@ const CustomPage = async () => { const totalCategories = await prisma.category.count(); return ( - +

Custom page diff --git a/apps/example/pages/pagerouter/admin/[[...nextadmin]].tsx b/apps/example/pages/pagerouter/admin/[[...nextadmin]].tsx index 0a5c6cea..382aef7a 100644 --- a/apps/example/pages/pagerouter/admin/[[...nextadmin]].tsx +++ b/apps/example/pages/pagerouter/admin/[[...nextadmin]].tsx @@ -9,7 +9,19 @@ import "../../../styles.css"; const pageOptions = options; export default function Admin(props: AdminComponentProps) { - return ; + return ( + + ); } export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { diff --git a/apps/example/pages/pagerouter/admin/custom/index.tsx b/apps/example/pages/pagerouter/admin/custom/index.tsx index d375b0ca..7a6d189f 100644 --- a/apps/example/pages/pagerouter/admin/custom/index.tsx +++ b/apps/example/pages/pagerouter/admin/custom/index.tsx @@ -23,7 +23,15 @@ const CustomPage = ({ ...mainLayoutProps }: Props) => { return ( - +

Custom page diff --git a/packages/next-admin/src/components/MainLayout.tsx b/packages/next-admin/src/components/MainLayout.tsx index ec638814..da96e836 100644 --- a/packages/next-admin/src/components/MainLayout.tsx +++ b/packages/next-admin/src/components/MainLayout.tsx @@ -25,6 +25,7 @@ export const MainLayout = ({ title = "Admin", sidebar, resourcesIcons, + user, }: PropsWithChildren) => { const mergedTranslations = merge({ ...defaultTranslations }, translations); const localePath = locale ? `/${locale}` : ""; @@ -39,6 +40,7 @@ export const MainLayout = ({ customPages={customPages} configuration={sidebar} resourcesIcons={resourcesIcons} + user={user} />
diff --git a/packages/next-admin/src/components/Menu.tsx b/packages/next-admin/src/components/Menu.tsx index 2be3f43e..689bdf33 100644 --- a/packages/next-admin/src/components/Menu.tsx +++ b/packages/next-admin/src/components/Menu.tsx @@ -14,6 +14,17 @@ import { import { useConfig } from "../context/ConfigContext"; import { useRouterInternal } from "../hooks/useRouterInternal"; import ResourceIcon from "./common/ResourceIcon"; +import { + Dropdown, + DropdownBody, + DropdownContent, + DropdownItem, + DropdownLabel, + DropdownSeparator, + DropdownTrigger, +} from "./radix/Dropdown"; +import Button from "./radix/Button"; +import { Cog6ToothIcon, PowerIcon } from "@heroicons/react/24/solid"; export type MenuProps = { resource?: ModelName; @@ -22,6 +33,7 @@ export type MenuProps = { customPages?: AdminComponentProps["customPages"]; configuration?: SidebarConfiguration; resourcesIcons: AdminComponentProps["resourcesIcons"]; + user?: AdminComponentProps["user"]; }; export default function Menu({ @@ -31,6 +43,7 @@ export default function Menu({ customPages, configuration, resourcesIcons, + user, }: MenuProps) { const [sidebarOpen, setSidebarOpen] = useState(false); const { basePath } = useConfig(); @@ -94,6 +107,74 @@ export default function Menu({ }; }; + const getInitials = () => { + const username = user?.data.name; + + if (username) { + const [firstName, lastName] = username.split(" "); + + if (firstName && lastName) { + return `${firstName.charAt(0)}${lastName.charAt(0)}`; + } + + return username.charAt(0); + } + }; + + const renderUser = () => { + if (!user) { + return null; + } + + return ( +
+
+ {user.data.picture ? ( + User picture + ) : ( +
+ {getInitials()} +
+ )} + Logged in as + +
+ + + + + + + + {user.data.name} + + + + + + Logout + + + + + +
+ ); + }; + const renderNavigation = () => { return (

{/* Sidebar component, swap this element with another sidebar if you like */} -
-
- - - +
+
+
+ + + +
+ {renderNavigation()}
- {renderNavigation()} + {renderUser()}
@@ -205,13 +289,16 @@ export default function Menu({ {/* Static sidebar for desktop */}
{/* Sidebar component, swap this element with another sidebar if you like */} -
-
- - - +
+
+
+ + + +
+ {renderNavigation()}
- {renderNavigation()} + {renderUser()}
diff --git a/packages/next-admin/src/components/NextAdmin.tsx b/packages/next-admin/src/components/NextAdmin.tsx index de96f6df..928c7235 100644 --- a/packages/next-admin/src/components/NextAdmin.tsx +++ b/packages/next-admin/src/components/NextAdmin.tsx @@ -35,6 +35,7 @@ export function NextAdmin({ title, sidebar, resourcesIcons, + user, }: AdminComponentProps & CustomUIProps) { if (!isAppDir && !options) { throw new Error( @@ -119,6 +120,7 @@ export function NextAdmin({ title={title} sidebar={sidebar} resourcesIcons={resourcesIcons} + user={user} > {renderMainComponent()} diff --git a/packages/next-admin/src/components/radix/Dropdown.tsx b/packages/next-admin/src/components/radix/Dropdown.tsx index d86fb38f..30e27f63 100644 --- a/packages/next-admin/src/components/radix/Dropdown.tsx +++ b/packages/next-admin/src/components/radix/Dropdown.tsx @@ -67,3 +67,33 @@ export const DropdownItem = forwardRef< }); DropdownItem.displayName = "DropdownItem"; + +export const DropdownLabel = forwardRef< + ElementRef, + ComponentProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); + +DropdownLabel.displayName = "DropdownLabel"; + +export const DropdownSeparator = forwardRef< + ElementRef, + ComponentProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); + +DropdownSeparator.displayName = "DropdownSeparator"; diff --git a/packages/next-admin/src/types.ts b/packages/next-admin/src/types.ts index 9ca88017..9b872cd0 100644 --- a/packages/next-admin/src/types.ts +++ b/packages/next-admin/src/types.ts @@ -290,6 +290,16 @@ export type ListDataFieldValue = ListDataFieldValueWithFormat & } ); +export type UserData = { + name: string; + picture?: string; +}; + +export type AdminUser = { + data: UserData; + logoutUrl: string; +}; + export type AdminComponentProps = { basePath: string; schema?: Schema; @@ -333,6 +343,7 @@ export type AdminComponentProps = { */ title?: string; sidebar?: SidebarConfiguration; + user?: AdminUser; }; export type MainLayoutProps = Pick< @@ -350,6 +361,7 @@ export type MainLayoutProps = Pick< | "title" | "sidebar" | "resourcesIcons" + | "user" >; export type CustomUIProps = {