diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1b8fc7a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist +npm-debug.log +.DS_Store diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index d44c86a..4cce521 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/Dockerfile b/Dockerfile index bcea430..e8cec5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,43 +5,19 @@ FROM node:22-bullseye AS builder ARG API_BASE_URL="https://navigolabs.com/api" -# Enable Corepack and Yarn 4 -RUN corepack enable && corepack prepare yarn@4.9.2 --activate - WORKDIR /app -# Copy Yarn 4 files -COPY package.json yarn.lock .yarnrc.yml ./ -COPY .yarn .yarn +# Copy package.json and package-lock.json +COPY package*.json ./ -# Install dependencies (immutable to match lockfile) -RUN yarn install --immutable +# Install ALL dependencies (including devDependencies) +RUN npm install -# Copy all source files +# Copy source files COPY . . # Pass API URL to Vite ENV VITE_API_BASE_URL=$API_BASE_URL # Build the app -RUN yarn build - - -# ------------------------ -# Production stage -# ------------------------ -FROM node:22-alpine AS production - -WORKDIR /app - -# Copy built files from builder stage -COPY --from=builder /app/dist ./dist - -# Install a lightweight static server -RUN npm install -g serve@14 - -# Expose port (optional but recommended) -EXPOSE 3000 - -# Start the static server -CMD ["serve", "-s", "dist", "-l", "3000"] +RUN npm run build diff --git a/index.html b/index.html index 6c54df8..f414cde 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,15 @@ - - - - Vite + React + TS - - -
- - - + + + + + The Kalawati + + + +
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0d9481d..44423f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,15 @@ "name": "thekalawati", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.2", + "@mui/material": "^7.3.2", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "react-dropzone": "^14.3.8", + "react-intersection-observer": "^9.16.0", + "react-router-dom": "^7.8.2" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -32,7 +39,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -88,7 +94,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", @@ -122,7 +127,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -132,7 +136,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -174,7 +177,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -184,7 +186,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -218,7 +219,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.4" @@ -262,11 +262,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -281,7 +289,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -300,7 +307,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -310,6 +316,158 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -962,7 +1120,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -984,7 +1141,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -994,20 +1150,263 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.30", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", + "integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.5.tgz", + "integrity": "sha512-LciL1GLMZ+VlzyHAALSVAR22t8IST4LCXmljcUSx2NOutgO2XnxdIp8ilFbeNf9wpo0iUFbAuoQcB7h+HHIf3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", + "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/core-downloads-tracker": "^7.3.5", + "@mui/system": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz", + "integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz", + "integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz", + "integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/private-theming": "^7.3.5", + "@mui/styled-engine": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz", + "integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz", + "integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/types": "^7.4.8", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1046,6 +1445,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.34", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz", @@ -1406,11 +1815,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1426,6 +1846,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.43.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", @@ -1929,6 +2358,15 @@ "node": ">= 0.4" } }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1945,6 +2383,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2063,7 +2536,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2107,6 +2579,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2141,6 +2622,40 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2160,7 +2675,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -2221,7 +2735,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2291,6 +2804,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2313,6 +2836,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -2546,7 +3078,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2840,6 +3371,18 @@ "node": ">=16.0.0" } }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2853,6 +3396,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2926,7 +3475,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3178,7 +3726,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3187,6 +3734,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3201,7 +3757,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3257,6 +3812,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -3327,7 +3888,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3666,7 +4226,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3686,7 +4245,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3702,6 +4260,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3769,6 +4333,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3796,7 +4366,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -3866,7 +4435,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3906,7 +4474,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4082,7 +4649,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -4091,6 +4657,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4115,14 +4699,21 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -4191,7 +4782,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -4251,11 +4841,42 @@ "react": "^19.1.1" } }, + "node_modules/react-dropzone": { + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "node_modules/react-intersection-observer": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", + "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -4268,6 +4889,60 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz", + "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.6" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4334,7 +5009,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -4487,6 +5161,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4635,6 +5315,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4770,6 +5459,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4787,7 +5482,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4870,6 +5564,12 @@ "typescript": ">=4.8.4" } }, + "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/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5287,6 +5987,21 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e6a9853..ae89934 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "@mui/material": "^7.3.2", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-intersection-observer": "^9.16.0" + "react-dropzone": "^14.3.8", + "react-intersection-observer": "^9.16.0", + "react-router-dom": "^7.8.2" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/src/App.tsx b/src/App.tsx index 1bb563a..2d1237d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,29 @@ -// import { useState } from 'react' -// import reactLogo from './assets/react.svg' -// import viteLogo from '/vite.svg' -import './App.css' -// import VintageComingSoon from './comingsoon/comingsoon' -import VintageComingSoonPage from './comingsoon/comingsoon' +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import "./App.css"; +// import VintageComingSoonPage from "./components/comingsoon/comingsoon"; +// import DarkProductShowcase from "./components/product/product"; +// import BlogPage from "./components/blogs/BlogPage"; +// import BlogCard from "./components/blogs/BlogCard"; +import WeddingGallery from "./WeddingGallery/WeddingGallery"; +// import OtherPage from "./pages/OtherPage"; // example if you add more pages function App() { - // const [count, setCount] = useState(0) - return ( + + + {/* Default route */} + {/* } /> */} + } /> - + {/* Example extra routes */} + {/* } /> + } /> */} + } /> + {/* } /> */} + + ); } -export default App +export default App; diff --git a/src/WeddingGallery/Gallery.tsx b/src/WeddingGallery/Gallery.tsx new file mode 100644 index 0000000..480d761 --- /dev/null +++ b/src/WeddingGallery/Gallery.tsx @@ -0,0 +1,43 @@ +// Gallery.tsx +import React from 'react'; +import SafeGrid from './SafeGrid'; +import { WeddingPhoto } from './types'; +import { Box, Card, CardContent, CardMedia, Chip, IconButton, Typography } from '@mui/material'; +import { Download } from '@mui/icons-material'; + +type Props = { + photos: WeddingPhoto[]; + onDownload?: (url: string, title?: string) => void; +}; + +const Gallery: React.FC = ({ photos, onDownload }) => { + return ( + + {photos.map((p) => ( + + + + + + onDownload?.(p.url, p.title)} sx={{ color: 'white', bgcolor: 'rgba(0,0,0,0.6)' }}> + + + + + + {p.title || 'Untitled'} + + + + {p.created_at ? new Date(p.created_at).toLocaleDateString() : p.date ? new Date(p.date).toLocaleDateString() : ''} + + + + + + ))} + + ); +}; + +export default Gallery; diff --git a/src/WeddingGallery/LightboxModal.tsx b/src/WeddingGallery/LightboxModal.tsx new file mode 100644 index 0000000..8b0dd39 --- /dev/null +++ b/src/WeddingGallery/LightboxModal.tsx @@ -0,0 +1,592 @@ +// LightboxModal.tsx +import React, { useEffect, useRef, useState } from 'react'; +import { + Dialog, + IconButton, + Box, + Typography, + Tooltip, + CircularProgress, +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; +import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; +import DownloadIcon from '@mui/icons-material/Download'; +// import ZoomOutMapIcon from '@mui/icons-material/ZoomOutMap'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; +import FitScreenIcon from '@mui/icons-material/FitScreen'; + +export type LightboxPhoto = { + id: number | string; + url: string; + title?: string; + filename?: string; + created_at?: string; + naturalWidth?: number; + naturalHeight?: number; +}; + +type Props = { + open: boolean; + photos: LightboxPhoto[]; + startIndex?: number; + onClose: () => void; + startIndexCallback?: (idx: number) => void; +}; + +const SWIPE_THRESHOLD = 40; +const MIN_SCALE = 0.1; +const MAX_SCALE = 5; +const SCALE_STEP = 0.25; + +export default function LightboxModal({ + open, + photos, + startIndex = 0, + onClose, + startIndexCallback, +}: Props) { + const [index, setIndex] = useState(startIndex); + const [scale, setScale] = useState(1); + const [translate, setTranslate] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); + const [isPanning, setIsPanning] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null); + const panStart = useRef<{ x: number; y: number } | null>(null); + const lastTranslate = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + + const touchStartX = useRef(null); + const touchDeltaX = useRef(0); + + // For pinch + const pinchStartDist = useRef(null); + const pinchStartScale = useRef(1); + const imageRef = useRef(null); + + useEffect(() => setIndex(startIndex), [startIndex, open]); + + useEffect(() => { + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, index, scale]); + + useEffect(() => startIndexCallback?.(index), [index]); + + useEffect(() => { + // reset scale/translate when changing image or closing/opening + setScale(1); + setTranslate({ x: 0, y: 0 }); + lastTranslate.current = { x: 0, y: 0 }; + setIsLoading(true); + setImageDimensions(null); + }, [index, open]); + + const onKey = (e: KeyboardEvent) => { + if (!open) return; + if (e.key === 'Escape') onClose(); + if (e.key === 'ArrowLeft') prev(); + if (e.key === 'ArrowRight') next(); + if (e.key === '+' || (e.ctrlKey && e.key === '=')) zoomIn(); + if (e.key === '-') zoomOut(); + if (e.key === '0') fitToScreen(); + }; + + const prev = () => setIndex((i) => (i - 1 + photos.length) % photos.length); + const next = () => setIndex((i) => (i + 1) % photos.length); + + // Preload neighbors + useEffect(() => { + if (!photos?.length) return; + const a = new Image(); + a.src = photos[(index - 1 + photos.length) % photos.length]?.url; + const b = new Image(); + b.src = photos[(index + 1) % photos.length]?.url; + }, [index, photos]); + + const photo = photos && photos.length ? photos[index] : null; + + // Handle image load + const handleImageLoad = (e: React.SyntheticEvent) => { + setIsLoading(false); + const img = e.currentTarget; + setImageDimensions({ + width: img.naturalWidth, + height: img.naturalHeight + }); + }; + + const handleImageError = () => { + setIsLoading(false); + setImageDimensions(null); + }; + + if (!photo) return null; + + /* ============================ + Zoom / Pan Controls + ============================ */ + const setScaleClamped = (s: number) => { + const clamped = Math.max(MIN_SCALE, Math.min(MAX_SCALE, s)); + setScale(clamped); + if (clamped === 1) { + // reset translate when fully fit + setTranslate({ x: 0, y: 0 }); + lastTranslate.current = { x: 0, y: 0 }; + } + }; + + const zoomIn = () => setScaleClamped(scale + SCALE_STEP); + const zoomOut = () => setScaleClamped(scale - SCALE_STEP); + const fitToScreen = () => setScaleClamped(1); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const handleDoubleClick = (_e: React.MouseEvent) => { + if (scale === 1) setScaleClamped(2); + else setScaleClamped(1); + }; + + /* ============================ + Panning (pointer / touch) + ============================ */ + const onPointerDown = (e: React.PointerEvent) => { + if (scale <= 1) return; + (e.target as Element).setPointerCapture?.(e.pointerId); + setIsPanning(true); + panStart.current = { x: e.clientX, y: e.clientY }; + }; + + const onPointerMove = (e: React.PointerEvent) => { + if (!isPanning || !panStart.current) return; + const dx = e.clientX - panStart.current.x; + const dy = e.clientY - panStart.current.y; + const TX = lastTranslate.current.x + dx; + const TY = lastTranslate.current.y + dy; + setTranslate({ x: TX, y: TY }); + }; + + const onPointerUp = (e: React.PointerEvent) => { + if (!isPanning) return; + setIsPanning(false); + if (panStart.current) { + const dx = e.clientX - panStart.current.x; + const dy = e.clientY - panStart.current.y; + lastTranslate.current = { x: lastTranslate.current.x + dx, y: lastTranslate.current.y + dy }; + panStart.current = null; + } + try { + (e.target as Element).releasePointerCapture?.(e.pointerId); + } catch { + //ignore + } + }; + + const onWheel = (e: React.WheelEvent) => { + if (!open) return; + e.preventDefault(); + const delta = -e.deltaY; + const step = delta > 0 ? SCALE_STEP : -SCALE_STEP; + setScaleClamped(scale + step); + }; + + /* ============================ + Touch handlers for swipe + pinch + Note: use React.Touch types here to satisfy TS + ============================ */ + const onTouchStart = (e: React.TouchEvent) => { + touchDeltaX.current = 0; + if (e.touches.length === 1) { + touchStartX.current = e.touches[0].clientX; + if (scale > 1) { + panStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY }; + setIsPanning(true); + } + } else if (e.touches.length === 2) { + pinchStartDist.current = getDistance(e.touches[0], e.touches[1]); + pinchStartScale.current = scale; + setIsPanning(false); + } + }; + + const onTouchMove = (e: React.TouchEvent) => { + if (e.touches.length === 1 && scale > 1 && isPanning && panStart.current) { + const dx = e.touches[0].clientX - panStart.current.x; + const dy = e.touches[0].clientY - panStart.current.y; + setTranslate({ x: lastTranslate.current.x + dx, y: lastTranslate.current.y + dy }); + e.preventDefault(); + } else if (e.touches.length === 2 && pinchStartDist.current != null) { + const dist = getDistance(e.touches[0], e.touches[1]); + const ratio = dist / (pinchStartDist.current || dist); + setScaleClamped(pinchStartScale.current * ratio); + e.preventDefault(); + } else if (e.touches.length === 1) { + const x = e.touches[0].clientX; + touchDeltaX.current = x - (touchStartX.current ?? x); + } + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const onTouchEnd = (_e: React.TouchEvent) => { + if (isPanning) { + setIsPanning(false); + lastTranslate.current = { ...translate }; + panStart.current = null; + } + + if (pinchStartDist.current != null) { + pinchStartDist.current = null; + pinchStartScale.current = scale; + } + + if (Math.abs(touchDeltaX.current) > SWIPE_THRESHOLD && Math.abs(translate.x) < 10 && !isPanning) { + if (touchDeltaX.current < 0) next(); + else prev(); + } + + touchDeltaX.current = 0; + touchStartX.current = null; + }; + + // Helper accepting React.Touch (TS-friendly) + function getDistance(a: React.Touch, b: React.Touch) { + const dx = a.clientX - b.clientX; + const dy = a.clientY - b.clientY; + return Math.sqrt(dx * dx + dy * dy); + } + + // Calculate image display size based on original dimensions + const getImageDisplayStyle = () => { + if (!imageDimensions) return {}; + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + const { width: imgWidth, height: imgHeight } = imageDimensions; + + // Calculate the maximum dimensions to fit in the viewport + const maxWidth = windowWidth * 0.9; // 90% of window width + const maxHeight = windowHeight * 0.8; // 80% of window height + + let displayWidth = imgWidth; + let displayHeight = imgHeight; + + // If image is larger than max dimensions, scale it down + if (imgWidth > maxWidth || imgHeight > maxHeight) { + const widthRatio = maxWidth / imgWidth; + const heightRatio = maxHeight / imgHeight; + const ratio = Math.min(widthRatio, heightRatio); + + displayWidth = imgWidth * ratio; + displayHeight = imgHeight * ratio; + } + + return { + width: `${displayWidth}px`, + height: `${displayHeight}px`, + maxWidth: '90vw', + maxHeight: '80vh', + }; + }; + + /* ============================ + Render + ============================ */ + return ( + + { + if (e.target === e.currentTarget) onClose(); + }} + > + {/* Loading indicator */} + {isLoading && ( + + + + )} + + {/* Top right controls */} + + + { + ev.stopPropagation(); + zoomOut(); + }} + sx={{ + bgcolor: 'rgba(255,255,255,0.1)', + color: 'white', + '&:hover': { bgcolor: 'rgba(255,255,255,0.2)' } + }} + > + + + + + + { + ev.stopPropagation(); + fitToScreen(); + }} + sx={{ + bgcolor: 'rgba(255,255,255,0.1)', + color: 'white', + '&:hover': { bgcolor: 'rgba(255,255,255,0.2)' } + }} + > + + + + + + { + ev.stopPropagation(); + zoomIn(); + }} + sx={{ + bgcolor: 'rgba(255,255,255,0.1)', + color: 'white', + '&:hover': { bgcolor: 'rgba(255,255,255,0.2)' } + }} + > + + + + + + { + ev.stopPropagation(); + try { + const a = document.createElement('a'); + a.href = photo.url; + a.download = `${photo.filename ?? photo.title ?? 'image'}`; + document.body.appendChild(a); + a.click(); + a.remove(); + } catch (err) { + console.error('download', err); + } + }} + sx={{ + bgcolor: 'rgba(255,255,255,0.1)', + color: 'white', + '&:hover': { bgcolor: 'rgba(255,255,255,0.2)' } + }} + > + + + + + + { + ev.stopPropagation(); + onClose(); + }} + sx={{ + bgcolor: 'rgba(255,255,255,0.1)', + color: 'white', + '&:hover': { bgcolor: 'rgba(255,255,255,0.2)' } + }} + > + + + + + + {/* Prev / Next arrows */} + { + ev.stopPropagation(); + prev(); + }} + sx={{ + position: 'absolute', + left: 16, + zIndex: 50, + color: 'white', + bgcolor: 'rgba(0,0,0,0.5)', + backdropFilter: 'blur(10px)', + '&:hover': { bgcolor: 'rgba(0,0,0,0.7)' } + }} + > + + + + { + ev.stopPropagation(); + next(); + }} + sx={{ + position: 'absolute', + right: 16, + zIndex: 50, + color: 'white', + bgcolor: 'rgba(0,0,0,0.5)', + backdropFilter: 'blur(10px)', + '&:hover': { bgcolor: 'rgba(0,0,0,0.7)' } + }} + > + + + + {/* Image container */} + + + {photo.title 1 ? (isPanning ? 'grabbing' : 'grab') : 'default', + borderRadius: 4, + boxShadow: '0 10px 30px rgba(0,0,0,0.6)', + touchAction: 'none', + ...getImageDisplayStyle(), + }} + draggable={false} + /> + + + + {/* Caption */} + + + {photo.title ?? photo.filename} + + + {index + 1} of {photos.length} + {photo.created_at && ` • ${new Date(photo.created_at).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + })}`} + {imageDimensions && ` • ${imageDimensions.width}×${imageDimensions.height}`} + + + + {/* Zoom level indicator */} + {scale !== 1 && ( + + {Math.round(scale * 100)}% + + )} + + + ); +} \ No newline at end of file diff --git a/src/WeddingGallery/PreviewTester.tsx b/src/WeddingGallery/PreviewTester.tsx new file mode 100644 index 0000000..44b55ee --- /dev/null +++ b/src/WeddingGallery/PreviewTester.tsx @@ -0,0 +1,60 @@ +// PreviewTester.tsx +import React, { useCallback, useEffect, useState } from 'react'; +import { Box, Button, Card, CardMedia, CardContent, Typography } from '@mui/material'; +import { useDropzone } from 'react-dropzone'; + +type Item = { id: string; file: File; previewUrl: string }; + +export default function PreviewTester() { + const [items, setItems] = useState([]); + + const onDrop = useCallback((acceptedFiles: File[]) => { + console.log('onDrop called, acceptedFiles:', acceptedFiles); + const now = Date.now(); + const newItems = acceptedFiles.map((f, i) => ({ + id: `${now}-${i}-${f.name}`, + file: f, + previewUrl: URL.createObjectURL(f), + })); + setItems((prev) => [...newItems, ...prev]); + }, []); + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, multiple: true, accept: { 'image/*': [] } }); + + useEffect(() => { + return () => { + items.forEach((it) => { + try { + URL.revokeObjectURL(it.previewUrl); + } catch { + // ignore + } + }); + }; + }, [items]); + + + return ( + + + + {isDragActive ? 'Drop images here' : 'Drag & drop images (PreviewTester)'} + + + + {items.map((it) => ( + + + + {it.file.name} + + + ))} + + + + + + + ); +} diff --git a/src/WeddingGallery/SafeGrid.tsx b/src/WeddingGallery/SafeGrid.tsx new file mode 100644 index 0000000..18b4b79 --- /dev/null +++ b/src/WeddingGallery/SafeGrid.tsx @@ -0,0 +1,5 @@ +// SafeGrid.tsx +import Grid from '@mui/material/Grid'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const SafeGrid = Grid as any; +export default SafeGrid; diff --git a/src/WeddingGallery/UploadQueue.tsx b/src/WeddingGallery/UploadQueue.tsx new file mode 100644 index 0000000..3da9481 --- /dev/null +++ b/src/WeddingGallery/UploadQueue.tsx @@ -0,0 +1,98 @@ +// UploadQueue.tsx +import React, { useCallback } from 'react'; +import { Box, Button, Card, CardContent, CardMedia, IconButton, LinearProgress, Typography } from '@mui/material'; +import { Delete, Replay } from '@mui/icons-material'; +import { FileQueueItem, WeddingPhoto } from './types'; +import { uploadFileXHR, toAbsoluteUrl } from './apis'; + +type Props = { + queue: FileQueueItem[]; + setQueue: React.Dispatch>; + newPhotoTitle?: string; + onUploaded?: (photo: WeddingPhoto) => void; +}; + +const UploadQueue: React.FC = ({ queue, setQueue, newPhotoTitle, onUploaded }) => { + const uploadSingle = useCallback( + async (item: FileQueueItem) => { + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'uploading', progress: 0, error: undefined } : q))); + const res = await uploadFileXHR( + item.file, + (pct) => setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, progress: pct } : q))), + newPhotoTitle + ); + if (res.success) { + const it = res.data; + const uploadedPhoto: WeddingPhoto = { + id: it.id ?? it._id, + url: toAbsoluteUrl(it.path ?? it.url), + title: it.title ?? it.filename ?? item.file.name, + date: it.created_at ?? it.createdAt, + filename: it.filename, + path: it.path, + created_at: it.created_at ?? it.createdAt, + }; + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'done', progress: 100, uploadedPhoto } : q))); + onUploaded?.(uploadedPhoto); + return true; + } else { + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'error', error: res.error } : q))); + return false; + } + }, + [newPhotoTitle, setQueue, onUploaded] + ); + + const uploadAll = useCallback(async () => { + for (const item of queue.filter((q) => q.status === 'pending' || q.status === 'error')) { + + await uploadSingle(item); + } + }, [queue, uploadSingle]); + + const retryOne = (id: string) => { + const item = queue.find((q) => q.id === id); + if (item) uploadSingle(item); + }; + + const removeOne = (id: string) => { + const item = queue.find((q) => q.id === id); + if (item) URL.revokeObjectURL(item.previewUrl); + setQueue((prev) => prev.filter((q) => q.id !== id)); + }; + + return ( + + + + + + + {queue.map((q) => ( + + + + + {q.file.name} + + + {q.status === 'uploading' && Uploading — {q.progress}%} + {q.status === 'pending' && Pending} + {q.status === 'done' && Uploaded} + {q.status === 'error' && {q.error ?? 'Upload failed'}} + + + {q.status === 'error' && retryOne(q.id)}>} + removeOne(q.id)}> + + + + ))} + + + ); +}; + +export default UploadQueue; diff --git a/src/WeddingGallery/WeddingGallery.tsx b/src/WeddingGallery/WeddingGallery.tsx new file mode 100644 index 0000000..03d3a2e --- /dev/null +++ b/src/WeddingGallery/WeddingGallery.tsx @@ -0,0 +1,1544 @@ +// WeddingGallery.tsx +import React, { JSX, useCallback, useEffect, useState } from 'react'; +import { + Alert, + Box, + Button, + Card, + CardContent, + CardMedia, + Chip, + CircularProgress, + Container, + CssBaseline, + Grid, + IconButton, + LinearProgress, + Paper, + Snackbar, + TextField, + ThemeProvider, + Toolbar, + Typography, + createTheme, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + alpha, + styled, + AppBar, + Tabs, + Tab, +} from '@mui/material'; +import { + // CameraAlt, + CloudUpload, + Delete as DeleteIcon, + Download, + Replay, + PhotoAlbum, + Celebration, + Diamond, + AddPhotoAlternate, + FolderOpen, + LocalFlorist, + Favorite, + Event, + People, + LocationOn, + // DateRange, + Timeline, + ChevronRight, + PhotoCamera, + Videocam, +} from '@mui/icons-material'; +import { useDropzone } from 'react-dropzone'; +import LightboxModal from './LightboxModal'; + +/* ============================ + Types + ============================ */ +type WeddingPhoto = { + id: number | string; + url: string; + title?: string; + filename?: string; + created_at?: string; + date?: string; + path?: string; +}; + +type FileQueueItem = { + id: string; + file: File; + previewUrl: string; + progress: number; + status: 'pending' | 'uploading' | 'done' | 'error'; + error?: string; + uploadedPhoto?: WeddingPhoto; +}; + +type FolderInfo = { + title: string; + count: number; + thumb?: string; + date?: string; + description?: string; +}; + +type StoryStep = { + title: string; + description: string; + icon: React.ReactNode; + photos?: WeddingPhoto[]; +}; + +/* ============================ + Config + ============================ */ +const MFS_API_BASE = 'https://mfs-api.midastix.com'; +const MAX_FILE_SIZE = 10 * 1024 * 1024; +const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; +const MAX_QUEUE_FILES = 30; + +/* ============================ + Styled Components + ============================ */ +const FloralBackground = styled(Box)(() => ({ + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundImage: ` + radial-gradient(circle at 20% 80%, ${alpha('#ffebee', 0.1)} 0%, transparent 20%), + radial-gradient(circle at 80% 20%, ${alpha('#e8f5e9', 0.1)} 0%, transparent 20%), + radial-gradient(circle at 40% 40%, ${alpha('#f3e5f5', 0.1)} 0%, transparent 30%), + radial-gradient(circle at 60% 60%, ${alpha('#e3f2fd', 0.1)} 0%, transparent 25%) + `, + pointerEvents: 'none', + zIndex: -1, +})); + +const GoldButton = styled(Button)(() => ({ + background: 'linear-gradient(135deg, #d4af37 0%, #ffd700 50%, #d4af37 100%)', + color: '#2c1810', + fontWeight: 600, + padding: '12px 32px', + borderRadius: '50px', + textTransform: 'none', + letterSpacing: '0.5px', + transition: 'all 0.3s ease', + boxShadow: '0 4px 20px rgba(212, 175, 55, 0.3)', + '&:hover': { + transform: 'translateY(-2px)', + boxShadow: '0 8px 25px rgba(212, 175, 55, 0.4)', + }, + '&:disabled': { + background: 'linear-gradient(135deg, #cccccc 0%, #aaaaaa 100%)', + boxShadow: 'none', + }, +})); + +const DropzoneArea = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(4), + border: `2px dashed ${alpha('#d4af37', 0.4)}`, + borderRadius: '20px', + background: `linear-gradient(135deg, ${alpha('#fffaf0', 0.9)} 0%, ${alpha('#fff8e1', 0.9)} 100%)`, + transition: 'all 0.3s ease', + cursor: 'pointer', + textAlign: 'center', + '&:hover': { + borderColor: '#d4af37', + background: `linear-gradient(135deg, ${alpha('#fffaf0', 1)} 0%, ${alpha('#fff8e1', 1)} 100%)`, + transform: 'translateY(-2px)', + boxShadow: '0 12px 24px rgba(212, 175, 55, 0.15)', + }, +})); + +const AlbumCard = styled(Card)(() => ({ + position: 'relative', + borderRadius: '16px', + overflow: 'hidden', + transition: 'all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)', + background: 'linear-gradient(145deg, #ffffff 0%, #fafafa 100%)', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.08)', + border: '1px solid rgba(255, 255, 255, 0.3)', + height: '100%', + '&:hover': { + transform: 'translateY(-8px)', + boxShadow: '0 20px 40px rgba(0, 0, 0, 0.15)', + '& .album-overlay': { + opacity: 1, + }, + }, + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: '4px', + background: 'linear-gradient(90deg, #d4af37 0%, #ffd700 50%, #d4af37 100%)', + zIndex: 1, + }, +})); + +const AlbumOverlay = styled(Box)(({ theme }) => ({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'linear-gradient(0deg, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0) 100%)', + opacity: 0, + transition: 'opacity 0.3s ease', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + padding: theme.spacing(3), + color: 'white', +})); + +const PhotoCard = styled(Card)(() => ({ + position: 'relative', + borderRadius: '12px', + overflow: 'hidden', + transition: 'all 0.3s ease', + '&:hover': { + transform: 'scale(1.03)', + boxShadow: '0 12px 28px rgba(0, 0, 0, 0.2)', + '& .photo-actions': { + opacity: 1, + }, + }, +})); + +const PhotoActions = styled(Box)(({ theme }) => ({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + background: 'linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 100%)', + padding: theme.spacing(1), + opacity: 0, + transition: 'opacity 0.3s ease', + display: 'flex', + justifyContent: 'flex-end', + gap: theme.spacing(1), +})); + +const FloatingActionButton = styled(IconButton)(() => ({ + + position: 'fixed', + bottom: 24, + right: 24, + background: 'linear-gradient(135deg, #d4af37 0%, #ffd700 100%)', + color: '#2c1810', + width: 56, + height: 56, + boxShadow: '0 8px 25px rgba(212, 175, 55, 0.4)', + zIndex: 1000, + '&:hover': { + background: 'linear-gradient(135deg, #c19b2a 0%, #e6c200 100%)', + transform: 'scale(1.1)', + }, +})); + +const StoryStepCard = styled(Paper)(({ theme }) => ({ + position: 'relative', + padding: theme.spacing(3), + borderRadius: '16px', + background: 'rgba(255, 255, 255, 0.95)', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.08)', + border: '1px solid rgba(255, 255, 255, 0.3)', + '&:hover': { + transform: 'translateY(-4px)', + boxShadow: '0 16px 40px rgba(0, 0, 0, 0.12)', + }, +})); + +const WeddingHeader = styled(AppBar)(() => ({ + background: 'linear-gradient(135deg, rgba(15, 118, 110, 0.95) 0%, rgba(45, 212, 191, 0.9) 100%)', + backdropFilter: 'blur(10px)', + boxShadow: '0 4px 30px rgba(0, 0, 0, 0.1)', + borderBottom: '2px solid rgba(212, 175, 55, 0.3)', +})); + +/* ============================ + Helper Functions + ============================ */ +const toAbsoluteUrl = (path?: string) => { + if (!path) return ''; + if (path.startsWith('http://') || path.startsWith('https://')) return path; + return `${MFS_API_BASE}${path}`; +}; + +const uploadFileXHR = ( + file: File, + onProgress: (pct: number) => void, + title?: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise<{ success: boolean; data?: any; error?: string }> => + new Promise((resolve) => { + try { + const xhr = new XMLHttpRequest(); + const url = `${MFS_API_BASE}/image-upload/single`; + xhr.open('POST', url, true); + xhr.setRequestHeader('Accept', 'application/json'); + + xhr.upload.onprogress = (ev) => { + if (!ev.lengthComputable) return; + const pct = Math.round((ev.loaded / ev.total) * 100); + onProgress(pct); + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + try { + const json = JSON.parse(xhr.responseText); + resolve({ success: true, data: json }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + resolve({ success: true, data: xhr.responseText }); + } + } else { + const txt = xhr.responseText || `HTTP ${xhr.status}`; + resolve({ success: false, error: txt }); + } + }; + + xhr.onerror = () => resolve({ success: false, error: 'Network error' }); + + const fd = new FormData(); + fd.append('file', file, file.name); + if (title) fd.append('title', title); + + xhr.send(fd); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + resolve({ success: false, error: err?.message ?? String(err) }); + } + }); + +const fetchImagesApi = async (title = ''): Promise => { + const url = + title && title.trim() !== '' ? `${MFS_API_BASE}/image-upload?title=${encodeURIComponent(title)}` : `${MFS_API_BASE}/image-upload`; + const res = await fetch(url, { headers: { accept: 'application/json' } }); + if (!res.ok) { + const txt = await res.text(); + throw new Error(txt || `HTTP ${res.status}`); + } + const body = await res.json(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let imagesArray: any[] = []; + if (Array.isArray(body)) imagesArray = body; + else if (Array.isArray(body.images)) imagesArray = body.images; + else if (body.data && Array.isArray(body.data.images)) imagesArray = body.data.images; + else { + const firstArray = Object.values(body).find((v) => Array.isArray(v)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (Array.isArray(firstArray)) imagesArray = firstArray as any[]; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return imagesArray.map((it: any) => ({ + id: it.id ?? it._id ?? `${it.filename ?? Math.random()}`, + url: toAbsoluteUrl(it.path ?? it.url), + title: it.title ?? it.filename ?? '', + filename: it.filename, + created_at: it.created_at ?? it.createdAt, + date: it.date, + path: it.path, + })); +}; + +const deletePhotoApi = async (id: number | string) => { + const url = `${MFS_API_BASE}/image-upload/id/${id}`; + const res = await fetch(url, { method: 'DELETE', headers: { accept: 'application/json' } }); + const txt = await res.text(); + if (!res.ok) throw new Error(txt || `HTTP ${res.status}`); + return txt || 'deleted'; +}; + +const deleteFolderApi = async (title: string) => { + const url = `${MFS_API_BASE}/image-upload/folder?title=${encodeURIComponent(title)}`; + const res = await fetch(url, { method: 'DELETE', headers: { accept: 'application/json' } }); + const txt = await res.text(); + if (!res.ok) throw new Error(txt || `HTTP ${res.status}`); + return txt || 'folder deleted'; +}; + +/* ============================ + Theme + ============================ */ +const theme = createTheme({ + palette: { + primary: { main: '#0f766e', light: '#2dd4bf' }, + secondary: { main: '#d4af37', light: '#ffd700' }, + background: { + default: '#fefefe', + paper: '#fff' + }, + text: { + primary: '#2c1810', + secondary: '#6b7280' + }, + }, + typography: { + fontFamily: '"Poppins", "Inter", -apple-system, BlinkMacSystemFont, sans-serif', + h1: { + fontFamily: '"Great Vibes", cursive', + fontSize: '3.5rem', + fontWeight: 400, + letterSpacing: '1px', + }, + h2: { + fontFamily: '"Great Vibes", cursive', + fontSize: '2.8rem', + fontWeight: 400, + }, + h3: { + fontFamily: '"Playfair Display", serif', + fontWeight: 600, + }, + h4: { + fontFamily: '"Playfair Display", serif', + fontWeight: 500, + }, + h5: { + fontFamily: '"Poppins", sans-serif', + fontWeight: 500, + }, + h6: { + fontFamily: '"Poppins", sans-serif', + fontWeight: 500, + }, + body1: { + fontFamily: '"Inter", sans-serif', + lineHeight: 1.6, + }, + body2: { + fontFamily: '"Inter", sans-serif', + lineHeight: 1.5, + }, + }, + shape: { + borderRadius: 16, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + borderRadius: 12, + textTransform: 'none', + fontWeight: 500, + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + borderRadius: 16, + }, + }, + }, + MuiCard: { + styleOverrides: { + root: { + borderRadius: 16, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: 'none', + fontWeight: 500, + fontSize: '1rem', + }, + }, + }, + }, +}); + +/* ============================ + Component + ============================ */ +export default function WeddingGallery(): JSX.Element { + const [photosAll, setPhotosAll] = useState([]); + const [folders, setFolders] = useState([]); + const [folderOpenTitle, setFolderOpenTitle] = useState(null); + const [folderPhotos, setFolderPhotos] = useState([]); + const [folderModalOpen, setFolderModalOpen] = useState(false); + + const [queue, setQueue] = useState([]); + const [commonTitle, setCommonTitle] = useState(''); + const [snackbar, setSnackbar] = useState<{ open: boolean; severity?: 'success' | 'error' | 'info'; message: string }>({ + open: false, + severity: 'info', + message: '', + }); + + const [deletingId, setDeletingId] = useState(null); + const [deletingFolder, setDeletingFolder] = useState(null); + + const [lightboxOpen, setLightboxOpen] = useState(false); + const [lightboxStartIndex, setLightboxStartIndex] = useState(0); + const [showUploadPanel, setShowUploadPanel] = useState(false); + const [activeTab, setActiveTab] = useState(0); + + // Storyline steps + const [storySteps] = useState([ + { + title: 'The Proposal', + description: 'The magical moment when everything began', + icon: , + }, + { + title: 'Engagement Shoot', + description: 'Capturing our love and excitement', + icon: , + }, + { + title: 'Wedding Planning', + description: 'Preparing for our dream day', + icon: , + }, + { + title: 'The Ceremony', + description: 'Saying "I Do" surrounded by loved ones', + icon: , + }, + { + title: 'Reception', + description: 'Celebrating with family and friends', + icon: , + }, + { + title: 'Honeymoon', + description: 'Beginning our journey together', + icon: , + }, + ]); + + const loadAll = useCallback(async () => { + try { + const items = await fetchImagesApi(''); + setPhotosAll(items); + + // Group photos by title/album + const map = new Map(); + items.forEach((it) => { + const title = it.title ?? 'Untitled'; + if (!map.has(title)) { + map.set(title, { + title, + count: 0, + thumb: it.url, + date: it.date || it.created_at, + }); + } + const entry = map.get(title)!; + entry.count += 1; + if (!entry.thumb) entry.thumb = it.url; + }); + + const folderArray = Array.from(map.values()).sort((a, b) => b.count - a.count); + + // Add sample descriptions for demo + const sampleDescriptions = [ + 'Beautiful moments from our special day', + 'Cherished memories with family and friends', + 'Every glance, every smile, every tear of joy', + 'The beginning of our forever journey', + 'Love, laughter, and happily ever after', + ]; + + folderArray.forEach((folder, index) => { + folder.description = sampleDescriptions[index % sampleDescriptions.length]; + }); + + setFolders(folderArray); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + console.error('loadAll error', err); + setFolders([]); + setSnackbar({ open: true, severity: 'error', message: `Failed to load: ${err?.message || err}` }); + } + }, []); + + useEffect(() => { + loadAll(); + return () => { + queue.forEach((q) => { + try { + URL.revokeObjectURL(q.previewUrl); + } catch { + //ignore + } + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /* ============================ + Dropzone + ============================ */ + const onDrop = useCallback((acceptedFiles: File[]) => { + if (!acceptedFiles || acceptedFiles.length === 0) return; + + const currentCount = queue.length; + const available = Math.max(0, MAX_QUEUE_FILES - currentCount); + + if (available <= 0) { + setSnackbar({ open: true, severity: 'error', message: `Maximum ${MAX_QUEUE_FILES} files allowed.` }); + return; + } + + const acceptedToAdd = acceptedFiles.slice(0, available); + const rejectedCount = acceptedFiles.length - acceptedToAdd.length; + + const now = Date.now(); + const items: FileQueueItem[] = acceptedToAdd.map((f, i) => { + const id = `${now}-${currentCount + i}-${f.name}`; + const tooLarge = f.size > MAX_FILE_SIZE; + const invalidType = ALLOWED_TYPES.length > 0 && !ALLOWED_TYPES.includes(f.type); + return { + id, + file: f, + previewUrl: URL.createObjectURL(f), + progress: 0, + status: tooLarge || invalidType ? 'error' : 'pending', + error: tooLarge ? `File too large ${(f.size / (1024 * 1024)).toFixed(1)}MB` : invalidType ? 'Invalid type' : undefined, + } as FileQueueItem; + }); + + setQueue((prev) => [...items, ...prev]); + setShowUploadPanel(true); + + if (rejectedCount > 0) { + setSnackbar({ + open: true, + severity: 'info', + message: `Added ${acceptedToAdd.length} file(s). ${rejectedCount} file(s) were not added.`, + }); + } else { + setSnackbar({ open: true, severity: 'success', message: `Added ${acceptedToAdd.length} file(s)` }); + } + }, [queue.length]); + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + multiple: true, + maxSize: MAX_FILE_SIZE, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + accept: ALLOWED_TYPES.length ? ALLOWED_TYPES.reduce((acc, t) => ({ ...acc, [t]: [] }), {} as any) : undefined, + }); + + /* ============================ + Upload logic + ============================ */ + const uploadSingle = useCallback( + async (item: FileQueueItem) => { + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'uploading', progress: 0, error: undefined } : q))); + const res = await uploadFileXHR(item.file, (pct) => { + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, progress: pct } : q))); + }, commonTitle || undefined); + + if (res.success) { + const it = res.data; + const uploadedPhoto: WeddingPhoto = { + id: it.id ?? it._id, + url: toAbsoluteUrl(it.path ?? it.url), + title: it.title ?? it.filename ?? commonTitle ?? 'Untitled', + filename: it.filename, + created_at: it.created_at ?? it.createdAt, + path: it.path, + }; + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'done', progress: 100, uploadedPhoto } : q))); + setPhotosAll((prev) => [uploadedPhoto, ...prev]); + await loadAll(); + return true; + } else { + setQueue((prev) => prev.map((q) => (q.id === item.id ? { ...q, status: 'error', error: res.error } : q))); + console.error('upload failed', res.error); + return false; + } + }, + [commonTitle, loadAll] + ); + + const uploadAll = useCallback(async () => { + const pending = queue.filter((q) => q.status === 'pending' || q.status === 'error'); + if (!pending.length) { + setSnackbar({ open: true, severity: 'info', message: 'No files to upload' }); + return; + } + for (const item of pending) { + await uploadSingle(item); + } + setSnackbar({ open: true, severity: 'success', message: 'All photos uploaded successfully!' }); + setQueue((prev) => prev.filter((q) => q.status !== 'done')); + }, [queue, uploadSingle]); + + const retryOne = (id: string) => { + const item = queue.find((q) => q.id === id); + if (item) uploadSingle(item); + }; + + const removeOne = (id: string) => { + const item = queue.find((q) => q.id === id); + if (item) { + try { + URL.revokeObjectURL(item.previewUrl); + } catch { + //ignore + } + } + setQueue((prev) => prev.filter((q) => q.id !== id)); + }; + + /* ============================ + Folder Management + ============================ */ + const openFolder = async (title: string) => { + setFolderOpenTitle(title); + try { + const items = await fetchImagesApi(title); + setFolderPhotos(items); + setFolderModalOpen(true); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + console.error('openFolder error', err); + setSnackbar({ open: true, severity: 'error', message: `Failed to load folder: ${err?.message || err}` }); + } + }; + + const handleDeletePhoto = async (id: number | string) => { + // if (!confirm('Delete this photo? This cannot be undone.')) return; + setDeletingId(id); + try { + await deletePhotoApi(id); + setFolderPhotos((prev) => prev.filter((p) => p.id !== id)); + setPhotosAll((prev) => prev.filter((p) => p.id !== id)); + setSnackbar({ open: true, severity: 'success', message: 'Photo deleted' }); + await loadAll(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + console.error('deletePhoto error', err); + setSnackbar({ open: true, severity: 'error', message: `Failed to delete: ${err?.message || err}` }); + } finally { + setDeletingId(null); + } + }; + + const handleDeleteFolder = async (title: string) => { + if (!confirm(`Delete "${title}" and all ${folders.find(f => f.title === title)?.count || 0} photos?`)) return; + setDeletingFolder(title); + try { + await deleteFolderApi(title); + setSnackbar({ open: true, severity: 'success', message: `Folder "${title}" deleted` }); + await loadAll(); + setFolderModalOpen(false); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + console.error('deleteFolder error', err); + setSnackbar({ open: true, severity: 'error', message: `Failed to delete folder: ${err?.message || err}` }); + } finally { + setDeletingFolder(null); + } + }; + + /* ============================ + UI + ============================ */ + return ( + + + + + + + + {/* Header */} + + + + + + Sakashi and Chirags Wedding Memories + + + setActiveTab(newValue)} + sx={{ + display: { xs: 'none', md: 'flex' }, + '& .MuiTabs-indicator': { + backgroundColor: '#ffd700', + }, + }} + > + } + iconPosition="start" + label="Albums" + sx={{ color: 'white' }} + /> + } + iconPosition="start" + label="Our Story" + sx={{ color: 'white' }} + /> + } + iconPosition="start" + label="Moments" + sx={{ color: 'white' }} + /> + + + + + } + label="Love Story" + sx={{ + bgcolor: 'rgba(255, 215, 0, 0.2)', + color: '#ffd700', + border: '1px solid rgba(255, 215, 0, 0.3)', + display: { xs: 'none', sm: 'flex' }, + }} + /> + setShowUploadPanel(true)} + sx={{ color: '#ffd700' }} + > + + + + + + + + {/* Tab Content */} + {activeTab === 0 && ( + + {/* Hero Section */} + + + Our Wedding Albums + + + Preserving precious memories of our special day + + + + {/* Upload Section (Collapsible) */} + {showUploadPanel && ( + + + + 📸 Upload Photos + + setShowUploadPanel(false)} size="small"> + + + + + + + + + + + {isDragActive ? 'Drop photos here' : 'Drag & drop photos here'} + + + or click to browse files + + + + + + + + + + + {queue.length > 0 && ( + + + setCommonTitle(e.target.value)} + sx={{ mb: 2 }} + /> + + + q.status === 'pending' || q.status === 'error')} + startIcon={} + sx={{ flex: 1 }} + > + Upload All ({queue.filter(q => q.status === 'pending' || q.status === 'error').length}) + + + + + + {/* Queue Preview */} + + {queue.map((q) => ( + + + + + {q.file.name} + + + {q.status === 'uploading' && } + {q.status === 'error' && ( + retryOne(q.id)}> + + + )} + removeOne(q.id)}> + + + + + + + ))} + + + )} + + )} + + {/* Albums Grid */} + + + + All Albums ({folders.length}) + + + + + {folders.length === 0 ? ( + + + + No Albums Yet + + + Create your first album to start preserving wedding memories + + setShowUploadPanel(true)} + startIcon={} + > + Create Album + + + ) : ( + + {folders.map((folder) => ( + + + openFolder(folder.title)} sx={{ position: 'relative', cursor: 'pointer' }}> + + + + + + + Open Album + + + {folder.count} precious memories + + + + + + ALBUM + + + + + + + {folder.title} + + + {folder.description} + + + + + + {folder.count} photos + + + { + e.stopPropagation(); + if (confirm(`Delete album "${folder.title}"?`)) { + await handleDeleteFolder(folder.title); + } + }} + sx={{ + color: '#ef4444', + '&:hover': { + backgroundColor: 'rgba(239, 68, 68, 0.1)', + }, + }} + > + + + + + + + + + ))} + + )} + + + )} + + {activeTab === 1 && ( + + {/* Storyline Hero */} + + + Our Story + + + The beautiful journey that led us here + + + From the first hello to forever, every step of our journey has been magical. + Here's our story in chapters. + + + + {/* Story Steps */} + + {storySteps.map((step) => ( + + + + {step.icon} + + + {step.title} + + + {step.description} + + + + + + + ))} + + + )} + + {activeTab === 2 && ( + + {/* Moments Hero */} + + + Special Moments + + + Candid shots and heartfelt moments + + + The most beautiful moments are often the unplanned ones. + These photos capture the true essence of our wedding day. + + + + {/* Photo Grid */} + + {photosAll.length === 0 ? ( + + + + No Photos Yet + + + Upload photos to see your special moments here + + setShowUploadPanel(true)} + startIcon={} + > + Upload Photos + + + ) : ( + + {photosAll.slice(0, 12).map((photo) => ( + + + + { + try { + const a = document.createElement('a'); + a.href = photo.url; + a.download = photo.filename || 'wedding-photo.jpg'; + document.body.appendChild(a); + a.click(); + a.remove(); + } catch (err) { + console.error('download error', err); + } + }} + sx={{ + color: 'white', + bgcolor: 'rgba(15, 118, 110, 0.8)', + '&:hover': { bgcolor: '#0f766e' }, + }} + > + + + + + + + ))} + + )} + + + )} + + + {/* Album Modal */} + setFolderModalOpen(false)} + fullWidth + maxWidth="lg" + PaperProps={{ + sx: { + borderRadius: 3, + maxHeight: '90vh', + }, + }} + > + + + + + + {folderOpenTitle} + + {folderPhotos.length} photos + + + + + + + + {folderPhotos.length === 0 ? ( + + + No photos in this album + + + ) : ( + + {folderPhotos.map((p, idx) => ( + + + + handleDeletePhoto(p.id)} + disabled={deletingId === p.id} + sx={{ + color: 'white', + bgcolor: 'rgba(239, 68, 68, 0.9)', + '&:hover': { bgcolor: '#ef4444' }, + }} + > + + + { + try { + const a = document.createElement('a'); + a.href = p.url; + a.download = p.filename || `${p.title || 'wedding-photo'}.jpg`; + document.body.appendChild(a); + a.click(); + a.remove(); + setSnackbar({ open: true, severity: 'success', message: 'Photo downloaded!' }); + } catch (err) { + console.error('download error', err); + setSnackbar({ open: true, severity: 'error', message: 'Download failed' }); + } + }} + sx={{ + color: 'white', + bgcolor: 'rgba(15, 118, 110, 0.9)', + '&:hover': { bgcolor: '#0f766e' }, + }} + > + + + + { + setLightboxStartIndex(idx); + setLightboxOpen(true); + }} + > + + + + + ))} + + )} + + + + + + + {/* Floating Action Button */} + setShowUploadPanel(true)}> + + + + {/* Lightbox */} + setLightboxOpen(false)} + /> + + {/* Snackbar */} + setSnackbar((s) => ({ ...s, open: false }))} + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + > + setSnackbar((s) => ({ ...s, open: false }))} + sx={{ + borderRadius: 2, + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)', + fontFamily: '"Inter", sans-serif', + fontWeight: 500, + }} + > + {snackbar.message} + + + + {/* Footer */} + + + + + + + + + Forever Cherished • Always Remembered + + + © {new Date().getFullYear()} Our Wedding Gallery + + + + + ); +} \ No newline at end of file diff --git a/src/WeddingGallery/apis.ts b/src/WeddingGallery/apis.ts new file mode 100644 index 0000000..615a03d --- /dev/null +++ b/src/WeddingGallery/apis.ts @@ -0,0 +1,118 @@ +// apis.ts +const MFS_API_BASE = 'https://mfs-api.midastix.com'; + +export const toAbsoluteUrl = (path?: string) => { + if (!path) return ''; + if (path.startsWith('http://') || path.startsWith('https://')) return path; + return `${MFS_API_BASE}${path}`; +}; + +/** + * uploadFileXHR(file, onProgress, title?) + * returns { success, data?, error? } + */ +export const uploadFileXHR = ( + file: File, + onProgress: (pct: number) => void, + title?: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Promise<{ success: boolean; data?: any; error?: string }> => + new Promise((resolve) => { + try { + const xhr = new XMLHttpRequest(); + const url = `${MFS_API_BASE}/image-upload/single`; + xhr.open('POST', url, true); + xhr.setRequestHeader('Accept', 'application/json'); + + xhr.upload.onprogress = (ev) => { + if (!ev.lengthComputable) return; + const pct = Math.round((ev.loaded / ev.total) * 100); + onProgress(pct); + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + try { + const json = JSON.parse(xhr.responseText); + resolve({ success: true, data: json }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + resolve({ success: true, data: xhr.responseText }); + } + } else { + const txt = xhr.responseText || `HTTP ${xhr.status}`; + resolve({ success: false, error: txt }); + } + }; + + xhr.onerror = () => resolve({ success: false, error: 'Network error' }); + + const fd = new FormData(); + fd.append('file', file, file.name); + if (title) fd.append('title', title); + xhr.send(fd); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + resolve({ success: false, error: err?.message ?? String(err) }); + } + }); + +/* tolerant fetchImages mapping */ +export const fetchImagesApi = async (title = '') => { + const url = + title && title.trim() !== '' ? `${MFS_API_BASE}/image-upload?title=${encodeURIComponent(title)}` : `${MFS_API_BASE}/image-upload`; + const res = await fetch(url, { headers: { accept: 'application/json' } }); + if (!res.ok) { + const txt = await res.text(); + throw new Error(txt || `HTTP ${res.status}`); + } + const body = await res.json(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let imagesArray: any[] = []; + if (Array.isArray(body)) imagesArray = body; + else if (Array.isArray(body.images)) imagesArray = body.images; + else if (body.data && Array.isArray(body.data.images)) imagesArray = body.data.images; + else { + const firstArray = Object.values(body).find((v) => Array.isArray(v)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (Array.isArray(firstArray)) imagesArray = firstArray as any[]; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return imagesArray.map((it: any) => ({ + id: it.id ?? it._id ?? `${it.filename ?? Math.random()}`, + url: toAbsoluteUrl(it.path ?? it.url), + title: it.title ?? it.filename ?? '', + date: it.created_at ?? it.createdAt ?? it.date ?? '', + filename: it.filename, + path: it.path, + created_at: it.created_at ?? it.createdAt, + })); +}; + +/* Delete photo (by id) */ +export const deletePhotoApi = async (filename: string) => { + const url = `${MFS_API_BASE}/image-upload/${filename}`; + + const res = await fetch(url, { + method: "DELETE", + headers: { accept: "application/json" }, + }); + + const text = await res.text(); + + if (!res.ok) { + throw new Error(text || `HTTP ${res.status}`); + } + + return text || "deleted"; +}; + + +/* Delete folder by title (assumed endpoint) */ +export const deleteFolderApi = async (title: string) => { + const url = `${MFS_API_BASE}/image-upload/${encodeURIComponent(title)}`; + const res = await fetch(url, { method: 'DELETE', headers: { accept: 'application/json' } }); + const text = await res.text(); + if (!res.ok) throw new Error(text || `HTTP ${res.status}`); + return text || 'folder deleted'; +}; diff --git a/src/WeddingGallery/folderModel.tsx b/src/WeddingGallery/folderModel.tsx new file mode 100644 index 0000000..c31bfc3 --- /dev/null +++ b/src/WeddingGallery/folderModel.tsx @@ -0,0 +1,53 @@ +// FolderModal.tsx +import React from 'react'; +import { Box, Dialog, DialogTitle, DialogContent, DialogActions, Button, Grid, Card, CardMedia, IconButton, Typography } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { WeddingPhoto } from './types'; + +type Props = { + open: boolean; + onClose: () => void; + photos: WeddingPhoto[]; + onDeletePhoto: (id: number | string) => Promise; + deletingId?: number | string | null; +}; + +const FolderModal: React.FC = ({ open, onClose, photos, onDeletePhoto, deletingId }) => { + return ( + + Manage Photos ({photos.length}) + + {photos.length === 0 ? ( + No photos in this event. + ) : ( + + {photos.map((p) => ( + + + + + onDeletePhoto(p.id)} + disabled={deletingId === p.id} + > + + + + + {p.filename ?? p.title} + + + + ))} + + )} + + + + + + ); +}; + +export default FolderModal; diff --git a/src/WeddingGallery/foldergrid.tsx b/src/WeddingGallery/foldergrid.tsx new file mode 100644 index 0000000..cb84ea0 --- /dev/null +++ b/src/WeddingGallery/foldergrid.tsx @@ -0,0 +1,54 @@ +// FolderGrid.tsx +import React from 'react'; +import SafeGrid from './SafeGrid'; +import { FolderInfo } from './types'; +import { Box, Card, CardContent, CardMedia, IconButton, Paper, Typography } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; + +type Props = { + folders: FolderInfo[]; + onOpen: (title: string) => void; + onDeleteFolder: (title: string) => void; +}; + +const FolderGrid: React.FC = ({ folders, onOpen, onDeleteFolder }) => { + if (!folders.length) { + return ( + + No events found + Upload photos to create events (folders). + + ); + } + + return ( + + {folders.map((f) => ( + + + onOpen(f.title)}> + + + + + + + {f.title} + {f.count} photos + + + { e.stopPropagation(); onDeleteFolder(f.title); }}> + + + + + + + + + ))} + + ); +}; + +export default FolderGrid; diff --git a/src/WeddingGallery/types.tsx b/src/WeddingGallery/types.tsx new file mode 100644 index 0000000..15ea034 --- /dev/null +++ b/src/WeddingGallery/types.tsx @@ -0,0 +1,26 @@ +// types.ts +export type WeddingPhoto = { + id: number | string; + url: string; + title?: string; + date?: string; + filename?: string; + path?: string; + created_at?: string; +}; + +export type FileQueueItem = { + id: string; + file: File; + previewUrl: string; + progress: number; + status: 'pending' | 'uploading' | 'done' | 'error'; + error?: string; + uploadedPhoto?: WeddingPhoto; +}; + +export type FolderInfo = { + title: string; + count: number; + thumb?: string; +}; diff --git a/src/assets/bag1.jpg b/src/assets/bag1.jpg new file mode 100644 index 0000000..b2486f7 Binary files /dev/null and b/src/assets/bag1.jpg differ diff --git a/src/assets/bag2.jpg b/src/assets/bag2.jpg new file mode 100644 index 0000000..735e0bc Binary files /dev/null and b/src/assets/bag2.jpg differ diff --git a/src/assets/group1.jpg b/src/assets/group1.jpg new file mode 100644 index 0000000..5018f8d Binary files /dev/null and b/src/assets/group1.jpg differ diff --git a/src/assets/logobgremove.png b/src/assets/logobgremove.png new file mode 100644 index 0000000..daeae86 Binary files /dev/null and b/src/assets/logobgremove.png differ diff --git a/src/assets/solo1.jpg b/src/assets/solo1.jpg new file mode 100644 index 0000000..a9fa7e9 Binary files /dev/null and b/src/assets/solo1.jpg differ diff --git a/src/assets/solo2.jpg b/src/assets/solo2.jpg new file mode 100644 index 0000000..7357582 Binary files /dev/null and b/src/assets/solo2.jpg differ diff --git a/src/components/blogs/BlogCard.tsx b/src/components/blogs/BlogCard.tsx new file mode 100644 index 0000000..7ea53cd --- /dev/null +++ b/src/components/blogs/BlogCard.tsx @@ -0,0 +1,359 @@ +import React from 'react'; +import { Card, CardContent, CardMedia, Typography, Box, Chip, Button, Avatar } from '@mui/material'; +import { BlogPost } from '../../types/blogs'; +import { keyframes } from '@emotion/react'; +import { styled } from '@mui/system'; +import { useNavigate } from 'react-router-dom'; + +// Use your existing color palette +const colors = { + paper: '#F5F5EF', + ink: '#1A2526', + accent: '#D4A017', + secondary: '#7B4F3A', + highlight: '#FFF4CC', + border: '#B89A6E', + dark: '#3A1F0F', +}; + +// Storytelling Animations +const storyReveal = keyframes` + 0% { + opacity: 0; + transform: translateY(30px) rotate(2deg); + filter: blur(4px); + } + 100% { + opacity: 1; + transform: translateY(0) rotate(0); + filter: blur(0); + } +`; + +const imageFocus = keyframes` + 0% { transform: scale(1.1); filter: brightness(0.7) sepia(0.3); } + 100% { transform: scale(1); filter: brightness(1) sepia(0); } +`; + +const contentAppear = keyframes` + 0% { + opacity: 0; + transform: translateY(20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +`; + +const underlineDraw = keyframes` + 0% { width: 0; } + 100% { width: 100%; } +`; + +const tagFloat = keyframes` + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-3px); } +`; + +// Styled Components +const StoryCard = styled(Card)({ + animation: `${storyReveal} 1.2s forwards`, + opacity: 0, + transform: 'translateY(30px) rotate(2deg)', + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'linear-gradient(to bottom, transparent 60%, rgba(0,0,0,0.7) 100%)', + opacity: 0, + transition: 'opacity 0.5s ease', + zIndex: 1, + }, + '&:hover::before': { + opacity: 1, + }, +}); + +const StoryImage = styled(CardMedia)({ + animation: `${imageFocus} 1.5s forwards`, + transform: 'scale(1.1)', + filter: 'brightness(0.7) sepia(0.3)', + transition: 'all 0.7s ease', + position: 'relative', + '&:hover': { + transform: 'scale(1.05)', + }, +}); + +const StoryContent = styled(CardContent)({ + animation: `${contentAppear} 1s forwards`, + animationDelay: '0.5s', + opacity: 0, + position: 'relative', + zIndex: 2, +}); + +const StoryTitle = styled(Typography)({ + position: 'relative', + display: 'inline-block', + '&::after': { + content: '""', + position: 'absolute', + bottom: '-4px', + left: 0, + width: 0, + height: '2px', + backgroundColor: colors.accent, + animation: `${underlineDraw} 1s forwards`, + animationDelay: '1s', + }, +}); + +const StoryTag = styled(Chip)({ + animation: `${tagFloat} 3s ease-in-out infinite`, + animationDelay: '0.3s', +}); + +interface BlogCardProps { + post: BlogPost; + index: number; +} + +const BlogCard: React.FC = ({ post, index }) => { + const navigate = useNavigate(); + + const handleReadMore = () => { + navigate(`/blog/${post.id}`); + }; + + // Calculate animation delays for staggered effect + const animationDelay = `${index * 0.15}s`; + + return ( + + + + + + + + + + + + + + {post.author.name} + + + {post.author.role} + + + + + + {post.title} + + + + {post.excerpt} + + + + + + {post.date} • {post.readTime} + + + + + + + {post.tags.map((tag, i) => ( + + ))} + + + + ); +}; + +export default BlogCard; \ No newline at end of file diff --git a/src/components/blogs/BlogPage.tsx b/src/components/blogs/BlogPage.tsx new file mode 100644 index 0000000..0f93dda --- /dev/null +++ b/src/components/blogs/BlogPage.tsx @@ -0,0 +1,781 @@ +import React, { useState, useMemo } from 'react'; +import { + Box, Container, Grid, Typography, TextField, Chip, IconButton, + InputAdornment, Card, CardContent, CardMedia, Button, Avatar, + useTheme, useMediaQuery +} from '@mui/material'; +import { useInView } from 'react-intersection-observer'; +import InstagramIcon from '@mui/icons-material/Instagram'; +import FacebookIcon from '@mui/icons-material/Facebook'; +import TwitterIcon from '@mui/icons-material/Twitter'; +import SearchIcon from '@mui/icons-material/Search'; +import { keyframes } from '@emotion/react'; +import { styled } from '@mui/system'; +import { useNavigate } from 'react-router-dom'; + +// Define TypeScript interfaces +interface Author { + name: string; + avatar: string; + role: string; +} + +interface BlogPost { + id: string; + title: string; + excerpt: string; + content: string; + image: string; + author: Author; + date: string; + readTime: string; + tags: string[]; + category: string; +} + +interface BlogCardProps { + post: BlogPost; + index: number; +} + +// Color palette +const colors = { + paper: '#F5F5EF', + ink: '#1A2526', + accent: '#D4A017', + secondary: '#7B4F3A', + highlight: '#FFF4CC', + border: '#B89A6E', + dark: '#3A1F0F', +}; + +// Animations +const fadeInUp = keyframes` + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +`; + +// const fadeIn = keyframes` +// from { opacity: 0; } +// to { opacity: 1; } +// `; + +const float = keyframes` + 0% { transform: translateY(0px); } + 50% { transform: translateY(-8px); } + 100% { transform: translateY(0px); } +`; + +const drawUnderline = keyframes` + 0% { width: 0; } + 100% { width: 100%; } +`; + +const pulse = keyframes` + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +`; + +// Styled Components +interface AnimatedSectionProps { + delay?: string; +} + +const AnimatedSection = styled(Box, { + shouldForwardProp: (prop) => prop !== 'delay', +})(({ delay = '0s' }) => ({ + opacity: 0, + animation: `${fadeInUp} 0.8s forwards`, + animationDelay: delay, +})); + +const VintageUnderline = styled(Box)({ + position: 'relative', + display: 'inline-block', + '&::after': { + content: '""', + position: 'absolute', + bottom: '-4px', + left: 0, + width: '0', + height: '1.5px', + backgroundColor: colors.accent, + animation: `${drawUnderline} 1.2s forwards`, + animationDelay: '0.3s', + }, +}); + +const FloatingElement = styled(Box)({ + animation: `${float} 6s ease-in-out infinite`, +}); + +// BlogCard Component +const BlogCard: React.FC = ({ post, index }) => { + const navigate = useNavigate(); + const [ref, inView] = useInView({ threshold: 0.2, triggerOnce: true }); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + const handleReadMore = () => { + navigate(`/blog/${post.id}`); + }; + + return ( + + + + + + + + + + + + {post.author.name} + + + {post.author.role} + + + + + + {post.title} + + + + {post.excerpt} + + + + + {post.date} • {post.readTime} + + + + + + + {post.tags.map((tag: string, i: number) => ( + + ))} + + + + ); +}; + +// BlogPage Component +const BlogPage: React.FC = () => { + const [selectedCategory, setSelectedCategory] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [heroRef, heroInView] = useInView({ threshold: 0.1, triggerOnce: true }); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + // Mock data + const blogData = { + posts: [ + { + id: "1", + title: "The Art of Hand Block Printing", + excerpt: "Discover the ancient technique of hand block printing that has been passed down through generations of Indian artisans.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/6011599/pexels-photo-6011599.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Priya Sharma", + avatar: "https://images.pexels.com/photos/3762800/pexels-photo-3762800.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Textile Conservationist" + }, + date: "2023-10-15", + readTime: "5 min read", + tags: ["Textiles", "Craft", "Heritage"], + category: "Artisan Techniques" + }, + { + id: "2", + title: "Natural Dyes: Colors from Nature", + excerpt: "Explore how traditional Indian artisans extract vibrant colors from plants, minerals, and other natural sources.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/1375736/pexels-photo-1375736.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Rajiv Mehta", + avatar: "https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Natural Dye Expert" + }, + date: "2023-09-22", + readTime: "7 min read", + tags: ["Eco-friendly", "Sustainability", "Natural"], + category: "Sustainable Practices" + }, + { + id: "3", + title: "The Weavers of Varanasi", + excerpt: "A journey into the world of Varanasi's master weavers who create the exquisite Banarasi silk sarees.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/942803/pexels-photo-942803.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Anjali Patel", + avatar: "https://images.pexels.com/photos/3785077/pexels-photo-3785077.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Textile Historian" + }, + date: "2023-08-30", + readTime: "8 min read", + tags: ["Weaving", "Silk", "Heritage"], + category: "Artisan Stories" + }, + { + id: "4", + title: "Reviving Ancient Embroidery Techniques", + excerpt: "How contemporary designers are working with artisans to preserve and modernize traditional embroidery methods.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/6347892/pexels-photo-6347892.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Sanjay Kumar", + avatar: "https://images.pexels.com/photos/2182970/pexels-photo-2182970.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Fashion Designer" + }, + date: "2023-08-15", + readTime: "6 min read", + tags: ["Embroidery", "Design", "Revival"], + category: "Design Innovation" + }, + { + id: "5", + title: "Pottery Traditions of Rajasthan", + excerpt: "Exploring the centuries-old pottery techniques that continue to thrive in the desert state.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/4110012/pexels-photo-4110012.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Vikram Singh", + avatar: "https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Cultural Anthropologist" + }, + date: "2023-07-28", + readTime: "9 min read", + tags: ["Pottery", "Tradition", "Rajasthan"], + category: "Cultural Heritage" + }, + { + id: "6", + title: "Sustainable Fashion Revolution", + excerpt: "How traditional Indian textiles are leading the way in sustainable fashion practices worldwide.", + content: "Full content would be here...", + image: "https://images.pexels.com/photos/4947734/pexels-photo-4947734.jpeg?auto=compress&cs=tinysrgb&w=500", + author: { + name: "Meera Desai", + avatar: "https://images.pexels.com/photos/3756678/pexels-photo-3756678.jpeg?auto=compress&cs=tinysrgb&w=500", + role: "Fashion Entrepreneur" + }, + date: "2023-07-10", + readTime: "7 min read", + tags: ["Fashion", "Sustainability", "Innovation"], + category: "Sustainable Practices" + } + ], + categories: [ + "Artisan Techniques", + "Sustainable Practices", + "Artisan Stories", + "Design Innovation", + "Cultural Heritage" + ], + tags: [ + "Textiles", + "Craft", + "Heritage", + "Eco-friendly", + "Sustainability", + "Natural", + "Weaving", + "Silk", + "Embroidery", + "Design", + "Revival" + ] + }; + + const filteredPosts = useMemo(() => { + return blogData.posts.filter(post => { + const matchesCategory = selectedCategory === 'all' || post.category === selectedCategory; + const matchesSearch = searchQuery === '' || + post.title.toLowerCase().includes(searchQuery.toLowerCase()) || + post.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) || + post.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); + + return matchesCategory && matchesSearch; + }); + }, [selectedCategory, searchQuery]); + + return ( + + {/* Header Section */} + + + + + + + + Journal + + + + Stories of Craft, Culture & Heritage + + + + + + + + + + + + + + + + + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + sx: { + fontFamily: '"Cormorant Garamond", serif', + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: colors.border, + }, + '&:hover fieldset': { + borderColor: colors.accent, + }, + '&.Mui-focused fieldset': { + borderColor: colors.accent, + }, + }, + } + }} + sx={{ mb: 4 }} + /> + + + + + setSelectedCategory('all')} + variant={selectedCategory === 'all' ? "filled" : "outlined"} + sx={{ + color: selectedCategory === 'all' ? colors.ink : colors.secondary, + backgroundColor: selectedCategory === 'all' ? colors.accent : 'transparent', + borderColor: colors.border, + fontFamily: '"Cormorant Garamond", serif', + '&:hover': { + backgroundColor: selectedCategory === 'all' ? colors.accent : `${colors.highlight}80` + } + }} + /> + {blogData.categories.map((category: string) => ( + setSelectedCategory(category)} + variant={selectedCategory === category ? "filled" : "outlined"} + sx={{ + color: selectedCategory === category ? colors.ink : colors.secondary, + backgroundColor: selectedCategory === category ? colors.accent : 'transparent', + borderColor: colors.border, + fontFamily: '"Cormorant Garamond", serif', + '&:hover': { + backgroundColor: selectedCategory === category ? colors.accent : `${colors.highlight}80` + } + }} + /> + ))} + + + + + + {/* Blog Posts Grid */} + + + {filteredPosts.map((post: BlogPost, index: number) => ( + + + + ))} + + + {filteredPosts.length === 0 && ( + + + No articles found + + + Try a different search or category + + + )} + + + {/* Footer */} + + + + + The Craft Chronicle + + + Celebrating the stories, techniques, and people behind traditional crafts and heritage arts. + + + + + + + + + + + + + + © {new Date().getFullYear()} The Craft Chronicle. All rights reserved. + + + + + + ); +}; + +export default BlogPage; \ No newline at end of file diff --git a/src/comingsoon/comingsoon.tsx b/src/components/comingsoon/comingsoon.tsx similarity index 81% rename from src/comingsoon/comingsoon.tsx rename to src/components/comingsoon/comingsoon.tsx index 57f1ef7..02c2371 100644 --- a/src/comingsoon/comingsoon.tsx +++ b/src/components/comingsoon/comingsoon.tsx @@ -7,6 +7,14 @@ import TwitterIcon from '@mui/icons-material/Twitter'; import EmailIcon from '@mui/icons-material/Email'; import { keyframes } from '@emotion/react'; import { styled } from '@mui/system'; +import image1 from '../../assets/group1.jpg' +import image2 from '../../assets/solo1.jpg' +import image3 from '../../assets/solo2.jpg' +import image4 from '../../assets/bag1.jpg' +import image5 from '../../assets/bag2.jpg' +import logo from '../../assets/logobgremove.png' + + // Color palette inspired by Indian heritage const colors = { @@ -57,6 +65,60 @@ const scrollReveal = keyframes` to { opacity: 1; transform: translateY(0); } `; +const shimmer = keyframes` + 0% { background-position: -200px 0; } + 100% { background-position: calc(200px + 100%) 0; } +`; + +const textGlow = keyframes` + 0% { text-shadow: 0 0 5px rgba(212, 160, 23, 0.3); } + 50% { text-shadow: 0 0 15px rgba(212, 160, 23, 0.6); } + 100% { text-shadow: 0 0 5px rgba(212, 160, 23, 0.3); } +`; + +const borderFlow = keyframes` + 0% { border-color: ${colors.accent}; } + 33% { border-color: ${colors.secondary}; } + 66% { border-color: ${colors.border}; } + 100% { border-color: ${colors.accent}; } +`; + +// const typewriter = keyframes` +// from { width: 0; } +// to { width: 100%; } +// `; + +// const fadeInLeft = keyframes` +// from { opacity: 0; transform: translateX(-50px); } +// to { opacity: 1; transform: translateX(0); } +// `; + +// const fadeInRight = keyframes` +// from { opacity: 0; transform: translateX(50px); } +// to { opacity: 1; transform: translateX(0); } +// `; + +const bounce = keyframes` + 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-15px); } + 60% { transform: translateY(-7px); } +`; + +const flipIn = keyframes` + 0% { transform: perspective(400px) rotateY(90deg); opacity: 0; } + 100% { transform: perspective(400px) rotateY(0deg); opacity: 1; } +`; + +// const zoomIn = keyframes` +// from { transform: scale(0.95); opacity: 0; } +// to { transform: scale(1); opacity: 1; } +// `; + +const spinSlow = keyframes` + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +`; + // Styled components const VintageBox = styled(Box)({ position: 'relative', @@ -158,6 +220,35 @@ const AnimatedSection = styled(Box)({ animation: `${scrollReveal} 1s forwards`, }); +const GlowingText = styled(Typography)({ + animation: `${textGlow} 3s ease-in-out infinite`, +}); + +const BorderFlowBox = styled(Box)({ + border: `1px solid ${colors.accent}`, + animation: `${borderFlow} 6s infinite linear`, +}); + +// const TypewriterText = styled(Typography)({ +// overflow: 'hidden', +// whiteSpace: 'nowrap', +// margin: '0 auto', +// animation: `${typewriter} 4s steps(40, end)`, +// }); + +const BounceBox = styled(Box)({ + animation: `${bounce} 2s infinite`, +}); + +const FlipInBox = styled(Box)({ + animation: `${flipIn} 1s forwards`, + transformOrigin: 'center', +}); + +// const ZoomInBox = styled(Box)({ +// animation: `${zoomIn} 1s forwards`, +// }); + // Kalawati story sections with updated Pexels images const storyChapters = [ { @@ -165,42 +256,42 @@ const storyChapters = [ title: 'Where Curiosity Met Craft', content: 'Fresh out of fashion school, I found myself drawn to looms, dye pits, and village homes. I organized workshops to listen, learn, and share space with artisans, forging a bond between design, community, and culture.', - image: 'https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image1}`, }, { year: '2020', title: 'A Name Was Born', content: 'During the pandemic, I launched a campaign to highlight artisans hit by lockdowns. The Kalawati was born—named after women who hold culture in their palms and creativity in their hearts.', - image: 'https://images.pexels.com/photos/31308739/pexels-photo-31308739.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image2}`, }, { year: '2021', title: 'Strengthening the Circle', content: 'I spent the year mapping artisan strengths, connecting traditional skills to modern demand, and building trust for a long-term vision of community and craft.', - image: 'https://images.pexels.com/photos/3772504/pexels-photo-3772504.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image3}`, }, { year: '2022', title: 'Systems That Serve People', content: 'Through the JSW Foundation Fellowship, I built systems blending business and empathy—brand building for rural women, value chain development, and sustainable income pathways.', - image: 'https://images.pexels.com/photos/163064/pexels-photo-163064.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image4}`, }, { year: '2023', title: 'Quiet Creation', content: 'Working with artisan clusters, we prototyped collections with handloom, embroidery, and natural dyes—crafting stories you could wear.', - image: 'https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image1}`, }, { year: '2024', title: 'Kalawati Arrives', content: 'The Kalawati opens its doors as a brand and movement, offering ethically made handcrafted apparel and decor. Join us to co-create, support heritage, and connect to roots.', - image: 'https://images.pexels.com/photos/774859/pexels-photo-774859.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image5}`, }, ]; @@ -209,22 +300,22 @@ const processSteps = [ { title: 'Design Inspiration', description: 'Drawing from traditional Indian motifs and contemporary aesthetics', - image: 'https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image4}`, }, { title: 'Material Selection', description: 'Choosing the finest natural fibers and dyes', - image: 'https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image5}`, }, { title: 'Artisan Crafting', description: 'Skilled hands weaving stories into fabric', - image: 'https://images.pexels.com/photos/163064/pexels-photo-163064.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image4}`, }, { title: 'Quality Assurance', description: 'Meticulous inspection ensuring perfection', - image: 'https://images.pexels.com/photos/3772504/pexels-photo-3772504.jpeg?auto=compress&cs=tinysrgb&w=500', + image: `${image5}`, }, ]; @@ -287,10 +378,12 @@ export const VintageComingSoonPage: React.FC = () => { const handleSubscribe = (e: React.MouseEvent) => { e.preventDefault(); - console.log('Subscribed with email:', email); - setSubscribed(true); - setEmail(''); - setTimeout(() => setSubscribed(false), 3000); + if (email && email.includes('@')) { + console.log('Subscribed with email:', email); + setSubscribed(true); + setEmail(''); + setTimeout(() => setSubscribed(false), 3000); + } }; return ( @@ -310,6 +403,39 @@ export const VintageComingSoonPage: React.FC = () => { }} /> + {/* Animated decorative elements */} + + + + {/* Parallax background elements */} { right: '5%', width: '200px', height: '200px', - backgroundImage: 'url(https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500)', + backgroundImage: `url(${logo})`, backgroundSize: 'cover', - opacity: 0.1, + backgroundPosition: 'center', + opacity: 0.5, zIndex: 0, transform: `rotate(${scrollPosition * 0.02}deg)`, transition: 'transform 0.3s ease-out', + borderRadius: '50%', // makes it circular + overflow: 'hidden', // ensures image stays inside the circle }} /> + + { left: '5%', width: '150px', height: '150px', - backgroundImage: 'url(https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500)', + backgroundImage: `url(${logo})`, backgroundSize: 'cover', - opacity: 0.1, + backgroundPosition: 'center', + opacity: 0.4, zIndex: 0, transform: `rotate(${-scrollPosition * 0.03}deg)`, transition: 'transform 0.3s ease-out', + borderRadius: '50%', // circular shape + overflow: 'hidden', // ensures the image fits inside the circle }} /> + {/* Header */} @@ -361,27 +496,33 @@ export const VintageComingSoonPage: React.FC = () => { - - - - - - - - - + + + + + + + + + + + + + + + @@ -450,7 +591,7 @@ export const VintageComingSoonPage: React.FC = () => { flex: 1, }} > - { }} > A Journey Woven with Stories, People & Purpose - + { {/* Email Subscription */} - + { flex: 1, padding: '16px 24px', borderRadius: '0', - border: `1px solid ${colors.border}`, + border: 'none', fontSize: '16px', outline: 'none', background: colors.paper, @@ -579,7 +720,7 @@ export const VintageComingSoonPage: React.FC = () => { > Notify Me - + {subscribed && ( { '&::before': { content: '""', position: 'absolute', - top: 0, + top: "20%", left: '50%', transform: 'translateX(-50%)', width: '1px', @@ -788,9 +929,10 @@ export const VintageComingSoonPage: React.FC = () => { '&:hover': { transform: 'translateY(-5px)', }, + height: '100%', }} > - { backgroundPosition: 'center', mb: 3, filter: 'sepia(0.2) contrast(1.05)', - animation: `${scaleIn} 1s ease`, }} /> @@ -847,33 +988,41 @@ export const VintageComingSoonPage: React.FC = () => { Join our journey to weave stories, empower artisans, and celebrate Indian heritage. Be the first to experience our handcrafted collections. - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + { + const [selectedCategory, setSelectedCategory] = useState('all'); + const [hoveredProduct, setHoveredProduct] = useState(null); + + // Intersection Observer for scroll animations + const [heroRef, heroInView] = useInView({ threshold: 0.1, triggerOnce: true }); + const [productsRef, productsInView] = useInView({ threshold: 0.1, triggerOnce: true }); + const [footerRef, footerInView] = useInView({ threshold: 0.1, triggerOnce: true }); + + const filteredProducts = selectedCategory === 'all' + ? products + : products.filter(product => product.category === selectedCategory); + + return ( + + {/* Paper texture overlay */} + + + {/* Animated decorative elements */} + + + + + + {/* Header */} + + + + The Kalawati Collection + + + + + + + + + + + + + + + + + + {/* Hero Section */} + + + + + + + + + Timeless Craftsmanship, Modern Elegance + + + Each piece tells a story of tradition, skill, and the hands that crafted it with love and dedication. + + + + Explore Collection + + + + + {/* Category Filter */} + + + + Browse Categories + + + + + {categories.map((category) => ( + setSelectedCategory(category.value)} + variant={selectedCategory === category.value ? "filled" : "outlined"} + sx={{ + color: selectedCategory === category.value ? colors.lightPaper : colors.lightInk, + backgroundColor: selectedCategory === category.value ? colors.accent : 'transparent', + borderColor: colors.border, + fontFamily: '"Cormorant Garamond", serif', + fontSize: '1rem', + padding: '8px 16px', + '&:hover': { + backgroundColor: selectedCategory === category.value ? colors.accent : colors.highlight, + }, + }} + /> + ))} + + + + {/* Products Grid */} + + + {filteredProducts.map((product, index) => ( + + + setHoveredProduct(product.id)} + onMouseLeave={() => setHoveredProduct(null)} + > + + + + + + + + + + + + + + + {product.tags.map((tag, i) => ( + + ))} + + + + + {product.name} + + + {product.description} + + + + {product.price} + + + Add to Cart + + + + + + + ))} + + + + {/* Call to Action */} + + + + + Join Our Artisan Community + + + + + By purchasing our products, you're not just buying beautiful handcrafted items—you're supporting traditional artisans and helping preserve India's rich cultural heritage for future generations. + + + + + Discover Our Story + + + + + + {/* Footer */} + + + + Handcrafted Heritage + + + Each piece tells a story of India's rich heritage and skilled artisanship. + + + + + + + + + + + + + + + + + © {new Date().getFullYear()} Handcrafted Heritage. All rights reserved. + + + + + + ); +}; + +export default LightProductShowcase; \ No newline at end of file diff --git a/src/data/blogs.json b/src/data/blogs.json new file mode 100644 index 0000000..75dd740 --- /dev/null +++ b/src/data/blogs.json @@ -0,0 +1,104 @@ +{ + "posts": [ + { + "id": "1", + "title": "The Art of Hand Block Printing", + "excerpt": "Discover the ancient technique of hand block printing that has been passed down through generations of Indian artisans.", + "content": "Full content would be here...", + "image": "https://images.pexels.com/photos/6011599/pexels-photo-6011599.jpeg?auto=compress&cs=tinysrgb&w=500", + "author": { + "name": "Priya Sharma", + "avatar": "https://images.pexels.com/photos/3762800/pexels-photo-3762800.jpeg?auto=compress&cs=tinysrgb&w=500", + "role": "Textile Conservationist" + }, + "date": "2023-10-15", + "readTime": "5 min read", + "tags": [ + "Textiles", + "Craft", + "Heritage" + ], + "category": "Artisan Techniques" + }, + { + "id": "2", + "title": "Natural Dyes: Colors from Nature", + "excerpt": "Explore how traditional Indian artisans extract vibrant colors from plants, minerals, and other natural sources.", + "content": "Full content would be here...", + "image": "https://images.pexels.com/photos/1375736/pexels-photo-1375736.jpeg?auto=compress&cs=tinysrgb&w=500", + "author": { + "name": "Rajiv Mehta", + "avatar": "https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg?auto=compress&cs=tinysrgb&w=500", + "role": "Natural Dye Expert" + }, + "date": "2023-09-22", + "readTime": "7 min read", + "tags": [ + "Eco-friendly", + "Sustainability", + "Natural" + ], + "category": "Sustainable Practices" + }, + { + "id": "3", + "title": "The Weavers of Varanasi", + "excerpt": "A journey into the world of Varanasi's master weavers who create the exquisite Banarasi silk sarees.", + "content": "Full content would be here...", + "image": "https://images.pexels.com/photos/942803/pexels-photo-942803.jpeg?auto=compress&cs=tinysrgb&w=500", + "author": { + "name": "Anjali Patel", + "avatar": "https://images.pexels.com/photos/3785077/pexels-photo-3785077.jpeg?auto=compress&cs=tinysrgb&w=500", + "role": "Textile Historian" + }, + "date": "2023-08-30", + "readTime": "8 min read", + "tags": [ + "Weaving", + "Silk", + "Heritage" + ], + "category": "Artisan Stories" + }, + { + "id": "4", + "title": "Reviving Ancient Embroidery Techniques", + "excerpt": "How contemporary designers are working with artisans to preserve and modernize traditional embroidery methods.", + "content": "Full content would be here...", + "image": "https://images.pexels.com/photos/6347892/pexels-photo-6347892.jpeg?auto=compress&cs=tinysrgb&w=500", + "author": { + "name": "Sanjay Kumar", + "avatar": "https://images.pexels.com/photos/2182970/pexels-photo-2182970.jpeg?auto=compress&cs=tinysrgb&w=500", + "role": "Fashion Designer" + }, + "date": "2023-08-15", + "readTime": "6 min read", + "tags": [ + "Embroidery", + "Design", + "Revival" + ], + "category": "Design Innovation" + } + ], + "categories": [ + "Artisan Techniques", + "Sustainable Practices", + "Artisan Stories", + "Design Innovation", + "Cultural Heritage" + ], + "tags": [ + "Textiles", + "Craft", + "Heritage", + "Eco-friendly", + "Sustainability", + "Natural", + "Weaving", + "Silk", + "Embroidery", + "Design", + "Revival" + ] +} \ No newline at end of file diff --git a/src/types/blogs.tsx b/src/types/blogs.tsx new file mode 100644 index 0000000..ad1e83c --- /dev/null +++ b/src/types/blogs.tsx @@ -0,0 +1,24 @@ +export interface BlogAuthor { + name: string; + avatar: string; + role: string; +} + +export interface BlogPost { + id: string; + title: string; + excerpt: string; + content: string; + image: string; + author: BlogAuthor; + date: string; + readTime: string; + tags: string[]; + category: string; +} + +export interface BlogData { + posts: BlogPost[]; + categories: string[]; + tags: string[]; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3426bcb..2ac6d7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -173,7 +173,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.28.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 @@ -661,6 +661,22 @@ __metadata: languageName: node linkType: hard +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -728,49 +744,49 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/core-downloads-tracker@npm:7.3.2" - checksum: 10c0/8549ac661e07926e1c1de2664ad50f68fb4f3f6050f3cfe7bf2e8a7ceaefde99c1615f4ab5185dff22a7d72874d1dcc5fdc3651d08ed4eb1abfb798629f3991f +"@mui/core-downloads-tracker@npm:^7.3.5": + version: 7.3.5 + resolution: "@mui/core-downloads-tracker@npm:7.3.5" + checksum: 10c0/72c71d43b3609ccd5eab5b3bfc5bfc2232b79cfb210cb64a66298de0b2effccb1843aa8cdb6e062bc6f5df91c02d70de84984bb6fab9745ffdf00e81a574dc9b languageName: node linkType: hard "@mui/icons-material@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/icons-material@npm:7.3.2" + version: 7.3.5 + resolution: "@mui/icons-material@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" + "@babel/runtime": "npm:^7.28.4" peerDependencies: - "@mui/material": ^7.3.2 + "@mui/material": ^7.3.5 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/25d1f0dcbbb4a35f320aac03317657b15631f9d5b9d48d07e34850d61253a2b4d8dd5e5ca624d76f44b5ceda471ea57d2611f6627fec1eef9fce78855ec01cbf + checksum: 10c0/5fa551acabc8eddf30113a45a7c7b9923ec0f520608442cb914345493208aaa5b4ba852ad4b766ab58c547a2eb498ab4d6c1a3ebf5d4055a006c2327e397f88c languageName: node linkType: hard "@mui/material@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/material@npm:7.3.2" + version: 7.3.5 + resolution: "@mui/material@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" - "@mui/core-downloads-tracker": "npm:^7.3.2" - "@mui/system": "npm:^7.3.2" - "@mui/types": "npm:^7.4.6" - "@mui/utils": "npm:^7.3.2" + "@babel/runtime": "npm:^7.28.4" + "@mui/core-downloads-tracker": "npm:^7.3.5" + "@mui/system": "npm:^7.3.5" + "@mui/types": "npm:^7.4.8" + "@mui/utils": "npm:^7.3.5" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.12" clsx: "npm:^2.1.1" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" - react-is: "npm:^19.1.1" + react-is: "npm:^19.2.0" react-transition-group: "npm:^4.4.5" peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material-pigment-css": ^7.3.2 + "@mui/material-pigment-css": ^7.3.5 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -783,16 +799,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/4b82a65af93fe9517991f45c2f9dc127728199921f5c4c5cd7a8cd48e1c89ba17799011440f1b7e32993871c13b3044878a3170ddd9ce0e7cfe5ca0e7e3613f2 + checksum: 10c0/363ff79d0eaf8044510529bf8d143d7e6f5298297dcfbd0816c5caf49e9514bde6b187b0e135cc1d73ac2ecfec1b06d3fea3bba952e7d8d0bfc997d0510fff8a languageName: node linkType: hard -"@mui/private-theming@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/private-theming@npm:7.3.2" +"@mui/private-theming@npm:^7.3.5": + version: 7.3.5 + resolution: "@mui/private-theming@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" - "@mui/utils": "npm:^7.3.2" + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -800,15 +816,15 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/fb6067e92a1bc02d4b2b49fa58901200ccf4b79760a0227bf2859bd2cb99c46ba0f43ece9494eaa220710703eaf309a7c6e732daf4176520c0a16e1407846399 + checksum: 10c0/1347cf2a1ec1ae93d26134143c20314d53dac61fe5c8c7aa00ab37d9e89f5e6245f787dee9b0bf3d34fc614c9a5da1f5d45759fcd2520ddef4c10e755c4abc5e languageName: node linkType: hard -"@mui/styled-engine@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/styled-engine@npm:7.3.2" +"@mui/styled-engine@npm:^7.3.5": + version: 7.3.5 + resolution: "@mui/styled-engine@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" + "@babel/runtime": "npm:^7.28.4" "@emotion/cache": "npm:^11.14.0" "@emotion/serialize": "npm:^1.3.3" "@emotion/sheet": "npm:^1.4.0" @@ -823,19 +839,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10c0/d5644b40269a70a1c86844f7301aa6865289994e7835b471f3503e67795010d5334362cfd21d8804f54e8b71d6c9c932ca78bafc2325767e3abbe037f9e8e10b + checksum: 10c0/01dc8aefde58d5257564b7fd40f37de0f76d79e6bc6b52738cf41c333a053623baf2648f0557fb4b5ded306fd2b98e94797d7e48ad1c1f297747d2a265e22ad0 languageName: node linkType: hard -"@mui/system@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/system@npm:7.3.2" +"@mui/system@npm:^7.3.5": + version: 7.3.5 + resolution: "@mui/system@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" - "@mui/private-theming": "npm:^7.3.2" - "@mui/styled-engine": "npm:^7.3.2" - "@mui/types": "npm:^7.4.6" - "@mui/utils": "npm:^7.3.2" + "@babel/runtime": "npm:^7.28.4" + "@mui/private-theming": "npm:^7.3.5" + "@mui/styled-engine": "npm:^7.3.5" + "@mui/types": "npm:^7.4.8" + "@mui/utils": "npm:^7.3.5" clsx: "npm:^2.1.1" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -851,41 +867,41 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/ed385c37f29a8d4b57bc1c59f8bc06a3e4cc393d86a6e0059229eacc7c96bcb11ae80369de0e459971bde24bdd33078f5578f152f0ac2e796222b269a80833ed + checksum: 10c0/12ed6e0f4770848c91c066b6ffb315f2cd31d6281ff12780f8d994d5b677750277812491ba502831601bbe66cbc48812268ae08b7bbc10120e7faf5616807489 languageName: node linkType: hard -"@mui/types@npm:^7.4.6": - version: 7.4.6 - resolution: "@mui/types@npm:7.4.6" +"@mui/types@npm:^7.4.8": + version: 7.4.8 + resolution: "@mui/types@npm:7.4.8" dependencies: - "@babel/runtime": "npm:^7.28.3" + "@babel/runtime": "npm:^7.28.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/baa901e410591d0216b3f959cdbf5a1ee2ce560726d2fba1c700b40f64c1be3e63bd799f1b30a7d0bc8cc45a46d782928ea28d9906d64438f21e305884c48a99 + checksum: 10c0/dfdca47c894da372236f7c209544abd2998a77af646baf28d97a49313064b293b2fb434e45f9e5331e3123f9f8863f7b3e1db8542d7bde2d4f1e5f030d85f0c1 languageName: node linkType: hard -"@mui/utils@npm:^7.3.2": - version: 7.3.2 - resolution: "@mui/utils@npm:7.3.2" +"@mui/utils@npm:^7.3.5": + version: 7.3.5 + resolution: "@mui/utils@npm:7.3.5" dependencies: - "@babel/runtime": "npm:^7.28.3" - "@mui/types": "npm:^7.4.6" + "@babel/runtime": "npm:^7.28.4" + "@mui/types": "npm:^7.4.8" "@types/prop-types": "npm:^15.7.15" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^19.1.1" + react-is: "npm:^19.2.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/5a88ff08a823b976421f8d61098d56d527d95c222800d5b3f71acff795e7b0db6b02e40228773a6ed7ee22d8eaa607d816215b5a4b6497c21aaa9668c2699b56 + checksum: 10c0/c9f9ce12a5053d7aeafd0c390e7d17d331e0366dec9d993c9ad860f78c1d9410e5a33c40601afa039f4120ea299d2a59e76eff705359c7d96fb09ce636ba72b9 languageName: node linkType: hard @@ -916,16 +932,16 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" + lru-cache: "npm:^11.2.1" socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 languageName: node linkType: hard @@ -938,13 +954,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - "@popperjs/core@npm:^2.11.8": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" @@ -1355,10 +1364,10 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^3.0.0": - version: 3.0.1 - resolution: "abbrev@npm:3.0.1" - checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 languageName: node linkType: hard @@ -1535,6 +1544,13 @@ __metadata: languageName: node linkType: hard +"attr-accept@npm:^2.2.4": + version: 2.2.5 + resolution: "attr-accept@npm:2.2.5" + checksum: 10c0/9b4cb82213925cab2d568f71b3f1c7a7778f9192829aac39a281e5418cd00c04a88f873eb89f187e0bf786fa34f8d52936f178e62cbefb9254d57ecd88ada99b + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -1604,23 +1620,22 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" +"cacache@npm:^20.0.1": + version: 20.0.1 + resolution: "cacache@npm:20.0.1" dependencies: "@npmcli/fs": "npm:^4.0.0" fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" + glob: "npm:^11.0.3" + lru-cache: "npm:^11.1.0" minipass: "npm:^7.0.3" minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" p-map: "npm:^7.0.2" ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" unique-filename: "npm:^4.0.0" - checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + checksum: 10c0/e3efcf3af1c984e6e59e03372d9289861736a572e6e05b620606b87a67e71d04cff6dbc99607801cb21bcaae1fb4fb84d4cc8e3fda725e95881329ef03dac602 languageName: node linkType: hard @@ -1731,6 +1746,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.1": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b + languageName: node + linkType: hard + "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -1795,7 +1817,19 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": +"debug@npm:4": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -1918,11 +1952,11 @@ __metadata: linkType: hard "error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" dependencies: is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 languageName: node linkType: hard @@ -2334,9 +2368,9 @@ __metadata: linkType: hard "exponential-backoff@npm:^3.1.1": - version: 3.1.2 - resolution: "exponential-backoff@npm:3.1.2" - checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 languageName: node linkType: hard @@ -2404,6 +2438,15 @@ __metadata: languageName: node linkType: hard +"file-selector@npm:^2.1.0": + version: 2.1.2 + resolution: "file-selector@npm:2.1.2" + dependencies: + tslib: "npm:^2.7.0" + checksum: 10c0/fe827e0e95410aacfcc3eabc38c29cc36055257f03c1c06b631a2b5af9730c142ad2c52f5d64724d02231709617bda984701f52bd1f4b7aca50fb6585a27c1d2 + languageName: node + linkType: hard + "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -2456,7 +2499,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": +"foreground-child@npm:^3.3.1": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -2586,19 +2629,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:^11.0.3": + version: 11.0.3 + resolution: "glob@npm:11.0.3" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.0.3" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" + path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 languageName: node linkType: hard @@ -2792,9 +2835,9 @@ __metadata: linkType: hard "ip-address@npm:^10.0.1": - version: 10.0.1 - resolution: "ip-address@npm:10.0.1" - checksum: 10c0/1634d79dae18394004775cb6d699dc46b7c23df6d2083164025a2b15240c1164fccde53d0e08bd5ee4fc53913d033ab6b5e395a809ad4b956a940c446e948843 + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 languageName: node linkType: hard @@ -2855,7 +2898,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -3079,16 +3122,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" dependencies: "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 languageName: node linkType: hard @@ -3221,10 +3260,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.2 + resolution: "lru-cache@npm:11.2.2" + checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d languageName: node linkType: hard @@ -3237,22 +3276,22 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" http-cache-semantics: "npm:^4.1.1" minipass: "npm:^7.0.2" - minipass-fetch: "npm:^4.0.0" + minipass-fetch: "npm:^5.0.0" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" negotiator: "npm:^1.0.0" - proc-log: "npm:^5.0.0" + proc-log: "npm:^6.0.0" promise-retry: "npm:^2.0.1" - ssri: "npm:^12.0.0" - checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 languageName: node linkType: hard @@ -3280,6 +3319,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.3": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + "minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -3307,9 +3355,9 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^4.0.0": - version: 4.0.1 - resolution: "minipass-fetch@npm:4.0.1" +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" @@ -3318,7 +3366,7 @@ __metadata: dependenciesMeta: encoding: optional: true - checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 languageName: node linkType: hard @@ -3358,28 +3406,19 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 languageName: node linkType: hard -"minizlib@npm:^3.0.1": - version: 3.0.2 - resolution: "minizlib@npm:3.0.2" +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" dependencies: minipass: "npm:^7.1.2" - checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 - languageName: node - linkType: hard - -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec languageName: node linkType: hard @@ -3414,22 +3453,22 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.4.2 - resolution: "node-gyp@npm:11.4.2" + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" semver: "npm:^7.3.5" - tar: "npm:^7.4.3" + tar: "npm:^7.5.2" tinyglobby: "npm:^0.2.12" - which: "npm:^5.0.0" + which: "npm:^6.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/0bfd3e96770ed70f07798d881dd37b4267708966d868a0e585986baac487d9cf5831285579fd629a83dc4e434f53e6416ce301097f2ee464cb74d377e4d8bdbe + checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 languageName: node linkType: hard @@ -3440,14 +3479,14 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^8.0.0": - version: 8.1.0 - resolution: "nopt@npm:8.1.0" +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" dependencies: - abbrev: "npm:^3.0.0" + abbrev: "npm:^4.0.0" bin: nopt: bin/nopt.js - checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd languageName: node linkType: hard @@ -3566,9 +3605,9 @@ __metadata: linkType: hard "p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd languageName: node linkType: hard @@ -3621,13 +3660,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 languageName: node linkType: hard @@ -3684,10 +3723,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 +"proc-log@npm:^6.0.0": + version: 6.0.0 + resolution: "proc-log@npm:6.0.0" + checksum: 10c0/40c5e2b4c55e395a3bd72e38cba9c26e58598a1f4844fa6a115716d5231a0919f46aa8e351147035d91583ad39a794593615078c948bc001fe3beb99276be776 languageName: node linkType: hard @@ -3737,6 +3776,19 @@ __metadata: languageName: node linkType: hard +"react-dropzone@npm:^14.3.8": + version: 14.3.8 + resolution: "react-dropzone@npm:14.3.8" + dependencies: + attr-accept: "npm:^2.2.4" + file-selector: "npm:^2.1.0" + prop-types: "npm:^15.8.1" + peerDependencies: + react: ">= 16.8 || 18.0.0" + checksum: 10c0/e17b1832783cda7b8824fe9370e99185d1abbdd5e4980b2985d6321c5768c8de18ff7b9ad550c809ee9743269dea608ff74d5208062754ce8377ad022897b278 + languageName: node + linkType: hard + "react-intersection-observer@npm:^9.16.0": version: 9.16.0 resolution: "react-intersection-observer@npm:9.16.0" @@ -3757,10 +3809,10 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^19.1.1": - version: 19.1.1 - resolution: "react-is@npm:19.1.1" - checksum: 10c0/3dba763fcd69835ae263dcd6727d7ffcc44c1d616f04b7329e67aefdc66a567af4f8dcecdd29454c7a707c968aa1eb85083a83fb616f01675ef25e71cf082f97 +"react-is@npm:^19.2.0": + version: 19.2.0 + resolution: "react-is@npm:19.2.0" + checksum: 10c0/a63cb346aeced8ac0e671b0f9b33720d2906de02a066ca067075d871a5d4c64cdb328f495baf9b5842d5868c0d5edd1ce18465a7358b52f4b6aa983479c9bfa2 languageName: node linkType: hard @@ -3771,6 +3823,34 @@ __metadata: languageName: node linkType: hard +"react-router-dom@npm:^7.8.2": + version: 7.9.6 + resolution: "react-router-dom@npm:7.9.6" + dependencies: + react-router: "npm:7.9.6" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/63984c46385da232655b9e3a8a99f6dd7b94c36827be6e954f246c362f83740b5f59b1de99cae81da3b0cef2220d701dcc22e4fafb4a84600541e1c0450b9d57 + languageName: node + linkType: hard + +"react-router@npm:7.9.6": + version: 7.9.6 + resolution: "react-router@npm:7.9.6" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/2a177bbe19021e3b8211df849ea5b3f3a4f482327e6de3341aaeaa4f1406dc9be7b675b229eefea6761e04a59a40ccaaf8188f2ee88eb2d0b2a6b6448daea368 + languageName: node + linkType: hard + "react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -3831,15 +3911,15 @@ __metadata: linkType: hard "resolve@npm:^1.19.0": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" + version: 1.22.11 + resolution: "resolve@npm:1.22.11" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 languageName: node linkType: hard @@ -3857,15 +3937,15 @@ __metadata: linkType: hard "resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.16.1" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 languageName: node linkType: hard @@ -4040,7 +4120,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.6.0": +"semver@npm:^7.3.5": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"semver@npm:^7.6.0": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -4049,6 +4138,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + "set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -4208,6 +4304,15 @@ __metadata: languageName: node linkType: hard +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 + languageName: node + linkType: hard + "stop-iteration-iterator@npm:^1.1.0": version: 1.1.0 resolution: "stop-iteration-iterator@npm:1.1.0" @@ -4357,17 +4462,16 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" - minizlib: "npm:^3.0.1" - mkdirp: "npm:^3.0.1" + minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 languageName: node linkType: hard @@ -4392,7 +4496,9 @@ __metadata: globals: "npm:^16.3.0" react: "npm:^19.1.1" react-dom: "npm:^19.1.1" + react-dropzone: "npm:^14.3.8" react-intersection-observer: "npm:^9.16.0" + react-router-dom: "npm:^7.8.2" typescript: "npm:~5.8.3" typescript-eslint: "npm:^8.39.1" vite: "npm:^7.1.2" @@ -4427,6 +4533,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.7.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -4704,14 +4817,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" dependencies: isexe: "npm:^3.1.1" bin: node-which: bin/which.js - checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 languageName: node linkType: hard