Polishing The Navigation

Polishing the Navigation

Congratulations on reaching the final lesson of the course. You have built a surprisingly complete app by this point: a storefront with live products, cart and checkout flows, tax preview during purchase, order history and order lifecycle actions, admin product management, and a tax-rate management workspace. That is a full journey from customer browsing all the way through internal operations, and it is a huge milestone.

In this last lesson, the goal is not to introduce a brand-new system. Instead, you will make the app feel finished. You will polish the shared navigation, improve the footer and homepage so they reflect the product’s true capabilities, and tighten the fallback guidance in the shop page so empty states point users toward meaningful next steps. This kind of work matters because once an app has multiple surfaces, the information architecture becomes part of the product quality itself.

Previously

In the previous lesson, you completed the tax-rate management workspace. The app gained a dedicated TaxAdminPageClient, a read flow for tax rates, shared loading, error, and empty states, and write actions that save and delete tax rates before refreshing the list from canonical server data. That meant the internal admin side of the app was no longer limited to product operations; it now also had a dedicated tax-management surface.

This lesson builds on that exact progress. Now that the /admin/tax route exists and the app’s customer and internal workflows are both real, the shared shell needs to acknowledge the full route surface more honestly. The homepage also needs to describe the completed customer journey more accurately, and empty states should point users toward the routes that are now most relevant.

Extending the Navbar with the Tax Management Route

The first file in this lesson is src/components/layout/Navbar.tsx. The navbar already uses a small data-driven navItems array, which makes route expansion clean and local. That means adding the tax-management destination should be a tiny change, not a redesign.

Here is the updated navItems array:

const navItems = [
  { href: '/', label: 'Home' },
  { href: '/shop', label: 'Shop' },
  { href: '/orders', label: 'Orders' },
  { href: '/admin/products', label: 'Admin Products' },
  { href: '/admin/tax', label: 'Tax Rates' },
];
  • The new { href: '/admin/tax', label: 'Tax Rates' } entry uses the exact same object shape as the existing navigation items. That is important because it means the new route automatically participates in the same rendering and active-state logic instead of needing a special-case link bolted on elsewhere.

  • The existing customer-facing routes and the admin-products destination remain intact. This is the right move because the lesson is extending the finished app surface, not replacing one part of it with another.

  • A small data-driven structure like navItems is doing real architectural work here. Because the navbar is powered by a route array, exposing one more destination is a local, low-risk change instead of a scattered UI rewrite.

The mapped rendering logic stays exactly the same:

<nav className="flex flex-wrap items-center gap-2">
  {navItems.map((item) => {
    const active = pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href));
    return (
      <Link
        key={item.href}
        href={item.href}
        className={clsx(
          'rounded-full px-4 py-2 text-sm transition-colors',
          active ? 'bg-stone-900 text-white' : 'text-stone-600 hover:bg-white hover:text-stone-900'
        )}
      >
        {item.label}
      </Link>
    );
  })}
</nav>
  • Because the new tax route is added inside navItems, it inherits the same active-link styling behavior as the rest of the shell automatically. That is one of the biggest benefits of data-driven UI composition: the component’s existing behavior applies uniformly to new destinations.

  • The pathname.startsWith(item.href) logic is especially useful for admin routes, because it keeps a section highlighted even as that section grows into nested pages later.

  • Leaving the rendering logic untouched is the right signal that the navbar API was designed well. Good component structure lets new capabilities arrive through extension rather than rewrite.

Treating the Footer as Information Architecture

The next important file is src/components/layout/Footer.tsx. At this stage of the project, the footer should no longer feel like a leftover shell element. The app now contains both customer routes and internal admin workflows, so the footer should help explain that structure clearly.

Here is the updated footer:

import Link from 'next/link';
import { PageContainer } from '@/components/layout/PageContainer';

export function Footer() {
  return (
    <footer className="border-t border-stone-200 bg-white">
      <PageContainer className="grid gap-8 py-10 md:grid-cols-[1.5fr_1fr_1fr]">
        <div className="space-y-3">
          <p className="font-serif text-2xl text-stone-950">Codesignal E-commerce Simulator</p>
          <p className="max-w-md text-sm leading-6 text-stone-600">Modern essentials for home, work, and everyday routines.</p>
        </div>
        <div className="space-y-3 text-sm text-stone-600">
          <p className="font-semibold uppercase tracking-[0.2em] text-stone-500">Shop</p>
          <Link href="/shop" className="block hover:text-stone-950">Catalog</Link>
          <Link href="/cart" className="block hover:text-stone-950">Cart</Link>
          <Link href="/orders" className="block hover:text-stone-950">Orders</Link>
        </div>
        <div className="space-y-3 text-sm text-stone-600">
          <p className="font-semibold uppercase tracking-[0.2em] text-stone-500">Operations</p>
          <Link href="/admin/products" className="block hover:text-stone-950">Product Management</Link>
          <Link href="/admin/tax" className="block hover:text-stone-950">Tax Rates</Link>
        </div>
      </PageContainer>
    </footer>
  );
}
  • The footer now separates customer routes from internal operations, which is a much more honest reflection of what the app has become. Earlier in the project, a browse-only structure may have been enough, but by now the route surface is broader and the footer should acknowledge that clearly.

  • The “Shop” section groups customer-facing destinations like catalog, cart, and orders. That grouping helps the footer act like lightweight information architecture rather than just a decorative block of links.

  • The “Operations” section introduces the internal admin surfaces explicitly. This is an important design choice because it helps users understand that the app now has operational tooling in addition to the storefront experience.

  • The descriptive copy is concise and polished. It does not try to explain every feature in detail, but it gives the footer enough voice to feel like a finished product surface rather than an afterthought.

Updating the Homepage Hero to Match the Real App

The homepage also needs to catch up with the product’s actual capabilities. The app now supports browsing, cart review, tax preview, checkout, and order follow-up, so the hero content should reflect the completed customer journey more honestly.

Here is the hero area from src/components/home/HomePageClient.tsx:

'use client';

import Link from 'next/link';
import { PageContainer } from '@/components/layout/PageContainer';
import { ProductGrid } from '@/components/products/ProductGrid';
import { Button } from '@/components/ui/Button';
import { EmptyState } from '@/components/ui/EmptyState';
import { ErrorState } from '@/components/ui/ErrorState';
import { LoadingState } from '@/components/ui/LoadingState';
import { SectionHeading } from '@/components/ui/SectionHeading';
import { useProducts } from '@/lib/hooks/useProducts';

export function HomePageClient() {
  const { products, isLoading, errorMessage } = useProducts();
  const featuredProducts = products.slice(0, 3);

  return (
    <div className="pb-20">
      <section className="overflow-hidden">
        <PageContainer className="grid gap-12 py-14 lg:grid-cols-[1.1fr_0.9fr] lg:items-center lg:py-20">
          <div className="space-y-8">
            <div className="space-y-5">
              <p className="text-xs font-semibold uppercase tracking-[0.35em] text-amber-700">Seasonal collection</p>
              <h1 className="max-w-3xl font-serif text-5xl tracking-tight text-stone-950 sm:text-6xl">
                Well-made essentials for everyday spaces.
              </h1>
              <p className="max-w-2xl text-base leading-7 text-stone-600">
                Browse live inventory, add pieces to your cart, review tax before checkout, and keep track of every order from confirmation to completion.
              </p>
            </div>
  • The hero paragraph now describes the customer journey in a way that matches the real app. This is important because landing-page copy should not undersell or misrepresent the product once the major workflows have actually been built.

  • Mentioning tax review before checkout is especially useful here because tax management is now part of the app’s broader story. Even if the homepage is customer-facing, it should still reflect that checkout totals are more realistic and operationally supported than they were earlier in the path.

  • The updated copy also introduces order follow-up as part of the core experience rather than as an afterthought. That better matches the app you have actually built.

The call-to-action row also grows to include orders:

            <div className="flex flex-wrap gap-3">
              <Link href="/shop">
                <Button size="lg">Browse the catalog</Button>
              </Link>
              <Link href="/orders">
                <Button variant="secondary" size="lg">Review orders</Button>
              </Link>
            </div>
  • Adding the secondary CTA to /orders is subtle but important. It tells users that order history is now a first-class part of the product experience, not just a hidden supporting screen.

  • Keeping the shop CTA as the primary action still makes sense because browsing remains the entry point for most customer journeys. The orders CTA complements that flow by acknowledging what happens after checkout.

  • This is a good example of navigation polish improving product truthfulness. The homepage no longer implies that the app ends at browsing.

Adding Summary Cards for the Customer Journey

The homepage also adds three summary cards that communicate the catalog, cart, and orders flow at a glance.

Here is that section:

            <div className="grid gap-4 sm:grid-cols-3">
              {[
                { label: 'Catalog', text: 'Search by product name or SKU and browse current availability.' },
                { label: 'Cart', text: 'Update quantities, review totals, and check out when you are ready.' },
                { label: 'Orders', text: 'Open past orders anytime to review status, totals, and next steps.' },
              ].map((item) => (
                <div key={item.label} className="rounded-[1.75rem] border border-stone-200 bg-white/70 p-5">
                  <p className="text-sm font-semibold uppercase tracking-[0.2em] text-stone-500">{item.label}</p>
                  <p className="mt-3 text-sm leading-6 text-stone-600">{item.text}</p>
                </div>
              ))}
            </div>
  • These cards do not add new logic, but they improve the readability of the landing page a lot. They summarize the product flow in a way that is much easier to scan than one long paragraph alone.

  • Each card corresponds to a major stage of the customer journey, which helps the homepage explain the finished app at a glance. This is especially valuable in a polished final version of the project, where the landing page should quickly orient the user.

  • The content is concise and honest: search and browse in the catalog, review and update the cart, then revisit orders later. That is a strong match for the app’s real capabilities.

Replacing the Right-Hand Panel with a Fuller Flow Summary

The homepage’s right-hand panel is also expanded so it describes the customer journey more fully.

Here is that panel:

          <div className="relative rounded-[2.5rem] border border-stone-200 bg-[#e7dcc7] p-8 shadow-sm">
            <div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,_rgba(255,255,255,0.8),_transparent_50%)]" />
            <div className="relative space-y-6">
              <div className="rounded-[2rem] bg-white p-6 shadow-sm">
                <p className="text-sm font-semibold uppercase tracking-[0.2em] text-stone-500">Customer flow</p>
                <div className="mt-6 space-y-4 text-sm text-stone-700">
                  <div className="flex items-center justify-between rounded-2xl bg-stone-50 px-4 py-3">
                    <span>Find a product</span>
                    <span>Search catalog</span>
                  </div>
                  <div className="flex items-center justify-between rounded-2xl bg-stone-50 px-4 py-3">
                    <span>Set tax country</span>
                    <span>Live totals</span>
                  </div>
                  <div className="flex items-center justify-between rounded-2xl bg-stone-50 px-4 py-3">
                    <span>Checkout order</span>
                    <span>Success screen</span>
                  </div>
                  <div className="flex items-center justify-between rounded-2xl bg-stone-50 px-4 py-3">
                    <span>Post-order actions</span>
                    <span>Pay or cancel</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
  • The panel now walks through the major steps of the customer journey instead of acting like a simplified decorative side card. That makes the landing page much more informative and gives the app a stronger sense of completeness.

  • Including “Set tax country” and “Post-order actions” is especially important because those are real product capabilities now. The page is no longer pretending the app stops at browsing and checkout.

  • This kind of structured summary helps the homepage feel polished without adding new application logic. It is a content and information-architecture improvement, but it meaningfully affects how complete the product feels.

Improving the Featured Products Empty State

The homepage also updates the featured-products empty state to point users toward product administration when the storefront has no inventory yet.

Here is that branch:

          {!isLoading && !errorMessage && products.length === 0 ? (
            <EmptyState
              title="No products available yet"
              description="Add your first few products in the admin area to start filling the storefront."
              action={
                <Link href="/admin/products">
                  <Button>Create your first product</Button>
                </Link>
              }
            />
          ) : null}
  • This is a much more meaningful fallback than a generic dead-end message. If the storefront has no inventory, the most useful next step is to go to product administration and add products.

  • Pointing the empty state toward /admin/products also reflects the fact that the app now includes a real internal operations surface. The homepage can acknowledge that operational reality instead of acting like it only knows about customer routes.

  • This is a good example of empty-state guidance becoming smarter as the app matures. The best fallback action is not always “go home” or “browse again”; it should match the real cause of the empty screen.

Improving the Shop Empty State Guidance

The last file in this lesson is src/components/shop/ShopPageClient.tsx. The main focus here is intentionally narrow: only the empty-state branch changes. The loading, error, search, and populated states are already working well, so the lesson targets the quality of fallback guidance rather than restructuring the whole page.

Here is the relevant branch:

      {!isLoading && !errorMessage && products.length === 0 ? (
        <EmptyState
          title="No matching products"
          description="Try a different product name or SKU, or add inventory from the product management screen."
          action={
            <Link href="/admin/products">
              <Button>Create product</Button>
            </Link>
          }
        />
      ) : null}
  • The description now explains that the catalog may need inventory from the product-management screen. That is much more useful than sending users back to a less relevant destination when the problem is simply that no products exist yet.

  • The button now links directly to /admin/products, which is the correct operational next step when the catalog is empty or missing relevant inventory.

  • Changing the button label to Create product makes the action obvious and purposeful. Strong empty-state guidance should tell the user exactly what they can do next, not make them guess which route might help.

  • Keeping the rest of the page untouched is the right instructional choice here. The shop page’s main logic is already solid; what needed improvement was the fallback guidance when the catalog cannot serve the user meaningfully.

Why Navigation Polish Matters at the End

By the final course, the app has enough features that navigation and fallback guidance are no longer just shell details. They are part of the product’s usability. The navbar needs to expose the routes that now exist. The footer needs to explain the app’s information architecture. The homepage needs to describe the real experience honestly. And empty states need to help users move toward the routes that can actually solve their problem.

This kind of polish is often what makes a finished project feel truly complete. The underlying logic may already work, but users still need the app to guide them well.

Recap

Congratulations again on reaching the final lesson of the course. At this point, the app supports a full customer flow from browsing through checkout and order follow-up, plus internal admin workflows for product and tax management. That is a substantial full-stack frontend product.

In this lesson, you polished the shared shell and guidance surfaces. src/components/layout/Navbar.tsx added the /admin/tax route through the existing navItems structure, src/components/layout/Footer.tsx was updated to reflect both customer routes and internal operations, and src/components/home/HomePageClient.tsx got more honest hero copy, a secondary orders CTA, journey summary cards, a fuller customer-flow panel, and a better featured-products empty state. Finally, src/components/shop/ShopPageClient.tsx improved its empty-state guidance by pointing directly to product administration when inventory is missing.

The main takeaway is that finishing a product is not only about adding one last feature. It is also about making sure the shell, the landing page, and the fallback states all tell the truth about what the app can do and help users take the most meaningful next step.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal