Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend: Profile Page Tabs as URL Params #364

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from "react"
import { StoryFn, Meta } from "@storybook/react"
import { Story, Meta } from "@storybook/react"
import { DashboardHeader } from ".."

export default {
title: "Compositions/Header",
component: DashboardHeader
} as Meta<typeof DashboardHeader>

const Template: StoryFn<typeof DashboardHeader> = (args: any) => <DashboardHeader {...args} />
const Template: Story<typeof DashboardHeader> = (args: any) => <DashboardHeader {...args} />

export const Default = Template.bind({})
Default.parameters = {
Expand Down
3 changes: 1 addition & 2 deletions frontend/compositions/profile-nav/index.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<ProfileMenu>>
setActivePage: (newTab: ProfileMenu) => void
}

export default function ProfileNav({ activePage, setActivePage }: ProfileNavProps) {
Expand Down
6 changes: 3 additions & 3 deletions frontend/helpers/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(Component: (props: T) => JSX.Element) {
return function ProtectedRoute(props: T) {
const { user } = useAuth()
const router = useRouter()
const login = "/login"
Expand All @@ -42,7 +42,7 @@ export function requireAuth(Component: () => JSX.Element) {
}
})

return user ? <Component /> : null
return user ? <Component {...props} /> : null
}
}

Expand Down
34 changes: 34 additions & 0 deletions frontend/helpers/hooks/useProfileTab.ts
Original file line number Diff line number Diff line change
@@ -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<ProfileMenu>(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]
}
9 changes: 5 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 31 additions & 4 deletions frontend/pages/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from "react"
import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next"
import {
ProfileInfo,
ProfileNav,
Expand All @@ -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<typeof getServerSideProps>) {
const [activePage, handleTabChange] = useProfileTab(initialTab)

const ActivePageComp = (function (menuItem: ProfileMenu) {
switch (menuItem) {
Expand All @@ -40,7 +67,7 @@ export default requireAuth(function Profile() {
return (
<Layout>
<div className={styles.profileWrapper}>
<ProfileNav activePage={activePage} setActivePage={setActivePage} />
<ProfileNav activePage={activePage} setActivePage={handleTabChange} />
<ActivePageComp />
</div>
</Layout>
Expand Down
3 changes: 2 additions & 1 deletion frontend/tests/snapshots/profile.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<Profile />)
const { container } = render(<Profile initialTab={ProfileMenu.USER_INFO} />)
expect(container).toMatchSnapshot()
})
Loading