Skip to content

Commit

Permalink
edit mode toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeKarow committed Aug 21, 2023
1 parent 87d5f14 commit 24fa81a
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 60 deletions.
8 changes: 8 additions & 0 deletions apps/app/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
"try-again-text": "<Text>Something went wrong! Please try again.</Text>"
},
"exclude": "Exclude",
"exit": {
"edit-mode": "Exit edit mode"
},
"filter-by-service": "Filter by services",
"find-resources": "Find resources",
"find-x": "Find {{value}}",
Expand Down Expand Up @@ -396,9 +399,13 @@
"please-wait": "Please wait...",
"prev": "Prev",
"print": "Print",
"publish": "Publish",
"restore": "Restore",
"reverify": "Reverify",
"review": "Review",
"reviews": "Reviews",
"save": "Save",
"save-changes": "Save changes",
"saved": "Saved",
"search": "Search",
"service-hours": "Service hours",
Expand All @@ -407,6 +414,7 @@
"sign-up": "Sign up",
"skip": "Skip",
"support": "Support",
"unpublish": "Unpublish",
"website": "Website",
"yes": "Yes"
}
Expand Down
69 changes: 20 additions & 49 deletions apps/app/src/pages/org/[slug]/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable i18next/no-literal-string */
import { Grid, Stack, Tabs, Title } from '@mantine/core'
import { useElementSize } from '@mantine/hooks'
import { type GetServerSideProps, type NextPage } from 'next'
import { type GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import { type RoutedQuery } from 'nextjs-routes'
Expand All @@ -18,63 +18,31 @@ import { PhotosSection } from '@weareinreach/ui/components/sections/Photos'
import { ReviewSection } from '@weareinreach/ui/components/sections/Reviews'
import { ServicesInfoCard } from '@weareinreach/ui/components/sections/ServicesInfo'
import { VisitCard } from '@weareinreach/ui/components/sections/VisitCard'
import { type NextPageWithOptions } from '~app/pages/_app'
import { api } from '~app/utils/api'
import { getServerSideTranslations } from '~app/utils/i18n'

const OrganizationPage: NextPage = () => {
const OrganizationPage: NextPageWithOptions = () => {
const { t } = useTranslation()
const router = useRouter<'/org/[slug]'>()
const { query } = router.isReady ? router : { query: { slug: '' } }
const [activeTab, setActiveTab] = useState<string | null>('services')
const [loading, setLoading] = useState(true)
const { data, status } = api.organization.getBySlug.useQuery(query, { enabled: router.isReady })
const { data, status } = api.organization.forOrgPageEdits.useQuery(query, { enabled: router.isReady })
const { data: hasRemote } = api.service.forServiceInfoCard.useQuery(
{ parentId: data?.id ?? '', remoteOnly: true },
{
enabled: !!data?.id && data?.locations.length > 1,
select: (data) => data.length !== 0,
}
)
const { ref, width } = useElementSize()
useEffect(() => {
if (data && status === 'success') setLoading(false)
}, [data, status])
if (loading || !data) return <>Loading</>

const {
// emails,
// phones,
// socialMedia,
// websites,
userLists,
attributes,
description,
slug,
// photos,
reviews,
locations,
isClaimed,
id: organizationId,
} = data

const body =
locations?.length === 1 ? (
<Tabs w='100%' value={activeTab} onTabChange={setActiveTab}>
<Tabs.List>
<Tabs.Tab value='services'>{t('services')}</Tabs.Tab>
<Tabs.Tab value='photos'>{t('photo', { count: 2 })}</Tabs.Tab>
<Tabs.Tab value='reviews'>{t('review', { count: 2 })}</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value='services'>
<ServicesInfoCard parentId={organizationId} />
</Tabs.Panel>
<Tabs.Panel value='photos'>
<PhotosSection parentId={organizationId} />
</Tabs.Panel>
<Tabs.Panel value='reviews'>
<ReviewSection reviews={reviews} />
</Tabs.Panel>
</Tabs>
) : (
<>
{locations.map((location) => (
<LocationCard key={location.id} locationId={location.id} />
))}
</>
)
const { attributes, description, slug, reviews, locations, isClaimed, id: organizationId } = data

const sidebar =
locations?.length === 1 ? (
Expand All @@ -95,13 +63,15 @@ const OrganizationPage: NextPage = () => {

return (
<>
<Title order={1}>EDIT MODE</Title>
<Grid.Col sm={12} order={0}>
<Title order={1}>EDIT MODE</Title>
</Grid.Col>
<Grid.Col sm={8} order={1}>
<Toolbar
{/* <Toolbar
breadcrumbProps={{ option: 'back', backTo: 'search', onClick: () => router.back() }}
saved={Boolean(userLists?.length)}
organizationId={organizationId}
/>
/> */}
<Stack pt={24} align='flex-start' spacing={40}>
<ListingBasicInfo
role='org'
Expand All @@ -116,7 +86,9 @@ const OrganizationPage: NextPage = () => {
isClaimed,
}}
/>
{body}
{locations.map((location) => (
<LocationCard key={location.id} locationId={location.id} />
))}
</Stack>
</Grid.Col>
<Grid.Col order={2}>
Expand Down Expand Up @@ -166,5 +138,4 @@ export const getServerSideProps: GetServerSideProps<
props,
}
}

export default OrganizationPage
6 changes: 3 additions & 3 deletions packages/ui/components/core/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const useStyles = createStyles((theme) => ({
const isString = (...args: unknown[]) => args.every((val) => typeof val === 'string')

export const Breadcrumb = (props: BreadcrumbProps) => {
const { option, backTo, backToText, onClick } = props
const { option, backTo, backToText, onClick, children } = props
const { classes } = useStyles()
const theme = useMantineTheme()
const { t } = useTranslation('common')
Expand Down Expand Up @@ -125,7 +125,7 @@ export const Breadcrumb = (props: BreadcrumbProps) => {
className={classes.icon}
/>
<Text size='md' fw={theme.other.fontWeight.semibold} truncate m={0}>
{childrenRender}
{children || childrenRender}
</Text>
</Group>
</UnstyledButton>
Expand All @@ -150,7 +150,7 @@ type PossibleBreadcrumbProps = {
export type ModalTitleBreadcrumb = Omit<BreadcrumbProps, 'onClick'> & {
onClick?: MouseEventHandler<HTMLButtonElement>
}
export type BreadcrumbProps = Close | Back | BackToDynamic
export type BreadcrumbProps = (Close | Back | BackToDynamic) & { children?: React.ReactNode }
interface Close {
option: 'close'
onClick: MouseEventHandler<HTMLButtonElement>
Expand Down
105 changes: 102 additions & 3 deletions packages/ui/components/sections/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,122 @@
import { Container, createStyles, Flex, Group, rem } from '@mantine/core'
import { Container, createStyles, Flex, Group, rem, UnstyledButton, useMantineTheme } from '@mantine/core'
import Image from 'next/image'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'

import InReachLogo from '~ui/assets/inreach.svg'
import { Button } from '~ui/components/core/Button'
import { Link } from '~ui/components/core/Link'
import { MobileNav } from '~ui/components/core/MobileNav'
import { UserMenu } from '~ui/components/core/UserMenu'
import { useCustomVariant, useScreenSize } from '~ui/hooks'
import { useCustomVariant, useEditMode, useScreenSize } from '~ui/hooks'
import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'

const useStyles = createStyles((theme) => ({
desktopNav: {
height: rem(64),
minHeight: rem(64),
boxShadow: theme.shadows.xs,
marginBottom: rem(40),
[theme.fn.smallerThan('sm')]: {
display: 'none',
},
position: 'sticky',
top: 0,
left: 0,
right: 0,
zIndex: 10000,
backgroundColor: theme.other.colors.secondary.white,
},
mobileNav: {
[theme.fn.largerThan('sm')]: {
display: 'none',
},
},
editBar: {
background: `linear-gradient(180deg, ${theme.other.colors.secondary.white} 0%, ${theme.fn.lighten(
theme.other.colors.tertiary.red,
0.75
)} 10%)`,
padding: `${rem(8)} ${rem(128)} ${rem(8)} ${rem(64)}`,
margin: `0 ${rem(-64)}`,
},
editBarButtonText: {
...theme.other.utilityFonts.utility3,
...theme.fn.hover({ backgroundColor: theme.other.colors.secondary.white }),
padding: `${rem(4)} ${rem(8)}`,
borderRadius: rem(8),
'&:disabled': {
color: theme.other.colors.secondary.darkGray,
cursor: 'not-allowed',
...theme.fn.hover({ backgroundColor: theme.other.colors.primary.lightGray }),
},
},
}))

const EditModeBar = () => {
const { classes } = useStyles()
const theme = useMantineTheme()
const { canSave, handleEditSubmit } = useEditMode()
const { t } = useTranslation('common')
const router = useRouter()
const { orgLocationId, slug } = router.query
const { data, isLoading } = api.misc.forEditNavbar.useQuery(

Check warning on line 63 in packages/ui/components/sections/Navbar.tsx

View check run for this annotation

InReachBot / Check Code for Errors

packages/ui/components/sections/Navbar.tsx#L63

[@typescript-eslint/no-unused-vars] 'isLoading' is assigned a value but never used. Allowed unused vars must match /^_/u.
orgLocationId ? { orgLocationId: orgLocationId as string } : { slug: slug as string },
{
enabled: typeof orgLocationId === 'string' || typeof slug === 'string',
}
)

return (
<Group position='apart' noWrap className={classes.editBar}>
<UnstyledButton className={classes.editBarButtonText} onClick={router.back}>
<Group noWrap spacing={8}>
<Icon icon='carbon:arrow-left' height={20} />
{t('exit.edit-mode')}
</Group>
</UnstyledButton>
<Group noWrap>
<UnstyledButton
disabled={!canSave}
className={classes.editBarButtonText}
onClick={() => handleEditSubmit(() => console.log('save action from toolbar'))}
>
<Group noWrap spacing={8}>
<Icon
icon='mdi:content-save'
height={20}
color={canSave ? theme.other.colors.primary.allyGreen : undefined}
/>
{t('words.save-changes')}
</Group>
</UnstyledButton>
{slug && !orgLocationId && (
<UnstyledButton className={classes.editBarButtonText}>
<Group noWrap spacing={8}>
<Icon icon='carbon:checkmark-filled' color={theme.other.colors.primary.allyGreen} height={20} />
{t('words.reverify')}
</Group>
</UnstyledButton>
)}
<UnstyledButton className={classes.editBarButtonText}>
<Group noWrap spacing={8}>
<Icon icon={data?.published ? 'carbon:view-off' : 'carbon:view-filled'} height={20} />
{t(data?.published ? 'words.unpublish' : 'words.publish')}
</Group>
</UnstyledButton>
<UnstyledButton className={classes.editBarButtonText}>
<Group noWrap spacing={8}>
<Icon icon={data?.deleted ? 'fluent-mdl2:remove-from-trash' : 'carbon:trash-can'} height={20} />
{t(data?.deleted ? 'words.restore' : 'words.delete')}
</Group>
</UnstyledButton>
</Group>
</Group>
)
}

export const Navbar = () => {
const { isEditMode } = useEditMode()
const { t } = useTranslation('common')
const { classes } = useStyles()
const variants = useCustomVariant()
Expand Down Expand Up @@ -53,7 +144,15 @@ export const Navbar = () => {
</Link>
</Group>
</Flex>
{isEditMode ? <EditModeBar /> : null}
</Container>
</>
)
}

type NavbarProps = {

Check warning on line 153 in packages/ui/components/sections/Navbar.tsx

View check run for this annotation

InReachBot / Check Code for Errors

packages/ui/components/sections/Navbar.tsx#L153

[@typescript-eslint/no-unused-vars] 'NavbarProps' is defined but never used. Allowed unused vars must match /^_/u.
editMode?: boolean
editModeRef?: {
handleEditSubmit: (handler: () => void) => void
}
}
9 changes: 8 additions & 1 deletion packages/ui/hooks/useEditMode.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { useRouter } from 'next/router'
import { useContext } from 'react'

import { EditModeContext } from '~ui/providers/EditMode'

export const useEditMode = () => {
const router = useRouter()
const ctx = useContext(EditModeContext)
if (!ctx) {
throw new Error('useEditMode must be used within a EditModeProvider')
}
return ctx
const isEditMode = router.pathname.endsWith('/edit')

return {
isEditMode,
...ctx,
}
}
11 changes: 7 additions & 4 deletions packages/ui/providers/EditMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ export const EditModeContext = createContext<EditContext | null>(null)

export const EditModeProvider = ({ children }: { children: React.ReactNode }) => {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
const editModeRef = useRef<EditModeRef>({ handleEditSubmit: (handler: () => void) => handler() })
const editModeRef = useRef<EditModeRef>({
handleEditSubmit: (handler: () => void) => handler(),
canSave: false,
})

const contextValue = {
unsaved: {
set: setHasUnsavedChanges,
state: hasUnsavedChanges,
},
submitEditHandler: editModeRef.current.handleEditSubmit,
...editModeRef.current,
}

return <EditModeContext.Provider value={contextValue}>{children}</EditModeContext.Provider>
Expand All @@ -22,8 +25,8 @@ type EditContext = {
set: Dispatch<SetStateAction<boolean>>
state: boolean
}
submitEditHandler: (handler: () => void) => void
}
} & EditModeRef
type EditModeRef = {
handleEditSubmit: (handler: () => void) => void
canSave: boolean
}

0 comments on commit 24fa81a

Please sign in to comment.