我們要完善頭像的區塊,讓它顯示照片,如果用戶沒有設定照片就顯示預設的頭像。
修改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;
完成後可以看到以下的畫面。

安裝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。
