Admin-only links
Objectives
Section titled βObjectivesβIn the previous guide, we learned how to protect routes in Remix by using the loader
function to check if a user has the βadminβ role.
Now that we have protected the admin review page, we need to provide a link in the navbar to the admin review page only for users with the βadminβ role.
The problem is that our current function that checks the userβs role throws an error if they are not an admin.
This wouldnβt work in the case of a navbar link, as we still want users who are not admins to see the navbar but not the link.
This is actually a straightfoward fix, and we will do this in three stages:
Create an optional admin user check
Section titled βCreate an optional admin user checkβOpen app/utils/rootProviders/generateRootJson.ts
and add the following code to the generateRootJson
function that starts around line 43:
export default function generateRootJson({ user, request, honeyProps, toast, csrfToken, timings, toastHeaders, csrfCookieHeader,}: GenerateRootJsonArgs) { const pageData = { user, requestInfo: { hints: getHints(request), origin: getDomainUrl(request), path: new URL(request.url).pathname, userPrefs: { theme: getTheme(request), }, }, ENV: getEnv(), toast, honeyProps, csrfToken, isAdminUser: user ? user.roles.some(role => role.name === 'admin') : false, } const headerData = { headers: combineHeaders( { 'Server-Timing': timings.toString() }, toastHeaders, csrfCookieHeader ? { 'set-cookie': csrfCookieHeader } : null, ), }
return { pageData, headerData }}
Pass isAdminUser
to the navbar
Section titled βPass isAdminUser to the navbarβNext, open app/root.tsx
and add the following code to the HeaderWithSearch
component (or whichever navbar component you are using):
export default function App() {3 collapsed lines
const data = useLoaderData<typeof loader>() const theme = useTheme() const nonce = useNonce()
return ( <ParallaxProvider> <AuthenticityTokenProvider token={data.csrfToken}> <HoneypotProvider {...data.honeyProps}> <Document nonce={nonce} theme={theme}> <div className="flex h-screen flex-col justify-between"> <HeaderWithSearch isAdminUser={data.isAdminUser} />
10 collapsed lines
<div className="flex-1"> <Outlet /> </div>
<div className="fixed m-4 rounded-full bg-black/20 p-2 backdrop-blur-sm transition hover:bg-black/30 dark:bg-white/20 dark:hover:bg-white/30"> <ThemeSwitch userPreference={data.requestInfo.userPrefs.theme} /> </div> <FooterMenuRight companyName="Epic News" /> </div> </Document> </HoneypotProvider> </AuthenticityTokenProvider> </ParallaxProvider> )}
Conditionally render the admin link
Section titled βConditionally render the admin linkβFinally, open app/components/organisms/HeaderWithSearch.tsx
.
Add the following code to the HeaderWithSearch
component:
import { Link, useMatches } from '@remix-run/react'import logo from '~/assets/png/epic-news-logo.png'import { SearchBar } from '../molecules/SearchBar'import LoginOrUserDropdown from './LoginOrUserDropdown'
interface HeaderWithSearchProps { isAdminUser: boolean}export default function HeaderWithSearch({ isAdminUser }: HeaderWithSearchProps) { const matches = useMatches() const isOnSearchPage = matches.find(m => m.id === 'routes/users+/index') const searchBar = isOnSearchPage ? null : <SearchBar status="idle" />
return ( <header className="bg-emerald-50 py-6 dark:bg-emerald-950"> <nav className="container flex flex-wrap items-center justify-between gap-4 sm:flex-nowrap md:gap-8"> <Link to="/" className="group"> <div className="flex items-center gap-4 transition group-hover:opacity-80"> <img src={logo} alt="Epic News Logo" className="w-16" /> <span className="text-sm font-bold text-foreground">Epic News</span> </div> </Link> <div className="flex flex-1 items-center justify-center gap-8"> {isAdminUser && ( <Link to="/admin-review" className="rounded-lg bg-green-900 px-4 py-2 text-sm font-semibold text-foreground transition hover:bg-green-800" > Admin Review </Link> )} <Link to="/news" className="text-sm font-semibold text-foreground transition hover:text-foreground/80" > News </Link>13 collapsed lines
<Link to="/about-us" prefetch="intent" className="text-sm font-semibold text-foreground transition hover:text-foreground/80" > About us </Link> <Link to="/contact-us" className="text-sm font-semibold text-foreground transition hover:text-foreground/80" > Contact us </Link> </div> <div className="ml-auto hidden max-w-sm flex-1 sm:block"> {searchBar} </div> <div className="flex items-center gap-10"> <LoginOrUserDropdown /> </div> <div className="block w-full sm:hidden">{searchBar}</div> </nav> </header> )}
Testing - admin user login
Section titled βTesting - admin user loginβWhilst logged in as a normal user, or when not logged in at all, you should not be able to see the new admin link in the navbar:
Now, log out of the application completely.
Once successfully logged out, log back in with the following details:
Username: kody Password: kodylovesyou
Once you are logged in as βKodyβ, you should now see the βAdmin Reviewβ link has now appeared in the navbar:
Click on this link, and you should be taken straight to the admin review page.
Summary
Section titled βSummaryβIn this tutorial, we have:
- Created an optional admin user check in the
generateRootJson
function. - Added an admin link to the navbar that is only visible to users with the βadminβ role.