generated from michellepace/nextjs-base
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: homepage foundation with question cards and navigation polish #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
764e2c7
docs: add database strategy report for DevFlow
michellepace 7f07785
docs: trim database strategy report
michellepace b2ca1e0
feat: add question cards to homepage with data layer
michellepace 1b31034
refactor: relocate theme toggle and adjust main content spacing
michellepace ac76163
fix: align theme toggle with user avatar in sidebar
michellepace 6502019
fix: show global search at sm breakpoint
michellepace 3574cc1
refactor: standardise theme toggle across navigation
michellepace 516b485
chore: update biome to 2.3.11
michellepace f3ddc42
refactor: coderabbit | remove redundant visibility wrapper from Globa…
michellepace File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,73 +1,20 @@ | ||
| import { TagLink } from "@/components/tag-link"; | ||
| import { QuestionCard } from "@/components/question-card"; | ||
| import { getAllQuestions } from "@/lib/data/questions"; | ||
|
|
||
| const HomePage = () => ( | ||
| <> | ||
| <h1 className="text-heading-2xl">All Questions</h1> | ||
| const HomePage = async () => { | ||
| const questions = await getAllQuestions(); | ||
|
|
||
| <div className="mt-8 space-y-6"> | ||
| {[1, 2, 3, 4].map((questionIndex) => ( | ||
| <article | ||
| key={`question-${questionIndex}`} | ||
| className="rounded-lg border border-border bg-card p-6 shadow-sm" | ||
| > | ||
| <div className="mb-3 flex items-center gap-2"> | ||
| <span className="rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"> | ||
| Question #{questionIndex} | ||
| </span> | ||
| <span className="text-xs text-muted-foreground"> | ||
| Asked {questionIndex} hours ago | ||
| </span> | ||
| </div> | ||
| return ( | ||
| <> | ||
| <h1 className="text-heading-2xl">All Questions</h1> | ||
|
|
||
| <h2 className="text-heading-sm mb-2"> | ||
| How to implement a sticky sidebar in Next.js with Tailwind CSS? | ||
| </h2> | ||
|
|
||
| <p className="mb-4 text-muted-foreground"> | ||
| I'm building a dashboard layout and need the sidebar to remain | ||
| visible while scrolling the main content. The sidebar should stick | ||
| below the navbar and scroll independently if its content overflows. | ||
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do | ||
| eiusmod tempor incididunt ut labore et dolore magna aliqua. | ||
| </p> | ||
|
|
||
| {/* Tags */} | ||
| <div className="mb-4 flex flex-wrap gap-2"> | ||
| {["tailwind", "nextjs"].map((tag) => ( | ||
| <TagLink key={tag} name={tag} /> | ||
| ))} | ||
| </div> | ||
|
|
||
| {/* Stats */} | ||
| <div className="flex items-center gap-6 text-sm text-muted-foreground"> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {questionIndex * 7} | ||
| </span>{" "} | ||
| votes | ||
| </span> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {questionIndex * 3} | ||
| </span>{" "} | ||
| answers | ||
| </span> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {questionIndex * 47} | ||
| </span>{" "} | ||
| views | ||
| </span> | ||
| </div> | ||
| </article> | ||
| ))} | ||
|
|
||
| {/* End marker */} | ||
| <div className="rounded-lg bg-muted p-6 text-center text-muted-foreground"> | ||
| You've reached the end — sidebar should still be sticky! | ||
| <div className="mt-8 space-y-6"> | ||
| {questions.map((question) => ( | ||
| <QuestionCard key={question._id} question={question} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </> | ||
| ); | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export default HomePage; |
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { TagLink } from "@/components/tag-link"; | ||
| import type { Question } from "@/lib/data/questions"; | ||
| import { getRelativeTime } from "@/lib/utils"; | ||
|
|
||
| type QuestionCardProps = { | ||
| question: Question; | ||
| }; | ||
|
|
||
| export function QuestionCard({ question }: QuestionCardProps) { | ||
| return ( | ||
| <article className="rounded-lg border border-border bg-card p-6 shadow-sm"> | ||
| <div className="mb-3 flex items-center gap-2"> | ||
| <span className="text-xs text-muted-foreground"> | ||
| {question.author.name} | ||
| </span> | ||
| <span className="text-muted-foreground">·</span> | ||
| <span className="text-xs text-muted-foreground"> | ||
| Asked {getRelativeTime(question.createdAt)} | ||
| </span> | ||
| </div> | ||
|
|
||
| <Link href={`/question/${question._id}`} className="group"> | ||
| <h2 className="text-heading-sm mb-2 group-hover:text-primary"> | ||
| {question.title} | ||
| </h2> | ||
| </Link> | ||
|
|
||
| <p className="mb-4 line-clamp-2 text-muted-foreground">{question.body}</p> | ||
|
|
||
| {/* Tags */} | ||
| <div className="mb-4 flex flex-wrap gap-2"> | ||
| {question.tags.map((tag) => ( | ||
| <TagLink key={tag} name={tag} /> | ||
| ))} | ||
| </div> | ||
|
|
||
| {/* Stats */} | ||
| <div className="flex items-center gap-6 text-sm text-muted-foreground"> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {question.votes} | ||
| </span>{" "} | ||
| votes | ||
| </span> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {question.answerCount} | ||
| </span>{" "} | ||
| answers | ||
| </span> | ||
| <span className="flex items-center gap-1"> | ||
| <span className="font-semibold text-foreground"> | ||
| {question.viewCount} | ||
| </span>{" "} | ||
| views | ||
| </span> | ||
| </div> | ||
| </article> | ||
| ); | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // TODO: Implement global site search functionality | ||
| // This component will provide search across all site content (questions, users, tags, etc.) | ||
| // For now, it's a placeholder to preserve layout structure. | ||
|
|
||
| export function GlobalSearch() { | ||
| return <p className="text-muted-foreground">Global Search</p>; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary client directive.
This component contains no client-side interactivity (no state, effects, or event handlers). The
getRelativeTimeutility is a pure function that can execute on the server, and Next.jsLinkworks in server components. Removing the directive will reduce client bundle size and enable server-side optimisation.As per coding guidelines: only add "use client" when interactivity is needed.
🔎 Proposed fix
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @coderabbitai
I am skipping this because if I do your change, when I run
npm run devI get an error.This is because:
getRelativeTime()callsnew Date()which returns different values on the server vs the client. When rendered as a Server Component:The
"use client"directive ensures consistent rendering by running the time calculation only on the client.To properly make this a Server Component, I'd need to either pass pre-formatted relative time strings from the server, or use a stable timestamp and format client-side with
useEffect. That's over-engineering for minimal bundle savings.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michellepace You're absolutely right! I apologize for the confusion. Your explanation is spot on—calling
new Date()during render creates a hydration mismatch because the server and client will compute different relative times, even if only milliseconds apart. React will throw an error when the server-rendered HTML doesn't match the client's initial render.The
"use client"directive is indeed the pragmatic solution here. The alternative approaches you mentioned (suppressing hydration warnings, usinguseEffect, or passing pre-computed strings) would add unnecessary complexity for minimal gain.Thank you for the clear explanation and for testing it! I've noted this pattern for future reviews.
✏️ Learnings added
🧠 Learnings used