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

feat: update dashboard nav item styles #9795

Merged
merged 7 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
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
Loading