Full Stack E-Commerce + Dashboard

# 管理画面

## プロジェクト作成
<details><summary>Next作成</summary>

```zsh
npx create-next-app e-commerce-admin --typescript --tailwind --eslint

✔ Would you like to use `src/` directory with this project? … No
✔ Use App Router (recommended)? … Yes
✔ Would you like to customize the default import alias? … No
```
</details>

## ダッシュボードのUIインストール
https://ui.shadcn.com/
<details><summary>shadcnのインストール</summary>

```zsh
npx shadcn-ui@latest init

✔ Would you like to use TypeScript (recommended)? … yes
✔ Which style would you like to use? › Default
✔ Which color would you like to use as base color? › Slate
✔ Where is your global CSS file? … app/globals.css
✔ Would you like to use CSS variables for colors? … yes
✔ Where is your tailwind.config.js located? … tailwind.config.js
✔ Configure the import alias for components: … @/components
✔ Configure the import alias for utils: … @/lib/utils
✔ Are you using React Server Components? … yes
✔ Write configuration to components.json. Proceed? … yes
```
</details>

## 実装準備
- (auth)でディレクトリを作ってその下にpage.tsxを置く [github](https://github.com/yk-graph/admin-e-commerce/commit/9948966cb8fcada925f246f9bc55efca42d53bbe)
- shadcnのbuttonコンポーネントをinstall `npx shadcn-ui@latest add button`
    - 何かコンポーネントを追加するときは毎回installしないとダメらしい
    - installに成功ｈしたら `/components/ui/` 配下にコンポーネントができる

## 認証機能(Clerk)
https://clerk.com/?utm_source=www.google.com&utm_medium=referral&utm_campaign=none

https://clerk.com/docs/nextjs/get-started-with-nextjs

### 手順
基本clerkの公式に乗ってる手順で進める
1. clerkのインストール `npm install @clerk/nextjs`
2. envファイルに環境変数をセット
3. layout部分にProviderをセット
4. middleware.tsに記述追加
5. (auth)>(routers)ディレクトリを作って、その中にsign-upとsign-inに必要なディレクトリとファイルを作成する
6. envファイルに設定を記述する（環境変数で設定する命名を変更する場合は、app > (auth) > (routes) 配下で設定したディレクトリ名・ファイル名も併せて変更する必要があるので注意）
7. `http://localhost:3000/sign-in` `http://localhost:3000/sign-up` とかにアクセスすると、サインイン・サインアップ用のUIが勝手に作られてる
8. 共通のレイアウトとして `app/(auth)/layout.tsx` を作る [github](https://github.com/yk-graph/admin-e-commerce/commit/cce9ace51bb6de3abb9df306f80768fcc20750a3)
9. <UserButton />のコンポーネントをTOPページに入れる

## ストア作成用のモーダル
:::note info
やりたいこと
TOPページに訪問されたら、ストア作成用のモーダルをすぐに表示するようにする
:::

### 方針
1. 一番上の階層でmodalのコンポーネント自体は存在するようにする -> **layout.tsxに記述する**
1. モーダルの開閉の制御は各page.tsx内で制御させる
1. どのページからでもモーダルの開閉のstateにアクセスできるようにする -> **storeにグローバルな値を持たせる**
1. モーダルのコンポーネントはクライアントコンポーネントのため、Hydrationエラーを防ぐ考慮が必要 -> **モーダル用のProviderコンポーネントを設けて、マウントしてなければnullを返してHydrationエラーを防ぐ**
1. モーダルのUIコンポーネントと、用途別（ストアを作成する時に表示するモーダルなのか、アラートを表示するためのモーダルなのか）のモーダルコンポーネントを用意する

### 実装
1. 必要なUIパーツを用意
    - dialogのインストール `npx shadcn-ui@latest add dialog`
1. モーダルのUIコンポーネントを作る `components/ui/modal.tsx` [github](https://github.com/yk-graph/admin-e-commerce/commit/1d020fa1a91e40b37df9987693de9837efbcc80e)
    -  モーダルの開閉を制御するonChange関数を追加
    -  shadcn-uiのDialogで持っているpropsは[この辺](https://www.radix-ui.com/docs/primitives/components/dialog)を参考
    -  `<Dialog open={isOpen} onOpenChange={onChange}>` で使っている `open` とか `onOpenChange` とかはデフォルトのpropsで定義されている
1. グローバルな値を管理する
    - zustandのインストール
    - hooksディレクトリを作ってmodal用のstoreを作成 `hooks/use-store-modal.tsx`
    - isOpenの初期値はfalse（非表示のステータスにしとく）
1. 用途別のモーダルを作る
    - 管理画面で新規でストアを作成する時に表示されるモーダルのUIを作る `components/modals/store-modal.tsx`
1. モーダルのProviderを作ってlayout層に設置する
    - `providers/modal-provider.tsx` を作成
    - Hydrationエラーを防ぐため、クライアントのページがマウントされない場合はnullを返却するように記述する [github](https://github.com/yk-graph/admin-e-commerce/commit/80745f6bbc90079a9068c3fa6df6390f0a6b504a#diff-b76abc392cb2a448a7d4cd6827c054fc1a2976520c0e4db34251feeb9f2ceb3f)
    - クライアントのページがマウントされたらストア用のコンポーネントが表示されるような記述をする
    - Providerのコンポーネントをlayout.tsx内に配置
1. TOPページでページがマウントされたらストア作成用のモーダルを表示させる
    - useEffectを使って、マウントされたらグローバルstoreのisOpenの関数を呼び出す `app/(root)/page.tsx`
    - page.tsx内にはmodalのコンポーネントはないのがポイントかも
    - あくまでlayout.tsxのレイヤーでモーダルのコンポーネントがあって、表示・非表示のステータスはグローバルstoreで保持してる

## モーダル内のフォーム作成
:::note info
やりたいこと
ストア作成用のモーダルの中にストア名を入力するフォームをバリデーション機能付きで実装する
:::

### 方針
1. バリデーション機能 -> **zodを使用**
1. 一旦入力フォームはストア名のインプットフィールドのみ
1. 基本的にshadcn/uiの公式に乗ってるzodのバリデーションの記述を参考

https://ui.shadcn.com/docs/components/form

### 実装
1. 必要なUIパーツを用意 `npx shadcn-ui@latest add form` ` npx shadcn-ui@latest add input
`
1. zodインストール `npm install zod`
1. 記述は `components/modals/store-modal.tsx` に書いてく
    - useFormをimportしてコンポーネント内で使えるようにしとく（shadcn/uiのFormをインストールした時点で勝手にインストールされる）
    - バリデーションのルール（formSchema）を定義する
    - resolverとdefaultValuesを定義したformインスタンスを作成する
    - onSubmit関数を作る
1. [github](https://github.com/yk-graph/admin-e-commerce/commit/1e0ec4f11ac95cc24f3e6436d8dc652af1d906d4)を参照

### zodのTIPS
1. onSubmitをするときは `<form onSubmit={form.handleSubmit(onSubmit)}>` で指定
1. onSubmit関数の引数は `const onSubmit = async (values: z.infer<typeof formSchema>) => {...}` となる
1. resolverとdefaultValuesを定義したformインスタンスは `const form = useForm<z.infer<typeof formSchema>>({ ... })` 内に記述する

## prisma導入
:::note info
やりたいこと
1. MySQLでstoreテーブルを作る
2. Prismaを使ってDBと接続、マイグレーションする
3. アプリケーションからDBの値をPrisma経由で取得できるようにする
:::

### 使用サービス
- Prisma（ORM）：フロントからDBにアクセスするためのツール
- PlanetScale（サーバーレスDB）：今回使用するMySQLのDB

https://www.prisma.io/docs/concepts/database-connectors/mysql

https://planetscale.com/

### 実装
1. prismaのインストール `npm install @prisma/client`
1. prismaの初期セッティング `npx prisma init`
1. データベースにクエリを送信するための **PrismaClient** を作成する `lib/prismadb.ts` [github](https://github.com/yk-graph/admin-e-commerce/commit/356027dd4298e9a24071223fd157e03bf9aaf13a)
1. PlanetScaleにログインして新しくDBを作成する
1. DBの設定する
    - Connection strings をクリック
    - New Password をクリック
    - Connect with -> `Prisma` を選択
    - .env  をクリック -> DATABASE_URLの値をそのままenvファイルの追記
    - schema.prisma  をクリック -> コピーした内容を `prisma/schema.prisma` にペースト
1. storeテーブルのschemaの定義を `prisma/schema.prisma` に記述する
1. Prisma Client を作成するコマンドをうつ `npx prisma generate` -> **フロント側からPrismaClientを使ってDBにアクセスできるようになる**
1. Prisma経由でマイグレーションする `npx prisma db push`  -> **PlanetScaleのDBにStoreテーブルができる**

### Prisma Tips
- `npx prisma generate` ： schemaの定義した後にPrismaClientを使ってDBにアクセスを可能にするためのコマンド
- `npx prisma db push` ： schemaに定義した内容をマイグレーションするコマンド
- `npx prisma migrate reset` ： マイグレーションリセットするコマンド

## StoreのPOST ~Route Handlers~
:::note info
やりたいこと
PlanetScaleで構築したStoreテーブルにPrismaを使って情報を登録する
:::

### 方針
- `axios` を使ってPOSTの処理を行う
- `apiルート` を使ってprisma経由でDBにPOSTする具体的な処理内容を記述する
- **axiosでpostする時に指定するエンドポイントは、apiルートで指定したものになる**

### 実装
1. `/api/stores` に対して `POST` できる Route Handlers を作成する `app/api/stores/route.ts`
    - 作成するのは `export async function POST(req: Request) { ... }` 
    - clerkを通してユーザーのsessionがあればユーザーIDを取得する
    - userIdが存在しなかったら401エラーを返す
    - リクエストボディを取得して、nameを取り出す `const body = req.json` `const { name } = body`
    - nameが存在しなかったら400エラーを返す
1.  Prisma Clientを使って、storeテーブルにPOSTする `await prismadb.store.create({ ... })` [github](https://github.com/yk-graph/admin-e-commerce/commit/5fe83c3e2815523209fd15ba503bd16aa6a2d2bb#diff-39d3f83c6fe556fd7adaa1d46162fae61a0ca17afe6020bba7da8e728e66b47a)
1.  store-modal内でonSubmitされた時の処理を書く
    - axiosを使って `/api/stores` に対してPOSTする

## トーストのUI
:::note info
やりたいこと
POSTが成功した時、失敗した時にトーストを出す
:::

https://react-hot-toast.com/

### 実装
1. `npm i react-hot-toast`
1. Providerとしてトーストのコンポーネントを作成 `providers/modal-provider.tsx`
1. ページ全体で適用できるようにlayout.tsxに `<ToastProvider />` を記述
1. store-modal.tsx内で、トーストを出したい箇所で `toast.success('...')` `toast.error('...')`

## アーキテクチャ
[github](https://github.com/yk-graph/admin-e-commerce/commit/5b5589f35042663facb7e294ed6a84cadab2dbc8)