From 6acff3510da5185f9fbbba324c906f3e079826ef Mon Sep 17 00:00:00 2001 From: Cybervoid Date: Mon, 23 Dec 2024 20:12:37 +0100 Subject: [PATCH 1/2] refactor: is admin functionality --- app/components/header/menudata.ts | 7 ++++--- .../header/mobile-menu/mobile-menu.tsx | 8 +++++--- app/components/header/tabs/tabs.tsx | 8 +++++--- app/components/is-admin/index.ts | 1 + app/components/is-admin/is-admin.tsx | 16 ++++++++++++++++ env.d.ts | 17 +++++++++++++++++ utils/auth.ts | 17 +++++++++++++++++ 7 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 app/components/is-admin/index.ts create mode 100644 app/components/is-admin/is-admin.tsx diff --git a/app/components/header/menudata.ts b/app/components/header/menudata.ts index a4652c74..f197f9d3 100644 --- a/app/components/header/menudata.ts +++ b/app/components/header/menudata.ts @@ -1,9 +1,10 @@ -type UserType = 'Simple' | 'Admin' +import { AccessRole } from '@prisma/client' + interface MenuItem { id: number label: string link: string - visibleFor?: UserType + visibleFor?: AccessRole } export const menuData: MenuItem[] = [ @@ -36,6 +37,6 @@ export const menuData: MenuItem[] = [ id: 10, label: 'Admin', link: 'admin', - visibleFor: 'Admin', + visibleFor: AccessRole.ADMIN, }, ] diff --git a/app/components/header/mobile-menu/mobile-menu.tsx b/app/components/header/mobile-menu/mobile-menu.tsx index e1437c56..a027c01b 100644 --- a/app/components/header/mobile-menu/mobile-menu.tsx +++ b/app/components/header/mobile-menu/mobile-menu.tsx @@ -19,13 +19,14 @@ import * as motion from 'framer-motion/client' import { SiMaildotru, SiInstagram } from 'react-icons/si' import { IoIosArrowForward as Arrow } from 'react-icons/io' import { useSession } from 'next-auth/react' +import { AccessRole } from '@prisma/client' import { instagram, email, barometerTypesRoute } from '@/app/constants' import { menuData } from '../menudata' import { useBarometers } from '@/app/hooks/useBarometers' +import { isAdmin } from '../../is-admin' export const MobileMenu: FC = props => { - const { status } = useSession() - const isLoggedId = status === 'authenticated' + const { data: session } = useSession() const [opened, setOpened] = useState>({}) const toggle = (index: number) => setOpened(old => ({ ...old, [index]: !old[index] })) const { categories } = useBarometers() @@ -57,7 +58,8 @@ export const MobileMenu: FC = props => { {menuData .filter( ({ visibleFor }) => - typeof visibleFor === 'undefined' || (isLoggedId && visibleFor === 'Admin'), + typeof visibleFor === 'undefined' || + (isAdmin(session) && visibleFor === AccessRole.ADMIN), ) .map((outer, i, arr) => ( diff --git a/app/components/header/tabs/tabs.tsx b/app/components/header/tabs/tabs.tsx index 6d48dfe4..615386cb 100644 --- a/app/components/header/tabs/tabs.tsx +++ b/app/components/header/tabs/tabs.tsx @@ -7,15 +7,16 @@ import clsx from 'clsx' import dynamic from 'next/dynamic' import Link from 'next/link' import { useSession } from 'next-auth/react' +import { AccessRole } from '@prisma/client' import sx from './tabs.module.scss' import { menuData } from '../menudata' import { useBarometers } from '@/app/hooks/useBarometers' import { barometerTypesRoute } from '@/app/constants' +import { isAdmin } from '../../is-admin' const WideScreenTabs = ({ className, ...props }: CenterProps) => { const { categories: types } = useBarometers() - const { status } = useSession() - const isLoggedId = status === 'authenticated' + const { data: session } = useSession() const router = useRouter() const pathname = usePathname() const [activeTab, setActiveTab] = useState(-1) @@ -45,7 +46,8 @@ const WideScreenTabs = ({ className, ...props }: CenterProps) => { {menuData .filter( ({ visibleFor }) => - typeof visibleFor === 'undefined' || (isLoggedId && visibleFor === 'Admin'), + typeof visibleFor === 'undefined' || + (isAdmin(session) && visibleFor === AccessRole.ADMIN), ) .map((menuitem, i) => { const renderTab = (key?: Key) => ( diff --git a/app/components/is-admin/index.ts b/app/components/is-admin/index.ts new file mode 100644 index 00000000..fb765b4e --- /dev/null +++ b/app/components/is-admin/index.ts @@ -0,0 +1 @@ +export { IsAdmin, isAdmin } from './is-admin' diff --git a/app/components/is-admin/is-admin.tsx b/app/components/is-admin/is-admin.tsx new file mode 100644 index 00000000..73de619e --- /dev/null +++ b/app/components/is-admin/is-admin.tsx @@ -0,0 +1,16 @@ +'use client' + +import { type PropsWithChildren } from 'react' +import { useSession } from 'next-auth/react' +import { AccessRole } from '@prisma/client' +import { Session } from 'next-auth' + +export function isAdmin(session: Session | null): boolean { + const user = session?.user + return user?.role === AccessRole.ADMIN +} + +export function IsAdmin({ children }: PropsWithChildren) { + const { data } = useSession() + return isAdmin(data) ? <>{children} : <> +} diff --git a/env.d.ts b/env.d.ts index 6d0c8a23..b9f9f94b 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1,3 +1,5 @@ +import { AccessRole } from '@prisma/client' + declare namespace NodeJS { interface ProcessEnv { AUTH_SECRET: string @@ -9,3 +11,18 @@ declare namespace NodeJS { GCP_PRIVATE_KEY: string } } + +declare module 'next-auth' { + interface User { + role?: AccessRole + } + interface Session { + user: User + } +} + +declare module 'next-auth/jwt' { + interface JWT { + role?: AccessRole + } +} diff --git a/utils/auth.ts b/utils/auth.ts index 2519dfcd..f0df4bd9 100644 --- a/utils/auth.ts +++ b/utils/auth.ts @@ -27,6 +27,7 @@ export const authConfig: AuthOptions = { email: user.email, name: user.name, image: user.avatarURL, + role: user.role, } as User }, }), @@ -38,4 +39,20 @@ export const authConfig: AuthOptions = { session: { strategy: 'jwt', }, + callbacks: { + async jwt({ token, user }) { + // Adding Role to token if it exists in User + if (user?.role) { + token.role = user.role + } + return token + }, + async session({ session, token }) { + // Passing role from token to session + if (token.role) { + session.user.role = token.role + } + return session + }, + }, } From 53a52263a0e526595b57ac2bdd00300b3a9cc9c7 Mon Sep 17 00:00:00 2001 From: Cybervoid Date: Mon, 23 Dec 2024 20:27:06 +0100 Subject: [PATCH 2/2] feat: integrate IsAdmin component for conditional rendering of admin features in carousel and page components --- .../items/[slug]/components/carousel.tsx | 8 +-- app/collection/items/[slug]/page.tsx | 54 +++++++++---------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/collection/items/[slug]/components/carousel.tsx b/app/collection/items/[slug]/components/carousel.tsx index 6d6896fa..f05d649a 100644 --- a/app/collection/items/[slug]/components/carousel.tsx +++ b/app/collection/items/[slug]/components/carousel.tsx @@ -7,6 +7,7 @@ import NextImage from 'next/image' import clsx from 'clsx' import { ImagesEdit } from './edit-fields/images-edit' import { type BarometerDTO } from '@/app/types' +import { IsAdmin } from '@/app/components/is-admin' import 'swiper/css' import 'swiper/css/zoom' import 'swiper/css/navigation' @@ -16,13 +17,14 @@ import './styles.css' interface ImageCarouselProps { images: string[] barometer: BarometerDTO - isAdmin: boolean } -export function ImageCarousel({ images, barometer, isAdmin }: ImageCarouselProps) { +export function ImageCarousel({ images, barometer }: ImageCarouselProps) { return ( - {isAdmin && } + + + 1} diff --git a/app/collection/items/[slug]/page.tsx b/app/collection/items/[slug]/page.tsx index fc5dba21..3c7efe9b 100644 --- a/app/collection/items/[slug]/page.tsx +++ b/app/collection/items/[slug]/page.tsx @@ -1,9 +1,6 @@ import { type Metadata } from 'next' -import { getServerSession } from 'next-auth' import capitalize from 'lodash/capitalize' import { Container, Title, Text, Box, Divider, Tooltip } from '@mantine/core' -import { AccessRole } from '@prisma/client' -import { authConfig, getUserByEmail } from '@/utils/auth' import { googleStorageImagesFolder, barometerRoute } from '@/app/constants' import { ImageCarousel } from './components/carousel' import { Condition } from './components/condition' @@ -19,6 +16,7 @@ import { title, openGraph, twitter } from '@/app/metadata' import { Dimensions } from '@/app/types' import { withPrisma } from '@/prisma/prismaClient' import { getBarometer } from '@/app/api/v2/barometers/[slug]/getters' +import { IsAdmin } from '@/app/components/is-admin' export const dynamic = 'force-static' @@ -71,20 +69,7 @@ export const generateStaticParams = withPrisma(prisma => prisma.barometer.findMany({ select: { slug: true } }), ) -async function isAuthorized(): Promise { - try { - const session = await getServerSession(authConfig) - const email = session?.user?.email - if (!email) return false - const { role } = await getUserByEmail(email) - return role === AccessRole.ADMIN - } catch (error) { - return false - } -} - export default async function BarometerItem({ params: { slug } }: BarometerItemProps) { - const isAdmin = await isAuthorized() const barometer = await getBarometer(slug) const { name, images, description, manufacturer, dateDescription, condition, collectionId } = barometer @@ -94,7 +79,6 @@ export default async function BarometerItem({ params: { slug } }: BarometerItemP googleStorageImagesFolder + image)} /> @@ -102,7 +86,9 @@ export default async function BarometerItem({ params: { slug } }: BarometerItemP {`${name.split(' ').slice(0, -1).join(' ')} `} {name.split(' ').at(-1)} - {isAdmin && <TextFieldEdit barometer={barometer} property="name" size={22} />} + <IsAdmin> + <TextFieldEdit barometer={barometer} property="name" size={22} /> + </IsAdmin> {collectionId} @@ -116,7 +102,9 @@ export default async function BarometerItem({ params: { slug } }: BarometerItemP {`${manufacturer.name}${manufacturer.city ? `, ${manufacturer.city}` : ''}`} - {isAdmin && } + + + )} @@ -127,7 +115,9 @@ export default async function BarometerItem({ params: { slug } }: BarometerItemP {dateDescription} - {isAdmin && } + + + @@ -141,28 +131,38 @@ export default async function BarometerItem({ params: { slug } }: BarometerItemP {index < arr.length - 1 ? ', ' : ''} ))} - {isAdmin && } + + + )} } + editButton={ + + + + } /> , - labelPosition: 'right', - })} + labelPosition="right" + label={ + + + + } /> {description ? ( ) : ( - isAdmin && Add description + + Add description + )}