diff --git a/frontend/compositions/dashboard-header/dashboard-header.stories.tsx b/frontend/compositions/dashboard-header/dashboard-header.stories.tsx index 8958fdbd..1c0ee1cc 100644 --- a/frontend/compositions/dashboard-header/dashboard-header.stories.tsx +++ b/frontend/compositions/dashboard-header/dashboard-header.stories.tsx @@ -1,5 +1,5 @@ import React from "react" -import { StoryFn, Meta } from "@storybook/react" +import { Story, Meta } from "@storybook/react" import { DashboardHeader } from ".." export default { @@ -7,7 +7,7 @@ export default { component: DashboardHeader } as Meta -const Template: StoryFn = (args: any) => +const Template: Story = (args: any) => export const Default = Template.bind({}) Default.parameters = { diff --git a/frontend/compositions/profile-nav/index.tsx b/frontend/compositions/profile-nav/index.tsx index c4d6ddeb..037bbb60 100644 --- a/frontend/compositions/profile-nav/index.tsx +++ b/frontend/compositions/profile-nav/index.tsx @@ -1,10 +1,9 @@ -import * as React from "react" import { ProfileMenu, profileMenuItems } from "../../models/profile" import styles from "./profile-nav.module.css" interface ProfileNavProps { activePage: ProfileMenu - setActivePage: React.Dispatch> + setActivePage: (newTab: ProfileMenu) => void } export default function ProfileNav({ activePage, setActivePage }: ProfileNavProps) { diff --git a/frontend/helpers/auth.tsx b/frontend/helpers/auth.tsx index 90366b4e..8acf99fb 100644 --- a/frontend/helpers/auth.tsx +++ b/frontend/helpers/auth.tsx @@ -30,8 +30,8 @@ export function useAuth() { /** * Renders the given component if authenticated, otherwise redirects to login. */ -export function requireAuth(Component: () => JSX.Element) { - return function ProtectedRoute() { +export function requireAuth(Component: (props: T) => JSX.Element) { + return function ProtectedRoute(props: T) { const { user } = useAuth() const router = useRouter() const login = "/login" @@ -42,7 +42,7 @@ export function requireAuth(Component: () => JSX.Element) { } }) - return user ? : null + return user ? : null } } diff --git a/frontend/helpers/hooks/useProfileTab.ts b/frontend/helpers/hooks/useProfileTab.ts new file mode 100644 index 00000000..3975990b --- /dev/null +++ b/frontend/helpers/hooks/useProfileTab.ts @@ -0,0 +1,34 @@ +import { useRouter } from "next/router" +import { useEffect, useState } from "react" +import { ProfileMenu } from "../../models/profile" + +export function useProfileTab(initialTab: ProfileMenu) { + const router = useRouter() + const [activeTab, setActiveTab] = useState(initialTab) + + useEffect(() => { + const initialTab = router.query.tab as ProfileMenu + if (Object.values(ProfileMenu).includes(initialTab)) { + setActiveTab(initialTab) + } + + const handleRouteChange = () => { + const newTab = router.query.tab as ProfileMenu + if (Object.values(ProfileMenu).includes(newTab)) { + setActiveTab(newTab) + } + } + + router.events.on("routeChangeComplete", handleRouteChange) + + return () => { + router.events.off("routeChangeComplete", handleRouteChange) + } + }, [router]) + + const handleTabChange = (newTab: ProfileMenu) => { + router.push(`?tab=${newTab}`) + } + + return [activeTab, handleTabChange] as [ProfileMenu, (newTab: ProfileMenu) => void] +} diff --git a/frontend/package.json b/frontend/package.json index 948b2ddb..e746a9ab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,10 +4,11 @@ "private": true, "scripts": { "dev": "next dev", - "build:dev": "env-cmd -f .env.development next build && next export", - "build:stage": "env-cmd -f .env.staging next build && next export", - "build:prod": "env-cmd -f .env.production next build && next export", - "build:ui": "env-cmd -f .env.ui next build && next export", + "build:dev": "env-cmd -f .env.development next build", + "build:stage": "env-cmd -f .env.staging next build", + "build:prod": "env-cmd -f .env.production next build", + "build:ui": "env-cmd -f .env.ui next build", + "start": "next start", "lint": "next lint", "check-formatting": "prettier --check .", "prepare": "cd ${HOOKS_DIR:-..} && husky install frontend/.husky", diff --git a/frontend/pages/profile/index.tsx b/frontend/pages/profile/index.tsx index 967ddc3d..6b28c25f 100644 --- a/frontend/pages/profile/index.tsx +++ b/frontend/pages/profile/index.tsx @@ -1,4 +1,4 @@ -import * as React from "react" +import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next" import { ProfileInfo, ProfileNav, @@ -13,9 +13,36 @@ import { ProfileMenu } from "../../models/profile" import { Layout } from "../../shared-components" import styles from "./profile.module.css" import OrgUserTable from "../../compositions/profile-orguser/profile-orguser" +import { useProfileTab } from "../../helpers/hooks/useProfileTab" -export default requireAuth(function Profile() { - const [activePage, setActivePage] = React.useState(ProfileMenu.USER_INFO) +export async function getServerSideProps(context: GetServerSidePropsContext) { + const { query } = context + const initialTab: ProfileMenu = Object.values(ProfileMenu).includes(query.tab as ProfileMenu) + ? (query.tab as ProfileMenu) + : ProfileMenu.USER_INFO + + if (initialTab !== query.tab) { + // An invalid tab was requested, + // Redirect to ProfileMenu.USER_INFO tab + return { + redirect: { + destination: `/profile?tab=${initialTab}`, + permanent: false + } + } + } + + return { + props: { + initialTab + } + } +} + +export default requireAuth(function Profile({ + initialTab +}: InferGetServerSidePropsType) { + const [activePage, handleTabChange] = useProfileTab(initialTab) const ActivePageComp = (function (menuItem: ProfileMenu) { switch (menuItem) { @@ -40,7 +67,7 @@ export default requireAuth(function Profile() { return (
- +
diff --git a/frontend/tests/snapshots/profile.test.tsx b/frontend/tests/snapshots/profile.test.tsx index 4453ec7a..416baa52 100644 --- a/frontend/tests/snapshots/profile.test.tsx +++ b/frontend/tests/snapshots/profile.test.tsx @@ -1,9 +1,10 @@ +import { ProfileMenu } from "../../models/profile" import Profile from "../../pages/profile" import { render, setAuthForTest } from "../test-utils" beforeAll(() => setAuthForTest()) it("renders Profile Page correctly", () => { - const { container } = render() + const { container } = render() expect(container).toMatchSnapshot() })