Last active
March 7, 2025 16:45
-
-
Save Livog/56264a01dc58950e2ce8165681a2da6d to your computer and use it in GitHub Desktop.
Custom Payload Admin Bar RSC styled with Tailwind v4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use server' | |
import { | |
PAYLOAD_TOKEN_COOKIE_NAME, | |
AUTH_JS_COOKIE_NAME, | |
AUTH_JS_SECURE_COOKIE_NAME, | |
BETTER_AUTH_SESSION_COOKIE_NAME, | |
BETTER_AUTH_SESSION_COOKIE_NAME_SECURE, | |
BETTER_AUTH_SESSION_DATA_COOKIE_NAME, | |
BETTER_AUTH_SESSION_DATA_COOKIE_NAME_SECURE | |
} from '@/config/constants' | |
import { cookies } from 'next/headers' | |
import { redirect } from 'next/navigation' | |
export async function logout({ path }: { path: string }) { | |
const cookieStore = await cookies() | |
cookieStore.delete(PAYLOAD_TOKEN_COOKIE_NAME) | |
cookieStore.delete(BETTER_AUTH_SESSION_COOKIE_NAME) | |
cookieStore.delete(BETTER_AUTH_SESSION_COOKIE_NAME_SECURE) | |
cookieStore.delete(BETTER_AUTH_SESSION_DATA_COOKIE_NAME) | |
cookieStore.delete(BETTER_AUTH_SESSION_DATA_COOKIE_NAME_SECURE) | |
cookieStore.delete(AUTH_JS_COOKIE_NAME) | |
cookieStore.delete(AUTH_JS_SECURE_COOKIE_NAME) | |
redirect(path) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use client' | |
import { COLLECTION_SLUG_USER, COLLECTION_SLUG_PAGE, COLLECTION_SLUG_POST } from '@/config/collections' | |
import { cn } from '@/utils/cn' | |
import Link from 'next/link' | |
import { usePathname, useRouter } from 'next/navigation' | |
import type { User } from '@/payload-types' | |
import { Container } from '@/components/container' | |
import { CollectionSlug } from 'payload' | |
import { LayoutDashboard, CircleUserRound, Pencil, LogOut, CirclePlus } from 'lucide-react' | |
import { logout } from './actions' | |
interface AdminBarClientProps { | |
user: User | |
documentId: string | |
collectionSlug: CollectionSlug | |
isPreviewMode: boolean | |
icons?: boolean | |
} | |
export function AdminBarClient({ user, documentId, collectionSlug, isPreviewMode, icons = true }: AdminBarClientProps) { | |
const router = useRouter() | |
const pathName = usePathname() | |
const handleExitPreview = async () => { | |
await fetch('/next/exit-preview').catch(() => null) | |
router.push('/') | |
router.refresh() | |
} | |
const getCollectionLabel = () => { | |
switch (collectionSlug) { | |
case COLLECTION_SLUG_POST: | |
return 'Post' | |
case COLLECTION_SLUG_PAGE: | |
return 'Page' | |
default: | |
return collectionSlug | |
} | |
} | |
const linkClasses = 'inline-flex items-center gap-2 hover:text-zinc-300 cursor-pointer py-2 shrink-0' | |
return ( | |
<div className="admin-bar text-foreground bg-background border-border fixed top-0 z-50 w-full border-b text-sm [body:has(.admin-bar):has(&)]:pt-[36px] [body:has(.admin-bar):has(&)_header]:top-[36px]"> | |
<Container className="flex flex-wrap items-center justify-between"> | |
<div className="flex flex-wrap items-center gap-4"> | |
<Link href="/admin" className={linkClasses}> | |
{icons && <LayoutDashboard className="h-4 w-4" />} | |
Dashboard | |
</Link> | |
<Link href={`/admin/collections/${COLLECTION_SLUG_USER}/${user.id}`} className={linkClasses}> | |
{icons && <CircleUserRound className="h-4 w-4" />} | |
{user.email} | |
</Link> | |
</div> | |
<div className="flex flex-wrap items-center gap-4"> | |
{isPreviewMode && ( | |
<button onClick={handleExitPreview} className={cn('hover:text-zinc-300', 'transition-colors')}> | |
Exit Preview | |
</button> | |
)} | |
<Link href={`/admin/collections/${collectionSlug}/${documentId}`} className={linkClasses}> | |
{icons && <Pencil className="h-4 w-4" />} | |
Edit {getCollectionLabel()} | |
</Link> | |
<Link href={`/admin/collections/${collectionSlug}/create`} className={linkClasses}> | |
{icons && <CirclePlus className="h-4 w-4" />} | |
New {getCollectionLabel()} | |
</Link> | |
<button type="submit" onClick={() => logout({ path: pathName })} className={linkClasses}> | |
{icons && <LogOut className="h-4 w-4" />} | |
Logout | |
</button> | |
</div> | |
</Container> | |
</div> | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getCurrentUser } from '@/payload/utils/get-current-user' | |
import { draftMode } from 'next/headers' | |
import { AdminBarClient } from './client' | |
import { getDocumentByPath } from '@/payload/utils/get-document' | |
import { normalizePath } from '@/utils/normalize-path' | |
export interface AdminBarProps { | |
params: Promise<{ path?: string | string[] }> | |
} | |
export async function AdminBar({ params }: AdminBarProps) { | |
const user = await getCurrentUser() | |
if (user?.role !== 'admin') { | |
return null | |
} | |
const { path } = await params | |
const normalizedPath = normalizePath(path) | |
const document = await getDocumentByPath(normalizedPath) | |
if (!document) { | |
return null | |
} | |
const { isEnabled: isPreviewMode } = await draftMode() | |
return <AdminBarClient user={user} documentId={document.id} collectionSlug={document._collection} isPreviewMode={isPreviewMode} /> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment