diff --git a/README.md b/README.md index 9d988f0..10d8206 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,32 @@ -# admin application template +# next-admin -Admin application template using Next.js 16 (App Router) with yarn workspaces monorepo structure. +Next.jsを使用した管理画面アプリケーションのテンプレートプロジェクトで、 +Yarn Workspacesによるモノレポ構成になっています。 -## base libraries -- Next.js v16 -- Better Auth -- TailwindCSS -- prisma -- yarn workspace -- quill -- resend +## Base Libraries + +- Next.js 16 +- Better Auth 1.4 +- TailwindCSS 7 +- prisma 4 +- yarn 4 + +### Infrastructures -### infrastructures - docker compose -- PostgreSQL -- mailhog -- localstack - - S3 - - SES +- PostgreSQL 18 +- oven(SES) + + +## リポジトリ構成 +``` +next-admin/ +├── apps/ +│ ├── admin/ # 管理画面アプリケーション (Next.js) +│ └── web/ # サービスアプリケーション用フレイスホルダー +├── packages/ +│ └── db/ # マイグレーション管理 (Prisma) +├── infra/ # インフラ設定 +└── docker-compose.yml # ローカル開発用インフラ管理 +``` diff --git a/apps/admin/.env.template b/apps/admin/.env.template index bfa3c7e..657df42 100644 --- a/apps/admin/.env.template +++ b/apps/admin/.env.template @@ -21,5 +21,7 @@ AWS_S3_IMAGE_BUCKET='local-image' AWS_S3_RESION='us-east-1' # for local only AWS_S3_ENDPOINT=http://localhost:4566 +AWS_SES_ENDPOINT=http://localhost:58005 +MAIL_FROM="Next Admin " # add packages/db environments diff --git a/apps/admin/README.md b/apps/admin/README.md index db7e73a..d4f9c41 100644 --- a/apps/admin/README.md +++ b/apps/admin/README.md @@ -1,36 +1,82 @@ # Admin -# for Developers +Next.jsを使用した管理画面アプリケーション -## 0. start containers +## セットアップ手順 -``` -$ docker compose up -d -``` +### 1. インフラの起動 -## 1. install +プロジェクトルートでDockerコンテナを起動する。 +```bash +docker compose up -d ``` -$ cat .node-version | nodenv install -$ yarn + +### 2. Node.js のインストール + +`.node-version`に記載のバージョンを使用する。 + +```bash +cat .node-version | nodenv install ``` -## 2. create .env.local +### 3. 依存パッケージのインストール -create .env.local from .env.template and edit it. +プロジェクトルートで実行する。 +```bash +yarn ``` + +### 4. 環境変数の設定 + +`.env.template` を元に `.env.local` を作成し、必要な値を設定する。 + +```bash cp .env.template .env.local ``` -## 3. start local application +主な設定項目: +- `BETTER_AUTH_SECRET` - 認証用シークレットキー(要生成) +- `JWT_SECRET` - JWT署名用キー(要生成) +- AWS関連 - ローカル開発ではデフォルト値で動作 + +`packages/db` の環境変数も `.env.local` に含める(テンプレート内のコメント参照)。 + +### 5. データベースのセットアップ +```bash +# Prisma クライアント生成 +yarn db:generate + +# マイグレーション実行 +yarn db:deploy ``` -$ yarn dev + +### 6. 開発サーバーの起動 + +```bash +# ルートからの場合 +yarn admin:dev + +# apps/adminからの場合 +yarn dev ``` - http://localhost:3500/ -## 4. create first account +### 7. 初回アカウントの作成 + +ブラウザで以下にアクセスし、最初の管理者アカウントを作成する。 - http://localhost:3500/firstuser + + +## 開発用Tips + +### メールの受信確認 + +ローカル環境で送信したメールは、実際のアドレスには送信されません。 +内容は以下のURLで確認することができます。 + +- http://localhost:58005/ diff --git a/apps/admin/src/app/(app)/_layout/app-layout.tsx b/apps/admin/src/app/(app)/_layout/app-layout.tsx index b717f55..61c07c7 100644 --- a/apps/admin/src/app/(app)/_layout/app-layout.tsx +++ b/apps/admin/src/app/(app)/_layout/app-layout.tsx @@ -3,6 +3,7 @@ import { usePathname } from "next/navigation"; import { Separator } from "@/components/ui/separator"; import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { NavigationProgressProvider } from "@/hooks/use-navigation-router"; import { Breadcrumb } from "./breadcrumb"; import type { Navigation } from "./navigation"; import { ProtectedView } from "./protected-view"; @@ -20,30 +21,32 @@ export const AppLayout = ({ children, navigation, userName, userEmail }: Props) return ( - - - -
-
- - - -
-
-
{children}
- -
-
+ + + + +
+
+ + + +
+
+
{children}
+ +
+
+
); }; diff --git a/apps/admin/src/app/(app)/_layout/breadcrumb.tsx b/apps/admin/src/app/(app)/_layout/breadcrumb.tsx index 89aaa2f..4b4e18d 100644 --- a/apps/admin/src/app/(app)/_layout/breadcrumb.tsx +++ b/apps/admin/src/app/(app)/_layout/breadcrumb.tsx @@ -1,9 +1,9 @@ "use client"; import React, { useMemo } from "react"; -import Link from "next/link"; import { usePathname } from "next/navigation"; import { BREADCRUMB_ROUTES } from "@/app/(app)/_layout/navigation"; +import { Link } from "@/components/link"; import { BreadcrumbItem, BreadcrumbLink, diff --git a/apps/admin/src/app/(app)/_layout/sidebar.tsx b/apps/admin/src/app/(app)/_layout/sidebar.tsx index 33a8c44..ada75f7 100644 --- a/apps/admin/src/app/(app)/_layout/sidebar.tsx +++ b/apps/admin/src/app/(app)/_layout/sidebar.tsx @@ -1,7 +1,7 @@ "use client"; -import Link from "next/link"; import { ChevronsUpDown, Gauge, LogOut, type LucideIcon, Shield, User, Users } from "lucide-react"; +import { Link } from "@/components/link"; import { DropdownMenu, DropdownMenuContent, diff --git a/apps/admin/src/app/(app)/system/accounts/[id]/edit/_parts/edit-account.tsx b/apps/admin/src/app/(app)/system/accounts/[id]/edit/_parts/edit-account.tsx index bd1cb2e..8496f5f 100644 --- a/apps/admin/src/app/(app)/system/accounts/[id]/edit/_parts/edit-account.tsx +++ b/apps/admin/src/app/(app)/system/accounts/[id]/edit/_parts/edit-account.tsx @@ -1,7 +1,6 @@ "use client"; import { useState } from "react"; -import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2 } from "lucide-react"; import { useForm } from "react-hook-form"; @@ -21,6 +20,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from " import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; +import { useNavigationRouter } from "@/hooks/use-navigation-router"; import { deleteAccountAction, updateAccountAction } from "./actions"; import { type EditAccountInput, editAccountSchema } from "./lib"; @@ -30,7 +30,7 @@ type Props = { }; export const EditAccount = ({ account, currentUserId }: Props) => { - const router = useRouter(); + const { push } = useNavigationRouter(); const [errorMessage, setErrorMessage] = useState(""); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [isDeleting, setIsDeleting] = useState(false); @@ -54,7 +54,7 @@ export const EditAccount = ({ account, currentUserId }: Props) => { const result = await updateAccountAction(account.id, data); if (result.success) { toast.success("アカウントを更新しました"); - router.push("/system/accounts"); + push("/system/accounts"); } else { setErrorMessage(result.error || ""); if (result.fieldErrors) { @@ -75,7 +75,7 @@ export const EditAccount = ({ account, currentUserId }: Props) => { const result = await deleteAccountAction(account.id); if (result.success) { toast.success("アカウントを削除しました"); - router.push("/system/accounts"); + push("/system/accounts"); } else { toast.error(result.error || "削除に失敗しました"); setShowDeleteDialog(false); @@ -199,7 +199,7 @@ export const EditAccount = ({ account, currentUserId }: Props) => { + + このメールに心当たりがない場合は、無視してください。 + + + + + ); +}; diff --git a/apps/admin/src/styles/globals.css b/apps/admin/src/styles/globals.css index 672d4c1..4a5cb85 100644 --- a/apps/admin/src/styles/globals.css +++ b/apps/admin/src/styles/globals.css @@ -39,7 +39,23 @@ --foreground: oklch(0.145 0 0); } +@keyframes indicator-bar { + 0% { + width: 0%; + opacity: 1; + } + 70% { + width: 80%; + opacity: 1; + } + 100% { + width: 100%; + opacity: 0; + } +} + @theme inline { + --animate-indicator-bar: indicator-bar 1.5s ease-in-out forwards; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); diff --git a/docker-compose.yml b/docker-compose.yml index 419919a..9125ad3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: image: postgres:18.1 container_name: next-admin_postgres ports: - - 5432:5432 + - 55432:5432 volumes: - ./infra/postgres/sql/:/docker-entrypoint-initdb.d/ - postgres-data:/var/lib/postgres @@ -12,31 +12,12 @@ services: POSTGRES_PASSWORD: "admin" networks: - next-admin-network - - localstack: - image: localstack/localstack:4.11.1 - container_name: next-admin_localstack - ports: - - "4566:4566" # LocalStack Gateway - - "4510-4559:4510-4559" # external services port range - environment: - - DEBUG=${DEBUG-} - - PERSISTENCE=${PERSISTENCE-} - - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR-} - - DOCKER_HOST=unix:///var/run/docker.sock - volumes: - - localstack-volume:/var/lib/localstack - - "/var/run/docker.sock:/var/run/docker.sock" - networks: - - next-admin-network - - mailhog: - image: mailhog/mailhog:latest - platform: linux/amd64 - container_name: next-admin_mailhog + ses: + image: oven/bun:1 + container_name: next-admin_ses ports: - - "8025:8025" - - "1025:1025" + - 58005:8005 + command: bunx aws-ses-v2-local --host=0.0.0.0 networks: - next-admin-network diff --git a/packages/db/.env.template b/packages/db/.env.template index 9b6cb34..e6fd9e7 100644 --- a/packages/db/.env.template +++ b/packages/db/.env.template @@ -1,7 +1 @@ -DATABASE_HOST=localhost -DATABASE_PORT=5432 -DATABASE_USERNAME=admin -DATABASE_PASSWORD=admin -DATABASE_NAME=next_admin -DATABASE_TIMEZONE="+09:00" -DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}" +DATABASE_URL="postgresql://admin:admin@localhost:55432/next_admin" \ No newline at end of file diff --git a/packages/db/README.md b/packages/db/README.md index b67e705..8c0cbb1 100644 --- a/packages/db/README.md +++ b/packages/db/README.md @@ -1,20 +1,31 @@ # DB -## Environments +データベース管理パッケージ (`@next-admin/db`) -Set following environments at applications. +## セットアップ手順 -- DATABASE_URL +### 1. 環境変数の設定 +`.env.template` を元に `.env` を作成する。 -## Connect database - +```bash +cp .env.template .env ``` -psql postgresql://admin:admin@127.0.0.1:5432/next_admin + +### 2. Prisma クライアント生成 + +```bash +yarn db:generate ``` -## Create migration +### 3. マイグレーション実行 +```bash +yarn db:deploy ``` -yarn run prisma migrate dev --name first-migration + +## データベース接続 + +```bash +psql postgresql://admin:admin@127.0.0.1:55432/next_admin ```