#typescript #next-js #tailwindcss

유저 페이지

Nov 28, 2022


유저 페이지 완성 UI

유저 페이지 생성

유저 페이지의 context path/u/[username]의 형태를 갖도록 한다.
이를 위해 reddit-clone-app\client\src\pages\u\[username].tsx 파일을 생성하고 아래 내용을 입력한다.

import UserPage from 'react'

const UserPage = () => {
    const router = useRouter();
    // 사용자 이름을 URL에서 가져옴
    const username = router.query.username;

    // swr 요청을 사용해 사용자 정보 가져옴
    const {data, error} = useSWR(username ? `/users/${username}` : null);

    return (
        <div>UserPage</div>
    )
}

export default UserPage

유저 데이터를 가져오는 API 생성

사용자 관련 API 핸들러를 정의하기 위해 reddit-clone-app\server\src\routes\users.ts 파일을 생성하고
아래 내용을 추가한다.

import { Router, Request, Response } from "express";
import { User } from "../entities/User";
import user from "../middlewares/user";
import userMiddleware from "../middlewares/user";
import Post from "../entities/Post";
import Comment from "../entities/Comment"

const getUserData = async (req: Request, res: Response) => {
    console.log('테스트')
    try {
        // 유저 정보 가져오기
        const user = await User.findOneOrFail({
            where: { username: req.params.username},
            select: ["username", "createdAt"]
        })

        // 유저가 쓴 포스트 정보 가져오기
        const posts = await Post.find({
            where: {username: user.username},
            // 연관테이블을 가져오는 이유는 userVote, voteScore 데이터를 가져오기 위함
            relations: ["comments", "votes", "sub"]
        })

        // 유저가 쓴 댓글정보 가져오기
        const comments = await Comment.find({
            where: {username: user.username},
            relations: ["post"]
        })

        // 각 포스트와 코멘트에 현재 사용자의 투표 정보 입력
        if(res.locals.user) {
            const {user} = res.locals;
            posts.forEach(p => p.setUserVote(user));
            comments.forEach(c => c.setUserVote(user));
        }

        let userData: any[] = [];

        // 엘리먼트 타입의 데이터를 json 형태 데이터로 변경 필요
        // @Expose() 데코레이터를 사용한 Getter 데이터를 포함하기 위함
        posts.forEach(p => userData.push({ type: "Post", ...p.toJSON() }));
        comments.forEach(c => userData.push({ type: "Comment", ...c.toJSON() }))

        // 최신 정보가 먼저 오게 순서 정렬
        userData.sort((a, b) => {
            if(b.createdAt > a.createdAt) return 1;
            if(b.createdAt < a.createdAt) return -1;
            return 0;
        })

        return res.json({ user, userData });
    } catch (error) {
        console.log(error);
        return res.status(500).json({ error: "문제가 발생했습니다." });
    }
}

const router = Router();
// "/api/users/[username]" 형태의 요청을 받아 핸들러 매핑
router.get("/:username", userMiddleware, getUserData)

export default router;

유저 페이지 UI 생성

reddit-clone-app\client\src\pages\u\[username].tsx 파일을 아래 내용으로 수정한다.

import Link from 'next/link';
import { useRouter } from 'next/router'
import React from 'react'
import useSWR from 'swr'
import PostCard from '../../components/PostCard';
import { Post, Comment } from '../../types';
import Image from 'next/image'
import dayjs from 'dayjs';

const UserPage = () => {
    const router = useRouter();
    const username = router.query.username;

    const {data, error} = useSWR(username ? `/users/${username}` : null);
    if(!data) return null;

    return (
        <div className='flex max-w-5xl px-4 pt-5 mx-auto'>
            {/* 유저 포스트 댓글 리스트 */}
            <div className='w-full md:mr-3 md:w-8/12'>
                {data.userData.map((data: any) => {
                    if(data.type === "Post") {
                        const post:Post = data;
                        return <PostCard key={post.identifier} post={post} />
                    } else {
                        const comment: Comment = data;
                        return (
                            <div
                                key={comment.identifier}
                                className="flex my-4 bg-white rounded"
                            >
                                <div className='flex-shrink-0 w-10 py-10 text-center bg-gray-200 rounded-l'>
                                    <i className='text-gray-500 fas fa-comment-alt fa-xs'></i>
                                </div>
                                <div className='w-full p-2'>
                                    <p className='mb-2 text-xs text-gray-500'>
                                        <Link href={`/u/${comment.username}`}>
                                            <a className='cursor-pointer hover:underline'>
                                                {comment.username}
                                            </a>   
                                        </Link>
                                        <span>commented on</span>
                                        <Link href={`${comment.post?.url}`}>
                                            <a className='cursor-pointer font-semibold hover:underline'>
                                                {comment.post?.title}
                                            </a>   
                                        </Link>
                                        <span>*</span>
                                        <Link href={`/u/${comment.post?.subName}`}>
                                            <a className='text-black cursor-pointer hover:underline'>
                                                /r/{comment.post?.subName}
                                            </a>   
                                        </Link>
                                    </p>
                                    <hr />
                                    <p className='p-1'>{comment.body}</p>
                                </div>
                            </div>
                        )
                    }
                })}
            </div>

            {/* 유저 정보 */}
            <div className='hidden w-4/12 ml-3 md:block'>
                <div className='flex items-center p-3 bg-gray-400 rounded-t'>
                    <Image
                        src="http://www.gravatar.com/avatar?d=mp&f=y"
                        alt="user profile"
                        className="mx-auto border border-white rounded-full"
                        width={40}
                        height={40}
                    />
                    <p className='pl-2 text-md'>{data.user.username}</p>
                </div>
                <div>
                    <p>
                        {dayjs(data.user.create).format("YYYY.MM.DD")} 가입
                    </p>
                </div>
            </div>
        </div>
    )
}

export default UserPage


*****

© 2021, Ritij Jain | Pudhina Fresh theme for Jekyll.