Skip to content

feat: add landing page with GitHub star count and theme customizer - #25

Merged
itsnyein merged 3 commits into
mainfrom
feat/landing-page
Aug 21, 2026
Merged

itsnyein merged 3 commits into
mainfrom
feat/landing-page

Conversation

@nyeinphyoaung

Copy link
Copy Markdown
Collaborator

Summary

Adds a public landing page at /, replacing the previous redirect("/dashboard") stub.

proxy.ts already treats / as public and redirects authenticated users to /dashboard, so signed-in users still land on the dashboard while visitors now get the marketing page.

What's included

  • Route group app/(landing)/ with its own layout (navbar + footer) and metadata
  • Sections — hero with a spec datasheet, full-bleed demo dashboard preview, tech stack, features grid, page inventory, 3-step workflow, FAQ, closing CTA
  • GitHub star count (lib/github.ts) fetched server-side, revalidated hourly, rendered in the header pill and hero CTA. Returns null and drops the count segment on any API failure rather than breaking the page
  • Theme customizer on the right edge, reusing the dashboard's existing ThemeTab so every Shadcn/Tweakcn preset, radius, brand colour and Import Theme option is available. Layout tab is omitted — there is no sidebar to configure here
  • Copy-to-clipboard command pills in the workflow and CTA sections

Design

Monochrome by default: the page uses primary/foreground/muted only, and --primary is already near-black (light) / near-white (dark) in globals.css. Presets therefore tint the logo, buttons, eyebrows, icon tiles and demo dashboard together, while KPI numbers and table text stay high-contrast. The one colour accent is the gradient on "already finished".

The customizer applies nothing on mount, so a fresh load renders exactly what globals.css defines instead of stamping inline CSS variables.

Drive-by fix

hooks/use-circular-transitions.ts — transition.finished.finally() did not handle rejection, so an aborted View Transition surfaced as InvalidStateError: Transition was aborted (an unhandled rejection). Added .catch(). This is shared code, so it also affects the dashboard customizer.

Verification

  • tsc --noEmit clean
  • eslint clean
  • next build compiles
  • Checked in light and dark, and with a preset applied and reset

Known limitations

  • Preset fonts do not apply. Presets set --font-sans, but globals.css has :root.font-inter body { font-family: var(--font-inter) } and FontProvider puts font-inter on <html>, which wins. Pre-existing behaviour shared with the dashboard; fixing it means deciding whether presets or FontProvider own typography.
  • Breakpoints are written mobile-first but have only been verified at desktop widths.

@vercel

vercel Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shadcn-admindashboard Ready Ready Preview Aug 21, 2026 10:06am

@claude

claude Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

Claude finished @nyeinphyoaung's task in 6m 13s —— View job


Code Review: feat: add landing page with GitHub star count and theme customizer

Summary

Well-executed, low-risk addition: a fully static-data marketing page built from small, focused Server Components, plus a legitimate one-line fix for an unhandled View Transition rejection. No blocking bugs found. The main thing worth addressing before/after merge is a redundant auth check that also forces the page to render dynamically on every request, which undercuts the "cacheable marketing page" goal.


Critical Issues

No critical issues identified.


Medium Issues

1. Redundant session check makes the whole page dynamic (features/landing/components/landing-navbar.tsx)

export async function LandingNavbar() {
  const authenticated = await isAuthenticated();
  ...
  {authenticated ? (
    <Link href="/dashboard" ...>Dashboard ...</Link>
  ) : (
    <>...Sign in / Live demo...</>
  )}

proxy.ts already redirects any authenticated session away from / before this route renders (pathname === "/" && session → redirect to /dashboard). So by the time LandingNavbar executes, session is guaranteed falsy and the authenticated branch (lines 44-68) can never run — it's dead code. Worse, isAuthenticated() triggers a second, independent getServerSession() call on every single landing-page view, and its use of cookies()/headers() is very likely why app/(landing)/layout.tsx needs export const dynamic = "force-dynamic" — which forces this otherwise-static marketing page to skip the Full Route Cache entirely.

Suggested fix: drop the isAuthenticated() check and always render the anonymous nav, then remove dynamic = "force-dynamic" from the layout so the page can be statically generated / ISR'd (the GitHub star fetch already revalidates hourly on its own).

export function LandingNavbar() {
  return (
    <header ...>
      ...
      <Link href="/sign-in" ...>Sign in</Link>
      <Link href="/dashboard" ...>Live demo <ArrowRight ... /></Link>
    </header>
  );
}

Fix this →

2. GitHub star count fetched independently in two Server Components (features/landing/components/github-stars.tsx, features/landing/components/hero-section.tsx)

// github-stars.tsx
const stars = await getRepoStars();
// hero-section.tsx
const stars = await getRepoStars();

Both the navbar pill and the hero CTA call getRepoStars() independently. This only avoids a duplicate network call because Next.js's fetch request memoization happens to dedupe identical fetch() calls within one render pass — if either call site's options ever drift (a header added to one, not the other) they silently become two live calls to the GitHub API. Fetching once (e.g. in app/(landing)/page.tsx) and passing the value down as a prop removes the implicit dependency on that caching behavior.

Fix this →


Minor Issues

3. Clipboard copy failures are silently swallowed (features/landing/components/copy-command.tsx:22-27)

const handleCopy = async () => {
  try {
    await navigator.clipboard.writeText(command);
    setCopied(true);
  } catch {}
};

If navigator.clipboard.writeText rejects (insecure context, permission denied, older browser), the button gives no feedback at all - the user just sees nothing happen. Worth at least a toast or a brief "failed to copy" state.

Fix this →

4. Two icon libraries mixed in the same components (features/landing/components/hero-section.tsx, cta-section.tsx, landing-footer.tsx, github-stars.tsx, mock-dashboard-ui.tsx, data.ts)

These files import GitHub/dashboard icons from @tabler/icons-react alongside lucide-react icons in the same file (e.g. hero-section.tsx imports IconBrandGithub/IconStar from tabler and ArrowRight from lucide). @tabler/icons-react was already a dependency used elsewhere in the repo (config/sidebar.ts, command-search.tsx), so this isn't a new dependency, but standardizing on one icon set per component (or repo-wide) would trim bundle weight and keep visual weight consistent.

5. Singular/plural mismatch in star count label (features/landing/components/github-stars.tsx:17)

`Star this project on GitHub - ${stars} stars` reads "1 stars" when the count is exactly 1. Minor copy nit only.


Strengths

  • lib/github.ts fails closed correctly: any non-OK response, network error, or unexpected payload shape returns null and the UI drops the star segment instead of breaking (github-stars.tsx, hero-section.tsx).
  • The hooks/use-circular-transitions.ts fix is correct and minimal - .catch(() => {}) before .finally() properly handles the InvalidStateError on an aborted View Transition without changing behavior otherwise.
  • Feature is cleanly isolated under features/landing/ with static content centralized in data.ts, following the repo's folder convention with no unnecessary abstraction.
  • No hardcoded colors or inline styles beyond one legitimate dynamic sparkline height (preview-section.tsx:53); theming is done entirely through existing CSS variables and the shared ThemeTab/useThemeManager, matching the "monochrome by default, presets tint everything" design goal described in the PR.
  • Copy accurately reflects the actual stack (next@16.2.2, react@19.2.0, Better Auth + Drizzle) rather than aspirational claims.

Verdict: No critical issues. Item 1 is worth fixing before merge since it's dead code plus an unnecessary per-request session lookup that also blocks static rendering of the page; items 2-5 can reasonably be iterated on later.

Note: I was not able to run pnpm exec tsc --noEmit or pnpm eslint in this environment (command execution required approval that wasn't available) - the PR description states both were run clean locally, but that could not be independently re-verified here.

@itsnyein
itsnyein merged commit cc01408 into main Aug 21, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — a18412bc Deployed Aug 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants