From e881ffc26518324bc4853ffe30f68fc98ee65098 Mon Sep 17 00:00:00 2001 From: Ave Votsira Date: Tue, 20 Feb 2024 19:23:23 +0700 Subject: [PATCH] feat: user profile page --- src/app/(public)/[username]/page.tsx | 78 +++++++++++++++++++++++ src/app/api/sign-up/route.ts | 6 +- src/components/articles/article-card.tsx | 44 +++++++++++++ src/components/user-info/user-profile.tsx | 31 +++++++++ src/lib/query/article.ts | 22 +++++++ src/lib/query/user.ts | 14 ++++ 6 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 src/app/(public)/[username]/page.tsx create mode 100644 src/components/articles/article-card.tsx create mode 100644 src/components/user-info/user-profile.tsx create mode 100644 src/lib/query/user.ts diff --git a/src/app/(public)/[username]/page.tsx b/src/app/(public)/[username]/page.tsx new file mode 100644 index 0000000..97c291a --- /dev/null +++ b/src/app/(public)/[username]/page.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { getArticlesByUsername } from "@/lib/query/article"; +import { getUserByUsername } from "@/lib/query/user"; + +import { ArticleCard } from "@/components/articles/article-card"; +import { UserProfile } from "@/components/user-info/user-profile"; + +interface UserProfilePageProps { + params: { username: string }; +} + +const UserProfilePage: React.FC = (props) => { + const { username } = props.params; + + const decodedUserName = decodeURIComponent(username); + const usernameWithoutAt = decodedUserName.slice(1); + + const [user, setUser] = useState(null); + const [articles, setArticles] = useState([]); + const [offset, setOffset] = useState(0); + const [hasMore, setHasMore] = useState(true); + + const loadUser = async () => { + const user = await getUserByUsername(usernameWithoutAt); + setUser(user); + }; + const loadArticles = async () => { + const newArticles = await getArticlesByUsername( + usernameWithoutAt, + 10, + offset, + ); + + if (!newArticles) { + return setHasMore(false); + } + setArticles((prevArticles) => [...prevArticles, ...newArticles]); + setOffset((prevOffset) => prevOffset + newArticles.length); + setHasMore(newArticles.length === 10); + }; + + useEffect(() => { + loadUser(); + loadArticles(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (!user) { + return ( +
+
User not found...
+
+ ); + } + + return ( +
+
+ +
+ {articles.map((article) => ( + + ))} +
+ {hasMore && ( + + )} +
+
+ ); +}; + +export default UserProfilePage; diff --git a/src/app/api/sign-up/route.ts b/src/app/api/sign-up/route.ts index 19e363a..8e782be 100644 --- a/src/app/api/sign-up/route.ts +++ b/src/app/api/sign-up/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { auth } from "@/lib/auth/lucia"; -import { db } from "@/lib/db"; +import { getUserByUsername } from "@/lib/query/user"; import { LuciaError } from "lucia"; @@ -28,9 +28,7 @@ export const POST = async (request: NextRequest) => { return NextResponse.json({ error: "Invalid password" }, { status: 400 }); } - const existingUser = await db.query.users.findFirst({ - where: (field, op) => op.eq(field.username, username.toLowerCase()), - }); + const existingUser = await getUserByUsername(username); if (existingUser) { return NextResponse.json( diff --git a/src/components/articles/article-card.tsx b/src/components/articles/article-card.tsx new file mode 100644 index 0000000..d559c36 --- /dev/null +++ b/src/components/articles/article-card.tsx @@ -0,0 +1,44 @@ +/* eslint-disable @next/next/no-img-element */ +interface ArticleCardProps { + article: { + id: string; + createdAt: string; + updatedAt: string; + userId: string; + description: string | null; + title: string; + content: string; + cover: string | null; + }; +} + +export const ArticleCard = ({ article }: ArticleCardProps) => { + const formattedDate = article.createdAt + ? article.createdAt.slice(0, 10) + : "Unknown Date"; + return ( +
+
+
+
+ {formattedDate} +
+ + {article.title} + +

{article.description}

+
+
+ {article.title} +
+
+
+ ); +}; diff --git a/src/components/user-info/user-profile.tsx b/src/components/user-info/user-profile.tsx new file mode 100644 index 0000000..4a44109 --- /dev/null +++ b/src/components/user-info/user-profile.tsx @@ -0,0 +1,31 @@ +/* eslint-disable @next/next/no-img-element */ +interface UserProfileProps { + user: { + id: string; + name: string | null; + email: string | null; + username: string; + avatar: string | null; + about: string | null; + createdAt: string; + updatedAt: string; + }; +} + +export const UserProfile = ({ user }: UserProfileProps) => { + return ( +
+ {user.name +

{user.name}

+ {10} Followers +

{user.about}

+ +
+ ); +}; diff --git a/src/lib/query/article.ts b/src/lib/query/article.ts index 6d183b8..46d5012 100644 --- a/src/lib/query/article.ts +++ b/src/lib/query/article.ts @@ -1,4 +1,5 @@ import { db } from "../db"; +import { getUserByUsername } from "./user"; export type CompleteArticle = Awaited< ReturnType @@ -16,3 +17,24 @@ export const getArticleById = async (id: string) => { with: { author: true }, }); }; + +export const getArticlesByUsername = async ( + username: string, + limit = 10, + offset = 0, +) => { + const user = await getUserByUsername(username); + + if (!user) { + return null; // or throw an error here later ok, ave? + } + + const articles = await db.query.articles.findMany({ + where: (articles, { eq }) => eq(articles.userId, user.id), + limit, + offset, + orderBy: (articles, { desc }) => [desc(articles.createdAt)], + }); + + return articles; +}; diff --git a/src/lib/query/user.ts b/src/lib/query/user.ts new file mode 100644 index 0000000..4f3bd6d --- /dev/null +++ b/src/lib/query/user.ts @@ -0,0 +1,14 @@ +import { db } from "../db"; + +/** + * Retrieves a user by their username. + * + * @param {string} username - the username of the user to retrieve + * @return {Promise} the user object + */ +export const getUserByUsername = async (username: string) => { + const user = await db.query.users.findFirst({ + where: (field, op) => op.eq(field.username, username.toLowerCase()), + }); + return user; +};