diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 00000000..74edea0e --- /dev/null +++ b/.github/workflows/production.yml @@ -0,0 +1,29 @@ +name: Vercel Production Deployment + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + +on: + push: + branches: + - main + +jobs: + Deploy-Production: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v3 + + - name: Install Vercel CLI + run: npm install --global vercel + + - name: Pull Vercel Environment Information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/package-lock.json b/package-lock.json index 8d91ee86..7fe023bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,30 +8,35 @@ "name": "neurolearn_frontend", "version": "0.1.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@supabase/supabase-js": "^2.49.4", "autoprefixer": "^10.4.21", "axios": "^1.8.4", + "framer-motion": "^12.12.1", "next": "15.3.1", "postcss": "^8.5.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "@commitlint/cli": "^19.8.0", - "@commitlint/config-conventional": "^19.8.0", + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/webpack": "^5.28.5", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "eslint": "^9", "eslint-config-next": "15.3.1", "eslint-config-prettier": "^10.1.2", "eslint-plugin-prettier": "^5.2.6", - "husky": "^9.1.7", - "lint-staged": "^15.5.1", + "husky": "^9.0.11", + "lint-staged": "^15.2.2", "prettier": "^3.5.3", "tailwindcss": "^4.1.4", "typescript": "^5" @@ -665,6 +670,52 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1108,6 +1159,70 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "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" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", @@ -1718,6 +1833,28 @@ "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1774,6 +1911,18 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/webpack": { + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", + "integrity": "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -2242,6 +2391,181 @@ "win32" ] }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -2282,6 +2606,61 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -2689,6 +3068,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -2796,6 +3182,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -3513,6 +3909,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -4070,6 +4473,16 @@ "dev": true, "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -4307,6 +4720,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.12.1.tgz", + "integrity": "sha512-PFw4/GCREHI2suK/NlPSUxd+x6Rkp80uQsfCRFSOQNrm5pZif7eGtmG1VaD/UF1fW9tRBy5AaS77StatB3OJDg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.12.1", + "motion-utils": "^12.12.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4482,6 +4922,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -5241,6 +5688,37 @@ "node": ">= 0.4" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -5255,7 +5733,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": { @@ -5717,6 +6194,16 @@ "node": ">=18.0.0" } }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5866,7 +6353,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" @@ -5998,6 +6484,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.12.1.tgz", + "integrity": "sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.12.1" + } + }, + "node_modules/motion-utils": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", + "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6046,6 +6547,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/next": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/next/-/next-15.3.1.tgz", @@ -6176,7 +6684,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" @@ -6567,7 +7074,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", @@ -6612,6 +7118,16 @@ ], "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -6637,7 +7153,6 @@ "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/reflect.getprototypeof": { @@ -6840,6 +7355,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6881,6 +7417,50 @@ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "license": "MIT" }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -6894,6 +7474,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -7146,6 +7736,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "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", @@ -7155,6 +7755,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -7463,6 +8074,67 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.39.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.1.tgz", + "integrity": "sha512-Mm6+uad0ZuDtcV8/4uOZQDQ8RuiC5Pu+iZRedJtF7yA/27sPL7d++In/AJKpWZlU3SYMPPkVfwetn6sgZ66pUA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -7801,12 +8473,108 @@ "punycode": "^2.1.0" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/webpack": { + "version": "5.99.8", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", + "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index e20b867b..9e07f72d 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,13 @@ "test-all": "npm run check-format && npm run check-lint && npm run check-types && npm run build" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@supabase/supabase-js": "^2.49.4", "autoprefixer": "^10.4.21", "axios": "^1.8.4", + "framer-motion": "^12.12.1", "next": "15.3.1", "postcss": "^8.5.3", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70b7442b..d45e67c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@fortawesome/fontawesome-svg-core': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/free-brands-svg-icons': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/react-fontawesome': + specifier: ^0.2.2 + version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.1.0) '@supabase/supabase-js': specifier: ^2.49.4 version: 2.49.4 @@ -17,6 +26,9 @@ importers: axios: specifier: ^1.8.4 version: 1.8.4 + framer-motion: + specifier: ^12.12.1 + version: 12.12.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 15.3.1 version: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -218,6 +230,24 @@ packages: resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fortawesome/fontawesome-common-types@6.7.2': + resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.7.2': + resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==} + engines: {node: '>=6'} + + '@fortawesome/free-brands-svg-icons@6.7.2': + resolution: {integrity: sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==} + engines: {node: '>=6'} + + '@fortawesome/react-fontawesome@0.2.2': + resolution: {integrity: sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~1 || ~6 + react: '>=16.3' + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1444,6 +1474,20 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + framer-motion@12.12.1: + resolution: {integrity: sha512-PFw4/GCREHI2suK/NlPSUxd+x6Rkp80uQsfCRFSOQNrm5pZif7eGtmG1VaD/UF1fW9tRBy5AaS77StatB3OJDg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1949,6 +1993,12 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + motion-dom@12.12.1: + resolution: {integrity: sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ==} + + motion-utils@12.12.1: + resolution: {integrity: sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2801,6 +2851,22 @@ snapshots: '@eslint/core': 0.13.0 levn: 0.4.1 + '@fortawesome/fontawesome-common-types@6.7.2': {} + + '@fortawesome/fontawesome-svg-core@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/free-brands-svg-icons@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.1.0)': + dependencies: + '@fortawesome/fontawesome-svg-core': 6.7.2 + prop-types: 15.8.1 + react: 19.1.0 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -4152,6 +4218,15 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@12.12.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + motion-dom: 12.12.1 + motion-utils: 12.12.1 + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -4627,6 +4702,12 @@ snapshots: minimist@1.2.8: {} + motion-dom@12.12.1: + dependencies: + motion-utils: 12.12.1 + + motion-utils@12.12.1: {} + ms@2.1.3: {} nanoid@3.3.11: {} diff --git a/public/assets/home/Alarm.svg b/public/assets/home/Alarm.svg new file mode 100644 index 00000000..eb6dbd3d --- /dev/null +++ b/public/assets/home/Alarm.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/home/Arrow.svg b/public/assets/home/Arrow.svg new file mode 100644 index 00000000..450b8221 --- /dev/null +++ b/public/assets/home/Arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/home/Facebook.svg b/public/assets/home/Facebook.svg new file mode 100644 index 00000000..9eee60f4 --- /dev/null +++ b/public/assets/home/Facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/home/Heart.svg b/public/assets/home/Heart.svg new file mode 100644 index 00000000..34ce4597 --- /dev/null +++ b/public/assets/home/Heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/home/HeroSection.png b/public/assets/home/HeroSection.png new file mode 100644 index 00000000..73530d80 Binary files /dev/null and b/public/assets/home/HeroSection.png differ diff --git a/public/assets/home/Linkedin.svg b/public/assets/home/Linkedin.svg new file mode 100644 index 00000000..7d33079e --- /dev/null +++ b/public/assets/home/Linkedin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/home/Mail.svg b/public/assets/home/Mail.svg new file mode 100644 index 00000000..751c0659 --- /dev/null +++ b/public/assets/home/Mail.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/assets/home/TopTrend.png b/public/assets/home/TopTrend.png new file mode 100644 index 00000000..2dbe78b0 Binary files /dev/null and b/public/assets/home/TopTrend.png differ diff --git a/src/app/ClientLayout.tsx b/src/app/ClientLayout.tsx new file mode 100644 index 00000000..f78aa5a7 --- /dev/null +++ b/src/app/ClientLayout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import ModalContainer from '@/components/ModalContainer'; +import { ModalProvider } from '@/context/ModalContext'; +import '@/lib/fontawesome'; + +export default function ClientLayout({ children }: { readonly children: React.ReactNode }) { + return ( + <> + + {children} + + + + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d3ef21d1..418b07ec 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; import './globals.css'; +import ClientLayout from './ClientLayout'; const geistSans = Geist({ variable: '--font-geist-sans', @@ -13,8 +14,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: 'Academix', + description: 'AI-Powered Training & Market', }; export default function RootLayout({ @@ -24,7 +25,9 @@ export default function RootLayout({ }>) { return ( - {children} + + {children} + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 2561492e..7d8aff93 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,8 +1,19 @@ -import Image from 'next/image'; -import Link from 'next/link'; +'use client'; -// Sample course data -const courses = [ +import { Course } from '@/types/course'; +import Header from '@/components/layout/Header'; +import HeroSection from '@/components/home/HeroSection'; +import CourseGrid from '@/components/home/CourseGrid'; +import WhyStudySection from '@/components/home/WhyStudySection'; +import LearningFocusSection from '@/components/home/LearningFocusSection'; +import ExpertsSection from '@/components/home/ExpertsSection'; +import FutureOfWorkSection from '@/components/home/FutureOfWorkSection'; +import PersonalityTestSection from '@/components/home/PersonalityTestSection'; +import VideoSection from '@/components/home/VideoSection'; +import CategoriesSection from '@/components/home/CategoriesSection'; +import Footer from '@/components/layout/Footer'; + +const courses: Course[] = [ { id: '1', title: 'Thiết kế đồ họa', @@ -69,358 +80,45 @@ const courses = [ }, ]; -interface Course { - id: string; - title: string; - description: string; - imageUrl: string; - price: number; - teacherId: string; - teacherName: string; - rating: number; - totalStudents: number; - level: string; - category: string; - topics: string[]; - createdAt: string; - updatedAt: string; -} - -// Component for course card grid -const CourseGrid = ({ title, courses }: { title: string; courses: Course[] }) => ( -
-

{title}

-
- {courses.map((course: Course) => ( -
-
- {course.title} -
-
-

{course.title}

-

bởi {course.teacherName}

-
-
- - {course.rating?.toFixed(1)} -
- {course.totalStudents} học viên -
-
- ${course.price.toFixed(2)} - - Chi tiết - -
-
-
- ))} -
-
-); - export default function Home() { + return (
{/* Header */} -
-
-
- Udemy - -
-
-
- -
- Đăng nhập - - Đăng ký - -
-
-
+
{/* Hero Section */} -
-
-
-

- All The Skills You Need In One Place -

-

- From coding skills to business topics. - Udemy helps expand your professional development. -

- - Khám phá - -
-
-
- Students learning -
- 800+ Khóa học -
-
-
-
-
+ {/* Categories */} -
-
-
- {['Tất cả', 'IT Certifications', 'Leadership', 'Web Development', 'Communication', 'Business Analytics & Intelligence'].map((category) => ( - - ))} -
-
-
+ - {/* Course Listings */} -
- - - {/* Why Study Section */} -
-

Why do you study on EDUIO?

-
-
-

- 01 - - - -

-

Learn from Top Industry Experts

-
-
-

- 02 - - - -

-

Reasonable Cost, Exceptional Value

-
-
-

- 03 - - - -

-

Flexible Learning, Anytime, Anywhere

-
-
-
- - - - {/* Learning Focused */} -
-

Learning focused on your goals

-
-
-

- Hands-on training - - - -

-
-
-

- Certification prep - - - -

-
-
-

- Containerization - - - -

-
-
-
- - {/* Our Experts */} -
-

Our Experts

-
- {[1, 2, 3].map((i) => ( -
-
- Expert -
-

Tuyết Trinh

-

Academic Director

-
- - - -
- - View Profile - - - - -
- ))} -
-
- - {/* Future of Work */} -
-

Top Trends For The Future Of Work

-

Get the skills needed to stay current in today's rapidly changing workplace.

- - Get Started - -
- - {/* Personality Test */} -
-
-

Test your personality and interests

-

Explore your strengths, preferences, and discover your growth path with this quick personality quiz.

-
- - - - - - - -
-
- REALISTIC - CONVENTIONAL - INVESTIGATIVE - ENTERPRISING - ARTISTIC - SOCIAL -
- - Explore Now - -
-
- - {/* Video Section */} -
-

Top Trends For The Future Of Work

-
-
-
- - - -
-
-
-
-
+ {/* Popular Courses */} + + + {/* Why Study Section */} + + + {/* Learners are viewing */} + + + {/* Learning Focused */} + + + {/* Our Experts */} + + + {/* Future of Work Section */} + + + {/* Personality Test Section */} + + + {/* Video Section */} + {/* Footer */} -
-
-
-

We would love to hear from you.

-
- - -
-
-
-
-

Contact us

-

info@eduio.com

-

+84 123 456 789

-

123 Education St, Hanoi, Vietnam

-
-
-

Follow us

-
- - - - -
-
-
-

Services

-
    -
  • Online Courses
  • -
  • Live Webinars
  • -
  • Certification
  • -
  • Career Guidance
  • -
-
-
-
- © 2023 EDUIO. All rights reserved. -
-
-
+
); } diff --git a/src/components/BubbleButton.tsx b/src/components/BubbleButton.tsx new file mode 100644 index 00000000..e53a7573 --- /dev/null +++ b/src/components/BubbleButton.tsx @@ -0,0 +1,97 @@ +import React, { ReactNode, MouseEvent } from 'react'; + +interface BubbleButtonProps { + children: ReactNode; + onClick?: (e: MouseEvent) => void; + type?: 'button' | 'submit' | 'reset'; + disabled?: boolean; +} + +const BubbleButton: React.FC = ({ + children, + onClick, + type = 'button', + disabled = false, +}) => { + const handleClick = (e: MouseEvent) => { + if (!disabled && onClick) { + onClick(e); + } + }; + + return ( + + ); +}; + +export default BubbleButton; diff --git a/src/components/CTAButton.tsx b/src/components/CTAButton.tsx new file mode 100644 index 00000000..545e547b --- /dev/null +++ b/src/components/CTAButton.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +interface CtaButtonProps { + text: string; + onClick?: () => void; +} + +export default function CtaButton({ text, onClick }: CtaButtonProps) { + return ( + + ); +} diff --git a/src/components/CourseCard.tsx b/src/components/CourseCard.tsx deleted file mode 100644 index 11f5fc09..00000000 --- a/src/components/CourseCard.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { Course } from '@/types/course'; - -interface CourseCardProps { - readonly course: Course; - readonly isTeacher?: boolean; - readonly onEdit?: (id: string) => void; - readonly onDelete?: (id: string) => void; - readonly onEnroll?: (id: string) => void; -} - -export default function CourseCard({ - course, - isTeacher = false, - onEdit, - onDelete, - onEnroll, -}: CourseCardProps) { - return ( -
-
- {course.title} -
-
-

{course.title}

-

by {course.teacherName}

-
- {course.rating && ( -
- - {course.rating.toFixed(1)} -
- )} - {course.totalStudents && ( - {course.totalStudents} students - )} -
-

{course.description}

-
- ${course.price.toFixed(2)} -
- {isTeacher ? ( - <> - - - - ) : ( - - )} - - Details - -
-
-
-
- ); -} diff --git a/src/components/ModalContainer.tsx b/src/components/ModalContainer.tsx new file mode 100644 index 00000000..1167c7a1 --- /dev/null +++ b/src/components/ModalContainer.tsx @@ -0,0 +1,16 @@ +import { useModal } from '@/context/ModalContext'; +import SignInUpForm from './SignInSignUp'; + +export default function ModalContainer() { + const { modalType, hideModal } = useModal(); + + if (!modalType) return null; + + return ( +
+ {(modalType === 'register' || modalType === 'login') && ( + + )} +
+ ); +} diff --git a/src/components/SignInSignUp.tsx b/src/components/SignInSignUp.tsx new file mode 100644 index 00000000..bb73f978 --- /dev/null +++ b/src/components/SignInSignUp.tsx @@ -0,0 +1,160 @@ +import { useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faGoogle } from '@fortawesome/free-brands-svg-icons'; + +interface SignInUpFormProps { + onClose: () => void; + defaultToSignUp?: boolean; +} + +export default function SignInUpForm({ onClose, defaultToSignUp }: SignInUpFormProps) { + const [isRightPanelActive, setIsRightPanelActive] = useState(defaultToSignUp ?? false); + + const handleSignUpClick = () => { + setIsRightPanelActive(true); + }; + + const handleSignInClick = () => { + setIsRightPanelActive(false); + }; + + return ( +
+
+ + {/* Sign Up Form */} +
+
+

Create Account

+
+ + + +
+ or use your email for registration + + + + +
+
+ + {/* Sign In Form */} +
+
+

Sign in

+
+ + + +
+ or use your account + + + + Forgot your password? + + +
+
+ + {/* Overlay Container */} +
+
+
+

Welcome Back!

+

+ To keep connected with us please login with your personal info +

+ +
+
+

Hello, Friend!

+

+ Enter your personal details and start journey with us +

+ +
+
+
+
+
+ ); +} diff --git a/src/components/animations/AnimatedSection.tsx b/src/components/animations/AnimatedSection.tsx new file mode 100644 index 00000000..17a403ea --- /dev/null +++ b/src/components/animations/AnimatedSection.tsx @@ -0,0 +1,36 @@ +import { ReactNode } from 'react'; +import { motion, Variants } from 'framer-motion'; +import { useInView } from 'framer-motion'; +import { useRef } from 'react'; + +interface AnimatedSectionProps { + children: ReactNode; + variants: Variants; + className?: string; + delay?: number; + once?: boolean; +} + +export default function AnimatedSection({ + children, + variants, + className = '', + delay = 0, + once = true +}: AnimatedSectionProps) { + const ref = useRef(null); + const isInView = useInView(ref, { once }); + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx new file mode 100644 index 00000000..13dcde35 --- /dev/null +++ b/src/components/common/Button.tsx @@ -0,0 +1,56 @@ +"use client"; + +import Link from 'next/link'; +import { ReactNode } from 'react'; + +interface ButtonProps { + href?: string; + onClick?: () => void; + children: ReactNode; + variant?: 'primary' | 'secondary' | 'outline'; + className?: string; + showArrow?: boolean; +} + +const Button = ({ + href, + onClick, + children, + variant = 'primary', + className = '', + showArrow = true +}: ButtonProps) => { + const baseStyles = 'inline-flex items-center rounded-full text-center'; + + const variantStyles = { + primary: 'bg-blue-600 text-white hover:bg-blue-700', + secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200', + outline: 'border border-blue-600 text-blue-600 hover:bg-blue-50' + }; + + const buttonStyles = `${baseStyles} ${variantStyles[variant]} ${className}`; + + const arrowIcon = showArrow ? ( + + + + ) : null; + + if (href) { + return ( + + {children} + {arrowIcon} + + ); + } + + return ( + + ); +}; + +export default Button; \ No newline at end of file diff --git a/src/components/common/CourseCard.tsx b/src/components/common/CourseCard.tsx new file mode 100644 index 00000000..4b769ec1 --- /dev/null +++ b/src/components/common/CourseCard.tsx @@ -0,0 +1,97 @@ +"use client"; + +import Image from 'next/image'; +import Link from 'next/link'; +import { Course } from '@/types/course'; +import { motion } from 'framer-motion'; +import { hoverScale } from '@/utils/animations'; + +interface CourseCardProps { + readonly course: Course; +} + +export default function CourseCard({ + course, +}: CourseCardProps) { + return ( + +
+ + Hot Sale! + +
+ {course.title} +
+ +
+ Favorite +
+
+
+
+
+ + {course.level ?? 'All Levels'} + +
+

{course.title}

+

+ By {course.teacherName} +

+
+ {course.rating && ( +
+ + + + + + {course.rating.toFixed(1)} +
+ )} + {course.totalStudents && ( + + {course.totalStudents} students + + )} +
+
+ ${course.price.toFixed(2)} + + + View Details + + +
+
+
+ ); +} diff --git a/src/components/common/ExpertCard.tsx b/src/components/common/ExpertCard.tsx new file mode 100644 index 00000000..80a8bd65 --- /dev/null +++ b/src/components/common/ExpertCard.tsx @@ -0,0 +1,65 @@ +"use client"; + +import Image from 'next/image'; +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { hoverScale } from '@/utils/animations'; + +interface ExpertCardProps { + name: string; + role: string; + imageUrl: string; + profileUrl?: string; +} + +const ExpertCard = ({ name, role, imageUrl, profileUrl = '#' }: ExpertCardProps) => { + return ( + + + {name} + +

{name}

+

{role}

+
+ + + Facebook + + + + + LinkedIn + + + + + Mail + + +
+ + + View Profile + Arrow + + +
+ ); +}; + +export default ExpertCard; \ No newline at end of file diff --git a/src/components/home/CategoriesSection.tsx b/src/components/home/CategoriesSection.tsx new file mode 100644 index 00000000..68bc7b16 --- /dev/null +++ b/src/components/home/CategoriesSection.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { fadeIn, staggerContainer } from '@/utils/animations'; + +const CategoriesSection = () => { + const categories = [ + 'Tất cả', + 'IT Certifications', + 'Leadership', + 'Web Development', + 'Communication', + 'Business Analytics & Intelligence' + ]; + + return ( + +
+ + {categories.map((category, index) => ( + + {category} + + ))} + +
+
+ ); +}; + +export default CategoriesSection; \ No newline at end of file diff --git a/src/components/home/CourseGrid.tsx b/src/components/home/CourseGrid.tsx new file mode 100644 index 00000000..0e769194 --- /dev/null +++ b/src/components/home/CourseGrid.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { Course } from '@/types/course'; +import CourseCard from '@/components/common/CourseCard'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { motion } from 'framer-motion'; +import { fadeIn, staggerContainer } from '@/utils/animations'; + +interface CourseGridProps { + title: string; + courses: Course[]; +} + +const CourseGrid = ({ title, courses }: CourseGridProps) => { + return ( +
+
+ +

{title}

+
+ + {courses.map((course, index) => ( + + + + ))} + +
+
+ ); +}; + +export default CourseGrid; \ No newline at end of file diff --git a/src/components/home/ExpertsSection.tsx b/src/components/home/ExpertsSection.tsx new file mode 100644 index 00000000..9acd932b --- /dev/null +++ b/src/components/home/ExpertsSection.tsx @@ -0,0 +1,74 @@ +"use client"; + +import ExpertCard from '@/components/common/ExpertCard'; +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { fadeIn, staggerContainer } from '@/utils/animations'; + +interface ExpertsProps { + experts?: Array<{ + id: string; + name: string; + role: string; + imageUrl: string; + }>; +} + +const ExpertsSection = ({ experts }: ExpertsProps) => { + const defaultExperts = [ + { + id: '1', + name: 'Tuyết Trinh', + role: 'Academic Director', + imageUrl: '/placeholder-course.jpg', + }, + { + id: '2', + name: 'Tuyết Trinh', + role: 'Senior Instructor', + imageUrl: '/placeholder-course.jpg', + }, + { + id: '3', + name: 'Tuyết Trinh', + role: 'Technology Lead', + imageUrl: '/placeholder-course.jpg', + }, + ]; + + const displayExperts = experts || defaultExperts; + + return ( +
+
+ +

Our Experts

+
+ + {displayExperts.map((expert, index) => ( + + + + ))} + +
+
+ ); +}; + +export default ExpertsSection; \ No newline at end of file diff --git a/src/components/home/FutureOfWorkSection.tsx b/src/components/home/FutureOfWorkSection.tsx new file mode 100644 index 00000000..3e2ade76 --- /dev/null +++ b/src/components/home/FutureOfWorkSection.tsx @@ -0,0 +1,50 @@ +"use client"; + +import Button from '@/components/common/Button'; + +interface FutureOfWorkSectionProps { + title?: string; + subtitle?: string; + description?: string; + buttonText?: string; + buttonUrl?: string; +} + +const FutureOfWorkSection = ({ + title = 'Top Trends For', + subtitle = 'The Future Of Work', + description = 'Our 2025 Global Learning & Skills Trends Report is out now! Find out how to build the skills to keep pace with change.', + buttonText = 'Get Started', + buttonUrl = '/trends' +}: FutureOfWorkSectionProps) => { + return ( +
+
+
+
+
+
+
+

{title}

+

{subtitle}

+

{description}

+ +
+
+
+
+
+ ); +}; + +export default FutureOfWorkSection; \ No newline at end of file diff --git a/src/components/home/HeroSection.tsx b/src/components/home/HeroSection.tsx new file mode 100644 index 00000000..01551c9c --- /dev/null +++ b/src/components/home/HeroSection.tsx @@ -0,0 +1,67 @@ +"use client"; + +import Image from 'next/image'; +import Button from '@/components/common/Button'; +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { slideFromLeft, slideFromRight } from '@/utils/animations'; + +const HeroSection = () => { + return ( +
+
+ +

+ All The Skills You
+ Need In One Place +

+

+ From coding skills to business topics. Udemy helps
+ expand your professional development. +

+ + + +
+ +
+ Students learning + + 800 + + + + +
+
+
50%
+
+
Growth rate
+
+
+
+
+
+ ); +}; + +export default HeroSection; \ No newline at end of file diff --git a/src/components/home/LearningFocusSection.tsx b/src/components/home/LearningFocusSection.tsx new file mode 100644 index 00000000..6a4c2ee6 --- /dev/null +++ b/src/components/home/LearningFocusSection.tsx @@ -0,0 +1,73 @@ +"use client"; + +import Image from 'next/image'; +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { fadeIn, slideUp, staggerContainer } from '@/utils/animations'; + +interface FocusCardProps { + title: string; + index: number; +} + +const FocusCard = ({ title, index }: FocusCardProps) => ( + + + {title} + +
+

{title}

+
+ + Arrow + +
+); + +const LearningFocusSection = () => { + const focusAreas = [ + 'Hands-on training', + 'Certification prep', + 'Containerization' + ]; + + return ( +
+
+ +

Learning focused on your goals

+
+ + {focusAreas.map((area, index) => ( + + ))} + +
+
+ ); +}; + +export default LearningFocusSection; \ No newline at end of file diff --git a/src/components/home/PersonalityTestSection.tsx b/src/components/home/PersonalityTestSection.tsx new file mode 100644 index 00000000..8fde7077 --- /dev/null +++ b/src/components/home/PersonalityTestSection.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { slideUp } from '@/utils/animations'; +import Link from 'next/link'; + +interface PersonalityTestSectionProps { + title?: string; + buttonText?: string; + buttonUrl?: string; +} + +const PersonalityTestSection = ({ + title = 'Test your personality and interests', + buttonText = 'Explore Now', + buttonUrl = '/personality-test' +}: PersonalityTestSectionProps) => { + return ( +
+
+ + {/* Header with title and button */} +
+ + {title} + + + + + + {buttonText} + + + +
+ + {/* Main content */} +
+ {/* Left side - Empty space */} +
+ + {/* Right side - Description */} +
+ +

Explore your strengths, preferences, and discover your growth path with this quick personality quiz.

+
+
+
+ + {/* Progress indicator */} + +
+ {[0, 1, 2, 3, 4, 5].map((index) => ( + + ))} +
+ + + + {/* Personality types */} + +
+ REALISTIC + Practical, hands-on, tangible work +
+
+ CONVENTIONAL + Structured, organized, careful +
+
+ INVESTIGATIVE + Practical, hands-on, tangible work +
+
+ ENTERPRISING + Practical, hands-on, tangible work +
+
+ ARTISTIC + Practical, hands-on, tangible work +
+
+ SOCIAL + Practical, hands-on, tangible work +
+
+
+
+
+ ); +}; + +export default PersonalityTestSection; \ No newline at end of file diff --git a/src/components/home/VideoSection.tsx b/src/components/home/VideoSection.tsx new file mode 100644 index 00000000..f90f68ec --- /dev/null +++ b/src/components/home/VideoSection.tsx @@ -0,0 +1,100 @@ +"use client"; + +import React from 'react'; +import Image from 'next/image'; +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { fadeIn, slideUp } from '@/utils/animations'; + +const VideoSection: React.FC = () => { + return ( +
+
+ + + Top Trends For + + + The Future Of Work + + + + +
+ Video thumbnail +
+ + + 7:32 + + +
+ + + + + +
+
+ + +
+ Our latest course learning trends have made trainers more efficient with data-driven insights and personalized content. +
+
+
+
+ ); +}; + +export default VideoSection; \ No newline at end of file diff --git a/src/components/home/WhyStudySection.tsx b/src/components/home/WhyStudySection.tsx new file mode 100644 index 00000000..4a903b13 --- /dev/null +++ b/src/components/home/WhyStudySection.tsx @@ -0,0 +1,89 @@ +"use client"; + +import Image from 'next/image'; +import { motion } from 'framer-motion'; +import AnimatedSection from '@/components/animations/AnimatedSection'; +import { fadeIn, slideUp, staggerContainer } from '@/utils/animations'; + +interface ReasonCardProps { + number: string; + title: string; + description: string; + index: number; +} + +const ReasonCard = ({ number, title, description, index }: ReasonCardProps) => ( + +
+ + {number} + +
+ + Arrow + +
+
+

{title}

+

{description}

+
+); + +const WhyStudySection = () => { + const reasons = [ + { + number: '01', + title: 'Learn from Top Industry Experts', + description: 'Get access to high-quality content from industry professionals' + }, + { + number: '02', + title: 'Reasonable Cost, Exceptional Value', + description: 'Affordable pricing with premium features and lifetime access' + }, + { + number: '03', + title: 'Flexible Learning, Anytime, Anywhere', + description: 'Study at your own pace and according to your personal schedule' + }, + ]; + + return ( +
+
+ +

Why do you study on EDUIO?

+
+ + {reasons.map((reason, index) => ( + + ))} + +
+
+ ); +}; + +export default WhyStudySection; \ No newline at end of file diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx new file mode 100644 index 00000000..d68e53ba --- /dev/null +++ b/src/components/layout/Footer.tsx @@ -0,0 +1,78 @@ +"use client"; + +import Link from 'next/link'; +import Image from 'next/image'; +import Button from '@/components/common/Button'; +import { useModal } from '@/context/ModalContext'; + +const Footer = () => { + const { showModal } = useModal(); + + return ( +
+
+
+
+
+ + Academix + +
+

+ We would love to hear from you. +

+
+
+
+ + +
+
+
+ +
+
+

Contact us

+

info@eduio.com

+

+1 (123) 456-7890

+

123 Education St, New York, USA

+
+
+

Follow us

+
+ + Facebook + + + LinkedIn + + + Mail + +
+
+
+

Services

+
    +
  • Online Courses
  • +
  • Live Webinars
  • +
  • Certification
  • +
  • Career Guidance
  • +
+
+
+
+ © 2023 EDUIO. All rights reserved. +
+
+
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx new file mode 100644 index 00000000..22c96919 --- /dev/null +++ b/src/components/layout/Header.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; +import Button from '@/components/common/Button'; +import { useModal } from '@/context/ModalContext'; + +interface Category { + name: string; + href: string; +} + +const Header: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [isSearchActive, setIsSearchActive] = useState(false); + const [isCartOpen, setIsCartOpen] = useState(false); + const searchRef = useRef(null); + const { showModal } = useModal(); + + const categories: Category[] = [ + { name: "Programming", href: "/categories/programming" }, + { name: "Business", href: "/categories/business" }, + { name: "Design", href: "/categories/design" }, + { name: "Marketing", href: "/categories/marketing" }, + ]; + + useEffect(() => { + if (isSearchActive && searchRef.current) { + searchRef.current.focus(); + } + }, [isSearchActive]); + + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') setIsSearchActive(false); + }; + window.addEventListener("keydown", handleEsc); + return () => window.removeEventListener("keydown", handleEsc); + }, []); + + return ( + <> +
+
+ {/* Logo & Explore */} +
+ + Academix + +
+ + + {isOpen && ( +
+ {categories.map((category) => ( + setIsOpen(false)} + > + {category.name} + + ))} +
+ )} +
+
+ + {/* Actions */} +
+ {/* Search toggle */} +
+ + + {isSearchActive && ( +
+ setIsSearchActive(false)} + /> +
+ )} +
+ + {/* Cart */} + + + + +
+
+
+ + {/* Cart Drawer (Push layout) */} +
+ {/* Nội dung trang ở đây nếu bạn muốn đẩy sang trái khi cart mở */} +
+ + {isCartOpen && ( +
+
+

Your Cart

+ +
+

Your cart is empty.

+ +
+ )} + + ); +}; + +export default Header; diff --git a/src/context/ModalContext.tsx b/src/context/ModalContext.tsx new file mode 100644 index 00000000..5712a606 --- /dev/null +++ b/src/context/ModalContext.tsx @@ -0,0 +1,32 @@ +'use client'; + +import React, { createContext, useContext, useState } from 'react'; + +interface ModalContextType { + showModal: (type: string) => void; + hideModal: () => void; + modalType: string | null; +} + +const ModalContext = createContext(undefined); + +export const ModalProvider = ({ children }: { children: React.ReactNode }) => { + const [modalType, setModalType] = useState(null); + + const showModal = (type: string) => setModalType(type); + const hideModal = () => setModalType(null); + + return ( + + {children} + + ); +}; + +export const useModal = () => { + const context = useContext(ModalContext); + if (!context) { + throw new Error('useModal must be used within a ModalProvider'); + } + return context; +}; diff --git a/src/lib/fontawesome.ts b/src/lib/fontawesome.ts new file mode 100644 index 00000000..19a56379 --- /dev/null +++ b/src/lib/fontawesome.ts @@ -0,0 +1,5 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faGoogle } from '@fortawesome/free-brands-svg-icons'; + +// Add icons to the library so they're available in the whole app +library.add(faGoogle); \ No newline at end of file diff --git a/src/utils/animations.ts b/src/utils/animations.ts new file mode 100644 index 00000000..a89b97a2 --- /dev/null +++ b/src/utils/animations.ts @@ -0,0 +1,83 @@ +import { Variants } from 'framer-motion'; + +// Fade in animation +export const fadeIn: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { duration: 0.5 } + } +}; + +// Slide up animation +export const slideUp: Variants = { + hidden: { y: 50, opacity: 0 }, + visible: { + y: 0, + opacity: 1, + transition: { + type: "spring", + stiffness: 100, + damping: 15 + } + } +}; + +// Slide from left animation +export const slideFromLeft: Variants = { + hidden: { x: -100, opacity: 0 }, + visible: { + x: 0, + opacity: 1, + transition: { + type: "spring", + stiffness: 100, + damping: 15 + } + } +}; + +// Slide from right animation +export const slideFromRight: Variants = { + hidden: { x: 100, opacity: 0 }, + visible: { + x: 0, + opacity: 1, + transition: { + type: "spring", + stiffness: 100, + damping: 15 + } + } +}; + +// Scale animation +export const scaleUp: Variants = { + hidden: { scale: 0.8, opacity: 0 }, + visible: { + scale: 1, + opacity: 1, + transition: { + type: "spring", + stiffness: 100, + damping: 15 + } + } +}; + +// Staggered children animation container +export const staggerContainer: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.2 + } + } +}; + +// Hover scale animation +export const hoverScale = { + scale: 1.05, + transition: { duration: 0.2 } +}; \ No newline at end of file