diff --git a/next/src/app/[locale]/(common-layout)/user/[handle]/components/page-list.server.tsx b/next/src/app/[locale]/(common-layout)/user/[handle]/components/page-list.server.tsx new file mode 100644 index 00000000..68d5fd88 --- /dev/null +++ b/next/src/app/[locale]/(common-layout)/user/[handle]/components/page-list.server.tsx @@ -0,0 +1,65 @@ +import { PageCard } from "@/app/[locale]/components/page-card"; +import { fetchPaginatedPublicPagesWithInfo } from "@/app/[locale]/db/queries.server"; +import { fetchUserByHandle } from "@/app/db/queries.server"; +import { getCurrentUser } from "@/auth"; +import { getGuestId } from "@/lib/get-guest-id"; +import { notFound } from "next/navigation"; +import { PaginationControls } from "./pagination-controls"; +interface PageListServerProps { + handle: string; + page: number; + locale: string; +} + +export async function PageListServer({ + handle, + page, + locale, +}: PageListServerProps) { + const currentUser = await getCurrentUser(); + const guestId = !currentUser ? await getGuestId() : undefined; + const isOwner = currentUser?.handle === handle; + + const pageOwner = await fetchUserByHandle(handle); + if (!pageOwner) { + return notFound(); + } + const { pagesWithInfo, totalPages, currentPage } = + await fetchPaginatedPublicPagesWithInfo({ + page: page, + pageSize: 9, + currentUserId: currentUser?.id, + currentGuestId: guestId, + pageOwnerId: pageOwner.id, + onlyUserOwn: true, + locale, + }); + + if (pagesWithInfo.length === 0) { + return ( +

+ {isOwner ? "You haven't created any pages yet." : "No pages yet."} +

+ ); + } + + return ( + <> +
+ {pagesWithInfo.map((pageWithInfo) => ( + + ))} +
+ +
+ +
+ + ); +} diff --git a/next/src/app/[locale]/(common-layout)/user/[handle]/components/user-info.server.tsx b/next/src/app/[locale]/(common-layout)/user/[handle]/components/user-info.server.tsx new file mode 100644 index 00000000..ab0a302f --- /dev/null +++ b/next/src/app/[locale]/(common-layout)/user/[handle]/components/user-info.server.tsx @@ -0,0 +1,107 @@ +import { fetchUserByHandle } from "@/app/db/queries.server"; +import { getCurrentUser } from "@/auth"; +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 { Link } from "@/i18n/routing"; +import Linkify from "linkify-react"; +import { Settings } from "lucide-react"; +import { + fetchFollowerList, + fetchFollowingList, + getFollowCounts, +} from "../db/queries.server"; +import { FollowButton } from "./follow-button"; +import { FollowStats } from "./follow-stats"; + +export async function UserInfo({ + handle, +}: { + handle: string; +}) { + const pageOwner = await fetchUserByHandle(handle); + if (!pageOwner) { + const error = new Error("Unauthorized"); + error.message = "Unauthorized"; + throw error; + } + + const currentUser = await getCurrentUser(); + + const isOwner = currentUser?.handle === handle; + + const followCounts = await getFollowCounts(pageOwner.id); + const followerList = await fetchFollowerList(pageOwner.id); + const followingList = await fetchFollowingList(pageOwner.id); + + return ( + + +
+
+ + + + + {pageOwner.name.charAt(0).toUpperCase()} + + + +
+
+
+ + {pageOwner.name} + +
+ + @{pageOwner.handle} + + ({ + handle: item.following.handle, + name: item.following.name, + image: item.following.image, + }))} + followerList={followerList.map((item) => ({ + handle: item.follower.handle, + name: item.follower.name, + image: item.follower.image, + }))} + /> +
+
+ + {isOwner ? ( + + + + ) : ( + + )} +
+
+
+ + + + {pageOwner.profile} + + +
+ ); +} diff --git a/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/components/page-management-tab/server.tsx b/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/components/page-management-tab/server.tsx index d676078e..68c0197e 100644 --- a/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/components/page-management-tab/server.tsx +++ b/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/components/page-management-tab/server.tsx @@ -5,7 +5,7 @@ import { PageManagementTabClient } from "./client"; interface PageManagementTabProps { currentUserId: string; locale: string; - page: string; + page: number; query: string; handle: string; } @@ -18,13 +18,7 @@ export async function PageManagementTab({ handle, }: PageManagementTabProps) { const { pagesWithTitle, totalPages, currentPage } = - await fetchPaginatedOwnPages( - currentUserId, - locale, - Number(page), - 10, - query, - ); + await fetchPaginatedOwnPages(currentUserId, locale, page, 10, query); const pagesWithTitleAndViewData = await Promise.all( pagesWithTitle.map(async (pageData) => { const path = `/user/${handle}/page/${pageData.slug}`; diff --git a/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/page.tsx b/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/page.tsx index e5bba5cd..a70139e0 100644 --- a/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/page.tsx +++ b/next/src/app/[locale]/(common-layout)/user/[handle]/page-management/page.tsx @@ -2,7 +2,8 @@ import { getCurrentUser } from "@/auth"; import { Skeleton } from "@/components/ui/skeleton"; import dynamic from "next/dynamic"; import { redirect } from "next/navigation"; - +import { createLoader, parseAsInteger, parseAsString } from "nuqs/server"; +import type { SearchParams } from "nuqs/server"; const PageManagementTab = dynamic( () => import("./components/page-management-tab/server").then( @@ -22,22 +23,26 @@ const PageManagementTab = dynamic( ), }, ); + +const searchParamsSchema = { + page: parseAsInteger.withDefault(1), + query: parseAsString.withDefault(""), +}; +const loadSearchParams = createLoader(searchParamsSchema); + export default async function PageManagementPage({ params, searchParams, }: { params: Promise<{ locale: string }>; - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; + searchParams: Promise; }) { const currentUser = await getCurrentUser(); if (!currentUser || !currentUser.id) { return redirect("/auth/login"); } const { locale } = await params; - const { page = "1", query = "" } = await searchParams; - if (typeof page !== "string" || typeof query !== "string") { - throw new Error("Invalid page or query"); - } + const { page, query } = await loadSearchParams(searchParams); return (
diff --git a/next/src/app/[locale]/(common-layout)/user/[handle]/page.tsx b/next/src/app/[locale]/(common-layout)/user/[handle]/page.tsx index 873dc713..f681677a 100644 --- a/next/src/app/[locale]/(common-layout)/user/[handle]/page.tsx +++ b/next/src/app/[locale]/(common-layout)/user/[handle]/page.tsx @@ -1,31 +1,30 @@ -import { PageCard } from "@/app/[locale]/components/page-card"; -import { fetchPaginatedPublicPagesWithInfo } from "@/app/[locale]/db/queries.server"; import { fetchUserByHandle } from "@/app/db/queries.server"; -import { getCurrentUser } from "@/auth"; -import { Link } from "@/i18n/routing"; - -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Button } from "@/components/ui/button"; -import { - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Card } from "@/components/ui/card"; -import { getGuestId } from "@/lib/get-guest-id"; -import Linkify from "linkify-react"; -import { Settings } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; import type { Metadata } from "next"; +import dynamic from "next/dynamic"; import { notFound } from "next/navigation"; -import { FollowButton } from "./components/follow-button"; -import { FollowStats } from "./components/follow-stats"; -import { PaginationControls } from "./components/pagination-controls"; -import { - fetchFollowerList, - fetchFollowingList, - getFollowCounts, -} from "./db/queries.server"; +import { createLoader, parseAsInteger } from "nuqs/server"; +import type { SearchParams } from "nuqs/server"; +const PageList = dynamic( + () => + import("./components/page-list.server").then((mod) => mod.PageListServer), + { + loading: () => ( +
+ + + +
+ ), + }, +); +const UserInfo = dynamic( + () => import("./components/user-info.server").then((mod) => mod.UserInfo), + { + loading: () => , + }, +); + export async function generateMetadata({ params, }: { @@ -33,150 +32,34 @@ export async function generateMetadata({ }): Promise { const { handle } = await params; if (!handle) { - return { title: "Profile" }; + return notFound(); } const pageOwner = await fetchUserByHandle(handle); if (!pageOwner) { - return { title: "Not Found" }; + return notFound(); } return { title: pageOwner.name }; } +const searchParamsSchema = { + page: parseAsInteger.withDefault(1), +}; +const loadSearchParams = createLoader(searchParamsSchema); + export default async function UserPage({ params, searchParams, }: { params: Promise<{ locale: string; handle: string }>; - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; + searchParams: Promise; }) { const { locale, handle } = await params; - const { page = "1" } = await searchParams; - if (typeof page !== "string") { - const error = new Error("Invalid page number"); - error.message = "Invalid page number"; - throw error; - } - - const pageOwner = await fetchUserByHandle(handle); - if (!pageOwner) { - const error = new Error("Unauthorized"); - error.message = "Unauthorized"; - throw error; - } - - const pageSize = 9; - - const currentUser = await getCurrentUser(); - const guestId = !currentUser ? await getGuestId() : undefined; - - const isOwner = currentUser?.handle === handle; - - const { pagesWithInfo, totalPages, currentPage } = - await fetchPaginatedPublicPagesWithInfo({ - page: Number(page), - pageSize, - currentUserId: currentUser?.id, - currentGuestId: guestId, - pageOwnerId: pageOwner.id, - onlyUserOwn: true, - locale, - }); - if (!pagesWithInfo) return notFound(); - const followCounts = await getFollowCounts(pageOwner.id); - const followerList = await fetchFollowerList(pageOwner.id); - const followingList = await fetchFollowingList(pageOwner.id); + const { page } = await loadSearchParams(searchParams); return ( -
- - -
-
- - - - - {pageOwner.name.charAt(0).toUpperCase()} - - - -
-
-
- - {pageOwner.name} - -
- - @{pageOwner.handle} - - ({ - handle: item.following.handle, - name: item.following.name, - image: item.following.image, - }))} - followerList={followerList.map((item) => ({ - handle: item.follower.handle, - name: item.follower.name, - image: item.follower.image, - }))} - /> -
-
- - {isOwner ? ( - - - - ) : ( - - )} -
-
-
- - - - {pageOwner.profile} - - -
- -
- {pagesWithInfo.map((page) => ( - - ))} -
- - {pagesWithInfo.length > 0 && ( -
- -
- )} - - {pagesWithInfo.length === 0 && ( -

- {isOwner ? "You haven't created any pages yet." : "No pages yet."} -

- )} -
+ <> + + + ); } diff --git a/next/src/app/[locale]/components/pages-list-tab/index.tsx b/next/src/app/[locale]/components/pages-list-tab/index.tsx index 86c7d9df..652a5584 100644 --- a/next/src/app/[locale]/components/pages-list-tab/index.tsx +++ b/next/src/app/[locale]/components/pages-list-tab/index.tsx @@ -4,13 +4,13 @@ import { getGuestId } from "@/lib/get-guest-id"; import { createLoader, parseAsInteger, parseAsString } from "nuqs/server"; import type { SearchParams } from "nuqs/server"; import { PagesListTabClient } from "./client"; -export const coordinatesSearchParams = { +const searchParamsSchema = { activeTab: parseAsString.withDefault("recommended"), recommendedPage: parseAsInteger.withDefault(1), newPage: parseAsInteger.withDefault(1), }; -export const loadSearchParams = createLoader(coordinatesSearchParams); +const loadSearchParams = createLoader(searchParamsSchema); interface PageListTabProps { locale: string;