From 5de41d1a865d673a8073115e43da4f07afa55414 Mon Sep 17 00:00:00 2001 From: Ibnu Date: Sun, 8 Oct 2023 22:58:24 +0700 Subject: [PATCH] THis will initial deploy --- .env | 3 + next.config.js | 28 +- package-lock.json | 383 +++++++++++++++++- package.json | 7 +- src/app/dashboard/layout.tsx | 104 +++-- src/app/dashboard/page.tsx | 301 ++++++++++++++ src/app/dashboard/template-dashboard.tsx | 5 + src/app/forgot-password/page.tsx | 6 +- src/app/globals.css | 4 +- src/app/layout.tsx | 29 +- src/app/login/page.tsx | 137 +------ src/app/middleware.tsx | 13 + src/app/page.tsx | 160 +++++++- src/app/register/page.tsx | 7 +- src/app/template-auth.tsx | 32 ++ src/components/Atoms/Button/ButtonBasic.tsx | 13 + src/components/Common/AuthLayout.tsx | 2 +- src/components/Organisms/Loader/index.tsx | 29 ++ src/components/Organisms/Table/index.tsx | 2 +- src/helpers/GatewayService.tsx | 74 +++- src/helpers/Routes.tsx | 2 +- src/helpers/Utils.tsx | 52 +++ src/middleware/authMiddleware.tsx | 24 ++ .../actions/AuthenticationAction.tsx | 24 +- src/redux/provider.tsx | 8 + src/redux/reducers.tsx | 2 +- src/styles/dashboard.module.scss | 137 +++++++ 27 files changed, 1346 insertions(+), 242 deletions(-) create mode 100644 .env create mode 100644 src/app/dashboard/page.tsx create mode 100644 src/app/dashboard/template-dashboard.tsx create mode 100644 src/app/middleware.tsx create mode 100644 src/app/template-auth.tsx create mode 100644 src/components/Organisms/Loader/index.tsx create mode 100644 src/helpers/Utils.tsx create mode 100644 src/middleware/authMiddleware.tsx rename src/{ => redux}/actions/AuthenticationAction.tsx (92%) create mode 100644 src/redux/provider.tsx create mode 100644 src/styles/dashboard.module.scss diff --git a/.env b/.env new file mode 100644 index 0000000..6356bc8 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +NEXT_PUBLIC_API_URL = https://api-staging.constructapp.team +NEXT_PUBLIC_PORT = 3000 +NEXT_ENCRYPTION_KEY = IBNU \ No newline at end of file diff --git a/next.config.js b/next.config.js index 767719f..20419fd 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,28 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + images: { + domains: ["via.placeholder.com"], + remotePatterns: [ + { + protocol: "https", + hostname: "via.placeholder.com", + port: "", + pathname: "/dashboard", + }, + ], + }, + // serverMiddleware: [ + // { + // path: "/dashboard", + // handler: require.resolve("./src/app/middleware.tsx"), // Sesuaikan dengan lokasi middleware Anda + // }, + // ], + serverMiddleware: [ + { + path: "/dashboard", + handler: "./src/app/middleware.tsx", + }, + ], +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 12177b1..9bc04d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,16 @@ "name": "ta-fe-new", "version": "0.1.0", "dependencies": { + "@react-spring/web": "^9.7.3", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", - "boostrap": "^2.0.0", + "bootstrap": "^5.3.2", "chart.js": "^4.3.0", + "crypto-js": "^4.1.1", "js-cookie": "^3.0.5", + "moment": "^2.29.4", "next": "13.5.4", + "next-auth": "^4.23.2", "react": "^18", "react-bootstrap": "^2.9.0", "react-chartjs-2": "^5.2.0", @@ -27,6 +31,7 @@ "sass-loader": "^13.2.2" }, "devDependencies": { + "@types/crypto-js": "^4.1.2", "@types/js-cookie": "^3.0.3", "@types/node": "^20", "@types/react": "^18", @@ -393,6 +398,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -416,6 +429,66 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@reduxjs/toolkit": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", @@ -492,6 +565,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/crypto-js": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.2.tgz", + "integrity": "sha512-t33RNmTu5ufG/sorROIafiCVJMx3jz95bXUMoPAZcUD14fxMXnuTzqzXZoxpR0tNx2xpw11Dlmem9vGCsrSOfA==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -1177,11 +1256,23 @@ "node": ">=8" } }, - "node_modules/boostrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boostrap/-/boostrap-2.0.0.tgz", - "integrity": "sha512-JEeFMOweKeGXEM9rt95eaVISOkluG9aKcl0jQCETOVH9jynCZxuBZe2oWgcWJpj5wqYWZl625SnW7OgHT2Ineg==", - "deprecated": "Package no longer supported. Contact support@npmjs.com for more info." + "node_modules/bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -1426,6 +1517,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1440,6 +1539,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -3065,6 +3169,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz", + "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -3242,7 +3354,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3318,6 +3429,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3397,6 +3516,33 @@ } } }, + "node_modules/next-auth": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.23.2.tgz", + "integrity": "sha512-VRmInu0r/yZNFQheDFeOKtiugu3bt90Po3owAQDnFQ3YLQFmUKgFjcE2+3L0ny5jsJpBXaKbm7j7W2QTc6Ye2A==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -3411,6 +3557,11 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3419,6 +3570,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -3528,6 +3687,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3537,6 +3704,20 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz", + "integrity": "sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==", + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -3681,6 +3862,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.18.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz", + "integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3690,6 +3891,11 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4807,6 +5013,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -5005,8 +5219,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yocto-queue": { "version": "0.1.0", @@ -5245,6 +5458,11 @@ "fastq": "^1.6.0" } }, + "@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==" + }, "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -5258,6 +5476,49 @@ "@swc/helpers": "^0.5.0" } }, + "@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "requires": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + } + }, + "@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "requires": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + } + }, + "@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "requires": { + "@react-spring/types": "~9.7.3" + } + }, + "@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "requires": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + } + }, "@reduxjs/toolkit": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", @@ -5315,6 +5576,12 @@ "tslib": "^2.4.0" } }, + "@types/crypto-js": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.2.tgz", + "integrity": "sha512-t33RNmTu5ufG/sorROIafiCVJMx3jz95bXUMoPAZcUD14fxMXnuTzqzXZoxpR0tNx2xpw11Dlmem9vGCsrSOfA==", + "dev": true + }, "@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -5876,10 +6143,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, - "boostrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boostrap/-/boostrap-2.0.0.tgz", - "integrity": "sha512-JEeFMOweKeGXEM9rt95eaVISOkluG9aKcl0jQCETOVH9jynCZxuBZe2oWgcWJpj5wqYWZl625SnW7OgHT2Ineg==" + "bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "requires": {} }, "brace-expansion": { "version": "1.1.11", @@ -6045,6 +6313,11 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6056,6 +6329,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -7254,6 +7532,11 @@ } } }, + "jose": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz", + "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==" + }, "js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -7404,7 +7687,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -7459,6 +7741,11 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7504,6 +7791,22 @@ "watchpack": "2.4.0" } }, + "next-auth": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.23.2.tgz", + "integrity": "sha512-VRmInu0r/yZNFQheDFeOKtiugu3bt90Po3owAQDnFQ3YLQFmUKgFjcE2+3L0ny5jsJpBXaKbm7j7W2QTc6Ye2A==", + "requires": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + } + }, "node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -7515,11 +7818,21 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" + }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -7599,6 +7912,11 @@ "es-abstract": "^1.22.1" } }, + "oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7608,6 +7926,17 @@ "wrappy": "1" } }, + "openid-client": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz", + "integrity": "sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==", + "requires": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + } + }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7699,12 +8028,30 @@ "source-map-js": "^1.0.2" } }, + "preact": { + "version": "10.18.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz", + "integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==" + }, + "preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "requires": { + "pretty-format": "^3.8.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8426,6 +8773,11 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -8575,8 +8927,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index a0dea33..ead3730 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,16 @@ "lint": "next lint" }, "dependencies": { + "@react-spring/web": "^9.7.3", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", - "boostrap": "^2.0.0", + "bootstrap": "^5.3.2", "chart.js": "^4.3.0", + "crypto-js": "^4.1.1", "js-cookie": "^3.0.5", + "moment": "^2.29.4", "next": "13.5.4", + "next-auth": "^4.23.2", "react": "^18", "react-bootstrap": "^2.9.0", "react-chartjs-2": "^5.2.0", @@ -28,6 +32,7 @@ "sass-loader": "^13.2.2" }, "devDependencies": { + "@types/crypto-js": "^4.1.2", "@types/js-cookie": "^3.0.3", "@types/node": "^20", "@types/react": "^18", diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index d36c8ba..514601d 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,20 +1,42 @@ -import React, { useState } from "react"; +"use client"; + +import React, { useEffect, useState } from "react"; import { Nav, Collapse, Navbar, Dropdown } from "react-bootstrap"; -import { useRouter } from "next/router"; +import { usePathname, useRouter } from "next/navigation"; import Logo from "@/assets/svg/Logo.svg"; import Link from "next/link"; import Image from "next/image"; -import styles from "./layout.module.scss"; +import styles from "@/styles/dashboard.module.scss"; +import { useAppDispatch } from "@/redux/store"; +import { logoutRequest } from "@/redux/actions/AuthenticationAction"; +// import { privateMiddleware } from "@/middleware/authMiddleware"; +import { getCredential } from "@/helpers/Utils"; +import Loader from "@/components/Organisms/Loader"; export default function DashboardLayout({ children, }: { children: React.ReactNode; }): JSX.Element { - const router = useRouter(); + const pathname = usePathname(); + const dispatch = useAppDispatch(); + + const middleware = getCredential(); + const navigate = useRouter(); + + // TODO:MIDDLEWARE + useEffect(() => { + if (middleware === null) { + navigate.push("/login"); + } + }, []); + + const handleLogout = () => { + dispatch(logoutRequest()); + }; const [adminMenu, setAdminMenu] = useState([ { @@ -37,10 +59,10 @@ export default function DashboardLayout({ ), name: "Dashboard", - url: "/", + url: "/dashboard", isDropdown: false, isCollapse: false, - active: ["/"], + active: ["/dashboard"], subItems: [], }, { @@ -67,31 +89,31 @@ export default function DashboardLayout({ isDropdown: true, isCollapse: false, active: [ - "/leads-overzicht", - "/niuewe-leads", - "/creditaties", - "/lead-toevoegen", + "/dashboard/leads-overzicht", + "/dashboard/niuewe-leads", + "/dashboard/creditaties", + "/dashboard/lead-toevoegen", ], subItems: [ { id: 21, name: "Leads Overzicht", - url: "/leads-overzicht", + url: "/dashboard/leads-overzicht", }, { id: 22, name: "Niuewe Leads", - url: "/niuewe-leads", + url: "/dashboard/niuewe-leads", }, { id: 23, name: "Creditaties", - url: "/creditaties", + url: "/dashboard/creditaties", }, { id: 24, name: "Lead Toevoegen", - url: "/lead-toevoegen", + url: "/dashboard/lead-toevoegen", }, ], }, @@ -118,22 +140,26 @@ export default function DashboardLayout({ url: "#", isCollapse: false, isDropdown: true, - active: ["/bedrijfsoverzicht", "/dubbel-accounts", "uitgezette-accounts"], + active: [ + "/dashboard/bedrijfsoverzicht", + "/dashboard/dubbel-accounts", + "/dashboard/uitgezette-accounts", + ], subItems: [ { id: 31, name: "Bedrijfsoverzicht", - url: "/bedrijfsoverzicht", + url: "/dashboard/bedrijfsoverzicht", }, { id: 32, name: "Dubbel Accounts", - url: "/dubbel-accounts", + url: "/dashboard/dubbel-accounts", }, { id: 33, name: "Uitgezette Accounts", - url: "/uitgezette-accounts", + url: "/dashboard/uitgezette-accounts", }, ], }, @@ -157,10 +183,10 @@ export default function DashboardLayout({ ), name: "Bellijst", - url: "/bellijst", + url: "/dashboard/bellijst", isCollapse: false, isDropdown: false, - active: ["/bellijst"], + active: ["dashboard/bellijst"], subItems: [], }, { @@ -186,12 +212,12 @@ export default function DashboardLayout({ url: "#", isCollapse: false, isDropdown: true, - active: ["/overzicht"], + active: ["/dashboard/overzicht"], subItems: [ { id: 51, name: "Overzicht", - url: "/overzicht", + url: "/dashboard/overzicht", }, ], }, @@ -223,38 +249,38 @@ export default function DashboardLayout({ { id: 61, name: "Branches", - url: "/branches", + url: "/dashboard/branches", }, { id: 62, name: "Werkzaamheden", - url: "/werkzaamheden", + url: "/dashboard/werkzaamheden", }, // { // id: 43, // name: "Add Leads", - // url: "/add-leads", + // url: "/dashboard/add-leads", // }, // { // id: 45, // name: "Creditaties", - // url: "/credit-management", + // url: "/dashboard/credit-management", // }, // { // id: 46, // name: "Formulate", - // url: "/formulate", + // url: "/dashboard/formulate", // }, // { // id: 47, // name: "Statistics", - // url: "/statistics", + // url: "/dashboard/statistics", // }, // { // id: 48, // name: "Open Invoices", - // url: "/open-invoices", + // url: "/dashboard/open-invoices", // }, ], }, @@ -286,12 +312,12 @@ export default function DashboardLayout({ { id: 71, name: "Leads", - url: "/leads", + url: "/dashboard/leads", }, { id: 72, name: "Omzet", - url: "/omzet", + url: "/dashboard/omzet", }, ], }, @@ -354,9 +380,7 @@ export default function DashboardLayout({ as={Link} href={item.url} className={`${styles["nav-link"]} ${ - item.active.includes(router.pathname) - ? `${styles["active"]}` - : "" + item.active.includes(pathname) ? `${styles["active"]}` : "" }`} > {item.icon} @@ -371,7 +395,7 @@ export default function DashboardLayout({ className={`d-flex justify-content-between align-items-center ${ styles["nav-link"] } ${ - item.active.includes(router.pathname) + item.active.includes(pathname) ? `${styles["active"]}` : "" }`} @@ -436,7 +460,7 @@ export default function DashboardLayout({ onClick={() => handleCollapse(item.id)} className={`${styles["nav-link"]} ${ - subItem.url === router.pathname + subItem.url === pathname ? `${styles["active"]}` : "" } @@ -497,7 +521,10 @@ export default function DashboardLayout({ - + handleLogout()} + className={styles["dropdown-item"]} + > - Logout + Logout @@ -546,6 +573,7 @@ export default function DashboardLayout({
{children}
+ {/* {middleware === null && } */} ); } diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..d1c41df --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,301 @@ +"use client"; + +import { useState } from "react"; +import { Col, Row } from "react-bootstrap"; + +import Head from "next/head"; + +import TableBasic from "@/components/Organisms/Table"; +import BadgeBasic from "@/components/Atoms/Badge/BadgeBasic"; + +import CardAnalytics from "@/components/Pages/Dashboard/CardAnalytics"; +import DetailAnalytics from "@/components/Pages/Dashboard/DetailAnalytics"; +// import { middleware } from "../middleware"; + +export function Home() { + const [selectedCard, setSelectedCard] = useState(0); + + const cardLists = [ + { + id: 1, + title: "Receive Leads", + value: "3.254", + percent: "7.25%", + increase: "250", + from: "last month", + status: 4, + }, + { + id: 2, + title: "Locations", + value: "3.254", + percent: "120%", + increase: "13", + from: "last month", + status: 2, + }, + { + id: 3, + title: "Cost", + value: "€2.302,5", + percent: "8.5%", + increase: "250,12", + from: "last month", + status: 2, + }, + ]; + + const detailReceiveLeads = [ + { + id: 1, + name: "Tile Roof", + total: "12 Leads", + }, + { + id: 2, + name: "No plate", + total: "19 Leads", + }, + { + id: 3, + name: "Chimney", + total: "35 Leads", + }, + { + id: 4, + name: "Gutter", + total: "33 Leads", + }, + { + id: 5, + name: "Dormer window / Skylight", + total: "8 Leads", + }, + ]; + + const detailsLocations = [ + { + id: 1, + name: "Groningen", + total: "40 Leads", + }, + { + id: 2, + name: "Zeeland", + total: "20 Leads", + }, + { + id: 3, + name: "Noord-Brabant", + total: "15 Leads", + }, + { + id: 4, + name: "Limburg", + total: "45 leads", + }, + ]; + + const detailCosts: never[] = []; + + const data = { + data: [ + { + id: 1, + date_received: "2022-12-01 • 13:47:16", + lead_data: "Martin Garrix", + phone: "0650603160", + email: "mgarrix@gmail.com", + status: 4, + }, + { + id: 2, + date_received: "2022-12-06 • 13:47:16", + lead_data: "Roy Kiyoshi", + phone: "(808) 555-0111", + email: "rkiyoshi@gmail.com", + status: 2, + }, + + { + id: 3, + date_received: "2022-13-01 • 13:47:16", + lead_data: "Jane Cooper", + phone: "(217) 555-0113", + email: "alexandre@yahoo.com", + status: 4, + }, + ], + meta: { + current_page: 1, + from: 1, + last_page: 10, + links: [ + { + url: null, + label: "« Previous", + active: false, + }, + { + url: "https://api-staging.constructapp.online/project/documents?page=1", + label: "1", + active: true, + }, + { + url: null, + label: "Next »", + active: false, + }, + ], + path: "https://api-staging.constructapp.online/project/documents", + per_page: 15, + to: 1, + total: 1, + }, + }; + + const headers = [ + { + title: "Date Received", + selector: "date_received", + }, + { + title: "Lead Data", + selector: "lead_data", + }, + { + title: "Phone Number", + selector: "phone", + }, + { + title: "Email", + selector: "email", + }, + { + title: "Invoiced", + Cell: (row: { status: number }) => ( + + ), + }, + { + title: "Action", + selector: "action", + isAction: true, + actions: [ + { + label: "Edit Inhouse User", + navigate: "user/details", + icon: ( + + + + + ), + + onClick: (row: any) => console.log(row), + }, + { + label: "Login as this User", + onClick: (row: any) => console.log(row), + icon: ( + + + + + ), + }, + ], + }, + ]; + + const handleSelectedDetails = (id: number) => { + switch (id) { + case 1: + return detailReceiveLeads; + case 2: + return detailsLocations; + case 3: + return detailCosts; + default: + return []; + } + }; + + return ( + <> + + Dashboard + + + + + + + + + + + {selectedCard !== 0 && ( + + )} + + + ); +} +export default Home; diff --git a/src/app/dashboard/template-dashboard.tsx b/src/app/dashboard/template-dashboard.tsx new file mode 100644 index 0000000..158c827 --- /dev/null +++ b/src/app/dashboard/template-dashboard.tsx @@ -0,0 +1,5 @@ +import BgAuth from "@/assets/images/bg-auth.jpg"; + +export default function Template({ children }: { children: React.ReactNode }) { + return
{children}
; +} diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx index 1e0bfef..9ecdbd8 100644 --- a/src/app/forgot-password/page.tsx +++ b/src/app/forgot-password/page.tsx @@ -11,6 +11,8 @@ import Image from "next/image"; import { useForm } from "react-hook-form"; import Link from "next/link"; import Head from "next/head"; +import Template from "../template-auth"; +import { Fragment } from "react"; function ForgotPassword(): JSX.Element { const { @@ -48,7 +50,7 @@ function ForgotPassword(): JSX.Element { }; return ( - <> + ); } diff --git a/src/app/globals.css b/src/app/globals.css index d4f491e..515036b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,4 @@ -:root { +/* :root { --max-width: 1100px; --border-radius: 12px; --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', @@ -104,4 +104,4 @@ a { html { color-scheme: dark; } -} +} */ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5a89aab..b4c9165 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,13 +1,17 @@ -import "./globals.css"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; -import BgAuth from "@/assets/images/bg-auth.jpg"; +import { Providers } from "@/redux/provider"; + +import "react-toastify/dist/ReactToastify.css"; +import "@/styles/custom.scss"; +import "./globals.css"; +import { ToastContainer } from "react-toastify"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Bouwopdrachten", + description: "Bouwopdrachten", }; export default function RootLayout({ @@ -17,19 +21,12 @@ export default function RootLayout({ }) { return ( - -
+ + + {children} -
- + + ); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 65dee41..a352b40 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,18 +1,9 @@ "use client"; -import { Col, Form, Card } from "react-bootstrap"; - -import ButtonBasic from "@/components/Atoms/Button/ButtonBasic"; -import InputBasic from "@/components/Atoms/Input/InputBasic"; - -import Logo from "@/assets/svg/Logo.svg"; - -import Link from "next/link"; -import Image from "next/image"; import { useForm } from "react-hook-form"; -import Head from "next/head"; -import { makeLoginRequest } from "@/actions/AuthenticationAction"; -import { useAppDispatch } from "@/redux/store"; +import Template from "../template-auth"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; function Login(): JSX.Element { const { @@ -23,123 +14,17 @@ function Login(): JSX.Element { criteriaMode: "all", }); - const dispatch = useAppDispatch(); - - /** - * Function on submit - * - * @param {object} data - * @return {void} - * @see cypress/e2e/authentication/login/login.cy.ts - * To cypress unit tester - */ - const onSubmit = (data: any): void => { - // initialize form data - const payload = new FormData(); - - // append data to form data - for (const key in data) { - payload.append(key, data[key as keyof typeof data]); - } - - console.log(payload); + const navigate = useRouter(); - // Dispatch login action with form data as payload - // dispatch(getCSRF()); - dispatch(makeLoginRequest(payload as any)); - }; + // if go there redirect to / + useEffect(() => { + navigate.push("/"); + }, []); return ( - <> - - Log in - - - - - - Logo -
-

Login

-

- Vul hieronder je gegevens in om in te loggen. -

-
-
- - E-mailadres - - - - - Wachtwoord - - - - - - - -
- -
-
- - Registreer Gratis! - - - - Je wachtwoord vergeten - -
-
-
- - + ); } diff --git a/src/app/middleware.tsx b/src/app/middleware.tsx new file mode 100644 index 0000000..b04bd03 --- /dev/null +++ b/src/app/middleware.tsx @@ -0,0 +1,13 @@ +import { withAuth } from "next-auth/middleware"; +// middleware is applied to all routes, use conditionals to select + +export default withAuth(function middleware(req) {}, { + callbacks: { + authorized: ({ req, token }) => { + if (req.nextUrl.pathname.startsWith("/protected") && token === null) { + return false; + } + return true; + }, + }, +}); diff --git a/src/app/page.tsx b/src/app/page.tsx index 3ed8acb..df2ed18 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,158 @@ -import Image from 'next/image' -import styles from './page.module.css' +"use client"; -export default function Page() { - return

Hello, Next.js!

; +import { Col, Form, Card } from "react-bootstrap"; + +import ButtonBasic from "@/components/Atoms/Button/ButtonBasic"; +import InputBasic from "@/components/Atoms/Input/InputBasic"; + +import Logo from "@/assets/svg/Logo.svg"; + +import Image from "next/image"; +import Link from "next/link"; +import { useForm } from "react-hook-form"; +import Head from "next/head"; +import { makeLoginRequest } from "@/redux/actions/AuthenticationAction"; +import { RootState, useAppDispatch } from "@/redux/store"; +import Template from "./template-auth"; +import { useRouter } from "next/navigation"; +import { useSelector } from "react-redux"; +// import { middleware } from "./middleware"; +import { NextPage } from "next"; + +function LoginPage(): JSX.Element { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + criteriaMode: "all", + }); + + const loginRequestState = useSelector( + (state: RootState) => state.loginRequest + ); + + const dispatch = useAppDispatch(); + const navigate = useRouter(); + + /** + * Function on submit + * + * @param {object} data + * @return {void} + * @see cypress/e2e/authentication/login/login.cy.ts + * To cypress unit tester + */ + const onSubmit = (data: any): void => { + // initialize form data + const payload = new FormData(); + + // append data to form data + for (const key in data) { + payload.append(key, data[key as keyof typeof data]); + } + + // Dispatch login action with form data as payload + // dispatch(getCSRF()); + + dispatch(makeLoginRequest(payload as any)); + // navigate.push("/dashboard"); + }; + + return ( + + ); } + +export default LoginPage; +LoginPage.Layout = "Auth"; diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 275d5a5..260b9f8 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -11,7 +11,8 @@ import Image from "next/image"; import { useForm } from "react-hook-form"; import InputPhone from "@/components/Atoms/Input/InputPhone"; import Head from "next/head"; - +import Template from "../template-auth"; +import { Fragment } from "react"; function Register(): JSX.Element { const { @@ -49,7 +50,7 @@ function Register(): JSX.Element { }; return ( - <> + ); } diff --git a/src/app/template-auth.tsx b/src/app/template-auth.tsx new file mode 100644 index 0000000..5865a73 --- /dev/null +++ b/src/app/template-auth.tsx @@ -0,0 +1,32 @@ +"use client"; +import BgAuth from "@/assets/images/bg-auth.jpg"; +import { getCredential } from "@/helpers/Utils"; +import { useEffect } from "react"; +// import { publicMiddleware } from "../middleware/authMiddleware"; +import { useRouter } from "next/navigation"; + +export default function Template({ children }: { children: React.ReactNode }) { + const middleware = getCredential(); + const navigate = useRouter(); + + // TODO:MIDDLEWARE + useEffect(() => { + if (middleware !== null) { + navigate.push("/dashboard"); + } + }, []); + + return ( +
+ {children} +
+ ); +} diff --git a/src/components/Atoms/Button/ButtonBasic.tsx b/src/components/Atoms/Button/ButtonBasic.tsx index e8545dc..f90891b 100644 --- a/src/components/Atoms/Button/ButtonBasic.tsx +++ b/src/components/Atoms/Button/ButtonBasic.tsx @@ -7,6 +7,8 @@ export default function ButtonBasic({ className, type, onClick, + loading, + disabled, }: { id?: string; text: string | JSX.Element; @@ -14,6 +16,8 @@ export default function ButtonBasic({ className?: string; type: "button" | "submit" | "reset"; onClick?: () => void; + loading?: boolean; + disabled?: boolean; }): JSX.Element { return ( ); diff --git a/src/components/Common/AuthLayout.tsx b/src/components/Common/AuthLayout.tsx index 93ea081..d887195 100644 --- a/src/components/Common/AuthLayout.tsx +++ b/src/components/Common/AuthLayout.tsx @@ -1,4 +1,4 @@ -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useEffect } from "react"; import Cookies from "js-cookie"; diff --git a/src/components/Organisms/Loader/index.tsx b/src/components/Organisms/Loader/index.tsx new file mode 100644 index 0000000..da71484 --- /dev/null +++ b/src/components/Organisms/Loader/index.tsx @@ -0,0 +1,29 @@ +"use client"; +import { Spinner } from "react-bootstrap"; + +import Logo from "@/assets/svg/Logo.svg"; +import Image from "next/image"; + +export default function Loader() { + return ( +
+ Logo +
+ {/* + Loading...   + */} + + Loading... + +
+
+ ); +} diff --git a/src/components/Organisms/Table/index.tsx b/src/components/Organisms/Table/index.tsx index 493afb4..674990d 100644 --- a/src/components/Organisms/Table/index.tsx +++ b/src/components/Organisms/Table/index.tsx @@ -1,7 +1,7 @@ import { Table, Pagination, Dropdown, Col, Row } from "react-bootstrap"; import styles from "./table.module.scss"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import BannerBasic from "@/components/Molecules/BannerBasic"; import InputBasic from "@/components/Atoms/Input/InputBasic"; import InputSelect from "@/components/Atoms/Input/InputSelect"; diff --git a/src/helpers/GatewayService.tsx b/src/helpers/GatewayService.tsx index 92d6026..00e5961 100644 --- a/src/helpers/GatewayService.tsx +++ b/src/helpers/GatewayService.tsx @@ -1,6 +1,7 @@ import axios, { AxiosError, AxiosResponse } from "axios"; import { toast } from "react-toastify"; import Cookies from "js-cookie"; +import { getCredential } from "./Utils"; const apiUrl: string = process.env.NEXT_PUBLIC_API_URL ?? "https://api-staging.constructapp.online"; @@ -28,20 +29,20 @@ const onRequest = (config: any): any => { // Proceed with the initial response // If token not null, add token to header - if (Cookies.get("admin") !== undefined) { + if (getCredential() !== null) { // initialize token const { token, }: { token: string; - } = JSON.parse(Cookies.get("admin") as any); + } = getCredential() as any; // add token to header config.headers.Authorization = `Bearer ${token}`; } // If token null, remove token from header - if (Cookies.get("admin") === undefined) { + if (getCredential() === null) { // remove token from header delete config.headers.Authorization; } @@ -53,6 +54,7 @@ const onRequest = (config: any): any => { * Function for handle error by status * * @param {number} status + * @param message * @returns {string} */ const handleError = (status: number, message: any): string => { @@ -62,7 +64,8 @@ const handleError = (status: number, message: any): string => { errorMessage = message ?? "Bad request"; break; case 401: - errorMessage = message ?? "Unauthorized"; + errorMessage = + "Your session has expired, you'll be redirected to login page"; break; case 403: errorMessage = @@ -89,20 +92,27 @@ const handleError = (status: number, message: any): string => { // attach request axiosClient.interceptors.request.use(onRequest, null); -let errorDisplayed: boolean = false; +// let errorDisplayed: boolean = false; // attach response axiosClient.interceptors.response.use( async (response: AxiosResponse) => { return response; }, - async (error: AxiosError) => { - // Initialize error message + async (error: AxiosError) => { // If error has response, show error message - if (error?.response?.data !== undefined && error?.response?.data !== null) { - error?.response?.status !== 422 - ? toast.error( + + if ( + error.config !== undefined && + error.config.params?.intercept_error !== true + ) { + if ( + error?.response?.data !== undefined && + error?.response?.data !== null + ) { + if (error?.response?.status !== 422) { + toast.error( handleError( error?.response?.status, error?.response?.data?.message @@ -110,15 +120,38 @@ axiosClient.interceptors.response.use( { toastId: error?.response?.data?.message, } - ) - : Object.keys(error?.response?.data?.errors).map((key: any) => { + ); + + // If error status is 500, reload page + if (error?.response?.status === 500) { + // reload page + setTimeout(() => { + window.location.reload(); + }, 2000); + } + } else { + // Special Case for 422 when invite member to project + if ( + error.response?.data?.message === + "This user or their company owner is already a member of this project." + ) { + toast.error( + "This user or their company owner is already a member of this project.", + { + toastId: + "This user or their company owner is already a member of this project.", + } + ); + return await Promise.reject(error); + } + + Object.keys(error?.response?.data?.errors).map((key: any) => { return toast.error(error?.response?.data?.errors[key][0], { - toastId: error?.response?.data?.errors, + toastId: error?.response?.data?.errors[key][0], }); }); - } else { - if (!errorDisplayed) { - errorDisplayed = true; + } + } else { toast.error("An error occurred, please try again later", { toastId: "An error occurred, please try again later", }); @@ -126,13 +159,14 @@ axiosClient.interceptors.response.use( } // if unauthorized, redirect to login page - setTimeout(async () => { - if (error?.response?.status === 401) { + if (error?.response?.status === 401) { + setTimeout(async () => { Cookies.remove("user"); + // await beamsClient.stop(); window.location.href = "/login"; - } - }, 2000); + }, 2000); + } return await Promise.reject(error); } diff --git a/src/helpers/Routes.tsx b/src/helpers/Routes.tsx index 8c08768..a1c3a3b 100644 --- a/src/helpers/Routes.tsx +++ b/src/helpers/Routes.tsx @@ -1,5 +1,5 @@ import Cookies from "js-cookie"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useEffect } from "react"; interface PrivateRouteProps { diff --git a/src/helpers/Utils.tsx b/src/helpers/Utils.tsx new file mode 100644 index 0000000..e7e59b6 --- /dev/null +++ b/src/helpers/Utils.tsx @@ -0,0 +1,52 @@ +import Cookies from "js-cookie"; +import CryptoJS from "crypto-js"; +import moment from "moment"; + +import { toast } from "react-toastify"; + +// Key of encryption +const keyEncryption: string = process.env.NEXT_ENCRYPTION_KEY ?? ""; + +/** + * Function to encrypt data + * + * @param data string + */ +export const encryptData = (data: string): string => { + return CryptoJS.AES.encrypt(data, keyEncryption as string).toString(); +}; + +/** + * Function to encrypt data + * + * @param data string + */ +export const decryptData = (data: string): any => { + const decryptedBytes = CryptoJS.AES.decrypt(data, keyEncryption as string); + + return decryptedBytes?.toString(CryptoJS.enc.Utf8); +}; + +/** + * Function to get credential as object + * + * @returns void + */ +export const getCredential = (): object | null => { + const user = Cookies.get("user"); + + if (user !== undefined) { + try { + const data = decryptData(user); + + return JSON.parse(data); + } catch (error) { + toast.error("Something went wrong, please login again", { + toastId: "error", + }); + + Cookies.remove("user"); + } + } + return null; +}; diff --git a/src/middleware/authMiddleware.tsx b/src/middleware/authMiddleware.tsx new file mode 100644 index 0000000..c10aa38 --- /dev/null +++ b/src/middleware/authMiddleware.tsx @@ -0,0 +1,24 @@ +import { getCredential } from "@/helpers/Utils"; +import { NextRequest, NextResponse } from "next/server"; + +export function privateMiddleware(request: NextRequest) { + // Add your own logic here to check if the user is authenticated + const userIsAuthenticated = getCredential(); + + if (userIsAuthenticated === null) { + return NextResponse.rewrite(new URL("/login", request.url)); + } + + return NextResponse.next(); +} + +export function publicMiddleware(request: NextRequest) { + // Add your own logic here to check if the user is authenticated + const userIsAuthenticated = getCredential(); + + if (userIsAuthenticated !== null) { + return NextResponse.rewrite(new URL("/dashboard", request.url)); + } + + return NextResponse.next(); +} diff --git a/src/actions/AuthenticationAction.tsx b/src/redux/actions/AuthenticationAction.tsx similarity index 92% rename from src/actions/AuthenticationAction.tsx rename to src/redux/actions/AuthenticationAction.tsx index c6655e2..47dbfb2 100644 --- a/src/actions/AuthenticationAction.tsx +++ b/src/redux/actions/AuthenticationAction.tsx @@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { AppDispatch } from "@/redux/store"; import Cookies from "js-cookie"; import { axiosClient } from "@/helpers/GatewayService"; +import { encryptData } from "@/helpers/Utils"; interface initialState { isLoading: boolean; @@ -60,15 +61,22 @@ export const { } = requestLoginSlice.actions; export const makeLoginRequest = - (formData: LoginForm) => async (dispatch: AppDispatch) => { + (formData: FormData) => async (dispatch: AppDispatch) => { try { dispatch(requestLoginStart()); const response = await axiosClient.post("auth/login", formData); - Cookies.set("rememberMe", formData.remember_me); - dispatch(requestLoginSuccess(response.data)); + // if remember me is true, set cookie expires on session/default 1 month + (formData.get("rememberMe") as any) !== "false" + ? Cookies.set("user", encryptData(JSON.stringify(response.data))) + : Cookies.set("user", encryptData(JSON.stringify(response.data)), { + expires: 1, + }); + + // redirect to dashboard + window.location.href = "/dashboard"; } catch (error: any) { dispatch(requestLoginFailure(error?.response?.data?.message)); } @@ -162,16 +170,16 @@ export const { export const logoutRequest = () => async (dispatch: AppDispatch) => { try { dispatch(logoutRequestStart()); - const response = await axiosClient.delete("administrator/auth/logout"); + const response = await axiosClient.delete("auth/logout"); dispatch(logoutRequestSuccess(response.data)); - Cookies.remove("admin"); + Cookies.remove("user"); - setTimeout(() => { - window.location.href = "/login"; - }, 1000); + window.location.href = "/login"; } catch (error: any) { + console.log(error); + dispatch(logoutRequestFailure(error?.response?.data?.message)); } }; diff --git a/src/redux/provider.tsx b/src/redux/provider.tsx new file mode 100644 index 0000000..cef6414 --- /dev/null +++ b/src/redux/provider.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { store } from "./store"; +import { Provider } from "react-redux"; + +export function Providers({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/src/redux/reducers.tsx b/src/redux/reducers.tsx index 8dfef92..96eac8e 100644 --- a/src/redux/reducers.tsx +++ b/src/redux/reducers.tsx @@ -6,7 +6,7 @@ import { logoutRequestSlice, resetPasswordByEmailSlice, acceptPasswordByEmailSlice, -} from "@/actions/AuthenticationAction"; +} from "@/redux/actions/AuthenticationAction"; export const RootReducer = combineReducers({ // Define reducers here diff --git a/src/styles/dashboard.module.scss b/src/styles/dashboard.module.scss new file mode 100644 index 0000000..64d5ef8 --- /dev/null +++ b/src/styles/dashboard.module.scss @@ -0,0 +1,137 @@ +.custom-nav { + background: #f8fafc; + height: 100vh; + padding-right: 0; +} + +.nav-link { + padding: 1rem 2rem 1rem 0.5rem !important; + color: rgb(184, 184, 184) !important; + border-right: 4px solid #f8fafc; + + &:hover { + padding: 1rem 2rem 1rem 0.5rem !important; + color: #000 !important; + font-weight: 500; + opacity: 0.8; + border-right: 4px solid #007ed3; + background: linear-gradient( + 270deg, + #cce5f6 0%, + rgba(204, 229, 246, 0) 100% + ); + + .active-icon { + color: #007ed3 !important; + } + } +} + +.collapse { + scroll-behavior: auto; + overflow: scroll; + height: 20rem; +} + +.active { + padding: 1rem 2rem 1rem 0.5rem !important; + color: #000 !important; + font-weight: 500; + opacity: 0.8; + border-right: 4px solid #007ed3; + background: linear-gradient(270deg, #cce5f6 0%, rgba(204, 229, 246, 0) 100%); + + .active-icon { + color: #007ed3 !important; + } +} + +.nav-active { + padding: 1rem 2rem 1rem 0.5rem !important; + color: #000 !important; + font-weight: 500; + opacity: 0.8; + border-right: 4px solid #007ed3; + background: linear-gradient(270deg, #cce5f6 0%, rgba(204, 229, 246, 0) 100%); + + .active-icon { + color: #007ed3 !important; + } +} + +.profile-options { + position: absolute; + bottom: 0; + width: 100%; + z-index: 1; + padding: 0rem 2rem 0rem 1rem !important; +} + +.dropdown-toggle { + background-color: transparent; + border: none; + box-shadow: none; + color: #6c757d; + padding: 0; + + //after + &::after { + content: none !important; + } + + &:hover { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + &:focus { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + &:active { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + &:visited { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + &:focus-visible { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + //focus within + &:focus-within { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } + + //first child + &:first-child { + background-color: transparent !important; + border: none; + box-shadow: none; + color: #6c757d; + } +} + +.dropdown-item.active, +.dropdown-item:active { + background-color: #cce5f6; +}