Open13

みんしゅみフロントエンド バグ修正、追加機能等

ダイソンめがねーずダイソンめがねーず

ログインメニューに自分のプロフィール画面へのリンクを追加

https://github.com/megane-s/minshumi-frontend/issues/163
(2024/1/11)
以下のコードを追加した

HeaderMenu.tsx
<NavLink
    label="MYプロフィール"
    rightSection={<CgProfile />}
    component={Link}
    href={`/user/${session.user.id}`}
    onClick={close}
 />

追加した機能

全体図

これで自分のプロフィールに飛べるようになった

URLについて

以下のサイトを参考
https://into-the-program.com/javascript-embed-variables-in-strings/

(property) Session.user: {
    id: string;
} & {
    name?: string | null | undefined;
    email?: string | null | undefined;
    image?: string | null | undefined;
}

Sessionのuserのidを使いたいので

HeaderMenu.tsx
 href={`/user/${session.user.id}`}

URLではこのように表示される(userの後には自分のuseridが表示されている)

ダイソンめがねーずダイソンめがねーず

プロフィール画面内に今見ている作品や好きな作品の編集ボタンを設置

https://github.com/megane-s/minshumi-frontend/issues/165
(2024/1/11)
以下のコードを追加した

page.tsx
//好きな作品
<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ページの実装

https://github.com/megane-s/minshumi-frontend/issues/37
https://github.com/megane-s/minshumi-frontend/issues/38

404ページ

(2024/1/15)
以下のコードを追加した

not-found.tsx
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を打ち込めばいける

参考にしたサイト
https://nextjs.org/docs/app/api-reference/file-conventions/not-found

500ページ

以下のコードを追加した

error.tsx
"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.tsx
import { onlyDevelopPage } from >"@/util/server/onlyDevelop";
import Content from "./Content";

export default function DebugPage() {
 onlyDevelopPage()
 throw new Error("デバッグ用エラー")
 return (
  )
}

サーバーエラーはなかなか起きないのでthrow new Error("デバッグ用エラー")を用いて
わざとエラーを起きさせます

参考にしたサイト
https://nextjs.org/docs/app/building-your-application/routing/error-handling

文字とボタンを位置調整

not_found.tsx
            <center>
                <p style={{ color: 'red' }}>
                    指定されたページが見つかりません
                </p>

                <LinkButton href="/">
                    トップに戻る
                </LinkButton>
            </center>

コンポーネントを使用して文字を表示

(2024/1/16)
以下のコードを変更

debug.tsx
"use client"
                <PageTitle style={{ color: 'red' }}>
                    指定されたページが表示できませんでした
                </PageTitle>
}
export default ErrorPage


文字が大きくなりました

ダイソンめがねーずダイソンめがねーず

0件表示を実装

https://github.com/megane-s/minshumi-frontend/issues/129
(2024/1/18)
以下のコードを追加した (タグごとの作品一覧)

page.tsx
            {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件結果に表示させるコード

seach.ts
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 [] // デバッグ用に空配列

ダイソンめがねーずダイソンめがねーず

日付を取得しよう

https://github.com/megane-s/minshumi-frontend/issues/256

参考にしたサイト

https://gizanbeak.com/post/typescript-date-today
下記のコードを入力すればできる

CommentListItem.tsx
const today = new Date()
CommentListItem.tsx
<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画像の導入

https://github.com/megane-s/minshumi-frontend/issues/39

OGPとは?

https://mieru-ca.com/blog/ogp/

参考サイト

https://nextjs.org/docs/app/api-reference/functions/generate-metadata

使用例

page or layoutページのどちらかに設定する

静的(idがない)ページの場合

page.tsx
export const metadata: Metadata = getMetadata({
    title: "ログイン | みんしゅみ",
})

動的(idがある)ページの場合

page.tsx
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,
    })
}
ダイソンめがねーずダイソンめがねーず

ハンバーガーメニューの方のリンクをクリックし、ドロワーが閉じる

https://github.com/megane-s/minshumi-frontend/issues/303
(2024/1/25)
以下のコードを追加した

onClick={close}

Linkの中に上記のコードを追加する

HeaderDrawerMenu.tsx
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

https://github.com/megane-s/minshumi-frontend/issues/314

参考にしたサイト

https://www.r-staffing.co.jp/engineer/entry/20230616_1
(2024/1/29)
以下のコードを追加した

 <div className={css({ display: { base: "block", sm: "inline" } })}>

baseはデフォルトサイズ、smはスマホサイズ

使用例

Comments.tsx
<PageTitle>
   <div className={css({ display: { base: "block", sm: "inline" } })}>
   コメントが
   </div>
  まだ無いようです
</PageTitle>

PC用

スマホ用

ダイソンめがねーずダイソンめがねーず

通知の数を表示する

https://github.com/megane-s/minshumi-frontend/pull/350
https://github.com/megane-s/minshumi-frontend/issues/333

参考にしたサイト

https://mantine.dev/core/indicator/
(2024/1/30)
以下のコードを追加した

Header.tsx
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とは?

https://react.dev/reference/react/Suspense
SuspenceとはReactが提供するローディング画面を表示するためのコンポーネントです。
Suspenceを使うことでデータフェッチ等の非同期処理時に表示する待機画面
(例:スピナー等のローディング画面)の実装を非常に簡単に実装することができます。

参考サイト

https://dev-harry-next.com/frontend/nextjs-loading-based-suspence

書き方

page.tsx
import { Suspense } from "react"

export default function Page({ articleId }) {
  return (
    <>
      <Suspense fallback={<Loading />}>
        <ArticleDetail articleId={article.id} />
      </Suspense>
    </>
  );
}

Suspenseはインポートする必要がある

ダイソンめがねーずダイソンめがねーず

プロフィール編集画面のImageInputの調整

UserSettingForm.tsx
                    <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
                    />

完成図