From 31784d91b2aaed3eb6866dd4444c8c3879d0217a Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Tue, 26 Aug 2025 14:26:55 -0500 Subject: [PATCH] Add SQLite database and directory management system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Install better-sqlite3 for embedded SQLite support - Create complete database schema with photos, albums, tags, directories tables - Add PhotoService class with full CRUD operations and relationships - Create comprehensive API endpoints for photos, albums, directories, and stats - Add DirectoryList component with delete functionality and visual feedback - Implement directory saving to database when user selects path - Add automatic refresh of directory list when new directories are saved - Update Button component with enhanced enabled/disabled states and animations - Add Tab key handling to hide suggestions in directory modal - Update .gitignore to exclude SQLite database files 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 8 +- package-lock.json | 898 +++++++++++++++++++++++- package.json | 3 + src/app/api/albums/route.ts | 30 + src/app/api/directories/[id]/route.ts | 38 + src/app/api/directories/route.ts | 47 ++ src/app/api/photos/[id]/route.ts | 79 +++ src/app/api/photos/route.ts | 56 ++ src/app/api/stats/route.ts | 27 + src/app/api/validate-directory/route.ts | 83 ++- src/components/Button.tsx | 22 +- src/components/DirectoryList.tsx | 175 +++++ src/components/DirectoryModal.tsx | 40 +- src/components/Header.tsx | 13 +- src/components/MainLayout.tsx | 10 +- src/components/Sidebar.tsx | 27 +- src/lib/database.ts | 138 ++++ src/lib/photo-service.ts | 277 ++++++++ src/types/photo.ts | 54 ++ 19 files changed, 1974 insertions(+), 51 deletions(-) create mode 100644 src/app/api/albums/route.ts create mode 100644 src/app/api/directories/[id]/route.ts create mode 100644 src/app/api/directories/route.ts create mode 100644 src/app/api/photos/[id]/route.ts create mode 100644 src/app/api/photos/route.ts create mode 100644 src/app/api/stats/route.ts create mode 100644 src/components/DirectoryList.tsx create mode 100644 src/lib/database.ts create mode 100644 src/lib/photo-service.ts create mode 100644 src/types/photo.ts diff --git a/.gitignore b/.gitignore index 0563835..f0dc299 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,10 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts + +# SQLite database files +/data/ +*.db +*.db-shm +*.db-wal \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0c0c98e..77f89b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "ISC", "dependencies": { "@tabler/icons-react": "^3.34.1", + "@types/better-sqlite3": "^7.6.13", + "better-sqlite3": "^12.2.0", + "glob": "^11.0.3", "next": "^15.5.0", "react": "^19.1.1", "react-dom": "^19.1.1" @@ -466,6 +469,44 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "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/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -974,11 +1015,19 @@ "tailwindcss": "4.1.12" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.10.0" @@ -1004,6 +1053,30 @@ "@types/react": "^19.0.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -1042,6 +1115,60 @@ "postcss": "^8.1.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.2.0.tgz", + "integrity": "sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/browserslist": { "version": "4.25.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", @@ -1075,6 +1202,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001737", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", @@ -1130,7 +1281,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", - "optional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1142,8 +1292,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", @@ -1156,6 +1305,20 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -1163,16 +1326,45 @@ "dev": true, "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.209", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.209.tgz", @@ -1180,6 +1372,21 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -1204,6 +1411,37 @@ "node": ">=6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "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==", + "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/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -1218,6 +1456,41 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1225,6 +1498,38 @@ "dev": true, "license": "ISC" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -1232,6 +1537,36 @@ "license": "MIT", "optional": true }, + "node_modules/is-fullwidth-code-point": { + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", @@ -1481,6 +1816,15 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.18", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", @@ -1491,11 +1835,46 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -1530,6 +1909,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1548,6 +1933,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/next": { "version": "15.5.0", "resolved": "https://registry.npmjs.org/next/-/next-15.5.0.tgz", @@ -1628,6 +2019,18 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -1645,6 +2048,46 @@ "node": ">=0.10.0" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "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==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1687,6 +2130,57 @@ "dev": true, "license": "MIT" }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/react": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", @@ -1708,6 +2202,40 @@ "react": "^19.1.1" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -1719,7 +2247,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -1770,6 +2297,84 @@ "@img/sharp-win32-x64": "0.34.3" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -1789,6 +2394,120 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "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/string-width-cjs": { + "name": "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==", + "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/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -1851,12 +2570,58 @@ "node": ">=18" } }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/typescript": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", @@ -1875,7 +2640,6 @@ "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -1909,6 +2673,124 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "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/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "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/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "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==", + "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/wrap-ansi-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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index fb3ca79..37bf13d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "description": "", "dependencies": { "@tabler/icons-react": "^3.34.1", + "@types/better-sqlite3": "^7.6.13", + "better-sqlite3": "^12.2.0", + "glob": "^11.0.3", "next": "^15.5.0", "react": "^19.1.1", "react-dom": "^19.1.1" diff --git a/src/app/api/albums/route.ts b/src/app/api/albums/route.ts new file mode 100644 index 0000000..5184c06 --- /dev/null +++ b/src/app/api/albums/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' + +export async function GET() { + try { + const albums = photoService.getAlbums() + return NextResponse.json({ albums }) + } catch (error) { + console.error('Error fetching albums:', error) + return NextResponse.json( + { error: 'Failed to fetch albums' }, + { status: 500 } + ) + } +} + +export async function POST(request: NextRequest) { + try { + const albumData = await request.json() + const album = photoService.createAlbum(albumData) + + return NextResponse.json(album, { status: 201 }) + } catch (error) { + console.error('Error creating album:', error) + return NextResponse.json( + { error: 'Failed to create album' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/directories/[id]/route.ts b/src/app/api/directories/[id]/route.ts new file mode 100644 index 0000000..4dcd6ce --- /dev/null +++ b/src/app/api/directories/[id]/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params + + // Get the directory first to check if it exists + const directory = photoService.getDirectory(id) + if (!directory) { + return NextResponse.json( + { error: 'Directory not found' }, + { status: 404 } + ) + } + + // Delete the directory record + const success = photoService.deleteDirectory(id) + + if (!success) { + return NextResponse.json( + { error: 'Failed to delete directory' }, + { status: 500 } + ) + } + + return NextResponse.json({ success: true }) + } catch (error) { + console.error('Error deleting directory:', error) + return NextResponse.json( + { error: 'Failed to delete directory' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/directories/route.ts b/src/app/api/directories/route.ts new file mode 100644 index 0000000..56a1381 --- /dev/null +++ b/src/app/api/directories/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' +import path from 'path' + +export async function GET() { + try { + const directories = photoService.getDirectories() + return NextResponse.json({ directories }) + } catch (error) { + console.error('Error fetching directories:', error) + return NextResponse.json( + { error: 'Failed to fetch directories' }, + { status: 500 } + ) + } +} + +export async function POST(request: NextRequest) { + try { + const { path: directoryPath } = await request.json() + + if (!directoryPath || typeof directoryPath !== 'string') { + return NextResponse.json( + { error: 'Directory path is required' }, + { status: 400 } + ) + } + + // Create directory record + const directoryName = path.basename(directoryPath) + const directory = photoService.createOrUpdateDirectory({ + path: directoryPath, + name: directoryName, + last_scanned: new Date().toISOString(), + photo_count: 0, + total_size: 0 + }) + + return NextResponse.json(directory, { status: 201 }) + } catch (error) { + console.error('Error saving directory:', error) + return NextResponse.json( + { error: 'Failed to save directory' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/photos/[id]/route.ts b/src/app/api/photos/[id]/route.ts new file mode 100644 index 0000000..d91d5fb --- /dev/null +++ b/src/app/api/photos/[id]/route.ts @@ -0,0 +1,79 @@ +import { NextRequest, NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' + +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params + const photo = photoService.getPhoto(id) + + if (!photo) { + return NextResponse.json( + { error: 'Photo not found' }, + { status: 404 } + ) + } + + return NextResponse.json(photo) + } catch (error) { + console.error('Error fetching photo:', error) + return NextResponse.json( + { error: 'Failed to fetch photo' }, + { status: 500 } + ) + } +} + +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params + const updates = await request.json() + + const photo = photoService.updatePhoto(id, updates) + + if (!photo) { + return NextResponse.json( + { error: 'Photo not found' }, + { status: 404 } + ) + } + + return NextResponse.json(photo) + } catch (error) { + console.error('Error updating photo:', error) + return NextResponse.json( + { error: 'Failed to update photo' }, + { status: 500 } + ) + } +} + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params + const success = photoService.deletePhoto(id) + + if (!success) { + return NextResponse.json( + { error: 'Photo not found' }, + { status: 404 } + ) + } + + return NextResponse.json({ success: true }) + } catch (error) { + console.error('Error deleting photo:', error) + return NextResponse.json( + { error: 'Failed to delete photo' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/photos/route.ts b/src/app/api/photos/route.ts new file mode 100644 index 0000000..61eacdc --- /dev/null +++ b/src/app/api/photos/route.ts @@ -0,0 +1,56 @@ +import { NextRequest, NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + + const options = { + directory: searchParams.get('directory') || undefined, + limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined, + offset: searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined, + sortBy: (searchParams.get('sortBy') as 'created_at' | 'modified_at' | 'filename' | 'filesize') || 'created_at', + sortOrder: (searchParams.get('sortOrder') as 'ASC' | 'DESC') || 'DESC', + favorite: searchParams.get('favorite') ? searchParams.get('favorite') === 'true' : undefined, + rating: searchParams.get('rating') ? parseInt(searchParams.get('rating')!) : undefined + } + + const photos = photoService.getPhotos(options) + + return NextResponse.json({ + photos, + count: photos.length + }) + } catch (error) { + console.error('Error fetching photos:', error) + return NextResponse.json( + { error: 'Failed to fetch photos' }, + { status: 500 } + ) + } +} + +export async function POST(request: NextRequest) { + try { + const photoData = await request.json() + + // Check if photo already exists + const existingPhoto = photoService.getPhotoByPath(photoData.filepath) + if (existingPhoto) { + return NextResponse.json( + { error: 'Photo already exists', photo: existingPhoto }, + { status: 409 } + ) + } + + const photo = photoService.createPhoto(photoData) + + return NextResponse.json(photo, { status: 201 }) + } catch (error) { + console.error('Error creating photo:', error) + return NextResponse.json( + { error: 'Failed to create photo' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts new file mode 100644 index 0000000..bec0792 --- /dev/null +++ b/src/app/api/stats/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server' +import { photoService } from '@/lib/photo-service' + +export async function GET() { + try { + const photoCount = photoService.getPhotoCount() + const totalSize = photoService.getTotalFileSize() + const directories = photoService.getDirectories() + const albums = photoService.getAlbums() + const tags = photoService.getTags() + + return NextResponse.json({ + photoCount, + totalSize, + directoryCount: directories.length, + albumCount: albums.length, + tagCount: tags.length, + directories: directories.slice(0, 5) // Latest 5 directories + }) + } catch (error) { + console.error('Error fetching stats:', error) + return NextResponse.json( + { error: 'Failed to fetch statistics' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/api/validate-directory/route.ts b/src/app/api/validate-directory/route.ts index afcd7b6..03d49e9 100644 --- a/src/app/api/validate-directory/route.ts +++ b/src/app/api/validate-directory/route.ts @@ -1,7 +1,56 @@ import { NextRequest, NextResponse } from 'next/server' import { existsSync, statSync } from 'fs' +import { glob } from 'glob' import path from 'path' +async function getSuggestions(inputPath: string): Promise { + try { + const trimmedInput = inputPath.trim() + if (!trimmedInput) return [] + + // Create glob pattern based on input + const globPattern = `${trimmedInput}*` + + console.log('Searching with pattern:', globPattern) + console.log('Input path exists:', existsSync(trimmedInput)) + + // Check if the base path exists first + if (trimmedInput.endsWith('/')) { + const basePath = trimmedInput.slice(0, -1) + console.log('Base path:', basePath, 'exists:', existsSync(basePath)) + } + + // Use glob to find matching directories + const matches = await glob(globPattern, { + withFileTypes: false, // Return strings not Dirent objects + absolute: true, // Return absolute paths + dot: true, // Include hidden directories + ignore: [], // Don't ignore any patterns + nodir: false // Include directories + }) + + console.log('Glob matches found:', matches.length, matches) + + // Filter to only include directories + const directories = matches.filter(match => { + try { + const isDir = statSync(match).isDirectory() + if (isDir) console.log('Directory found:', match) + return isDir + } catch (error) { + console.log('Error checking:', match, error) + return false + } + }) + + console.log('Final directories:', directories) + return directories.slice(0, 10) // Limit to 10 suggestions + } catch (error) { + console.error('getSuggestions error:', error) + return [] + } +} + export async function POST(request: NextRequest) { try { const { directory } = await request.json() @@ -9,39 +58,31 @@ export async function POST(request: NextRequest) { if (!directory || typeof directory !== 'string') { return NextResponse.json({ valid: false, - error: 'Directory path is required' + error: 'Directory path is required', + suggestions: [] }) } - const normalizedPath = path.resolve(directory.trim()) + const trimmedInput = directory.trim() - const exists = existsSync(normalizedPath) + // Always get suggestions using glob search + const suggestions = await getSuggestions(trimmedInput) - if (!exists) { - return NextResponse.json({ - valid: false, - error: 'Directory does not exist' - }) - } - - const stats = statSync(normalizedPath) - - if (!stats.isDirectory()) { - return NextResponse.json({ - valid: false, - error: 'Path is not a directory' - }) - } + // Check if the input exactly matches an existing directory + const normalizedPath = path.resolve(trimmedInput) + const isValid = existsSync(normalizedPath) && statSync(normalizedPath).isDirectory() return NextResponse.json({ - valid: true, - path: normalizedPath + valid: isValid, + path: isValid ? normalizedPath : undefined, + suggestions }) } catch (error) { return NextResponse.json({ valid: false, - error: 'Invalid directory path' + error: 'Invalid directory path', + suggestions: [] }, { status: 400 }) } } \ No newline at end of file diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 5a3b6f6..a1291e6 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -3,25 +3,39 @@ import { ButtonHTMLAttributes, ReactNode } from 'react' interface ButtonProps extends ButtonHTMLAttributes { variant?: 'primary' | 'secondary' children: ReactNode + enabled?: boolean } export default function Button({ variant = 'primary', children, className = '', + enabled = true, + disabled, ...props }: ButtonProps) { - const baseClasses = 'px-12 py-2 rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2' + // Determine if button should be disabled + const isDisabled = disabled || !enabled + + const baseClasses = 'px-12 py-2 rounded font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 transform' const variantClasses = { - primary: 'bg-blue-600 text-white hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed focus:ring-blue-500', - secondary: 'border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500' + primary: isDisabled + ? 'bg-gray-400 text-gray-200 cursor-not-allowed opacity-60' + : 'bg-blue-600 text-white hover:bg-blue-700 hover:scale-105 active:scale-95 focus:ring-blue-500 shadow-md hover:shadow-lg', + secondary: isDisabled + ? 'border border-gray-200 dark:border-gray-700 text-gray-400 dark:text-gray-600 bg-gray-100 dark:bg-gray-800 cursor-not-allowed opacity-60' + : 'border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 hover:scale-105 active:scale-95 focus:ring-gray-500 shadow-sm hover:shadow-md' } const finalClasses = `${baseClasses} ${variantClasses[variant]} ${className}` return ( - ) diff --git a/src/components/DirectoryList.tsx b/src/components/DirectoryList.tsx new file mode 100644 index 0000000..b58f64c --- /dev/null +++ b/src/components/DirectoryList.tsx @@ -0,0 +1,175 @@ +'use client' + +import { useState, useEffect } from 'react' +import { IconFolder, IconClock, IconTrash } from '@tabler/icons-react' +import { Directory } from '@/types/photo' +import Button from "@/components/Button"; + +interface DirectoryListProps { + onDirectorySelect?: (directory: Directory) => void + selectedDirectory?: string + refreshTrigger?: number // Add a trigger to force refresh +} + +export default function DirectoryList({ onDirectorySelect, selectedDirectory, refreshTrigger }: DirectoryListProps) { + const [directories, setDirectories] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const fetchDirectories = async () => { + try { + setLoading(true) + const response = await fetch('/api/directories') + + if (!response.ok) { + throw new Error('Failed to fetch directories') + } + + const data = await response.json() + setDirectories(data.directories || []) + setError(null) + } catch (error) { + console.error('Error fetching directories:', error) + setError('Failed to load directories') + } finally { + setLoading(false) + } + } + + const deleteDirectory = async (directoryId: string) => { + try { + const response = await fetch(`/api/directories/${directoryId}`, { + method: 'DELETE', + }) + + if (!response.ok) { + throw new Error('Failed to delete directory') + } + + // Refresh the list + fetchDirectories() + } catch (error) { + console.error('Error deleting directory:', error) + } + } + + useEffect(() => { + fetchDirectories() + }, [refreshTrigger]) + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + } + + const formatFileSize = (bytes: number) => { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + } + + if (loading) { + return ( +
+

+ Saved Directories +

+
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+
+ ) + } + + if (error) { + return ( +
+

+ Saved Directories +

+
+ {error} +
+
+ ) + } + + return ( +
+

+ Saved Directories +

+ + {directories.length === 0 ? ( +
+ No directories saved yet +
+ ) : ( +
+ {directories.map((directory) => ( +
onDirectorySelect?.(directory)} + > +
+
+ +
+
+ {directory.name} +
+
+ {directory.path} +
+ + +
+
+ + {formatDate(directory.last_scanned)} +
+ {directory.photo_count > 0 && ( +
+ {directory.photo_count} photos +
+ )} + {directory.total_size > 0 && ( +
+ {formatFileSize(directory.total_size)} +
+ )} +
+
+
+ + +
+
+ ))} +
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/components/DirectoryModal.tsx b/src/components/DirectoryModal.tsx index 0bdfd5a..b473467 100644 --- a/src/components/DirectoryModal.tsx +++ b/src/components/DirectoryModal.tsx @@ -9,9 +9,10 @@ interface DirectoryModalProps { isOpen: boolean onClose: () => void onSave: (directory: string) => void + onDirectoryListRefresh?: () => void } -export default function DirectoryModal({ isOpen, onClose, onSave }: DirectoryModalProps) { +export default function DirectoryModal({ isOpen, onClose, onSave, onDirectoryListRefresh }: DirectoryModalProps) { const [directory, setDirectory] = useState('') const [isValidating, setIsValidating] = useState(false) const [isValid, setIsValid] = useState(null) @@ -82,6 +83,13 @@ export default function DirectoryModal({ isOpen, onClose, onSave }: DirectoryMod } const handleKeyDown = (e: React.KeyboardEvent) => { + // Handle Tab key to hide suggestions if directory is valid + if (e.key === 'Tab' && directory.trim() && isValid) { + setShowSuggestions(false) + setSelectedSuggestionIndex(-1) + return // Allow default Tab behavior + } + if (!showSuggestions || suggestions.length === 0) return switch (e.key) { @@ -130,12 +138,32 @@ export default function DirectoryModal({ isOpen, onClose, onSave }: DirectoryMod console.log('Modal is rendering with isOpen:', isOpen) - const handleSave = () => { + const handleSave = async () => { if (directory.trim() && isValid) { - onSave(directory.trim()) - setDirectory('') - setIsValid(null) - onClose() + try { + // Save directory to database + await fetch('/api/directories', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ path: directory.trim() }), + }) + + onSave(directory.trim()) + onDirectoryListRefresh?.() // Trigger directory list refresh + setDirectory('') + setIsValid(null) + onClose() + } catch (error) { + console.error('Failed to save directory:', error) + // Still proceed with the save even if database save fails + onSave(directory.trim()) + onDirectoryListRefresh?.() // Trigger directory list refresh + setDirectory('') + setIsValid(null) + onClose() + } } } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 7ab214d..52db81c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,12 +1,13 @@ 'use client' import Button from './Button' +import {useState} from "react"; interface HeaderProps { - onScanDirectory: () => void + onSelectDirectory: () => void } -export default function Header({ onScanDirectory }: HeaderProps) { +export default function Header({ onSelectDirectory }: HeaderProps) { return (
@@ -19,11 +20,11 @@ export default function Header({ onScanDirectory }: HeaderProps) {
diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx index 2f702e7..c474e94 100644 --- a/src/components/MainLayout.tsx +++ b/src/components/MainLayout.tsx @@ -11,17 +11,22 @@ interface MainLayoutProps { export default function MainLayout({ children }: MainLayoutProps) { const [isModalOpen, setIsModalOpen] = useState(false) + const [directoryListRefreshTrigger, setDirectoryListRefreshTrigger] = useState(0) const handleDirectorySave = (directory: string) => { console.log('Directory to scan:', directory) } + const handleDirectoryListRefresh = () => { + setDirectoryListRefreshTrigger(prev => prev + 1) + } + console.log('Modal state:', isModalOpen) return (
-
setIsModalOpen(true)} /> +
setIsModalOpen(true)} />
- +
{children}
@@ -32,6 +37,7 @@ export default function MainLayout({ children }: MainLayoutProps) { isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} onSave={handleDirectorySave} + onDirectoryListRefresh={handleDirectoryListRefresh} /> )}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 1fadb41..d83326e 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,8 +1,29 @@ 'use client' -export default function Sidebar() { + +import DirectoryList from './DirectoryList' +import { Directory } from '@/types/photo' +import { useState } from 'react' + +interface SidebarProps { + refreshTrigger?: number +} + +export default function Sidebar({ refreshTrigger }: SidebarProps) { + const [selectedDirectory, setSelectedDirectory] = useState() + + const handleDirectorySelect = (directory: Directory) => { + setSelectedDirectory(directory.path) + // TODO: Implement photo loading for selected directory + console.log('Selected directory:', directory) + } + return ( -