Skip to content

Commit

Permalink
feat: update dashboard nav item styles (#9795)
Browse files Browse the repository at this point in the history
Co-authored-by: Terry Acker <[email protected]>
  • Loading branch information
nickoferrall and ackernaut authored Jun 14, 2024
1 parent 095cf71 commit 71b17c2
Show file tree
Hide file tree
Showing 23 changed files with 637 additions and 351 deletions.
165 changes: 61 additions & 104 deletions packages/client/components/DashNavList/DashNavList.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React, {Fragment, useMemo} from 'react'
import React from 'react'
import {useFragment} from 'react-relay'
import {PALETTE} from '~/styles/paletteV3'
import {Breakpoint} from '~/types/constEnums'
import makeMinWidthMediaQuery from '~/utils/makeMinWidthMediaQuery'
import {
DashNavList_organization$data,
DashNavList_organization$key
} from '../../__generated__/DashNavList_organization.graphql'
import {DashNavList_organization$key} from '../../__generated__/DashNavList_organization.graphql'
import {TierEnum} from '../../__generated__/InvoiceHeader_invoice.graphql'
import useBreakpoint from '../../hooks/useBreakpoint'
import {Breakpoint} from '../../types/constEnums'
import {upperFirst} from '../../utils/upperFirst'
import LeftDashNavItem from '../Dashboard/LeftDashNavItem'

const DashNavListStyles = styled('div')({
paddingRight: 8,
width: '100%'
})

const OrgName = styled('div')({
paddingTop: 8,
paddingLeft: 8,
fontWeight: 600,
fontSize: 12,
lineHeight: '24px',
color: PALETTE.SLATE_500,
[makeMinWidthMediaQuery(Breakpoint.SIDEBAR_LEFT)]: {
paddingLeft: 16
}
})
import BaseTag from '../Tag/BaseTag'
import DashNavListTeams from './DashNavListTeams'
import DashNavMenu from './DashNavMenu'

const EmptyTeams = styled('div')({
fontSize: 16,
Expand All @@ -35,111 +20,81 @@ const EmptyTeams = styled('div')({
textAlign: 'center'
})

const DashHR = styled('div')({
borderBottom: `1px solid ${PALETTE.SLATE_300}`,
width: 'calc(100% + 8px)'
})

const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isViewerOnTeam: boolean}>(
({isViewerOnTeam}) => ({
color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600
color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600,
borderRadius: 44,
paddingLeft: 15
})
)

const Tag = styled(BaseTag)<{tier: TierEnum | null}>(({tier}) => ({
backgroundColor:
tier === 'enterprise' ? PALETTE.SKY_500 : tier === 'team' ? PALETTE.GOLD_300 : PALETTE.JADE_400,
color: tier === 'team' ? PALETTE.GRAPE_700 : PALETTE.WHITE
}))

interface Props {
className?: string
organizationsRef: DashNavList_organization$key | null
onClick?: () => void
}

type Team = DashNavList_organization$data[0]['allTeams'][0]

const DashNavList = (props: Props) => {
const {className, onClick, organizationsRef} = props
const {onClick, organizationsRef} = props
const organizations = useFragment(
graphql`
fragment DashNavList_organization on Organization @relay(plural: true) {
allTeams {
...DashNavListTeam @relay(mask: false)
}
...DashNavListTeams_organization
...DashNavMenu_organization
id
name
tier
viewerTeams {
...DashNavListTeam @relay(mask: false)
}
featureFlags {
publicTeams
id
}
}
`,
organizationsRef
)
const teams = organizations?.flatMap((org) => {
// if the user is a billing leader, allTeams will return all teams even if they don't have the publicTeams flag
const hasPublicTeamsFlag = org.featureFlags.publicTeams
return hasPublicTeamsFlag ? org.allTeams : org.viewerTeams
})
const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT)
const teams = organizations?.flatMap((org) => org.viewerTeams)

const teamsByOrgKey = useMemo(() => {
if (!teams) return null
const teamsByOrgId = {} as {[key: string]: Team[]}
teams.forEach((team) => {
const {organization} = team
const {id: orgId, name: orgName} = organization
const key = `${orgName}:${orgId}`
teamsByOrgId[key] = teamsByOrgId[key] || []
teamsByOrgId[key]!.push(team)
})
return Object.entries(teamsByOrgId).sort((a, b) =>
a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1
)
}, [teams])
if (!teams || !teamsByOrgKey) return null

if (teams.length === 0) {
if (teams?.length === 0) {
return <EmptyTeams>It appears you are not a member of any team!</EmptyTeams>
}

const isSingleOrg = teamsByOrgKey.length === 1

const getIcon = (team: Team) => (team.organization.lockedAt || !team.isPaid ? 'warning' : 'group')

return (
<DashNavListStyles>
{isSingleOrg
? teams.map((team) => (
<StyledLeftDashNavItem
className={className}
onClick={onClick}
isViewerOnTeam={team.isViewerOnTeam}
key={team.id}
icon={getIcon(team)}
href={team.isViewerOnTeam ? `/team/${team.id}` : `/team/${team.id}/requestToJoin`}
label={team.name}
/>
))
: teamsByOrgKey.map((entry, idx) => {
const [key, teams] = entry
const name = key.slice(0, key.lastIndexOf(':'))
return (
<Fragment key={key}>
<OrgName>{name}</OrgName>
{teams.map((team) => (
<StyledLeftDashNavItem
className={className}
isViewerOnTeam={team.isViewerOnTeam}
onClick={onClick}
key={team.id}
icon={getIcon(team)}
href={
team.isViewerOnTeam ? `/team/${team.id}` : `/team/${team.id}/requestToJoin`
}
label={team.name}
/>
))}
{idx !== teamsByOrgKey.length - 1 && <DashHR />}
</Fragment>
)
})}
</DashNavListStyles>
<div className='w-full p-2 lg:pt-4'>
{organizations?.map((org) => (
<div key={org.id} className='mb-3 w-full rounded-lg border border-solid border-slate-400'>
<div className='border-b border-solid border-slate-300 p-2'>
<div className='flex flex-wrap items-center pb-1'>
<div className='flex min-w-0 flex-1 flex-wrap items-center justify-between'>
<span className='pl-2 text-base font-semibold leading-6 text-slate-700'>
{org.name}
</span>
<div className='flex w-auto justify-end px-0 text-right'>
<Tag tier={org.tier}>{upperFirst(org.tier)}</Tag>
</div>
</div>
</div>
{isDesktop ? (
<DashNavMenu organizationRef={org} />
) : (
<StyledLeftDashNavItem
className={'bg-transparent'}
icon={'manageAccounts'}
isViewerOnTeam
onClick={onClick}
href={`/me/organizations/${org.id}/billing`}
label={'Settings & Members'}
/>
)}
</div>
<DashNavListTeams onClick={onClick} organizationRef={org} />
</div>
))}
</div>
)
}

Expand All @@ -149,11 +104,13 @@ graphql`
isPaid
name
isViewerOnTeam
tier
organization {
id
name
lockedAt
}
}
`

export default DashNavList
93 changes: 93 additions & 0 deletions packages/client/components/DashNavList/DashNavListTeams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React, {useState} from 'react'
import {useFragment} from 'react-relay'
import {DashNavListTeams_organization$key} from '../../__generated__/DashNavListTeams_organization.graphql'
import {PALETTE} from '../../styles/paletteV3'
import plural from '../../utils/plural'
import LeftDashNavItem from '../Dashboard/LeftDashNavItem'
import PublicTeamsModal from './PublicTeamsModal'

const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isPublicTeams?: boolean}>(
({isPublicTeams}) => ({
color: isPublicTeams ? PALETTE.SLATE_600 : PALETTE.SLATE_700,
borderRadius: 44,
paddingLeft: 15
})
)

type Props = {
organizationRef: DashNavListTeams_organization$key
onClick?: () => void
}

const DashNavListTeams = (props: Props) => {
const {organizationRef, onClick} = props
const organization = useFragment(
graphql`
fragment DashNavListTeams_organization on Organization {
id
name
tier
featureFlags {
publicTeams
}
viewerTeams {
...DashNavListTeam @relay(mask: false)
}
publicTeams {
...PublicTeamsModal_team
}
}
`,
organizationRef
)
const [showModal, setShowModal] = useState(false)
const {publicTeams, viewerTeams, featureFlags} = organization
const publicTeamsEnabled = featureFlags?.publicTeams
const publicTeamsCount = publicTeamsEnabled ? publicTeams.length : 0

const handleClose = () => {
setShowModal(false)
}

const handleClick = () => {
setShowModal(true)
onClick && onClick()
}

const getIcon = (lockedAt: string | null, isPaid: boolean | null) =>
lockedAt || !isPaid ? 'warning' : 'group'

return (
<div className='p-2'>
{viewerTeams.map((team) => {
return (
<StyledLeftDashNavItem
key={team.id}
icon={getIcon(team.organization.lockedAt, team.isPaid)}
href={team.isViewerOnTeam ? `/team/${team.id}` : `/team/${team.id}/requestToJoin`}
label={team.name}
onClick={onClick}
/>
)
})}
{publicTeamsCount > 0 && (
<StyledLeftDashNavItem
className='bg-white pl-11 lg:bg-slate-200'
onClick={handleClick}
isPublicTeams
label={`View ${publicTeamsCount} ${plural(publicTeamsCount, 'Public Team', 'Public Teams')}`}
/>
)}
<PublicTeamsModal
orgName={organization.name}
teamsRef={publicTeams}
isOpen={showModal}
onClose={handleClose}
/>
</div>
)
}

export default DashNavListTeams
Loading

0 comments on commit 71b17c2

Please sign in to comment.