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
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]
}
8 changes: 4 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"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 && next start",
"build:stage": "env-cmd -f .env.staging next build && next start",
"build:prod": "env-cmd -f .env.production next build && next start",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these && next start and add a command "start": "next start", or edit Dockerfile.cloud to add a & to the end of the npm run build:{... command.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do both of these things? I've adjusted the build commands to remove next start from them -- do I need to add & npm run start to the Dockerfile.cloud RUN command as well?

"scripts": {
    "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",
  },

"build:ui": "env-cmd -f .env.ui next build",
"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()
})