From 90f4824127fc9d94ce52c386fa76c50f4a19052b Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:23:47 +0000 Subject: [PATCH 01/46] =?UTF-8?q?add:=20zod=E3=81=AE=E5=B0=8E=E5=85=A5?= =?UTF-8?q?=E3=81=A8=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=82=AD=E3=83=BC=E3=83=9E=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 12 ++++++++++- frontend/package.json | 3 ++- frontend/src/types/block.ts | 42 ++++++++++++++++++++++--------------- frontend/src/types/index.ts | 1 + frontend/src/types/page.ts | 14 ++++++++----- frontend/src/types/trip.ts | 14 ++++++++----- 6 files changed, 57 insertions(+), 29 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 26c24bb..be020b9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,7 +33,8 @@ "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", "react-router-dom": "^7.8.2", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zod": "^4.2.1" }, "devDependencies": { "@biomejs/biome": "^2.0.6", @@ -9848,6 +9849,15 @@ "engines": { "node": ">= 14.6" } + }, + "node_modules/zod": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 6c04764..0153ba5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,8 @@ "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", "react-router-dom": "^7.8.2", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zod": "^4.2.1" }, "devDependencies": { "@biomejs/biome": "^2.0.6", diff --git a/frontend/src/types/block.ts b/frontend/src/types/block.ts index a58be26..574ecf1 100644 --- a/frontend/src/types/block.ts +++ b/frontend/src/types/block.ts @@ -1,21 +1,29 @@ -type BaseBlock = { - id: string; - type: 'schedule' | 'transportation'; - title: string; - startTime: Date; - endTime: Date; - details?: string; -}; +import { z } from 'zod'; -export type Block = ScheduleBlock | TransportationBlock; +export const TransportationTypeEnum = z.enum(['car', 'bicycle', 'walk', 'ship', 'train', 'bus', 'flight']); -export type ScheduleBlock = BaseBlock & { - type: 'schedule'; -}; +export const BaseBlockSchema = z.object({ + id: z.string(), + type: z.enum(['schedule', 'transportation']), + title: z.string(), + startTime: z.date(), + endTime: z.date(), + details: z.string().optional(), +}); -export type TransportationBlock = BaseBlock & { - type: 'transportation'; - transportationType: TransportationType; -}; +export const ScheduleBlockSchema = BaseBlockSchema.extend({ + type: z.literal('schedule'), +}); -export type TransportationType = 'car' | 'bicycle' | 'walk' | 'ship' | 'train' | 'bus' | 'flight'; +export const TransportationBlockSchema = BaseBlockSchema.extend({ + type: z.literal('transportation'), + transportationType: TransportationTypeEnum, +}); + +export const BlockSchema = z.discriminatedUnion('type', [ScheduleBlockSchema, TransportationBlockSchema]); + +export type BaseBlock = z.infer; +export type Block = z.infer; +export type ScheduleBlock = z.infer; +export type TransportationBlock = z.infer; +export type TransportationType = z.infer; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 145f98e..757abc2 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -1,2 +1,3 @@ export * from './block'; export * from './page'; +export * from './trip'; diff --git a/frontend/src/types/page.ts b/frontend/src/types/page.ts index e82014e..77d520b 100644 --- a/frontend/src/types/page.ts +++ b/frontend/src/types/page.ts @@ -1,5 +1,9 @@ -export type Page = { - id: string; - title: string; - details?: string; -}; +import { z } from 'zod'; + +export const PageSchema = z.object({ + id: z.string(), + title: z.string(), + details: z.string().optional(), +}); + +export type Page = z.infer; diff --git a/frontend/src/types/trip.ts b/frontend/src/types/trip.ts index 8a33c8a..1168d9a 100644 --- a/frontend/src/types/trip.ts +++ b/frontend/src/types/trip.ts @@ -1,5 +1,9 @@ -export type Trip = { - id: string; - title: string; - peopleNum?: number; -}; +import { z } from 'zod'; + +export const TripSchema = z.object({ + id: z.string(), + title: z.string(), + peopleNum: z.number().optional(), +}); + +export type Trip = z.infer; From 8e2fca99cb34d11c5d34e895a9a5d9b884bd63ce Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:28:06 +0000 Subject: [PATCH 02/46] =?UTF-8?q?add:=20=E5=85=B1=E9=80=9A=E3=81=AEAGENTS.?= =?UTF-8?q?md=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gemini/settings.json | 3 ++- AGENTS.md | 48 +++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 4 ++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/.gemini/settings.json b/.gemini/settings.json index 9c83831..8d6702b 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -4,5 +4,6 @@ "command": "uvx", "args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"] } - } + }, + "contextFileName": "AGENTS.md" } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9fdc0a3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# AGENTS.md + +このファイルは、このリポジトリでコードを操作する際にAIコーディングエージェントにガイダンスを提供します。 +日本語で回答するように設定してください。 +また、コードにコメントを追加する際は、日本語を使用してください。ただし、コメントはコードから意図が読み取れない場合にのみ追記し、必要最低限に留めてください。 + +## プロジェクト概要 + +国内車旅行(主に温泉地巡り)の旅程計画を効率的に作成・共有・管理するWebアプリケーションです。ブロック式のUIを使用して、友人・家族との旅程を協力して編集できます。 + +## 要件定義書 + +[@docs/requirements.md](docs/requirements.md) + +## 開発上の注意点 + +- ブロック式旅程管理がコアUXパターン +- 閲覧はログイン不要、編集はFirebase認証が必要 +- リアルタイム協調編集は後勝ちルールで競合解決 +- UIテキストと仕様書は日本語で記述 + +## 共通コーディング規約 + +[@docs/coding_standards.md](docs/coding_standards.md) + +また、フロントエンドの実装では基本的にnamed exportを使用してください。ただし、設定ファイルやライブラリの慣習など、特別な理由がある場合はdefault exportも許容します。 + +## エージェントの行動規範 + +- **正確性の確保:** ユーザーからファイル名や行番号でコードの箇所を指摘された際は、自身の推測や過去の経験則に頼らず、必ず指定されたファイルを読み込むこと。その上で、指摘箇所がどの関数やクラスの範囲に属するかを厳密に解析し、事実に基づいて応答・作業を行うこと。思い込みによる判断は絶対に避ける。 + +## 開発コマンド + +### リンター・フォーマッター + +```bash +# リンターチェック +npm run lint:check + +# リンターエラーの自動修正 +npm run lint:fix + +# フォーマットチェック +npm run format:check + +# フォーマットの自動修正 +npm run format:fix +``` \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 2355b85..e7267b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,6 +3,10 @@ このファイルは、このリポジトリでコードを操作する際にClaude Code (claude.ai/code) にガイダンスを提供します。 日本語で回答するように設定してください。 +## 他のエージェント向けドキュメント + +- [AGENTS.md](AGENTS.md) - AIコーディングエージェント向けのガイダンス + ## プロジェクト概要 国内車旅行(主に温泉地巡り)の旅程計画を効率的に作成・共有・管理するWebアプリケーションです。ブロック式のUIを使用して、友人・家族との旅程を協力して編集できます。 From 18ccc8f7fd2a978e86692112bbc77b5f8bbda6c2 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:30:34 +0000 Subject: [PATCH 03/46] =?UTF-8?q?add:=20Trip=E3=81=AECRUD=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E4=BB=AE=E5=AE=9F=E8=A3=85(axios,=20swr?= =?UTF-8?q?=E5=90=AB=E3=82=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 1755 +++++++---------- frontend/package.json | 2 + .../components/timeline/Timeline.stories.tsx | 16 +- frontend/src/hooks/README.md | 81 + frontend/src/hooks/useTrips.ts | 198 ++ frontend/src/lib/apiClient.ts | 10 + frontend/src/pages/test-data.ts | 3 +- frontend/src/types/trip.ts | 6 +- 8 files changed, 1057 insertions(+), 1014 deletions(-) create mode 100644 frontend/src/hooks/README.md create mode 100644 frontend/src/hooks/useTrips.ts create mode 100644 frontend/src/lib/apiClient.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index be020b9..c2ffe5a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@tailwindcss/postcss": "^4.1.11", "@types/react-router-dom": "^5.3.3", + "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -33,6 +34,7 @@ "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", "react-router-dom": "^7.8.2", + "swr": "^2.3.8", "tailwind-merge": "^3.3.1", "zod": "^4.2.1" }, @@ -1273,7 +1275,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1682,7 +1683,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -4028,7 +4028,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4038,7 +4037,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4095,6 +4093,23 @@ "node": ">=4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -4114,7 +4129,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/better-opn": { @@ -4187,6 +4201,19 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4238,9 +4265,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4312,7 +4337,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4325,9 +4349,20 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4373,7 +4408,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4491,11 +4525,19 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4542,11 +4584,24 @@ "license": "MIT", "peer": true }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -4560,7 +4615,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { @@ -4598,6 +4652,24 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -4605,6 +4677,33 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -4742,11 +4841,30 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -4759,6 +4877,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4793,6 +4927,30 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -4802,11 +4960,23 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4827,7 +4997,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4837,7 +5006,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4862,6 +5030,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4872,13 +5052,38 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5054,7 +5259,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5109,14 +5313,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -5516,6 +5718,15 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -5531,6 +5742,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -5627,15 +5859,16 @@ "license": "MIT" }, "node_modules/npm": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.6.0.tgz", - "integrity": "sha512-d/P7DbvYgYNde9Ehfeq99+13/E7E82PfZPw8uYZASr9sQ3ZhBBCA9cXSJRA1COfJ6jDLJ0K36UJnXQWhCvLXuQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.7.0.tgz", + "integrity": "sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", "@npmcli/config", "@npmcli/fs", "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", "@npmcli/package-json", "@npmcli/promise-spawn", "@npmcli/redact", @@ -5673,7 +5906,6 @@ "ms", "node-gyp", "nopt", - "normalize-package-data", "npm-audit-report", "npm-install-checks", "npm-package-arg", @@ -5708,71 +5940,71 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.4", - "@npmcli/config": "^10.4.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.2.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.2.2", - "@npmcli/run-script": "^9.1.0", - "@sigstore/tuf": "^3.1.1", - "abbrev": "^3.0.1", + "@npmcli/arborist": "^9.1.9", + "@npmcli/config": "^10.4.5", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.4", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.3", + "@sigstore/tuf": "^4.0.0", + "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.4.1", - "ci-info": "^4.3.0", + "cacache": "^20.0.3", + "chalk": "^5.6.2", + "ci-info": "^4.3.1", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.4.5", + "glob": "^13.0.0", "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.1.0", - "ini": "^5.0.0", - "init-package-json": "^8.2.1", - "is-cidr": "^5.1.1", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^10.0.1", - "libnpmdiff": "^8.0.7", - "libnpmexec": "^10.1.6", - "libnpmfund": "^7.0.7", - "libnpmorg": "^8.0.0", - "libnpmpack": "^9.0.7", - "libnpmpublish": "^11.1.0", - "libnpmsearch": "^9.0.0", - "libnpmteam": "^8.0.1", - "libnpmversion": "^8.0.1", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.4", + "is-cidr": "^6.0.1", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.0.12", + "libnpmexec": "^10.1.11", + "libnpmfund": "^7.0.12", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.0.12", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.3", + "minimatch": "^10.1.1", "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^11.2.0", - "nopt": "^8.1.0", - "normalize-package-data": "^7.0.1", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.2", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^7.0.3", - "pacote": "^21.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", + "node-gyp": "^12.1.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.0.4", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", - "read": "^4.1.0", - "semver": "^7.7.2", + "read": "^5.0.1", + "semver": "^7.7.3", "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^10.0.0", - "tar": "^6.2.1", + "ssri": "^13.0.0", + "supports-color": "^10.2.2", + "tar": "^7.5.2", "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.2", - "which": "^5.0.0" + "validate-npm-package-name": "^7.0.0", + "which": "^6.0.0" }, "bin": { "npm": "bin/npm-cli.js", @@ -5782,66 +6014,23 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/npm/node_modules/@isaacs/balanced-match": { + "version": "4.0.1", "inBundle": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "20 || >=22" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/npm/node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "20 || >=22" } }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { @@ -5861,57 +6050,56 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.4", + "version": "9.1.9", "inBundle": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^9.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", + "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^21.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", "semver": "^7.3.7", - "ssri": "^12.0.0", + "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, @@ -5923,16 +6111,16 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.4.0", + "version": "10.4.5", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.1.0", - "proc-log": "^5.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" }, @@ -5941,72 +6129,72 @@ } }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.3", + "version": "7.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", + "version": "5.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "9.0.1", + "version": "9.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", - "proc-log": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { @@ -6014,114 +6202,105 @@ } }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.2.0", + "version": "7.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.5.3", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", + "version": "9.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.1", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.2.2", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.1.0", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "3.1.0", + "version": "4.0.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "2.0.0", + "version": "3.0.0", "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", + "version": "0.5.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -6129,44 +6308,52 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "3.1.0", + "version": "4.0.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.2", "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.1.1", + "version": "4.0.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "2.1.1", + "version": "3.0.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@tufjs/canonical-json": { @@ -6178,7 +6365,7 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "3.0.1", + "version": "4.0.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -6186,15 +6373,29 @@ "minimatch": "^9.0.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/abbrev": { - "version": "3.0.1", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/agent-base": { @@ -6213,17 +6414,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm/node_modules/aproba": { "version": "2.1.0", "inBundle": true, @@ -6240,18 +6430,18 @@ "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/binary-extensions": { @@ -6274,86 +6464,28 @@ } }, "node_modules/npm/node_modules/cacache": { - "version": "19.0.1", + "version": "20.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/chalk": { - "version": "5.4.1", + "version": "5.6.2", "inBundle": true, "license": "MIT", "engines": { @@ -6364,15 +6496,15 @@ } }, "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "version": "3.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.3.0", + "version": "4.3.1", "funding": [ { "type": "github", @@ -6386,14 +6518,14 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.3", + "version": "5.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "ip-regex": "^5.0.0" + "ip-regex": "5.0.0" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/npm/node_modules/cli-columns": { @@ -6409,61 +6541,18 @@ } }, "node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", + "version": "8.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", "inBundle": true, @@ -6476,7 +6565,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.4.1", + "version": "4.4.3", "inBundle": true, "license": "MIT", "dependencies": { @@ -6492,18 +6581,13 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "7.0.0", + "version": "8.0.2", "inBundle": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", "inBundle": true, @@ -6532,7 +6616,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.2", + "version": "3.1.3", "inBundle": true, "license": "Apache-2.0" }, @@ -6544,21 +6628,6 @@ "node": ">= 4.9.1" } }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", "inBundle": true, @@ -6571,19 +6640,16 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "10.4.5", + "version": "13.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6595,14 +6661,14 @@ "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.1.0", + "version": "9.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/http-cache-semantics": { @@ -6647,14 +6713,14 @@ } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", + "version": "8.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/imurmurhash": { @@ -6666,38 +6732,34 @@ } }, "node_modules/npm/node_modules/ini": { - "version": "5.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.1", + "version": "8.2.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/package-json": "^6.1.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", + "version": "10.0.1", "inBundle": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -6714,14 +6776,14 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.1", + "version": "6.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^4.1.1" + "cidr-regex": "5.0.1" }, "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/npm/node_modules/is-fullwidth-code-point": { @@ -6732,36 +6794,20 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", + "node_modules/npm/node_modules/isexe": { + "version": "3.1.1", "inBundle": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=16" + } }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/json-stringify-nice": { @@ -6791,50 +6837,51 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "10.0.1", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.7", + "version": "8.0.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", - "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/arborist": "^9.1.9", + "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", - "diff": "^7.0.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^21.0.0", - "tar": "^6.2.1" + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.6", + "version": "10.1.11", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", - "@npmcli/package-json": "^6.1.1", - "@npmcli/run-script": "^9.0.1", + "@npmcli/arborist": "^9.1.9", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^21.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "read": "^5.0.1", "semver": "^7.3.7", + "signal-exit": "^4.1.0", "walk-up-path": "^4.0.0" }, "engines": { @@ -6842,92 +6889,92 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.7", + "version": "7.0.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4" + "@npmcli/arborist": "^9.1.9" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "8.0.0", + "version": "8.0.1", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.7", + "version": "9.0.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.4", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^21.0.0" + "@npmcli/arborist": "^9.1.9", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "11.1.0", + "version": "11.1.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/package-json": "^6.2.0", + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" + "sigstore": "^4.0.0", + "ssri": "^13.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "9.0.0", + "version": "9.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "8.0.1", + "version": "8.0.2", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "8.0.1", + "version": "8.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7" }, "engines": { @@ -6935,48 +6982,43 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", + "version": "11.2.2", "inBundle": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", + "version": "15.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", + "version": "10.1.1", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7002,7 +7044,7 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.1", + "version": "5.0.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -7011,23 +7053,12 @@ "minizlib": "^3.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", "inBundle": true, @@ -7095,37 +7126,14 @@ } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.1.0", "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "minipass": "^7.1.2" }, "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/npm/node_modules/ms": { @@ -7134,248 +7142,176 @@ "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", + "version": "3.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/npm/node_modules/node-gyp": { - "version": "11.2.0", + "version": "12.1.0", "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.2", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/nopt": { - "version": "8.1.0", + "version": "9.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", + "version": "7.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", + "version": "8.0.0", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.2", + "version": "13.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.0", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "ignore-walk": "^7.0.0" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", + "version": "11.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", + "version": "12.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", + "version": "19.1.1", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">= 18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", + "version": "4.0.0", "inBundle": true, "license": "BSD-2-Clause", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/p-map": { - "version": "7.0.3", + "version": "7.0.4", "inBundle": true, "license": "MIT", "engines": { @@ -7385,33 +7321,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", - "inBundle": true, - "license": "BlueOak-1.0.0" - }, "node_modules/npm/node_modules/pacote": { - "version": "21.0.0", + "version": "21.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" @@ -7421,36 +7352,28 @@ } }, "node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", + "version": "5.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^4.0.0", + "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", + "version": "2.0.0", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7469,19 +7392,19 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "5.0.0", + "version": "6.1.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/proggy": { - "version": "3.0.0", + "version": "4.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/promise-all-reject-late": { @@ -7513,14 +7436,14 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "2.0.0", + "version": "3.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "read": "^4.0.0" + "read": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/qrcode-terminal": { @@ -7531,34 +7454,22 @@ } }, "node_modules/npm/node_modules/read": { - "version": "4.1.0", + "version": "5.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "^2.0.0" + "mute-stream": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/retry": { @@ -7576,7 +7487,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.3", "inBundle": true, "license": "ISC", "bin": { @@ -7586,25 +7497,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/signal-exit": { "version": "4.1.0", "inBundle": true, @@ -7617,19 +7509,19 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "3.1.0", + "version": "4.0.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/smart-buffer": { @@ -7642,11 +7534,11 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.6", + "version": "2.8.7", "inBundle": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -7700,24 +7592,19 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.21", + "version": "3.0.22", "inBundle": true, "license": "CC0-1.0" }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ssri": { - "version": "12.0.0", + "version": "13.0.0", "inBundle": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/string-width": { @@ -7733,20 +7620,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", "inBundle": true, @@ -7758,20 +7631,8 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/supports-color": { - "version": "10.0.0", + "version": "10.2.2", "inBundle": true, "license": "MIT", "engines": { @@ -7782,49 +7643,26 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", + "version": "7.5.2", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { + "node_modules/npm/node_modules/tar/node_modules/yallist": { "version": "5.0.0", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/npm/node_modules/text-table": { @@ -7833,17 +7671,17 @@ "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "version": "2.0.2", "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.14", + "version": "0.2.15", "inBundle": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -7853,9 +7691,12 @@ } }, "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", + "version": "6.5.0", "inBundle": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -7885,38 +7726,38 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "3.1.0", + "version": "4.0.0", "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", + "@tufjs/models": "4.0.0", "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" + "make-fetch-happen": "^15.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/unique-filename": { - "version": "4.0.0", + "version": "5.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "unique-slug": "^5.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/util-deprecate": { @@ -7943,11 +7784,11 @@ } }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.2", + "version": "7.0.0", "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/walk-up-path": { @@ -7959,7 +7800,7 @@ } }, "node_modules/npm/node_modules/which": { - "version": "5.0.0", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -7969,112 +7810,11 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", + "version": "7.0.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -8082,7 +7822,7 @@ "signal-exit": "^4.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/yallist": { @@ -8119,7 +7859,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -8169,7 +7908,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8185,7 +7923,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -8202,7 +7939,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-type": { @@ -8309,6 +8045,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8706,7 +8448,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8722,7 +8463,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8735,7 +8475,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8752,7 +8491,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -8795,9 +8533,9 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "9.0.15", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.15.tgz", - "integrity": "sha512-r9hwcSMM3dq7dkMveaWFTosrmyHCL2FRrV3JOwVnVWraF6GtCgp2k+r4hsYtyp1bY3zdmK9e4KYzXsGs5q1h/Q==", + "version": "9.1.17", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.17.tgz", + "integrity": "sha512-kfr6kxQAjA96ADlH6FMALJwJ+eM80UqXy106yVHNgdsAP/CdzkkicglRAhZAvUycXK9AeadF6KZ00CWLtVMN4w==", "dev": true, "license": "MIT", "dependencies": { @@ -8805,6 +8543,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", @@ -8846,7 +8585,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -8865,7 +8603,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8880,14 +8617,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8900,7 +8635,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -8917,7 +8651,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8930,7 +8663,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8992,9 +8724,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9014,6 +8744,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz", + "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -9392,10 +9135,19 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "dependencies": { @@ -9675,7 +9427,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9708,7 +9459,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -9727,7 +9477,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -9745,14 +9494,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9767,7 +9514,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9780,7 +9526,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/frontend/package.json b/frontend/package.json index 0153ba5..b6345a2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-tabs": "^1.1.12", "@tailwindcss/postcss": "^4.1.11", "@types/react-router-dom": "^5.3.3", + "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -43,6 +44,7 @@ "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", "react-router-dom": "^7.8.2", + "swr": "^2.3.8", "tailwind-merge": "^3.3.1", "zod": "^4.2.1" }, diff --git a/frontend/src/components/timeline/Timeline.stories.tsx b/frontend/src/components/timeline/Timeline.stories.tsx index c4bd672..1218760 100644 --- a/frontend/src/components/timeline/Timeline.stories.tsx +++ b/frontend/src/components/timeline/Timeline.stories.tsx @@ -23,7 +23,7 @@ type Story = StoryObj; const sampleBlocks: Block[] = [ { - id: '1', + id: 1, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 12, 0), @@ -31,32 +31,36 @@ const sampleBlocks: Block[] = [ details: `detail detail • detail detail リンク`.repeat(20), + pageId: 1, }, { - id: '2', + id: 2, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 15, 0), endTime: new Date(2024, 0, 1, 16, 0), + pageId: 1, }, { - id: '3', + id: 3, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 17, 0), endTime: new Date(2024, 0, 1, 19, 0), + pageId: 1, }, { - id: '4', + id: 4, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 19, 0), endTime: new Date(2024, 0, 1, 20, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 1, }, ]; diff --git a/frontend/src/hooks/README.md b/frontend/src/hooks/README.md new file mode 100644 index 0000000..0148760 --- /dev/null +++ b/frontend/src/hooks/README.md @@ -0,0 +1,81 @@ +# Hooks (`src/hooks`) + +このディレクトリには、アプリケーション全体で再利用可能なカスタムフックを配置します。 +特に、API通信や状態管理に関連するロジックをカプセル化することを目的としています。 + +## 開発上の注意点と規則 + +このプロジェクトのカスタムフックを実装する際は、以下の点に注意してください。 + +### 1. データ取得と状態管理 + +データ取得とそれに伴う状態管理には、 **SWR** ライブラリを全面的に採用しています。 + +- **データ取得 (`GET`)**: `useSWR` を使用します。 +- **データ作成・更新・削除 (`POST`, `PUT`, `DELETE`)**: `useSWRMutation` を使用します。 + +これにより、キャッシュ管理、再検証、ローディング状態の管理などを SWR に一任し、効率的で一貫性のあるデータフローを実現します。 + +### 2. APIクライアント + +APIリクエストは、`@/lib/apiClient` で定義されている共通の `apiClient` (axiosインスタンス) と `fetcher` を使用してください。 + +```typescript +import { apiClient, fetcher } from '@/lib/apiClient'; +import useSWR from 'swr'; + +// GETリクエストの例 +const { data, error, isLoading } = useSWR('/api/path', fetcher); +``` + +これにより、リクエストヘッダー、ベースURL、タイムアウト、エラーハンドリングなどの設定を一元管理します。 + +### 3. Mutation(データ更新処理)の実装パターン + +`useSWRMutation` を使用したデータ作成・更新・削除処理は、以下のパターンで実装します。 + +#### 楽観的更新 (Optimistic UI) + +ユーザー体験を向上させるため、更新・削除処理では楽観的更新を積極的に採用します。サーバーからのレスポンスを待たずにUIを即時更新し、処理が失敗した場合は元の状態にロールバックします。 + +- `optimisticData` オプションで、mutation実行前のキャッシュデータを元に新しい状態を生成します。 +- `rollbackOnError: true` を設定し、エラー発生時にキャッシュを自動的にロールバックさせます。 +- `revalidate: true` を設定し、mutation成功後に最新のデータを再取得してキャッシュを更新します。 + +#### キャッシュの手動更新 + +mutation成功後、関連する他のキャッシュ(例: 一覧表示用のキャッシュと詳細表示用のキャッシュ)の一貫性を保つために、`onSuccess` コールバック内で `mutate` 関数を呼び出して手動で更新します。 + +`mutate` 関数は `useSWRConfig` フックから取得します。 + +```typescript +import { useSWRConfig } from 'swr'; + +// 更新処理フック内 +const { mutate } = useSWRConfig(); + +// mutationのオプション内 +onSuccess: (updatedData) => { + // 詳細ページのキャッシュを更新 + mutate(`/api/items/${updatedData.id}`, updatedData, false); + // 一覧ページのキャッシュも再検証するなど、必要に応じた処理 +} +``` + +### 4. 型定義 + +APIから取得するデータやフックの引数などに関連する型は、`@/types` ディレクトリで一元管理します。フック内ではこれらの型をインポートして使用し、型安全性を確保してください。 + +```typescript +import type { Trip } from '@/types/trip'; + +type UpdateTripArg = { id: string; data: Omit }; +``` + +### 5. フックの命名 + +Reactの規則に従い、カスタムフックの関数名は必ず `use` から始めてください(例: `useTrips`, `useUpdateTrip`)。 + +### 参考実装 + +これらの規則の具体的な実装例として、`useTrips.ts` を参照してください。基本的なCRUDS操作(Read, Create, Update, Delete)におけるSWRのベストプラクティスが網羅されています。 diff --git a/frontend/src/hooks/useTrips.ts b/frontend/src/hooks/useTrips.ts new file mode 100644 index 0000000..3aeeac4 --- /dev/null +++ b/frontend/src/hooks/useTrips.ts @@ -0,0 +1,198 @@ +import type { AxiosError } from 'axios'; +import { useCallback } from 'react'; +import useSWR, { useSWRConfig } from 'swr'; +import useSWRMutation from 'swr/mutation'; +import z from 'zod'; +import { apiClient, fetcher } from '@/lib/apiClient'; +import { type Trip, TripSchema } from '@/types/trip'; + +const TRIPS_BASE_PATH = '/trips'; + +/** + * 全てのTripを取得するフック + */ +export const useTrips = () => { + const { data, error, isLoading } = useSWR(TRIPS_BASE_PATH, async (url: string) => { + const res = await fetcher(url); + return z.array(TripSchema).parse(res); + }); + + return { + trips: data, + error, + isLoading, + }; +}; + +/** + * URLのIDを指定して単一のTripを取得するフック + */ +export const useTripByUrlId = (urlId: Trip['urlId'] | null) => { + const { mutate } = useSWRConfig(); + const { data, error, isLoading } = useSWR( + urlId ? `${TRIPS_BASE_PATH}/url/${urlId}` : null, + async (url: string) => { + const res = await fetcher(url); + return TripSchema.parse(res); + }, + { + onSuccess: trip => { + if (trip) { + // /trips/{id} のキャッシュを更新 + mutate(`${TRIPS_BASE_PATH}/${trip.id}`, trip, { revalidate: false }); + } + }, + } + ); + + return { + trip: data, + error, + isLoading, + }; +}; + +/** + * IDを指定して単一のTripを取得するフック + */ +export const useTrip = (id: Trip['id'] | null) => { + const { mutate } = useSWRConfig(); + const { data, error, isLoading } = useSWR( + id ? `${TRIPS_BASE_PATH}/${id}` : null, + async (url: string) => { + const res = await fetcher(url); + return TripSchema.parse(res); + }, + { + onSuccess: trip => { + if (trip) { + // /trips/url/{urlId} のキャッシュを更新 + mutate(`${TRIPS_BASE_PATH}/url/${trip.urlId}`, trip, { revalidate: false }); + } + }, + } + ); + + return { + trip: data, + error, + isLoading, + }; +}; + +type CreateTripArg = Omit; +const CreateTripSchema = TripSchema.omit({ id: true }); + +/** + * 新しいTripを作成する + */ +export const useCreateTrip = () => { + const createTrip = useCallback(async (url: string, { arg: tripData }: { arg: CreateTripArg }) => { + CreateTripSchema.parse(tripData); + const response = await apiClient.post(url, tripData); + return response.data; + }, []); + + const { trigger, isMutating, error, data } = useSWRMutation( + TRIPS_BASE_PATH, + createTrip + ); + + return { + createTrip: trigger, + isCreating: isMutating, + error, + createdTrip: data, + }; +}; + +type UpdateTripArg = { id: Trip['id']; data: Omit }; +const UpdateTripSchema = TripSchema.omit({ id: true }); + +/** + * Tripを更新するためのフック + */ +export const useUpdateTrip = () => { + const { mutate } = useSWRConfig(); + + const updateTripFetcher = useCallback(async (_key: string | null, { arg }: { arg: UpdateTripArg }) => { + const { id, data } = arg; + UpdateTripSchema.parse(data); + const response = await apiClient.put(`${TRIPS_BASE_PATH}/${id}`, data); + return response.data; + }, []); + + const { trigger, isMutating, error, data } = useSWRMutation( + TRIPS_BASE_PATH, // リストの再検証トリガーとして利用 + updateTripFetcher, + { + // 更新成功時、APIレスポンスを使ってキャッシュを操作 + onSuccess: (updatedTrip: Trip) => { + // 個別IDのキャッシュを更新 (populate) + mutate( + `${TRIPS_BASE_PATH}/${updatedTrip.id}`, + updatedTrip, + false // 再検証はしない + ); + }, + // リスト自体のキャッシュ更新 + populateCache: (updatedTrip, currentTrips: Trip[] | undefined) => { + if (!currentTrips) return []; + return currentTrips.map(t => (t.id === updatedTrip.id ? updatedTrip : t)); + }, + revalidate: false, // populateCacheで更新したのでリストの再取得は防ぐ + } + ); + + return { + updateTrip: trigger, + isUpdating: isMutating, + error, + updatedTrip: data, + }; +}; + +type DeleteTripArg = Trip['id']; +const DeleteTripSchema = TripSchema.shape.id; + +/** + * Tripを削除する + */ +export const useDeleteTrip = () => { + const { mutate } = useSWRConfig(); + + const deleteTripFetcher = useCallback(async (_: string | null, { arg: id }: { arg: DeleteTripArg }) => { + DeleteTripSchema.parse(id); + await apiClient.delete(`${TRIPS_BASE_PATH}/${id}`); + return undefined; + }, []); + + const { trigger, isMutating, error } = useSWRMutation( + TRIPS_BASE_PATH, // リストのキー(削除完了後のリスト自動再検証のため) + deleteTripFetcher + ); + + const deleteTrip = useCallback( + async (id: DeleteTripArg) => { + // リスト側の楽観的更新 + await trigger(id, { + optimisticData: (currentTrips: Trip[] | undefined) => { + if (!currentTrips) return []; + return currentTrips.filter(trip => trip.id !== id); + }, + revalidate: true, // 念のためリストをサーバーと同期する(不要ならfalse) + rollbackOnError: true, + }); + + // 個別キャッシュ(/trips/:id)の削除 + mutate(`${TRIPS_BASE_PATH}/${id}`, undefined, false); + }, + [trigger, mutate] + ); + + return { + deleteTrip, + isDeleting: isMutating, + error, + }; +}; diff --git a/frontend/src/lib/apiClient.ts b/frontend/src/lib/apiClient.ts new file mode 100644 index 0000000..6fa5f2b --- /dev/null +++ b/frontend/src/lib/apiClient.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +export const apiClient = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +export const fetcher = (url: string) => apiClient.get(url).then(res => res.data); diff --git a/frontend/src/pages/test-data.ts b/frontend/src/pages/test-data.ts index e1c0ded..25d0a82 100644 --- a/frontend/src/pages/test-data.ts +++ b/frontend/src/pages/test-data.ts @@ -131,6 +131,7 @@ export const demoPages: Page[] = [ ]; export const demoTrip: Trip = { - id: 'trip1', + id: 1, + urlId: 'trip1', title: 'サンプル旅行', }; diff --git a/frontend/src/types/trip.ts b/frontend/src/types/trip.ts index 1168d9a..4747cff 100644 --- a/frontend/src/types/trip.ts +++ b/frontend/src/types/trip.ts @@ -1,9 +1,11 @@ import { z } from 'zod'; export const TripSchema = z.object({ - id: z.string(), + id: z.number(), title: z.string(), - peopleNum: z.number().optional(), + detail: z.string().nullish(), + peopleNum: z.number().nullish(), + urlId: z.string(), }); export type Trip = z.infer; From 6e5d5be1b896726ac37208b82cf05540f24dd002 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Fri, 26 Dec 2025 17:21:32 +0000 Subject: [PATCH 04/46] =?UTF-8?q?fix:=20serena=E3=81=ABproject=E3=82=92?= =?UTF-8?q?=E6=B8=A1=E3=81=97=E5=BF=98=E3=82=8C=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=9F=E3=82=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gemini/settings.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.gemini/settings.json b/.gemini/settings.json index 8d6702b..3c97dfc 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,9 +1,18 @@ { + "context": { + "fileName": "AGENTS.md" + }, "mcpServers": { "serena": { "command": "uvx", - "args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"] + "args": [ + "--from", + "git+https://github.com/oraios/serena", + "serena", + "start-mcp-server", + "--project", + "$PWD" + ] } - }, - "contextFileName": "AGENTS.md" -} + } +} \ No newline at end of file From 1fba11f39eb72456f438ffcc0f03d7ab19c1b5a3 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 11:20:49 +0000 Subject: [PATCH 05/46] =?UTF-8?q?add:=20pages=E9=96=A2=E9=80=A3=E3=81=AECR?= =?UTF-8?q?UD=E5=87=A6=E7=90=86=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/usePages.ts | 194 ++++++++++++++++++++++++++++++++ frontend/src/pages/test-data.ts | 9 +- frontend/src/types/page.ts | 5 +- 3 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 frontend/src/hooks/usePages.ts diff --git a/frontend/src/hooks/usePages.ts b/frontend/src/hooks/usePages.ts new file mode 100644 index 0000000..593c766 --- /dev/null +++ b/frontend/src/hooks/usePages.ts @@ -0,0 +1,194 @@ +import type { AxiosError } from 'axios'; +import { useCallback } from 'react'; +import useSWR, { useSWRConfig } from 'swr'; +import useSWRMutation from 'swr/mutation'; +import { z } from 'zod'; +import { apiClient, fetcher } from '@/lib/apiClient'; +import { type Page, PageSchema } from '@/types/page'; + +const TRIPS_BASE_PATH = '/trips'; +const PAGES_BASE_PATH = '/pages'; + +/** + * tripId に紐づく Page をすべて取得するフック + */ +export const usePages = (tripId: number | null) => { + const { data, error, isLoading } = useSWR( + tripId ? `${TRIPS_BASE_PATH}/${tripId}/pages` : null, + async (url: string) => { + const res = await fetcher(url); + return z.array(PageSchema).parse(res); + } + ); + + return { + pages: data, + error, + isLoading, + }; +}; + +/** + * IDを指定して単一のPageを取得するフック + */ +export const usePage = (id: number | null) => { + const { data, error, isLoading } = useSWR(id ? `${PAGES_BASE_PATH}/${id}` : null, async (url: string) => { + const res = await fetcher(url); + return PageSchema.parse(res); + }); + + return { + page: data, + error, + isLoading, + }; +}; + +type CreatePageArg = Omit; +const CreatePageSchema = PageSchema.omit({ id: true }); + +/** + * 新しいPageを作成する + */ +export const useCreatePage = (tripId: number | null) => { + const listKey = tripId ? `${TRIPS_BASE_PATH}/${tripId}/pages` : null; + const { mutate } = useSWRConfig(); + + const createPageFetcher = useCallback( + async (_key: string | null, { arg: pageData }: { arg: CreatePageArg }) => { + CreatePageSchema.parse(pageData); + const response = await apiClient.post(`${TRIPS_BASE_PATH}/${tripId}/pages`, pageData); + return response.data; + }, + [tripId] + ); + + const { trigger, isMutating, error, data } = useSWRMutation( + listKey, // リストの更新 + createPageFetcher, + { + onSuccess: (newPage: Page) => + mutate( + listKey, + (currentPages: Page[] | undefined) => { + if (currentPages == null) return [newPage]; + return [...currentPages, newPage]; + }, + { revalidate: false } + ), + } + ); + + return { + createPage: trigger, + isCreating: isMutating, + error, + createdPage: data, + }; +}; + +type UpdatePageArg = { id: number; data: Omit }; +const UpdatePageSchema = PageSchema.omit({ id: true }); + +/** + * Pageを更新するためのフック + */ +export const useUpdatePage = (tripId: number | null) => { + const listKey = tripId ? `${TRIPS_BASE_PATH}/${tripId}/pages` : null; + const { mutate } = useSWRConfig(); + + const updatePageFetcher = useCallback(async (_key: string | null, { arg }: { arg: UpdatePageArg }) => { + const { id, data } = arg; + UpdatePageSchema.parse(data); + const response = await apiClient.put(`${PAGES_BASE_PATH}/${id}`, data); + return response.data; + }, []); + + const { trigger, isMutating, error, data } = useSWRMutation(listKey, updatePageFetcher, { + // API成功時の処理(サーバーからの正式なレスポンスで確定させる) + onSuccess: updatedPage => { + // 個別データのキャッシュを、APIのレスポンス(確実なデータ)で上書き保存 + mutate(`${PAGES_BASE_PATH}/${updatedPage.id}`, updatedPage, { revalidate: false }); + }, + }); + + const updatePage = useCallback( + async (arg: UpdatePageArg) => { + const optimisticPage = { ...arg.data, id: arg.id } as Page; + + // 【個別データ】の楽観的更新 + const individualKey = `${PAGES_BASE_PATH}/${arg.id}`; + mutate(individualKey, optimisticPage, { revalidate: false }); + + // 【リストデータ】の楽観的更新 + return trigger(arg, { + optimisticData: (currentPages: Page[] | undefined) => { + if (!currentPages) return []; + return currentPages.map(p => (p.id === arg.id ? { ...p, ...arg.data } : p)); + }, + revalidate: false, + rollbackOnError: true, + onError: () => mutate(`${PAGES_BASE_PATH}/${arg.id}`), // 個別データのロールバック + }); + }, + [trigger, mutate] + ); + + return { + updatePage, + isUpdating: isMutating, + error, + updatedPage: data, + }; +}; + +type DeletePageArg = Page['id']; +const DeletePageSchema = PageSchema.shape.id; + +/** + * Pageを削除する + */ +export const useDeletePage = (tripId: number | null) => { + const listKey = tripId ? `${TRIPS_BASE_PATH}/${tripId}/pages` : null; + const { mutate } = useSWRConfig(); + + const deletePageFetcher = useCallback(async (_: string | null, { arg: id }: { arg: DeletePageArg }) => { + DeletePageSchema.parse(id); + await apiClient.delete(`${PAGES_BASE_PATH}/${id}`); + return id; + }, []); + + const { trigger, isMutating, error } = useSWRMutation(listKey, deletePageFetcher, { + onSuccess: deletedId => { + if (!deletedId) return; + // 個別キャッシュの削除を確定 + mutate(`${PAGES_BASE_PATH}/${deletedId}`, undefined, { revalidate: false }); + }, + }); + + const deletePage = useCallback( + (id: DeletePageArg) => { + const individualKey = `${PAGES_BASE_PATH}/${id}`; + // 【個別データ】を楽観的に削除 + mutate(individualKey, undefined, { revalidate: false }); + + // 【リストデータ】の楽観的更新 + return trigger(id, { + optimisticData: (currentPages: Page[] | undefined) => { + if (!currentPages) return []; + return currentPages.filter(p => p.id !== id); + }, + revalidate: false, + rollbackOnError: true, + onError: () => mutate(individualKey), // エラー時に個別データを再検証して戻す + }); + }, + [trigger, mutate] + ); + + return { + deletePage, + isDeleting: isMutating, + error, + }; +}; diff --git a/frontend/src/pages/test-data.ts b/frontend/src/pages/test-data.ts index 25d0a82..7283917 100644 --- a/frontend/src/pages/test-data.ts +++ b/frontend/src/pages/test-data.ts @@ -117,16 +117,19 @@ export const demoBlocks1: Block[] = [ export const demoPages: Page[] = [ { - id: 'page1', + id: 1, title: '1日目', + tripId: 1, }, { - id: 'page2', + id: 2, title: '2日目', + tripId: 1, }, { - id: 'page3', + id: 3, title: '3日目', + tripId: 1, }, ]; diff --git a/frontend/src/types/page.ts b/frontend/src/types/page.ts index 77d520b..04420df 100644 --- a/frontend/src/types/page.ts +++ b/frontend/src/types/page.ts @@ -1,9 +1,10 @@ import { z } from 'zod'; export const PageSchema = z.object({ - id: z.string(), + id: z.number(), title: z.string(), - details: z.string().optional(), + details: z.string().nullish(), + tripId: z.number(), }); export type Page = z.infer; From a9757541636c80c960b3973cdb0ae2ac61149335 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:42:16 +0000 Subject: [PATCH 06/46] =?UTF-8?q?add:=20Block=E3=81=AECRUDs=E5=87=A6?= =?UTF-8?q?=E7=90=86=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useBlocks.ts | 194 ++++++++++++++++++++++++++++++++ frontend/src/pages/test-data.ts | 56 +++++---- frontend/src/types/block.ts | 110 ++++++++++++++++-- 3 files changed, 327 insertions(+), 33 deletions(-) create mode 100644 frontend/src/hooks/useBlocks.ts diff --git a/frontend/src/hooks/useBlocks.ts b/frontend/src/hooks/useBlocks.ts new file mode 100644 index 0000000..1a943a5 --- /dev/null +++ b/frontend/src/hooks/useBlocks.ts @@ -0,0 +1,194 @@ +import type { AxiosError } from 'axios'; +import { useCallback } from 'react'; +import useSWR, { useSWRConfig } from 'swr'; +import useSWRMutation from 'swr/mutation'; +import { z } from 'zod'; +import { apiClient, fetcher } from '@/lib/apiClient'; +import { AppDataSchema, type Block, BlockSchema, createOmittedApiBlockSchema } from '@/types/block'; + +const PAGES_BASE_PATH = '/pages'; +const BLOCKS_BASE_PATH = '/blocks'; + +/** + * pageId に紐づく Block をすべて取得するフック + */ +export const useBlocks = (pageId: number | null) => { + const { data, error, isLoading } = useSWR( + pageId ? `${PAGES_BASE_PATH}/${pageId}/blocks` : null, + async (url: string) => { + const res = await fetcher(url); + return z.array(AppDataSchema).parse(res); + } + ); + + return { + blocks: data, + error, + isLoading, + }; +}; + +/** + * IDを指定して単一のBlockを取得するフック + */ +export const useBlock = (id: number | null) => { + const { data, error, isLoading } = useSWR(id ? `${BLOCKS_BASE_PATH}/${id}` : null, async (url: string) => { + const res = await fetcher(url); + return AppDataSchema.parse(res); + }); + + return { + block: data, + error, + isLoading, + }; +}; + +type CreateBlockArg = Omit; +const CreateBlockSchema = createOmittedApiBlockSchema(['id']); + +/** + * 新しいBlockを作成する + */ +export const useCreateBlock = (pageId: number | null) => { + const listKey = pageId ? `${PAGES_BASE_PATH}/${pageId}/blocks` : null; + const { mutate } = useSWRConfig(); + + const createBlockFetcher = useCallback( + async (_key: string | null, { arg: blockData }: { arg: CreateBlockArg }) => { + CreateBlockSchema.parse(blockData); + const response = await apiClient.post(`${PAGES_BASE_PATH}/${pageId}/blocks`, blockData); + return response.data; + }, + [pageId] + ); + + const { trigger, isMutating, error, data } = useSWRMutation( + listKey, // リストの更新 + createBlockFetcher, + { + onSuccess: (newBlock: Block) => + mutate( + listKey, + (currentBlocks: Block[] | undefined) => { + if (currentBlocks == null) return [newBlock]; + return [...currentBlocks, newBlock]; + }, + { revalidate: false } + ), + } + ); + + return { + createBlock: trigger, + isCreating: isMutating, + error, + createdBlock: data, + }; +}; + +type UpdateBlockArg = { id: number; data: Omit }; +const UpdateBlockSchema = createOmittedApiBlockSchema(['id']); + +/** + * Blockを更新するためのフック + */ +export const useUpdateBlock = (pageId: number | null) => { + const listKey = pageId ? `${PAGES_BASE_PATH}/${pageId}/blocks` : null; + const { mutate } = useSWRConfig(); + + const updateBlockFetcher = useCallback(async (_key: string | null, { arg }: { arg: UpdateBlockArg }) => { + const { id, data } = arg; + UpdateBlockSchema.parse(data); + const response = await apiClient.put(`${BLOCKS_BASE_PATH}/${id}`, data); + return response.data; + }, []); + + const { trigger, isMutating, error, data } = useSWRMutation(listKey, updateBlockFetcher, { + // API成功時の処理(サーバーからの正式なレスポンスで確定させる) + onSuccess: updatedBlock => { + // 個別データのキャッシュを、APIのレスポンス(確実なデータ)で上書き保存 + mutate(`${BLOCKS_BASE_PATH}/${updatedBlock.id}`, updatedBlock, { revalidate: false }); + }, + }); + + const updateBlock = useCallback( + async (arg: UpdateBlockArg) => { + const optimisticBlock = { ...arg.data, id: arg.id } as Block; + + // 【個別データ】の楽観的更新 + const individualKey = `${BLOCKS_BASE_PATH}/${arg.id}`; + mutate(individualKey, optimisticBlock, { revalidate: false }); + + // 【リストデータ】の楽観的更新 + return trigger(arg, { + optimisticData: (currentBlocks: Block[] | undefined) => { + if (!currentBlocks) return []; + return currentBlocks.map(b => (b.id === arg.id ? { ...b, ...arg.data } : b)) as Block[]; + }, + revalidate: false, + rollbackOnError: true, + onError: () => mutate(`${BLOCKS_BASE_PATH}/${arg.id}`), // 個別データのロールバック + }); + }, + [trigger, mutate] + ); + + return { + updateBlock, + isUpdating: isMutating, + error, + updatedBlock: data, + }; +}; + +type DeleteBlockArg = Block['id']; +const DeleteBlockSchema = BlockSchema.options[0].shape.id; + +/** + * Blockを削除する + */ +export const useDeleteBlock = (pageId: number | null) => { + const listKey = pageId ? `${PAGES_BASE_PATH}/${pageId}/blocks` : null; + const { mutate } = useSWRConfig(); + + const deleteBlockFetcher = useCallback(async (_: string | null, { arg: id }: { arg: DeleteBlockArg }) => { + DeleteBlockSchema.parse(id); + await apiClient.delete(`${BLOCKS_BASE_PATH}/${id}`); + return id; + }, []); + + const { trigger, isMutating, error } = useSWRMutation(listKey, deleteBlockFetcher, { + onSuccess: deletedId => { + if (!deletedId) return; + // 個別キャッシュの削除を確定 + mutate(`${BLOCKS_BASE_PATH}/${deletedId}`, undefined, { revalidate: false }); + }, + }); + + const deleteBlock = useCallback( + (id: DeleteBlockArg) => { + const individualKey = `${BLOCKS_BASE_PATH}/${id}`; + // 【個別データ】を楽観的に削除 + mutate(individualKey, undefined, { revalidate: false }); + + // 【リストデータ】の楽観的更新 + return trigger(id, { + optimisticData: (currentBlocks: Block[] | undefined) => { + if (!currentBlocks) return []; + return currentBlocks.filter(b => b.id !== id); + }, + revalidate: false, + rollbackOnError: true, + onError: () => mutate(individualKey), // エラー時に個別データを再検証して戻す + }); + }, + [trigger, mutate] + ); + + return { + deleteBlock, + isDeleting: isMutating, + error, + }; +}; diff --git a/frontend/src/pages/test-data.ts b/frontend/src/pages/test-data.ts index 7283917..0bc37bb 100644 --- a/frontend/src/pages/test-data.ts +++ b/frontend/src/pages/test-data.ts @@ -3,7 +3,7 @@ import type { Trip } from '@/types/trip'; export const demoBlocks2: Block[] = [ { - id: '1', + id: 1, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 12, 0), @@ -11,107 +11,119 @@ export const demoBlocks2: Block[] = [ details: `detail detail • detail detail リンク`.repeat(20), + pageId: 2, }, { - id: '2', + id: 2, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 15, 0), endTime: new Date(2024, 0, 1, 16, 0), + pageId: 2, }, { - id: '3', + id: 3, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 17, 0), endTime: new Date(2024, 0, 1, 19, 0), + pageId: 2, }, { - id: '4', + id: 4, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 19, 0), endTime: new Date(2024, 0, 1, 20, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 2, }, ]; export const demoBlocks1: Block[] = [ { - id: '1', + id: 1, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 12, 0), endTime: new Date(2024, 0, 1, 14, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 1, }, { - id: '2', + id: 2, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 15, 0), endTime: new Date(2024, 0, 1, 16, 0), + pageId: 1, }, { - id: '3', + id: 3, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 17, 0), endTime: new Date(2024, 0, 1, 19, 0), + pageId: 1, }, { - id: '4', + id: 4, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 19, 0), endTime: new Date(2024, 0, 1, 20, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 1, }, { - id: '5', + id: 5, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 20, 0), endTime: new Date(2024, 0, 1, 22, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 1, }, { - id: '6', + id: 6, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 1, 22, 0), endTime: new Date(2024, 0, 1, 23, 0), + pageId: 1, }, { - id: '7', + id: 7, type: 'schedule', title: '予定のタイトル(テンプレート)', startTime: new Date(2024, 0, 1, 23, 0), endTime: new Date(2024, 0, 2, 1, 0), + pageId: 1, }, { - id: '8', + id: 8, type: 'transportation', transportationType: 'car', title: '車移動', startTime: new Date(2024, 0, 2, 1, 0), endTime: new Date(2024, 0, 2, 2, 0), details: `detail detail -• detail detail -リンク`.repeat(20), + • detail detail + リンク`.repeat(20), + pageId: 1, }, ]; diff --git a/frontend/src/types/block.ts b/frontend/src/types/block.ts index 574ecf1..c26a9c1 100644 --- a/frontend/src/types/block.ts +++ b/frontend/src/types/block.ts @@ -1,29 +1,117 @@ import { z } from 'zod'; +// --- 共通の型定義 --- + +/** + * 交通手段の列挙型スキーマ + */ export const TransportationTypeEnum = z.enum(['car', 'bicycle', 'walk', 'ship', 'train', 'bus', 'flight']); +/** + * 交通手段の型 + */ +export type TransportationType = z.infer; -export const BaseBlockSchema = z.object({ - id: z.string(), - type: z.enum(['schedule', 'transportation']), +// --- アプリケーション層のスキーマ --- + +/** + * アプリケーション内で利用するブロックのベーススキーマ + * 各ブロックに共通するプロパティを定義します。 + */ +const AppBaseBlockSchema = z.object({ + id: z.number(), title: z.string(), startTime: z.date(), endTime: z.date(), - details: z.string().optional(), + details: z.string().nullish(), + pageId: z.number(), }); -export const ScheduleBlockSchema = BaseBlockSchema.extend({ +export const ScheduleBlockSchema = AppBaseBlockSchema.extend({ type: z.literal('schedule'), }); +export type ScheduleBlock = z.infer; -export const TransportationBlockSchema = BaseBlockSchema.extend({ +export const TransportationBlockSchema = AppBaseBlockSchema.extend({ type: z.literal('transportation'), transportationType: TransportationTypeEnum, }); +export type TransportationBlock = z.infer; +/** + * アプリケーション内部で利用するブロックのスキーマ + */ export const BlockSchema = z.discriminatedUnion('type', [ScheduleBlockSchema, TransportationBlockSchema]); - -export type BaseBlock = z.infer; export type Block = z.infer; -export type ScheduleBlock = z.infer; -export type TransportationBlock = z.infer; -export type TransportationType = z.infer; +export type BlockType = Block['type']; + +// --- API層のスキーマ --- + +const ApiDefinitionSchema = z.object({ + id: z.number(), + pageId: z.number(), + startTime: z.string(), // APIからは文字列で返却 + endTime: z.string(), + details: z.string().nullish(), + title: z.string(), +}); + +const ApiMoveSchema = ApiDefinitionSchema.extend({ + blockType: z.literal('move'), + transportationType: TransportationTypeEnum, +}); + +const ApiEventSchema = ApiDefinitionSchema.extend({ + blockType: z.literal('event'), +}); + +const ApiStaySchema = ApiDefinitionSchema.extend({ + blockType: z.literal('stay'), +}); + +/** + * APIから返ってくる生のデータ形式を表すスキーマユニオン + */ +const ApiBlockSchema = z.discriminatedUnion('blockType', [ApiMoveSchema, ApiEventSchema, ApiStaySchema]); + +// --- 変換スキーマ (API -> アプリケーション) --- +// ApiBlockSchemaをBlockSchemaに変換するロジック + +export const AppDataSchema = ApiBlockSchema.transform(apiData => { + const { startTime, endTime, ...rest } = apiData; + const common = { + ...rest, + startTime: new Date(startTime), + endTime: new Date(endTime), + }; + + if (common.blockType === 'move') { + // biome-ignore lint/correctness/noUnusedVariables: delete key + const { blockType, ...moveData } = common; + return { + ...moveData, + type: 'transportation' as const, + }; + } + + // biome-ignore lint/correctness/noUnusedVariables: delete key + const { blockType, ...scheduleData } = common; + return { + ...scheduleData, + type: 'schedule' as const, + }; +}).pipe(BlockSchema); + +/** + * API送信時に一部プロパティを省略したApiBlockスキーマを生成します。 + * 主に新規作成時のペイロード生成に利用されます。 + * @template K 省略するキーの型 + * @param keys 省略するキーの配列 + * @returns プロパティが省略されたApiBlockスキーマ + */ +export const createOmittedApiBlockSchema = (keys: K[]) => { + const omitOptions = Object.fromEntries(keys.map(k => [k, true as true])); + return z.discriminatedUnion( + 'blockType', + ApiBlockSchema.options.map((schema: z.ZodObject) => schema.omit(omitOptions)) as [z.ZodObject, ...z.ZodObject[]] + ); +}; From 3102a52a701f1a95990f13f0ff0dd75cd034d89f Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:13:48 +0000 Subject: [PATCH 07/46] =?UTF-8?q?add:=20Trip=E3=83=9A=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header.tsx | 8 ++--- frontend/src/pages/TripPage.tsx | 58 ++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index c62a303..aacb510 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -11,8 +11,8 @@ type HeaderProps = { trip: Trip; pages: Page[]; mode?: 'view' | 'edit'; - selectedPageId?: string; - onSelectPage: (pageId: string) => void; + selectedPageId?: Page['id']; + onSelectPage: (pageId: Page['id']) => void; scrollContainerRef: React.RefObject; className?: string; }; @@ -72,13 +72,13 @@ export function Header({ ) ) : ( - onSelectPage(Number(v))}> {pages.map(page => ( - + {page.title} ))} diff --git a/frontend/src/pages/TripPage.tsx b/frontend/src/pages/TripPage.tsx index c26046c..224a131 100644 --- a/frontend/src/pages/TripPage.tsx +++ b/frontend/src/pages/TripPage.tsx @@ -1,29 +1,51 @@ import { useRef, useState } from 'react'; import { Header } from '@/components/Header'; import { Timeline } from '@/components/timeline/Timeline'; -import { demoBlocks1, demoBlocks2, demoPages, demoTrip } from './test-data'; +import { useBlocks } from '@/hooks/useBlocks'; +import { usePages } from '@/hooks/usePages'; +import { useTrip } from '@/hooks/useTrips'; +import type { Page } from '@/types'; const TripPage = () => { const scrollContainerRef = useRef(null); - const [selectedPageId, setSelectedPageId] = useState(demoPages[0]?.id); + const [selectedPageId, setSelectedPageId] = useState(); + + const { trip, error: tripError, isLoading: isTripLoading } = useTrip(1); + const { pages, error: pagesError, isLoading: isPagesLoading } = usePages(trip?.id ?? null); + const { blocks, error: blocksError, isLoading: isBlocksLoading } = useBlocks(selectedPageId ?? null); + + const isLoading = isTripLoading || isPagesLoading || trip == null || pages == null; + const isError = pagesError || trip == null; return ( -
-
-
- -
-
+ <> + {isLoading &&
Loading Trip ...
} + {!(isLoading || isError) && ( +
+ {selectedPageId != null && ( + <> +
+
+ {!isBlocksLoading && blocks && } +
+ + )} +
+ )} + {tripError &&
Trip Loading Error: {String(tripError)}
} + {pagesError &&
Trip Loading Error: {String(pagesError)}
} + {blocksError &&
Trip Loading Error: {String(blocksError)}
} + ); }; From 47c5351c9a534af10c56d3061b8466f4f39d9eb0 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:22:15 +0000 Subject: [PATCH 08/46] =?UTF-8?q?change:=20=E5=9E=8B=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header.stories.tsx | 31 ++++++++++++------- .../blocks/edit/BlockScheduleEdit.stories.ts | 3 +- .../blocks/view/BlockScheduleView.stories.ts | 3 +- .../view/BlockTransportationView.stories.tsx | 3 +- .../src/components/timeline/ViewTimeline.tsx | 2 +- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/Header.stories.tsx b/frontend/src/components/Header.stories.tsx index b4d0daa..376f641 100644 --- a/frontend/src/components/Header.stories.tsx +++ b/frontend/src/components/Header.stories.tsx @@ -4,34 +4,39 @@ import type { Page } from '@/types'; import type { Trip } from '@/types/trip'; import { Header } from './Header'; -const onSelectPage = (pageId: string) => { +const onSelectPage = (pageId: Page['id']) => { console.log('Selected page ID:', pageId); }; const demoTrip: Trip = { - id: 'trip-1', + id: 1, title: '北海道旅行', + urlId: 'trip1', }; const demoPages: Page[] = [ { - id: 'one-day', + id: 1, title: '1日目', + tripId: 1, }, { - id: 'two-day', + id: 2, title: '2日目', + tripId: 1, }, { - id: 'three-day', + id: 3, title: '3日目', + tripId: 1, }, ]; const singlePage: Page[] = [ { - id: 'day-trip', + id: 1, title: '日帰り旅行', + tripId: 1, }, ]; @@ -79,7 +84,7 @@ export const Default: Story = { trip: demoTrip, pages: demoPages, mode: 'view', - selectedPageId: 'one-day', + selectedPageId: 1, onSelectPage, scrollContainerRef: { current: null }, }, @@ -97,7 +102,7 @@ export const EditMode: Story = { trip: demoTrip, pages: demoPages, mode: 'edit', - selectedPageId: 'two-day', + selectedPageId: 2, onSelectPage, scrollContainerRef: { current: null }, }, @@ -113,8 +118,9 @@ export const EditMode: Story = { export const SinglePage: Story = { args: { trip: { - id: 'day-trip', + id: 1, title: '日帰り温泉ツアー', + urlId: 'trip1', }, pages: singlePage, mode: 'view', @@ -133,8 +139,9 @@ export const SinglePage: Story = { export const EmptyPages: Story = { args: { trip: { - id: 'new-trip', + id: 1, title: '新しい旅行計画', + urlId: 'trip1', }, pages: [], mode: 'edit', @@ -155,7 +162,7 @@ export const WithCustomClass: Story = { trip: demoTrip, pages: demoPages, mode: 'view', - selectedPageId: 'two-day', + selectedPageId: 2, className: 'border-b-2 border-blue-500', onSelectPage, scrollContainerRef: { current: null }, @@ -174,7 +181,7 @@ export const ScrolledState: Story = { trip: demoTrip, pages: demoPages, mode: 'view', - selectedPageId: 'one-day', + selectedPageId: 1, onSelectPage, scrollContainerRef: { current: null }, }, diff --git a/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts b/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts index 69b2890..6ce26c2 100644 --- a/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts +++ b/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts @@ -12,10 +12,11 @@ export default meta; type Story = StoryObj; const baseScheduleBlock = { - id: '1', + id: 1, type: 'schedule' as const, startTime: new Date(2024, 0, 1, 9, 0), endTime: new Date(2024, 0, 1, 21, 30), + pageId: 1, }; export const Default: Story = { diff --git a/frontend/src/components/blocks/view/BlockScheduleView.stories.ts b/frontend/src/components/blocks/view/BlockScheduleView.stories.ts index ab520f9..7def589 100644 --- a/frontend/src/components/blocks/view/BlockScheduleView.stories.ts +++ b/frontend/src/components/blocks/view/BlockScheduleView.stories.ts @@ -12,10 +12,11 @@ export default meta; type Story = StoryObj; const baseScheduleBlock = { - id: '1', + id: 1, type: 'schedule' as const, startTime: new Date(2024, 0, 1, 9, 0), endTime: new Date(2024, 0, 1, 11, 30), + pageId: 1, }; export const Default: Story = { diff --git a/frontend/src/components/blocks/view/BlockTransportationView.stories.tsx b/frontend/src/components/blocks/view/BlockTransportationView.stories.tsx index e9b5867..6146326 100644 --- a/frontend/src/components/blocks/view/BlockTransportationView.stories.tsx +++ b/frontend/src/components/blocks/view/BlockTransportationView.stories.tsx @@ -25,12 +25,13 @@ export default meta; type Story = StoryObj; const baseBlock: TransportationBlock = { - id: '1', + id: 1, type: 'transportation', transportationType: 'car', title: '移動の予定', startTime: new Date('2024-01-01T09:00:00'), endTime: new Date('2024-01-01T10:30:00'), + pageId: 1, }; export const Default: Story = { diff --git a/frontend/src/components/timeline/ViewTimeline.tsx b/frontend/src/components/timeline/ViewTimeline.tsx index 67fde37..b94cb38 100644 --- a/frontend/src/components/timeline/ViewTimeline.tsx +++ b/frontend/src/components/timeline/ViewTimeline.tsx @@ -38,7 +38,7 @@ export function ViewTimeline({ blocks, className }: ViewTimelineProps) { timelineItems.push({ type: 'block', block: currentBlock, - id: currentBlock.id, + id: String(currentBlock.id), isConnectedWithNextBlock: nextBlock ? nextBlock.startTime.getTime() === currentBlock.endTime.getTime() : false, }); } From 4010d2eb75a5b9c22a9278b1b943b6c88c12527b Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:52:35 +0000 Subject: [PATCH 09/46] add: endTime null --- frontend/src/components/timeline/Timeline.stories.tsx | 8 ++++++++ frontend/src/components/timeline/ViewTimeline.tsx | 6 +++--- frontend/src/types/block.ts | 6 +++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/timeline/Timeline.stories.tsx b/frontend/src/components/timeline/Timeline.stories.tsx index 1218760..ed6e291 100644 --- a/frontend/src/components/timeline/Timeline.stories.tsx +++ b/frontend/src/components/timeline/Timeline.stories.tsx @@ -62,6 +62,14 @@ const sampleBlocks: Block[] = [ リンク`.repeat(20), pageId: 1, }, + { + id: 5, + type: 'schedule', + title: '点の予定', + startTime: new Date(2024, 0, 1, 22, 0), + endTime: null, + pageId: 1, + }, ]; export const ViewMode: Story = { diff --git a/frontend/src/components/timeline/ViewTimeline.tsx b/frontend/src/components/timeline/ViewTimeline.tsx index b94cb38..4cf2bcc 100644 --- a/frontend/src/components/timeline/ViewTimeline.tsx +++ b/frontend/src/components/timeline/ViewTimeline.tsx @@ -28,7 +28,7 @@ export function ViewTimeline({ blocks, className }: ViewTimelineProps) { const nextBlock = i < sortedBlocks.length - 1 ? sortedBlocks[i + 1] : null; // 前のブロックとの間に時間間隔がある場合、破線ブロックを追加 - if (previousBlock && previousBlock.endTime.getTime() !== currentBlock.startTime.getTime()) { + if (previousBlock && previousBlock.endTime?.getTime() !== currentBlock.startTime.getTime()) { timelineItems.push({ type: 'gap', id: `gap-${previousBlock.id}-${currentBlock.id}`, @@ -39,7 +39,7 @@ export function ViewTimeline({ blocks, className }: ViewTimelineProps) { type: 'block', block: currentBlock, id: String(currentBlock.id), - isConnectedWithNextBlock: nextBlock ? nextBlock.startTime.getTime() === currentBlock.endTime.getTime() : false, + isConnectedWithNextBlock: nextBlock ? nextBlock.startTime.getTime() === currentBlock.endTime?.getTime() : false, }); } @@ -87,7 +87,7 @@ function ViewTimelineBlock({ item }: ViewTimelineBlockProps) {
{formatTime(item.block.startTime)}
- {!item.isConnectedWithNextBlock && ( + {!item.isConnectedWithNextBlock && item.block.endTime && (
{formatTime(item.block.endTime)}
diff --git a/frontend/src/types/block.ts b/frontend/src/types/block.ts index c26a9c1..0bf179a 100644 --- a/frontend/src/types/block.ts +++ b/frontend/src/types/block.ts @@ -21,7 +21,7 @@ const AppBaseBlockSchema = z.object({ id: z.number(), title: z.string(), startTime: z.date(), - endTime: z.date(), + endTime: z.date().nullable(), details: z.string().nullish(), pageId: z.number(), }); @@ -50,7 +50,7 @@ const ApiDefinitionSchema = z.object({ id: z.number(), pageId: z.number(), startTime: z.string(), // APIからは文字列で返却 - endTime: z.string(), + endTime: z.string().nullish(), details: z.string().nullish(), title: z.string(), }); @@ -81,7 +81,7 @@ export const AppDataSchema = ApiBlockSchema.transform(apiData => { const common = { ...rest, startTime: new Date(startTime), - endTime: new Date(endTime), + endTime: endTime != null ? new Date(endTime) : null, }; if (common.blockType === 'move') { From f10da6961bba48dd2d54d85ebf4f5c41aa5b0408 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:31:11 +0000 Subject: [PATCH 10/46] =?UTF-8?q?add:=20URLID=E3=81=8B=E3=82=89trip?= =?UTF-8?q?=E3=82=92=E5=8F=96=E5=BE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 2 +- frontend/src/pages/TripPage.tsx | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 77652f6..c97d028 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,7 @@ const App = () => { return ( } /> - } /> + } /> } /> ); diff --git a/frontend/src/pages/TripPage.tsx b/frontend/src/pages/TripPage.tsx index 224a131..f8a44b1 100644 --- a/frontend/src/pages/TripPage.tsx +++ b/frontend/src/pages/TripPage.tsx @@ -1,22 +1,30 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { useParams } from 'react-router-dom'; import { Header } from '@/components/Header'; import { Timeline } from '@/components/timeline/Timeline'; import { useBlocks } from '@/hooks/useBlocks'; import { usePages } from '@/hooks/usePages'; -import { useTrip } from '@/hooks/useTrips'; +import { useTripByUrlId } from '@/hooks/useTrips'; import type { Page } from '@/types'; const TripPage = () => { const scrollContainerRef = useRef(null); const [selectedPageId, setSelectedPageId] = useState(); + const { urlId } = useParams<{ urlId: string }>(); - const { trip, error: tripError, isLoading: isTripLoading } = useTrip(1); + const { trip, error: tripError, isLoading: isTripLoading } = useTripByUrlId(urlId ?? null); const { pages, error: pagesError, isLoading: isPagesLoading } = usePages(trip?.id ?? null); const { blocks, error: blocksError, isLoading: isBlocksLoading } = useBlocks(selectedPageId ?? null); const isLoading = isTripLoading || isPagesLoading || trip == null || pages == null; const isError = pagesError || trip == null; + useEffect(() => { + if (selectedPageId == null && pages != null) { + setSelectedPageId(pages[0].id); + } + }, [pages, selectedPageId]); + return ( <> {isLoading &&
Loading Trip ...
} From e2945f9897ef5c7987be8064829e4f7977204e5a Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:03:19 +0000 Subject: [PATCH 11/46] =?UTF-8?q?docs:=20hooks=E3=81=AE=E3=81=98=E3=81=A3?= =?UTF-8?q?=E3=81=A3=E3=81=9D=E3=81=86=E6=96=B9=E9=87=9D=E3=83=89=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/README.md | 90 +++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/frontend/src/hooks/README.md b/frontend/src/hooks/README.md index 0148760..8b51ba4 100644 --- a/frontend/src/hooks/README.md +++ b/frontend/src/hooks/README.md @@ -7,7 +7,7 @@ このプロジェクトのカスタムフックを実装する際は、以下の点に注意してください。 -### 1. データ取得と状態管理 +### 1. データ取得と状態管理(SWR) データ取得とそれに伴う状態管理には、 **SWR** ライブラリを全面的に採用しています。 @@ -16,66 +16,82 @@ これにより、キャッシュ管理、再検証、ローディング状態の管理などを SWR に一任し、効率的で一貫性のあるデータフローを実現します。 -### 2. APIクライアント +#### SWRキーの管理 -APIリクエストは、`@/lib/apiClient` で定義されている共通の `apiClient` (axiosインスタンス) と `fetcher` を使用してください。 +- **基本**: SWRのキャッシュキーには、APIのエンドポイントURLをそのまま使用します。 +- **親子関係**: `tripId`に紐づくPage一覧(`/trips/:tripId/pages`)のように、他のデータに依存する場合は、親IDが`null`の間はリクエストが実行されないよう、キーを `null` に設定します。 +- **複数キーでの同一データアクセス**: `useTrip(id)`と`useTripByUrlId(urlId)`のように、異なるキーで同一リソースにアクセスする可能性があります。この場合、片方のフックでデータを取得した際に、もう一方のキーのキャッシュも`mutate`関数で手動更新し、キャッシュの同期を保ちます(`useTrips.ts`参照)。 -```typescript -import { apiClient, fetcher } from '@/lib/apiClient'; -import useSWR from 'swr'; - -// GETリクエストの例 -const { data, error, isLoading } = useSWR('/api/path', fetcher); -``` +### 2. APIクライアントとバリデーション -これにより、リクエストヘッダー、ベースURL、タイムアウト、エラーハンドリングなどの設定を一元管理します。 +- **APIクライアント**: APIリクエストは、`@/lib/apiClient` で定義されている共通の `apiClient` (axiosインスタンス) と `fetcher` を使用してください。 +- **レスポンスのバリデーション**: APIからのレスポンスは、必ず`zod`スキーマを用いてパース・バリデーションを行ってください。これにより、予期しないデータ構造による実行時エラーを防ぎます。 +- **APIエンドポイント**: 各フックファイル内で、関連するAPIのベースパスを`const TRIPS_BASE_PATH = '/trips';`のように定数として定義してください。 ### 3. Mutation(データ更新処理)の実装パターン -`useSWRMutation` を使用したデータ作成・更新・削除処理は、以下のパターンで実装します。 +`useSWRMutation` を使用したデータ作成・更新・削除処理は、ユーザー体験向上のため、楽観的更新(Optimistic UI)を基本パターンとします。 -#### 楽観的更新 (Optimistic UI) +#### a. 作成 (Create) -ユーザー体験を向上させるため、更新・削除処理では楽観的更新を積極的に採用します。サーバーからのレスポンスを待たずにUIを即時更新し、処理が失敗した場合は元の状態にロールバックします。 +作成処理では、APIが成功した後にリストのキャッシュを更新します。 -- `optimisticData` オプションで、mutation実行前のキャッシュデータを元に新しい状態を生成します。 -- `rollbackOnError: true` を設定し、エラー発生時にキャッシュを自動的にロールバックさせます。 -- `revalidate: true` を設定し、mutation成功後に最新のデータを再取得してキャッシュを更新します。 +- `useSWRMutation` の `onSuccess` コールバック内で `mutate` を呼び出し、現在のリストキャッシュに新しいデータを追加します。この際、リストの再検証は不要なため `revalidate: false` を設定します。 -#### キャッシュの手動更新 +```typescript +// useCreatePage.ts の例 +onSuccess: (newPage: Page) => + mutate( + listKey, + (currentPages: Page[] | undefined) => { + if (currentPages == null) return [newPage]; + return [...currentPages, newPage]; + }, + { revalidate: false } + ), +``` + +#### b. 更新 (Update) -mutation成功後、関連する他のキャッシュ(例: 一覧表示用のキャッシュと詳細表示用のキャッシュ)の一貫性を保つために、`onSuccess` コールバック内で `mutate` 関数を呼び出して手動で更新します。 +更新処理では、UIの即時反映とデータ整合性の確保を両立させます。 -`mutate` 関数は `useSWRConfig` フックから取得します。 +1. `useSWRMutation` をラップした `updateXXX` 関数をコンポーネントに提供します。 +2. `updateXXX` 関数内で、まず更新対象の**個別データ**のキャッシュ(例: `/pages/:id`)を楽観的に更新します。 +3. 次に `trigger` を呼び出し、**リストデータ**のキャッシュ(例: `/trips/:tripId/pages`)を楽観的に更新します (`optimisticData`)。 +4. `trigger` の `onSuccess` コールバックで、サーバーからのレスポンスに基づき**個別データ**のキャッシュを確定させます。 +5. エラー発生時は `rollbackOnError: true` と `onError` コールバックで、両方のキャッシュをロールバックします。 ```typescript -import { useSWRConfig } from 'swr'; +// useUpdatePage.ts の updatePage 関数の流れを参考にしてください。 +``` -// 更新処理フック内 -const { mutate } = useSWRConfig(); +#### c. 削除 (Delete) -// mutationのオプション内 -onSuccess: (updatedData) => { - // 詳細ページのキャッシュを更新 - mutate(`/api/items/${updatedData.id}`, updatedData, false); - // 一覧ページのキャッシュも再検証するなど、必要に応じた処理 -} -``` +削除処理も更新と同様のパターンで、個別データとリストデータの両方を楽観的に更新します。 + +1. `useSWRMutation` をラップした `deleteXXX` 関数を提供します。 +2. `deleteXXX` 関数内で、まず**個別データ**のキャッシュを `undefined` に設定して楽観的に削除します。 +3. 次に `trigger` を呼び出し、**リストデータ**のキャッシュから対象データをフィルタリングして楽観的に更新します。 +4. `trigger` の `onSuccess` で、個別キャッシュの削除を確定させます。 ### 4. 型定義 APIから取得するデータやフックの引数などに関連する型は、`@/types` ディレクトリで一元管理します。フック内ではこれらの型をインポートして使用し、型安全性を確保してください。 -```typescript -import type { Trip } from '@/types/trip'; - -type UpdateTripArg = { id: string; data: Omit }; -``` - ### 5. フックの命名 Reactの規則に従い、カスタムフックの関数名は必ず `use` から始めてください(例: `useTrips`, `useUpdateTrip`)。 ### 参考実装 -これらの規則の具体的な実装例として、`useTrips.ts` を参照してください。基本的なCRUDS操作(Read, Create, Update, Delete)におけるSWRのベストプラクティスが網羅されています。 +これらの規則の具体的な実装例として、以下のファイルを参照してください。それぞれのファイルが異なるパターンのベストプラクティスを網羅しています。 + +- **`useTrips.ts`**: + - 基本的なCRUDS操作。 + - `useTrip` と `useTripByUrlId` における複数キーでのキャッシュ同期。 + - `useUpdateTrip` における `populateCache` を利用したリスト更新。 +- **`usePages.ts`**: + - 親子関係(Trip -> Page)を持つデータのCRUDS操作。 + - `updatePage` / `deletePage` における、個別・リスト両キャッシュの楽観的更新パターン。 +- **`useBlocks.ts`**: + - `usePages.ts` と同様、親子関係(Page -> Block)のデータ操作における実践的な実装例。 From dbe69d753b3d92da6d1607cedf87cf9ddbf17642 Mon Sep 17 00:00:00 2001 From: kuu13580 <46004336+kuu13580@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:16:10 +0000 Subject: [PATCH 12/46] refactor: details -> detail --- .../blocks/edit/BlockScheduleEdit.stories.ts | 8 ++++---- .../blocks/view/BlockScheduleView.stories.ts | 8 ++++---- .../src/components/blocks/view/BlockScheduleView.tsx | 6 +++--- .../blocks/view/BlockTransportationView.stories.tsx | 8 ++++---- .../blocks/view/BlockTransportationView.tsx | 6 +++--- .../src/components/timeline/Timeline.stories.tsx | 4 ++-- frontend/src/pages/test-data.ts | 12 ++++++------ frontend/src/types/block.ts | 4 ++-- frontend/src/types/page.ts | 2 +- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts b/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts index 6ce26c2..f7cfc2f 100644 --- a/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts +++ b/frontend/src/components/blocks/edit/BlockScheduleEdit.stories.ts @@ -24,12 +24,12 @@ export const Default: Story = { block: { ...baseScheduleBlock, title: '草津温泉入浴', - details: '湯畑周辺の温泉施設を巡る。特に西の河原公園の露天風呂がおすすめ。', + detail: '湯畑周辺の温泉施設を巡る。特に西の河原公園の露天風呂がおすすめ。', }, }, }; -export const WithoutDetails: Story = { +export const WithoutDetail: Story = { args: { block: { ...baseScheduleBlock, @@ -38,12 +38,12 @@ export const WithoutDetails: Story = { }, }; -export const LongTitleAndDetails: Story = { +export const LongTitleAndDetail: Story = { args: { block: { ...baseScheduleBlock, title: '草津温泉街散策と湯畑見学、お土産購入とカフェタイム', - details: '温泉街をゆっくり散策しながら、湯畑の見学と地元の名産品を購入。最後にカフェでひと休み。'.repeat(5), + detail: '温泉街をゆっくり散策しながら、湯畑の見学と地元の名産品を購入。最後にカフェでひと休み。'.repeat(5), }, }, }; diff --git a/frontend/src/components/blocks/view/BlockScheduleView.stories.ts b/frontend/src/components/blocks/view/BlockScheduleView.stories.ts index 7def589..e39671e 100644 --- a/frontend/src/components/blocks/view/BlockScheduleView.stories.ts +++ b/frontend/src/components/blocks/view/BlockScheduleView.stories.ts @@ -24,12 +24,12 @@ export const Default: Story = { block: { ...baseScheduleBlock, title: '草津温泉入浴', - details: '湯畑周辺の温泉施設を巡る。特に西の河原公園の露天風呂がおすすめ。', + detail: '湯畑周辺の温泉施設を巡る。特に西の河原公園の露天風呂がおすすめ。', }, }, }; -export const WithoutDetails: Story = { +export const WithoutDetail: Story = { args: { block: { ...baseScheduleBlock, @@ -38,12 +38,12 @@ export const WithoutDetails: Story = { }, }; -export const LongTitleAndDetails: Story = { +export const LongTitleAndDetail: Story = { args: { block: { ...baseScheduleBlock, title: '草津温泉街散策と湯畑見学、お土産購入とカフェタイム', - details: '温泉街をゆっくり散策しながら、湯畑の見学と地元の名産品を購入。最後にカフェでひと休み。'.repeat(5), + detail: '温泉街をゆっくり散策しながら、湯畑の見学と地元の名産品を購入。最後にカフェでひと休み。'.repeat(5), }, }, }; diff --git a/frontend/src/components/blocks/view/BlockScheduleView.tsx b/frontend/src/components/blocks/view/BlockScheduleView.tsx index 8363a1c..10d09bc 100644 --- a/frontend/src/components/blocks/view/BlockScheduleView.tsx +++ b/frontend/src/components/blocks/view/BlockScheduleView.tsx @@ -48,7 +48,7 @@ export function BlockScheduleView({ block, className }: BlockScheduleViewProps) )} >
{block.title}
- {block.details && ( + {block.detail && (
- {block.details} + {block.detail}
)} - {block.details && isOverflowDetail && ( + {block.detail && isOverflowDetail && ( + <> + + + ) : ( - + )} )} diff --git a/frontend/src/pages/TripPage.tsx b/frontend/src/pages/TripPage.tsx index 40f745f..736e1b3 100644 --- a/frontend/src/pages/TripPage.tsx +++ b/frontend/src/pages/TripPage.tsx @@ -4,11 +4,13 @@ import { Header } from '@/components/Header'; import { usePages } from '@/hooks/usePages'; import { useTripByUrlId } from '@/hooks/useTrips'; import type { Page } from '@/types'; +import { EditTripLayout } from './TripPage/EditTripLayout'; import { ViewTripLayout } from './TripPage/ViewTripLayout'; const TripPage = () => { const scrollContainerRef = useRef(null); const [selectedPageId, setSelectedPageId] = useState(); + const [mode, setMode] = useState<'view' | 'edit'>('view'); const { urlId } = useParams<{ urlId: string }>(); const { trip, error: tripError, isLoading: isTripLoading } = useTripByUrlId(urlId ?? null); @@ -48,12 +50,14 @@ const TripPage = () => { selectedPageId={selectedPageId} pages={pages} onSelectPage={setSelectedPageId} + setMode={setMode} trip={trip} - mode='view' + mode={mode} scrollContainerRef={scrollContainerRef} /> )} - {selectedPageId != null && } + {selectedPageId != null && mode === 'view' && } + {selectedPageId != null && mode === 'edit' && } )} From 6acf958eb7895b5b9c061e0b66d9960b80ca835e Mon Sep 17 00:00:00 2001 From: kuu13580 <13580kuu@gmail.com> Date: Tue, 6 Jan 2026 16:52:20 +0000 Subject: [PATCH 23/46] =?UTF-8?q?fix:=20Header=E3=81=AE=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E6=8C=99=E5=8B=95=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biome.json | 1 - frontend/src/components/Header.tsx | 33 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/biome.json b/biome.json index b99a15d..07a9615 100644 --- a/biome.json +++ b/biome.json @@ -45,7 +45,6 @@ } }, "complexity": { - "noExcessiveCognitiveComplexity": "warn", "noExtraBooleanCast": "error", "noStaticOnlyClass": "error", "noUselessCatch": "error", diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 9d47332..82ec326 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { type Dispatch, type SetStateAction, useCallback, useEffect, useState } from 'react'; +import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; import { cn } from '@/lib/utils'; import type { Page } from '@/types'; import type { Trip } from '@/types/trip'; @@ -29,18 +29,37 @@ export function Header({ scrollContainerRef, }: HeaderProps) { const [isScrolled, setIsScrolled] = useState(false); + const scrollY = useRef(0); const handleScroll = useCallback(() => { const container = scrollContainerRef?.current; if (!container) return; const scrollTop = container.scrollTop; - // ヘッダーの大きさを考慮して間を空ける - if (!isScrolled && scrollTop > 100) { - setIsScrolled(true); - return; - } else if (isScrolled && scrollTop <= 50) { - setIsScrolled(false); + const deltaY = scrollTop - scrollY.current; + + // isScrolledの次の状態を計算する + let nextIsScrolled = isScrolled; + + if (scrollTop < 50) { + // ページ最上部では常に表示 + nextIsScrolled = false; + } else if (Math.abs(deltaY) > 10) { + if (deltaY < 0 && isScrolled) { + // 上スクロール and ヘッダーが非表示中 -> 表示させる + nextIsScrolled = false; + } else if (deltaY > 0 && !isScrolled) { + // 下スクロール and ヘッダーが表示中 -> 非表示にさせる + nextIsScrolled = true; + } + } + + if (nextIsScrolled !== isScrolled) { + setIsScrolled(nextIsScrolled); + } else { + // 状態が変化しなかった場合のみ、スクロール位置の基準を更新 + // レイアウトシフトによる誤作動を防ぐ + scrollY.current = scrollTop; } }, [isScrolled, scrollContainerRef]); From ba44317aff559818a1bad71e8a0ffd77808b3e85 Mon Sep 17 00:00:00 2001 From: kuu13580 <13580kuu@gmail.com> Date: Wed, 7 Jan 2026 17:06:24 +0000 Subject: [PATCH 24/46] =?UTF-8?q?fix:=20=E3=82=B9=E3=82=B1=E3=82=B8?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=83=AB=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/edit/BlockScheduleEdit.css | 34 +++++++++++++++++++ .../blocks/edit/BlockScheduleEdit.tsx | 19 +++++------ frontend/src/index.css | 5 +++ 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/blocks/edit/BlockScheduleEdit.css diff --git a/frontend/src/components/blocks/edit/BlockScheduleEdit.css b/frontend/src/components/blocks/edit/BlockScheduleEdit.css new file mode 100644 index 0000000..c6a9c51 --- /dev/null +++ b/frontend/src/components/blocks/edit/BlockScheduleEdit.css @@ -0,0 +1,34 @@ +/* frontend/src/components/blocks/edit/BlockScheduleEdit.css */ +.BlockScheduleEdit .schedule-time-wrapper { + /* デフォルトは縦並び */ + flex-direction: column; + + .vertical-divider { + display: none; + } + + .horizontal-divider { + display: block; + } +} + +@container (height < 4rem) { + .BlockScheduleEdit .schedule-time-wrapper { + /* 横並びに変更 */ + flex-direction: row; + + .vertical-divider { + display: block; + } + + .horizontal-divider { + display: none; + } + } +} + +@container (height < 2rem) { + .BlockScheduleEdit .schedule-time-wrapper { + font-size: 12px !important; + } +} diff --git a/frontend/src/components/blocks/edit/BlockScheduleEdit.tsx b/frontend/src/components/blocks/edit/BlockScheduleEdit.tsx index eb179da..27e20bb 100644 --- a/frontend/src/components/blocks/edit/BlockScheduleEdit.tsx +++ b/frontend/src/components/blocks/edit/BlockScheduleEdit.tsx @@ -1,8 +1,8 @@ import dayjs from 'dayjs'; import circleInfoIcon from '@/assets/icons/circle-info.svg'; -import gripVerticalIcon from '@/assets/icons/grip-vertical-white.svg'; import { cn } from '@/lib/utils'; import type { ScheduleBlockComponentProps } from '../types'; +import './BlockScheduleEdit.css'; interface BlockScheduleEditProps extends ScheduleBlockComponentProps {} @@ -10,24 +10,23 @@ export function BlockScheduleEdit({ block, className }: BlockScheduleEditProps) return (
-
- test -
-
+
{block.endTime ? ( <> -
{String(dayjs(block.startTime).format('HH:mm'))}
-
|
-
{String(dayjs(block.endTime).format('HH:mm'))}
+
{String(dayjs(block.startTime).format('HH:mm'))}
+
|
+
+
{String(dayjs(block.endTime).format('HH:mm'))}
) : ( -
{dayjs(block.startTime).format('HH:mm')}
+
{dayjs(block.startTime).format('HH:mm')}
)}
+
{block.title || 'タイトル未設定'}
+ ); } From 22d6bb9de3ccd1a63da3645a7e75653f372fb696 Mon Sep 17 00:00:00 2001 From: kuu13580 <13580kuu@gmail.com> Date: Fri, 30 Jan 2026 22:06:40 +0000 Subject: [PATCH 37/46] =?UTF-8?q?add:=20Header=E3=81=AE=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=83=BB=E6=8C=99=E5=8B=95=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/icons/edit-schedule-white.svg | 5 + frontend/src/assets/icons/eye-solid-white.svg | 1 + .../icons/pen-to-square-solid-white.svg | 1 + frontend/src/components/Header.tsx | 114 +++++++++++++++--- frontend/src/index.css | 6 + 5 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 frontend/src/assets/icons/edit-schedule-white.svg create mode 100644 frontend/src/assets/icons/eye-solid-white.svg create mode 100644 frontend/src/assets/icons/pen-to-square-solid-white.svg diff --git a/frontend/src/assets/icons/edit-schedule-white.svg b/frontend/src/assets/icons/edit-schedule-white.svg new file mode 100644 index 0000000..709a2c2 --- /dev/null +++ b/frontend/src/assets/icons/edit-schedule-white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/icons/eye-solid-white.svg b/frontend/src/assets/icons/eye-solid-white.svg new file mode 100644 index 0000000..a56e42e --- /dev/null +++ b/frontend/src/assets/icons/eye-solid-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/pen-to-square-solid-white.svg b/frontend/src/assets/icons/pen-to-square-solid-white.svg new file mode 100644 index 0000000..4da6e04 --- /dev/null +++ b/frontend/src/assets/icons/pen-to-square-solid-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 82ec326..8b5bc34 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,4 +1,7 @@ import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; +import editScheduleIcon from '@/assets/icons/edit-schedule-white.svg'; +import eyeSolidIcon from '@/assets/icons/eye-solid-white.svg'; +import penToSquareSolidIcon from '@/assets/icons/pen-to-square-solid-white.svg'; import { cn } from '@/lib/utils'; import type { Page } from '@/types'; import type { Trip } from '@/types/trip'; @@ -18,6 +21,8 @@ type HeaderProps = { className?: string; }; +const transitionClassNames = 'duration-300 ease-in-out'; + export function Header({ pages, trip, @@ -77,24 +82,24 @@ export function Header({
-
+
{trip.title}
{isScrolled ? ( selectedPage && ( - + {selectedPage.title} ) ) : ( )} - {!isScrolled && ( -
- {mode === 'edit' ? ( - <> - - - - ) : ( - - )} -
- )} +
+ {mode === 'edit' ? ( + <> + + + + ) : ( + + )} +
); } + +type HeaderButtonBaseProps = React.ComponentProps & { + isScrolled: boolean; + iconSrc: string; + iconAlt?: string; +}; + +const HeaderButtonBase = ({ + isScrolled, + iconSrc, + iconAlt, + className, + children, + ...buttonProps +}: HeaderButtonBaseProps) => { + return ( + + ); +}; + +const EditModeButton = ({ + isScrolled, + setMode, +}: { + isScrolled: boolean; + setMode: Dispatch>; +}) => ( + setMode('edit')} + > + 編集モード + +); + +const ViewModeButton = ({ + isScrolled, + setMode, +}: { + isScrolled: boolean; + setMode: Dispatch>; +}) => ( + setMode('view')} + > + 閲覧モード + +); + +const PageInfoEditButton = ({ isScrolled }: { isScrolled: boolean }) => ( + + ページ情報編集 + +); diff --git a/frontend/src/index.css b/frontend/src/index.css index b582cf1..1e16176 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -5,6 +5,12 @@ @custom-variant dark (&:is(.dark *)); +:root { + interpolate-size: allow-keywords; + /* autoのアニメーション有効化 + Chrome, Edge 129- のみ対応 */ +} + :root { --background: oklch(1 0 0); --foreground: oklch(0.1448 0 0); From 8ffcab293e820e13ceadb7f995e31e2ba9b38f38 Mon Sep 17 00:00:00 2001 From: kuu13580 <13580kuu@gmail.com> Date: Fri, 30 Jan 2026 22:12:39 +0000 Subject: [PATCH 38/46] =?UTF-8?q?change:=20Grid=E3=83=AC=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=82=A6=E3=83=88=E3=81=A7=E3=83=98=E3=83=83=E3=83=80=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3=E4=B8=AD=E5=A4=AE=E3=82=92?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 8b5bc34..bab8d05 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -87,10 +87,19 @@ export function Header({ )} > -
-
+
+ {/* 左カラム */} +
{trip.title}
+ + {/* 中央カラム */} {isScrolled ? ( selectedPage && ( @@ -111,7 +120,9 @@ export function Header({ )} -
+ + {/* 右カラム */} +
{mode === 'edit' ? ( <> From 102f291c5723af4df110b0720291b8ba7fcbfde7 Mon Sep 17 00:00:00 2001 From: kuu13580 <13580kuu@gmail.com> Date: Sun, 1 Feb 2026 13:47:50 +0000 Subject: [PATCH 39/46] =?UTF-8?q?add:=20=E8=BF=BD=E5=8A=A0=E3=83=80?= =?UTF-8?q?=E3=82=A4=E3=82=A2=E3=83=AD=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/dialogs/AddBlockDialog.tsx | 364 ++++++++++++++++++ frontend/src/dialogs/index.ts | 1 + .../src/pages/TripPage/EditTripLayout.tsx | 64 +-- frontend/src/types/block.ts | 13 + 4 files changed, 410 insertions(+), 32 deletions(-) create mode 100644 frontend/src/dialogs/index.ts diff --git a/frontend/src/dialogs/AddBlockDialog.tsx b/frontend/src/dialogs/AddBlockDialog.tsx index e69de29..4f7b0fc 100644 --- a/frontend/src/dialogs/AddBlockDialog.tsx +++ b/frontend/src/dialogs/AddBlockDialog.tsx @@ -0,0 +1,364 @@ +import { useCallback, useEffect, useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Textarea } from '@/components/ui/textarea'; +import type { Block, ScheduleBlock, TransportationBlock, TransportationType } from '@/types/block'; +import { TRANSPORTATION_OPTIONS } from '@/types/block'; + +interface AddBlockDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + initialStartTime: Date; + initialEndTime: Date; + pageId: number; + onSubmit: (block: Omit) => Promise; +} + +// ユーティリティ関数: DateをHH:MM形式に変換 +const formatTimeInput = (date: Date): string => { + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; +}; + +// ユーティリティ関数: 2つのDateから時間差を計算(分単位) +const calculateDurationMinutes = (start: Date, end: Date): number => { + return Math.floor((end.getTime() - start.getTime()) / (1000 * 60)); +}; + +// ユーティリティ関数: 分数を時間と分に分解 +const minutesToHoursAndMinutes = (totalMinutes: number): { hours: number; minutes: number } => { + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return { hours, minutes }; +}; + +export const AddBlockDialog = ({ + open, + onOpenChange, + initialStartTime, + initialEndTime, + pageId, + onSubmit, +}: AddBlockDialogProps) => { + // タブの状態 + const [blockType, setBlockType] = useState<'schedule' | 'transportation'>('schedule'); + + // 予定ブロック用のstate + const [scheduleTitle, setScheduleTitle] = useState(''); + const [scheduleStartTime, setScheduleStartTime] = useState(formatTimeInput(initialStartTime)); + const [scheduleDurationHours, setScheduleDurationHours] = useState(() => { + const duration = calculateDurationMinutes(initialStartTime, initialEndTime); + return minutesToHoursAndMinutes(duration).hours.toString(); + }); + const [scheduleDurationMinutes, setScheduleDurationMinutes] = useState(() => { + const duration = calculateDurationMinutes(initialStartTime, initialEndTime); + return minutesToHoursAndMinutes(duration).minutes.toString(); + }); + const [scheduleNoEndTime, setScheduleNoEndTime] = useState(false); + const [scheduleDetail, setScheduleDetail] = useState(''); + + // 移動ブロック用のstate + const [transportationType, setTransportationType] = useState('car'); + const [transportationTitle, setTransportationTitle] = useState( + TRANSPORTATION_OPTIONS.find(opt => opt.value === 'car')?.label ?? '移動' + ); + const [transportationStartTime, setTransportationStartTime] = useState(formatTimeInput(initialStartTime)); + const [transportationDurationHours, setTransportationDurationHours] = useState(() => { + const duration = calculateDurationMinutes(initialStartTime, initialEndTime); + return minutesToHoursAndMinutes(duration).hours.toString(); + }); + const [transportationDurationMinutes, setTransportationDurationMinutes] = useState(() => { + const duration = calculateDurationMinutes(initialStartTime, initialEndTime); + return minutesToHoursAndMinutes(duration).minutes.toString(); + }); + const [transportationNoEndTime, setTransportationNoEndTime] = useState(false); + const [transportationDetail, setTransportationDetail] = useState(''); + + // フォームをリセットする関数 + const resetForm = useCallback(() => { + setBlockType('schedule'); + setScheduleTitle(''); + setScheduleStartTime(formatTimeInput(initialStartTime)); + const duration = calculateDurationMinutes(initialStartTime, initialEndTime); + const { hours, minutes } = minutesToHoursAndMinutes(duration); + setScheduleDurationHours(hours.toString()); + setScheduleDurationMinutes(minutes.toString()); + setScheduleNoEndTime(false); + setScheduleDetail(''); + setTransportationType('car'); + setTransportationTitle(''); + setTransportationStartTime(formatTimeInput(initialStartTime)); + setTransportationDurationHours(hours.toString()); + setTransportationDurationMinutes(minutes.toString()); + setTransportationNoEndTime(false); + setTransportationDetail(''); + }, [initialStartTime, initialEndTime]); + + // ダイアログが開いたときにフォームを新しい初期値で初期化 + useEffect(() => { + if (open) { + resetForm(); + } + }, [open, resetForm]); + + // ダイアログが閉じたときにフォームをリセット + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen) { + resetForm(); + } + onOpenChange(newOpen); + }; + + // サブミット処理 + const handleSubmit = async () => { + if (blockType === 'schedule') { + // 予定ブロックの処理 + if (!scheduleTitle.trim()) { + return; + } + + // startTimeを構築 + const [hours, minutes] = scheduleStartTime.split(':').map(Number); + const startTime = new Date(initialStartTime); + startTime.setHours(hours, minutes, 0, 0); + + // endTimeを計算 + let endTime: Date | null = null; + if (!scheduleNoEndTime) { + const durationHours = Number.parseInt(scheduleDurationHours) || 0; + const durationMinutes = Number.parseInt(scheduleDurationMinutes) || 0; + const totalMinutes = durationHours * 60 + durationMinutes; + endTime = new Date(startTime.getTime() + totalMinutes * 60 * 1000); + } + + const block: Omit = { + type: 'schedule', + title: scheduleTitle.trim(), + startTime, + endTime, + detail: scheduleDetail.trim() || null, + pageId, + }; + + await onSubmit(block); + handleOpenChange(false); + } else { + // 移動ブロックの処理 + const transportationLabel = TRANSPORTATION_OPTIONS.find(opt => opt.value === transportationType)?.label ?? '移動'; + + // startTimeを構築(移動ブロックでは初期時間をそのまま使用) + const startTime = new Date(initialStartTime); + + // endTimeを計算 + let endTime: Date | null = null; + if (!transportationNoEndTime) { + const durationHours = Number.parseInt(transportationDurationHours) || 0; + const durationMinutes = Number.parseInt(transportationDurationMinutes) || 0; + const totalMinutes = durationHours * 60 + durationMinutes; + endTime = new Date(startTime.getTime() + totalMinutes * 60 * 1000); + } + + const block: Omit = { + type: 'transportation', + transportationType, + title: transportationLabel, + startTime, + endTime, + detail: transportationDetail.trim() || null, + pageId, + }; + + await onSubmit(block); + handleOpenChange(false); + } + }; + + return ( + + + + ブロックの追加 + + + setBlockType(value as 'schedule' | 'transportation')}> + + 予定 + 移動 + + + {/* 予定ブロック */} + +
+ + setScheduleTitle(e.target.value)} + placeholder='予定のタイトル' + /> +
+ +
+ + setScheduleStartTime(e.target.value)} + /> +
+ +
+ +
+ setScheduleDurationHours(e.target.value)} + disabled={scheduleNoEndTime} + className='w-20' + /> + 時間 + setScheduleDurationMinutes(e.target.value)} + disabled={scheduleNoEndTime} + className='w-20' + /> + +
+
+ setScheduleNoEndTime(!!checked)} + /> + +
+
+ +
+ +