From 80f226c296e6a041cd88914146374a895095c31a Mon Sep 17 00:00:00 2001 From: sergesoroka Date: Tue, 15 Oct 2024 09:05:17 +0300 Subject: [PATCH] Added Login/Logout func and buttons --- package-lock.json | 78 ++++++++++++++++++++--- package.json | 2 + src/DialogComp/styles.css | 25 ++++++++ src/MenuBar/LogoBar.tsx | 54 +++++++++++----- src/MenuBar/TopMenuBar.tsx | 4 +- src/StartScreenComp/StartScreenComp.css | 29 ++++++++- src/StartScreenComp/StartScreenComp.tsx | 84 +++++++++++++++++++++++-- src/main.jsx | 24 +++++-- src/pages/HomePage.tsx | 2 - src/utils/keycloak.js | 14 +++++ 10 files changed, 278 insertions(+), 38 deletions(-) create mode 100644 src/utils/keycloak.js diff --git a/package-lock.json b/package-lock.json index e7ec552..47d96c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-slot": "^1.0.2", + "@react-keycloak/web": "^3.4.0", "@tanstack/react-table": "^8.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -20,6 +21,7 @@ "framer-motion": "^10.18.0", "jquery": "^3.7.1", "json-format-highlight": "^1.0.4", + "keycloak-js": "^26.0.0", "lucide-react": "^0.350.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1687,6 +1689,48 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@react-keycloak/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@react-keycloak/core/-/core-3.2.0.tgz", + "integrity": "sha512-1yzU7gQzs+6E1v6hGqxy0Q+kpMHg9sEcke2yxZR29WoU8KNE8E50xS6UbI8N7rWsgyYw8r9W1cUPCOF48MYjzw==", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.2.0" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/reactkeycloak" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@react-keycloak/web": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@react-keycloak/web/-/web-3.4.0.tgz", + "integrity": "sha512-yKKSCyqBtn7dt+VckYOW1IM5NW999pPkxDZOXqJ6dfXPXstYhOQCkTZqh8l7UL14PkpsoaHDh7hSJH8whah01g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.0", + "@react-keycloak/core": "^3.2.0", + "hoist-non-react-statics": "^3.3.2" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/reactkeycloak" + }, + "peerDependencies": { + "keycloak-js": ">=9.0.2", + "react": ">=16.8", + "react-dom": ">=16.8", + "typescript": ">=3.8" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", @@ -1949,13 +1993,13 @@ "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.45", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1966,7 +2010,7 @@ "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -1975,7 +2019,7 @@ "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "devOptional": true }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", @@ -2459,7 +2503,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/debug": { "version": "4.3.4", @@ -3555,6 +3599,15 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -4229,6 +4282,12 @@ "node": ">=4.0" } }, + "node_modules/keycloak-js": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.0.0.tgz", + "integrity": "sha512-uUvoc6luDuAOQ74i4/wKm1tGduYcJ/2jSh29Krekkt+ytn1mLL+mHug27FfpwgxcTb6yx+uL0OcehXwl3WIRMQ==", + "license": "Apache-2.0" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4806,11 +4865,16 @@ "react": "^18.2.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-refresh": { "version": "0.14.0", diff --git a/package.json b/package.json index 0fa9cc6..793585f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-slot": "^1.0.2", + "@react-keycloak/web": "^3.4.0", "@tanstack/react-table": "^8.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -24,6 +25,7 @@ "framer-motion": "^10.18.0", "jquery": "^3.7.1", "json-format-highlight": "^1.0.4", + "keycloak-js": "^26.0.0", "lucide-react": "^0.350.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/DialogComp/styles.css b/src/DialogComp/styles.css index 3519d56..5b392d5 100644 --- a/src/DialogComp/styles.css +++ b/src/DialogComp/styles.css @@ -137,6 +137,31 @@ button[disabled] { color: #919191; } +.buttonLogin { + display: flex; + align-items: center; + justify-content: space-between; + gap: 9px; + padding: 0.3rem 1rem; + border: 1px solid #def4ff !important; + background: #fff; + color: #282828; + white-space: nowrap; + border-radius: 6px; +} +.buttonLogin:hover { + display: flex; + align-items: center; + justify-content: space-between; + gap: 9px; + padding: 0.3rem 1rem; + border: 1px solid #def4ff !important; + background: #def4ff !important; + color: #282828; + white-space: nowrap; + border-radius: 6px; +} + .IconButton { font-family: inherit; border-radius: 100%; diff --git a/src/MenuBar/LogoBar.tsx b/src/MenuBar/LogoBar.tsx index 9a879d0..a5a896a 100644 --- a/src/MenuBar/LogoBar.tsx +++ b/src/MenuBar/LogoBar.tsx @@ -1,12 +1,10 @@ import React, { useState } from "react"; -import { Navigate } from "react-router-dom"; import "./Header.css"; -import PreferencesDialog from "@/DialogComp/PreferencesDialog"; +import { useKeycloak } from "@react-keycloak/web"; -import { useSetUuid, useSetIsShosen } from "../store/store"; import { Link } from "react-router-dom"; -import Button from "@/ui/Button"; +import { useSetIsShosen, useSetUuid } from "../store/store"; export default function LogoBar({ startScreen, uuid, setProjectID }) { const setUUID = useSetUuid(); @@ -14,6 +12,20 @@ export default function LogoBar({ startScreen, uuid, setProjectID }) { const [projectName, setProjectName] = useState(() => localStorage.getItem("project") ); + + const { keycloak } = useKeycloak(); + + const username = keycloak.tokenParsed?.preferred_username + ? keycloak.tokenParsed?.preferred_username + : localStorage.getItem("username"); + + const stored_token = localStorage.getItem("token"); + + const logoutHandle = () => { + localStorage.removeItem("username"); + localStorage.removeItem("token"); + localStorage.removeItem("refreshToken"); + }; return (
@@ -32,19 +44,31 @@ export default function LogoBar({ startScreen, uuid, setProjectID }) {
-
- {projectName ? ( -
- Project: - {projectName} -
+
+
+ {keycloak.authenticated || username ? username : ""} +
+ {keycloak.authenticated || stored_token ? ( + ) : ( -

Project is not selected

+ )} -
); diff --git a/src/MenuBar/TopMenuBar.tsx b/src/MenuBar/TopMenuBar.tsx index 60fa6cf..af1e3b7 100644 --- a/src/MenuBar/TopMenuBar.tsx +++ b/src/MenuBar/TopMenuBar.tsx @@ -6,9 +6,9 @@ import "./Header.css"; import config from "../utils/config"; -import { useIntermediateData, useUuid, useProjectID } from "../store/store"; +import { useIntermediateData, useProjectID, useUuid } from "../store/store"; -import { postRequestUUID, downloadFile } from "../lib/request"; +import { downloadFile, postRequestUUID } from "../lib/request"; export default function TopMenuBar() { const [copied, setCopied] = useState(false); diff --git a/src/StartScreenComp/StartScreenComp.css b/src/StartScreenComp/StartScreenComp.css index 85e6388..d9d2723 100644 --- a/src/StartScreenComp/StartScreenComp.css +++ b/src/StartScreenComp/StartScreenComp.css @@ -3,11 +3,23 @@ background-color: #fff; padding: 0 2rem; } +.description { + line-height: 1.6rem; +} .descriptionNew { - margin: 2rem; + margin: 1rem 0 2rem 2rem; display: flex; gap: 60px; - align-items: flex-start; + align-items: center; + gap: 120px; + justify-content: space-between; +} + +.topBarAuth { + /* display: flex; */ + justify-content: space-between; + align-items: center; + padding-top: 12px; } .underDev { @@ -19,6 +31,19 @@ padding: 0.3rem 0.8rem; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; + margin-top: -30px; +} +.userInfo { + display: flex; + align-items: center; + gap: 12px; +} + +.username { + color: #6d6d6d; + font-size: 14px; + font-weight: 600; + text-transform: capitalize; } .createNewBtn { diff --git a/src/StartScreenComp/StartScreenComp.tsx b/src/StartScreenComp/StartScreenComp.tsx index 4bf7ab9..4bafbc1 100644 --- a/src/StartScreenComp/StartScreenComp.tsx +++ b/src/StartScreenComp/StartScreenComp.tsx @@ -1,7 +1,8 @@ // @ts-nocheck -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Navigate } from "react-router-dom"; import MakeCopyDialog from "../DialogComp/MakeCopyDialog"; +import PreferencesDialog from "@/DialogComp/PreferencesDialog"; import LogoBar from "../MenuBar/LogoBar"; import Button from "../ui/Button"; @@ -10,6 +11,8 @@ import CreateNewDialog from "./../DialogComp/CreateNewDialog"; import TemplateTable from "@/DataTable/TemplateTable"; +import { useKeycloak } from "@react-keycloak/web"; + import { Link } from "react-router-dom"; import useSWR from "swr"; @@ -42,6 +45,51 @@ export default function StartScreenComp({}) { const idShosen = useIsShosen(); const setIdShosen = useSetIsShosen(); + const { keycloak } = useKeycloak(); + + useEffect(() => { + if (keycloak.authenticated) { + localStorage.setItem("refreshToken", keycloak.refreshToken); + localStorage.setItem("token", keycloak.token); + localStorage.setItem("username", keycloak.tokenParsed.preferred_username); + } + }, [keycloak.authenticated]); + + useEffect(() => { + const interval = setInterval(() => { + keycloak + .updateToken(30) + .then((refreshed) => { + if (refreshed) { + console.log("app: Token refreshed and updated in localStorage."); + localStorage.setItem("token", keycloak.token); + } else { + console.log("app: Token is still valid."); + } + }) + .catch(() => { + localStorage.removeItem("token"); + localStorage.removeItem("username"); + + console.error("app: Failed to refresh token."); + }); + }, 10000); + + return () => clearInterval(interval); + }, []); + + const username = keycloak.tokenParsed?.preferred_username + ? keycloak.tokenParsed?.preferred_username + : localStorage.getItem("username"); + + const stored_token = localStorage.getItem("token"); + + const logoutHandle = () => { + localStorage.removeItem("username"); + localStorage.removeItem("token"); + localStorage.removeItem("refreshToken"); + }; + const apiUrl = config.apiUrl; const templateURL = `${apiUrl}/${idShosen}?format=xlsx&project=${projectID}`; @@ -61,6 +109,10 @@ export default function StartScreenComp({}) { } }); + const [projectName, setProjectName] = useState(() => + localStorage.getItem("project") + ); + const dowloadXLS = () => { idShosen && downloadFile(idShosen, templateURL); }; @@ -83,10 +135,32 @@ export default function StartScreenComp({}) { const storageItemKey = "my-survey"; return (
-

- The Template Designer App is under development right now -

- +
+ +
+ +
+ {projectName ? ( +
+ Project: + {projectName} +
+ ) : ( +

Project is not selected

+ )} + +

diff --git a/src/main.jsx b/src/main.jsx index fac175e..e623561 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -6,6 +6,10 @@ import TemplatePage from "./pages/TemplatePage.tsx"; import WizardPage from "./pages/WizardPage.tsx"; import PreferencesPage from "./pages/PreferencesPage.tsx"; +import { ReactKeycloakProvider } from "@react-keycloak/web"; + +import _kc from "./utils/keycloak.js"; + import "./index.css"; const router = createBrowserRouter( @@ -16,7 +20,7 @@ const router = createBrowserRouter( }, { - path: "/template/:templateId", + path: "/template/:templateId/", Component: TemplatePage, }, { @@ -28,11 +32,21 @@ const router = createBrowserRouter( Component: PreferencesPage, }, ], - { basename: "/templates" } + { basename: "/templates/" } ); ReactDOM.createRoot(document.getElementById("root")).render( - - - + + + + + ); diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 8fda451..a0b6f7b 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -15,8 +15,6 @@ export default function HomePage() { localStorage.getItem("projectID") ); - // console.log("Home", prID, localStorage.getItem("projectID")); - const location = useLocation(); const queryParams = new URLSearchParams(location.search); const uuidParams = queryParams.get("uuid"); diff --git a/src/utils/keycloak.js b/src/utils/keycloak.js new file mode 100644 index 0000000..4c9f319 --- /dev/null +++ b/src/utils/keycloak.js @@ -0,0 +1,14 @@ +import Keycloak from "keycloak-js"; + +const _kc = new Keycloak({ + realm: "nano", + url: "https://iam.ideaconsult.net/auth", + "ssl-required": "external", + resource: "idea-ui", + "public-client": true, + "confidential-port": 0, + clientId: "idea-ui", + tokenStore: "localStorage ", +}); + +export default _kc;