From 2995c3f75efd9dd4cd9f7f32deb205db80d9cbfa Mon Sep 17 00:00:00 2001 From: Nathan Blenke Date: Tue, 12 Mar 2024 14:30:04 -0400 Subject: [PATCH] feat: session persistence after refresh [27] --- __tests__/components/dashboard.test.tsx | 16 -- __tests__/pages/dashboard.test.tsx | 12 +- components/dashboard.tsx | 248 ++++++++++++------------ components/login-wrapper.tsx | 20 ++ components/menu.tsx | 2 +- hooks/useAuthHandlers.ts | 7 +- hooks/useOpenLoginSession.ts | 21 ++ pages/_app.tsx | 17 +- pages/dashboard.tsx | 63 +++--- pages/index.tsx | 4 +- 10 files changed, 231 insertions(+), 179 deletions(-) create mode 100644 components/login-wrapper.tsx create mode 100644 hooks/useOpenLoginSession.ts diff --git a/__tests__/components/dashboard.test.tsx b/__tests__/components/dashboard.test.tsx index 35617bd..1000511 100644 --- a/__tests__/components/dashboard.test.tsx +++ b/__tests__/components/dashboard.test.tsx @@ -56,22 +56,6 @@ describe("Dashboard", () => { const { container } = render() expect(container.querySelector(".chakra-spinner")).toBeInTheDocument() }) - it("renders error if no data", () => { - useFetchMetas.mockReturnValue([ - { - data: null, - isLoading: false, - refetch: jest.fn(), - }, - 0, - 0, - jest.fn(), - ]) - render() - expect( - screen.getByText("Error fetching deployments. Is the backend online?"), - ).toBeInTheDocument() - }) it("renders creact deployment button if logged in", () => { useFetchMetas.mockReturnValue([ { diff --git a/__tests__/pages/dashboard.test.tsx b/__tests__/pages/dashboard.test.tsx index 7ad369e..54c1ed0 100644 --- a/__tests__/pages/dashboard.test.tsx +++ b/__tests__/pages/dashboard.test.tsx @@ -1,19 +1,21 @@ // @ts-nocheck import "@testing-library/jest-dom" -import { render, screen } from "@testing-library/react" +import { render, screen, waitFor } from "@testing-library/react" import Dashboard from "../../pages/dashboard" import useWeb3AuthStore from "../../store/web3-auth" jest.mock("../../store/web3-auth", () => jest.fn()) describe("Dashboard", () => { - it("renders login message if not connected", () => { + it("renders login message if not connected", async () => { useWeb3AuthStore.mockReturnValue({ // provider: null, isConnected: () => false, }) render() - expect( - screen.getByText("Please login to view this page"), - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByText("Please login to view this page"), + ).toBeInTheDocument() + }) }) }) diff --git a/components/dashboard.tsx b/components/dashboard.tsx index 48095bf..b4238ea 100644 --- a/components/dashboard.tsx +++ b/components/dashboard.tsx @@ -3,6 +3,7 @@ import { Box, Button, Center, + Flex, Icon, Table, Thead, @@ -15,7 +16,7 @@ import { Spinner, HStack, Tooltip, - useTheme + useTheme, } from "@chakra-ui/react" import { ArrowBackIcon, @@ -32,9 +33,7 @@ import { GHOSTCLOUD_URL_DOMAIN, GHOSTCLOUD_URL_SCHEME, } from "../config/ghostcloud-chain" -import { - GHOSTCLOUD_INFRA_LOADBALANCER_IP -} from "../config/ghostcloud-infra" +import { GHOSTCLOUD_INFRA_LOADBALANCER_IP } from "../config/ghostcloud-infra" import useWeb3AuthStore from "../store/web3-auth" import { truncateAddress } from "../helpers/address" import { FaInfoCircle } from "react-icons/fa" @@ -87,131 +86,130 @@ const Dashboard = () => { } } + if (isMetaLoading || !metas) { + return ( + + + + ) + } + return ( - {isMetaLoading ? : null} - {metas ? ( - <> - - { - refetchMetas() - setIsCreateModalOpen(false) - }} - /> - setIsUpdateModalOpen(false)} - deploymentName={selectedDeploymentName} - deploymentDescription={selectedDeploymentDescription} - deploymentDomain={selectedDeploymentDomain} - /> - { - refetchMetas() - setIsRemoveModalOpen(false) - }} - deploymentName={selectedDeploymentName} - /> - - - - - - - - - - - - {metas.meta.map((meta, index) => ( - - - - - + + + + ))} + +
NameDescriptionDomainURLActions
{meta.name}{meta.description} - {meta.domain && ( - <> - {meta.domain} - - - - - - - )} - - setIsCreateModalOpen(true)} float="right" mb={2}> + Create Deployment + + { + refetchMetas() + setIsCreateModalOpen(false) + }} + /> + setIsUpdateModalOpen(false)} + deploymentName={selectedDeploymentName} + deploymentDescription={selectedDeploymentDescription} + deploymentDomain={selectedDeploymentDomain} + /> + { + refetchMetas() + setIsRemoveModalOpen(false) + }} + deploymentName={selectedDeploymentName} + /> + + + + + + + + + + + + {metas.meta.map((meta, index) => ( + + + + - - - ))} - -
NameDescriptionDomainURLActions
{meta.name}{meta.description} + {meta.domain && ( + <> + {meta.domain} + - {createUrl(meta.name, truncateAddress(address, 4))} - - - - - handleUpdate(meta.name, meta.description, meta.domain) - } - aria-label="Update" - icon={} - size="sm" - mr={2} - > - Update - - handleRemove(meta.name)} - aria-label="Remove" - icon={} - size="sm" - > - Remove - - -
+ + + + + + )} +
+ + {createUrl(meta.name, truncateAddress(address, 4))} + + + + + handleUpdate(meta.name, meta.description, meta.domain) + } + aria-label="Update" + icon={} + size="sm" + mr={2} + > + Update + + handleRemove(meta.name)} + aria-label="Remove" + icon={} + size="sm" + > + Remove + + +
- {pageCount > 1 && ( -
- } - aria-label="Previous" - isDisabled={currentPage === 1} - onClick={() => handlePageClick("prev")} - data-testid="previous-button" - /> - - {currentPage} / {pageCount} - - } - aria-label="Next" - isDisabled={currentPage === pageCount} - onClick={() => handlePageClick("next")} - data-testid="next-button" - /> -
- )} - - ) : ( -
Error fetching deployments. Is the backend online?
+ {pageCount > 1 && ( +
+ } + aria-label="Previous" + isDisabled={currentPage === 1} + onClick={() => handlePageClick("prev")} + data-testid="previous-button" + /> + + {currentPage} / {pageCount} + + } + aria-label="Next" + isDisabled={currentPage === pageCount} + onClick={() => handlePageClick("next")} + data-testid="next-button" + /> +
)}
) diff --git a/components/login-wrapper.tsx b/components/login-wrapper.tsx new file mode 100644 index 0000000..c7762de --- /dev/null +++ b/components/login-wrapper.tsx @@ -0,0 +1,20 @@ +import React from "react" +import useAuthHandlers from "../hooks/useAuthHandlers" +import useOpenLoginSession from "../hooks/useOpenLoginSession" + +const LoginWrapper = ({ children }: { children: React.ReactNode }) => { + const { handleLogin } = useAuthHandlers() + const [hasLoginAttempt, setHasLoginAttempt] = React.useState(false) + const hasSession = useOpenLoginSession() + + React.useEffect(() => { + if (hasSession && !hasLoginAttempt) { + handleLogin({}, false) + setHasLoginAttempt(true) + } + }, [handleLogin, hasLoginAttempt, hasSession]) + + return <>{children} +} + +export default LoginWrapper diff --git a/components/menu.tsx b/components/menu.tsx index fdc8947..a40b541 100644 --- a/components/menu.tsx +++ b/components/menu.tsx @@ -38,7 +38,7 @@ const Menu: React.FC = () => { Logout ) : ( - Login + handleLogin(ev, true)}>Login )} diff --git a/hooks/useAuthHandlers.ts b/hooks/useAuthHandlers.ts index e307fd0..0ae07c6 100644 --- a/hooks/useAuthHandlers.ts +++ b/hooks/useAuthHandlers.ts @@ -17,7 +17,10 @@ export default function useAuthHandlers() { // Handle the login to Web3Auth. // Redirect to the dashboard if the user is already connected. - const handleLogin = async () => { + const handleLogin = async ( + ev: any, + redirectToDashboard: boolean | null = null, + ) => { if (!store.isConnected()) { const uiConfig = { ...GHOSTCLOUD_UI_CONFIG, @@ -36,7 +39,7 @@ export default function useAuthHandlers() { }) } } - await router.push("/dashboard") + redirectToDashboard && (await router.push("/dashboard")) } // Handle the logout from Web3Auth. diff --git a/hooks/useOpenLoginSession.ts b/hooks/useOpenLoginSession.ts new file mode 100644 index 0000000..8e06958 --- /dev/null +++ b/hooks/useOpenLoginSession.ts @@ -0,0 +1,21 @@ +import { useState, useEffect } from "react" +import useWeb3AuthStore from "../store/web3-auth" + +function useOpenLoginSession() { + const store = useWeb3AuthStore() + const [hasSession, setHasSession] = useState(false) + + useEffect(() => { + const openLoginStore = window.localStorage.getItem("openlogin_store") + if (!openLoginStore) { + setHasSession(false) + return + } + const { sessionId } = JSON.parse(openLoginStore) + setHasSession(!!sessionId) + }, [store]) + + return hasSession +} + +export default useOpenLoginSession diff --git a/pages/_app.tsx b/pages/_app.tsx index 2feb0f2..3c7e09e 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -5,19 +5,22 @@ import { defaultTheme } from "../config" import "@interchain-ui/react/styles" import Header from "../components/header" import Footer from "../components/footer" +import LoginWrapper from "../components/login-wrapper" import { QueryClient, QueryClientProvider } from "react-query" const queryClient = new QueryClient() function CreateCosmosApp({ Component, pageProps }: AppProps) { return ( - - -
- -