diff --git a/backend/.env b/backend/.env index 681d54af2bef12390fd73c2822d62e1adf76680e..aab12ae63038b86a3a2afa6a72e19b5bfaff1721 100644 --- a/backend/.env +++ b/backend/.env @@ -5,5 +5,8 @@ POSTGRES_DB=postgres POSTGRES_HOST=db.falrefwqcpbstyshqbkr.supabase.co POSTGRES_PORT=5432 +# JWT CONFIGURATION +JWT_SECRET=RPL2023-K2-G13 + # SUPABASE CONFIGURATION SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpzbW9id3Bsa2x0dWFjeXlmZW1mIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA2NTQ3MzgsImV4cCI6MjAxNjIzMDczOH0.xNfvNgxZl9-PYGn1-IH3np_qrAierKb3f9h-TOORmeQ \ No newline at end of file diff --git a/backend/node_modules/.package-lock.json b/backend/node_modules/.package-lock.json index 917cf7ee9b0f1989a9434707feec8f6c6c8af2bf..9277e0d12968a2b50e79c62518dabd49f2587f7b 100644 --- a/backend/node_modules/.package-lock.json +++ b/backend/node_modules/.package-lock.json @@ -323,6 +323,12 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -401,6 +407,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -931,6 +946,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -1010,6 +1030,11 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -1515,6 +1540,14 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1879,6 +1912,11 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -2571,6 +2609,51 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2613,12 +2696,47 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/backend/package-lock.json b/backend/package-lock.json index 7ddebeea8ca6d13711004347897eb2168dd24e82..0a54c2056209b678eb33c4a56f7932f6cadd01e4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,11 +11,14 @@ "dependencies": { "@supabase/supabase-js": "^2.38.5", "axios": "^1.6.2", + "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", "cors": "^2.8.5", "crypto-js": "^4.2.0", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.2", "pg": "^8.11.3", "react": "^18.2.0", "react-dotenv": "^0.1.3", @@ -26,9 +29,11 @@ "zod": "^3.22.4" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/cors": "^2.8.16", "@types/crypto-js": "^4.2.1", "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@types/node": "^20.9.0", "@types/react": "^18.2.38", "@typescript-eslint/eslint-plugin": "^6.11.0", @@ -360,6 +365,12 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -438,6 +449,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -968,6 +988,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -1047,6 +1072,11 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -1552,6 +1582,14 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1916,6 +1954,11 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -2622,6 +2665,51 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2664,12 +2752,47 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/backend/package.json b/backend/package.json index 9b8dc69067c28a53492032912f3afcefd7e9e236..67a97e20e9c0d236f33a8bbec8c7e8ddd17d9c49 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,11 +17,14 @@ "dependencies": { "@supabase/supabase-js": "^2.38.5", "axios": "^1.6.2", + "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", "cors": "^2.8.5", "crypto-js": "^4.2.0", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "jsonwebtoken": "^9.0.2", "pg": "^8.11.3", "react": "^18.2.0", "react-dotenv": "^0.1.3", @@ -32,9 +35,11 @@ "zod": "^3.22.4" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/cors": "^2.8.16", "@types/crypto-js": "^4.2.1", "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@types/node": "^20.9.0", "@types/react": "^18.2.38", "@typescript-eslint/eslint-plugin": "^6.11.0", diff --git a/backend/src/config/database.ts b/backend/src/config/database.ts index decc6dc1edcbe7deef8d3c9beed946ed6819846c..79439de61e20aaa5e742efcabcd81b03c05e8560 100644 --- a/backend/src/config/database.ts +++ b/backend/src/config/database.ts @@ -1,5 +1,4 @@ import { Sequelize } from "sequelize-typescript"; -import * as dotenv from "dotenv"; import { OrderProduct } from "../model/OrderProduct"; import { Orders } from "../model/Orders"; import { Payments } from "../model/Payments"; @@ -7,6 +6,7 @@ import { Products } from "../model/Products"; import { Tables } from "../model/Tables"; import { Tenants } from "../model/Tenants"; import { Users } from "../model/Users"; +import * as dotenv from "dotenv"; dotenv.config(); class Database { diff --git a/backend/src/constant/httpStatus.ts b/backend/src/constant/httpStatus.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ada321d973fa45d453612a070da68b1b6c5255e --- /dev/null +++ b/backend/src/constant/httpStatus.ts @@ -0,0 +1,2 @@ +export const BAD_REQUEST = 400; +export const UNAUTHORIZED = 401; diff --git a/backend/src/controller/AuthController.ts b/backend/src/controller/AuthController.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2ca83278c74c761f0dae19ce5555fdcf3ac7a4b --- /dev/null +++ b/backend/src/controller/AuthController.ts @@ -0,0 +1,120 @@ +import { Request, Response } from "express"; +import { Users } from "../model/Users"; +import UsersRepo from "../repository/UsersRepo"; + +import { BAD_REQUEST } from "../constant/httpStatus"; +// import auth from "../middleware/auth.mid.js"; +// import handler from "express-async-handler"; +import jwt from "jsonwebtoken"; +import bcrypt from "bcryptjs"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const PASSWORD_HASH_SALT_ROUNDS = 10; + +class AuthController { + generateTokenResponse(user: Users) { + const token = jwt.sign( + { + id: user.id, + email: user.email, + role: user.role, + }, + process.env.JWT_SECRET as unknown as string, + { + expiresIn: "30d", + }, + ); + + return { + id: user.id, + username: user.username, + fullname: user.fullname, + email: user.email, + role: user.role, + token, + }; + } + + async login(req: Request, res: Response) { + try { + const { email, password, role } = req.body; + console.log(`role: ${role} email: ${email} password: ${password}`); + + if (role === "tenant-cashier") { + console.log("masuk tenant-cashier"); + const user = await new UsersRepo().getUserByEmail(email); + console.log(`User ${user.email} nih! ${user.password}`); + + if ( + user && + password == + user.password /* && (await bcrypt.compare(password, user.password)) */ + ) { + console.log(`User ${user.email} logged in!`); + res.send(this.generateTokenResponse(user)); + return; + } + + res.status(BAD_REQUEST).send("Username or password is invalid"); + } else if (role === "customer") { + // email is num_seat, password is id_table + console.log("dah bener woi"); + const user = await new UsersRepo().getUserById(email); + + if (user) { + console.log(`User ${user.email} logged in!`); + res.send(this.generateTokenResponse(user)); + return; + } + + res.status(BAD_REQUEST).send("Username or password is invalid"); + } + } catch (err) { + console.error(err); + res.status(500).json({ + status: "Internal Server Error!", + message: "Internal Server Error!", + }); + } + } + + async register(req: Request, res: Response) { + try { + const { name, email, password, address } = req.body; + + const user = await Users.findOne({ where: { email: email } }); + + if (user) { + res.status(BAD_REQUEST).send( + "User already exists, please login!", + ); + return; + } + + const hashedPassword = await bcrypt.hash( + password, + PASSWORD_HASH_SALT_ROUNDS, + ); + + const newUser = { + name, + email: email.toLowerCase(), + password: hashedPassword, + address, + }; + + const result = await Users.create(newUser); + res.send(this.generateTokenResponse(result)); + } catch (err) { + console.error(err); + res.status(500).json({ + status: "Internal Server Error!", + message: "Internal Server Error!", + }); + } + } +} + +export default new AuthController(); diff --git a/backend/src/controller/UsersController.ts b/backend/src/controller/UsersController.ts index 08889f3271c49c3a3b6d966b16646cba125baee5..6fb003272619e90864281210a2333aefb7b788bd 100644 --- a/backend/src/controller/UsersController.ts +++ b/backend/src/controller/UsersController.ts @@ -66,6 +66,27 @@ class UsersController { } } + async getUserByEmail(req: Request, res: Response) { + try { + const email = req.params["email"]; + console.log(email); + console.log("masuk get user by email"); + const user = await new UsersRepo().getUserByEmail(email); + + res.status(200).json({ + status: "Ok!", + message: "Successfully fetched user by email!", + data: user, + }); + } catch (err) { + console.error(err); + res.status(500).json({ + status: "Internal Server Error!", + message: "Internal Server Error!", + }); + } + } + async getAllUsers(req: Request, res: Response) { try { const users = await new UsersRepo().getAllUsers(); diff --git a/backend/src/middleware/auth.mid.ts b/backend/src/middleware/auth.mid.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bc451f9df22e614245c1213f83428f5525f01dc --- /dev/null +++ b/backend/src/middleware/auth.mid.ts @@ -0,0 +1,28 @@ +import { Request, Response, NextFunction } from "express"; +import { verify } from "jsonwebtoken"; +import { UNAUTHORIZED } from "../constant/httpStatus"; + +interface decodedToken extends Request { + user?: string; + iat: number; + exp: number; +} + +export default (req: decodedToken, res: Response, next: NextFunction) => { + const token: string | undefined = req.headers.access_token as + | string + | undefined; + + if (!token) { + return res.status(UNAUTHORIZED).send(); + } + + try { + const decoded = verify(token, process.env.JWT_SECRET as string); + req.user = decoded; + } catch (error) { + return res.status(UNAUTHORIZED).send(); + } + + return next(); +}; diff --git a/backend/src/repository/UsersRepo.ts b/backend/src/repository/UsersRepo.ts index 946d0eb78d690443ec28c381fce3c7c0b6b7b8e7..72492b200c36940dfdf4e2ef3444714be2afb970 100644 --- a/backend/src/repository/UsersRepo.ts +++ b/backend/src/repository/UsersRepo.ts @@ -31,6 +31,19 @@ export default class UsersRepo implements IUsersRepo { } } + async getUserByEmail(email: string): Promise<Users> { + try { + console.log(email); + const user = await Users.findOne({ where: { email } }); + if (!user) { + throw new Error(`User with email ${email} not found`); + } + return user; + } catch (error: any) { + throw new Error(`Error while fetching user: ${error.message}`); + } + } + async createUser(user: Users): Promise<void> { try { await Users.create({ diff --git a/backend/src/router/AuthRouter.ts b/backend/src/router/AuthRouter.ts new file mode 100644 index 0000000000000000000000000000000000000000..e270b0c8af8fd6b2f41c2a725a29e97eedf8f296 --- /dev/null +++ b/backend/src/router/AuthRouter.ts @@ -0,0 +1,22 @@ +// router/UsersRouter.ts +import BaseRoutes from "./base/BaseRouter"; +import AuthController from "../controller/AuthController"; +import validate from "../helper/validate"; +import { createUserSchema /* loginUserSchema */ } from "../schema/UsersSchema"; + +class AuthRouter extends BaseRoutes { + public routes(): void { + this.router.post( + "/login", + // validate(loginUserSchema), + AuthController.login, + ); + this.router.post( + "/register", + validate(createUserSchema), + AuthController.register, + ); + } +} + +export default new AuthRouter().router; diff --git a/backend/src/router/AuthRouterstashed.ts b/backend/src/router/AuthRouterstashed.ts new file mode 100644 index 0000000000000000000000000000000000000000..aac18d347c58c078a0b3559b24ffbbd0c409a2a2 --- /dev/null +++ b/backend/src/router/AuthRouterstashed.ts @@ -0,0 +1,90 @@ +import { Router, Request, Response } from "express"; +import { BAD_REQUEST } from "../constant/httpStatus"; +import { Users } from "../model/Users"; +// import auth from "../middleware/auth.mid.js"; +import handler from "express-async-handler"; +import jwt from "jsonwebtoken"; +import bcrypt from "bcryptjs"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const PASSWORD_HASH_SALT_ROUNDS = 10; +const router = Router(); + +router.get("/test", (req, res) => { + res.send("test"); +}); + +router.post( + "/login", + handler(async (req: Request, res: Response) => { + const { email, password } = req.body; + const user = await Users.findOne({ where: { email: email } }); + + if ( + user && + password === + user.password /*(await bcrypt.compare(password, user.password)) */ + ) { + res.send(generateTokenResponse(user)); + return; + } + + res.status(BAD_REQUEST).send("Username or password is invalid"); + }), +); + +router.post( + "/register", + handler(async (req: Request, res: Response) => { + const { name, email, password, address } = req.body; + + const user = await Users.findOne({ where: { email: email } }); + + if (user) { + res.status(BAD_REQUEST).send("User already exists, please login!"); + return; + } + + const hashedPassword = await bcrypt.hash( + password, + PASSWORD_HASH_SALT_ROUNDS, + ); + + const newUser = { + name, + email: email.toLowerCase(), + password: hashedPassword, + address, + }; + + const result = await Users.create(newUser); + res.send(generateTokenResponse(result)); + }), +); + +const generateTokenResponse = (user: Users) => { + const token = jwt.sign( + { + id: user.id, + email: user.email, + role: user.role, + }, + process.env.JWT_SECRET as unknown as string, + { + expiresIn: "30d", + }, + ); + + return { + id: user.id, + username: user.username, + fullname: user.fullname, + email: user.email, + role: user.role, + token, + }; +}; + +export default router; diff --git a/backend/src/schema/UsersSchema.ts b/backend/src/schema/UsersSchema.ts index c2342a29ac0743e82288d6af0c2e6805b095a17c..3979b0f2886472c15733a150373f617ccc5b7c1e 100644 --- a/backend/src/schema/UsersSchema.ts +++ b/backend/src/schema/UsersSchema.ts @@ -20,26 +20,29 @@ export const createUserSchema = z.object({ }), }); +export const loginUserSchema = z.object({ + body: z.object({ + email: z.string().email({ message: "Invalid email format!" }), + password: z + .string() + .min(6, { message: "Password must be greater than 6 characters!" }), + }), +}); + export const updateUserSchema = z.object({ params: z.object({ id: z.string() }), body: z .object({ - username: z - .string() - .min(1, { - message: "Username must be greater than 1 character!", - }), - fullname: z - .string() - .min(1, { - message: "Fullname must be greater than 1 character!", - }), + username: z.string().min(1, { + message: "Username must be greater than 1 character!", + }), + fullname: z.string().min(1, { + message: "Fullname must be greater than 1 character!", + }), email: z.string().email({ message: "Invalid email format!" }), - password: z - .string() - .min(6, { - message: "Password must be greater than 6 characters!", - }), + password: z.string().min(6, { + message: "Password must be greater than 6 characters!", + }), role: z .string() .min(1, { message: "Role must be greater than 1 character!" }), diff --git a/backend/src/server.ts b/backend/src/server.ts index bccbcbf4c0aa7b1c08d04b603f894cabb6251eca..27a892ae7dd59a7ff9189d49f141e42bfe197e48 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -7,6 +7,7 @@ import UsersRouter from "./router/UsersRouter"; import ProductsRouter from "./router/ProductsRouter"; import OrderProductRouter from "./router/OrderProductRouter"; import TenantsRouter from "./router/TenantsRouter"; +import AuthRouter from "./router/AuthRouterstashed"; import cors from "cors"; class App { @@ -42,6 +43,7 @@ class App { this.app.use("/orderproduct", OrderProductRouter); this.app.use("/payments", PaymentsRouter); this.app.use("/users", UsersRouter); + this.app.use("/auth", AuthRouter); } } diff --git a/backend/tsconfig.json b/backend/tsconfig.json index cd0713d12e5957e8bafe99beecbf774601bb2b00..a4e988f961e34e3328f0bcc56d8ccb720d326939 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -106,5 +106,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": ["src"] + "include": ["src", "../frontend/src/services/userService.ts"] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc77fb00ffadd7cc59082f12ab60d54df7f9f730..6d37a5f7c902d68c3409ae1382390f116b20a116 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", "@supabase/supabase-js": "^2.38.5", "axios": "^1.6.2", "crypto-js": "^4.2.0", @@ -1254,6 +1257,51 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", + "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz", + "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz", + "integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -8975,6 +9023,26 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -9094,6 +9162,16 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index ddf4ddd8b62f77979c1387a8c8f740511a351fd5..35fedbd9a954e57fc01a353efb6bf54630c6f9ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,9 @@ "test": "jest" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", "@supabase/supabase-js": "^2.38.5", "axios": "^1.6.2", "crypto-js": "^4.2.0", diff --git a/frontend/public/images/RegisterTableImage.png b/frontend/public/images/RegisterTableImage.png new file mode 100644 index 0000000000000000000000000000000000000000..0812c43d90a8542646c5d567a263eecba2777e96 Binary files /dev/null and b/frontend/public/images/RegisterTableImage.png differ diff --git a/frontend/public/images/SignUpImg.png b/frontend/public/images/SignUpImg.png new file mode 100644 index 0000000000000000000000000000000000000000..0b34319a71e425491787224358c4e748b245eac1 Binary files /dev/null and b/frontend/public/images/SignUpImg.png differ diff --git a/frontend/public/images/WelcomingPage(Role).png b/frontend/public/images/WelcomingPage(Role).png new file mode 100644 index 0000000000000000000000000000000000000000..e0547d27ca8c06a8bdd6cee0465c38fe85899907 Binary files /dev/null and b/frontend/public/images/WelcomingPage(Role).png differ diff --git a/frontend/src/AppRoutes.tsx b/frontend/src/AppRoutes.tsx index dd954899b042c1fb0b1927a155d526a31b0ce5ac..bb20f3582147ec084811e09575485f8bfe3084e1 100644 --- a/frontend/src/AppRoutes.tsx +++ b/frontend/src/AppRoutes.tsx @@ -8,8 +8,8 @@ import OrderList from "./pages/OrderList"; import OrderSummary from "./pages/OrderSummary"; import PageManageOrder from "./pages/PageManageOrder"; import PageManageMenu from "./pages/PageManageMenu"; -import SignUpTenant from "./pages/SignUpTenant"; -import SignUpCashier from "./pages/SignUpCashier"; +import SignUpTenant from "./pages/RegisterTenant"; +import SignUpCashier from "./pages/RegisterPage"; import RegisterTable from "./pages/RegisterTable"; import ChooseRolePage from "./pages/RolePage"; import OrderDetails from "./pages/ManageOrderTenant"; @@ -17,30 +17,28 @@ import Summary from "./pages/ManagePayment"; import Payment from "./pages/Payment"; import ViewPaymentHistory from "./pages/ViewPaymentHistory"; import ManagePayment from "./pages/ManagePayment"; +import RegisterPage from "./pages/RegisterPage"; export default function AppRoutes() { return ( <Routes> <Route path="/" element={<Homepage />} /> <Route path="/role" element={<ChooseRolePage />} /> - {/* <Route path="/login" element={<LoginPage />} /> */} + <Route path="/login" element={<LoginPage />} /> <Route path="/login/customer" element={<RegisterTable />} /> - <Route path="/signup/tenant" element={<SignUpTenant />} /> - <Route path="/signup/cashier" element={<SignUpCashier />} /> + <Route path="/register/" element={<RegisterPage />} /> + <Route path="/register/tenant" element={<SignUpTenant />} /> <Route path="/tenant/:tenantid" element={<TenantInfo />} /> <Route path="/cart" element={<ShoppingCart />} /> <Route path="/order/list/:tableid" element={<OrderList />} /> + <Route path="/order/summary/:orderid" element={<OrderSummary />} /> + <Route path="/tenant/orders" element={<PageManageOrder />} /> + <Route path="/tenant/menus" element={<PageManageMenu />} /> + <Route path="/tenant/orders/:orderid" element={<OrderDetails />} /> <Route - path="/order/summary/:orderid" - element={<OrderSummary />} + path="/cashier/payments/:orderid" + element={<ManagePayment />} /> - <Route path="/tenantpage/orders" element={<PageManageOrder />} /> - <Route path="/tenantpage/menus" element={<PageManageMenu />} /> - <Route - path="/tenantpage/orders/:orderid" - element={<OrderDetails />} - /> - <Route path="/cashier/payments/:orderid" element={<ManagePayment />} /> <Route path="/payment/:paymentid" element={<Payment />} /> <Route path="/cashier/payments" element={<ViewPaymentHistory />} /> </Routes> diff --git a/frontend/src/components/Input.tsx b/frontend/src/components/Input.tsx index 5ffc1a64f46b5df0337909e05fcd2af1e324cbe3..8f9730eebabb74e53c38adcaadd27b577d2663eb 100644 --- a/frontend/src/components/Input.tsx +++ b/frontend/src/components/Input.tsx @@ -1,30 +1,28 @@ import React from "react"; import { InputContainer } from "./InputContainer"; +import { iconName } from "@fortawesome/free-solid-svg-icons/fa8"; interface InputProps { label: string; type: string; - defaultValue: any; - // onChange: () => void; - // onBlur: () => void; + defaultValue?: string; + onChange?: (e: any) => void; + // onBlur?: () => void; + // name: string; name: string; error: any; } function Input( - { - label, - type, - defaultValue, - /* onChange, onBlur, */ name, - error, - }: InputProps, + { label, type, defaultValue, onChange, name, error }: InputProps, ref: any, ) { const getErrorMessages = () => { + console.log(error); if (!error) return; if (error.message) return error.message; // default + console.log(error); switch (error.type) { case "required": return "This field is required"; @@ -38,16 +36,15 @@ function Input( }; return ( - <InputContainer label={label} bgColor="white"> + <InputContainer label={label}> <input - className="w-full h-full transition border-none hover:border-2 duration-150 ease-out bg-white text-base outline-none" - type={type} + className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5" defaultValue={defaultValue} - // onChange={onChange} - // onBlur={onBlur} - name={name} - ref={ref} + type={type} placeholder={label} + ref={ref} + name={name} + onChange={onChange} /> {error && ( <div className="flex justify-center items-center absolute top-0 right-4 h-full w-48 text-red-500 text-center text-sm"> diff --git a/frontend/src/components/InputContainer.tsx b/frontend/src/components/InputContainer.tsx index 216119646d4988b3a0ad6e48004f0c42af5b3ca4..af8cc4ff9c3ce4f25c2573a94422f39c12351e96 100644 --- a/frontend/src/components/InputContainer.tsx +++ b/frontend/src/components/InputContainer.tsx @@ -2,21 +2,16 @@ import React from "react"; interface InputContainerProps { label: string; - bgColor: string; children: React.ReactNode; } export const InputContainer: React.FC<InputContainerProps> = ({ label, - bgColor, children, }) => { return ( - <div - className="relative mb-1/2 rounded-2xl pt-1/3 border border-gray-300" - style={{ backgroundColor: bgColor }} - > - <label className="inline-block ml-2 text-gray-600 text-base"> + <div> + <label className="block mb-2 text-md font-medium text-mealshub-blue"> {label} </label> <div className="px-2 h-10 flex items-center">{children}</div> diff --git a/frontend/src/components/Logo.tsx b/frontend/src/components/Logo.tsx index e46fbe73a97efad67cafad5290556e18ae20adfe..295cca8fc5d6ba3eac491824fdc9e876e4d553aa 100644 --- a/frontend/src/components/Logo.tsx +++ b/frontend/src/components/Logo.tsx @@ -3,7 +3,7 @@ import { Link } from "react-router-dom"; interface LogoProps { height?: string; width?: string; - default: string; + default?: string; } export default function Logo(props: LogoProps) { @@ -19,6 +19,5 @@ export default function Logo(props: LogoProps) { ${width ? `w-${width}` : ""}`} /> </a> - ); } diff --git a/frontend/src/components/ProfileDropDown.tsx b/frontend/src/components/ProfileDropDown.tsx index ce33eefecb82794acc8befbd5e7f1b620c3b37de..68d909f9aa5f8b0c141e22ac0c0348fae8410df1 100644 --- a/frontend/src/components/ProfileDropDown.tsx +++ b/frontend/src/components/ProfileDropDown.tsx @@ -1,13 +1,24 @@ +import { Link } from "react-router-dom"; + export default function ProfileDropDown(props: any) { return ( - <div id="userDropdown" className="z-10 bg-white divide-y divide-gray-100 rounded-lg shadow gl-new-dropdown-inner"> + <div + id="userDropdown" + className="z-10 bg-white divide-y divide-gray-100 rounded-lg shadow gl-new-dropdown-inner" + > <div className="px-4 py-3 text-sm text-gray-900"> <div>{props.name}</div> <div className="font-medium text-s truncate">{props.email}</div> </div> <div className="py-1"> - <a href="#" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Sign out</a> + <a + href="/role" + onClick={props.onClick} + className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" + > + Sign out + </a> </div> </div> - ) -} \ No newline at end of file + ); +} diff --git a/frontend/src/components/RegisterForm.tsx b/frontend/src/components/RegisterForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3b410caa8ad45d17e0e4d9e000b3a084bc4da13a --- /dev/null +++ b/frontend/src/components/RegisterForm.tsx @@ -0,0 +1,88 @@ +export default function RegisterForm() { + return ( + <div className="relative bg-white rounded-xl px-16"> + <div className="flex flex-col items-center justify-between mt-4 p-4 md:p-5"> + <h1 className="text-5xl font-bold text-mealshub-orange"> + Register + </h1> + </div> + + <form action="#" className="p-4 md:p-5"> + <div className="gap-4 mb-4"> + <div className="my-2"> + <label + htmlFor="fullname" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Full Name + </label> + <input + type="text" + name="fullname" + id="fullname" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Name" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="email" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Email + </label> + <input + type="text" + name="email" + id="email" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Email" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="username" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Username + </label> + <input + type="text" + name="username" + id="username" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Username" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="password" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Password + </label> + <input + type="text" + name="password" + id="password" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Password" + required + /> + </div> + </div> + <div className="flex flex-col items-center justify-center"> + <button + type="submit" + className="text-white inline-flex items-center bg-mealshub-orange hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-blue-100 font-medium rounded-2xl text-xl px-5 py-2.5 justify-center w-full h-16 mb-8" + > + Sign Up + </button> + </div> + </form> + </div> + ); +} diff --git a/frontend/src/components/RegisterTableCard.tsx b/frontend/src/components/RegisterTableCard.tsx index 97043fb58565fd435772c72affc61609d3466b24..31348f05cfbe16a39471698d532465b3bb12ee86 100644 --- a/frontend/src/components/RegisterTableCard.tsx +++ b/frontend/src/components/RegisterTableCard.tsx @@ -1,44 +1,131 @@ +import { useState, useEffect } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { useAuth } from "../hooks/useAuth"; + +interface LoginFormInput { + num_seat: string; + id_table: string; +} + export default function RegisterTableCard() { + const { + handleSubmit, + register, + formState: { errors }, + } = useForm<LoginFormInput>(); + + const navigate = useNavigate(); + const { user, login } = useAuth(); + const [params] = useSearchParams(); + const returnUrl = params.get("returnUrl"); + + useEffect(() => { + if (!user) return; + returnUrl ? navigate(returnUrl) : navigate("/"); + }, [user]); + + const submit: SubmitHandler<LoginFormInput> = async () => { + const email = `table${idTable}@gmail.com`; + await login(email, "table", "customer"); + }; + + const [numSeat, setNumSeat] = useState<string>(""); + const [idTable, setIdTable] = useState<string>(""); + + function handleSelectNumSeat(event: React.ChangeEvent<HTMLSelectElement>) { + console.log(event.target.value); + setNumSeat(event.target.value); + } + + function handleSelectIdTable(event: React.ChangeEvent<HTMLSelectElement>) { + console.log(event.target.value); + setIdTable(event.target.value); + } + return ( <div className="h-3/4 w-3/4 bg-white rounded-3xl shadow-xl py-10 px-16"> - <h1 className="text-mealshub-orange text-3xl font-bold text-center mb-16">Register Table</h1> - <h2 className="text-mealshub-orange text-xl font-bold mb-4">Number of People</h2> - <div className="flex flex-row mb-8"> - <div className="flex flex-row h-16 w-1/6 bg-mealshub-orange rounded-2xl justify-center items-center"> - <svg xmlns="http://www.w3.org/2000/svg" width="45" height="25" viewBox="0 0 56 36" fill="none"> - <path d="M38 15.5C42.15 15.5 45.475 12.15 45.475 8C45.475 3.85 42.15 0.5 38 0.5C33.85 0.5 30.5 3.85 30.5 8C30.5 12.15 33.85 15.5 38 15.5ZM18 15.5C22.15 15.5 25.475 12.15 25.475 8C25.475 3.85 22.15 0.5 18 0.5C13.85 0.5 10.5 3.85 10.5 8C10.5 12.15 13.85 15.5 18 15.5ZM18 20.5C12.175 20.5 0.5 23.425 0.5 29.25V35.5H35.5V29.25C35.5 23.425 23.825 20.5 18 20.5ZM38 20.5C37.275 20.5 36.45 20.55 35.575 20.625C38.475 22.725 40.5 25.55 40.5 29.25V35.5H55.5V29.25C55.5 23.425 43.825 20.5 38 20.5Z" fill="white"/> - </svg> + <h1 className="text-mealshub-orange text-3xl font-bold text-center mb-16"> + Register Table + </h1> + <form onSubmit={handleSubmit(submit)}> + <h2 className="text-mealshub-orange text-xl font-bold mb-4"> + Number of People + </h2> + <div className="flex flex-row mb-8"> + <div className="flex flex-row h-16 w-1/6 bg-mealshub-orange rounded-2xl justify-center items-center"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="45" + height="25" + viewBox="0 0 56 36" + fill="none" + > + <path + d="M38 15.5C42.15 15.5 45.475 12.15 45.475 8C45.475 3.85 42.15 0.5 38 0.5C33.85 0.5 30.5 3.85 30.5 8C30.5 12.15 33.85 15.5 38 15.5ZM18 15.5C22.15 15.5 25.475 12.15 25.475 8C25.475 3.85 22.15 0.5 18 0.5C13.85 0.5 10.5 3.85 10.5 8C10.5 12.15 13.85 15.5 18 15.5ZM18 20.5C12.175 20.5 0.5 23.425 0.5 29.25V35.5H35.5V29.25C35.5 23.425 23.825 20.5 18 20.5ZM38 20.5C37.275 20.5 36.45 20.55 35.575 20.625C38.475 22.725 40.5 25.55 40.5 29.25V35.5H55.5V29.25C55.5 23.425 43.825 20.5 38 20.5Z" + fill="white" + /> + </svg> + </div> + + <select + id="category" + className="ml-4 border-2 border-mealshub-orange focus:ring-mealshub-orange focus:border-mealshub-orange text-gray-500 text-normal rounded-xl block w-full p-4" + onChange={handleSelectNumSeat} + defaultValue={numSeat} + > + <option selected={true}>Number of People</option> + <option value="1">4</option> + <option value="2">2</option> + <option value="3">5</option> + <option value="4">8</option> + <option value="5">5</option> + </select> </div> - <select id="category" className="border-2 border-mealshub-orange focus:ring-mealshub-orange focus:border-mealshub-orange text-gray-500 text-normal rounded-xl block w-full p-4"> - <option selected={true}>Number of People</option> - <option value="1">1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - <option value="6">6</option> - </select> - </div> - <h2 className="text-mealshub-orange text-xl font-bold mb-4">Number of Table</h2> - <div className="flex flex-row mb-8"> - <div className="flex flex-row h-16 w-1/6 bg-mealshub-orange rounded-2xl justify-center items-center"> - <svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 50 50" fill="none"> - <path d="M32.5 5.55556V25H17.5V5.55556H32.5ZM32.5 0H17.5C16.1739 0 14.9021 0.585316 13.9645 1.62718C13.0268 2.66905 12.5 4.08213 12.5 5.55556V30.5556H37.5V5.55556C37.5 4.08213 36.9732 2.66905 36.0355 1.62718C35.0979 0.585316 33.8261 0 32.5 0ZM50 19.4444H42.5V27.7778H50V19.4444ZM7.5 19.4444H0V27.7778H7.5V19.4444ZM45 33.3333H5V50H10V38.8889H40V50H45V33.3333Z" fill="white"/> - </svg> + <h2 className="text-mealshub-orange text-xl font-bold mb-4"> + Number of Table + </h2> + <div className="flex flex-row mb-8"> + <div className="flex flex-row h-16 w-1/6 bg-mealshub-orange rounded-2xl justify-center items-center"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="35" + height="35" + viewBox="0 0 50 50" + fill="none" + > + <path + d="M32.5 5.55556V25H17.5V5.55556H32.5ZM32.5 0H17.5C16.1739 0 14.9021 0.585316 13.9645 1.62718C13.0268 2.66905 12.5 4.08213 12.5 5.55556V30.5556H37.5V5.55556C37.5 4.08213 36.9732 2.66905 36.0355 1.62718C35.0979 0.585316 33.8261 0 32.5 0ZM50 19.4444H42.5V27.7778H50V19.4444ZM7.5 19.4444H0V27.7778H7.5V19.4444ZM45 33.3333H5V50H10V38.8889H40V50H45V33.3333Z" + fill="white" + /> + </svg> + </div> + <select + id="category" + className="ml-4 border-2 border-mealshub-orange focus:ring-mealshub-orange focus:border-mealshub-orange text-gray-500 text-normal rounded-xl block w-full p-4" + onChange={handleSelectIdTable} + value={idTable} + > + <option + selected={true} + className="hover:bg-mealshub-orange" + > + Table Number + </option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + </select> </div> - <select id="category" className="border-2 border-mealshub-orange focus:ring-mealshub-orange focus:border-mealshub-orange text-gray-500 text-normal rounded-xl block w-full p-4"> - <option selected={true} className="hover:bg-mealshub-orange">Number of Table</option> - <option value="1" className="hover:bg-mealshub-orange">1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - <option value="6">6</option> - </select> - </div> - <button className="w-full text-white bg-mealshub-orange font-bold text-xl rounded-full px-5 py-4 text-center shadow-xl mt-8"> - Get Table - </button> + <button + type="submit" + className="w-full text-white bg-mealshub-orange font-bold text-xl rounded-full px-5 py-4 text-center shadow-xl mt-8 mt-3 hover:bg-mealshub-cream focus:ring-4 focus:outline-none focus:ring-primary-300 " + > + Get Table + </button> + </form> </div> - ) -} \ No newline at end of file + ); +} diff --git a/frontend/src/components/RegisterTenantForm.tsx b/frontend/src/components/RegisterTenantForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..83920e50000dafa678c8373bc9cb00f8c8bfe055 --- /dev/null +++ b/frontend/src/components/RegisterTenantForm.tsx @@ -0,0 +1,103 @@ +export default function RegisterTenant() { + return ( + <div className="relative bg-white rounded-xl px-16"> + <div className="flex flex-col items-center justify-between mt-4 p-4 md:p-5"> + <h1 className="text-5xl font-bold text-mealshub-orange"> + Register + </h1> + </div> + + <form action="#" className="p-4 md:p-5"> + <div className="gap-4 mb-4"> + <div className="my-2"> + <label + htmlFor="name" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Name + </label> + <input + type="text" + name="name" + id="name" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Name" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="image" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Image + </label> + <input + type="text" + name="image" + id="image" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Image URL" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="openhour" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Open Hour + </label> + <input + type="text" + name="openhour" + id="openhour" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Open Hour" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="closehour" + className="block mb-2 text-xl font-bold text-mealshub-orange " + > + Close Hour + </label> + <input + type="text" + name="closehour" + id="closehour" + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" + placeholder="Close Hour" + required + /> + </div> + <div className="my-2"> + <label + htmlFor="description" + className="block mb-2 text-xl font-bold text-mealshub-orange" + > + Description + </label> + <textarea + id="description" + rows={4} + className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5" + placeholder="Write your tenant description here" + required + ></textarea> + </div> + </div> + <div className="flex flex-col items-center justify-center"> + <button + type="submit" + className="text-white inline-flex items-center bg-mealshub-orange hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-blue-100 font-medium rounded-2xl text-xl px-5 py-2.5 justify-center w-full h-16 mb-8" + > + Sign Up + </button> + </div> + </form> + </div> + ); +} diff --git a/frontend/src/components/RoleCard.tsx b/frontend/src/components/RoleCard.tsx index 9afaf105c2a671de56b70d5bbcb02c36b5c3362b..7870b51389463a6dd41fa09733fc290beb65f483 100644 --- a/frontend/src/components/RoleCard.tsx +++ b/frontend/src/components/RoleCard.tsx @@ -1,110 +1,130 @@ +import { Link } from "react-router-dom"; + export default function RoleCard() { return ( <div className="w-3/4 bg-white rounded-3xl shadow-xl py-8 px-10"> <h1 className="text-mealshub-orange text-3xl font-bold mb-12"> Who Are You? </h1> - <button - type="button" - className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-7" - > - <div className="flex flex-col w-52"> - <a href="#" className="flex items-center rounded-lg group"> - <div className="absolute group text-mealshub-orange ms-2"> - <svg - xmlns="http://www.w3.org/2000/svg" - width="35" - height="35" - viewBox="0 0 45 45" - fill="none" - > - <mask - id="mask0_60_761" - maskUnits="userSpaceOnUse" - x="5" - y="1" + <Link to="/login/customer"> + <button + type="button" + className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-7" + > + <div className="flex flex-col w-52"> + <a + href="/login/customer" + className="flex items-center rounded-lg group" + > + <div className="absolute group text-mealshub-orange ms-2"> + <svg + xmlns="http://www.w3.org/2000/svg" width="35" - height="43" + height="35" + viewBox="0 0 45 45" + fill="none" + > + <mask + id="mask0_60_761" + maskUnits="userSpaceOnUse" + x="5" + y="1" + width="35" + height="43" + > + <path + d="M13.125 3.75V41.25M7.5 4.6875V14.0625C7.5 18.75 13.125 18.75 13.125 18.75C13.125 18.75 18.75 18.75 18.75 14.0625V4.6875M31.875 18.75V41.25" + stroke="white" + stroke-width="4" + stroke-linecap="round" + stroke-linejoin="round" + /> + <path + d="M37.5 11.25C37.5 15.3919 34.9819 18.75 31.875 18.75C28.7681 18.75 26.25 15.3919 26.25 11.25C26.25 7.10812 28.7681 3.75 31.875 3.75C34.9819 3.75 37.5 7.10812 37.5 11.25Z" + fill="white" + stroke="white" + stroke-width="4" + stroke-linecap="round" + stroke-linejoin="round" + /> + </mask> + <g mask="url(#mask0_60_761)"> + <path + d="M0 0H45V45H0V0Z" + fill="white" + /> + </g> + </svg> + </div> + <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> + Customer + </span> + </a> + </div> + </button> + </Link> + <Link to="/login"> + <button + type="button" + className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-7" + > + <div className="flex flex-col w-52"> + <a + href="/login" + className="flex items-center rounded-lg group" + > + <div className="absolute group text-mealshub-orange ms-2"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="35" + height="35" + viewBox="0 0 45 45" + fill="none" > <path - d="M13.125 3.75V41.25M7.5 4.6875V14.0625C7.5 18.75 13.125 18.75 13.125 18.75C13.125 18.75 18.75 18.75 18.75 14.0625V4.6875M31.875 18.75V41.25" - stroke="white" - stroke-width="4" - stroke-linecap="round" - stroke-linejoin="round" + d="M39.375 28.125C39.375 19.4531 32.7994 12.2981 24.375 11.3606V7.5H20.625V11.3606C12.2006 12.2981 5.625 19.4531 5.625 28.125V31.875H39.375V28.125ZM3.75 33.75H41.25V37.5H3.75V33.75Z" + fill="white" /> + </svg> + </div> + <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> + Tenant + </span> + </a> + </div> + </button> + </Link> + <Link to="/login"> + <button + type="button" + className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-5" + > + <div className="flex flex-col w-52"> + <a + href="/login" + className="flex items-center rounded-lg group" + > + <div className="absolute group text-mealshub-orange ms-2"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="35" + height="35" + viewBox="0 0 45 45" + fill="none" + > <path - d="M37.5 11.25C37.5 15.3919 34.9819 18.75 31.875 18.75C28.7681 18.75 26.25 15.3919 26.25 11.25C26.25 7.10812 28.7681 3.75 31.875 3.75C34.9819 3.75 37.5 7.10812 37.5 11.25Z" + d="M22.5 15.4688C21.1094 15.4688 19.7499 15.8811 18.5936 16.6537C17.4374 17.4263 16.5361 18.5245 16.004 19.8093C15.4718 21.0941 15.3326 22.5078 15.6039 23.8717C15.8752 25.2357 16.5448 26.4885 17.5282 27.4718C18.5115 28.4552 19.7643 29.1248 21.1283 29.3961C22.4922 29.6674 23.906 29.5282 25.1907 28.996C26.4755 28.4639 27.5737 27.5626 28.3463 26.4064C29.1189 25.2501 29.5312 23.8906 29.5312 22.5C29.5312 20.6352 28.7905 18.8468 27.4718 17.5282C26.1532 16.2095 24.3648 15.4688 22.5 15.4688ZM22.5 26.7188C21.6656 26.7188 20.85 26.4713 20.1562 26.0078C19.4624 25.5442 18.9217 24.8853 18.6024 24.1144C18.2831 23.3436 18.1995 22.4953 18.3623 21.677C18.5251 20.8586 18.9269 20.1069 19.5169 19.5169C20.1069 18.9269 20.8586 18.5251 21.677 18.3623C22.4953 18.1995 23.3436 18.2831 24.1144 18.6024C24.8853 18.9217 25.5442 19.4624 26.0078 20.1562C26.4713 20.85 26.7188 21.6656 26.7188 22.5C26.7188 23.6189 26.2743 24.6919 25.4831 25.4831C24.6919 26.2743 23.6189 26.7188 22.5 26.7188ZM42.1875 9.84375H2.8125C2.43954 9.84375 2.08185 9.99191 1.81813 10.2556C1.55441 10.5194 1.40625 10.877 1.40625 11.25V33.75C1.40625 34.123 1.55441 34.4806 1.81813 34.7444C2.08185 35.0081 2.43954 35.1562 2.8125 35.1562H42.1875C42.5605 35.1562 42.9181 35.0081 43.1819 34.7444C43.4456 34.4806 43.5938 34.123 43.5938 33.75V11.25C43.5938 10.877 43.4456 10.5194 43.1819 10.2556C42.9181 9.99191 42.5605 9.84375 42.1875 9.84375ZM34.04 32.3438H10.96C10.4878 30.747 9.62368 29.2937 8.44626 28.1162C7.26884 26.9388 5.81554 26.0747 4.21875 25.6025V19.3975C5.81554 18.9253 7.26884 18.0612 8.44626 16.8838C9.62368 15.7063 10.4878 14.253 10.96 12.6562H34.04C34.5122 14.253 35.3763 15.7063 36.5537 16.8838C37.7312 18.0612 39.1845 18.9253 40.7812 19.3975V25.6025C39.1845 26.0747 37.7312 26.9388 36.5537 28.1162C35.3763 29.2937 34.5122 30.747 34.04 32.3438ZM40.7812 16.4127C39.0945 15.6874 37.7501 14.343 37.0248 12.6562H40.7812V16.4127ZM7.97519 12.6562C7.24991 14.343 5.90548 15.6874 4.21875 16.4127V12.6562H7.97519ZM4.21875 28.5873C5.90548 29.3126 7.24991 30.657 7.97519 32.3438H4.21875V28.5873ZM37.0248 32.3438C37.7501 30.657 39.0945 29.3126 40.7812 28.5873V32.3438H37.0248Z" fill="white" - stroke="white" - stroke-width="4" - stroke-linecap="round" - stroke-linejoin="round" /> - </mask> - <g mask="url(#mask0_60_761)"> - <path d="M0 0H45V45H0V0Z" fill="white" /> - </g> - </svg> - </div> - <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> - Customer - </span> - </a> - </div> - </button> - <button - type="button" - className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-7" - > - <div className="flex flex-col w-52"> - <a href="#" className="flex items-center rounded-lg group"> - <div className="absolute group text-mealshub-orange ms-2"> - <svg - xmlns="http://www.w3.org/2000/svg" - width="35" - height="35" - viewBox="0 0 45 45" - fill="none" - > - <path - d="M39.375 28.125C39.375 19.4531 32.7994 12.2981 24.375 11.3606V7.5H20.625V11.3606C12.2006 12.2981 5.625 19.4531 5.625 28.125V31.875H39.375V28.125ZM3.75 33.75H41.25V37.5H3.75V33.75Z" - fill="white" - /> - </svg> - </div> - <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> - Tenant - </span> - </a> - </div> - </button> - <button - type="button" - className="text-white bg-mealshub-orange font-bold rounded-3xl p-4 w-full inline-flex hover:shadow-xl mb-5" - > - <div className="flex flex-col w-52"> - <a href="#" className="flex items-center rounded-lg group"> - <div className="absolute group text-mealshub-orange ms-2"> - <svg - xmlns="http://www.w3.org/2000/svg" - width="35" - height="35" - viewBox="0 0 45 45" - fill="none" - > - <path - d="M22.5 15.4688C21.1094 15.4688 19.7499 15.8811 18.5936 16.6537C17.4374 17.4263 16.5361 18.5245 16.004 19.8093C15.4718 21.0941 15.3326 22.5078 15.6039 23.8717C15.8752 25.2357 16.5448 26.4885 17.5282 27.4718C18.5115 28.4552 19.7643 29.1248 21.1283 29.3961C22.4922 29.6674 23.906 29.5282 25.1907 28.996C26.4755 28.4639 27.5737 27.5626 28.3463 26.4064C29.1189 25.2501 29.5312 23.8906 29.5312 22.5C29.5312 20.6352 28.7905 18.8468 27.4718 17.5282C26.1532 16.2095 24.3648 15.4688 22.5 15.4688ZM22.5 26.7188C21.6656 26.7188 20.85 26.4713 20.1562 26.0078C19.4624 25.5442 18.9217 24.8853 18.6024 24.1144C18.2831 23.3436 18.1995 22.4953 18.3623 21.677C18.5251 20.8586 18.9269 20.1069 19.5169 19.5169C20.1069 18.9269 20.8586 18.5251 21.677 18.3623C22.4953 18.1995 23.3436 18.2831 24.1144 18.6024C24.8853 18.9217 25.5442 19.4624 26.0078 20.1562C26.4713 20.85 26.7188 21.6656 26.7188 22.5C26.7188 23.6189 26.2743 24.6919 25.4831 25.4831C24.6919 26.2743 23.6189 26.7188 22.5 26.7188ZM42.1875 9.84375H2.8125C2.43954 9.84375 2.08185 9.99191 1.81813 10.2556C1.55441 10.5194 1.40625 10.877 1.40625 11.25V33.75C1.40625 34.123 1.55441 34.4806 1.81813 34.7444C2.08185 35.0081 2.43954 35.1562 2.8125 35.1562H42.1875C42.5605 35.1562 42.9181 35.0081 43.1819 34.7444C43.4456 34.4806 43.5938 34.123 43.5938 33.75V11.25C43.5938 10.877 43.4456 10.5194 43.1819 10.2556C42.9181 9.99191 42.5605 9.84375 42.1875 9.84375ZM34.04 32.3438H10.96C10.4878 30.747 9.62368 29.2937 8.44626 28.1162C7.26884 26.9388 5.81554 26.0747 4.21875 25.6025V19.3975C5.81554 18.9253 7.26884 18.0612 8.44626 16.8838C9.62368 15.7063 10.4878 14.253 10.96 12.6562H34.04C34.5122 14.253 35.3763 15.7063 36.5537 16.8838C37.7312 18.0612 39.1845 18.9253 40.7812 19.3975V25.6025C39.1845 26.0747 37.7312 26.9388 36.5537 28.1162C35.3763 29.2937 34.5122 30.747 34.04 32.3438ZM40.7812 16.4127C39.0945 15.6874 37.7501 14.343 37.0248 12.6562H40.7812V16.4127ZM7.97519 12.6562C7.24991 14.343 5.90548 15.6874 4.21875 16.4127V12.6562H7.97519ZM4.21875 28.5873C5.90548 29.3126 7.24991 30.657 7.97519 32.3438H4.21875V28.5873ZM37.0248 32.3438C37.7501 30.657 39.0945 29.3126 40.7812 28.5873V32.3438H37.0248Z" - fill="white" - /> - </svg> - </div> - <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> - Central Cashier - </span> - </a> - </div> - </button> + </svg> + </div> + <span className="flex ms-14 whitespace-nowrap text-2xl text-left"> + Central Cashier + </span> + </a> + </div> + </button> + </Link> </div> ); } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 8c91ba0f65f5c18ec606c7a11c6310e8e97dbb91..7efbeaec54fc69ea12075c913e8dadc2be96a61b 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,6 +1,19 @@ import Logo from "./Logo"; +import { useAuth } from "../hooks/useAuth"; +import { useEffect } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; export default function Sidebar(props: any) { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + const [params] = useSearchParams(); + const returnUrl = params.get("returnUrl"); + + useEffect(() => { + if (!user) navigate("/role"); + // returnUrl ? navigate(returnUrl) : navigate("/"); + }, [user]); + const menuItems = Array.from({ length: props.number }, (_, index) => ( <li key={index} className="flex flex-col px-10 py-3.5"> { @@ -123,21 +136,21 @@ export default function Sidebar(props: any) { </ul> </div> {props.customer === true ? ( - <div className="flex flex-col px-10 py-3.5 mt-auto mb-10"> - <button - type="button" - className="flex text-mealshub-red bg-white hover:bg-mealshub-red hover:text-white font-medium rounded-2xl p-4 inline-flex group" - > - <div className="flex flex-col w-52"> - <span className="flex whitespace-nowrap text-lg text-left"> - Log Out - </span> - </div> - </button> - </div> - ) : ( - null - )} + <a onClick={logout}> + <div className="flex flex-col px-10 py-3.5 mt-auto mb-10"> + <button + type="button" + className="flex text-mealshub-red bg-white hover:bg-mealshub-red hover:text-white font-medium rounded-2xl p-4 inline-flex group" + > + <div className="flex flex-col w-52"> + <span className="flex whitespace-nowrap text-lg text-left"> + Log Out + </span> + </div> + </button> + </div> + </a> + ) : null} </div> </div> ); diff --git a/frontend/src/components/SignUpCardCashier.tsx b/frontend/src/components/SignUpCardCashier.tsx deleted file mode 100644 index 168827f49c0f062bef492d84998e436bdc33a158..0000000000000000000000000000000000000000 --- a/frontend/src/components/SignUpCardCashier.tsx +++ /dev/null @@ -1,33 +0,0 @@ -export default function SignUpCardCashier() { - return ( - <div className="relative bg-white rounded-xl px-16"> - <div className="flex flex-col items-center justify-between mt-4 p-4 md:p-5" > - <h1 className="text-5xl font-bold text-mealshub-orange"> - Register - </h1> - </div> - - <form action="#" className="p-4 md:p-5"> - <div className="gap-4 mb-4"> - <div className="my-2"> - <label htmlFor="fullname" className="block mb-2 text-xl font-bold text-mealshub-orange ">Full Name</label> - <input type="text" name="fullname" id="fullname" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Name" required /> - </div> - <div className="my-2"> - <label htmlFor="email" className="block mb-2 text-xl font-bold text-mealshub-orange ">Email</label> - <input type="text" name="email" id="email" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Email" required /> - </div> - <div className="my-2"> - <label htmlFor="password" className="block mb-2 text-xl font-bold text-mealshub-orange ">Password</label> - <input type="text" name="password" id="password" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Password" required /> - </div> - </div > - <div className="flex flex-col items-center justify-center"> - <button type="submit" className="text-white inline-flex items-center bg-mealshub-orange hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-blue-100 font-medium rounded-2xl text-xl px-5 py-2.5 justify-center w-full h-16 mb-8"> - Sign Up - </button> - </div> - </form> - </div> - ) -} diff --git a/frontend/src/components/SignUpCardTenant.tsx b/frontend/src/components/SignUpCardTenant.tsx deleted file mode 100644 index c2fa7882878120be304dc535af78e68f0c2d3411..0000000000000000000000000000000000000000 --- a/frontend/src/components/SignUpCardTenant.tsx +++ /dev/null @@ -1,37 +0,0 @@ -export default function SignUpCardTenant() { - return ( - <div className="relative bg-white rounded-xl px-16"> - <div className="flex flex-col items-center justify-between mt-4 p-4 md:p-5" > - <h1 className="text-5xl font-bold text-mealshub-orange"> - Register - </h1> - </div> - - <form action="#" className="p-4 md:p-5"> - <div className="gap-4 mb-4"> - <div className="my-2"> - <label htmlFor="name" className="block mb-2 text-xl font-bold text-mealshub-orange ">Name</label> - <input type="text" name="name" id="name" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Name" required /> - </div> - <div className="my-2"> - <label htmlFor="openhour" className="block mb-2 text-xl font-bold text-mealshub-orange ">Open Hour</label> - <input type="text" name="openhour" id="openhour" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Open Hour" required /> - </div> - <div className="my-2"> - <label htmlFor="closehour" className="block mb-2 text-xl font-bold text-mealshub-orange ">Close Hour</label> - <input type="text" name="closehour" id="closehour" className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5 h-14" placeholder="Close Hour" required /> - </div> - <div className="my-2"> - <label htmlFor="description" className="block mb-2 text-xl font-bold text-mealshub-orange">Description</label> - <textarea id="description" rows={4} className="font-nunito bg-white border border-mealshub-orange text-gray-900 text-m rounded-2xl focus:ring-mealshub-orange focus:border-mealshub-orange block w-96 px-4 p-2.5" placeholder="Write your tenant description here" required></textarea> - </div> - </div > - <div className="flex flex-col items-center justify-center"> - <button type="submit" className="text-white inline-flex items-center bg-mealshub-orange hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-blue-100 font-medium rounded-2xl text-xl px-5 py-2.5 justify-center w-full h-16 mb-8"> - Sign Up - </button> - </div> - </form> - </div> - ) -} diff --git a/frontend/src/components/TableOrder.tsx b/frontend/src/components/TableOrder.tsx index 154c218be14b7b32d46ccddca6e38809a7111ec1..6ca8cf57eac61bad7968b6ad4245c5361d67a869 100644 --- a/frontend/src/components/TableOrder.tsx +++ b/frontend/src/components/TableOrder.tsx @@ -53,10 +53,11 @@ function TableOrder({ data }: { data: Props[] }) { return ( <button onClick={onClick} - className={`${sortKey === columnKey && sortOrder === "desc" - ? "sort-button sort-reverse" - : "sort-button" - }`} + className={`${ + sortKey === columnKey && sortOrder === "desc" + ? "sort-button sort-reverse" + : "sort-button" + }`} > <svg className="w-3 h-3 ms-1.5" @@ -185,7 +186,7 @@ function TableOrder({ data }: { data: Props[] }) { value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} onKeyPress={(e) => { - if (e.key === 'Enter') { + if (e.key === "Enter") { e.preventDefault(); } }} @@ -276,14 +277,19 @@ function TableOrder({ data }: { data: Props[] }) { {filteredRecords .slice(firstIndex, lastIndex) .map((record, index) => { - const date = moment(record.time).format("DD/MM/YYYY hh:mm:ss"); + const date = moment(record.time).format( + "DD/MM/YYYY hh:mm:ss", + ); return ( - <tr className="odd:bg-white even:bg-gray-50 border-b" key={record.orderId}> + <tr + className="odd:bg-white even:bg-gray-50 border-b" + key={record.orderId} + > <td className="px-6 py-4"> {index + 1 + (currentPage - 1) * - recordsPerPage} + recordsPerPage} </td> <td className="px-6 py-4 whitespace-nowrap"> {record.orderId} @@ -305,7 +311,7 @@ function TableOrder({ data }: { data: Props[] }) { </td> <td className="px-6 py-4"> <a - href={`/tenantpage/orders/${record.orderId}`} + href={`/tenant/orders/${record.orderId}`} className="text-mealshub-blue hover:underline" > Click for Details @@ -355,10 +361,11 @@ function TableOrder({ data }: { data: Props[] }) { <li> <a href="#" - className={`page-item ${currentPage === n - ? "z-10 flex items-center justify-center px-3 h-8 leading-tight text-mealshub-blue rounded-lg bg-mealshub-greenpalet hover:bg-blue-100 hover:text-blue-700 mx-1" - : "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 mx-1" - }`} + className={`page-item ${ + currentPage === n + ? "z-10 flex items-center justify-center px-3 h-8 leading-tight text-mealshub-blue rounded-lg bg-mealshub-greenpalet hover:bg-blue-100 hover:text-blue-700 mx-1" + : "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 mx-1" + }`} key={i} onClick={() => setCurrentPage(n)} > diff --git a/frontend/src/components/TenantCard.tsx b/frontend/src/components/TenantCard.tsx index 03cdeb1d880c6b82fb1c8323d02665fb6615b230..0d0d92d5d74189f2c4b3ed285af5c403568c1898 100644 --- a/frontend/src/components/TenantCard.tsx +++ b/frontend/src/components/TenantCard.tsx @@ -39,10 +39,10 @@ export default function TenantCard({ data }: { data: TenantCardProps[] }) { <span className="px-3 font-light">Rp.{lowestpriceidr} - Rp.{highestpriceidr}</span> </div> </div> - </div> - </a> - ) - }); + </a> + ); + }, + ); return tenantlist; -} \ No newline at end of file +} diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx index dae9052764e37ba638e80516bafc36655ad3578a..80f5f37171ef3e2794f3d52f7900e02663d6effc 100644 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/hooks/useAuth.tsx @@ -2,10 +2,28 @@ import React, { useState, createContext, useContext, ReactNode } from "react"; import * as userService from "../services/userService"; import { toast } from "react-toastify"; +interface AuthUser { + id: number; + username: string; + fullname: string; + email: string; + role: string; + token: string; +} + +interface AuthTable { + num_seat: number; + id_table: number; + fullname: string; + email: string; + id: number; +} + interface AuthContextProps { - user: string | null; - login: (email: string, password: string) => Promise<void>; + user: AuthUser | AuthTable | null; + login: (email: string, password: string, role: string) => Promise<void>; logout: () => void; + showUser: () => void; } export const AuthContext = createContext<AuthContextProps | null>(null); @@ -14,30 +32,84 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const [user, setUser] = useState(userService.getUser()); + console.log(user); - const login = async (email: string, password: string) => { - try { - const loggedInUser = await userService.login(email, password); - setUser(loggedInUser); - toast.success("Logged in successfully."); - } catch (error: any) { - toast.error( - error.response?.data?.message || - "An error occurred during login.", - ); + const login = async (email: string, password: string, role: string) => { + if (role === "tenant-cashier") { + try { + console.log(`email: ${email}, password: ${password}. MASUK`); + const loggedInUser = await userService.login( + email, + password, + "tenant-cashier", + ); + setUser(loggedInUser); + toast.success("Logged in successfully."); + } catch (error: any) { + toast.error( + error.response?.data?.message || + "An error occurred during login.", + ); + } + } else if (role === "customer") { + try { + console.log(`num_seat: ${email}, id_table: ${password}. MASUK`); + const loggedInUser = await userService.login( + email, + password, + "customer", + ); + setUser(loggedInUser); + toast.success("Logged in successfully."); + } catch (error: any) { + toast.error( + error.response?.data?.message || + "An error occurred during login.", + ); + } } }; + // const register = async (data) => { + // try { + // const user = await userService.register(data); + // setUser(user); + // toast.success("Register Successful"); + // } catch (error: any) { + // toast.error(error.response.data); + // } + // }; + const logout = () => { userService.logout(); setUser(null); toast.success("Logged out successfully."); }; + // const updateProfile = async (user) => { + // const updatedUser = await userService.updateProfile(user); + // toast.success("Profile Update Was Successful"); + // if (updatedUser) setUser(updatedUser); + // }; + + // const changePassword = async (passwords) => { + // await userService.changePassword(passwords); + // logout(); + // toast.success("Password Changed Successfully, Please Login Again!"); + // }; + + const showUser = () => { + console.log(user); + }; + const values: AuthContextProps = { user, login, logout, + showUser, + // register, + // updateProfile, + // changePassword, }; return ( diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 854d43f0c60c5b3f6c13d79a319586fe98862d4c..b9cab446c1114dda507b4c135799519ba6b81219 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,21 +1,33 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; +import { AuthProvider } from "./hooks/useAuth.tsx"; +import { ShoppingCartProvider } from "./contexts/ShoppingCartContext"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; import App from "./App.tsx"; import "./index.css"; -import AppRoutes from "./AppRoutes.tsx"; -import { ShoppingCartProvider } from "./contexts/ShoppingCartContext.tsx"; -// import { AuthProvider } from "./hooks/useAuth.tsx"; -// import { ShoppingCartProvider } from "./contexts/ShoppingCartContext"; -// import { ToastContainer } from "react-toastify"; -// import "react-toastify/dist/ReactToastify.css"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> - <ShoppingCartProvider> - <App /> - </ShoppingCartProvider> + <AuthProvider> + <ShoppingCartProvider> + <App /> + <ToastContainer + position="bottom-right" + autoClose={5000} + hideProgressBar={false} + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + theme="light" + /> + </ShoppingCartProvider> + </AuthProvider> </BrowserRouter> </React.StrictMode>, ); diff --git a/frontend/src/pages/Homepage.tsx b/frontend/src/pages/Homepage.tsx index e253490f8224268a21ee986010cdff8144472f97..57713d4f5a4f6e88257eb143793ca08c05f9960c 100644 --- a/frontend/src/pages/Homepage.tsx +++ b/frontend/src/pages/Homepage.tsx @@ -4,64 +4,70 @@ import Sidebar from "../components/Sidebar"; import TenantCard from "../components/TenantCard"; import Search from "../components/Search"; import WelcomingText from "../components/WelcomingText"; +import { useAuth } from "../hooks/useAuth"; interface Tenant { - id: number; - image: string; - name: string; - rating: number; - open_hour: string; - close_hour: string; + id: number; + image: string; + name: string; + rating: number; + open_hour: string; + close_hour: string; } interface Product { - id_tenant: number; - price: number; + id_tenant: number; + price: number; } export default function Homepage() { - const [dataTenant, setDataTenant] = useState<Tenant[]>([]); - const [dataProduct, setDataProduct] = useState<Product[]>([]); - const [searchTerm, setSearchTerm] = useState<string>(""); - const [filteredData, setFilteredData] = useState<Tenant[]>([]); + const { user } = useAuth(); + const [dataTenant, setDataTenant] = useState<Tenant[]>([]); + const [dataProduct, setDataProduct] = useState<Product[]>([]); + const [searchTerm, setSearchTerm] = useState<string>(""); + const [filteredData, setFilteredData] = useState<Tenant[]>([]); - const getDataTenant = async () => { - const res = await Axios.get("http://localhost:8000/tenants"); - setDataTenant(res.data.data); - }; + const getDataTenant = async () => { + const res = await Axios.get("http://localhost:8000/tenants"); + setDataTenant(res.data.data); + }; - const getDataProduct = async () => { - const res = await Axios.get("http://localhost:8000/products"); - setDataProduct(res.data.data); - }; + const getDataProduct = async () => { + const res = await Axios.get("http://localhost:8000/products"); + setDataProduct(res.data.data); + }; - useEffect(() => { - getDataTenant(); - getDataProduct(); - }, []); + useEffect(() => { + getDataTenant(); + getDataProduct(); + }, []); - useEffect(() => { - const filtered = dataTenant.filter((tenant) => - tenant.name.toLowerCase().includes(searchTerm.toLowerCase()) - ); - setFilteredData(filtered); - }, [searchTerm, dataTenant]); + useEffect(() => { + const filtered = dataTenant.filter((tenant) => + tenant.name.toLowerCase().includes(searchTerm.toLowerCase()), + ); + setFilteredData(filtered); + }, [searchTerm, dataTenant]); - const data = filteredData.map((tenant: Tenant) => { - const products: Product[] = dataProduct.filter( - (product: Product) => product.id_tenant === tenant.id - ); - return { - id: tenant.id, - image: tenant.image, - name: tenant.name, - rating: tenant.rating, - open_hour: tenant.open_hour, - close_hour: tenant.close_hour, - lowestprice: Math.min(...products.map((product: Product) => product.price)), - highestprice: Math.max(...products.map((product: Product) => product.price)), - }; - }); + const data = filteredData.map((tenant: Tenant) => { + const products: Product[] = dataProduct.filter( + (product: Product) => product.id_tenant === tenant.id, + ); + return { + id: tenant.id, + image: tenant.image, + name: tenant.name, + rating: tenant.rating, + open_hour: tenant.open_hour, + close_hour: tenant.close_hour, + lowestprice: Math.min( + ...products.map((product: Product) => product.price), + ), + highestprice: Math.max( + ...products.map((product: Product) => product.price), + ), + }; + }); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { setSearchTerm(e.target.value); @@ -71,33 +77,51 @@ export default function Homepage() { if (e.key === "Enter") { e.preventDefault(); } - } + }; const tableid = 1; - return ( - <div className="grid grid-cols-5 grid-rows-8 bg-mealshub-cream min-h-screen"> - {/* Sidebar */} - <div className="col-span-1 row-span-8"> - <Sidebar customer={true} number={3} current={1} menu1="Home" page1="/" path1="M27 14.6465V29.4398C27 30.1188 26.7629 30.77 26.341 31.2501C25.919 31.7303 25.3467 32 24.75 32H19.125C18.5282 32 17.9559 31.7303 17.534 31.2501C17.112 30.77 16.875 30.1188 16.875 29.4398V23.0392C16.875 22.6997 16.7565 22.3741 16.5455 22.1341C16.3345 21.894 16.0484 21.7591 15.75 21.7591H11.25C10.9516 21.7591 10.6655 21.894 10.4545 22.1341C10.2435 22.3741 10.125 22.6997 10.125 23.0392V29.4398C10.125 30.1188 9.88794 30.77 9.46598 31.2501C9.04402 31.7303 8.47173 32 7.87499 32H2.25C1.65326 32 1.08097 31.7303 0.659009 31.2501C0.237052 30.77 2.59985e-08 30.1188 2.59985e-08 29.4398V14.6465C-4.73102e-05 14.2922 0.0645457 13.9417 0.189691 13.6172C0.314836 13.2928 0.497809 13.0014 0.72703 12.7616L11.977 0.683737L11.9925 0.666135C12.4067 0.237512 12.9464 0 13.5063 0C14.0662 0 14.6059 0.237512 15.0201 0.666135C15.0249 0.672397 15.0301 0.678277 15.0356 0.683737L26.2856 12.7616C26.5125 13.0027 26.6931 13.2946 26.8161 13.619C26.939 13.9434 27.0016 14.2933 27 14.6465Z" menu2="Shopping Cart" page2="/cart" path2="M22.2968 25.6C20.7499 25.6 19.5097 27.024 19.5097 28.8C19.5097 29.6487 19.8033 30.4626 20.326 31.0627C20.8487 31.6629 21.5576 32 22.2968 32C23.036 32 23.7449 31.6629 24.2675 31.0627C24.7902 30.4626 25.0839 29.6487 25.0839 28.8C25.0839 27.9513 24.7902 27.1374 24.2675 26.5373C23.7449 25.9371 23.036 25.6 22.2968 25.6ZM0 0V3.2H2.7871L7.80387 15.344L5.90864 19.264C5.69961 19.712 5.57419 20.24 5.57419 20.8C5.57419 21.6487 5.86783 22.4626 6.39051 23.0627C6.9132 23.6629 7.62211 24 8.36129 24H25.0839V20.8H8.94658C8.85418 20.8 8.76557 20.7579 8.70023 20.6828C8.6349 20.6078 8.59819 20.5061 8.59819 20.4C8.59819 20.32 8.61213 20.256 8.64 20.208L9.89419 17.6H20.2761C21.3213 17.6 22.241 16.928 22.7148 15.952L27.7037 5.6C27.8013 5.344 27.871 5.072 27.871 4.8C27.871 4.37565 27.7241 3.96869 27.4628 3.66863C27.2015 3.36857 26.847 3.2 26.4774 3.2H5.86684L4.5569 0M8.36129 25.6C6.81445 25.6 5.57419 27.024 5.57419 28.8C5.57419 29.6487 5.86783 30.4626 6.39051 31.0627C6.9132 31.6629 7.62211 32 8.36129 32C9.10047 32 9.80938 31.6629 10.3321 31.0627C10.8547 30.4626 11.1484 29.6487 11.1484 28.8C11.1484 27.9513 10.8547 27.1374 10.3321 26.5373C9.80938 25.9371 9.10047 25.6 8.36129 25.6Z" menu3="Orders" page3={`/order/list/${tableid}`} path3="M4.10306 1.10306C3 2.20612 3 3.97929 3 7.52941V24.4706C3 28.0207 3 29.7939 4.10306 30.8969C5.20612 32 6.97929 32 10.5294 32H21.8235C25.3736 32 27.1468 32 28.2499 30.8969C29.3529 29.7939 29.3529 28.0207 29.3529 24.4706V7.52941C29.3529 3.97929 29.3529 2.20612 28.2499 1.10306C27.1468 -1.12197e-07 25.3736 0 21.8235 0H10.5294C6.97929 0 5.20612 -1.12197e-07 4.10306 1.10306ZM10.5294 7.52941C10.0302 7.52941 9.5514 7.72773 9.19839 8.08074C8.84538 8.43375 8.64706 8.91253 8.64706 9.41176C8.64706 9.911 8.84538 10.3898 9.19839 10.7428C9.5514 11.0958 10.0302 11.2941 10.5294 11.2941H21.8235C22.3228 11.2941 22.8015 11.0958 23.1546 10.7428C23.5076 10.3898 23.7059 9.911 23.7059 9.41176C23.7059 8.91253 23.5076 8.43375 23.1546 8.08074C22.8015 7.72773 22.3228 7.52941 21.8235 7.52941H10.5294ZM10.5294 15.0588C10.0302 15.0588 9.5514 15.2571 9.19839 15.6102C8.84538 15.9632 8.64706 16.4419 8.64706 16.9412C8.64706 17.4404 8.84538 17.9192 9.19839 18.2722C9.5514 18.6252 10.0302 18.8235 10.5294 18.8235H21.8235C22.3228 18.8235 22.8015 18.6252 23.1546 18.2722C23.5076 17.9192 23.7059 17.4404 23.7059 16.9412C23.7059 16.4419 23.5076 15.9632 23.1546 15.6102C22.8015 15.2571 22.3228 15.0588 21.8235 15.0588H10.5294ZM10.5294 22.5882C10.0302 22.5882 9.5514 22.7866 9.19839 23.1396C8.84538 23.4926 8.64706 23.9714 8.64706 24.4706C8.64706 24.9698 8.84538 25.4486 9.19839 25.8016C9.5514 26.1546 10.0302 26.3529 10.5294 26.3529H18.0588C18.5581 26.3529 19.0368 26.1546 19.3898 25.8016C19.7429 25.4486 19.9412 24.9698 19.9412 24.4706C19.9412 23.9714 19.7429 23.4926 19.3898 23.1396C19.0368 22.7866 18.5581 22.5882 18.0588 22.5882H10.5294Z"/> - </div> - {/* Header */} - <div className="col-span-4"> - <div className="ms-20"> - <div className="row-span-1 mt-9 py-3 w-11/12"> - <WelcomingText name="Table 1"/> - </div> - <div className="row-span-1 mt-6 py-3 w-11/12"> - <Search onChange={handleSearch} onKeyDown={handleEnter} /> - </div> - <div className="row-span-6 mt-6 mb-9 py-7 w-11/12 bg-white rounded-3xl"> - <h2 className="text-mealshub-red text-3xl font-bold ps-9 mb-10">Recommended Tenants</h2> - <div className="grid grid-cols-3 gap-12 my-6 mx-9 justify-items-center"> - <TenantCard data={data} /> + return ( + <div className="grid grid-cols-5 grid-rows-8 bg-mealshub-cream min-h-screen"> + {/* Sidebar */} + <div className="col-span-1 row-span-8"> + <Sidebar + customer={true} + number={3} + current={1} + menu1="Home" + page1="/" + path1="M27 14.6465V29.4398C27 30.1188 26.7629 30.77 26.341 31.2501C25.919 31.7303 25.3467 32 24.75 32H19.125C18.5282 32 17.9559 31.7303 17.534 31.2501C17.112 30.77 16.875 30.1188 16.875 29.4398V23.0392C16.875 22.6997 16.7565 22.3741 16.5455 22.1341C16.3345 21.894 16.0484 21.7591 15.75 21.7591H11.25C10.9516 21.7591 10.6655 21.894 10.4545 22.1341C10.2435 22.3741 10.125 22.6997 10.125 23.0392V29.4398C10.125 30.1188 9.88794 30.77 9.46598 31.2501C9.04402 31.7303 8.47173 32 7.87499 32H2.25C1.65326 32 1.08097 31.7303 0.659009 31.2501C0.237052 30.77 2.59985e-08 30.1188 2.59985e-08 29.4398V14.6465C-4.73102e-05 14.2922 0.0645457 13.9417 0.189691 13.6172C0.314836 13.2928 0.497809 13.0014 0.72703 12.7616L11.977 0.683737L11.9925 0.666135C12.4067 0.237512 12.9464 0 13.5063 0C14.0662 0 14.6059 0.237512 15.0201 0.666135C15.0249 0.672397 15.0301 0.678277 15.0356 0.683737L26.2856 12.7616C26.5125 13.0027 26.6931 13.2946 26.8161 13.619C26.939 13.9434 27.0016 14.2933 27 14.6465Z" + menu2="Shopping Cart" + page2="/cart" + path2="M22.2968 25.6C20.7499 25.6 19.5097 27.024 19.5097 28.8C19.5097 29.6487 19.8033 30.4626 20.326 31.0627C20.8487 31.6629 21.5576 32 22.2968 32C23.036 32 23.7449 31.6629 24.2675 31.0627C24.7902 30.4626 25.0839 29.6487 25.0839 28.8C25.0839 27.9513 24.7902 27.1374 24.2675 26.5373C23.7449 25.9371 23.036 25.6 22.2968 25.6ZM0 0V3.2H2.7871L7.80387 15.344L5.90864 19.264C5.69961 19.712 5.57419 20.24 5.57419 20.8C5.57419 21.6487 5.86783 22.4626 6.39051 23.0627C6.9132 23.6629 7.62211 24 8.36129 24H25.0839V20.8H8.94658C8.85418 20.8 8.76557 20.7579 8.70023 20.6828C8.6349 20.6078 8.59819 20.5061 8.59819 20.4C8.59819 20.32 8.61213 20.256 8.64 20.208L9.89419 17.6H20.2761C21.3213 17.6 22.241 16.928 22.7148 15.952L27.7037 5.6C27.8013 5.344 27.871 5.072 27.871 4.8C27.871 4.37565 27.7241 3.96869 27.4628 3.66863C27.2015 3.36857 26.847 3.2 26.4774 3.2H5.86684L4.5569 0M8.36129 25.6C6.81445 25.6 5.57419 27.024 5.57419 28.8C5.57419 29.6487 5.86783 30.4626 6.39051 31.0627C6.9132 31.6629 7.62211 32 8.36129 32C9.10047 32 9.80938 31.6629 10.3321 31.0627C10.8547 30.4626 11.1484 29.6487 11.1484 28.8C11.1484 27.9513 10.8547 27.1374 10.3321 26.5373C9.80938 25.9371 9.10047 25.6 8.36129 25.6Z" + menu3="Orders" + page3={`/order/list/${tableid}`} + path3="M4.10306 1.10306C3 2.20612 3 3.97929 3 7.52941V24.4706C3 28.0207 3 29.7939 4.10306 30.8969C5.20612 32 6.97929 32 10.5294 32H21.8235C25.3736 32 27.1468 32 28.2499 30.8969C29.3529 29.7939 29.3529 28.0207 29.3529 24.4706V7.52941C29.3529 3.97929 29.3529 2.20612 28.2499 1.10306C27.1468 -1.12197e-07 25.3736 0 21.8235 0H10.5294C6.97929 0 5.20612 -1.12197e-07 4.10306 1.10306ZM10.5294 7.52941C10.0302 7.52941 9.5514 7.72773 9.19839 8.08074C8.84538 8.43375 8.64706 8.91253 8.64706 9.41176C8.64706 9.911 8.84538 10.3898 9.19839 10.7428C9.5514 11.0958 10.0302 11.2941 10.5294 11.2941H21.8235C22.3228 11.2941 22.8015 11.0958 23.1546 10.7428C23.5076 10.3898 23.7059 9.911 23.7059 9.41176C23.7059 8.91253 23.5076 8.43375 23.1546 8.08074C22.8015 7.72773 22.3228 7.52941 21.8235 7.52941H10.5294ZM10.5294 15.0588C10.0302 15.0588 9.5514 15.2571 9.19839 15.6102C8.84538 15.9632 8.64706 16.4419 8.64706 16.9412C8.64706 17.4404 8.84538 17.9192 9.19839 18.2722C9.5514 18.6252 10.0302 18.8235 10.5294 18.8235H21.8235C22.3228 18.8235 22.8015 18.6252 23.1546 18.2722C23.5076 17.9192 23.7059 17.4404 23.7059 16.9412C23.7059 16.4419 23.5076 15.9632 23.1546 15.6102C22.8015 15.2571 22.3228 15.0588 21.8235 15.0588H10.5294ZM10.5294 22.5882C10.0302 22.5882 9.5514 22.7866 9.19839 23.1396C8.84538 23.4926 8.64706 23.9714 8.64706 24.4706C8.64706 24.9698 8.84538 25.4486 9.19839 25.8016C9.5514 26.1546 10.0302 26.3529 10.5294 26.3529H18.0588C18.5581 26.3529 19.0368 26.1546 19.3898 25.8016C19.7429 25.4486 19.9412 24.9698 19.9412 24.4706C19.9412 23.9714 19.7429 23.4926 19.3898 23.1396C19.0368 22.7866 18.5581 22.5882 18.0588 22.5882H10.5294Z" + /> + </div> + {/* Header */} + <div className="col-span-4"> + <div className="ms-20"> + <div className="row-span-1 mt-9 py-3 w-11/12"> + <WelcomingText name={user ? user!.fullname : ""} /> + </div> + <div className="row-span-1 mt-6 py-3 w-11/12"> + <Search + onChange={handleSearch} + onKeyDown={handleEnter} + /> + </div> + <div className="row-span-6 mt-6 mb-9 py-7 w-11/12 bg-white rounded-3xl"> + <h2 className="text-mealshub-red text-3xl font-bold ps-9 mb-10"> + Recommended Tenants + </h2> + <div className="grid grid-cols-3 gap-12 my-6 mx-9 justify-items-center"> + <TenantCard data={data} /> + </div> + </div> + </div> </div> - </div> </div> - </div> - </div> - ); + ); } diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index a7f9470a785247e35014f3299d8f30030f445f4b..1dbeed3c2b5e0680ac1f299ca51bcae8ed3b2b41 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -2,7 +2,8 @@ import { useEffect } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; import { useNavigate, useSearchParams } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; -import LoginForm from "../components/LoginForm"; +import Logo from "../components/Logo"; +import Input from "../components/Input"; interface LoginFormInput { email: string; @@ -23,20 +24,91 @@ export default function LoginPage() { useEffect(() => { if (!user) return; - returnUrl ? navigate(returnUrl) : navigate("/"); + returnUrl ? navigate(returnUrl) : navigate("/tenant/menus"); }, [user]); const submit: SubmitHandler<LoginFormInput> = async ({ email, password, }) => { - await login(email, password); + console.log(`email: ${email}, password: ${password}. MASUK`); + await login(email, password, "tenant-cashier"); + }; + + const handleGoBack = () => { + navigate(-1); }; return ( <div className="grid min-h-screen bg-[url('images/LoginBackground.png')]"> <div className="login-container"> - <LoginForm /> + <div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"> + <div className="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-4xl xl:p-0"> + <div className="p-6 space-y-4 md:space-y-6 sm:p-8 flex items-center justify-center"> + <div className="p-4 space-y-4 md:space-y-6 sm:p-6 w-full"> + <h1 className="text-xl font-lato font-bold leading-tight tracking-tight text-mealshub-orange md:text-4xl text-center"> + LOGIN + </h1> + <form + className="space-y-4 md:space-y-6" + onSubmit={handleSubmit(submit)} + noValidate + > + <Input + type="email" + label="Email" + {...register("email", { + required: true, + pattern: { + value: /^[\w-.]+@([\w-]+\.)+[\w-]{2,63}$/i, + message: "Email is not valid.", + }, + minLength: 8, + maxLength: 30, + })} + error={errors.email} + /> + <Input + type="password" + label="Password" + {...register("password", { + required: true, + minLength: 6, + maxLength: 30, + })} + error={errors.password} + /> + <button + type="submit" + className="w-full text-white bg-mealshub-orange hover:bg-mealshub-cream focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center" + > + Login + </button> + <div className="flex items-center justify-center"> + <p className="text-sm font-light text-gray-500"> + Don't have an account yet?{" "} + <a + href="#" + className="font-medium text-mealshub-blue hover:underline" + > + Register + </a> + </p> + </div> + </form> + </div> + <div className="p-12"> + <Logo height="30" width="30" /> + </div> + </div> + </div> + <button + onClick={handleGoBack} + className="mt-3 bg-mealshub-orange text-white px-4 py-2 rounded-lg hover:bg-mealshub-cream focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium text-sm" + > + Go Back + </button> + </div> </div> </div> ); diff --git a/frontend/src/pages/ManageOrderTenant.tsx b/frontend/src/pages/ManageOrderTenant.tsx index 3dc1d984581cbccdc62f7d1f50165f61034103c1..0f6bbe980b4755481a51fa1dcc122440a6261678 100644 --- a/frontend/src/pages/ManageOrderTenant.tsx +++ b/frontend/src/pages/ManageOrderTenant.tsx @@ -11,34 +11,34 @@ import { useParams } from "react-router-dom"; import crypto from "crypto-js"; interface Order { - id: number, - status: string, - time: Date, - id_table: number, - id_tenant: number + id: number; + status: string; + time: Date; + id_table: number; + id_tenant: number; } interface OrderProduct { - num_product: number, - id_product: number, - id_order: number + num_product: number; + id_product: number; + id_order: number; } interface Product { - id: number, - name: string, - price: number + id: number; + name: string; + price: number; } interface Tenant { - id: number, - name: string + id: number; + name: string; } interface Payment { - id: number, - status: string, - id_order: number + id: number; + status: string; + id_order: number; } interface OrderSummary { @@ -63,12 +63,20 @@ export default function OrderDetails() { const handleProfileClick = () => { setShowProfileDropDown(!showProfileDropDown); }; - const [joinedOrderSummaryData, setJoinedOrderSummaryData] = useState<OrderSummary[]>([]); + const [joinedOrderSummaryData, setJoinedOrderSummaryData] = useState< + OrderSummary[] + >([]); const getOrderSummaryData = async () => { - const orderResponse = await Axios.get(`http://localhost:8000/orders/${orderid}`); - const productResponse = await Axios.get("http://localhost:8000/products"); - const orderProductResponse = await Axios.get("http://localhost:8000/orderproduct"); + const orderResponse = await Axios.get( + `http://localhost:8000/orders/${orderid}`, + ); + const productResponse = await Axios.get( + "http://localhost:8000/products", + ); + const orderProductResponse = await Axios.get( + "http://localhost:8000/orderproduct", + ); const tenantResponse = await Axios.get("http://localhost:8000/tenants"); const orderData = orderResponse.data.data; @@ -80,12 +88,26 @@ export default function OrderDetails() { // OrderData is not an array, so we need to convert it into an array const orderDataArray = [orderData]; const result = orderDataArray.map((order: Order) => { - const tenant = tenantData.find((tenant: Tenant) => tenant.id === order.id_tenant); - const orderproduct = orderProductData.filter((orderProduct: OrderProduct) => orderProduct.id_order === order.id); - const listproduct = orderproduct.map((orderProduct: OrderProduct) => { - const product = productData.find((product: Product) => product.id === orderProduct.id_product); - return [product?.name || 'Product Not Found', orderProduct.num_product, product?.price || 0]; - }); + const tenant = tenantData.find( + (tenant: Tenant) => tenant.id === order.id_tenant, + ); + const orderproduct = orderProductData.filter( + (orderProduct: OrderProduct) => + orderProduct.id_order === order.id, + ); + const listproduct = orderproduct.map( + (orderProduct: OrderProduct) => { + const product = productData.find( + (product: Product) => + product.id === orderProduct.id_product, + ); + return [ + product?.name || "Product Not Found", + orderProduct.num_product, + product?.price || 0, + ]; + }, + ); return { id: tenant?.id || 0, @@ -95,7 +117,6 @@ export default function OrderDetails() { }); setJoinedOrderSummaryData(result); - }; useEffect(() => { @@ -104,11 +125,17 @@ export default function OrderDetails() { console.log(joinedOrderSummaryData); - const [joinedOrderDetailsData, setJoinedOrderDetailsData] = useState<OrderDetails[]>([]); + const [joinedOrderDetailsData, setJoinedOrderDetailsData] = useState< + OrderDetails[] + >([]); const getOrderDetailsData = async () => { - const orderResponse = await Axios.get(`http://localhost:8000/orders/${orderid}`); - const paymentResponse = await Axios.get("http://localhost:8000/payments"); + const orderResponse = await Axios.get( + `http://localhost:8000/orders/${orderid}`, + ); + const paymentResponse = await Axios.get( + "http://localhost:8000/payments", + ); const orderData = orderResponse.data.data; const paymentData = paymentResponse.data.data; @@ -116,7 +143,9 @@ export default function OrderDetails() { // Perform the join based on the specified conditions const OrderDataArray = [orderData]; const result = OrderDataArray.map((order: Order) => { - const matchingPayment = paymentData.find((payment: Payment) => payment.id_order === order.id); + const matchingPayment = paymentData.find( + (payment: Payment) => payment.id_order === order.id, + ); // Hash the code (matchingPayment.id) using SHA-256 from crypto-js and take the first 5 characters const hashedCode = matchingPayment ? crypto.SHA256(matchingPayment.id.toString()).toString().substring(0, 5) : null; @@ -145,7 +174,7 @@ export default function OrderDetails() { label: "Waiting for Payment", disabled: true, color: "mealshub-greenpalet", - onClick: () => { }, + onClick: () => {}, }); useEffect(() => { @@ -163,24 +192,32 @@ export default function OrderDetails() { label: "Waiting for Payment", disabled: true, color: "mealshub-greenpalet", - onClick: () => { }, + onClick: () => {}, }); - } else if (paymentStatus === "Confirmed" && orderStatus === "Waiting for Payment") { + } else if ( + paymentStatus === "Confirmed" && + orderStatus === "Waiting for Payment" + ) { setButtonState({ label: "Prepare Order", disabled: false, color: "mealshub-red", onClick: handlePrepareOrder, }); - } else if (paymentStatus === "Confirmed" && orderStatus === "Prepared") { + } else if ( + paymentStatus === "Confirmed" && + orderStatus === "Prepared" + ) { setButtonState({ label: "Complete Order", disabled: false, color: "mealshub-red", onClick: handleCompleteOrder, }); - } - else if (paymentStatus === "Confirmed" && orderStatus === "Completed") { + } else if ( + paymentStatus === "Confirmed" && + orderStatus === "Completed" + ) { setButtonState({ label: "Completed", disabled: true, @@ -194,7 +231,7 @@ export default function OrderDetails() { const handlePrepareOrder = async () => { // Call API or perform actions to update order status to "Prepared" await Axios.patch(`http://localhost:8000/orders/${orderid}`, { - status: "Prepared" + status: "Prepared", }); getOrderDetailsData(); // Set button state accordingly @@ -209,7 +246,7 @@ export default function OrderDetails() { const handleCompleteOrder = async () => { // Call API or perform actions to update order status to "Completed" await Axios.patch(`http://localhost:8000/orders/${orderid}`, { - status: "Completed" + status: "Completed", }); getOrderDetailsData(); // Set button state accordingly @@ -217,7 +254,7 @@ export default function OrderDetails() { label: "Completed", disabled: true, color: "mealshub-greenpalet", - onClick: () => { }, + onClick: () => {}, }); }; return ( @@ -226,15 +263,15 @@ export default function OrderDetails() { {/* Sidebar */} <div className="col-span-1 row-span-8"> <Sidebar - default="/tenantpage/menus" + default="/tenant/menus" number={2} current={2} menu1="Menu" path1="M0 0H27V32H0V0ZM2.7 3.2V12H24.3V3.2H2.7ZM24.3 15.2H2.7V28.8H24.3V15.2ZM5.3946 6.4H8.1V9.6H8.0946V9.6064H5.3946V6.4ZM10.8 6.4H21.6V9.6H10.8V6.4Z" - page1="/tenantpage/menus" + page1="/tenant/menus" menu2="Orders" path2="M4.10306 1.10306C3 2.20612 3 3.97929 3 7.52941V24.4706C3 28.0207 3 29.7939 4.10306 30.8969C5.20612 32 6.97929 32 10.5294 32H21.8235C25.3736 32 27.1468 32 28.2499 30.8969C29.3529 29.7939 29.3529 28.0207 29.3529 24.4706V7.52941C29.3529 3.97929 29.3529 2.20612 28.2499 1.10306C27.1468 -1.12197e-07 25.3736 0 21.8235 0H10.5294C6.97929 0 5.20612 -1.12197e-07 4.10306 1.10306ZM10.5294 7.52941C10.0302 7.52941 9.5514 7.72773 9.19839 8.08074C8.84538 8.43375 8.64706 8.91253 8.64706 9.41176C8.64706 9.911 8.84538 10.3898 9.19839 10.7428C9.5514 11.0958 10.0302 11.2941 10.5294 11.2941H21.8235C22.3228 11.2941 22.8015 11.0958 23.1546 10.7428C23.5076 10.3898 23.7059 9.911 23.7059 9.41176C23.7059 8.91253 23.5076 8.43375 23.1546 8.08074C22.8015 7.72773 22.3228 7.52941 21.8235 7.52941H10.5294ZM10.5294 15.0588C10.0302 15.0588 9.5514 15.2571 9.19839 15.6102C8.84538 15.9632 8.64706 16.4419 8.64706 16.9412C8.64706 17.4404 8.84538 17.9192 9.19839 18.2722C9.5514 18.6252 10.0302 18.8235 10.5294 18.8235H21.8235C22.3228 18.8235 22.8015 18.6252 23.1546 18.2722C23.5076 17.9192 23.7059 17.4404 23.7059 16.9412C23.7059 16.4419 23.5076 15.9632 23.1546 15.6102C22.8015 15.2571 22.3228 15.0588 21.8235 15.0588H10.5294ZM10.5294 22.5882C10.0302 22.5882 9.5514 22.7866 9.19839 23.1396C8.84538 23.4926 8.64706 23.9714 8.64706 24.4706C8.64706 24.9698 8.84538 25.4486 9.19839 25.8016C9.5514 26.1546 10.0302 26.3529 10.5294 26.3529H18.0588C18.5581 26.3529 19.0368 26.1546 19.3898 25.8016C19.7429 25.4486 19.9412 24.9698 19.9412 24.4706C19.9412 23.9714 19.7429 23.4926 19.3898 23.1396C19.0368 22.7866 18.5581 22.5882 18.0588 22.5882H10.5294Z" - page2="/tenantpage/orders" + page2="/tenant/orders" /> </div> {/* Header */} @@ -243,17 +280,27 @@ export default function OrderDetails() { <Welcome user="Aldaebaran" /> </div> <div className="absolute top-0 right-0 mt-9 mx-12"> - <Profile image="/public/images/ProfileDefault.png" onProfileClick={handleProfileClick} /> + <Profile + image="/public/images/ProfileDefault.png" + onProfileClick={handleProfileClick} + /> </div> <div className="absolute top-12 right-0 mt-9 mx-12"> - {showProfileDropDown && <ProfileDropDown name="Aldaebaran" email="Aldaebaran@example.com" />} + {showProfileDropDown && ( + <ProfileDropDown + name="Aldaebaran" + email="Aldaebaran@example.com" + /> + )} </div> <div className="row-span-7 mt-6 mb-9 w-11/12"> <div className="ms-4"> - <BackButton page="/tenantpage/orders" /> + <BackButton page="/tenant/orders" /> </div> <div className="ms-20 py-12 bg-white rounded-3xl"> - <h2 className="text-mealshub-red text-3xl font-bold ms-16">Order Details</h2> + <h2 className="text-mealshub-red text-3xl font-bold ms-16"> + Order Details + </h2> <OrderDetailsCard data={joinedOrderDetailsData} /> <OrderSummaryCard data={joinedOrderSummaryData} customer={false} /> <div className="flex flex-col items-center"> @@ -269,5 +316,5 @@ export default function OrderDetails() { </div> </div> </div> - ) -} \ No newline at end of file + ); +} diff --git a/frontend/src/pages/PageManageMenu.tsx b/frontend/src/pages/PageManageMenu.tsx index 6f6a895ebe5c3457e8757b1c4abaa16ce8b2a6ec..e10936e93f8771cc9f577ae3fd1b2058ddd142d8 100644 --- a/frontend/src/pages/PageManageMenu.tsx +++ b/frontend/src/pages/PageManageMenu.tsx @@ -9,42 +9,50 @@ import DeletePopUp from "../components/DeletePopUp"; import { useState, useEffect } from "react"; import AddForm from "../components/AddForm"; import ProfileDropDown from "../components/ProfileDropDown"; +import { useAuth } from "../hooks/useAuth"; import Axios from "axios"; interface Product { - id: number, - image: string, - name: string, - description: string, - price: number, - stock: number, - id_tenant: number + id: number; + image: string; + name: string; + description: string; + price: number; + stock: number; + id_tenant: number; } interface MenuCard { - id: number, - image: string, - name: string, - description: string, - price: number, - stock: number + id: number; + image: string; + name: string; + description: string; + price: number; + stock: number; } export default function PageManageMenu() { - const idTenant = 1; + const { user, logout } = useAuth(); + const idTenant = user!.id; const [MenuData, setMenuData] = useState<MenuCard[]>([]); const getMenuData = async () => { - const tenantResponse = await Axios.get(`http://localhost:8000/tenants/${idTenant}`); - const productResponse = await Axios.get("http://localhost:8000/products"); + const tenantResponse = await Axios.get( + `http://localhost:8000/tenants/${idTenant}`, + ); + const productResponse = await Axios.get( + "http://localhost:8000/products", + ); const tenantData = tenantResponse.data.data; const productData = productResponse.data.data; // Perform the join based on the specified conditions // OrderData is not an array, so we need to convert it into an array - const producttenant = productData.filter((product: Product) => product.id_tenant === tenantData.id); + const producttenant = productData.filter( + (product: Product) => product.id_tenant === tenantData.id, + ); const result = producttenant.map((product: Product) => { return { id: product.id, @@ -52,12 +60,11 @@ export default function PageManageMenu() { name: product.name, description: product.description, price: product.price, - stock: product.stock - } + stock: product.stock, + }; }); setMenuData(result); - }; useEffect(() => { @@ -78,7 +85,6 @@ export default function PageManageMenu() { getData(); }, []); - const [menuToDelete, setMenuToDelete] = useState(null); const [menuToEdit, setMenuToEdit] = useState(null); const [isEditFormVisible, setEditFormVisible] = useState(false); @@ -122,7 +128,7 @@ export default function PageManageMenu() { // Perbarui data setelah penghapusan getMenuData(); } catch (error) { - console.error('Error deleting menu:', error); + console.error("Error deleting menu:", error); } }; @@ -133,21 +139,21 @@ export default function PageManageMenu() { {/* Sidebar */} <div className="col-span-1 row-span-8"> <Sidebar - default="/tenantpage/menus" + default="/tenant/menus" number={2} current={1} menu1="Menu" path1="M0 0H27V32H0V0ZM2.7 3.2V12H24.3V3.2H2.7ZM24.3 15.2H2.7V28.8H24.3V15.2ZM5.3946 6.4H8.1V9.6H8.0946V9.6064H5.3946V6.4ZM10.8 6.4H21.6V9.6H10.8V6.4Z" - page1="/tenantpage/menus" + page1="/tenant/menus" menu2="Orders" path2="M4.10306 1.10306C3 2.20612 3 3.97929 3 7.52941V24.4706C3 28.0207 3 29.7939 4.10306 30.8969C5.20612 32 6.97929 32 10.5294 32H21.8235C25.3736 32 27.1468 32 28.2499 30.8969C29.3529 29.7939 29.3529 28.0207 29.3529 24.4706V7.52941C29.3529 3.97929 29.3529 2.20612 28.2499 1.10306C27.1468 -1.12197e-07 25.3736 0 21.8235 0H10.5294C6.97929 0 5.20612 -1.12197e-07 4.10306 1.10306ZM10.5294 7.52941C10.0302 7.52941 9.5514 7.72773 9.19839 8.08074C8.84538 8.43375 8.64706 8.91253 8.64706 9.41176C8.64706 9.911 8.84538 10.3898 9.19839 10.7428C9.5514 11.0958 10.0302 11.2941 10.5294 11.2941H21.8235C22.3228 11.2941 22.8015 11.0958 23.1546 10.7428C23.5076 10.3898 23.7059 9.911 23.7059 9.41176C23.7059 8.91253 23.5076 8.43375 23.1546 8.08074C22.8015 7.72773 22.3228 7.52941 21.8235 7.52941H10.5294ZM10.5294 15.0588C10.0302 15.0588 9.5514 15.2571 9.19839 15.6102C8.84538 15.9632 8.64706 16.4419 8.64706 16.9412C8.64706 17.4404 8.84538 17.9192 9.19839 18.2722C9.5514 18.6252 10.0302 18.8235 10.5294 18.8235H21.8235C22.3228 18.8235 22.8015 18.6252 23.1546 18.2722C23.5076 17.9192 23.7059 17.4404 23.7059 16.9412C23.7059 16.4419 23.5076 15.9632 23.1546 15.6102C22.8015 15.2571 22.3228 15.0588 21.8235 15.0588H10.5294ZM10.5294 22.5882C10.0302 22.5882 9.5514 22.7866 9.19839 23.1396C8.84538 23.4926 8.64706 23.9714 8.64706 24.4706C8.64706 24.9698 8.84538 25.4486 9.19839 25.8016C9.5514 26.1546 10.0302 26.3529 10.5294 26.3529H18.0588C18.5581 26.3529 19.0368 26.1546 19.3898 25.8016C19.7429 25.4486 19.9412 24.9698 19.9412 24.4706C19.9412 23.9714 19.7429 23.4926 19.3898 23.1396C19.0368 22.7866 18.5581 22.5882 18.0588 22.5882H10.5294Z" - page2="/tenantpage/orders" + page2="/tenant/orders" /> </div> {/* Header */} <div className="col-span-4"> <div className="ms-20 row-span-1 mt-9 py-3 w-11/12"> - <Welcome user="Aldaebaran" /> + <Welcome user={user ? user!.fullname : ""} /> </div> <div className="absolute top-0 right-0 mt-9 mx-12"> <Profile @@ -158,8 +164,9 @@ export default function PageManageMenu() { <div className="absolute top-12 right-0 mt-9 mx-12"> {showProfileDropDown && ( <ProfileDropDown - name="Aldaebaran" - email="Aldaebaran@example.com" + name={user!.fullname} + email={user!.email} + onClick={logout} /> )} </div> @@ -174,16 +181,25 @@ export default function PageManageMenu() { image={menus.image} stock={menus.stock} name={menus.name} - onEditClick={() => handleEditClick(menus.id)} - onDeleteClick={() => handleDeleteClick(menus.id)} + onEditClick={() => + handleEditClick(menus.id) + } + onDeleteClick={() => + handleDeleteClick(menus.id) + } /> {isEditFormVisible && ( - <EditForm onClose={handleCloseEditForm} idMenu={menuToEdit} /> + <EditForm + onClose={handleCloseEditForm} + idMenu={menuToEdit} + /> )} {isDeletePopUpVisible && ( <DeletePopUp onClose={handleCloseDeletePopUp} - onConfirm={() => handleConfirmDelete(menuToDelete)} + onConfirm={() => + handleConfirmDelete(menuToDelete) + } /> )} </div> @@ -192,7 +208,11 @@ export default function PageManageMenu() { <div className="flex flex-col items-center justify-center"> <AddButton onClick={handleAddMenuClick} /> {showAddMenu && ( - <AddForm onClose={handleCloseAddMenu} idTenant={idTenant} idProduct={maxId + 1} /> + <AddForm + onClose={handleCloseAddMenu} + idTenant={idTenant} + idProduct={maxId + 1} + /> )} </div> </div> diff --git a/frontend/src/pages/PageManageOrder.tsx b/frontend/src/pages/PageManageOrder.tsx index add10095d31011cf0b086571902529b7ef957841..ea6840536235be550d6e1bbdadd18d093d578aed 100644 --- a/frontend/src/pages/PageManageOrder.tsx +++ b/frontend/src/pages/PageManageOrder.tsx @@ -1,16 +1,28 @@ import Sidebar from "../components/Sidebar"; import Welcome from "../components/Welcome"; import Profile from "../components/Profile"; +import { useAuth } from "../hooks/useAuth"; +// import data from "../dataOrder.json"; import TableOrder from "../components/TableOrder"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; import ProfileDropDown from "../components/ProfileDropDown"; import joinedOrderPayment from "../../../backend/src/services/api/joinedOrderPayment"; - export default function PageManageOrder() { - const tenantid = 1; + const { user, logout } = useAuth(); + const tenantid = user!.id; const [showProfileDropDown, setShowProfileDropDown] = useState(false); + // const navigate = useNavigate(); + // const [params] = useSearchParams(); + // const returnUrl = params.get("returnUrl"); + + // useEffect(() => { + // if (!user) return; + // returnUrl ? navigate(returnUrl) : navigate(returnUrl); + // }, [user]); + const handleProfileClick = () => { setShowProfileDropDown(!showProfileDropDown); }; @@ -23,21 +35,21 @@ export default function PageManageOrder() { {/* Sidebar */} <div className="col-span-1 row-span-8"> <Sidebar - default="/tenantpage/menus" + default="/tenant/menus" number={2} current={2} menu1="Menu" path1="M0 0H27V32H0V0ZM2.7 3.2V12H24.3V3.2H2.7ZM24.3 15.2H2.7V28.8H24.3V15.2ZM5.3946 6.4H8.1V9.6H8.0946V9.6064H5.3946V6.4ZM10.8 6.4H21.6V9.6H10.8V6.4Z" - page1="/tenantpage/menus" + page1="/tenant/menus" menu2="Orders" path2="M4.10306 1.10306C3 2.20612 3 3.97929 3 7.52941V24.4706C3 28.0207 3 29.7939 4.10306 30.8969C5.20612 32 6.97929 32 10.5294 32H21.8235C25.3736 32 27.1468 32 28.2499 30.8969C29.3529 29.7939 29.3529 28.0207 29.3529 24.4706V7.52941C29.3529 3.97929 29.3529 2.20612 28.2499 1.10306C27.1468 -1.12197e-07 25.3736 0 21.8235 0H10.5294C6.97929 0 5.20612 -1.12197e-07 4.10306 1.10306ZM10.5294 7.52941C10.0302 7.52941 9.5514 7.72773 9.19839 8.08074C8.84538 8.43375 8.64706 8.91253 8.64706 9.41176C8.64706 9.911 8.84538 10.3898 9.19839 10.7428C9.5514 11.0958 10.0302 11.2941 10.5294 11.2941H21.8235C22.3228 11.2941 22.8015 11.0958 23.1546 10.7428C23.5076 10.3898 23.7059 9.911 23.7059 9.41176C23.7059 8.91253 23.5076 8.43375 23.1546 8.08074C22.8015 7.72773 22.3228 7.52941 21.8235 7.52941H10.5294ZM10.5294 15.0588C10.0302 15.0588 9.5514 15.2571 9.19839 15.6102C8.84538 15.9632 8.64706 16.4419 8.64706 16.9412C8.64706 17.4404 8.84538 17.9192 9.19839 18.2722C9.5514 18.6252 10.0302 18.8235 10.5294 18.8235H21.8235C22.3228 18.8235 22.8015 18.6252 23.1546 18.2722C23.5076 17.9192 23.7059 17.4404 23.7059 16.9412C23.7059 16.4419 23.5076 15.9632 23.1546 15.6102C22.8015 15.2571 22.3228 15.0588 21.8235 15.0588H10.5294ZM10.5294 22.5882C10.0302 22.5882 9.5514 22.7866 9.19839 23.1396C8.84538 23.4926 8.64706 23.9714 8.64706 24.4706C8.64706 24.9698 8.84538 25.4486 9.19839 25.8016C9.5514 26.1546 10.0302 26.3529 10.5294 26.3529H18.0588C18.5581 26.3529 19.0368 26.1546 19.3898 25.8016C19.7429 25.4486 19.9412 24.9698 19.9412 24.4706C19.9412 23.9714 19.7429 23.4926 19.3898 23.1396C19.0368 22.7866 18.5581 22.5882 18.0588 22.5882H10.5294Z" - page2="/tenantpage/orders" + page2="/tenant/orders" /> </div> {/* Header */} <div className="col-span-4"> <div className="ms-20 row-span-1 mt-9 py-3 w-11/12"> - <Welcome user="Aldaebaran" /> + <Welcome user={user ? user!.fullname : ""} /> </div> <div className="absolute top-0 right-0 mt-9 mx-12"> <Profile @@ -48,8 +60,9 @@ export default function PageManageOrder() { <div className="absolute top-12 right-0 mt-9 mx-12"> {showProfileDropDown && ( <ProfileDropDown - name="Aldaebaran" - email="Aldaebaran@example.com" + name={user!.fullname} + email={user!.email} + onClick={logout} /> )} </div> diff --git a/frontend/src/pages/SignUpCashier.tsx b/frontend/src/pages/RegisterPage.tsx similarity index 59% rename from frontend/src/pages/SignUpCashier.tsx rename to frontend/src/pages/RegisterPage.tsx index 2afb7cade932a710747aa52faa30975bbc68b706..03a52bd60963a8d8263dfa4c1ce59388b5cd8a98 100644 --- a/frontend/src/pages/SignUpCashier.tsx +++ b/frontend/src/pages/RegisterPage.tsx @@ -1,23 +1,25 @@ -import SignUpCardCashier from "../components/SignUpCardCashier"; +import RegisterForm from "../components/RegisterForm"; -export default function SignUpCashier() { +export default function RegisterPage() { return ( <div className="grid grid-cols-2 min-h-screen"> - <div className="relative"> + <div className="col-span-1 relative"> <img src="../../public/images/SignUpImg.png" alt="" - className="opacity-50 min-h-screen min-w-full" + className="absolute opacity-50 h-screen min-w-full" /> <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> - <h1 className="text-9xl font-bold text-white">JOIN US</h1> + <h1 className="text-9xl font-bold text-white drop-shadow-2xl"> + JOIN US + </h1> <span className="bg-mealshub-blue text-white font-nunito font-bold text-lg my-16 px-24 py-2"> Discover a new way to dine! </span> </div> </div> - <div className="flex items-center justify-center flex-col bg-mealshub-cream "> - <SignUpCardCashier /> + <div className="flex items-center justify-center bg-mealshub-cream col-span-1"> + <RegisterForm /> </div> </div> ); diff --git a/frontend/src/pages/RegisterTable.tsx b/frontend/src/pages/RegisterTable.tsx index faa600488e6254afb0eb8c3ebd8fc8045a11c2d9..f73fc8572f45790b3b000c0c7c4b35f7e688808a 100644 --- a/frontend/src/pages/RegisterTable.tsx +++ b/frontend/src/pages/RegisterTable.tsx @@ -1,29 +1,43 @@ +import { useNavigate } from "react-router-dom"; + import RegisterTableCard from "../components/RegisterTableCard"; export default function RegisterTable() { - return ( - <div className="relative"> - <img - src="../../public/images/RegisterTableImage.png" - alt="" - className="absolute top-0 left-0 w-full h-full opacity-50" - style={{ zIndex: -1 }} - /> - <div className="grid grid-cols-2 min-h-screen relative"> - <div className="relative z-10"> - <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> - <h1 className="text-9xl font-bold text-white text-center"> - GET YOUR SEAT - </h1> - <span className="bg-mealshub-blue text-white font-lato font-bold text-lg my-16 px-24 py-2"> - Secure Your Comfort, Reserve Your Space - </span> - </div> - </div> - <div className="flex items-center justify-center z-10"> - <RegisterTableCard /> + const navigate = useNavigate(); + + const handleGoBack = () => { + navigate(-1); + }; + + return ( + <div className="relative"> + <img + src="../../public/images/RegisterTableImage.png" + alt="" + className="absolute top-0 left-0 w-full h-full opacity-50" + style={{ zIndex: -1 }} + /> + <div className="grid grid-cols-2 min-h-screen relative"> + <div className="relative z-10"> + <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> + <h1 className="text-7xl font-bold text-white text-center drop-shadow-xl"> + GET YOUR SEAT + </h1> + <span className="bg-mealshub-blue text-white font-lato font-bold text-lg my-16 px-24 py-2"> + Secure Your Comfort, Reserve Your Space + </span> + <button + onClick={handleGoBack} + className="text-white text-lg font-lato bg-mealshub-orange px-4 py-2 rounded-md cursor-pointer" + > + Go Back + </button> + </div> + </div> + <div className="flex items-center justify-center z-10"> + <RegisterTableCard /> + </div> + </div> </div> - </div> - </div> - ); + ); } diff --git a/frontend/src/pages/RegisterTenant.tsx b/frontend/src/pages/RegisterTenant.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6bb43122b92cefaf0d4d6930638614092a57e777 --- /dev/null +++ b/frontend/src/pages/RegisterTenant.tsx @@ -0,0 +1,26 @@ +import RegisterTenantForm from "../components/RegisterTenantForm"; + +export default function RegisterTenant() { + return ( + <div className="grid grid-cols-2 min-h-screen"> + <div className="col-span-1 relative"> + <img + src="../../public/images/SignUpImg.png" + alt="" + className="absolute opacity-50 h-screen min-w-full" + /> + <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> + <h1 className="text-9xl font-bold text-white drop-shadow-2xl"> + JOIN US + </h1> + <span className="bg-mealshub-blue text-white font-nunito font-bold text-lg my-16 px-24 py-2"> + Discover a new way to dine! + </span> + </div> + </div> + <div className="flex items-center justify-center bg-mealshub-cream col-span-1"> + <RegisterTenantForm /> + </div> + </div> + ); +} diff --git a/frontend/src/pages/RolePage.tsx b/frontend/src/pages/RolePage.tsx index 14a8134a42c1980789edb8ae911bccdc7cc40c86..2578bd06cf21cdebedb24a8b111c4b74bff3310f 100644 --- a/frontend/src/pages/RolePage.tsx +++ b/frontend/src/pages/RolePage.tsx @@ -1,4 +1,4 @@ -import RoleCard from "../components/RoleCard" +import RoleCard from "../components/RoleCard"; export default function ChooseRolePage() { return ( @@ -7,16 +7,26 @@ export default function ChooseRolePage() { <RoleCard /> </div> <div className="col-span-2 relative"> - <img src="../../public/images/WelcomingPage(Role).png" alt="" className="absolute opacity-50 h-screen min-w-full" /> + <img + src="../../public/images/WelcomingPage(Role).png" + alt="" + className="absolute opacity-50 h-screen min-w-full" + /> <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> - <h1 className="text-9xl font-bold text-white">WELCOME</h1> + <h1 className="text-9xl font-bold text-white drop-shadow-2xl"> + WELCOME + </h1> <span className="bg-mealshub-blue text-white font-nunito font-bold text-lg mt-12 px-24 py-2 flex items-center justify-center"> - MealsHub, your gateway to a centralized dining experience + MealsHub, your gateway to a centralized dining + experience </span> - <img src="../../public/images/MealsHub.png" alt="" className="object-cover h-80 w-80" /> + <img + src="../../public/images/MealsHub.png" + alt="" + className="object-cover h-80 w-80" + /> </div> </div> </div> - ) + ); } - diff --git a/frontend/src/pages/SignUpTenant.tsx b/frontend/src/pages/SignUpTenant.tsx deleted file mode 100644 index 4d9cad1cac567b44eabc6e4dcb3e737619685b02..0000000000000000000000000000000000000000 --- a/frontend/src/pages/SignUpTenant.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import SignUpCardTenant from "../components/SignUpCardTenant"; - -export default function SignUpTenant() { - return ( - <div className="grid grid-cols-2 min-h-screen"> - <div className="relative"> - <img src="../../public/images/SignUpImg.png" alt="" className="opacity-50 min-h-screen min-w-full" /> - <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center flex-col"> - <h1 className="text-9xl font-bold text-white">JOIN US</h1> - <span className="bg-mealshub-blue text-white font-nunito font-bold text-lg my-16 px-24 py-2"> - Discover a new way to dine! - </span> - </div> - </div> - <div className="flex items-center justify-center flex-col bg-mealshub-cream "> - <SignUpCardTenant /> - </div> - </div> - ) -} \ No newline at end of file diff --git a/frontend/src/services/userService.ts b/frontend/src/services/userService.ts index 04fded82c417a75f940c7af059aadaa969b7c665..a3b189d9cd5558b083163042657026db1623a59a 100644 --- a/frontend/src/services/userService.ts +++ b/frontend/src/services/userService.ts @@ -5,12 +5,35 @@ export const getUser = () => ? JSON.parse(localStorage.getItem("user")!) : null; -export const login = async (email: string, password: string) => { - const { data } = await axios.post("/users/login", { email, password }); +export const login = async (email: string, password: string, role: string) => { + console.log("masuk sini login di user service"); + const { data } = await axios.post("http://localhost:8000/auth/login", { + email, + password, + role, + }); + console.log("axios berhasil"); localStorage.setItem("user", JSON.stringify(data)); + console.log("data berhasil disimpan, user berhasil ganti"); return data; }; export const logout = () => { localStorage.removeItem("user"); }; + +export const register = async (registerData: any) => { + const { data } = await axios.post("users/register", registerData); + localStorage.setItem("user", JSON.stringify(data)); + return data; +}; + +export const updateProfile = async (user: any) => { + const { data } = await axios.put("/api/users/updateProfile", user); + localStorage.setItem("user", JSON.stringify(data)); + return data; +}; + +export const changePassword = async (passwords: string) => { + await axios.put("/api/users/changePassword", passwords); +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index af9c3116a63e14f574378319a7a570e6328bd31e..ecb5668eb1e7a70808ef23b97efb187b2ec1e81f 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -20,6 +20,10 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "../backend/src/services", "tsconfig.jest.json", "jest.setup.cjs"], + "include": [ + "src", + "../backend/src/services", "src/services/userService.ts", "tsconfig.jest.json", "jest.setup.cjs", + "src/services/userService.ts" + ], "references": [{ "path": "./tsconfig.node.json" }] }