create-einja-app 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1041 -0
- package/dist/cli.js.map +1 -0
- package/package.json +62 -0
- package/templates/turborepo-pandacss/.biomeignore +15 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/biome-format.sh +49 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/design-doc-check.sh +61 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/detect-secrets.sh +62 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/large-file-warning.sh +42 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/playwright-resize.sh +36 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/typecheck.sh +37 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/validate-git-commit.sh +239 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/warn-index-ts.sh +34 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/warn-relative-import.sh +48 -0
- package/templates/turborepo-pandacss/.claude/settings.json +174 -0
- package/templates/turborepo-pandacss/.claude/skills/create-einja-app-release/SKILL.md +186 -0
- package/templates/turborepo-pandacss/.claude/skills/dev-cli-release/SKILL.md +173 -0
- package/templates/turborepo-pandacss/.cursor/commands/spec-create.md +227 -0
- package/templates/turborepo-pandacss/.cursor/commands/start-dev.md +98 -0
- package/templates/turborepo-pandacss/.cursor/commands/task-exec.md +287 -0
- package/templates/turborepo-pandacss/.cursor/commands/task-vibe-kanban-loop.md +532 -0
- package/templates/turborepo-pandacss/.cursor/commands/update-docs-by-task-specs.md +448 -0
- package/templates/turborepo-pandacss/.cursor/mcp.json +45 -0
- package/templates/turborepo-pandacss/.cursor/rules/api-rules.mdc +171 -0
- package/templates/turborepo-pandacss/.cursor/rules/api-test-rules.mdc +181 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-code.mdc +70 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-commit-rules.mdc +174 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-design.mdc +12 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-rules.mdc +231 -0
- package/templates/turborepo-pandacss/.cursor/rules/error-handling-rules.mdc +188 -0
- package/templates/turborepo-pandacss/.cursor/rules/refactor-rules.mdc +93 -0
- package/templates/turborepo-pandacss/.dockerignore +126 -0
- package/templates/turborepo-pandacss/.einja-sync.json +35 -0
- package/templates/turborepo-pandacss/.env.ci +25 -0
- package/templates/turborepo-pandacss/.env.example +35 -0
- package/templates/turborepo-pandacss/.env.personal.example +27 -0
- package/templates/turborepo-pandacss/.envrc +4 -0
- package/templates/turborepo-pandacss/.gitattributes +5 -0
- package/templates/turborepo-pandacss/.husky/pre-commit +1 -0
- package/templates/turborepo-pandacss/.lintstagedrc.js +24 -0
- package/templates/turborepo-pandacss/.mcp.json +45 -0
- package/templates/turborepo-pandacss/.node-version +1 -0
- package/templates/turborepo-pandacss/.templateignore +60 -0
- package/templates/turborepo-pandacss/.vscode/extensions.json +3 -0
- package/templates/turborepo-pandacss/CLAUDE.md +415 -0
- package/templates/turborepo-pandacss/README.md +322 -0
- package/templates/turborepo-pandacss/apps/web/middleware.ts +28 -0
- package/templates/turborepo-pandacss/apps/web/next.config.ts +10 -0
- package/templates/turborepo-pandacss/apps/web/package.json +80 -0
- package/templates/turborepo-pandacss/apps/web/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/apps/web/postcss.config.cjs +6 -0
- package/templates/turborepo-pandacss/apps/web/public/file.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/globe.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/next.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/vercel.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/window.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/dashboard/page.tsx +79 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +203 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/page.tsx +57 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout-client.tsx +31 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout.tsx +17 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/profile/page.tsx +59 -0
- package/templates/turborepo-pandacss/apps/web/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/templates/turborepo-pandacss/apps/web/src/app/api/auth/signup/route.ts +70 -0
- package/templates/turborepo-pandacss/apps/web/src/app/error.tsx +106 -0
- package/templates/turborepo-pandacss/apps/web/src/app/favicon.ico +0 -0
- package/templates/turborepo-pandacss/apps/web/src/app/global-error.tsx +110 -0
- package/templates/turborepo-pandacss/apps/web/src/app/globals.css +121 -0
- package/templates/turborepo-pandacss/apps/web/src/app/layout.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/app/not-found.tsx +54 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.module.css +165 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.test.tsx +52 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.tsx +284 -0
- package/templates/turborepo-pandacss/apps/web/src/app/signin/page.tsx +296 -0
- package/templates/turborepo-pandacss/apps/web/src/app/signup/page.tsx +395 -0
- package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.test.ts +229 -0
- package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.ts +115 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/login-button.tsx +35 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/logout-button.tsx +24 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.test.tsx +68 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.tsx +43 -0
- package/templates/turborepo-pandacss/apps/web/src/components/dashboard/dashboard-stats.tsx +128 -0
- package/templates/turborepo-pandacss/apps/web/src/components/providers/query-provider.tsx +30 -0
- package/templates/turborepo-pandacss/apps/web/src/components/providers/session-provider.tsx +12 -0
- package/templates/turborepo-pandacss/apps/web/src/components/shared/Sidebar.tsx +175 -0
- package/templates/turborepo-pandacss/apps/web/src/components/shared/header.tsx +166 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/accordion.tsx +64 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/alert-dialog.tsx +135 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/alert.tsx +60 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/aspect-ratio.tsx +9 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/avatar.tsx +41 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/badge.tsx +39 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/breadcrumb.tsx +101 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/button.tsx +56 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/card.tsx +75 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/checkbox.tsx +29 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/data-table.tsx +189 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog-hook.tsx +210 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog.tsx +129 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/drawer.tsx +124 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dropdown-menu.tsx +228 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/form.tsx +152 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/hover-card.tsx +38 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/input.tsx +21 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/label.tsx +21 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/pagination.tsx +105 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/popover.tsx +42 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/progress.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/select.tsx +170 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/separator.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/sonner.tsx +25 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/table.tsx +92 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/tabs.tsx +54 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/textarea.tsx +18 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/tooltip.tsx +57 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/typography.tsx +158 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/auth/guard.ts +36 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/auth/index.ts +22 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/prisma.ts +3 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/utils.ts +6 -0
- package/templates/turborepo-pandacss/apps/web/test/globals.d.ts +1 -0
- package/templates/turborepo-pandacss/apps/web/test/matchers.d.ts +1 -0
- package/templates/turborepo-pandacss/apps/web/test/setup.ts +22 -0
- package/templates/turborepo-pandacss/apps/web/tsconfig.json +37 -0
- package/templates/turborepo-pandacss/apps/web/vitest.config.ts +20 -0
- package/templates/turborepo-pandacss/apps/web/vitest.d.ts +2 -0
- package/templates/turborepo-pandacss/biome.json +60 -0
- package/templates/turborepo-pandacss/components.json +21 -0
- package/templates/turborepo-pandacss/docker-compose.yml +27 -0
- package/templates/turborepo-pandacss/middleware.ts +32 -0
- package/templates/turborepo-pandacss/next.config.ts +10 -0
- package/templates/turborepo-pandacss/package-lock.json +9346 -0
- package/templates/turborepo-pandacss/package.json +64 -0
- package/templates/turborepo-pandacss/packages/config/package.json +41 -0
- package/templates/turborepo-pandacss/packages/config/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/packages/config/src/index.ts +24 -0
- package/templates/turborepo-pandacss/packages/config/src/worktree-config-loader.ts +129 -0
- package/templates/turborepo-pandacss/packages/config/src/worktree-config.ts +75 -0
- package/templates/turborepo-pandacss/packages/config/tsconfig.build.json +19 -0
- package/templates/turborepo-pandacss/packages/config/tsconfig.json +24 -0
- package/templates/turborepo-pandacss/packages/config/typescript/base.json +19 -0
- package/templates/turborepo-pandacss/packages/front-core/package.json +24 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/config.ts +84 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/index.ts +5 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/types/next-auth.d.ts +20 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/utils.ts +29 -0
- package/templates/turborepo-pandacss/packages/front-core/src/context/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/src/hooks/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/src/index.ts +4 -0
- package/templates/turborepo-pandacss/packages/front-core/src/utils/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/tsconfig.json +14 -0
- package/templates/turborepo-pandacss/packages/server-core/package.json +32 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma/schema.prisma +102 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma/seed.ts +67 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma.config.ts +8 -0
- package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.d.ts +270 -0
- package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.js +484 -0
- package/templates/turborepo-pandacss/packages/server-core/src/core/result.test.ts +78 -0
- package/templates/turborepo-pandacss/packages/server-core/src/core/result.ts +53 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/.gitkeep +0 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.test.ts +232 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.ts +105 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/repository-interfaces/IUserRepository.ts +101 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/client.ts +15 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.test.ts +278 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.ts +103 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.test.ts +317 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.ts +169 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/index.ts +22 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/user.factory.ts +123 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/fixtures/users.ts +92 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/date.ts +49 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/password.ts +50 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/index.ts +26 -0
- package/templates/turborepo-pandacss/packages/server-core/tsconfig.json +14 -0
- package/templates/turborepo-pandacss/packages/server-core/vitest.config.ts +15 -0
- package/templates/turborepo-pandacss/packages/ui/package.json +37 -0
- package/templates/turborepo-pandacss/packages/ui/src/accordion.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/alert-dialog.tsx +157 -0
- package/templates/turborepo-pandacss/packages/ui/src/alert.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/aspect-ratio.tsx +11 -0
- package/templates/turborepo-pandacss/packages/ui/src/avatar.tsx +53 -0
- package/templates/turborepo-pandacss/packages/ui/src/badge.tsx +46 -0
- package/templates/turborepo-pandacss/packages/ui/src/breadcrumb.tsx +108 -0
- package/templates/turborepo-pandacss/packages/ui/src/button.tsx +59 -0
- package/templates/turborepo-pandacss/packages/ui/src/card.tsx +92 -0
- package/templates/turborepo-pandacss/packages/ui/src/checkbox.tsx +32 -0
- package/templates/turborepo-pandacss/packages/ui/src/data-table.tsx +216 -0
- package/templates/turborepo-pandacss/packages/ui/src/dialog-hook.tsx +226 -0
- package/templates/turborepo-pandacss/packages/ui/src/dialog.tsx +143 -0
- package/templates/turborepo-pandacss/packages/ui/src/drawer.tsx +135 -0
- package/templates/turborepo-pandacss/packages/ui/src/dropdown-menu.tsx +257 -0
- package/templates/turborepo-pandacss/packages/ui/src/form.tsx +168 -0
- package/templates/turborepo-pandacss/packages/ui/src/hover-card.tsx +44 -0
- package/templates/turborepo-pandacss/packages/ui/src/input.tsx +21 -0
- package/templates/turborepo-pandacss/packages/ui/src/label.tsx +24 -0
- package/templates/turborepo-pandacss/packages/ui/src/lib/utils.ts +6 -0
- package/templates/turborepo-pandacss/packages/ui/src/pagination.tsx +126 -0
- package/templates/turborepo-pandacss/packages/ui/src/popover.tsx +48 -0
- package/templates/turborepo-pandacss/packages/ui/src/progress.tsx +31 -0
- package/templates/turborepo-pandacss/packages/ui/src/select.tsx +185 -0
- package/templates/turborepo-pandacss/packages/ui/src/separator.tsx +28 -0
- package/templates/turborepo-pandacss/packages/ui/src/skeleton.tsx +13 -0
- package/templates/turborepo-pandacss/packages/ui/src/sonner.tsx +25 -0
- package/templates/turborepo-pandacss/packages/ui/src/table.tsx +116 -0
- package/templates/turborepo-pandacss/packages/ui/src/tabs.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/textarea.tsx +18 -0
- package/templates/turborepo-pandacss/packages/ui/src/tooltip.tsx +61 -0
- package/templates/turborepo-pandacss/packages/ui/src/typography.tsx +187 -0
- package/templates/turborepo-pandacss/packages/ui/tsconfig.json +20 -0
- package/templates/turborepo-pandacss/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/pnpm-lock.yaml +9032 -0
- package/templates/turborepo-pandacss/pnpm-workspace.yaml +11 -0
- package/templates/turborepo-pandacss/postcss.config.cjs +6 -0
- package/templates/turborepo-pandacss/prisma/schema.prisma +82 -0
- package/templates/turborepo-pandacss/public/file.svg +1 -0
- package/templates/turborepo-pandacss/public/globe.svg +1 -0
- package/templates/turborepo-pandacss/public/next.svg +1 -0
- package/templates/turborepo-pandacss/public/vercel.svg +1 -0
- package/templates/turborepo-pandacss/public/window.svg +1 -0
- package/templates/turborepo-pandacss/scripts/cli-template-update.ts +387 -0
- package/templates/turborepo-pandacss/scripts/env-show.ts +129 -0
- package/templates/turborepo-pandacss/scripts/env.ts +555 -0
- package/templates/turborepo-pandacss/scripts/init.sh +92 -0
- package/templates/turborepo-pandacss/scripts/setup-dev.ts +640 -0
- package/templates/turborepo-pandacss/scripts/template-update.ts +277 -0
- package/templates/turborepo-pandacss/scripts/worktree/dev.ts +872 -0
- package/templates/turborepo-pandacss/test/globals.d.ts +1 -0
- package/templates/turborepo-pandacss/test/setup.ts +22 -0
- package/templates/turborepo-pandacss/tsconfig.json +46 -0
- package/templates/turborepo-pandacss/turbo.json +57 -0
- package/templates/turborepo-pandacss/vitest.config.ts +20 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserRepository
|
|
3
|
+
*
|
|
4
|
+
* IUserRepositoryの実装。Prismaを使用してユーザーデータを操作する。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Prisma } from "@prisma/client";
|
|
8
|
+
import type { User } from "../../../domain/entities/User";
|
|
9
|
+
import type {
|
|
10
|
+
IUserRepository,
|
|
11
|
+
PaginatedResult,
|
|
12
|
+
PaginationOptions,
|
|
13
|
+
UserSearchCriteria,
|
|
14
|
+
} from "../../../domain/repository-interfaces/IUserRepository";
|
|
15
|
+
import { type Result, failure, success } from "../../../core/result";
|
|
16
|
+
import { prisma } from "../client";
|
|
17
|
+
import { UserMapper } from "../mappers/UserMapper";
|
|
18
|
+
|
|
19
|
+
/** デフォルトのページネーション設定 */
|
|
20
|
+
const DEFAULT_PAGE = 1;
|
|
21
|
+
const DEFAULT_LIMIT = 10;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 検索条件をPrismaのwhere句に変換
|
|
25
|
+
*/
|
|
26
|
+
function buildWhereClause(criteria: UserSearchCriteria): Prisma.UserWhereInput {
|
|
27
|
+
const where: Prisma.UserWhereInput = {};
|
|
28
|
+
|
|
29
|
+
if (criteria.id !== undefined) {
|
|
30
|
+
where.id = criteria.id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (criteria.email !== undefined) {
|
|
34
|
+
where.email = criteria.email;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (criteria.status !== undefined) {
|
|
38
|
+
where.status = criteria.status;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (criteria.role !== undefined) {
|
|
42
|
+
where.role = criteria.role;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (criteria.search !== undefined && criteria.search.trim() !== "") {
|
|
46
|
+
where.OR = [
|
|
47
|
+
{ name: { contains: criteria.search, mode: "insensitive" } },
|
|
48
|
+
{ email: { contains: criteria.search, mode: "insensitive" } },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return where;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* UserRepository実装
|
|
57
|
+
*/
|
|
58
|
+
export const userRepository: IUserRepository = {
|
|
59
|
+
async search(
|
|
60
|
+
criteria: UserSearchCriteria,
|
|
61
|
+
pagination?: PaginationOptions,
|
|
62
|
+
): Promise<Result<PaginatedResult<User>, Error>> {
|
|
63
|
+
try {
|
|
64
|
+
const page = pagination?.page ?? DEFAULT_PAGE;
|
|
65
|
+
const limit = pagination?.limit ?? DEFAULT_LIMIT;
|
|
66
|
+
const skip = (page - 1) * limit;
|
|
67
|
+
|
|
68
|
+
const where = buildWhereClause(criteria);
|
|
69
|
+
|
|
70
|
+
const [users, total] = await Promise.all([
|
|
71
|
+
prisma.user.findMany({
|
|
72
|
+
where,
|
|
73
|
+
skip,
|
|
74
|
+
take: limit,
|
|
75
|
+
orderBy: { createdAt: "desc" },
|
|
76
|
+
}),
|
|
77
|
+
prisma.user.count({ where }),
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const items = users.map(UserMapper.toDomain);
|
|
81
|
+
const totalPages = Math.ceil(total / limit);
|
|
82
|
+
|
|
83
|
+
return success({
|
|
84
|
+
items,
|
|
85
|
+
total,
|
|
86
|
+
page,
|
|
87
|
+
limit,
|
|
88
|
+
totalPages,
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return failure(
|
|
92
|
+
error instanceof Error ? error : new Error("Unknown error occurred during user search"),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async find(criteria: UserSearchCriteria): Promise<Result<User | null, Error>> {
|
|
98
|
+
try {
|
|
99
|
+
const where = buildWhereClause(criteria);
|
|
100
|
+
|
|
101
|
+
const user = await prisma.user.findFirst({ where });
|
|
102
|
+
|
|
103
|
+
if (!user) {
|
|
104
|
+
return success(null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return success(UserMapper.toDomain(user));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return failure(
|
|
110
|
+
error instanceof Error ? error : new Error("Unknown error occurred during user find"),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async findById(id: string): Promise<Result<User | null, Error>> {
|
|
116
|
+
try {
|
|
117
|
+
const user = await prisma.user.findUnique({
|
|
118
|
+
where: { id },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!user) {
|
|
122
|
+
return success(null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return success(UserMapper.toDomain(user));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return failure(
|
|
128
|
+
error instanceof Error ? error : new Error("Unknown error occurred during user findById"),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async findByEmail(email: string): Promise<Result<User | null, Error>> {
|
|
134
|
+
try {
|
|
135
|
+
const user = await prisma.user.findUnique({
|
|
136
|
+
where: { email },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!user) {
|
|
140
|
+
return success(null);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return success(UserMapper.toDomain(user));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return failure(
|
|
146
|
+
error instanceof Error
|
|
147
|
+
? error
|
|
148
|
+
: new Error("Unknown error occurred during user findByEmail"),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async updateLastLogin(id: string, loginTime: Date): Promise<Result<void, Error>> {
|
|
154
|
+
try {
|
|
155
|
+
await prisma.user.update({
|
|
156
|
+
where: { id },
|
|
157
|
+
data: { lastLogin: loginTime },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return success(undefined);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return failure(
|
|
163
|
+
error instanceof Error
|
|
164
|
+
? error
|
|
165
|
+
: new Error("Unknown error occurred during updateLastLogin"),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ファクトリーのエクスポート
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// fabbricaの初期化関数をエクスポート
|
|
6
|
+
export { initialize } from "../../__generated__/fabbrica";
|
|
7
|
+
|
|
8
|
+
// ユーザーファクトリー
|
|
9
|
+
export {
|
|
10
|
+
UserFactory,
|
|
11
|
+
ActiveUserFactory,
|
|
12
|
+
InactiveUserFactory,
|
|
13
|
+
PendingUserFactory,
|
|
14
|
+
AdminUserFactory,
|
|
15
|
+
ModeratorUserFactory,
|
|
16
|
+
VerifiedUserFactory,
|
|
17
|
+
buildUserProps,
|
|
18
|
+
} from "./user.factory";
|
|
19
|
+
|
|
20
|
+
// 将来的に他のモデルのファクトリーもここに追加
|
|
21
|
+
// export { AccountFactory } from "./account.factory";
|
|
22
|
+
// export { SessionFactory } from "./session.factory";
|
package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/user.factory.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Prisma } from "@prisma/client";
|
|
2
|
+
import { UserRole, UserStatus } from "@prisma/client";
|
|
3
|
+
import { defineUserFactory } from "../../__generated__/fabbrica";
|
|
4
|
+
import { faker } from "@faker-js/faker/locale/ja";
|
|
5
|
+
import { getDefaultHashedPassword } from "../helpers/password";
|
|
6
|
+
import { randomDateInPastDays } from "../helpers/date";
|
|
7
|
+
import type { UserProps } from "../../domain/entities/User";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ユーザーファクトリーの基本定義
|
|
11
|
+
* @faker-js/fakerを使用してリアルなダミーデータを生成
|
|
12
|
+
*/
|
|
13
|
+
export const UserFactory = defineUserFactory({
|
|
14
|
+
defaultData: async () => ({
|
|
15
|
+
id: faker.string.nanoid(25), // cuid()と同様の長さのIDを生成
|
|
16
|
+
name: faker.person.fullName(),
|
|
17
|
+
email: faker.internet.email(),
|
|
18
|
+
password: await getDefaultHashedPassword(),
|
|
19
|
+
status: UserStatus.active,
|
|
20
|
+
role: UserRole.user,
|
|
21
|
+
createdAt: randomDateInPastDays(30),
|
|
22
|
+
lastLogin: randomDateInPastDays(7),
|
|
23
|
+
emailVerified: null,
|
|
24
|
+
image: null,
|
|
25
|
+
}),
|
|
26
|
+
traits: {
|
|
27
|
+
active: {
|
|
28
|
+
data: {
|
|
29
|
+
status: UserStatus.active,
|
|
30
|
+
lastLogin: randomDateInPastDays(7),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
inactive: {
|
|
34
|
+
data: {
|
|
35
|
+
status: UserStatus.inactive,
|
|
36
|
+
lastLogin: randomDateInPastDays(60),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
pending: {
|
|
40
|
+
data: {
|
|
41
|
+
status: UserStatus.pending,
|
|
42
|
+
lastLogin: null,
|
|
43
|
+
emailVerified: null,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
admin: {
|
|
47
|
+
data: {
|
|
48
|
+
role: UserRole.admin,
|
|
49
|
+
status: UserStatus.active,
|
|
50
|
+
emailVerified: new Date(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
moderator: {
|
|
54
|
+
data: {
|
|
55
|
+
role: UserRole.moderator,
|
|
56
|
+
status: UserStatus.active,
|
|
57
|
+
emailVerified: new Date(),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
verified: {
|
|
61
|
+
data: {
|
|
62
|
+
emailVerified: new Date(),
|
|
63
|
+
status: UserStatus.active,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* アクティブユーザーのファクトリー
|
|
71
|
+
*/
|
|
72
|
+
export const ActiveUserFactory = UserFactory.use("active");
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 非アクティブユーザーのファクトリー
|
|
76
|
+
*/
|
|
77
|
+
export const InactiveUserFactory = UserFactory.use("inactive");
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 保留中ユーザーのファクトリー
|
|
81
|
+
*/
|
|
82
|
+
export const PendingUserFactory = UserFactory.use("pending");
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 管理者ユーザーのファクトリー
|
|
86
|
+
*/
|
|
87
|
+
export const AdminUserFactory = UserFactory.use("admin");
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* モデレーターユーザーのファクトリー
|
|
91
|
+
*/
|
|
92
|
+
export const ModeratorUserFactory = UserFactory.use("moderator");
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* メール認証済みユーザーのファクトリー
|
|
96
|
+
*/
|
|
97
|
+
export const VerifiedUserFactory = UserFactory.use("verified");
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* UserFactory.build()の結果をUserPropsに変換するヘルパー
|
|
101
|
+
* テストでUserエンティティを作成する際に使用
|
|
102
|
+
*/
|
|
103
|
+
export async function buildUserProps(
|
|
104
|
+
overrides?: Partial<Prisma.UserCreateInput>,
|
|
105
|
+
): Promise<UserProps> {
|
|
106
|
+
const data = await UserFactory.build(overrides);
|
|
107
|
+
|
|
108
|
+
// createdAtとlastLoginは文字列の可能性があるのでDate型に変換
|
|
109
|
+
const parseDate = (value: string | Date | null | undefined): Date | null => {
|
|
110
|
+
if (value == null) return null;
|
|
111
|
+
return typeof value === "string" ? new Date(value) : value;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
id: data.id ?? faker.string.nanoid(25),
|
|
116
|
+
email: data.email,
|
|
117
|
+
name: data.name ?? null,
|
|
118
|
+
status: data.status ?? "active",
|
|
119
|
+
role: data.role ?? "user",
|
|
120
|
+
createdAt: parseDate(data.createdAt) ?? new Date(),
|
|
121
|
+
lastLogin: parseDate(data.lastLogin),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type Prisma, UserRole, UserStatus } from "@prisma/client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* シードユーザーの型定義(Prisma.UserCreateInputから必要なフィールドを抽出)
|
|
5
|
+
*/
|
|
6
|
+
export type SeedUser = Pick<
|
|
7
|
+
Prisma.UserCreateInput,
|
|
8
|
+
"name" | "email" | "status" | "role"
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* シードデータ用の固定ユーザー定義
|
|
13
|
+
* 決定論的なデータで再現性を確保
|
|
14
|
+
*/
|
|
15
|
+
export const SEED_USERS: readonly SeedUser[] = [
|
|
16
|
+
{
|
|
17
|
+
name: "田中太郎",
|
|
18
|
+
email: "tanaka@example.com",
|
|
19
|
+
status: UserStatus.active,
|
|
20
|
+
role: UserRole.admin,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "佐藤花子",
|
|
24
|
+
email: "sato@example.com",
|
|
25
|
+
status: UserStatus.active,
|
|
26
|
+
role: UserRole.user,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "鈴木一郎",
|
|
30
|
+
email: "suzuki@example.com",
|
|
31
|
+
status: UserStatus.inactive,
|
|
32
|
+
role: UserRole.user,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "高橋美咲",
|
|
36
|
+
email: "takahashi@example.com",
|
|
37
|
+
status: UserStatus.pending,
|
|
38
|
+
role: UserRole.moderator,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "伊藤健太",
|
|
42
|
+
email: "ito@example.com",
|
|
43
|
+
status: UserStatus.active,
|
|
44
|
+
role: UserRole.user,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "山田恵子",
|
|
48
|
+
email: "yamada@example.com",
|
|
49
|
+
status: UserStatus.active,
|
|
50
|
+
role: UserRole.admin,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "中村誠",
|
|
54
|
+
email: "nakamura@example.com",
|
|
55
|
+
status: UserStatus.inactive,
|
|
56
|
+
role: UserRole.user,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "小林優子",
|
|
60
|
+
email: "kobayashi@example.com",
|
|
61
|
+
status: UserStatus.pending,
|
|
62
|
+
role: UserRole.user,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 管理者ユーザーのフィクスチャ
|
|
68
|
+
*/
|
|
69
|
+
export const ADMIN_USERS = SEED_USERS.filter(
|
|
70
|
+
(user) => user.role === UserRole.admin,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* アクティブユーザーのフィクスチャ
|
|
75
|
+
*/
|
|
76
|
+
export const ACTIVE_USERS = SEED_USERS.filter(
|
|
77
|
+
(user) => user.status === UserStatus.active,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 非アクティブユーザーのフィクスチャ
|
|
82
|
+
*/
|
|
83
|
+
export const INACTIVE_USERS = SEED_USERS.filter(
|
|
84
|
+
(user) => user.status === UserStatus.inactive,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 保留中ユーザーのフィクスチャ
|
|
89
|
+
*/
|
|
90
|
+
export const PENDING_USERS = SEED_USERS.filter(
|
|
91
|
+
(user) => user.status === UserStatus.pending,
|
|
92
|
+
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ランダムな日付を生成(指定範囲内)
|
|
3
|
+
* @param start 開始日時
|
|
4
|
+
* @param end 終了日時
|
|
5
|
+
* @returns 範囲内のランダムな日時
|
|
6
|
+
*/
|
|
7
|
+
export function randomDate(start: Date, end: Date): Date {
|
|
8
|
+
return new Date(
|
|
9
|
+
start.getTime() + Math.random() * (end.getTime() - start.getTime()),
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 現在から指定日数前の日付を取得
|
|
15
|
+
* @param days 日数
|
|
16
|
+
* @returns 指定日数前の日付
|
|
17
|
+
*/
|
|
18
|
+
export function daysAgo(days: number): Date {
|
|
19
|
+
const now = new Date();
|
|
20
|
+
return new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 現在から指定日数後の日付を取得
|
|
25
|
+
* @param days 日数
|
|
26
|
+
* @returns 指定日数後の日付
|
|
27
|
+
*/
|
|
28
|
+
export function daysFromNow(days: number): Date {
|
|
29
|
+
const now = new Date();
|
|
30
|
+
return new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 過去N日間内のランダムな日付を生成
|
|
35
|
+
* @param days 日数
|
|
36
|
+
* @returns 過去N日間内のランダムな日付
|
|
37
|
+
*/
|
|
38
|
+
export function randomDateInPastDays(days: number): Date {
|
|
39
|
+
return randomDate(daysAgo(days), new Date());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 未来N日間内のランダムな日付を生成
|
|
44
|
+
* @param days 日数
|
|
45
|
+
* @returns 未来N日間内のランダムな日付
|
|
46
|
+
*/
|
|
47
|
+
export function randomDateInFutureDays(days: number): Date {
|
|
48
|
+
return randomDate(new Date(), daysFromNow(days));
|
|
49
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import bcrypt from "bcryptjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* パスワードをハッシュ化
|
|
5
|
+
* @param password プレーンテキストのパスワード
|
|
6
|
+
* @param saltRounds ソルトラウンド数(デフォルト: 10)
|
|
7
|
+
* @returns ハッシュ化されたパスワード
|
|
8
|
+
*/
|
|
9
|
+
export async function hashPassword(
|
|
10
|
+
password: string,
|
|
11
|
+
saltRounds = 10,
|
|
12
|
+
): Promise<string> {
|
|
13
|
+
const salt = await bcrypt.genSalt(saltRounds);
|
|
14
|
+
return bcrypt.hash(password, salt);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* パスワードを検証
|
|
19
|
+
* @param password プレーンテキストのパスワード
|
|
20
|
+
* @param hashedPassword ハッシュ化されたパスワード
|
|
21
|
+
* @returns パスワードが一致する場合true
|
|
22
|
+
*/
|
|
23
|
+
export async function verifyPassword(
|
|
24
|
+
password: string,
|
|
25
|
+
hashedPassword: string,
|
|
26
|
+
): Promise<boolean> {
|
|
27
|
+
return bcrypt.compare(password, hashedPassword);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 開発環境用のデフォルトパスワード
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_PASSWORD = "password123";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 開発環境用のデフォルトパスワード(ハッシュ化済み)
|
|
37
|
+
* テストやシードで使用するためのキャッシュ
|
|
38
|
+
*/
|
|
39
|
+
let cachedHashedPassword: string | null = null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* デフォルトパスワードのハッシュを取得(キャッシュあり)
|
|
43
|
+
* @returns ハッシュ化されたデフォルトパスワード
|
|
44
|
+
*/
|
|
45
|
+
export async function getDefaultHashedPassword(): Promise<string> {
|
|
46
|
+
if (!cachedHashedPassword) {
|
|
47
|
+
cachedHashedPassword = await hashPassword(DEFAULT_PASSWORD);
|
|
48
|
+
}
|
|
49
|
+
return cachedHashedPassword;
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* テストデータファクトリー - メインエクスポート
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { initialize, UserFactory } from "{{packageName}}/server-core/testing";
|
|
7
|
+
* import { prisma } from "{{packageName}}/server-core";
|
|
8
|
+
*
|
|
9
|
+
* // 初期化(テストセットアップ時)
|
|
10
|
+
* initialize({ prisma });
|
|
11
|
+
*
|
|
12
|
+
* // ユーザー作成
|
|
13
|
+
* const user = await UserFactory.create();
|
|
14
|
+
* const users = await UserFactory.createList(5);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ファクトリー
|
|
19
|
+
export * from "./factories";
|
|
20
|
+
|
|
21
|
+
// フィクスチャ(シードデータ用)
|
|
22
|
+
export * from "./fixtures/users";
|
|
23
|
+
|
|
24
|
+
// ヘルパー関数
|
|
25
|
+
export * from "./helpers/date";
|
|
26
|
+
export * from "./helpers/password";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { defineConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: "node",
|
|
8
|
+
include: ["src/**/*.test.ts"],
|
|
9
|
+
},
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
"@": path.resolve(__dirname, "./src"),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@repo/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"exports": {
|
|
6
|
+
"./button": "./src/button.tsx",
|
|
7
|
+
"./card": "./src/card.tsx",
|
|
8
|
+
"./dialog": "./src/dialog.tsx",
|
|
9
|
+
"./input": "./src/input.tsx",
|
|
10
|
+
"./label": "./src/label.tsx",
|
|
11
|
+
"./table": "./src/table.tsx",
|
|
12
|
+
"./avatar": "./src/avatar.tsx",
|
|
13
|
+
"./badge": "./src/badge.tsx",
|
|
14
|
+
"./skeleton": "./src/skeleton.tsx",
|
|
15
|
+
"./separator": "./src/separator.tsx",
|
|
16
|
+
"./utils": "./src/lib/utils.ts"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
20
|
+
"@radix-ui/react-dialog": "^1.1.14",
|
|
21
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
22
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
23
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
24
|
+
"class-variance-authority": "^0.7.1",
|
|
25
|
+
"clsx": "^2.1.1",
|
|
26
|
+
"lucide-react": "^0.523.0",
|
|
27
|
+
"react": "^19.0.0",
|
|
28
|
+
"react-dom": "^19.0.0",
|
|
29
|
+
"tailwind-merge": "^3.3.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@repo/config": "workspace:*",
|
|
33
|
+
"@types/react": "^19",
|
|
34
|
+
"@types/react-dom": "^19",
|
|
35
|
+
"typescript": "^5"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
4
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
function Accordion({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
12
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AccordionItem({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
19
|
+
return (
|
|
20
|
+
<AccordionPrimitive.Item
|
|
21
|
+
data-slot="accordion-item"
|
|
22
|
+
className={cn("border-b last:border-b-0", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AccordionTrigger({
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
33
|
+
return (
|
|
34
|
+
<AccordionPrimitive.Header className="flex">
|
|
35
|
+
<AccordionPrimitive.Trigger
|
|
36
|
+
data-slot="accordion-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
45
|
+
</AccordionPrimitive.Trigger>
|
|
46
|
+
</AccordionPrimitive.Header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AccordionContent({
|
|
51
|
+
className,
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
55
|
+
return (
|
|
56
|
+
<AccordionPrimitive.Content
|
|
57
|
+
data-slot="accordion-content"
|
|
58
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
62
|
+
</AccordionPrimitive.Content>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|