tosanoob commited on
Commit
708809b
·
1 Parent(s): cd3fc96

Feature: frontend add update user

Browse files
frontend/.gitignore CHANGED
@@ -21,3 +21,7 @@
21
  npm-debug.log*
22
  yarn-debug.log*
23
  yarn-error.log*
 
 
 
 
 
21
  npm-debug.log*
22
  yarn-debug.log*
23
  yarn-error.log*
24
+
25
+ .env
26
+ note_dev.txt
27
+ object.json
frontend/package-lock.json CHANGED
@@ -11,10 +11,14 @@
11
  "@testing-library/jest-dom": "^5.17.0",
12
  "@testing-library/react": "^13.4.0",
13
  "@testing-library/user-event": "^13.5.0",
 
14
  "bootstrap": "^5.3.3",
 
 
15
  "react": "^18.3.1",
16
  "react-bootstrap": "^2.10.5",
17
  "react-dom": "^18.3.1",
 
18
  "react-router-dom": "^6.27.0",
19
  "react-scripts": "5.0.1",
20
  "validator": "^13.12.0",
@@ -5870,6 +5874,31 @@
5870
  "node": ">=4"
5871
  }
5872
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5873
  "node_modules/axobject-query": {
5874
  "version": "4.1.0",
5875
  "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -6402,6 +6431,12 @@
6402
  "node-int64": "^0.4.0"
6403
  }
6404
  },
 
 
 
 
 
 
6405
  "node_modules/buffer-from": {
6406
  "version": "1.1.2",
6407
  "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -7812,6 +7847,15 @@
7812
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
7813
  "license": "MIT"
7814
  },
 
 
 
 
 
 
 
 
 
7815
  "node_modules/ee-first": {
7816
  "version": "1.1.1",
7817
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -13187,6 +13231,15 @@
13187
  "jiti": "bin/jiti.js"
13188
  }
13189
  },
 
 
 
 
 
 
 
 
 
13190
  "node_modules/js-tokens": {
13191
  "version": "4.0.0",
13192
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -13350,6 +13403,28 @@
13350
  "node": ">=0.10.0"
13351
  }
13352
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13353
  "node_modules/jsx-ast-utils": {
13354
  "version": "3.3.5",
13355
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -13365,6 +13440,27 @@
13365
  "node": ">=4.0"
13366
  }
13367
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13368
  "node_modules/keyv": {
13369
  "version": "4.5.4",
13370
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -13513,6 +13609,42 @@
13513
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
13514
  "license": "MIT"
13515
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13516
  "node_modules/lodash.memoize": {
13517
  "version": "4.1.2",
13518
  "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -13525,6 +13657,12 @@
13525
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
13526
  "license": "MIT"
13527
  },
 
 
 
 
 
 
13528
  "node_modules/lodash.sortby": {
13529
  "version": "4.7.0",
13530
  "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -15955,6 +16093,12 @@
15955
  "node": ">= 0.10"
15956
  }
15957
  },
 
 
 
 
 
 
15958
  "node_modules/psl": {
15959
  "version": "1.9.0",
15960
  "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -16356,6 +16500,15 @@
16356
  "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
16357
  "license": "MIT"
16358
  },
 
 
 
 
 
 
 
 
 
16359
  "node_modules/react-is": {
16360
  "version": "17.0.2",
16361
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 
11
  "@testing-library/jest-dom": "^5.17.0",
12
  "@testing-library/react": "^13.4.0",
13
  "@testing-library/user-event": "^13.5.0",
14
+ "axios": "^1.7.7",
15
  "bootstrap": "^5.3.3",
16
+ "js-cookie": "^3.0.5",
17
+ "jsonwebtoken": "^9.0.2",
18
  "react": "^18.3.1",
19
  "react-bootstrap": "^2.10.5",
20
  "react-dom": "^18.3.1",
21
+ "react-image-crop": "^11.0.7",
22
  "react-router-dom": "^6.27.0",
23
  "react-scripts": "5.0.1",
24
  "validator": "^13.12.0",
 
5874
  "node": ">=4"
5875
  }
5876
  },
5877
+ "node_modules/axios": {
5878
+ "version": "1.7.7",
5879
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
5880
+ "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
5881
+ "license": "MIT",
5882
+ "dependencies": {
5883
+ "follow-redirects": "^1.15.6",
5884
+ "form-data": "^4.0.0",
5885
+ "proxy-from-env": "^1.1.0"
5886
+ }
5887
+ },
5888
+ "node_modules/axios/node_modules/form-data": {
5889
+ "version": "4.0.1",
5890
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
5891
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
5892
+ "license": "MIT",
5893
+ "dependencies": {
5894
+ "asynckit": "^0.4.0",
5895
+ "combined-stream": "^1.0.8",
5896
+ "mime-types": "^2.1.12"
5897
+ },
5898
+ "engines": {
5899
+ "node": ">= 6"
5900
+ }
5901
+ },
5902
  "node_modules/axobject-query": {
5903
  "version": "4.1.0",
5904
  "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
 
6431
  "node-int64": "^0.4.0"
6432
  }
6433
  },
6434
+ "node_modules/buffer-equal-constant-time": {
6435
+ "version": "1.0.1",
6436
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
6437
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
6438
+ "license": "BSD-3-Clause"
6439
+ },
6440
  "node_modules/buffer-from": {
6441
  "version": "1.1.2",
6442
  "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 
7847
  "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
7848
  "license": "MIT"
7849
  },
7850
+ "node_modules/ecdsa-sig-formatter": {
7851
+ "version": "1.0.11",
7852
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
7853
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
7854
+ "license": "Apache-2.0",
7855
+ "dependencies": {
7856
+ "safe-buffer": "^5.0.1"
7857
+ }
7858
+ },
7859
  "node_modules/ee-first": {
7860
  "version": "1.1.1",
7861
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
 
13231
  "jiti": "bin/jiti.js"
13232
  }
13233
  },
13234
+ "node_modules/js-cookie": {
13235
+ "version": "3.0.5",
13236
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
13237
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
13238
+ "license": "MIT",
13239
+ "engines": {
13240
+ "node": ">=14"
13241
+ }
13242
+ },
13243
  "node_modules/js-tokens": {
13244
  "version": "4.0.0",
13245
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 
13403
  "node": ">=0.10.0"
13404
  }
13405
  },
13406
+ "node_modules/jsonwebtoken": {
13407
+ "version": "9.0.2",
13408
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
13409
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
13410
+ "license": "MIT",
13411
+ "dependencies": {
13412
+ "jws": "^3.2.2",
13413
+ "lodash.includes": "^4.3.0",
13414
+ "lodash.isboolean": "^3.0.3",
13415
+ "lodash.isinteger": "^4.0.4",
13416
+ "lodash.isnumber": "^3.0.3",
13417
+ "lodash.isplainobject": "^4.0.6",
13418
+ "lodash.isstring": "^4.0.1",
13419
+ "lodash.once": "^4.0.0",
13420
+ "ms": "^2.1.1",
13421
+ "semver": "^7.5.4"
13422
+ },
13423
+ "engines": {
13424
+ "node": ">=12",
13425
+ "npm": ">=6"
13426
+ }
13427
+ },
13428
  "node_modules/jsx-ast-utils": {
13429
  "version": "3.3.5",
13430
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
 
13440
  "node": ">=4.0"
13441
  }
13442
  },
13443
+ "node_modules/jwa": {
13444
+ "version": "1.4.1",
13445
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
13446
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
13447
+ "license": "MIT",
13448
+ "dependencies": {
13449
+ "buffer-equal-constant-time": "1.0.1",
13450
+ "ecdsa-sig-formatter": "1.0.11",
13451
+ "safe-buffer": "^5.0.1"
13452
+ }
13453
+ },
13454
+ "node_modules/jws": {
13455
+ "version": "3.2.2",
13456
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
13457
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
13458
+ "license": "MIT",
13459
+ "dependencies": {
13460
+ "jwa": "^1.4.1",
13461
+ "safe-buffer": "^5.0.1"
13462
+ }
13463
+ },
13464
  "node_modules/keyv": {
13465
  "version": "4.5.4",
13466
  "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 
13609
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
13610
  "license": "MIT"
13611
  },
13612
+ "node_modules/lodash.includes": {
13613
+ "version": "4.3.0",
13614
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
13615
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
13616
+ "license": "MIT"
13617
+ },
13618
+ "node_modules/lodash.isboolean": {
13619
+ "version": "3.0.3",
13620
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
13621
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
13622
+ "license": "MIT"
13623
+ },
13624
+ "node_modules/lodash.isinteger": {
13625
+ "version": "4.0.4",
13626
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
13627
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
13628
+ "license": "MIT"
13629
+ },
13630
+ "node_modules/lodash.isnumber": {
13631
+ "version": "3.0.3",
13632
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
13633
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
13634
+ "license": "MIT"
13635
+ },
13636
+ "node_modules/lodash.isplainobject": {
13637
+ "version": "4.0.6",
13638
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
13639
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
13640
+ "license": "MIT"
13641
+ },
13642
+ "node_modules/lodash.isstring": {
13643
+ "version": "4.0.1",
13644
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
13645
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
13646
+ "license": "MIT"
13647
+ },
13648
  "node_modules/lodash.memoize": {
13649
  "version": "4.1.2",
13650
  "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
 
13657
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
13658
  "license": "MIT"
13659
  },
13660
+ "node_modules/lodash.once": {
13661
+ "version": "4.1.1",
13662
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
13663
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
13664
+ "license": "MIT"
13665
+ },
13666
  "node_modules/lodash.sortby": {
13667
  "version": "4.7.0",
13668
  "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
 
16093
  "node": ">= 0.10"
16094
  }
16095
  },
16096
+ "node_modules/proxy-from-env": {
16097
+ "version": "1.1.0",
16098
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
16099
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
16100
+ "license": "MIT"
16101
+ },
16102
  "node_modules/psl": {
16103
  "version": "1.9.0",
16104
  "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
 
16500
  "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
16501
  "license": "MIT"
16502
  },
16503
+ "node_modules/react-image-crop": {
16504
+ "version": "11.0.7",
16505
+ "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz",
16506
+ "integrity": "sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==",
16507
+ "license": "ISC",
16508
+ "peerDependencies": {
16509
+ "react": ">=16.13.1"
16510
+ }
16511
+ },
16512
  "node_modules/react-is": {
16513
  "version": "17.0.2",
16514
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
frontend/package.json CHANGED
@@ -6,10 +6,14 @@
6
  "@testing-library/jest-dom": "^5.17.0",
7
  "@testing-library/react": "^13.4.0",
8
  "@testing-library/user-event": "^13.5.0",
 
9
  "bootstrap": "^5.3.3",
 
 
10
  "react": "^18.3.1",
11
  "react-bootstrap": "^2.10.5",
12
  "react-dom": "^18.3.1",
 
13
  "react-router-dom": "^6.27.0",
14
  "react-scripts": "5.0.1",
15
  "validator": "^13.12.0",
 
6
  "@testing-library/jest-dom": "^5.17.0",
7
  "@testing-library/react": "^13.4.0",
8
  "@testing-library/user-event": "^13.5.0",
9
+ "axios": "^1.7.7",
10
  "bootstrap": "^5.3.3",
11
+ "js-cookie": "^3.0.5",
12
+ "jsonwebtoken": "^9.0.2",
13
  "react": "^18.3.1",
14
  "react-bootstrap": "^2.10.5",
15
  "react-dom": "^18.3.1",
16
+ "react-image-crop": "^11.0.7",
17
  "react-router-dom": "^6.27.0",
18
  "react-scripts": "5.0.1",
19
  "validator": "^13.12.0",
frontend/public/default_avatar.jpg ADDED
frontend/src/index.js CHANGED
@@ -10,6 +10,8 @@ import LoginPage from './pages/LoginPage';
10
  import RegisterPage from './pages/RegisterPage';
11
  import NewsPage from './pages/NewsPage';
12
  import MenuPage from './pages/MenuPage';
 
 
13
 
14
  const router = createBrowserRouter([
15
  {
@@ -35,6 +37,16 @@ const router = createBrowserRouter([
35
  {
36
  path: "/menu",
37
  element: <MenuPage/>
 
 
 
 
 
 
 
 
 
 
38
  }
39
  ]);
40
 
 
10
  import RegisterPage from './pages/RegisterPage';
11
  import NewsPage from './pages/NewsPage';
12
  import MenuPage from './pages/MenuPage';
13
+ import CartPage from './pages/CartPage';
14
+ import UserInfoPage from './pages/UserInfoPage';
15
 
16
  const router = createBrowserRouter([
17
  {
 
37
  {
38
  path: "/menu",
39
  element: <MenuPage/>
40
+ },
41
+ {
42
+ path: "/cart",
43
+ element: <CartPage/>,
44
+ errorElement: <ErrorPage/>
45
+ },
46
+ {
47
+ path: "/userinfo",
48
+ element: <UserInfoPage/>,
49
+ errorElement: <ErrorPage/>
50
  }
51
  ]);
52
 
frontend/src/molecules/AdminNavBar.js ADDED
File without changes
frontend/src/molecules/Navbar.js CHANGED
@@ -1,23 +1,23 @@
1
- import Container from 'react-bootstrap/Container';
2
- import Nav from 'react-bootstrap/Nav';
3
- import Navbar from 'react-bootstrap/Navbar';
4
- import Button from 'react-bootstrap/Button';
5
- import { Stack } from 'react-bootstrap';
6
  import { useNavigate } from 'react-router-dom';
 
7
 
8
  export default function ANavbar() {
9
 
10
  const navigate = useNavigate();
11
-
12
  function handleLogout() {
13
- sessionStorage.setItem('isLoggedIn','false');
14
- sessionStorage.removeItem('username');
15
- sessionStorage.removeItem('cart');
 
 
 
16
  navigate('/');
17
  }
18
 
19
- let username = sessionStorage.getItem('username');
20
- let isLoggedIn = sessionStorage.getItem('isLoggedIn');
21
 
22
  let userContent;
23
  if (isLoggedIn === 'true') {
@@ -26,7 +26,9 @@ export default function ANavbar() {
26
  <Button href="/userinfo" variant='primary'>
27
  Xin chào, {username}
28
  </Button>
29
- {' '}
 
 
30
  <Button onClick={handleLogout} variant='outline-primary'>
31
  Đăng xuất
32
  </Button>
 
1
+ import {Container, Nav, Navbar, Button, Stack} from 'react-bootstrap'
 
 
 
 
2
  import { useNavigate } from 'react-router-dom';
3
+ import DataStorage from '../organisms/DataStorage';
4
 
5
  export default function ANavbar() {
6
 
7
  const navigate = useNavigate();
8
+
9
  function handleLogout() {
10
+ DataStorage.set('isLoggedIn','false');
11
+ DataStorage.remove('accessToken');
12
+ DataStorage.remove('role');
13
+ DataStorage.remove('username');
14
+ DataStorage.remove('cart');
15
+ DataStorage.remove('expiryDate');
16
  navigate('/');
17
  }
18
 
19
+ let username = DataStorage.get('username');
20
+ let isLoggedIn = DataStorage.get('isLoggedIn');
21
 
22
  let userContent;
23
  if (isLoggedIn === 'true') {
 
26
  <Button href="/userinfo" variant='primary'>
27
  Xin chào, {username}
28
  </Button>
29
+ <Button href="/cart" variant='outline-primary'>
30
+ Giỏ hàng
31
+ </Button>
32
  <Button onClick={handleLogout} variant='outline-primary'>
33
  Đăng xuất
34
  </Button>
frontend/src/organisms/DataStorage.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Cookies from 'js-cookie';
2
+
3
+ export default class DataStorage {
4
+ static storageMethod = process.env.REACT_APP_STORAGE_METHOD;
5
+
6
+ // Get data
7
+ static get(key) {
8
+ if (this.storageMethod === 'session') {
9
+ return sessionStorage.getItem(key);
10
+ } else if (this.storageMethod === 'cookie') {
11
+ return Cookies.get(key);
12
+ }
13
+ return null;
14
+ }
15
+
16
+ // Set data
17
+ static set(key, value, param = { expiryDate: null }) {
18
+ if (this.storageMethod === 'session') {
19
+ sessionStorage.setItem(key, value);
20
+ } else if (this.storageMethod === 'cookie') {
21
+ if (param.expiryDate) {
22
+ const expiryDate = new Date(param.expiryDate * 1000);
23
+ Cookies.set(key, value, { expires: expiryDate })
24
+ } else if (Cookies.get('expiryDate')) {
25
+ const expiryDate = new Date(Cookies.get('expiryDate') * 1000);
26
+ Cookies.set(key, value, { expires: expiryDate });
27
+ } else Cookies.set(key, value, { expires: 7 })// Expires in 7 by default
28
+ }
29
+ }
30
+
31
+ // Remove data
32
+ static remove(key) {
33
+ if (this.storageMethod === 'session') {
34
+ sessionStorage.removeItem(key);
35
+ } else if (this.storageMethod === 'cookie') {
36
+ Cookies.remove(key);
37
+ }
38
+ }
39
+
40
+ // Get all data
41
+ static getAll() {
42
+ const data = {};
43
+ if (this.storageMethod === 'session') {
44
+ for (let i = 0; i < sessionStorage.length; i++) {
45
+ const key = sessionStorage.key(i);
46
+ data[key] = sessionStorage.getItem(key);
47
+ }
48
+ } else if (this.storageMethod === 'cookie') {
49
+ const cookies = Cookies.get();
50
+ Object.keys(cookies).forEach(key => {
51
+ data[key] = cookies[key];
52
+ });
53
+ }
54
+ return data;
55
+ }
56
+
57
+ // Clear all data
58
+ static clearAll() {
59
+ if (this.storageMethod === 'session') {
60
+ sessionStorage.clear();
61
+ } else if (this.storageMethod === 'cookie') {
62
+ const cookies = Cookies.get();
63
+ Object.keys(cookies).forEach(key => {
64
+ Cookies.remove(key);
65
+ });
66
+ }
67
+ }
68
+
69
+ // Check if a key exists
70
+ static hasKey(key) {
71
+ if (this.storageMethod === 'session') {
72
+ return sessionStorage.getItem(key) !== null;
73
+ } else if (this.storageMethod === 'cookie') {
74
+ return Cookies.get(key) !== undefined;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ // Get all keys
80
+ static getKeys() {
81
+ const keys = [];
82
+ if (this.storageMethod === 'session') {
83
+ for (let i = 0; i < sessionStorage.length; i++) {
84
+ keys.push(sessionStorage.key(i));
85
+ }
86
+ } else if (this.storageMethod === 'cookie') {
87
+ keys.push(...Object.keys(Cookies.get()));
88
+ }
89
+ return keys;
90
+ }
91
+ }
frontend/src/organisms/MenuSection.js CHANGED
@@ -1,5 +1,5 @@
1
  import { useState } from 'react';
2
- import { Container, Carousel, Row, Col } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
 
5
  function MenuSection() {
@@ -20,7 +20,7 @@ function MenuSection() {
20
  return (
21
  <Container id="menu" className="text-center justify-content-center align-items-center my-5">
22
  <h1 className='mb-5'>Menu</h1>
23
- <Carousel as='a' href='/menu' activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
24
  {Array.from({ length: countCarouselSlides }).map((_, slideIndex) => (
25
  <Carousel.Item key={slideIndex}>
26
  <div className="d-flex justify-content-center align-items-center" style={{ width: '100%' }}>
@@ -33,7 +33,7 @@ function MenuSection() {
33
  </div>
34
 
35
  <Carousel.Caption>
36
- <div >
37
  <Row md={3} className="g-4">
38
  {menuItems.map((item, idx) => (
39
  <Col key={idx}>
@@ -45,6 +45,7 @@ function MenuSection() {
45
  </Col>
46
  ))}
47
  </Row>
 
48
  </div>
49
  </Carousel.Caption>
50
  </Carousel.Item>
 
1
  import { useState } from 'react';
2
+ import { Container, Carousel, Row, Col, Button } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
 
5
  function MenuSection() {
 
20
  return (
21
  <Container id="menu" className="text-center justify-content-center align-items-center my-5">
22
  <h1 className='mb-5'>Menu</h1>
23
+ <Carousel activeIndex={index} onSelect={handleSelect} data-bs-theme="dark">
24
  {Array.from({ length: countCarouselSlides }).map((_, slideIndex) => (
25
  <Carousel.Item key={slideIndex}>
26
  <div className="d-flex justify-content-center align-items-center" style={{ width: '100%' }}>
 
33
  </div>
34
 
35
  <Carousel.Caption>
36
+ <div className='my-5'>
37
  <Row md={3} className="g-4">
38
  {menuItems.map((item, idx) => (
39
  <Col key={idx}>
 
45
  </Col>
46
  ))}
47
  </Row>
48
+ <Button as='a' href='/menu' className='mt-5'> Xem thêm các món </Button>
49
  </div>
50
  </Carousel.Caption>
51
  </Carousel.Item>
frontend/src/organisms/jwtDecoder.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function decodeBase64Url(base64Url) {
2
+ // Thay các ký tự theo chuẩn Base64 URL thành chuẩn Base64 thông thường
3
+ let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
4
+ // Thêm padding nếu thiếu
5
+ base64 += '='.repeat((4 - base64.length % 4) % 4);
6
+ // Giải mã từ Base64 sang chuỗi JSON
7
+ return JSON.parse(atob(base64));
8
+ }
9
+
10
+ export default function jwtDecoder(jwtToken) {
11
+ const [header, payload, _] = jwtToken.split('.');
12
+
13
+ // Giải mã Header và Payload
14
+ const decodedHeader = decodeBase64Url(header);
15
+ const decodedPayload = decodeBase64Url(payload);
16
+ console.log("Signature:", _);
17
+
18
+ console.log("Header:", decodedHeader);
19
+ console.log("Payload:", decodedPayload);
20
+ return {"header": decodedHeader, "payload": decodedPayload}
21
+ }
22
+
frontend/src/pages/AdminHomePage.js ADDED
File without changes
frontend/src/pages/CartPage.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BasicTemplate from "../templates/BasicTemplate";
2
+ import { Container, Row, Col, Card, Button } from "react-bootstrap";
3
+ import { useState, useEffect } from "react";
4
+ import DataStorage from "../organisms/DataStorage";
5
+
6
+ export default function CartPage() {
7
+
8
+ const [cartItems, setCartItems] = useState([]);
9
+
10
+ useEffect(() => {
11
+ // Lấy giỏ hàng từ sessionStorage
12
+ const cart = JSON.parse(DataStorage.get('cart')) || {};
13
+
14
+ // Chuyển cart thành mảng chứa các món có số lượng > 0
15
+ const items = Object.entries(cart)
16
+ .filter(([name, amount]) => amount > 0)
17
+ .map(([name, amount]) => ({
18
+ name,
19
+ imageSrc: '/placeholder3.jpg',
20
+ price: 100, // Thêm đơn giá của món ăn (giả sử ở đây là 100 cho mỗi món)
21
+ amount
22
+ }));
23
+
24
+ setCartItems(items);
25
+ }, []);
26
+
27
+
28
+ return (
29
+ <BasicTemplate content={
30
+ (
31
+ <Container className="d-flex align-items-center justify-content-center my-5" style={{ 'min-height': '70vh' }}>
32
+ {cartItems.length > 0 ? (
33
+ <div className="text-center">
34
+ <h2 className="text-center mb-4">Giỏ hàng của bạn</h2>
35
+ <Row className="g-3">
36
+ {cartItems.map((item, idx) => (
37
+ <Col md={12} key={idx} className="my-3">
38
+ <Card className="shadow-sm" style={{ display: 'flex', flexDirection: 'row' }}>
39
+ <Card.Img
40
+ variant="left"
41
+ src={item.imageSrc}
42
+ style={{ width: '150px', objectFit: 'cover' }}
43
+ />
44
+ <Card.Body>
45
+ <Row xs={4} className="align-items-center justify-content-center">
46
+ <Card.Title as='Col'>{item.name}</Card.Title>
47
+ <Card.Text as='Col'>Đơn giá: {item.price} VND</Card.Text>
48
+ <Card.Text as='Col'>Số lượng: {item.amount}</Card.Text>
49
+ <Card.Text as='Col'>
50
+ Tổng cộng: {item.price * item.amount} VND
51
+ </Card.Text>
52
+ </Row>
53
+ </Card.Body>
54
+ </Card>
55
+ </Col>
56
+ ))}
57
+ </Row>
58
+ <Button as='a' href='/payment' className='my-3'>
59
+ Thanh toán
60
+ </Button>
61
+ </div>
62
+ ) : (
63
+ <div className="text-center">
64
+ <p className="text-center my-3">Giỏ hàng của bạn hiện đang trống.</p>
65
+ <Button as='a' href='/menu'>Xem menu</Button>
66
+ </div>
67
+ )}
68
+ </Container>
69
+ )
70
+ } />
71
+ )
72
+ }
frontend/src/pages/LoginPage.js CHANGED
@@ -2,9 +2,14 @@ import { useState } from "react";
2
  import { Alert, Container, Form, Row, Col, Button, Card } from "react-bootstrap";
3
  import BasicTemplate from "../templates/BasicTemplate";
4
  import { useNavigate } from "react-router-dom";
 
 
 
5
 
6
  export default function LoginPage() {
7
 
 
 
8
  const [username, setUsername] = useState('');
9
  const [password, setPassword] = useState('');
10
  const [error, setError] = useState('');
@@ -16,16 +21,43 @@ export default function LoginPage() {
16
  // Validate password and confirm password match
17
  if (password.length === 0) {
18
  setError('Hãy nhập mật khẩu');
19
- } else if (username.length === 0) {
20
  setError('Tên đăng nhập không thể trống')
21
  } else {
22
  setError('');
23
- sessionStorage.setItem('username','testUser');
24
- sessionStorage.setItem('isLoggedIn','true');
25
- sessionStorage.setItem('cart','{}');
26
- // Xử lý submit form ở đây (ví dụ: gọi API đăng nhập)
27
- console.log('Đăng nhập thành công', { username, password });
28
- navigate('/');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
  };
31
 
 
2
  import { Alert, Container, Form, Row, Col, Button, Card } from "react-bootstrap";
3
  import BasicTemplate from "../templates/BasicTemplate";
4
  import { useNavigate } from "react-router-dom";
5
+ import axios from 'axios';
6
+ import jwtDecoder from "../organisms/jwtDecoder";
7
+ import DataStorage from "../organisms/DataStorage";
8
 
9
  export default function LoginPage() {
10
 
11
+ const domain = process.env.REACT_APP_API_URL;
12
+
13
  const [username, setUsername] = useState('');
14
  const [password, setPassword] = useState('');
15
  const [error, setError] = useState('');
 
21
  // Validate password and confirm password match
22
  if (password.length === 0) {
23
  setError('Hãy nhập mật khẩu');
24
+ } else if (username.trim().length === 0) {
25
  setError('Tên đăng nhập không thể trống')
26
  } else {
27
  setError('');
28
+
29
+ let data = {
30
+ 'username': username,
31
+ 'password': password
32
+ }
33
+
34
+ axios.post(domain + '/authentication/login', data)
35
+ .then((response) => {
36
+ if (response.status === 200) {
37
+ // setError(JSON.stringify(jwtDecoder(response.data.access_token)));
38
+ const decodedToken = jwtDecoder(response.data.access_token);
39
+ const full_name = decodedToken.payload.username;
40
+ const role = decodedToken.payload.roles;
41
+ const expiryTime = decodedToken.payload.exp;
42
+ DataStorage.set('expiryDate', expiryTime, {expiryDate:expiryTime});
43
+ DataStorage.set('username', full_name);
44
+ DataStorage.set('role', role);
45
+ DataStorage.set('isLoggedIn', 'true');
46
+ DataStorage.set('accessToken', response.data.access_token);
47
+ DataStorage.set('cart', '{}');
48
+ navigate('/');
49
+ } else {
50
+ setError(JSON.stringify(response));
51
+ }
52
+ })
53
+ .catch((error) => {
54
+ if (error.status === 400) {
55
+ setError('Lỗi đăng nhập, vui lòng kiểm tra lại tài khoản và mật khẩu');
56
+ } else if (error.status === 500) {
57
+ setError('Server đang tạm gặp vấn đề');
58
+ }
59
+ })
60
+
61
  }
62
  };
63
 
frontend/src/pages/MenuPage.js CHANGED
@@ -1,16 +1,55 @@
1
  import { useState } from 'react';
2
- import { Modal, Button, Container, Row, Col, Tab, Tabs } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
  import BasicTemplate from '../templates/BasicTemplate';
 
 
5
  function MenuPage() {
6
  const [key, setKey] = useState('cat1');
7
  const [selectedDish, setSelectedDish] = useState(null);
8
  const [show, setShow] = useState(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- const handleClose = () => setShow(false);
11
  function handleShow(dish) {
12
- setSelectedDish(dish); // Set the selected dish to state
13
- setShow(true); // Show the modal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
  const menuItems1 = [
@@ -35,23 +74,65 @@ function MenuPage() {
35
  { name: 'Món 6 thể loại 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
36
  ];
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  return <BasicTemplate content={(
39
  <Container fluid className='my-5'>
40
  <>
41
- <Modal show={show} onHide={handleClose}>
42
- <Modal.Header closeButton>
43
- <Modal.Title>{selectedDish?.name}</Modal.Title> {/* Dish name in the title */}
44
- </Modal.Header>
45
- <Modal.Body>
46
- <p>{selectedDish?.description}</p> {/* Dish description */}
47
- <img src={selectedDish?.imageSrc} alt={selectedDish?.name} style={{ width: '100%' }} /> {/* Dish image */}
48
- </Modal.Body>
49
- <Modal.Footer>
50
- <Button variant="secondary" onClick={handleClose}>
51
- Close
52
- </Button>
53
- </Modal.Footer>
54
- </Modal>
55
  </>
56
  <h1 className='text-center mb-5'>Thực đơn</h1>
57
  <Row>
 
1
  import { useState } from 'react';
2
+ import { Modal, Button, Container, Row, Col, Tab, Tabs, Form, InputGroup } from 'react-bootstrap';
3
  import MenuItem from '../molecules/MenuItem';
4
  import BasicTemplate from '../templates/BasicTemplate';
5
+ import DataStorage from '../organisms/DataStorage';
6
+
7
  function MenuPage() {
8
  const [key, setKey] = useState('cat1');
9
  const [selectedDish, setSelectedDish] = useState(null);
10
  const [show, setShow] = useState(false);
11
+ const [cartAmount, setCartAmount] = useState(0);
12
+
13
+ function handleClose() {
14
+ const cart = JSON.parse(DataStorage.get('cart')) || {}; // Đảm bảo cart không null
15
+ cart[selectedDish.name] = cartAmount;
16
+ const filteredCart = Object.fromEntries(
17
+ Object.entries(cart).filter(([key, value]) => value > 0)
18
+ );
19
+ DataStorage.set('cart', JSON.stringify(filteredCart));
20
+ // console.log(JSON.stringify(filteredCart));
21
+ setShow(false);
22
+ }
23
+
24
+ function notLoggedInClose() {
25
+ setShow(false);
26
+ }
27
 
 
28
  function handleShow(dish) {
29
+ if (DataStorage.get('isLoggedIn') === null) {
30
+ setShow(true);
31
+ return;
32
+ } else {
33
+ setSelectedDish(dish); // Đặt selectedDish ngay lập tức
34
+
35
+ // Sau khi cập nhật selectedDish, lấy dữ liệu từ cart theo tên món ăn mới
36
+ const cart = JSON.parse(DataStorage.get('cart')) || {};
37
+
38
+ // Kiểm tra số lượng món ăn trong cart
39
+ const amount = cart[dish.name] !== undefined ? cart[dish.name] : 0;
40
+ setCartAmount(amount); // Cập nhật số lượng
41
+ setShow(true); // Hiển thị modal
42
+ }
43
+ }
44
+
45
+ function setIncrease() {
46
+ setCartAmount(cartAmount + 1);
47
+ }
48
+
49
+ function setDecrease() {
50
+ if (cartAmount > 0) {
51
+ setCartAmount(cartAmount - 1);
52
+ }
53
  }
54
 
55
  const menuItems1 = [
 
74
  { name: 'Món 6 thể loại 3', description: 'Mô tả món 3', imageSrc: '/placeholder3.jpg' }
75
  ];
76
 
77
+ let modalContent;
78
+
79
+ if (DataStorage.get('isLoggedIn') === null) {
80
+ modalContent = (
81
+ <Modal show={show} onHide={notLoggedInClose} className='text-center align-items-center'>
82
+ <Modal.Header closeButton className='text-center'>
83
+ <Modal.Title>Bạn chưa đăng nhập</Modal.Title>
84
+ </Modal.Header>
85
+ <Modal.Body>
86
+ Vui lòng đăng nhập để xem chi tiết món và đặt hàng
87
+ </Modal.Body>
88
+ <Modal.Footer>
89
+ <Button variant="primary" as='a' href='/login'>
90
+ Đăng nhập
91
+ </Button>
92
+ <Button variant="secondary" onClick={notLoggedInClose}>
93
+ Đóng
94
+ </Button>
95
+ </Modal.Footer>
96
+ </Modal>
97
+ )
98
+ }
99
+ else {
100
+ modalContent = (<Modal show={show} onHide={handleClose} className='text-center'>
101
+ <Modal.Header closeButton className='text-center'>
102
+ <Modal.Title >{selectedDish?.name}</Modal.Title> {/* Dish name in the title */}
103
+ </Modal.Header>
104
+ <Modal.Body>
105
+ <img src={selectedDish?.imageSrc} alt={selectedDish?.name} style={{ width: '100%' }} /> {/* Dish image */}
106
+ <p>{selectedDish?.description}</p> {/* Dish description */}
107
+
108
+ <Row className='mb-5'>
109
+ <Col md={2}></Col>
110
+ <Col md={8}>
111
+ <Form.Label>Số lượng</Form.Label>
112
+ <InputGroup as='row' mb={4} className='mb-3'>
113
+ <InputGroup.Text as='button' onClick={setDecrease}>-</InputGroup.Text>
114
+ <Form.Control
115
+ value={cartAmount}
116
+ aria-label="Amount"
117
+ onChange={(e) => setCartAmount(e.target.value)} />
118
+ <InputGroup.Text as='button' onClick={setIncrease}>+</InputGroup.Text>
119
+ </InputGroup></Col>
120
+ <Col md={2}></Col>
121
+ </Row>
122
+
123
+ </Modal.Body>
124
+ <Modal.Footer>
125
+ <Button variant="secondary" onClick={handleClose}>
126
+ Đóng
127
+ </Button>
128
+ </Modal.Footer>
129
+ </Modal>);
130
+ }
131
+
132
  return <BasicTemplate content={(
133
  <Container fluid className='my-5'>
134
  <>
135
+ {modalContent}
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  </>
137
  <h1 className='text-center mb-5'>Thực đơn</h1>
138
  <Row>
frontend/src/pages/RegisterPage.js CHANGED
@@ -19,7 +19,7 @@ const RegisterPage = () => {
19
  // Validate password and confirm password match
20
  if (full_name.length === 0) {
21
  setError('Họ và tên không thể để trống');
22
- } else if (!validator.isMobilePhone(phone_number)) {
23
  setError('Số điện thoại không hợp lệ');
24
  } else if (!validator.isEmail(email)) {
25
  setError('Email không hợp lệ');
 
19
  // Validate password and confirm password match
20
  if (full_name.length === 0) {
21
  setError('Họ và tên không thể để trống');
22
+ } else if (!validator.isMobilePhone(phone_number, 'vi-VN')) {
23
  setError('Số điện thoại không hợp lệ');
24
  } else if (!validator.isEmail(email)) {
25
  setError('Email không hợp lệ');
frontend/src/pages/UserInfoPage.js ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BasicTemplate from "../templates/BasicTemplate";
2
+ import { Container, Form, Row, Col, Card, Alert, Button, Image } from "react-bootstrap";
3
+ import React, { useState, useEffect } from "react";
4
+ import validator from "validator";
5
+ import axios from "axios";
6
+ import DataStorage from "../organisms/DataStorage";
7
+
8
+ export default function UserInfoPage() {
9
+
10
+ // fetch data:
11
+
12
+ const [error, setErrors] = useState("");
13
+ const [initialData, setInitialData] = useState(null);
14
+ const [formData, setFormData] = useState({
15
+ avatar: "/default_avatar.jpg",
16
+ full_name: "",
17
+ phone_number: "",
18
+ address: "",
19
+ email: "",
20
+ password: "",
21
+ confirmPassword: "",
22
+ });
23
+
24
+ useEffect(() => {
25
+ axios
26
+ .get(process.env.REACT_APP_API_URL + '/authentication/profile', {
27
+ headers: {
28
+ Authorization: `Bearer ${DataStorage.get('accessToken')}`,
29
+ },
30
+ })
31
+ .then((response) => {
32
+ if (response.status === 401) {
33
+ // phiên hết hạn
34
+ setErrors(JSON.stringify(response));
35
+ } else {
36
+ const { avatar, full_name, address, phone_number, email } = response.data;
37
+ const fetchedData = {
38
+ avatar: avatar || "/default_avatar.jpg",
39
+ full_name: full_name || "",
40
+ phone_number: phone_number || "",
41
+ address: address || "",
42
+ email: email || "",
43
+ password: "",
44
+ confirmPassword: "",
45
+ };
46
+
47
+ if (fetchedData.full_name !== DataStorage.get('username')) DataStorage.set('username', fetchedData.full_name)
48
+ setInitialData(fetchedData);
49
+ setFormData(fetchedData);
50
+ }
51
+ })
52
+ .catch((error) => {
53
+ setErrors(JSON.stringify(error));
54
+ });
55
+ }, []);
56
+
57
+ // console.log('formdata', formData);
58
+ // console.log('initialdata', initialData);
59
+ // State để hiển thị lỗi khi validation
60
+
61
+ const [isChanged, setChanged] = useState(false);
62
+
63
+ const handleChange = (e) => {
64
+ const { name, value } = e.target;
65
+ setFormData({ ...formData, [name]: value });
66
+ };
67
+
68
+ // Hàm xử lý thay đổi avatar
69
+ const handleAvatarChange = (e) => {
70
+ const file = e.target.files[0];
71
+ if (file) {
72
+ const reader = new FileReader();
73
+ reader.onloadend = () => {
74
+ setFormData((prevData) => ({
75
+ ...prevData,
76
+ avatar: reader.result // Cập nhật ảnh đại diện
77
+ }));
78
+ };
79
+ reader.readAsDataURL(file);
80
+ setChanged(true);
81
+ }
82
+ };
83
+
84
+ useEffect(() => {
85
+ const hasChanges = () => {
86
+ if (initialData) {
87
+ for (let key in formData) {
88
+ if (formData[key] !== initialData[key]) {
89
+ return true;
90
+ }
91
+ }
92
+ return false;
93
+ } else {
94
+ return false;
95
+ }
96
+ };
97
+ setChanged(hasChanges());
98
+ }, [formData, initialData]);
99
+
100
+ const handleSubmit = async (e) => {
101
+ e.preventDefault();
102
+ setChanged(false); // prevent another click
103
+ if (formData.phone_number.trim() !== "" && !validator.isMobilePhone(formData.phone_number, 'vi-VN')) setErrors("Số điện thoại không hợp lệ");
104
+ else if (formData.email.trim() !== "" && !validator.isEmail(formData.email)) setErrors("Email không hợp lệ");
105
+ else if (formData.password !== "" && formData.confirmPassword !== formData.password) setErrors("Mật khẩu không khớp");
106
+ else {
107
+
108
+ // let avatarBase64 = null;
109
+
110
+ // if (formData.avatar) {
111
+ // // Chuyển ảnh đại diện thành chuỗi Base64
112
+ // avatarBase64 = await new Promise((resolve, reject) => {
113
+ // const reader = new FileReader();
114
+ // reader.onloadend = () => resolve(reader.result.split(",")[1]); // Chỉ lấy phần Base64
115
+ // reader.onerror = (error) => reject(error);
116
+ // reader.readAsDataURL(formData.avatar);
117
+ // });
118
+ // }
119
+
120
+ let data = {};
121
+
122
+ // if (avatarBase64) data.avatar = avatarBase64;
123
+ if (formData.full_name.trim() !== "" && formData.full_name.trim() !== initialData.full_name) data.full_name = formData.full_name.trim();
124
+ if (formData.phone_number.trim() !== "" && formData.phone_number.trim() !== initialData.phone_number) data.phone_number = formData.phone_number.trim();
125
+ if (formData.address.trim() !== "" && formData.address.trim() !== initialData.address) data.address = formData.address.trim();
126
+ if (formData.email.trim() !== "" && formData.email.trim() !== initialData.email) data.email = formData.email.trim();
127
+ if (formData.password.trim() !== "") data.hash_password = formData.password;
128
+
129
+ // gửi request ở đây
130
+ axios.post(process.env.REACT_APP_API_URL + '/users/updateUser', data, {
131
+ headers: {
132
+ Authorization: `Bearer ${DataStorage.get('accessToken')}`,
133
+ }
134
+ }).then((response) => {
135
+ if (response.status === 200 || response.status === 201) {
136
+ window.location.reload(); // cập nhật lại thông tin
137
+ } else {
138
+ setErrors(JSON.stringify(response));
139
+ }
140
+ }).catch((error) => setErrors(JSON.stringify(error)));
141
+ }
142
+ };
143
+
144
+ return <BasicTemplate content={
145
+ (
146
+ <Container fluid className="d-flex align-items-center justify-content-center mt-5">
147
+ <Row>
148
+ <Col xs={1} md={2}></Col>
149
+ <Col xs={10} md={8}>
150
+ <Card style={{ width: '30rem' }} className='justify-content-center'>
151
+ <Card.Header>
152
+ <Card.Title className='mt-1 text-center'>Thông tin khách hàng</Card.Title>
153
+ </Card.Header>
154
+ <Card.Body>
155
+
156
+ <Form onSubmit={handleSubmit}>
157
+ {error && <Alert variant="danger">{error}</Alert>}
158
+ <Row className="mb-3">
159
+ <Col xs={12} md={6} className="text-center">
160
+ {/*Image*/}
161
+ <Image
162
+ src={formData.avatar} // Hiển thị ảnh đại diện từ formData
163
+ alt="User Avatar"
164
+ roundedCircle
165
+ width={120}
166
+ height={120}
167
+ />
168
+ <Form.Group controlId="formAvatar">
169
+ <Form.Label>Ảnh đại diện</Form.Label>
170
+ <Form.Control type="file" accept="image/*" onChange={handleAvatarChange} />
171
+ </Form.Group>
172
+ </Col>
173
+ <Col xs={12} md={6}>
174
+ <Form.Group controlId="formUsername">
175
+ <Form.Label>Họ tên người dùng</Form.Label>
176
+ <Form.Control
177
+ type="text"
178
+ name="full_name"
179
+ placeholder="Họ tên người dùng"
180
+ value={formData.full_name}
181
+ onChange={handleChange}
182
+ />
183
+ </Form.Group>
184
+ </Col>
185
+ </Row>
186
+
187
+ <Form.Group controlId="formPhone" className="mb-3">
188
+ <Form.Label>Số điện thoại</Form.Label>
189
+ <Form.Control
190
+ type="text"
191
+ name="phone_number"
192
+ placeholder="Số điện thoại"
193
+ value={formData.phone_number}
194
+ onChange={handleChange}
195
+ />
196
+ </Form.Group>
197
+
198
+ <Form.Group controlId="formAddress" className="mb-3">
199
+ <Form.Label>Địa chỉ giao hàng mặc định</Form.Label>
200
+ <Form.Control
201
+ type="text"
202
+ name="address"
203
+ placeholder="Địa chỉ giao hàng"
204
+ value={formData.address}
205
+ onChange={handleChange}
206
+ />
207
+ </Form.Group>
208
+
209
+ <Form.Group controlId="formEmail" className="mb-3">
210
+ <Form.Label>Email</Form.Label>
211
+ <Form.Control
212
+ type="text"
213
+ name="email"
214
+ placeholder="Email"
215
+ value={formData.email}
216
+ onChange={handleChange}
217
+ />
218
+ </Form.Group>
219
+
220
+ <Form.Group controlId="formPassword" className="mb-3">
221
+ <Form.Label>Password</Form.Label>
222
+ <Form.Control
223
+ type="password"
224
+ name="password"
225
+ placeholder="********"
226
+ value={formData.password}
227
+ onChange={handleChange}
228
+ />
229
+ </Form.Group>
230
+
231
+ <Form.Group controlId="formConfirmPassword" className="mb-3">
232
+ <Form.Label>Nhập lại Password</Form.Label>
233
+ <Form.Control
234
+ type="password"
235
+ name="confirmPassword"
236
+ placeholder="********"
237
+ value={formData.confirmPassword}
238
+ onChange={handleChange}
239
+ />
240
+ </Form.Group>
241
+
242
+ <Button variant="primary" type="submit" disabled={!isChanged}>
243
+ Cập nhật
244
+ </Button>
245
+ </Form>
246
+ </Card.Body>
247
+ </Card>
248
+
249
+ </Col>
250
+ <Col xs={1} md={2}></Col>
251
+ </Row>
252
+
253
+
254
+ </Container>
255
+ )
256
+ } />
257
+ }