diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 531e6b120..78344d178 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -4,6 +4,8 @@ on: branches: [main, master] pull_request: branches: [main, master] +env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: test: timeout-minutes: 60 diff --git a/.prettierrc b/.prettierrc index 5e69c1bbd..78b69592e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,6 @@ "tabWidth": 4, "semi": true, "printWidth": 100, - "endOfLine": "lf" + "endOfLine": "lf", + "plugins": ["prettier-plugin-tailwindcss"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index ffb5385da..ed4a61c3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,5 +53,9 @@ "titleBar.inactiveBackground": "#c5203e99", "titleBar.inactiveForeground": "#e7e7e799" }, - "peacock.color": "#c5203e" + "peacock.color": "#c5203e", + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "typescript.format.enable": true } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d75e27b4a..9de266252 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "@ai-sdk/azure": "^1.0.7", "@googlemaps/react-wrapper": "^1.1.35", "@googlemaps/typescript-guards": "^2.0.1", + "@octokit/rest": "^21.0.2", "@playwright/test": "^1.49.0", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^1.1.0", "@types/express": "^4.17.17", @@ -26,11 +28,11 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "fast-equals": "3.0.3", - "framer-motion": "^6.5.1", "gray-matter": "^4.0.3", "lucide-react": "^0.378.0", "mailchimp-api-v3": "^1.15.0", "marked": "^4.0.18", + "motion": "^11.13.3", "next": "^15.0.3", "next-auth": "^4.24.10", "next-pwa": "^5.6.0", @@ -77,7 +79,8 @@ "jest": "^29.7.0", "lint-staged": "^12.3.7", "postcss": "^8.4.12", - "prettier": "^2.6.2", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", "rimraf": "^3.0.2", "tailwindcss": "^3.0.23", "ts-jest": "^29.2.5", @@ -1862,6 +1865,7 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "optional": true, + "peer": true, "dependencies": { "@emotion/memoize": "0.7.4" } @@ -1870,7 +1874,8 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "optional": true + "optional": true, + "peer": true }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", @@ -2917,64 +2922,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@motionone/animation": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.18.0.tgz", - "integrity": "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==", - "dependencies": { - "@motionone/easing": "^10.18.0", - "@motionone/types": "^10.17.1", - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/dom": { - "version": "10.12.0", - "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz", - "integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==", - "dependencies": { - "@motionone/animation": "^10.12.0", - "@motionone/generators": "^10.12.0", - "@motionone/types": "^10.12.0", - "@motionone/utils": "^10.12.0", - "hey-listen": "^1.0.8", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/easing": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.18.0.tgz", - "integrity": "sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==", - "dependencies": { - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/generators": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.18.0.tgz", - "integrity": "sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==", - "dependencies": { - "@motionone/types": "^10.17.1", - "@motionone/utils": "^10.18.0", - "tslib": "^2.3.1" - } - }, - "node_modules/@motionone/types": { - "version": "10.17.1", - "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.17.1.tgz", - "integrity": "sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==" - }, - "node_modules/@motionone/utils": { - "version": "10.18.0", - "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.18.0.tgz", - "integrity": "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==", - "dependencies": { - "@motionone/types": "^10.17.1", - "hey-listen": "^1.0.8", - "tslib": "^2.3.1" - } - }, "node_modules/@next/env": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.1.tgz", @@ -3150,6 +3097,148 @@ "node": ">=12.4.0" } }, + "node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "dependencies": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", + "dependencies": { + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz", + "integrity": "sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz", + "integrity": "sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==", + "dependencies": { + "@octokit/types": "^13.6.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.0.2.tgz", + "integrity": "sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==", + "dependencies": { + "@octokit/core": "^6.1.2", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -7046,6 +7135,11 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -9376,6 +9470,11 @@ "node >=0.6.0" ] }, + "node_modules/fast-content-type-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.0.tgz", + "integrity": "sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9692,34 +9791,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/framer-motion": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", - "integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==", - "dependencies": { - "@motionone/dom": "10.12.0", - "framesync": "6.0.1", - "hey-listen": "^1.0.8", - "popmotion": "11.0.3", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" - }, - "optionalDependencies": { - "@emotion/is-prop-valid": "^0.8.2" - }, - "peerDependencies": { - "react": ">=16.8 || ^17.0.0 || ^18.0.0", - "react-dom": ">=16.8 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/framesync": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", - "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -10148,11 +10219,6 @@ "node": ">= 0.4" } }, - "node_modules/hey-listen": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", - "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -12282,6 +12348,67 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/motion": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.13.3.tgz", + "integrity": "sha512-7WCtns1245mdDRpm50PpIGrRySmIfXDFDmWJtVO+zoevwGTOR4gw3irsA7TIdJzbBKptDaZ26Fk2c4v4z/Da/g==", + "dependencies": { + "framer-motion": "^11.13.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.13.0.tgz", + "integrity": "sha512-Oc1MLGJQ6nrvXccXA89lXtOqFyBmvHtaDcTRGT66o8Czl7nuA8BeHAd9MQV1pQKX0d2RHFBFaw5g3k23hQJt0w==" + }, + "node_modules/motion-utils": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.13.0.tgz", + "integrity": "sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A==" + }, + "node_modules/motion/node_modules/framer-motion": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.13.3.tgz", + "integrity": "sha512-3ZSNuYpDFeNxqVKUyYipOm5A1fXSbMje1XIfEWxKTJ4ughl5FEjvkp6gKmFHLjzwijCVU/PjsMNlTMVCmi+Twg==", + "dependencies": { + "motion-dom": "^11.13.0", + "motion-utils": "^11.13.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -13141,17 +13268,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/popmotion": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", - "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", - "dependencies": { - "framesync": "6.0.1", - "hey-listen": "^1.0.8", - "style-value-types": "5.0.0", - "tslib": "^2.1.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -13366,15 +13482,15 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -13392,6 +13508,84 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz", + "integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -15041,15 +15235,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/style-value-types": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", - "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", - "dependencies": { - "hey-listen": "^1.0.8", - "tslib": "^2.1.0" - } - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -16088,6 +16273,11 @@ "node": ">=8" } }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index d02458362..ab9172c98 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,9 @@ "@ai-sdk/azure": "^1.0.7", "@googlemaps/react-wrapper": "^1.1.35", "@googlemaps/typescript-guards": "^2.0.1", + "@octokit/rest": "^21.0.2", + "@playwright/test": "^1.49.0", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^1.1.0", "@types/express": "^4.17.17", @@ -34,11 +37,11 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "fast-equals": "3.0.3", - "framer-motion": "^6.5.1", "gray-matter": "^4.0.3", "lucide-react": "^0.378.0", "mailchimp-api-v3": "^1.15.0", "marked": "^4.0.18", + "motion": "^11.13.3", "next": "^15.0.3", "next-auth": "^4.24.10", "next-pwa": "^5.6.0", @@ -52,8 +55,7 @@ "react-simple-typewriter": "^3.0.1", "swiper": "^8.3.1", "tailwind-merge": "^2.3.0", - "tailwindcss-animate": "^1.0.7", - "@playwright/test": "^1.49.0" + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@fullhuman/postcss-purgecss": "^4.1.3", @@ -86,7 +88,8 @@ "jest": "^29.7.0", "lint-staged": "^12.3.7", "postcss": "^8.4.12", - "prettier": "^2.6.2", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", "rimraf": "^3.0.2", "tailwindcss": "^3.0.23", "ts-jest": "^29.2.5", diff --git a/public/fallback-V6Ee7KjBBk3_9xilHmQYv.js b/public/fallback-a7KM73030O40tzcX6icA5.js similarity index 100% rename from public/fallback-V6Ee7KjBBk3_9xilHmQYv.js rename to public/fallback-a7KM73030O40tzcX6icA5.js diff --git a/src/assets/css/tailwind.css b/src/assets/css/tailwind.css index c1ad6a5ad..f3314ab8b 100644 --- a/src/assets/css/tailwind.css +++ b/src/assets/css/tailwind.css @@ -89,4 +89,7 @@ -ms-overflow-style: none; scrollbar-width: none; } + .backface-hidden { + backface-visibility: hidden; + } } diff --git a/src/components/course-card/course-02.tsx b/src/components/course-card/course-02.tsx index 03ec78138..19a26f0ef 100644 --- a/src/components/course-card/course-02.tsx +++ b/src/components/course-card/course-02.tsx @@ -2,6 +2,8 @@ import { forwardRef } from "react"; import clsx from "clsx"; import Anchor from "@ui/anchor"; import { ICourse } from "@utils/types"; +import { motion } from "motion/react"; +import { scrollUpVariants } from "@utils/variants"; type TProps = Pick & { className?: string; @@ -10,7 +12,7 @@ type TProps = Pick & { const CourseCard02 = forwardRef( ({ className, thumbnail, title, path }, ref) => { return ( -
( className )} ref={ref} + initial="offscreen" + whileInView="onscreen" + viewport={{ once: true, amount: 0.2 }} + variants={scrollUpVariants} >
{thumbnail?.src && ( @@ -40,7 +46,7 @@ const CourseCard02 = forwardRef( {title}
- + ); } ); diff --git a/src/components/forms/apply-form.tsx b/src/components/forms/apply-form.tsx index 05dabeb84..08b55a3c0 100644 --- a/src/components/forms/apply-form.tsx +++ b/src/components/forms/apply-form.tsx @@ -9,7 +9,7 @@ import Button from "@ui/button"; import { hasKey } from "@utils/methods"; import Feedback from "@ui/form-elements/feedback"; import { linkedinRegex, githubRegex } from "@utils/formValidations"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; interface IFormValues { firstName: string; diff --git a/src/components/funfact/funfact-01.tsx b/src/components/funfact/funfact-01.tsx index e8f3c4659..b9b82f8f7 100644 --- a/src/components/funfact/funfact-01.tsx +++ b/src/components/funfact/funfact-01.tsx @@ -1,5 +1,5 @@ import { forwardRef, useState, useRef, useEffect } from "react"; -import { motion, animate } from "framer-motion"; +import { motion, animate } from "motion/react"; type TProps = { counter: number; diff --git a/src/components/funfact/funfact-02.tsx b/src/components/funfact/funfact-02.tsx index 650641d50..9bc719431 100644 --- a/src/components/funfact/funfact-02.tsx +++ b/src/components/funfact/funfact-02.tsx @@ -1,6 +1,6 @@ import { forwardRef, useState, useRef, useEffect } from "react"; import clsx from "clsx"; -import { motion, animate } from "framer-motion"; +import { motion, animate } from "motion/react"; type TProps = { counter: number; diff --git a/src/components/menu/mobile-menu/index.tsx b/src/components/menu/mobile-menu/index.tsx index 8388ba785..c5096a8d8 100644 --- a/src/components/menu/mobile-menu/index.tsx +++ b/src/components/menu/mobile-menu/index.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Offcanvas from "@ui/offcanvas"; import OffcanvasHeader from "@ui/offcanvas/header"; import OffcanvasBody from "@ui/offcanvas/body"; diff --git a/src/components/ui/accordion/item.tsx b/src/components/ui/accordion/item.tsx index c8d0a81f1..bb864f340 100644 --- a/src/components/ui/accordion/item.tsx +++ b/src/components/ui/accordion/item.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import clsx from "clsx"; import { IDType } from "@utils/types"; diff --git a/src/components/ui/modal/modal.tsx b/src/components/ui/modal/modal.tsx index 5008164a5..6a5a1a816 100644 --- a/src/components/ui/modal/modal.tsx +++ b/src/components/ui/modal/modal.tsx @@ -1,4 +1,4 @@ -import { motion, AnimatePresence } from "framer-motion"; +import { motion, AnimatePresence } from "motion/react"; import clsx from "clsx"; import dynamic from "next/dynamic"; import { fadeIn } from "@utils/variants"; @@ -37,7 +37,7 @@ const Modal = ({ className, show, size, centered, children, onClose }: TModal) = return ( - null}> + null}> {show && ( <> { const childArr = Children.toArray(children); const activeChild = childArr.find((_el, i) => i === activeIdx); return ( - + { diff --git a/src/components/ui/video-modal/index.tsx b/src/components/ui/video-modal/index.tsx index e65140cd0..311073e55 100644 --- a/src/components/ui/video-modal/index.tsx +++ b/src/components/ui/video-modal/index.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import clsx from "clsx"; import dynamic from "next/dynamic"; -import { motion, AnimatePresence } from "framer-motion"; +import { motion, AnimatePresence } from "motion/react"; import { useKeyboardFocus } from "@hooks"; import { fadeIn02 } from "@utils/variants"; import Spinner from "../spinner"; @@ -39,7 +39,7 @@ const VideoModal = ({ videoId, show, onClose, className }: TModal) => { return ( - null}> + null}> {show && ( <> ( + ({ className, thumbnail, title, headline }, ref) => { + return ( + +
+ {thumbnail?.src && ( + {thumbnail?.alt + )} +
+
+

{title}

+ {headline && ( +
+ {headline} +
+ )} +
+
+ ); + } +); + +export default VWCGridCard; diff --git a/src/components/vwc-grid/index.tsx b/src/components/vwc-grid/index.tsx new file mode 100644 index 000000000..9933cd1dc --- /dev/null +++ b/src/components/vwc-grid/index.tsx @@ -0,0 +1,23 @@ +import Section from "@ui/section"; +import { PropsWithChildren } from "react"; + +interface TProps { + title: string; +} + +export const VWCGrid = ({ title, children }: PropsWithChildren) => { + // const { itemsToShow } = useLoadMore(sortedItems, 9, 3); + + return ( +
+

{title}

+
+
+ {children} +
+
+
+ ); +}; + +export default VWCGrid; diff --git a/src/containers/about/layout-01/index.tsx b/src/containers/about/layout-01/index.tsx index 51c5ebe4d..5c269d490 100644 --- a/src/containers/about/layout-01/index.tsx +++ b/src/containers/about/layout-01/index.tsx @@ -1,7 +1,7 @@ import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import Anchor from "@ui/anchor"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { scrollUpVariants } from "@utils/variants"; import { SectionTitleType, TextType, AnchorType, ImageType, TSection } from "@utils/types"; diff --git a/src/containers/about/layout-02/index.tsx b/src/containers/about/layout-02/index.tsx index 729dadae5..600029b85 100644 --- a/src/containers/about/layout-02/index.tsx +++ b/src/containers/about/layout-02/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import MottoText from "@ui/motto-text"; import SectionTitle from "@components/section-title"; @@ -14,12 +14,7 @@ type TProps = TSection & { }; }; -const AboutArea = ({ - data: { section_title, motto, images }, - space, - bg, - titleSize, -}: TProps) => { +const AboutArea = ({ data: { section_title, motto, images }, space, bg, titleSize }: TProps) => { const { trans1 } = useUI(); return (
@@ -32,15 +27,9 @@ const AboutArea = ({ variants={scrollUpVariants} > {section_title && ( - - )} - {motto && ( - + )} + {motto && }
{images?.[0]?.src && ( @@ -92,10 +81,7 @@ const AboutArea = ({ y: trans1().y, }} > - + - + { {section_title?.subtitle} -

- {section_title.title} -

+

{section_title.title}

)} {items?.map((item) => ( @@ -45,9 +43,7 @@ const ContactInfo = ({ data: { section_title, items, images } }: TProps) => { "tw-text-[32px] tw-text-primary tw-absolute tw-left-0 tw-top-0" )} /> -

- {item.title} -

+

{item.title}

{item.texts?.map((text) => (

{ }} > - + @@ -99,10 +92,7 @@ const ContactInfo = ({ data: { section_title, items, images } }: TProps) => { y: trans2().y, }} > - + { - const { sortedItems } = useSort(courses, courseSorting); - const { itemsToShow } = useLoadMore(sortedItems, 9, 3); - - return ( -

-

Course Section

-
-
- {itemsToShow?.map((course) => ( - - ))} -
-
-
- ); -}; - -export default CourseArea; diff --git a/src/containers/course-full/layout-03/index.tsx b/src/containers/course-full/layout-03/index.tsx index 7418d28ea..242113a51 100644 --- a/src/containers/course-full/layout-03/index.tsx +++ b/src/containers/course-full/layout-03/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import CourseCard from "@components/course-card/course-03"; import { ICourse } from "@utils/types"; diff --git a/src/containers/course/layout-01/index.tsx b/src/containers/course/layout-01/index.tsx index b04cf5fa9..6408a3a7b 100644 --- a/src/containers/course/layout-01/index.tsx +++ b/src/containers/course/layout-01/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import CourseCard from "@components/course-card/course-01"; diff --git a/src/containers/course/layout-02/index.tsx b/src/containers/course/layout-02/index.tsx index a8073c999..a9f37b94e 100644 --- a/src/containers/course/layout-02/index.tsx +++ b/src/containers/course/layout-02/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import Button from "@ui/button"; import { useUI } from "@contexts/ui-context"; @@ -63,10 +63,7 @@ const CtaArea = ({ data: { section_title, buttons }, space, bg }: TProps) => { y: trans1().y, }} > - +
{courses.map((course) => ( - ))}
diff --git a/src/containers/cta/layout-01/index.tsx b/src/containers/cta/layout-01/index.tsx index fca4bf96c..a2dc7c668 100644 --- a/src/containers/cta/layout-01/index.tsx +++ b/src/containers/cta/layout-01/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import Button from "@ui/button"; diff --git a/src/containers/cta/layout-02/index.tsx b/src/containers/cta/layout-02/index.tsx index a8073c999..a9f37b94e 100644 --- a/src/containers/cta/layout-02/index.tsx +++ b/src/containers/cta/layout-02/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import Button from "@ui/button"; import { useUI } from "@contexts/ui-context"; @@ -63,10 +63,7 @@ const CtaArea = ({ data: { section_title, buttons }, space, bg }: TProps) => { y: trans1().y, }} > - + { +const HeroArea = ({ data: { headings, texts, buttons, motto, images } }: TProps) => { const { trans1, trans2 } = useUI(); return (
@@ -56,13 +48,7 @@ const HeroArea = ({ {content} ))} - {motto && ( - - )} + {motto && }
@@ -103,10 +89,7 @@ const HeroArea = ({ y: trans2().y, }} > - + - +
diff --git a/src/containers/hero/layout-03/index.tsx b/src/containers/hero/layout-03/index.tsx index 109827378..c9ac77c65 100644 --- a/src/containers/hero/layout-03/index.tsx +++ b/src/containers/hero/layout-03/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import clsx from "clsx"; import Button from "@ui/button"; import { ButtonType, HeadingType, ImageType } from "@utils/types"; diff --git a/src/containers/hero/layout-04/index.tsx b/src/containers/hero/layout-04/index.tsx index 4b6684b97..f2093142c 100644 --- a/src/containers/hero/layout-04/index.tsx +++ b/src/containers/hero/layout-04/index.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import clsx from "clsx"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import dynamic from "next/dynamic"; import { useTypewriter, Cursor } from "react-simple-typewriter"; import Button from "@ui/button"; diff --git a/src/containers/hero/layout-05/index.tsx b/src/containers/hero/layout-05/index.tsx index a26918f7d..f99b0f83e 100644 --- a/src/containers/hero/layout-05/index.tsx +++ b/src/containers/hero/layout-05/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Button from "@ui/button"; import BottomShape from "@ui/bottom-shape/shape-04"; import { ButtonType, HeadingType, ImageType, TextType } from "@utils/types"; diff --git a/src/containers/hero/layout-06/index.tsx b/src/containers/hero/layout-06/index.tsx index 511580414..15caa34cb 100644 --- a/src/containers/hero/layout-06/index.tsx +++ b/src/containers/hero/layout-06/index.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { useScroll, motion, useTransform } from "framer-motion"; +import { useScroll, motion, useTransform } from "motion/react"; import Button from "@ui/button"; import Video from "@ui/video-with-poster/video-01"; import BottomShape from "@ui/bottom-shape/shape-05"; diff --git a/src/containers/hero/layout-07/index.tsx b/src/containers/hero/layout-07/index.tsx index 2fde2e085..e34a485ad 100644 --- a/src/containers/hero/layout-07/index.tsx +++ b/src/containers/hero/layout-07/index.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import SwiperCore, { EffectFade } from "swiper"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import SwiperSlider, { SwiperSlide } from "@components/ui/swiper"; import { ItemType } from "@utils/types"; import { fadeInUp } from "@utils/variants"; diff --git a/src/containers/hero/layout-08/index.tsx b/src/containers/hero/layout-08/index.tsx index aa9f92e90..ec704b703 100644 --- a/src/containers/hero/layout-08/index.tsx +++ b/src/containers/hero/layout-08/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { ImageType, HeadingType } from "@utils/types"; import { scrollUpVariants } from "@utils/variants"; diff --git a/src/containers/newsletter/layout-01/index.tsx b/src/containers/newsletter/layout-01/index.tsx index 03ffb4bad..49bfcc753 100644 --- a/src/containers/newsletter/layout-01/index.tsx +++ b/src/containers/newsletter/layout-01/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import { useUI } from "@contexts/ui-context"; import SectionTitle from "@components/section-title"; diff --git a/src/containers/newsletter/layout-02/index.tsx b/src/containers/newsletter/layout-02/index.tsx index efc5088a7..1deb483c9 100644 --- a/src/containers/newsletter/layout-02/index.tsx +++ b/src/containers/newsletter/layout-02/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import NewsletterForm from "@components/forms/newsletter-form"; diff --git a/src/containers/quote/layout-01/index.tsx b/src/containers/quote/layout-01/index.tsx index 416fd9eb6..fca675ac6 100644 --- a/src/containers/quote/layout-01/index.tsx +++ b/src/containers/quote/layout-01/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SwiperSlider, { SwiperSlide } from "@ui/swiper"; import QuoteItem from "@components/quote-item"; diff --git a/src/containers/quote/layout-02/index.tsx b/src/containers/quote/layout-02/index.tsx index 9be7efead..efe9658a0 100644 --- a/src/containers/quote/layout-02/index.tsx +++ b/src/containers/quote/layout-02/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { HeadingType, TextType } from "@utils/types"; import { scrollUpVariants } from "@utils/variants"; diff --git a/src/containers/register-guide/index.tsx b/src/containers/register-guide/index.tsx index c3f75de20..9895c91a6 100644 --- a/src/containers/register-guide/index.tsx +++ b/src/containers/register-guide/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import ListWithCheck from "@ui/list-with-check"; diff --git a/src/containers/service/layout-01/index.tsx b/src/containers/service/layout-01/index.tsx index 7db9a30be..0eb9cbc0f 100644 --- a/src/containers/service/layout-01/index.tsx +++ b/src/containers/service/layout-01/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/icon-box/icon-box-01"; diff --git a/src/containers/service/layout-02/index.tsx b/src/containers/service/layout-02/index.tsx index 598393619..2cf8ad3ad 100644 --- a/src/containers/service/layout-02/index.tsx +++ b/src/containers/service/layout-02/index.tsx @@ -1,7 +1,7 @@ import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/image-box/image-box-01"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { scrollUpVariants } from "@utils/variants"; import { SectionTitleType, ItemType, TSection } from "@utils/types"; diff --git a/src/containers/service/layout-03/index.tsx b/src/containers/service/layout-03/index.tsx index 85a39f8a4..fe9777185 100644 --- a/src/containers/service/layout-03/index.tsx +++ b/src/containers/service/layout-03/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import ServiceCard from "@components/image-box/image-box-02"; import MottoText from "@ui/motto-text"; diff --git a/src/containers/service/layout-04/index.tsx b/src/containers/service/layout-04/index.tsx index 5882c5aae..ccae28fbc 100644 --- a/src/containers/service/layout-04/index.tsx +++ b/src/containers/service/layout-04/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/image-box/image-box-03"; diff --git a/src/containers/service/layout-05/index.tsx b/src/containers/service/layout-05/index.tsx index 20f3f8e54..4dd9f2506 100644 --- a/src/containers/service/layout-05/index.tsx +++ b/src/containers/service/layout-05/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import Button from "@ui/button"; diff --git a/src/containers/service/layout-06/index.tsx b/src/containers/service/layout-06/index.tsx index e13e4a118..7c8a6ac71 100644 --- a/src/containers/service/layout-06/index.tsx +++ b/src/containers/service/layout-06/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/icon-box/icon-box-02"; diff --git a/src/containers/service/layout-07/index.tsx b/src/containers/service/layout-07/index.tsx index 1547615e2..876949527 100644 --- a/src/containers/service/layout-07/index.tsx +++ b/src/containers/service/layout-07/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/image-box/image-box-02"; import { scrollUpVariants } from "@utils/variants"; diff --git a/src/containers/service/layout-08/index.tsx b/src/containers/service/layout-08/index.tsx index 163a9b827..2cabc6508 100644 --- a/src/containers/service/layout-08/index.tsx +++ b/src/containers/service/layout-08/index.tsx @@ -1,7 +1,7 @@ import clsx from "clsx"; import SectionTitle from "@components/section-title"; import ServiceCard from "@components/icon-box/icon-box-02"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { scrollUpVariants } from "@utils/variants"; import { SectionTitleType, ItemType, ImageType } from "@utils/types"; diff --git a/src/containers/team/layout-01/index.tsx b/src/containers/team/layout-01/index.tsx index c970b9eb7..c1b76b68d 100644 --- a/src/containers/team/layout-01/index.tsx +++ b/src/containers/team/layout-01/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import Shape2 from "@assets/svgs/shape-2.svg"; import SectionTitle from "@components/section-title"; @@ -14,12 +14,7 @@ type TProps = TSection & { }; }; -const TeamArea = ({ - data: { section_title, buttons }, - space, - bg, - titleSize, -}: TProps) => { +const TeamArea = ({ data: { section_title, buttons }, space, bg, titleSize }: TProps) => { const { trans1, trans2 } = useUI(); return (
@@ -97,10 +92,7 @@ const TeamArea = ({ y: trans1().y, }} > - + {section_title && ( - + )} {buttons?.map(({ id, content, ...rest }) => ( )} diff --git a/src/containers/testimonial/layout-05/index.tsx b/src/containers/testimonial/layout-05/index.tsx index 8e20445eb..3527f2470 100644 --- a/src/containers/testimonial/layout-05/index.tsx +++ b/src/containers/testimonial/layout-05/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SwiperSlider, { SwiperSlide } from "@ui/swiper"; import Testimonial from "@components/testimonial/testimonial-05"; diff --git a/src/containers/testimonial/layout-06/index.tsx b/src/containers/testimonial/layout-06/index.tsx index f73719179..a08c845da 100644 --- a/src/containers/testimonial/layout-06/index.tsx +++ b/src/containers/testimonial/layout-06/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import SectionTitle from "@components/section-title"; import Swiper, { SwiperSlide } from "@ui/swiper"; import Testimonial from "@components/testimonial/testimonial-06"; diff --git a/src/containers/testimonial/layout-07/index.tsx b/src/containers/testimonial/layout-07/index.tsx index 4074abbe1..4376944f2 100644 --- a/src/containers/testimonial/layout-07/index.tsx +++ b/src/containers/testimonial/layout-07/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import dynamic from "next/dynamic"; import { SwiperSlide } from "@ui/swiper"; import Testimonial from "@components/testimonial/testimonial-02"; diff --git a/src/containers/timeline/index.tsx b/src/containers/timeline/index.tsx index 60afb8ba3..281f4f29b 100644 --- a/src/containers/timeline/index.tsx +++ b/src/containers/timeline/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import SectionTitle from "@components/section-title"; import { ItemType, SectionTitleType, TSection } from "@utils/types"; diff --git a/src/containers/timeline/item.tsx b/src/containers/timeline/item.tsx index 186b98bae..6e79b977b 100644 --- a/src/containers/timeline/item.tsx +++ b/src/containers/timeline/item.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import clsx from "clsx"; import { HeadingType, ImageType, TextType } from "@utils/types"; import { scrollLeftVariants, scrollRightVariants } from "@utils/variants"; diff --git a/src/containers/video/layout-01/index.tsx b/src/containers/video/layout-01/index.tsx index c108df79f..14d91451c 100644 --- a/src/containers/video/layout-01/index.tsx +++ b/src/containers/video/layout-01/index.tsx @@ -4,7 +4,7 @@ import SectionTitle from "@components/section-title"; import BottomShape from "@ui/bottom-shape/shape-02"; import Video from "@ui/video-with-poster/video-02"; import Shape2 from "@assets/svgs/shape-2.svg"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { useUI } from "@contexts/ui-context"; import { scrollUpVariants } from "@utils/variants"; import { ImageType, SectionTitleType, TSection, VideoType } from "@utils/types"; @@ -20,11 +20,7 @@ type TProps = TSection & { }; }; -const VideoArea = ({ - data: { section_title, images, video }, - space, - bg, -}: TProps) => { +const VideoArea = ({ data: { section_title, images, video }, space, bg }: TProps) => { const { trans1, trans2 } = useUI(); return (
{section_title && ( - + )} - {motto && ( - - )} + {motto && } diff --git a/src/containers/video/layout-04/index.tsx b/src/containers/video/layout-04/index.tsx index c379579cd..df82a76e3 100644 --- a/src/containers/video/layout-04/index.tsx +++ b/src/containers/video/layout-04/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import Video from "@ui/video-with-poster/video-02"; import Shape2 from "@assets/svgs/shape-2.svg"; @@ -18,11 +18,7 @@ type TProps = TSection & { const VideoArea = ({ data: { images, video }, space, bg }: TProps) => { const { trans1, trans2 } = useUI(); return ( -
+

Video Section

{video && images?.[0] && ( diff --git a/src/containers/video/layout-05/index.tsx b/src/containers/video/layout-05/index.tsx index 103b31fee..0c0bedf8d 100644 --- a/src/containers/video/layout-05/index.tsx +++ b/src/containers/video/layout-05/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import SectionTitle from "@components/section-title"; import Video from "@ui/video-with-poster/video-02"; import BottomShape from "@components/ui/bottom-shape/shape-02"; diff --git a/src/containers/video/layout-06/index.tsx b/src/containers/video/layout-06/index.tsx index ee9d6b182..5734ce93f 100644 --- a/src/containers/video/layout-06/index.tsx +++ b/src/containers/video/layout-06/index.tsx @@ -1,15 +1,10 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Video from "@ui/video-with-poster/video-02"; import MottoText from "@ui/motto-text"; import SectionTitle from "@components/section-title"; import Shape2 from "@assets/svgs/shape-2.svg"; import { useUI } from "@contexts/ui-context"; -import { - ImageType, - MottoType, - SectionTitleType, - VideoType, -} from "@utils/types"; +import { ImageType, MottoType, SectionTitleType, VideoType } from "@utils/types"; import { scrollUpVariants } from "@utils/variants"; const AnimatedVideo = motion(Video); @@ -24,10 +19,7 @@ type TProps = { titleSize?: "default" | "large"; }; -const VideoArea = ({ - data: { section_title, motto, images, video }, - titleSize, -}: TProps) => { +const VideoArea = ({ data: { section_title, motto, images, video }, titleSize }: TProps) => { const { trans1, trans2 } = useUI(); return ( @@ -102,9 +94,7 @@ const VideoArea = ({ className="lg:tw-max-w-[420px]" /> )} - {motto && ( - - )} + {motto && }
diff --git a/src/containers/video/layout-07/index.tsx b/src/containers/video/layout-07/index.tsx index b4b177d42..cabd2f7e0 100644 --- a/src/containers/video/layout-07/index.tsx +++ b/src/containers/video/layout-07/index.tsx @@ -4,7 +4,7 @@ import SectionTitle from "@components/section-title"; import BottomShape from "@ui/bottom-shape/shape-02"; import Video from "@ui/video-with-poster/video-02"; import Shape2 from "@assets/svgs/shape-2.svg"; -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import { useUI } from "@contexts/ui-context"; import { scrollUpVariants } from "@utils/variants"; import { ImageType, SectionTitleType, TSection, VideoType } from "@utils/types"; diff --git a/src/containers/zoom-meetings/index.tsx b/src/containers/zoom-meetings/index.tsx index 1864f3472..66393eb76 100644 --- a/src/containers/zoom-meetings/index.tsx +++ b/src/containers/zoom-meetings/index.tsx @@ -1,4 +1,4 @@ -import { motion } from "framer-motion"; +import { motion } from "motion/react"; import Section from "@ui/section"; import { IZoomMeeting } from "@utils/types"; import Pagination from "@components/pagination/pagination-01"; diff --git a/src/data/menu.ts b/src/data/menu.ts index 2c1555137..8f2a5cdce 100644 --- a/src/data/menu.ts +++ b/src/data/menu.ts @@ -51,11 +51,16 @@ const navigation: NavigationItem[] = [ }, { id: 22, + label: "Projects", + path: "/projects", + }, + { + id: 23, label: "Subjects", path: "/subjects/all", }, { - id: 23, + id: 24, label: "FAQ", path: "/faq", }, diff --git a/src/data/projects/api-list.json b/src/data/projects/api-list.json new file mode 100644 index 000000000..299a61cfc --- /dev/null +++ b/src/data/projects/api-list.json @@ -0,0 +1,25 @@ +{ + "index": 5, + "name": "API List", + "headline": "Curated Collection of Fun and Free APIs for Learning JavaScript", + "long_description": [ + "The API List is a curated collection of interesting and free APIs designed to help learners practice and enhance their JavaScript skills. It offers a diverse range of APIs across various categories, including AI, art, books, code repositories, food, gaming, government data, health, language, open-source projects, and more.", + "Key features include:", + "- **Diverse Categories**: Explore APIs in categories such as AI, art, books, code repositories, food, gaming, government data, health, language, open-source projects, and miscellaneous collections.", + "- **Free Access**: All listed APIs are free to use, providing ample opportunities for hands-on practice.", + "- **Learning Resource**: Ideal for beginners and intermediate learners looking to apply JavaScript in real-world scenarios.", + "- **Community Contribution**: Open for contributions, allowing users to add new APIs and share valuable resources with the community." + ], + "technologies": [ + "JavaScript", + "APIs", + "Web Development" + ], + "owner": "Vets-Who-Code", + "repo": "api-list", + "live_url": null, + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload//f_auto,q_auto/v1735960355/projects/APIList_z0efro.png", + "alt": "API List Project Thumbnail" + } +} diff --git a/src/data/projects/prework.json b/src/data/projects/prework.json new file mode 100644 index 000000000..585f3f523 --- /dev/null +++ b/src/data/projects/prework.json @@ -0,0 +1,29 @@ +{ + "index": 6, + "name": "Prework", + "headline": "Foundational Prework for Prospective Vets Who Code Participants", + "long_description": [ + "The Prework project is a meticulously crafted program designed to introduce prospective Vets Who Code (VWC) participants to essential tools, technologies, and fundamental concepts in software engineering. This preparatory course is structured to ensure that learners are well-equipped for the upcoming VWC curriculum.", + "Key components of the Prework include:", + "- **Command Line Basics**: Understanding the command line interface and its operations.", + "- **Code Editor Setup**: Guidance on setting up and utilizing code editors effectively.", + "- **HTML & CSS Fundamentals**: Building and styling web pages using HTML and CSS.", + "- **JavaScript Introduction**: Writing and integrating JavaScript to enhance web interactivity.", + "- **Capstone Project**: A culminating challenge that allows learners to apply their acquired skills in a practical project." + ], + "technologies": [ + "Command Line", + "Git", + "GitHub", + "HTML", + "CSS", + "JavaScript" + ], + "owner": "Vets-Who-Code", + "repo": "Prework", + "live_url": null, + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload/f_auto,q_auto/v1735960355/projects/Prework_vamz91.png", + "alt": "Prework Project Thumbnail" + } +} diff --git a/src/data/projects/vets-ai.json b/src/data/projects/vets-ai.json new file mode 100644 index 000000000..ba1d41ec5 --- /dev/null +++ b/src/data/projects/vets-ai.json @@ -0,0 +1,26 @@ +{ + "index": 2, + "name": "VetsAI", + "headline": "AI-Powered Employment Assistance for Veterans", + "long_description": [ + "VetsAI is an AI-powered virtual assistant designed to help veterans navigate employment transitions and find opportunities in civilian careers. The application allows users to interact via chat, upload resumes in PDF or DOCX format, and receive tailored assistance, such as translating military job codes to civilian job suggestions.", + "Key features include:", + "- **Chat Assistant**: Ask questions and receive advice on job searching and career transitions.", + "- **Military Job Code Translation**: Provide a military job code (e.g., MOS, AFSC) to get suggestions for related civilian careers.", + "- **Document Upload**: Upload employment-related documents for personalized feedback and guidance." + ], + "technologies": [ + "Python", + "Streamlit", + "OpenAI API", + "Pandas", + "NumPy" + ], + "owner": "Vets-Who-Code", + "repo": "VetsAI", + "live_url": null, + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload/f_auto,q_auto/bo_1px_solid_black/v1735960357/projects/VetsAI_ie8v4u.png", + "alt": "VetsAI Project Thumbnail" + } +} diff --git a/src/data/projects/vets-who-code-app.json b/src/data/projects/vets-who-code-app.json new file mode 100644 index 000000000..19cb5606c --- /dev/null +++ b/src/data/projects/vets-who-code-app.json @@ -0,0 +1,28 @@ +{ + "index": 1, + "name": "Vets Who Code App", + "headline": "Empowering Veterans Through a Communal Coding Platform", + "long_description": [ + "The Vets Who Code App is a communal platform built to empower military veterans and their spouses by enhancing their coding skills. This production-grade application evolves continuously to meet the unique needs of the veteran community, providing real-world coding experience.", + "Key features of the application include:", + "- **Community-Driven Development**: A collaborative codebase that allows contributors to build and refine features, fostering growth and mentorship.", + "- **Modern Tech Stack**: Developed with Next.js, TypeScript, and other cutting-edge technologies to deliver a robust and scalable platform.", + "- **Practical Learning Environment**: Offers tools and challenges that mimic real-world scenarios, enabling users to learn by doing and preparing them for careers in the tech industry." + ], + "technologies": [ + "Next.js", + "React", + "TypeScript", + "Node.js", + "Jest", + "Docker", + "GitHub Actions" + ], + "owner": "Vets-Who-Code", + "repo": "vets-who-code-app", + "live_url": "https://vetswhocode.io/", + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload/f_auto,q_auto/v1735960356/projects/VWCApp_zpuui4.png", + "alt": "Vets Who Code App Thumbnail" + } +} diff --git a/src/data/projects/web-curriculum.json b/src/data/projects/web-curriculum.json new file mode 100644 index 000000000..1fe4e0cb6 --- /dev/null +++ b/src/data/projects/web-curriculum.json @@ -0,0 +1,30 @@ +{ + "index": 4, + "name": "Web Curriculum", + "headline": "Structured Learning Path for Aspiring Web Developers", + "long_description": [ + "The Web Curriculum is a comprehensive educational resource designed to guide learners through the fundamentals and advanced topics of web development. It offers a structured learning path, starting with foundational concepts and progressing to more complex subjects, ensuring a well-rounded understanding of web technologies.", + "The curriculum is divided into two main collections:", + "- **Collection I: Web Fundamentals**: Covers essential topics such as command line usage, Git and GitHub for version control, HTML and CSS for web page structure and styling, accessibility considerations, user experience design, and an introduction to JavaScript.", + "- **Collection II: JavaScript Engineering**: Focuses on advanced JavaScript topics, including Node.js, TypeScript, React, Next.js, and testing with Playwright, providing learners with the skills needed to build robust and scalable web applications." + ], + "technologies": [ + "Git", + "GitHub", + "HTML", + "CSS", + "JavaScript", + "Node.js", + "TypeScript", + "React", + "Next.js", + "Playwright" + ], + "owner": "Vets-Who-Code", + "repo": "web-curriculum", + "live_url": null, + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload/f_auto,q_auto/v1735960355/projects/WebCurriculum_ymkrti.png", + "alt": "Web Curriculum Thumbnail" + } +} diff --git a/src/data/projects/windows-dev-setup-guide.json b/src/data/projects/windows-dev-setup-guide.json new file mode 100644 index 000000000..56204fac2 --- /dev/null +++ b/src/data/projects/windows-dev-setup-guide.json @@ -0,0 +1,30 @@ +{ + "index": 3, + "name": "Windows Dev Guide", + "headline": "Comprehensive Guide to Setting Up a Windows Development Environment", + "long_description": [ + "The Windows Dev Guide provides a step-by-step walkthrough for setting up a development environment on Windows. It covers essential tools and configurations to enhance productivity for developers working on Windows platforms.", + "Key sections include:", + "- **Prerequisites**: Guidance on system requirements and initial setup.", + "- **Installing Windows Subsystem for Linux (WSL)**: Instructions for enabling WSL to run a Linux environment on Windows.", + "- **Setting Up Development Tools**: Recommendations for IDEs, text editors, and other essential software.", + "- **Configuring Version Control**: Steps to set up Git and GitHub for source code management.", + "- **Additional Resources**: Links to further reading and tutorials to deepen your understanding." + ], + "technologies": [ + "Windows Subsystem for Linux (WSL)", + "Git", + "Visual Studio Code", + "Windows Terminal", + "Docker", + "Node.js", + "Python" + ], + "owner": "Vets-Who-Code", + "repo": "windows-dev-guide", + "live_url": null, + "thumbnail": { + "src": "https://res.cloudinary.com/vetswhocode/image/upload/f_auto,q_auto/v1735960356/projects/WindowsDevGuide_otu8mq.png", + "alt": "Windows Dev Guide Thumbnail" + } +} diff --git a/src/lib/github.ts b/src/lib/github.ts new file mode 100644 index 000000000..74267d94b --- /dev/null +++ b/src/lib/github.ts @@ -0,0 +1,59 @@ +import { Octokit } from "@octokit/rest"; +import { VWCContributor, GithubRepo, GithubContributor } from "@utils/types"; + +const token = process.env.GITHUB_ACCESS_TOKEN || ""; +const octokit = new Octokit({ auth: token }); + +export const getProjectContributors = async ( + owner: string, + repo: string, + top: number = 4 +): Promise => { + const topContributors = await getGithubRepoContributors(owner, repo, top); + const projectContributors = Promise.all( + topContributors.map(async (contributor) => { + const user = await octokit.rest.users.getByUsername({ + username: contributor.login, + }); + if (user.data.name) { + return { + ...contributor, + ...user.data, + name: user.data.name!, + }; + } + return; + }) + ); + return (await projectContributors).filter((contributor) => contributor !== undefined); +}; + +export const getGithubRepo = async (owner: string, repo: string): Promise => { + const response = await octokit.rest.repos.get({ + owner: owner, + repo: repo, + }); + return response.data; +}; + +export const getGithubRepoContributors = async ( + owner: string, + repo: string, + top: number = 4 +): Promise => { + const response = await octokit.rest.repos.listContributors({ + owner: owner, + repo: repo, + per_page: top, + }); + const contributors = response.data.map((contributor) => { + if (contributor.login) { + return { + ...contributor, + login: contributor.login!, + }; + } + return; + }); + return contributors.filter((contributor) => contributor !== undefined); +}; diff --git a/src/lib/project.ts b/src/lib/project.ts new file mode 100644 index 000000000..95a1498a9 --- /dev/null +++ b/src/lib/project.ts @@ -0,0 +1,37 @@ +import path from "path"; +import { VWCProject, VWCProjectDetails } from "@utils/types"; +import fs from "fs"; +import { getSlugs } from "./util"; +import { getGithubRepo, getProjectContributors } from "./github"; + +const projectDirectory = path.join(process.cwd(), "src/data/projects"); + +export function getProjectBySlug(slug: string): VWCProjectDetails { + const fullPath = path.join(projectDirectory, slug); + return JSON.parse(fs.readFileSync(fullPath, "utf8")); +} + +export const getAllProjects = (): VWCProjectDetails[] => { + const slugs = getSlugs(projectDirectory); + return slugs.map((slug) => getProjectBySlug(slug)); +}; + +export const getProjectData = async (): Promise => { + const projects = getAllProjects(); + const data = Promise.all( + projects.map(async (project) => { + const repo = await getGithubRepo(project.owner, project.repo); + const contributors = await getProjectContributors(project.owner, project.repo); + const data = { + details: project, + repo: { + ...repo, + contributors: contributors, + }, + }; + return data; + }) + ); + // Sort projects by index + return (await data).sort((a, b) => a.details.index - b.details.index); +}; diff --git a/src/pages/projects.tsx b/src/pages/projects.tsx new file mode 100644 index 000000000..10fa3d456 --- /dev/null +++ b/src/pages/projects.tsx @@ -0,0 +1,350 @@ +import type { GetStaticProps, NextPage } from "next"; +import SEO from "@components/seo/page-seo"; +import Layout01 from "@layout/layout-01"; +import Breadcrumb from "@components/breadcrumb"; +import { VWCContributor, VWCProject, VWCProjectRepo } from "@utils/types"; +import { VWCGrid } from "@components/vwc-grid"; +import { VWCGridCard } from "@components/vwc-card"; +import * as Dialog from "@radix-ui/react-dialog"; +import { useState } from "react"; +import { AnimatePresence, motion } from "motion/react"; +import { getProjectData } from "../lib/project"; +import { Star, CircleDot, Eye, GitFork, XIcon } from "lucide-react"; +import { GitHubLogoIcon, GlobeIcon } from "@radix-ui/react-icons"; +import clsx from "clsx"; +import Link from "next/link"; +import MarkdownRenderer from "@components/markdown-renderer"; + +interface TechStackProps { + techStack: string[]; +} + +const TechStack = ({ techStack }: TechStackProps) => { + return ( +
+ {techStack.map((tech) => ( +
+ {tech} +
+ ))} +
+ ); +}; + +interface LinkButtonsProps { + github_url: string; + live_url?: string; +} + +const LinkButtons = ({ github_url, live_url }: LinkButtonsProps) => { + return ( + <> +
+ + +
+ GitHub +
+ + {live_url && ( + + +
+ Live +
+ + )} +
+ + ); +}; + +interface RepoStatsProps { + repo: VWCProjectRepo; +} + +const RepoStats = ({ repo }: RepoStatsProps) => { + return ( + <> + {/* Repo stats */} +
Repo Statistics
+
+ {/* Github Stars */} +
+
+ {repo.stargazers_count} +
+
+ +
Stars
+
+
+ {/* Github Issues */} +
+
+ {repo.open_issues_count} +
+
+ +
Issues
+
+
+ {/* Github Watching */} +
+
+ {repo.subscribers_count} +
+
+ +
Watching
+
+
+ {/* Github Forks */} +
+
+ {repo.forks_count} +
+
+ +
Forks
+
+
+
+ + ); +}; + +interface TopContributorsProps { + contributors: VWCContributor[]; +} + +const TopContributors = ({ contributors }: TopContributorsProps) => { + return ( + <> +
Top Contributors
+
+ {contributors.map((contributor) => { + return ( + +
+ {contributor.name} +
+
+
{contributor.name}
+
@{contributor.login}
+
+ + ); + })} +
+ + ); +}; + +interface ProjectModalProps { + project: VWCProject; + className?: string; +} + +const ProjectDetailModal = ({ project, className }: ProjectModalProps) => { + const duration = 0.35; + const xOffset = 25; + const delay = 0.1; + + return ( +
+ {/* Left Column */} + + {project.details.thumbnail.alt} + + + + {/* Center divider */} +
+ +
+ +
+ {`${project.details.name} +
+
+ {/* Right column */} + +

{project.details.name}

+

{project.details.headline}

+ + + {project.details.long_description.map((pg) => { + return ( +

+ +

+ ); + })} +
+
+ ); +}; + +interface ProjectCardProps { + project: VWCProject; +} + +const ProjectCard = ({ project }: ProjectCardProps) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + + + + {isOpen && ( + + + + + + + + + + + + + + )} + + + ); +}; + +type TProps = { + projects: VWCProject[]; +}; + +type PageProps = NextPage & { + Layout: typeof Layout01; +}; + +const Projects: PageProps = ({ projects }: TProps) => { + return ( + <> + + + + {projects.map((project) => ( + + ))} + + + ); +}; + +Projects.Layout = Layout01; + +export const getStaticProps: GetStaticProps = async () => { + try { + const projects = await getProjectData(); + return { + props: { + projects, + layout: { + headerShadow: true, + headerFluid: false, + footerMode: "light", + }, + }, + revalidate: 10 * 60, // Regenerate every 10 minutes + }; + } catch (err) { + if (err instanceof Error) { + console.error(`Error while regenerating projects page: ${err.message}`); + } + throw new Error(`Failed to update github project data`); + } +}; + +export default Projects; diff --git a/src/pages/subjects/all.tsx b/src/pages/subjects/all.tsx index d91de3941..c7d143b78 100644 --- a/src/pages/subjects/all.tsx +++ b/src/pages/subjects/all.tsx @@ -2,8 +2,12 @@ import type { GetStaticProps, NextPage } from "next"; import SEO from "@components/seo/page-seo"; import Layout01 from "@layout/layout-01"; import Breadcrumb from "@components/breadcrumb"; -import CourseArea from "@containers/course-full/layout-01"; +import { VWCGrid } from "@components/vwc-grid"; import { ICourse } from "@utils/types"; +import { courseSorting } from "@utils/methods"; +import { useSort } from "@hooks"; +import { VWCGridCard } from "@components/vwc-card"; +import Anchor from "@ui/anchor"; import { getallCourses } from "../../lib/course"; type TProps = { @@ -17,11 +21,18 @@ type PageProps = NextPage & { }; const Coursegrid01: PageProps = ({ data }) => { + const { sortedItems } = useSort(data.courses, courseSorting); return ( <> - + + {sortedItems?.map((course) => ( + + + + ))} + ); }; diff --git a/src/utils/types.ts b/src/utils/types.ts index c7b9ec7a1..0ad04268c 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -172,6 +172,48 @@ export interface ICourse { curriculum: IDType[]; } +export interface VWCProjectDetails { + index: number; + name: string; + headline: string; + long_description: string[]; + technologies: string[]; + owner: string; + repo: string; + live_url?: string; + thumbnail: ImageType; +} + +export interface VWCProject { + details: VWCProjectDetails; + repo: VWCProjectRepo; +} + +export interface GithubUser { + name: string; + avatar_url: string; + html_url: string; +} + +export interface GithubContributor { + login: string; + contributions: number; +} + +export interface GithubRepo { + html_url: string; + stargazers_count: number; + open_issues_count: number; + forks_count: number; + subscribers_count: number; +} + +export interface VWCProjectRepo extends GithubRepo { + contributors: VWCContributor[]; +} + +export interface VWCContributor extends GithubContributor, GithubUser {} + export interface BlogMetaType { title: string; slug: string; diff --git a/src/utils/variants.ts b/src/utils/variants.ts index b622d47da..de0b5c7a8 100644 --- a/src/utils/variants.ts +++ b/src/utils/variants.ts @@ -1,4 +1,4 @@ -import { Variants } from "framer-motion"; +import { Variants } from "motion/react"; export const scrollUpVariants: Variants = { offscreen: {