みんしゅみフロントエンド バグ修正、追加機能等
ログインメニューに自分のプロフィール画面へのリンクを追加
以下のコードを追加した
<NavLink
label="MYプロフィール"
rightSection={<CgProfile />}
component={Link}
href={`/user/${session.user.id}`}
onClick={close}
/>
追加した機能
全体図
これで自分のプロフィールに飛べるようになった
URLについて
以下のサイトを参考
(property) Session.user: {
id: string;
} & {
name?: string | null | undefined;
email?: string | null | undefined;
image?: string | null | undefined;
}
Sessionのuserのidを使いたいので
href={`/user/${session.user.id}`}
URLではこのように表示される(userの後には自分のuseridが表示されている)
作品編集画面までのリンクボタンをタイトル横に追加
以下のコードを追加した
{loginUser
? <LinkButton href={`${artId}/edit`}>
編集する
</LinkButton>
: null
}
追加した機能
全体図
ログインしていないユーザは編集できないようにする
リンク先の実装
プロフィール画面内に今見ている作品や好きな作品の編集ボタンを設置
以下のコードを追加した
//好きな作品
<Flex w="100%" justify="space-between" className={css({ mt: "lg", mb: "sm" })}>
<SectionTitle my="md" >
好きな作品
</SectionTitle>
{loginUser && (user.id === loginUser?.id
? <LinkButton href="/settings">
好きな作品を編集する
</LinkButton>
: null
)}
</Flex>
//今見ている作品
<Flex w="100%" justify="space-between" className={css({ mt: "lg", mb: "sm" })}>
<SectionTitle my="md">
今見ている作品
</SectionTitle>
{loginUser && (user.id === loginUser?.id
? <LinkButton href="/settings">
今見ている作品を編集する
</LinkButton>
: null
)}
</Flex>
追加した機能
全体図
他のユーザのプロフィール
本人以外のプロフィールは編集できないようにする
(2024/1/12)
オレンジ色の余白が邪魔なので以下のコードを削除
//my="md"を削除
<SectionTitle my="md">
オレンジ色の余白がなくなってすっきり!
404,500ページの実装
404ページ
(2024/1/15)
以下のコードを追加した
import LinkButton from '@/components/LinkButton'
import Image from 'next/image'
export default function NotFound() {
return (
<div>
<Image
src="/404.png"
alt='404'
width={800}
height={800}
/>
<p>指定されたページが見つかりません</p>
<LinkButton href="/">
トップに戻る
</LinkButton>
</div>
)
}
追加したページ
404ページのアクセスの仕方は存在しないページのURLを打ち込めばいける
参考にしたサイト
500ページ
以下のコードを追加した
"use client"
import LinkButton from "@/components/LinkButton"
import Image from 'next/image'
const ErrorPage = () => {
return (
<div>
<Image
src="/500.png"
alt='500'
width={800}
height={800}
/>
<p>指定されたページが表示できませんでした</p>
<LinkButton href="/">
トップに戻る
</LinkButton>
</div>
)
}
export default ErrorPage
追加したページ
500ページのアクセスの仕方
debug.tsximport { onlyDevelopPage } from >"@/util/server/onlyDevelop"; import Content from "./Content"; export default function DebugPage() { onlyDevelopPage() throw new Error("デバッグ用エラー") return ( ) }
サーバーエラーはなかなか起きないので
throw new Error("デバッグ用エラー")
を用いて
わざとエラーを起きさせます
参考にしたサイト
文字とボタンを位置調整
<center>
<p style={{ color: 'red' }}>
指定されたページが見つかりません
</p>
<LinkButton href="/">
トップに戻る
</LinkButton>
</center>
コンポーネントを使用して文字を表示
(2024/1/16)
以下のコードを変更
"use client"
<PageTitle style={{ color: 'red' }}>
指定されたページが表示できませんでした
</PageTitle>
}
export default ErrorPage
文字が大きくなりました
0件表示を実装
以下のコードを追加した (タグごとの作品一覧)
{arts.length === 0 &&
<Flex justify="center" align="center" p={50}>
<center>
<Image
src="/cat.png"
alt='none'
width={200}
height={400}
/>
<PageTitle >
作品がまだ無いようです....
</PageTitle>
<SectionTitle>
作品を追加してみよう<LinkButton href="/art/new" variant="gradient">作品追加</LinkButton>
</SectionTitle>
</center>
</Flex>
}
全体図
恋愛のタグが付いている作品はまだ無いため
0件用の画像が掲載できた
わざと0件結果に表示させるコード
export const searchArt = cache(async (query: string): Promise<Art[]> => {
return [] // デバッグ用に空配列
notImplementWarn(`searchArt(${query}) はまだ実装されていません。`)
await sleep(2000)
return (await prisma.art.findMany()).filter(() => Math.random() >= 0.8)
})
このコードでわざと0件結果させます
return [] // デバッグ用に空配列
日付を取得しよう
参考にしたサイト
下記のコードを入力すればできる
const today = new Date()
<span>
{today.getFullYear() !== comment.createAt.getFullYear() &&
comment.createAt.getFullYear() + "年"
}
{comment.createAt.getMonth() + 1}月
{comment.createAt.getDate()}日
</span>
comment.createAtはコメントの作成日
(property) CommentListItemProps.comment: { commentId: string; content: string; targetUserId: string; commentUserId: string; createAt: Date; updateAt: Date; }
今年のコメントだったら今年を非表示にする
CommentListItem.tsx{today.getFullYear() !== comment.createAt.getFullYear() && comment.createAt.getFullYear() + "年" }
完成図
OGP画像の導入
OGPとは?
参考サイト
使用例
page or layoutページのどちらかに設定する
静的(idがない)ページの場合
export const metadata: Metadata = getMetadata({
title: "ログイン | みんしゅみ",
})
動的(idがある)ページの場合
export async function generateMetadata({ params: { art_id } }: { params: { art_id: string } }) {
const art = await getArt(art_id)
if (!art) notFound()
return getMetadata({
title: `${art.title}のタグの編集 | みんしゅみ`,
description: `${art.description}`,
image: art.imageUrl,
})
}
ハンバーガーメニューの方のリンクをクリックし、ドロワーが閉じる
以下のコードを追加した
onClick={close}
Linkの中に上記のコードを追加する
const HeaderDrawerMenu: FC<HeaderDrawerMenuProps> = () => {
return(
<>
<Flex
className={css({ cursor: "pointer", position: "relative", width: "fit-content" })}
align="center"
onClick={toggle}
>
<Burger opened={opened} onClick={toggle} aria-label="Toggle navigation" />
</Flex>
<Drawer
opened={opened}
onClose={close}
title={
<Link href={"/"} onClick={close}>
<Image
src={LogoImage}
alt="みんしゅみ"
width={150}
height={100}
style={{ objectFit: "contain" }}
/>
</Link>
}
>
{tags.map(tag =>
<NavLink
key={tag}
variant="light"
label={tag}
component={Link}
href={`/tag/${tag}`}
onClick={close}
/>
)}
</Drawer>
</>
)
}
スマホ用だけに改行するCSS
参考にしたサイト
以下のコードを追加した
<div className={css({ display: { base: "block", sm: "inline" } })}>
base
はデフォルトサイズ、sm
はスマホサイズ
使用例
<PageTitle>
<div className={css({ display: { base: "block", sm: "inline" } })}>
コメントが
</div>
まだ無いようです
</PageTitle>
PC用
スマホ用
通知の数を表示する
参考にしたサイト
以下のコードを追加した
const Header: FC<HeaderProps> = async () => {
<Flex align="center">
{session
? <>
<NotificationButton
userId={session.user.id}
/>
<Space w="0.5em" />
<HeaderAvatar
session={session}
/>
</>
: <LoginButton />
}
</Flex>
</Flex>
}
export const NotificationButton: FC<NotificationButtonProps> = async ({ userId }) => {
const count = await getUnreadNotificationCount({ userId, max: 100 })
return (
<Indicator
label={count >= 100 ? "99+" : count}
size="16"
offset={5}
color="error"
classNames={{ indicator: flex({ justify: "center", align: "center" }) }}
disabled={count === 0}
processing
>
<ActionIcon
size="lg"
variant="subtle"
radius="xl"
color="info"
component={Link}
href="/notification"
>
<NotificationIcon />
</ActionIcon>
</Indicator>
)
}
完成図
Suspenseとは?
Suspenceを使うことでデータフェッチ等の非同期処理時に表示する待機画面
(例:スピナー等のローディング画面)の実装を非常に簡単に実装することができます。
参考サイト
書き方
import { Suspense } from "react"
export default function Page({ articleId }) {
return (
<>
<Suspense fallback={<Loading />}>
<ArticleDetail articleId={article.id} />
</Suspense>
</>
);
}
Suspenseはインポートする必要がある
プロフィール編集画面のImageInputの調整
<ImageInput
className={css({ w: `${imageSize}px`, h: `${imageSize}px`, overflow: "hidden", borderRadius: "9999px" })}
src={image}
alt={name + "の画像"}
onUpload={(publicUrl) => setImage(publicUrl)}
type="icon"
imageProps={{
className: css({ objectFit: "contain" }),
width: imageSize,
height: imageSize,
}}
withIndicator
/>
完成図