Skip to content

Posts, Search, Voting, Comments & API Validation#310

Merged
motirebuma merged 3 commits intomainfrom
Posts-Search-Voting-Comments
Mar 18, 2026
Merged

Posts, Search, Voting, Comments & API Validation#310
motirebuma merged 3 commits intomainfrom
Posts-Search-Voting-Comments

Conversation

@motirebuma
Copy link
Copy Markdown
Collaborator

Summary

  • Implement Phase 3 core content features: dashboard layout, URL-synced search with tabs (Trending/Featured/Friends/Search), debounced search
    input, DateSearchBar, PostCard, PostController, VotingBoard, CommentInput/CommentList, LatestQuotes sidebar, and SearchGuestSections
  • Validate and fix all API communication between the Next.js frontend and the local backend (http://localhost:4000) for Phases 1–3, resolving
    8 critical mismatches between frontend GraphQL operations and the backend schema
  • Post detail page with two-column layout (post + comments left, quotes sidebar right), loading/error skeletons per route segment

API Fixes

Issue Root Cause Fix
.env.local pointed to production Hardcoded https://api.quote.vote Updated to http://localhost:4000
WebSocket URL broken getServerUrl.ts read NEXT_PUBLIC_SERVER (wrong env var) Now reads NEXT_PUBLIC_SERVER_URL first, falls back
correctly
loginUser() used raw process.env Bypassed Zod-validated config Now uses env.serverUrl from @/config/env
Login page used nonexistent login GraphQL mutation Backend login is REST-only (POST /login) Switched to REST loginUser() call
Signup page used nonexistent register GraphQL mutation Backend register is REST-only (POST /register) Added dual-mode: invite-based
(token → UPDATE_USER) + direct REST signup
SEARCH_USERNAMES query had wrong arg name Frontend sent query:, backend expects queryName: Fixed to searchUser(queryName: $query)

|
| LatestQuotes used nonexistent latestQuotes query | Query doesn't exist on backend | Rewrote to extract quotes from posts query |
| CommentInput used nonexistent updateComment mutation | Mutation doesn't exist on backend | Removed edit-comment feature; add + delete work
correctly |
| CSP blocked WebSocket | connect-src missing ws://localhost:4000 | Added WebSocket origin to CSP headers |

New Components

  • SearchContainer — URL-synced tabs with debounced search (?q=&tab=&from=&to=)
  • DateSearchBar — collapsible date range filter synced to URL params
  • SearchGuestSections — blurred teaser for unauthenticated visitors
  • UsernameResults — dropdown showing user matches with HighlightText
  • PostController — full post detail with network-only fetch
  • LatestQuotes — sidebar widget sourced from recent posts

Test plan

  • pnpm type-check → 0 errors
  • pnpm lint → 0 errors
  • pnpm build → succeeds
  • 1716 tests passing across 134 suites (0 failures)
  • Verified posts, featuredPosts, searchUser(queryName:) queries against live backend
  • Verified REST /login and /register endpoints respond correctly
  • Verified WebSocket URL resolves to ws://localhost:4000/graphql

closes #309

motirebuma and others added 3 commits March 18, 2026 16:56
- Add Sidebar to dashboard layout for desktop navigation
- Fix PostController: uses GET_POST query to render actual Post component
- Fix Post detail page: two-column layout (Post+Comments | LatestQuotes)
- Connect VotingBoard + VotingPopup in Post.tsx (remove placeholders)
- Create full SearchContainer with URL-synced tabs (Trending/Featured/Friends/Search)
- Create DateSearchBar with collapsible date range filter synced to URL params
- Create SearchGuestSections for unauthenticated user CTAs
- Update search page to use SearchContainer with Suspense
- Add dashboard loading.tsx, error.tsx, and route-level loading files
- Update tests: PostController, SearchPage, SearchContainer (1711 passing total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 18, 2026 16:06
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 18, 2026

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

Project Deployment Actions Updated (UTC)
quotevote Ready Ready Preview, Comment Mar 18, 2026 4:06pm

@motirebuma motirebuma merged commit 22a83ae into main Mar 18, 2026
6 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Phase 3 “core content” UX in the Next.js dashboard (search with URL-synced tabs + date range, post detail view, voting/comments widgets) and updates frontend networking/config to better align with the local backend (http://localhost:4000) across REST + GraphQL + WebSocket.

Changes:

  • Added the new tabbed SearchContainer (debounced query + URL params) and DateSearchBar, plus guest-only teaser sections.
  • Implemented post detail rendering via PostController, re-enabled voting UI components, and adjusted comments UI (add/delete + reactions).
  • Updated API/config plumbing (server URL derivation, REST login, CSP connect-src, query arg fixes) and added/updated tests.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
quotevote-frontend/src/types/post.ts Adds searchKey prop to support highlighting search terms in PostCard.
quotevote-frontend/src/lib/utils/getServerUrl.ts Refines server URL selection/fallbacks and supports deriving base URL from GraphQL endpoint.
quotevote-frontend/src/lib/auth.ts Switches REST login to use validated env.serverUrl.
quotevote-frontend/src/graphql/queries.ts Updates SEARCH_USERNAMES query argument name.
quotevote-frontend/src/components/SearchContainer/SearchGuestSections.tsx Adds unauthenticated CTA section below search results.
quotevote-frontend/src/components/SearchContainer/SearchContainer.tsx Introduces tabbed, URL-synced, debounced search with multiple feed tabs.
quotevote-frontend/src/components/Quotes/LatestQuotes.tsx Reworks “LatestQuotes” to derive quotes from recent posts.
quotevote-frontend/src/components/Post/PostController.tsx Fetches a post by ID and renders the post view with skeleton/error handling.
quotevote-frontend/src/components/Post/PostCard.tsx Highlights matching search terms in post titles via HighlightText.
quotevote-frontend/src/components/Post/Post.tsx Replaces voting placeholders with real VotingBoard + VotingPopup integration.
quotevote-frontend/src/components/DateSearchBar/DateSearchBar.tsx Adds URL-synced collapsible date range filter UI.
quotevote-frontend/src/components/Comment/CommentInput.tsx Removes edit-comment flow; keeps add-comment behavior.
quotevote-frontend/src/components/Comment/Comment.tsx Removes inline edit UI; adds CommentReactions and keeps delete/copy.
quotevote-frontend/src/app/dashboard/search/page.tsx Uses new SearchContainer under Suspense.
quotevote-frontend/src/app/dashboard/post/[group]/[title]/[postId]/page.tsx Adds two-column post detail layout (post+comments + latest quotes sidebar).
quotevote-frontend/src/app/dashboard/layout.tsx Adds persistent sidebar layout structure.
quotevote-frontend/src/app/auths/signup/PageContent.tsx Adds “invite token” path + REST fallback signup.
quotevote-frontend/src/app/auths/login/PageContent.tsx Switches login to REST loginUser() instead of GraphQL mutation.
quotevote-frontend/src/tests/utils/getServerUrl.test.ts Expands server URL derivation test coverage (env var precedence + ws URL).
quotevote-frontend/src/tests/components/SearchContainer/NewSearchContainer.test.tsx Adds tests for the new tabbed SearchContainer.
quotevote-frontend/src/tests/components/Post/PostController.test.tsx Updates tests for new PostController behavior (loading/missing postId).
quotevote-frontend/src/tests/app/dashboard/search/page.test.tsx Updates search page tests to reflect SearchContainer.
quotevote-frontend/src/tests/app/auths/login.test.tsx Updates login tests to mock REST login and validate redirects/errors.
quotevote-frontend/next.config.ts Updates CSP connect-src to include ws://localhost:4000.
quotevote-frontend/.env.local Changes local env defaults to point to localhost backend.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +233 to +269
// Determine active tab — if no query, don't show 'search' tab as active
const activeTab = q ? tab : tab === 'search' ? 'trending' : tab
const isLoggedIn = !!(user?._id || user?.id)

return (
<div className="space-y-4">
{/* Search input */}
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search posts..."
value={inputValue}
onChange={handleInputChange}
className="pl-9"
aria-label="Search posts"
/>
{debouncedQuery && (
<UsernameResults
users={usersData?.searchUser ?? []}
loading={usersLoading}
error={usersError ?? null}
query={debouncedQuery}
/>
)}
</div>

{/* Date range filter */}
<DateSearchBar />

{/* Tabs */}
<Tabs value={activeTab} onValueChange={handleTabChange}>
<TabsList>
<TabsTrigger value="trending">Trending</TabsTrigger>
<TabsTrigger value="featured">Featured</TabsTrigger>
{isLoggedIn && <TabsTrigger value="friends">Friends</TabsTrigger>}
{q && <TabsTrigger value="search">Search</TabsTrigger>}
Comment on lines 56 to 60
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' http://localhost:4000",
"connect-src 'self' http://localhost:4000 ws://localhost:4000",
"frame-ancestors 'none'",
Comment on lines +1 to +8
# Quote.Vote API — local development
NEXT_PUBLIC_SERVER_URL=http://localhost:4000
NEXT_PUBLIC_GRAPHQL_ENDPOINT=http://localhost:4000/graphql

# WebSocket is auto-derived by getGraphqlWsServerUrl():
# wss://api.quote.vote/graphql
# ws://localhost:4000/graphql
# Login is a REST endpoint (not GraphQL):
# POST https://api.quote.vote/login → { username, password } → { token }
# POST http://localhost:4000/login → { username, password } → { token }
Comment on lines +37 to +52
<PostController postId={postId} />
<CommentsSection postId={postId} />
</div>
{/* Right column - LatestQuotes sidebar */}
<div className="w-full lg:w-80 flex-shrink-0">
<LatestQuotes limit={5} />
</div>
</div>
)
}

function CommentsSection({ postId }: { postId: string }) {
const { loading, data } = useQuery<PostQueryData>(GET_POST, {
variables: { postId },
fetchPolicy: 'cache-first',
})
Comment on lines +396 to 399
const handleVoting = async (obj: { type: VoteType; tags: VoteOption }) => {
if (!ensureAuth()) return
if (hasVoted) {
toast('You have already voted on this post')
export const SEARCH_USERNAMES = gql`
query searchUsernames($query: String!) {
searchUser(query: $query) {
searchUser(queryName: $query) {
Comment on lines 21 to +92
@@ -39,19 +54,23 @@ const signupSchema = z

type SignupFormData = z.infer<typeof signupSchema>

function SubmitButton() {
const { pending } = useFormStatus()
return (
<Button type="submit" disabled={pending} className="w-full">
{pending && <Loader2 className="animate-spin mr-2 h-4 w-4" />}
Create Account
</Button>
)
}

export default function SignupPageContent() {
const router = useRouter()
const [signupMutation] = useMutation(SIGNUP_MUTATION)
const searchParams = useSearchParams()
const token = searchParams.get('token') || ''
const setUserData = useAppStore((s) => s.setUserData)
const [submitting, setSubmitting] = useState(false)

// Verify the invite token
const { data: tokenData, loading: tokenLoading, error: tokenError } = useQuery<VerifyTokenData>(VERIFY_PASSWORD_RESET_TOKEN, {
variables: { token },
skip: !token,
})

const verifiedUser = tokenData?.verifyUserPasswordResetToken

const [updateUser] = useMutation<UpdateUserData>(UPDATE_USER)

const {
register,
handleSubmit,
@@ -61,21 +80,84 @@ export default function SignupPageContent() {
})

const onSubmit = async (values: SignupFormData) => {
setSubmitting(true)
try {
await signupMutation({
variables: {
username: values.username,
email: values.email,
password: values.password,
},
})
toast.success('Account created! Please sign in.')
router.push('/auths/login')
if (token && verifiedUser) {
// Invite-based signup: update the existing user via GraphQL
setToken(token)
const result = await updateUser({
variables: {
user: {
_id: verifiedUser._id,
email: values.email,
Comment on lines +43 to +46
if (error) {
router.push('/error')
return null
}
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.

Posts, Search, Voting & Comments

2 participants