From 565085aba1cc423b71e8f724dd36495246dbcee6 Mon Sep 17 00:00:00 2001 From: Christopher Mayor Date: Sun, 26 Apr 2026 15:58:00 -0700 Subject: [PATCH] feat: wire up explore and profile pages Updated explore and profile page components. --- src/app/(main)/explore/page.tsx | 221 ++++++++++++++++++-------------- src/app/(main)/profile/page.tsx | 60 ++++----- 2 files changed, 144 insertions(+), 137 deletions(-) diff --git a/src/app/(main)/explore/page.tsx b/src/app/(main)/explore/page.tsx index 5493bca..230a54d 100644 --- a/src/app/(main)/explore/page.tsx +++ b/src/app/(main)/explore/page.tsx @@ -1,97 +1,94 @@ "use client" -import { useState } from "react" +import { useState, useEffect, useCallback } from "react" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" -import { Search, Eye, BarChart3, Filter, X, Loader2 } from "lucide-react" +import { Skeleton } from "@/components/ui/skeleton" +import { Search, Eye, Filter, X, Loader2, RefreshCw } from "lucide-react" import Link from "next/link" -const allComparisons = [ - { - id: "1", - title: "React vs Vue vs Svelte", - description: "Frontend framework comparison for modern web development", - items: ["React", "Vue", "Svelte"], - tags: ["Tech", "JavaScript"], - author: "Alex Johnson", - overallScore: 8.5, - views: 1247, - }, - { - id: "2", - title: "GPT-4 vs Claude vs Gemini", - description: "Comparing top AI language models for reasoning tasks", - items: ["GPT-4", "Claude 3", "Gemini Pro"], - tags: ["AI", "Products"], - author: "Sarah Chen", - overallScore: 8.8, - views: 3891, - }, - { - id: "3", - title: "Notion vs Obsidian vs Roam", - description: "Knowledge management tools for productivity", - items: ["Notion", "Obsidian", "Roam Research"], - tags: ["Productivity", "Tools"], - author: "Mike Peters", - overallScore: 7.5, - views: 892, - }, - { - id: "4", - title: "AWS vs GCP vs Azure", - description: "Cloud platform comparison for enterprise infrastructure", - items: ["AWS", "Google Cloud", "Microsoft Azure"], - tags: ["Tech", "Cloud"], - author: "Emma Wilson", - overallScore: 9.0, - views: 2156, - }, - { - id: "5", - title: "iPhone 15 Pro vs Samsung S24 Ultra", - description: "Flagship smartphone comparison with camera and performance benchmarks", - items: ["iPhone 15 Pro", "Samsung S24 Ultra"], - tags: ["Products", "Mobile"], - author: "James Lee", - overallScore: 8.2, - views: 3421, - }, - { - id: "6", - title: "Python vs Rust vs Go", - description: "Systems programming languages compared for performance and productivity", - items: ["Python", "Rust", "Go"], - tags: ["Tech", "Programming"], - author: "Anna Kim", - overallScore: 8.4, - views: 1873, - }, -] +interface Comparison { + id: string + title: string + summary: string + slug: string + tags: string[] + items: string[] + viewCount: number + createdAt: string +} -const categories = ["All", "Tech", "Products", "AI", "Cloud", "Productivity"] +interface ComparisonsResponse { + comparisons: Comparison[] + total: number + page: number + limit: number +} export default function ExplorePage() { - const [searchQuery, setSearchQuery] = useState("") - const [selectedCategory, setSelectedCategory] = useState("All") + const [comparisons, setComparisons] = useState([]) + const [total, setTotal] = useState(0) + const [page, setPage] = useState(1) const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [searchQuery, setSearchQuery] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [selectedCategory, setSelectedCategory] = useState("All") - const filteredComparisons = allComparisons.filter((comparison) => { - const matchesSearch = - searchQuery === "" || - comparison.title.toLowerCase().includes(searchQuery.toLowerCase()) || - comparison.items.some((item) => - item.toLowerCase().includes(searchQuery.toLowerCase()) - ) + const limit = 20 + + const fetchComparisons = useCallback(async (pageNum: number, search: string, append = false) => { + setLoading(true) + setError(null) + try { + const params = new URLSearchParams({ + page: pageNum.toString(), + limit: limit.toString(), + ...(search && { search }), + }) + const res = await fetch(`/api/comparisons?${params}`) + if (!res.ok) throw new Error("Failed to fetch comparisons") + const data: ComparisonsResponse = await res.json() + setComparisons(prev => append ? [...prev, ...data.comparisons] : data.comparisons) + setTotal(data.total) + setPage(pageNum) + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong") + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearch(searchQuery) + setPage(1) + fetchComparisons(1, searchQuery) + }, 300) + return () => clearTimeout(timer) + }, [searchQuery, fetchComparisons]) + + useEffect(() => { + fetchComparisons(1, "") + }, [fetchComparisons]) + + const categories = ["All", ...Array.from(new Set(comparisons.flatMap(c => c.tags)))] + + const filteredComparisons = comparisons.filter((comparison) => { const matchesCategory = selectedCategory === "All" || comparison.tags.some((tag) => tag.toLowerCase() === selectedCategory.toLowerCase()) - return matchesSearch && matchesCategory + return matchesCategory }) + const loadMore = () => { + fetchComparisons(page + 1, debouncedSearch, true) + } + + const hasMore = comparisons.length < total + return (
@@ -125,14 +122,44 @@ export default function ExplorePage() {
- {loading ? ( -
- + {loading && comparisons.length === 0 ? ( +
+ {[...Array(6)].map((_, i) => ( + + + + + + +
+ + +
+ +
+
+ ))}
+ ) : error ? ( + +
+
+ +
+
+

Failed to load comparisons

+

{error}

+
+ +
+
) : filteredComparisons.length > 0 ? (
{filteredComparisons.map((comparison) => ( - +
@@ -141,7 +168,7 @@ export default function ExplorePage() {
- {comparison.description} + {comparison.summary}
@@ -153,15 +180,6 @@ export default function ExplorePage() { ))}
-
- - - {comparison.author.split(" ").map((n) => n[0]).join("")} - - - {comparison.author} -
-
{comparison.items.join(" vs ")} @@ -169,10 +187,7 @@ export default function ExplorePage() {
- {comparison.views.toLocaleString()} - - - {comparison.overallScore}/10 + {comparison.viewCount.toLocaleString()}
@@ -206,11 +221,19 @@ export default function ExplorePage() { )} -
- -
+ {loading && comparisons.length > 0 && ( +
+ +
+ )} + + {!loading && hasMore && ( +
+ +
+ )}
) } \ No newline at end of file diff --git a/src/app/(main)/profile/page.tsx b/src/app/(main)/profile/page.tsx index 5ac15f0..5c3a9e4 100644 --- a/src/app/(main)/profile/page.tsx +++ b/src/app/(main)/profile/page.tsx @@ -1,52 +1,36 @@ "use client" +import { useState, useEffect, useCallback } from "react" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { BarChart3, Eye, Calendar, Plus, ArrowRight } from "lucide-react" +import { Skeleton } from "@/components/ui/skeleton" +import { BarChart3, Eye, Calendar, Plus, ArrowRight, RefreshCw, LogIn } from "lucide-react" import Link from "next/link" +import { useSession } from "@/lib/auth-client" -const mockUser = { - name: "Alex Johnson", - email: "alex@example.com", - avatar: "/placeholder-avatar.png", +interface Comparison { + id: string + title: string + slug: string + items: string[] + tags: string[] + viewCount: number + overallScore: number + createdAt: string } -const mockComparisons = [ - { - id: "1", - title: "React vs Vue vs Svelte", - items: ["React", "Vue", "Svelte"], - tags: ["Tech", "JavaScript"], - overallScore: 8.5, - views: 1247, - createdAt: "2024-01-15", - }, - { - id: "2", - title: "GPT-4 vs Claude vs Gemini", - items: ["GPT-4", "Claude 3", "Gemini Pro"], - tags: ["AI", "Products"], - overallScore: 8.8, - views: 3891, - createdAt: "2024-01-10", - }, - { - id: "3", - title: "Notion vs Obsidian vs Roam", - items: ["Notion", "Obsidian", "Roam Research"], - tags: ["Productivity"], - overallScore: 7.5, - views: 892, - createdAt: "2024-01-05", - }, -] +interface UserComparisonsResponse { + comparisons: Comparison[] + total: number + page: number +} -const stats = [ - { label: "Total Comparisons", value: 12, icon: BarChart3 }, - { label: "Total Views", value: "8.2K", icon: Eye }, -] +interface UserStats { + totalComparisons: number + totalViews: number +} export default function ProfilePage() { return (