-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add disclosure for settings (#451)
### TL;DR This PR introduces a folder settings modal in the folder page. ### What changed? - Imported necessary components like `Modal`, `Input`, `FormControl`, etc. - Added folder settings modal state management using `useDisclosure` hook. - Created a new `FolderSettingsModal` component for folder settings. - Utilized the `useZodForm` for form handling and validation. - Implemented the mutation to save folder settings changes to the database. - Updated schema and router to support editing folder's title and permalink. ### How to test? 1. Navigate to the folder page. 2. Click on the `Folder settings` button. 3. Update the title and permalink in the modal. 4. Click `Save changes` and verify if the changes are reflected. ### Why make this change? This change adds the ability to edit the folder title and permalink directly from the folder page, enhancing the user experience by providing a straightforward way to manage folder settings. --- https://github.com/user-attachments/assets/206c6663-663b-4c8d-a002-25dfc2c25a72
- Loading branch information
Showing
9 changed files
with
300 additions
and
35 deletions.
There are no files selected for viewing
194 changes: 194 additions & 0 deletions
194
apps/studio/src/features/dashboard/components/FolderSettingsModal/FolderSettingsModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { Suspense, useEffect } from "react" | ||
import { | ||
Box, | ||
FormControl, | ||
FormErrorMessage, | ||
FormHelperText, | ||
FormLabel, | ||
Icon, | ||
Input, | ||
Modal, | ||
ModalBody, | ||
ModalCloseButton, | ||
ModalContent, | ||
ModalFooter, | ||
ModalHeader, | ||
ModalOverlay, | ||
Skeleton, | ||
VStack, | ||
} from "@chakra-ui/react" | ||
import { Button, useToast } from "@opengovsg/design-system-react" | ||
import { BiLink } from "react-icons/bi" | ||
|
||
import { generateResourceUrl } from "~/features/editing-experience/components/utils" | ||
import { useZodForm } from "~/lib/form" | ||
import { | ||
baseEditFolderSchema, | ||
MAX_FOLDER_PERMALINK_LENGTH, | ||
MAX_FOLDER_TITLE_LENGTH, | ||
} from "~/schemas/folder" | ||
import { trpc } from "~/utils/trpc" | ||
|
||
interface FolderSettingsModalProps { | ||
isOpen: boolean | ||
onClose: () => void | ||
siteId: string | ||
resourceId: number | ||
} | ||
export const FolderSettingsModal = ({ | ||
isOpen, | ||
onClose, | ||
siteId, | ||
resourceId, | ||
}: FolderSettingsModalProps) => { | ||
return ( | ||
<Modal isOpen={isOpen} onClose={onClose}> | ||
<ModalOverlay /> | ||
<SuspendableModalContent | ||
isOpen={isOpen} | ||
siteId={siteId} | ||
resourceId={resourceId} | ||
onClose={onClose} | ||
/> | ||
</Modal> | ||
) | ||
} | ||
|
||
const SuspendableModalContent = ({ | ||
isOpen, | ||
onClose, | ||
siteId, | ||
resourceId, | ||
}: FolderSettingsModalProps) => { | ||
const [{ title: originalTitle, permalink: originalPermalink, parentId }] = | ||
trpc.folder.readFolder.useSuspenseQuery({ | ||
siteId: parseInt(siteId), | ||
resourceId, | ||
}) | ||
const { setValue, register, handleSubmit, watch, formState, getFieldState } = | ||
useZodForm({ | ||
defaultValues: { | ||
title: originalTitle, | ||
permalink: originalPermalink, | ||
}, | ||
schema: baseEditFolderSchema.omit({ siteId: true, resourceId: true }), | ||
}) | ||
const { errors, isValid } = formState | ||
const utils = trpc.useUtils() | ||
const toast = useToast() | ||
const { mutate, isLoading } = trpc.folder.editFolder.useMutation({ | ||
onSettled: onClose, | ||
onSuccess: async () => { | ||
await utils.site.list.invalidate() | ||
await utils.resource.list.invalidate() | ||
await utils.resource.getChildrenOf.invalidate({ | ||
resourceId: parentId ? String(parentId) : null, | ||
}) | ||
await utils.folder.readFolder.invalidate() | ||
toast({ title: "Folder created!", status: "success" }) | ||
}, | ||
onError: (err) => { | ||
toast({ | ||
title: "Failed to create folder", | ||
status: "error", | ||
// TODO: check if this property is correct | ||
description: err.message, | ||
}) | ||
}, | ||
}) | ||
|
||
const onSubmit = handleSubmit((data) => { | ||
mutate({ ...data, resourceId: String(resourceId), siteId }) | ||
}) | ||
|
||
const [title, permalink] = watch(["title", "permalink"]) | ||
|
||
useEffect(() => { | ||
const permalinkFieldState = getFieldState("permalink") | ||
// This allows the syncing to happen only when the page title is not dirty | ||
// Dirty means user has changed the value AND the value is not the same as the default value of "". | ||
// Once the value has been cleared, dirty state will reset. | ||
if (!permalinkFieldState.isDirty) { | ||
setValue("permalink", generateResourceUrl(title || ""), { | ||
shouldValidate: !!title, | ||
}) | ||
} | ||
}, [getFieldState, setValue, title]) | ||
|
||
return ( | ||
<Suspense fallback={<Skeleton />}> | ||
<ModalContent key={String(isOpen)}> | ||
<form onSubmit={onSubmit}> | ||
<ModalHeader>Edit "{originalTitle}"</ModalHeader> | ||
<ModalCloseButton size="sm" /> | ||
<ModalBody> | ||
<VStack alignItems="flex-start" spacing="1.5rem"> | ||
<FormControl isInvalid={!!errors.title}> | ||
<FormLabel color="base.content.strong"> | ||
Folder name | ||
<FormHelperText color="base.content.default"> | ||
This will be the title of the index page of your folder. | ||
</FormHelperText> | ||
</FormLabel> | ||
|
||
<Input | ||
placeholder="This is a title for your new folder" | ||
{...register("title")} | ||
/> | ||
{errors.title?.message ? ( | ||
<FormErrorMessage>{errors.title.message}</FormErrorMessage> | ||
) : ( | ||
<FormHelperText mt="0.5rem" color="base.content.medium"> | ||
{MAX_FOLDER_TITLE_LENGTH - (title || "").length} characters | ||
left | ||
</FormHelperText> | ||
)} | ||
</FormControl> | ||
<FormControl isInvalid={!!errors.permalink}> | ||
<FormLabel color="base.content.strong"> | ||
Folder URL | ||
<FormHelperText color="base.content.default"> | ||
This will be applied to every child under this folder. | ||
</FormHelperText> | ||
</FormLabel> | ||
<Input | ||
placeholder="This is a url for your new page" | ||
{...register("permalink")} | ||
/> | ||
{errors.permalink?.message && ( | ||
<FormErrorMessage> | ||
{errors.permalink.message} | ||
</FormErrorMessage> | ||
)} | ||
|
||
<Box | ||
mt="0.5rem" | ||
py="0.5rem" | ||
px="0.75rem" | ||
bg="interaction.support.disabled" | ||
> | ||
<Icon mr="0.5rem" as={BiLink} /> | ||
{permalink} | ||
</Box> | ||
|
||
<FormHelperText mt="0.5rem" color="base.content.medium"> | ||
{MAX_FOLDER_PERMALINK_LENGTH - (permalink || "").length}{" "} | ||
characters left | ||
</FormHelperText> | ||
</FormControl> | ||
</VStack> | ||
</ModalBody> | ||
|
||
<ModalFooter> | ||
<Button mr={3} onClick={onClose} variant="clear"> | ||
Close | ||
</Button> | ||
<Button isLoading={isLoading} isDisabled={!isValid} type="submit"> | ||
Save changes | ||
</Button> | ||
</ModalFooter> | ||
</form> | ||
</ModalContent> | ||
</Suspense> | ||
) | ||
} |
1 change: 1 addition & 0 deletions
1
apps/studio/src/features/dashboard/components/FolderSettingsModal/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./FolderSettingsModal" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.