Skip to content

Instantly share code, notes, and snippets.

@Livog
Last active March 7, 2025 16:45
Show Gist options
  • Save Livog/56264a01dc58950e2ce8165681a2da6d to your computer and use it in GitHub Desktop.
Save Livog/56264a01dc58950e2ce8165681a2da6d to your computer and use it in GitHub Desktop.
Custom Payload Admin Bar RSC styled with Tailwind v4
'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)
}
'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>
)
}
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