Skip to content

Commit 9bfb028

Browse files
authored
fix(collection-links): prevent stale data on gsib (#1668)
* chore: refactor to use links * chore: refactor to use formbuilder * chore: swap over to schemas * chore: tidy up pr * chore: swap to use currentdate * chore: increase threshold revert later zz * chore: fix collection links being unable to pubilsh - this is because the publish button takes a `isDisabled` prop - the navbar then passes in `linkAtom.ref` which we never set
1 parent d26a2e9 commit 9bfb028

File tree

9 files changed

+110
-126
lines changed

9 files changed

+110
-126
lines changed
Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
1-
import type { LinkRefPageSchema } from "@opengovsg/isomer-components"
2-
import type { Static } from "@sinclair/typebox"
3-
import { format } from "date-fns"
41
import { atom } from "jotai"
52

63
import type { ResourceItemContent } from "~/schemas/resource"
74

85
export const moveResourceAtom = atom<null | ResourceItemContent>(null)
9-
10-
export type CollectionLinkProps = Static<typeof LinkRefPageSchema> & {
11-
title: string
12-
}
13-
14-
export const linkAtom = atom<CollectionLinkProps>({
15-
ref: "",
16-
description: "",
17-
date: format(new Date(), "dd/mm/yyyy"),
18-
category: "",
19-
title: "",
20-
})
21-
22-
// NOTE: We need this because this atom takes in the value
23-
// for the ref when the link itself is saved.
24-
// This is strictly used for validation to see
25-
// if we should allow the `Publish`
26-
export const linkRefAtom = atom("")

apps/studio/src/features/editing-experience/components/EditLinkPreview.tsx

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import type { IsomerSitemap } from "@opengovsg/isomer-components"
2+
import { useMemo } from "react"
23
import { ResourceType } from "~prisma/generated/generatedEnums"
3-
import { useAtomValue } from "jotai"
44

55
import { useQueryParse } from "~/hooks/useQueryParse"
66
import { editLinkSchema } from "~/pages/sites/[siteId]/links/[linkId]"
7+
import { CollectionLinkProps } from "~/schemas/collection"
78
import { trpc } from "~/utils/trpc"
8-
import { linkAtom } from "../atoms"
99
import PreviewWithoutSitemap from "./PreviewWithoutSitemap"
1010
import { ViewportContainer } from "./ViewportContainer"
1111

12-
export const EditCollectionLinkPreview = (): JSX.Element => {
13-
const {
14-
description: summary,
15-
date,
16-
title,
17-
category,
18-
ref,
19-
image,
20-
tagged,
21-
} = useAtomValue(linkAtom)
12+
const currentDate = new Date().toString()
13+
14+
interface EditCollectionLinkPreviewProps {
15+
link: CollectionLinkProps
16+
title: string
17+
}
18+
export const EditCollectionLinkPreview = ({
19+
link,
20+
title,
21+
}: EditCollectionLinkPreviewProps): JSX.Element => {
2222
const { linkId, siteId } = useQueryParse(editLinkSchema)
2323
const [permalink] = trpc.page.getFullPermalink.useSuspenseQuery(
2424
{
@@ -38,43 +38,49 @@ export const EditCollectionLinkPreview = (): JSX.Element => {
3838
siteId,
3939
})
4040

41-
const parentPermalink = permalink.split("/").slice(0, -1).join("/")
42-
const parentTitle = parent?.title || ResourceType.Collection
41+
const parentPermalink = useMemo(
42+
() => permalink.split("/").slice(0, -1).join("/"),
43+
[permalink],
44+
)
45+
const parentTitle = useMemo(
46+
() => parent?.title || ResourceType.Collection,
47+
[parent?.title],
48+
)
4349

44-
const siteMap = {
45-
id: "root",
46-
permalink: "/",
47-
lastModified: "2024-10-14T07:05:08.803Z",
48-
layout: "homepage",
49-
title: "An Isomer Site",
50-
summary: "",
51-
children: [
52-
{
53-
id: "collection",
54-
permalink: parentPermalink,
55-
lastModified: "02 May 2023",
56-
layout: "collection",
57-
title: parentTitle,
50+
const siteMap = useMemo(
51+
() =>
52+
({
53+
id: "root",
54+
permalink: "/",
55+
lastModified: currentDate,
56+
layout: "homepage",
57+
title: "An Isomer Site",
5858
summary: "",
59-
collectionPagePageProps: { tagCategories },
6059
children: [
6160
{
62-
id: "9999999",
63-
title,
64-
image,
65-
date,
66-
ref,
67-
summary: summary ?? "",
68-
layout: "link",
69-
permalink,
70-
lastModified: new Date().toString(),
71-
category,
72-
tagged,
61+
id: "collection",
62+
permalink: parentPermalink,
63+
lastModified: currentDate,
64+
layout: "collection",
65+
title: parentTitle,
66+
summary: "",
67+
collectionPagePageProps: { tagCategories },
68+
children: [
69+
{
70+
id: "9999999",
71+
title,
72+
summary: link.description ?? "",
73+
layout: "link",
74+
permalink,
75+
lastModified: currentDate,
76+
...link,
77+
},
78+
],
7379
},
7480
],
75-
},
76-
],
77-
} satisfies IsomerSitemap
81+
}) satisfies IsomerSitemap,
82+
[parentPermalink, parentTitle, tagCategories, link, permalink, title],
83+
)
7884

7985
return (
8086
<ViewportContainer siteId={siteId}>

apps/studio/src/features/editing-experience/components/LinkEditNavbar.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import {
77
Text,
88
} from "@chakra-ui/react"
99
import { Breadcrumb } from "@opengovsg/design-system-react"
10-
import { useAtomValue } from "jotai"
1110

1211
import { ADMIN_NAVBAR_HEIGHT } from "~/constants/layouts"
1312
import { useQueryParse } from "~/hooks/useQueryParse"
1413
import { editLinkSchema } from "~/pages/sites/[siteId]/links/[linkId]"
1514
import { getResourceSubpath } from "~/utils/resource"
1615
import { trpc } from "~/utils/trpc"
17-
import { linkRefAtom } from "../atoms"
1816
import PublishButton from "./PublishButton"
1917

2018
interface NavigationBreadcrumbsProps {
@@ -96,7 +94,6 @@ const NavigationBreadcrumbs = ({
9694

9795
export const LinkEditNavbar = (): JSX.Element => {
9896
const { linkId, siteId } = useQueryParse(editLinkSchema)
99-
const ref = useAtomValue(linkRefAtom)
10097

10198
return (
10299
<Flex
@@ -117,7 +114,7 @@ export const LinkEditNavbar = (): JSX.Element => {
117114

118115
{linkId && siteId && (
119116
<Flex justifyContent={"end"} alignItems={"center"} flex={1}>
120-
<PublishButton pageId={linkId} siteId={siteId} isDisabled={!ref} />
117+
<PublishButton pageId={linkId} siteId={siteId} />
121118
</Flex>
122119
)}
123120
</Flex>

apps/studio/src/features/editing-experience/components/LinkEditorDrawer.tsx

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import type { Static } from "@sinclair/typebox"
2-
import { useEffect, useMemo, useState } from "react"
2+
import { useMemo, useState } from "react"
33
import { Box, Flex, Text, VStack } from "@chakra-ui/react"
44
import { Button, useToast } from "@opengovsg/design-system-react"
55
import { LAYOUT_PAGE_MAP } from "@opengovsg/isomer-components"
6-
import { useAtom, useSetAtom } from "jotai"
76
import isEmpty from "lodash/isEmpty"
87
import isEqual from "lodash/isEqual"
98
import { z } from "zod"
109

11-
import type { CollectionLinkProps } from "../atoms"
1210
import { BRIEF_TOAST_SETTINGS } from "~/constants/toast"
1311
import { useIsUserIsomerAdmin } from "~/hooks/useIsUserIsomerAdmin"
1412
import { useQueryParse } from "~/hooks/useQueryParse"
1513
import { ADMIN_ROLE } from "~/lib/growthbook"
14+
import { CollectionLinkProps } from "~/schemas/collection"
1615
import { ajv } from "~/utils/ajv"
1716
import { safeJsonParse } from "~/utils/safeJsonParse"
1817
import { trpc } from "~/utils/trpc"
19-
import { linkAtom, linkRefAtom } from "../atoms"
2018
import { ActivateRawJsonEditorMode } from "./ActivateRawJsonEditorMode"
2119
import { ErrorProvider, useBuilderErrors } from "./form-builder/ErrorProvider"
2220
import FormBuilder from "./form-builder/FormBuilder"
@@ -162,31 +160,19 @@ const DrawerState = (
162160
}
163161
}
164162

165-
export const LinkEditorDrawer = () => {
163+
interface LinkEditorDrawerProps {
164+
link: CollectionLinkProps
165+
setLink: (link: CollectionLinkProps) => void
166+
initialLinkState: IsomerLinkSchema
167+
}
168+
export const LinkEditorDrawer = ({
169+
initialLinkState,
170+
link,
171+
setLink,
172+
}: LinkEditorDrawerProps) => {
166173
const { linkId, siteId } = useQueryParse(editLinkSchema)
167-
const [data, setLinkAtom] = useAtom(linkAtom)
168174
const utils = trpc.useUtils()
169175
const toast = useToast()
170-
const setLinkRef = useSetAtom(linkRefAtom)
171-
172-
const [{ content, title }] =
173-
trpc.collection.readCollectionLink.useSuspenseQuery(
174-
{
175-
linkId,
176-
siteId,
177-
},
178-
{
179-
refetchOnWindowFocus: false,
180-
},
181-
)
182-
183-
useEffect(() => {
184-
setLinkAtom({
185-
...(content.page as CollectionLinkProps),
186-
title,
187-
})
188-
setLinkRef((content.page as CollectionLinkProps).ref)
189-
}, [content, setLinkAtom, setLinkRef, title])
190176

191177
const { mutate, isPending } =
192178
trpc.collection.updateCollectionLink.useMutation({
@@ -201,21 +187,14 @@ export const LinkEditorDrawer = () => {
201187
},
202188
})
203189

204-
const savedPageState = {
205-
...(content.page as CollectionLinkProps),
206-
title,
207-
}
208-
const handleChange = (data: IsomerLinkSchema) =>
209-
setLinkAtom(data as CollectionLinkProps)
210-
211190
return (
212191
<ErrorProvider>
213192
<DrawerState
214-
savedPageState={savedPageState}
215-
previewPageState={data}
193+
savedPageState={initialLinkState}
194+
previewPageState={link}
216195
isLoading={isPending}
217-
handleChange={handleChange}
218-
handleSaveChanges={() => mutate({ siteId, linkId, ...data })}
196+
handleChange={(data) => setLink(data)}
197+
handleSaveChanges={() => mutate({ siteId, linkId, ...link })}
219198
/>
220199
</ErrorProvider>
221200
)

apps/studio/src/features/editing-experience/components/PreviewWithoutSitemap.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ const FakeLink = forwardRef<HTMLAnchorElement, PropsWithChildren<unknown>>(
3232
),
3333
)
3434

35+
const defaultLastModified = new Date().toISOString()
36+
3537
function SuspendablePreview({
3638
permalink,
37-
lastModified = new Date().toISOString(),
39+
lastModified = defaultLastModified,
3840
siteId,
3941
overrides = {},
4042
siteMap,

apps/studio/src/features/editing-experience/components/PublishButton.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ interface PublishButtonProps extends ButtonProps {
3636
const SuspendablePublishButton = ({
3737
pageId,
3838
siteId,
39-
isDisabled,
4039
...rest
4140
}: PublishButtonProps): JSX.Element => {
4241
const gb = useGrowthBook()
@@ -118,7 +117,7 @@ const SuspendablePublishButton = ({
118117
<Button
119118
variant="solid"
120119
size="sm"
121-
isDisabled={!isChangesPendingPublish || isDisabled}
120+
isDisabled={!isChangesPendingPublish}
122121
isLoading={isPending}
123122
borderRightRadius={
124123
enableScheduledPublishing ? 0 : undefined
@@ -148,9 +147,7 @@ const SuspendablePublishButton = ({
148147
icon={<Icon as={BiChevronDown} boxSize="1rem" />}
149148
size="sm"
150149
variant="solid"
151-
isDisabled={
152-
!isChangesPendingPublish || isDisabled || isPending
153-
}
150+
isDisabled={!isChangesPendingPublish || isPending}
154151
borderLeftRadius={0}
155152
/>
156153
<MenuList>

apps/studio/src/features/editing-experience/components/form-builder/renderers/controls/JsonFormsCategoryControl.tsx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { useFeatureValue } from "@growthbook/growthbook-react"
44
import { and, rankWith, schemaMatches } from "@jsonforms/core"
55
import { withJsonFormsControlProps } from "@jsonforms/react"
66
import { FormLabel, SingleSelect } from "@opengovsg/design-system-react"
7-
import { useSetAtom } from "jotai"
87

98
import Suspense from "~/components/Suspense"
109
import { JSON_FORMS_RANKING } from "~/constants/formBuilder"
11-
import { linkAtom } from "~/features/editing-experience/atoms"
1210
import { collectionItemSchema } from "~/features/editing-experience/schema"
1311
import { useQueryParse } from "~/hooks/useQueryParse"
1412
import { CATEGORY_DROPDOWN_FEATURE_KEY } from "~/lib/growthbook"
@@ -62,26 +60,17 @@ export function JsonFormsCategoryControl({
6260
...props
6361
}: ControlProps) {
6462
const { siteId } = useQueryParse(collectionItemSchema)
65-
const setLink = useSetAtom(linkAtom)
6663
const { enabledSites } = useFeatureValue<{ enabledSites: string[] }>(
6764
CATEGORY_DROPDOWN_FEATURE_KEY,
6865
{ enabledSites: [] },
6966
)
70-
const handleChange: ControlProps["handleChange"] = (path, value: string) => {
71-
props.handleChange(path, value)
72-
setLink((prev) => ({ ...prev, category: value }))
73-
}
7467

7568
const isDropdownEnabled = enabledSites.includes(siteId.toString())
7669
return isDropdownEnabled ? (
7770
<FormControl isRequired={required} gap="0.5rem">
7871
<FormLabel description={description}>{label}</FormLabel>
7972
<Suspense fallback={<Skeleton />}>
80-
<SuspendableJsonFormsCategoryControl
81-
{...props}
82-
label={label}
83-
handleChange={handleChange}
84-
/>
73+
<SuspendableJsonFormsCategoryControl {...props} label={label} />
8574
</Suspense>
8675
</FormControl>
8776
) : (
@@ -90,7 +79,6 @@ export function JsonFormsCategoryControl({
9079
description={description}
9180
required={required}
9281
label={label}
93-
handleChange={handleChange}
9482
/>
9583
)
9684
}

0 commit comments

Comments
 (0)