Skip to content

Commit

Permalink
feat: create initial dashboard page (#308)
Browse files Browse the repository at this point in the history
### TL;DR
This PR adds a new `/dashboard` page to go to a specific site

### What changed?
1. **Site Listing**:
    - Introduced a new `SiteList` component that lists all sites a user has access to.
    - Added new database query in `site.router.ts` to fetch site list.
2. **Report Issue Button**:
    - Added a button in the AppNavbar to allow users to report issues via the Isomer issue tracker.
3. **Component Updates**:
    - Updated `AppNavbar` to include new functionality and style tweaks.
    - Changed the logo source in `AppNavbar.tsx`.
    - Updated `dashboard.tsx` with new site listing component.
    - Refactored various storybook files to include new handlers and remove unnecessary layouts.

### How to test?
New stories


---
  • Loading branch information
karrui authored Jul 19, 2024
1 parent 403c7cf commit 8ce3def
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 36 deletions.
2 changes: 2 additions & 0 deletions apps/studio/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ const preview: Preview = {
loaders: [mswLoader],
decorators,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
viewport,
/**
* If tablet view is needed, add it on a per-story basis.
Expand Down
28 changes: 28 additions & 0 deletions apps/studio/public/assets/isomer-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 16 additions & 4 deletions apps/studio/src/components/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Flex, HStack } from "@chakra-ui/react"
import {
AvatarMenu,
AvatarMenuDivider,
Button,
Link,
Menu,
} from "@opengovsg/design-system-react"
import { BiLinkExternal } from "react-icons/bi"

import { ADMIN_NAVBAR_HEIGHT } from "~/constants/layouts"
import { useMe } from "~/features/me/api"
Expand Down Expand Up @@ -39,9 +41,9 @@ export function AppNavbar(): JSX.Element {
>
<Image
// This component can only be used if this is an application created by OGP.
src="/assets/restricted-ogp-logo-full.svg"
width={233}
height={12}
src="/assets/isomer-logo.svg"
height={24}
width={22}
alt="OGP Logo"
priority
/>
Expand All @@ -50,8 +52,18 @@ export function AppNavbar(): JSX.Element {
textStyle="subhead-1"
spacing={{ base: "0.75rem", md: "1.5rem" }}
>
<Button
variant="clear"
size="xs"
rightIcon={<BiLinkExternal fontSize="1.25rem" />}
as={NextLink}
target="_blank"
href="https://go.gov.sg/isomer-issue"
>
Report an issue
</Button>
<AvatarMenu
name={me.name ?? undefined}
name={me.name}
variant="subtle"
bg="base.canvas.brand-subtle"
menuListProps={{ maxWidth: "19rem" }}
Expand Down
40 changes: 40 additions & 0 deletions apps/studio/src/features/dashboard/SiteList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import NextLink from "next/link"
import { Card, CardHeader, SimpleGrid, Skeleton } from "@chakra-ui/react"
import { Link } from "@opengovsg/design-system-react"

import { withSuspense } from "~/hocs/withSuspense"
import { trpc } from "~/utils/trpc"

const SuspendableSiteList = (): JSX.Element => {
// TODO: Only return sites that the user has access to
const [sites] = trpc.site.list.useSuspenseQuery()
return (
<SimpleGrid columns={3} gap="2.5rem" width="100%">
{sites.map((site) => (
<Card key={site.id} width="100%">
<CardHeader>
<Link href={`/sites/${site.id}`} as={NextLink}>
{site.name}
</Link>
</CardHeader>
</Card>
))}
</SimpleGrid>
)
}

const SiteListSkeleton = (): JSX.Element => {
return (
<SimpleGrid columns={3} gap="2.5rem">
{[1, 2, 3].map((index) => (
<Card key={index} width="100%">
<Skeleton>
<CardHeader>Loading...</CardHeader>
</Skeleton>
</Card>
))}
</SimpleGrid>
)
}

export const SiteList = withSuspense(SuspendableSiteList, <SiteListSkeleton />)
39 changes: 20 additions & 19 deletions apps/studio/src/pages/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { Box, Flex } from "@chakra-ui/react"
import { Flex, Text } from "@chakra-ui/react"
import { Link } from "@opengovsg/design-system-react"

import Suspense from "~/components/Suspense"
import {
ADMIN_NAVBAR_HEIGHT,
APP_GRID_COLUMN,
APP_GRID_TEMPLATE_COLUMN,
} from "~/constants/layouts"
import { SiteList } from "~/features/dashboard/SiteList"
import { type NextPageWithLayout } from "~/lib/types"
import { AppGrid } from "~/templates/AppGrid"
import { AdminLayout } from "~/templates/layouts/AdminLayout"

const Home: NextPageWithLayout = () => {
const DashboardPage: NextPageWithLayout = () => {
return (
<Flex
w="100%"
flexDir="column"
position={{ base: "absolute", sm: "inherit" }}
left={{ base: 0, sm: undefined }}
minH={`calc(100% - ${ADMIN_NAVBAR_HEIGHT})`}
>
Hello
<Flex flexDir="column" py="2rem" maxW="57rem" mx="auto" width="100%">
<Flex gap="0.75rem" flexDirection="column">
<Text as="h3" size="lg" textStyle="h3">
My sites
</Text>
<Text textStyle="body-2">
Don't see a site here?{" "}
<Link variant="inline" href="mailto:[email protected]">
Let us know
</Link>
.
</Text>
<SiteList />
</Flex>
</Flex>
)
}

Home.getLayout = AdminLayout
DashboardPage.getLayout = AdminLayout

export default Home
export default DashboardPage
10 changes: 10 additions & 0 deletions apps/studio/src/server/modules/site/site.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
setNotificationSchema,
} from "~/schemas/site"
import { protectedProcedure, router } from "~/server/trpc"
import { db } from "../database"
import { getFooter, getNavBar } from "../resource/resource.service"
import {
getNotification,
Expand All @@ -12,6 +13,15 @@ import {
} from "./site.service"

export const siteRouter = router({
list: protectedProcedure.query(() => {
return (
db
.selectFrom("Site")
// TODO: Only return sites that the user has access to
.select(["Site.id", "Site.name"])
.execute()
)
}),
getConfig: protectedProcedure
.input(getConfigSchema)
.query(async ({ input }) => {
Expand Down
14 changes: 11 additions & 3 deletions apps/studio/src/stories/Page/DashboardPage.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react"
import { meHandlers } from "tests/msw/handlers/me"
import { sitesHandlers } from "tests/msw/handlers/sites"

import DashboardPage from "~/pages/dashboard"

const meta: Meta<typeof DashboardPage> = {
title: "Pages/Dashboard Page",
component: DashboardPage,
parameters: {
getLayout: DashboardPage.getLayout,
mockdate: new Date("2023-06-28T07:23:18.349Z"),
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
msw: {
handlers: [meHandlers.me()],
handlers: [meHandlers.me(), sitesHandlers.list.default()],
},
},
}
Expand All @@ -22,3 +22,11 @@ type Story = StoryObj<typeof DashboardPage>
export const Default: Story = {
name: "Dashboard Page",
}

export const Loading: Story = {
parameters: {
msw: {
handlers: [meHandlers.me(), sitesHandlers.list.loading()],
},
},
}
4 changes: 0 additions & 4 deletions apps/studio/src/stories/Page/LandingPage.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import LandingPage from "~/pages/index"
const meta: Meta<typeof LandingPage> = {
title: "Pages/Landing Page",
component: LandingPage,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
},
}

export default meta
Expand Down
2 changes: 0 additions & 2 deletions apps/studio/src/stories/Page/SignInPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const meta: Meta<typeof SignInPage> = {
title: "Pages/Sign In Page",
component: SignInPage,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
loginState: false,
msw: {
handlers: [
Expand Down
9 changes: 6 additions & 3 deletions apps/studio/src/stories/Page/SitePage.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react"
import { meHandlers } from "tests/msw/handlers/me"

import SitePage from "~/pages/sites/[siteId]"
import { AdminLayout } from "~/templates/layouts/AdminLayout"

const meta: Meta<typeof SitePage> = {
title: "pages/site/[siteId]",
title: "Pages/Site Management/Site Page",
component: SitePage,
parameters: {
getLayout: AdminLayout,
getLayout: SitePage.getLayout,
msw: {
handlers: [meHandlers.me()],
},
},
decorators: [],
}
Expand Down
1 change: 0 additions & 1 deletion apps/studio/src/templates/layouts/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const AdminLayout: GetLayout = (page) => {
bg="base.canvas.alt"
justifyItems="center"
gridColumn={APP_GRID_COLUMN}
maxW="57rem"
width="100%"
>
{page}
Expand Down
25 changes: 25 additions & 0 deletions apps/studio/tests/msw/handlers/sites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { DelayMode } from "msw"
import { delay } from "msw"

import { trpcMsw } from "../mockTrpc"

const siteListQuery = (wait?: DelayMode | number) => {
return trpcMsw.site.list.query(async () => {
if (wait !== undefined) {
await delay(wait)
}
return [
{
id: 1,
name: "Ministry of Trade and Industry",
},
]
})
}

export const sitesHandlers = {
list: {
default: siteListQuery,
loading: () => siteListQuery("infinite"),
},
}

0 comments on commit 8ce3def

Please sign in to comment.