From 24fa81a3ce70e6bd3868f10bac4ac40418e97f05 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 21 Aug 2023 18:08:26 -0400 Subject: [PATCH] edit mode toolbar --- apps/app/public/locales/en/common.json | 8 ++ apps/app/src/pages/org/[slug]/edit.tsx | 69 ++++---------- packages/ui/components/core/Breadcrumb.tsx | 6 +- packages/ui/components/sections/Navbar.tsx | 105 ++++++++++++++++++++- packages/ui/hooks/useEditMode.ts | 9 +- packages/ui/providers/EditMode.tsx | 11 ++- 6 files changed, 148 insertions(+), 60 deletions(-) diff --git a/apps/app/public/locales/en/common.json b/apps/app/public/locales/en/common.json index 6f761ecd858..07f5ad60a77 100644 --- a/apps/app/public/locales/en/common.json +++ b/apps/app/public/locales/en/common.json @@ -95,6 +95,9 @@ "try-again-text": "Something went wrong! Please try again." }, "exclude": "Exclude", + "exit": { + "edit-mode": "Exit edit mode" + }, "filter-by-service": "Filter by services", "find-resources": "Find resources", "find-x": "Find {{value}}", @@ -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", @@ -407,6 +414,7 @@ "sign-up": "Sign up", "skip": "Skip", "support": "Support", + "unpublish": "Unpublish", "website": "Website", "yes": "Yes" } diff --git a/apps/app/src/pages/org/[slug]/edit.tsx b/apps/app/src/pages/org/[slug]/edit.tsx index 6d7574a5075..1e064c66558 100644 --- a/apps/app/src/pages/org/[slug]/edit.tsx +++ b/apps/app/src/pages/org/[slug]/edit.tsx @@ -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' @@ -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('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 ? ( - - - {t('services')} - {t('photo', { count: 2 })} - {t('review', { count: 2 })} - - - - - - - - - - - - ) : ( - <> - {locations.map((location) => ( - - ))} - - ) + const { attributes, description, slug, reviews, locations, isClaimed, id: organizationId } = data const sidebar = locations?.length === 1 ? ( @@ -95,13 +63,15 @@ const OrganizationPage: NextPage = () => { return ( <> - EDIT MODE + + EDIT MODE + - router.back() }} saved={Boolean(userLists?.length)} organizationId={organizationId} - /> + /> */} { isClaimed, }} /> - {body} + {locations.map((location) => ( + + ))} @@ -166,5 +138,4 @@ export const getServerSideProps: GetServerSideProps< props, } } - export default OrganizationPage diff --git a/packages/ui/components/core/Breadcrumb.tsx b/packages/ui/components/core/Breadcrumb.tsx index 9000c5afa79..96b435fa8b9 100644 --- a/packages/ui/components/core/Breadcrumb.tsx +++ b/packages/ui/components/core/Breadcrumb.tsx @@ -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') @@ -125,7 +125,7 @@ export const Breadcrumb = (props: BreadcrumbProps) => { className={classes.icon} /> - {childrenRender} + {children || childrenRender} @@ -150,7 +150,7 @@ type PossibleBreadcrumbProps = { export type ModalTitleBreadcrumb = Omit & { onClick?: MouseEventHandler } -export type BreadcrumbProps = Close | Back | BackToDynamic +export type BreadcrumbProps = (Close | Back | BackToDynamic) & { children?: React.ReactNode } interface Close { option: 'close' onClick: MouseEventHandler diff --git a/packages/ui/components/sections/Navbar.tsx b/packages/ui/components/sections/Navbar.tsx index ff8c35465ff..0cdaf1316ee 100644 --- a/packages/ui/components/sections/Navbar.tsx +++ b/packages/ui/components/sections/Navbar.tsx @@ -1,5 +1,6 @@ -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' @@ -7,25 +8,115 @@ 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( + orgLocationId ? { orgLocationId: orgLocationId as string } : { slug: slug as string }, + { + enabled: typeof orgLocationId === 'string' || typeof slug === 'string', + } + ) + + return ( + + + + + {t('exit.edit-mode')} + + + + handleEditSubmit(() => console.log('save action from toolbar'))} + > + + + {t('words.save-changes')} + + + {slug && !orgLocationId && ( + + + + {t('words.reverify')} + + + )} + + + + {t(data?.published ? 'words.unpublish' : 'words.publish')} + + + + + + {t(data?.deleted ? 'words.restore' : 'words.delete')} + + + + + ) +} + export const Navbar = () => { + const { isEditMode } = useEditMode() const { t } = useTranslation('common') const { classes } = useStyles() const variants = useCustomVariant() @@ -53,7 +144,15 @@ export const Navbar = () => { + {isEditMode ? : null} ) } + +type NavbarProps = { + editMode?: boolean + editModeRef?: { + handleEditSubmit: (handler: () => void) => void + } +} diff --git a/packages/ui/hooks/useEditMode.ts b/packages/ui/hooks/useEditMode.ts index dd0f0006f92..20b39fec319 100644 --- a/packages/ui/hooks/useEditMode.ts +++ b/packages/ui/hooks/useEditMode.ts @@ -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, + } } diff --git a/packages/ui/providers/EditMode.tsx b/packages/ui/providers/EditMode.tsx index fb6f026fc37..5808bef150f 100644 --- a/packages/ui/providers/EditMode.tsx +++ b/packages/ui/providers/EditMode.tsx @@ -4,14 +4,17 @@ export const EditModeContext = createContext(null) export const EditModeProvider = ({ children }: { children: React.ReactNode }) => { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) - const editModeRef = useRef({ handleEditSubmit: (handler: () => void) => handler() }) + const editModeRef = useRef({ + handleEditSubmit: (handler: () => void) => handler(), + canSave: false, + }) const contextValue = { unsaved: { set: setHasUnsavedChanges, state: hasUnsavedChanges, }, - submitEditHandler: editModeRef.current.handleEditSubmit, + ...editModeRef.current, } return {children} @@ -22,8 +25,8 @@ type EditContext = { set: Dispatch> state: boolean } - submitEditHandler: (handler: () => void) => void -} +} & EditModeRef type EditModeRef = { handleEditSubmit: (handler: () => void) => void + canSave: boolean }