iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
自我挑戰組

模仿知名網站的外觀系列 第 23

【Day23】模仿知名網站的外觀 X(10) 推薦追蹤區塊-2與完成個人檔案區塊

  • 分享至 

  • xImage
  •  

我們要完善頭像的區塊,讓它顯示照片,如果用戶沒有設定照片就顯示預設的頭像。

修改Avatar.tsx。

import useUser from "@/Hooks/useUser";
import { useCallback } from "react";
import { useRouter } from "next/router";
import Image from "next/image";

interface AvatarProps {
    userId: string;
    isLarge?: boolean;
    hasBorder?: boolean;
}

const Avatar: React.FC<AvatarProps> = ({
    userId,
    isLarge,
    hasBorder
}) => {
    const router = useRouter();
    const{ data: fetchedUser } = useUser(userId);

    const onClick = useCallback((event: any) => {
        event.stopPropagation();

        const url = `/users/${userId}`;

        router.push(url); 
    }, [router, userId]);

    return (
        <div className={`
            ${hasBorder ? 'border-4 border-black' : ''}
            ${isLarge ? 'h-32' : 'h-12'}
            ${isLarge ? 'w-32' : 'w-12'}
            rounded-full
            hover:opacity-90
            transition
            cursor-pointer
            relative
        `}>
            <Image
                fill
                style={{
                    objectFit: 'cover',
                    borderRadius: '100%'
                }}
                alt="Avatar"
                onClick={onClick}
                src={fetchedUser?.avatarImage || '/images/placeholder.png'}
            />
        </div>
    );
}

export default Avatar;

這張照片 下載下來後,放到public/images底下,這樣頭像區塊就完成了。

下個要完成的部分是個人檔案區塊。

安裝需要的套件

npm install react-spinners

在pages資料夾下,建立users資料夾。

在users資料夾下,建立[userId].tsx,用來顯示個人檔案,等待載入時會在畫面上顯示轉圈。

import { useRouter } from "next/router";
import { ClipLoader } from "react-spinners";
import Header from "@/Components/layout/Header";
import useUser from "@/Hooks/useUser";

const UserView = () => {
    const router = useRouter();
    const { userId } = router.query;

    const { data: fetchedUser, isLoading} = useUser(userId as string);

    if(isLoading || !fetchedUser){
        return (
            <div className="flex justify-center items-center h-full">
                <ClipLoader color="lightblue" size={80}/>
            </div>
        )
    }

    return (
        <>
            <Header showBackArrow label={fetchedUser?.name} />
        </>
    )
}

export default UserView;

修復以前Home顯示的位置錯誤。

Sidebar.tsx

import { BsHouseFill, BsBellFill } from "react-icons/bs";
import { FaUser } from "react-icons/fa";
import { BiLogOut } from "react-icons/bi";
import SidebarLogo from "./SidebarLogo";
import SidebarItem from "./SidebarItem";
import SidebarTweetButton from "./SidebarTweetButton";
import useCurrentUser from "@/Hooks/useCurrentUser";
import { signOut } from "next-auth/react";

const Sidebar = () => {
	const { data: currentUser } = useCurrentUser();
	const items = [
		{
			label: "Home",
			href: "/",
			icon: BsHouseFill,
		},
		{
			label: "Notifications",
			href: "/notifications",
			icon: BsBellFill,
			auth: true
		},
		{
			label: "Profile",
			href: "/users/1",
			icon: FaUser,
			auth: true
		},
	];
	return (
		<div className="col-span-1 h-full pr-4 md:pr-6">
			<div className="flex flex-col items-end">
				<div className="space-y-2 lg:w-[230px]">
					<SidebarLogo />
					{items.map((item) => (
						<SidebarItem
							key={item.href}
							href={item.href}
							label={item.label}
							icon={item.icon}
							auth={item.auth}
						/>
					))}
					{currentUser && (
						<SidebarItem onClick={() => signOut()} icon={BiLogOut} label="Logout" />
					)}
					<SidebarTweetButton />
				</div>
			</div>
		</div>
	);
};

export default Sidebar;

Layout.tsx

import FollowBar from "./layout/FollowBar";
import Sidebar from "./layout/Sidebar";

interface LayoutProps {
	children: React.ReactNode;
}

const Layout: React.FC<LayoutProps> = ({ children }) => {
	return (
		<div className="h-screen bg-black">
			<div className="container h-full mx-auto xl:px-30 max-w-6xl">
				<div className="grid grid-cols-4 h-full">
					<Sidebar />
					<div className="col-span-3 lg:col-span-2 border-x-[1px] border-neutral-800">
						{children}
					</div>
					<FollowBar />
				</div>
			</div>
		</div>
	);
};

export default Layout;

到MongoDB查看User的_id,假設是64f98ac22095377e41f59c90,我們就在瀏覽器開啟 http://localhost:3000/users/64f98ac22095377e41f59c90 ,就能看到中間的區塊顯示一個向左的箭頭和用戶名稱。

在Components資料夾下,建立users資料夾。

在users資料夾下,建立UserHero.tsx,用來顯示個人檔案的橫幅圖片和頭像。

import Image from "next/image";
import useUser from "@/Hooks/useUser";
import Avatar from "../Avatar";

interface UserHeroProps {
    userId: string;
}

const UserHero: React.FC<UserHeroProps> = ({userId}) => {
    const {data: fetchedUser} = useUser(userId);

  return (
    <div>
        <div className="bg-neutral-700 h-44 relative">
            {
                fetchedUser?.bannerImage && (
                    <Image src={fetchedUser.bannerImage}
                    fill
                    alt="Banner Image"
                    style={{objectFit: 'cover'}}
                    />
                )
            }
            <div className="absolute -bottom-16 left-4">
                <Avatar userId={userId} isLarge hasBorder />
            </div>
        </div>
    </div>
  )
}

export default UserHero

修改[userId].tsx,顯示UserHero。

import { useRouter } from "next/router";
import { ClipLoader } from "react-spinners";
import Header from "@/Components/layout/Header";
import useUser from "@/Hooks/useUser";
import UserHero from "@/Components/users/UserHero";

const UserView = () => {
    const router = useRouter();
    const { userId } = router.query;

    const { data: fetchedUser, isLoading} = useUser(userId as string);

    if(isLoading || !fetchedUser){
        return (
            <div className="flex justify-center items-center h-full">
                <ClipLoader color="lightblue" size={80}/>
            </div>
        )
    }

    return (
        <>
            <Header showBackArrow label={fetchedUser?.name} />
            <UserHero userId={userId as string}/>
        </>
    )
}

export default UserView;

完成後可以看到以下的畫面。

Untitled

安裝date-fns用來處理時間和日期

npm i date-fns

在users資料夾下,建立UserBio.tsx,顯示指定ID的使用者個人資料,包含姓名、用戶名、自我介紹、加入的時間、關注數與被關注數。

import { useMemo } from "react";
import { format } from "date-fns";
import useCurrentUser from "@/Hooks/useCurrentUser";
import useUser from "@/Hooks/useUser";
import Button from "../Button";
import { BiCalendar } from "react-icons/bi";

interface UserBioProps {
    userId: string;
}

const UserBio: React.FC<UserBioProps> = ({ userId }) => {
    const {data: currentUser} = useCurrentUser();
    const {data: fetchedUser} = useUser(userId);

    const createdAt = useMemo(() => {
        if(!fetchedUser?.createdAt){
            return null;
        }

        return format(new Date(fetchedUser.createdAt), 'MMMM yyyy');
    }, [fetchedUser?.createdAt]);

  return (
    <div className="border-b-[1px] border-neutral-800 pb-4">
        <div className="flex justify-end p-2">
            {
                currentUser?.id === userId ? (
                    <Button secondary label="Edit" onClick={() => {}} />
                ) : (
                <Button secondary
                label="Follow"
                onClick={() => {}}
                />
                )
            }
        </div>
        <div className="mt-8 px-4">
            <div className="flex flex-col">
                <p className="text-white text-2xl font-semibold">
                {fetchedUser?.name}
                </p>
                <p className="text-md text-neutral-500">
                @{fetchedUser?.username}
                </p>
            </div>
            <div className="flex flex-col mt-4">
                <p className="text-white">
                    {fetchedUser?.bio}
                </p>
                <div className="flex flex-row items-center gap-2 mt-4 text-neutral-500">
                    <BiCalendar size={24}/>
                    <p>
                        Joined {createdAt}
                    </p>
                </div>
            </div>
            <div className="flex flex-row items-center mt-4 gap-6">
                <div className="flex flex-row items-center gap-1">
                    <p className="text-white">
                        {fetchedUser?.followingIds?.length}
                    </p>
                    <p className="text-neutral-500">
                        Following
                    </p>
                </div>
                <div className="flex flex-row items-center gap-1">
                    <p className="text-white">
                        {fetchedUser?.followersCount || 0}
                    </p>
                    <p className="text-neutral-500">
                        Followers
                    </p>
                </div>
            </div>
        </div>
    </div>
  )
}

export default UserBio

修改[userId].tsx顯示UserBio區塊的內容

import { useRouter } from "next/router";
import { ClipLoader } from "react-spinners";
import Header from "@/Components/layout/Header";
import useUser from "@/Hooks/useUser";
import UserHero from "@/Components/users/UserHero";
import UserBio from "@/Components/users/UserBio";

const UserView = () => {
    const router = useRouter();
    const { userId } = router.query;

    const { data: fetchedUser, isLoading} = useUser(userId as string);

    if(isLoading || !fetchedUser){
        return (
            <div className="flex justify-center items-center h-full">
                <ClipLoader color="lightblue" size={80}/>
            </div>
        )
    }

    return (
        <>
            <Header showBackArrow label={fetchedUser?.name} />
            <UserHero userId={userId as string}/>
            <UserBio userId={userId as string}/>
        </>
    )
}

export default UserView;

在瀏覽器開啟 localhost:3000/users/使用者的ID ,如果是本人的情況下,會顯示以下畫面,如果是其他人的話Edit會變成Follow。

Untitled


上一篇
【Day22】模仿知名網站的外觀 X(9) 登入實作與推薦追蹤區塊
下一篇
【Day24】模仿知名網站的外觀 X(11) 開發個人資料修改功能
系列文
模仿知名網站的外觀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言