From 0f8d9048660a3900eff327edebde885f9722972c Mon Sep 17 00:00:00 2001 From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:35:39 +0800 Subject: [PATCH 01/52] feat(announcements): add SCSS styles from template --- .../homepage/announcements.scss | 28 ++++++++++--------- src/styles/isomer-template/styles.scss | 4 +-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/styles/isomer-template/homepage/announcements.scss b/src/styles/isomer-template/homepage/announcements.scss index 5494a51ce..8ec772a0b 100644 --- a/src/styles/isomer-template/homepage/announcements.scss +++ b/src/styles/isomer-template/homepage/announcements.scss @@ -16,20 +16,22 @@ } .announcements-announcement-link { - color: var(--site-secondary-color); - font-size: 1.125rem; - font-weight: 600; - line-height: 1.5rem; - letter-spacing: 0.27px; - text-decoration-line: underline; - text-transform: capitalize; - text-underline-offset: 0.25rem; + text-transform: unset !important; + letter-spacing: 0.27px !important; } -.announcements-announcement-link:hover { - color: var(--site-secondary-color-hover); -} +@media screen and (min-width: 1024px) { + .announcements-announcement-row-px-desktop { + padding-left: 0.75rem; + padding-right: 0.75rem; + } + + .announcements-announcement-col-px-desktop { + padding-left: 1.5rem; + padding-right: 1.5rem; + } -a[target="_blank"].announcements-announcement-link::after { - content: unset; + .announcements-announcement-title-mr-desktop { + margin-right: 1.5rem; + } } diff --git a/src/styles/isomer-template/styles.scss b/src/styles/isomer-template/styles.scss index 801cb90c8..43e0d3bac 100644 --- a/src/styles/isomer-template/styles.scss +++ b/src/styles/isomer-template/styles.scss @@ -8,6 +8,6 @@ $breakpoints: ( xxl: 1408px, ); -@import "helpers"; +@use "helpers"; -@import "homepage/announcements"; +@use "homepage/announcements"; From 8f1218b37a1e96e4e7000f503eb408c050ac0756 Mon Sep 17 00:00:00 2001 From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:36:34 +0800 Subject: [PATCH 02/52] feat(announcements): introduce preview component --- src/layouts/EditHomepage/HomepagePreview.tsx | 2 +- .../homepage/AnnouncementsSection.tsx | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/layouts/EditHomepage/HomepagePreview.tsx b/src/layouts/EditHomepage/HomepagePreview.tsx index 0c3ab93e8..176f0045c 100644 --- a/src/layouts/EditHomepage/HomepagePreview.tsx +++ b/src/layouts/EditHomepage/HomepagePreview.tsx @@ -5,7 +5,7 @@ import { Ref, useState } from "react" import editorStyles from "styles/isomer-cms/pages/Editor.module.scss" import { TemplateAnnouncementsSection } from "templates/homepage/AnnouncementsSection" -import { TemplateHeroSection } from "templates/homepage/HeroSection" +import TemplateHeroSection from "templates/homepage/HeroSection" import TemplateInfobarSection from "templates/homepage/InfobarSection" import TemplateInfopicLeftSection from "templates/homepage/InfopicLeftSection" import TemplateInfopicRightSection from "templates/homepage/InfopicRightSection" diff --git a/src/templates/homepage/AnnouncementsSection.tsx b/src/templates/homepage/AnnouncementsSection.tsx index 3b031ef79..bf1e56253 100644 --- a/src/templates/homepage/AnnouncementsSection.tsx +++ b/src/templates/homepage/AnnouncementsSection.tsx @@ -33,20 +33,22 @@ export const TemplateAnnouncementsSection = forwardRef< className={getClassNames(editorStyles, [ `bp-section`, sectionIndex % 2 === 1 ? "bg-newssection" : "", - "px-14", - "px-md-24", - "py-24", ])} >
-
+
<> {subtitle && (

{announcement.announcement}

{announcement.link_text && announcement.link_url && (
{announcement.link_text}
From 0599487e9c9db3294e505f1bb4017c9a3cfb66a4 Mon Sep 17 00:00:00 2001 From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com> Date: Tue, 19 Sep 2023 20:14:44 +0800 Subject: [PATCH 03/52] chore: sync padding and margin helpers --- src/styles/isomer-template/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/isomer-template/styles.scss b/src/styles/isomer-template/styles.scss index 43e0d3bac..e353e9bce 100644 --- a/src/styles/isomer-template/styles.scss +++ b/src/styles/isomer-template/styles.scss @@ -8,6 +8,6 @@ $breakpoints: ( xxl: 1408px, ); -@use "helpers"; +@import "helpers"; @use "homepage/announcements"; From 4064e7919343484a809d3cb6d4ed9e81b6d928f3 Mon Sep 17 00:00:00 2001 From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com> Date: Tue, 19 Sep 2023 20:15:08 +0800 Subject: [PATCH 04/52] chore: sync template changes to CMS preview --- .../homepage/announcements.scss | 28 +++++++++---------- src/styles/isomer-template/styles.scss | 2 +- .../homepage/AnnouncementsSection.tsx | 28 +++++++++---------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/styles/isomer-template/homepage/announcements.scss b/src/styles/isomer-template/homepage/announcements.scss index 8ec772a0b..5494a51ce 100644 --- a/src/styles/isomer-template/homepage/announcements.scss +++ b/src/styles/isomer-template/homepage/announcements.scss @@ -16,22 +16,20 @@ } .announcements-announcement-link { - text-transform: unset !important; - letter-spacing: 0.27px !important; + color: var(--site-secondary-color); + font-size: 1.125rem; + font-weight: 600; + line-height: 1.5rem; + letter-spacing: 0.27px; + text-decoration-line: underline; + text-transform: capitalize; + text-underline-offset: 0.25rem; } -@media screen and (min-width: 1024px) { - .announcements-announcement-row-px-desktop { - padding-left: 0.75rem; - padding-right: 0.75rem; - } - - .announcements-announcement-col-px-desktop { - padding-left: 1.5rem; - padding-right: 1.5rem; - } +.announcements-announcement-link:hover { + color: var(--site-secondary-color-hover); +} - .announcements-announcement-title-mr-desktop { - margin-right: 1.5rem; - } +a[target="_blank"].announcements-announcement-link::after { + content: unset; } diff --git a/src/styles/isomer-template/styles.scss b/src/styles/isomer-template/styles.scss index e353e9bce..801cb90c8 100644 --- a/src/styles/isomer-template/styles.scss +++ b/src/styles/isomer-template/styles.scss @@ -10,4 +10,4 @@ $breakpoints: ( @import "helpers"; -@use "homepage/announcements"; +@import "homepage/announcements"; diff --git a/src/templates/homepage/AnnouncementsSection.tsx b/src/templates/homepage/AnnouncementsSection.tsx index bf1e56253..9ccc1e335 100644 --- a/src/templates/homepage/AnnouncementsSection.tsx +++ b/src/templates/homepage/AnnouncementsSection.tsx @@ -33,22 +33,20 @@ export const TemplateAnnouncementsSection = forwardRef< className={getClassNames(editorStyles, [ `bp-section`, sectionIndex % 2 === 1 ? "bg-newssection" : "", + "px-14", + "px-md-24", + "py-24", ])} >
-
+
<> {subtitle && (

{announcement.announcement}

{announcement.link_text && announcement.link_url && (
{announcement.link_text}
From a4f214444fdf5dd3b87d16c895b78f1a3da2b688 Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:06:28 +0800 Subject: [PATCH 05/52] feat(announcements): define shape --- src/layouts/EditHomepage/constants.ts | 20 ++++++++++++++++++ src/types/homepage.ts | 30 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/layouts/EditHomepage/constants.ts b/src/layouts/EditHomepage/constants.ts index 59a7c1e34..4f61ae10a 100644 --- a/src/layouts/EditHomepage/constants.ts +++ b/src/layouts/EditHomepage/constants.ts @@ -3,6 +3,26 @@ export const RESOURCES_SECTION = { subtitle: "Add a preview and link to your Resource Room", id: "resources", } as const +export const LocalDateTimeNow = new Date().toLocaleString("en-SG", { + timeZone: "Asia/Singapore", +}) + +export const ANNOUNCEMENT_SECTION = { + title: "Announcement", + date: LocalDateTimeNow, + announcement: "Announcement content", + linkText: "Learn more", + linkUrl: "Insert /page-url of https://", +} as const + +export type AnnouncementSectionType = typeof ANNOUNCEMENT_SECTION +export const ANNOUNCEMENT_BLOCK = { + title: "New announcements block", + addSectionTitle: "Announcements", + id: "announcementBlock", + subtitle: "Add a list of announcements with dates", + announcements: [] as AnnouncementSectionType[], +} as const export const INFOBAR_SECTION = { title: "Infobar", diff --git a/src/types/homepage.ts b/src/types/homepage.ts index b7497090d..7bad40c53 100644 --- a/src/types/homepage.ts +++ b/src/types/homepage.ts @@ -20,6 +20,14 @@ export type HighlightOption = { url: string } +export type AnnouncementOption = { + title: string + date: string + announcementContent: string + link_text: string + link_url: string +} + interface BaseHeroSection { background: string subtitle: string @@ -63,16 +71,10 @@ export interface ResourcesSection { button?: string } -export interface AnnouncementsSection { +export interface AnnouncementBlockSection { title?: string subtitle?: string - announcement_items?: Array<{ - title?: string - date?: string - announcement?: string - link_text?: string - link_url?: string - }> + announcement_items?: AnnouncementOption[] } export interface HomepageDto { @@ -97,7 +99,11 @@ export interface HomepageDto { sha: string } -export type EditorHomepageElement = "section" | "dropdownelem" | "highlight" +export type EditorHomepageElement = + | "section" + | "dropdownelem" + | "highlight" + | "announcement" export type PossibleEditorSections = IterableElement< | EditorHomepageState["frontMatter"]["sections"] | EditorHeroDropdownSection["dropdown"]["options"] @@ -156,6 +162,10 @@ export const EditorHomepageFrontmatterSection = { section: EditorHomepageFrontmatterSection ): section is AnnouncementsFrontmatterSection => !!(section as AnnouncementsFrontmatterSection).announcements, + isAnnouncement: ( + section: PossibleEditorSections + ): section is AnnouncementOption => + !!(section as AnnouncementOption).announcementContent, } export interface EditorHomepageState { @@ -166,10 +176,12 @@ export interface EditorHomepageState { sections: unknown[] dropdownElems: unknown[] highlights: unknown[] + announcements: unknown[] } displaySections: unknown[] displayDropdownElems: unknown[] displayHighlights: unknown[] + displayAnnouncements: unknown[] } export interface EditorHeroDropdownSection { From aae494e6cc669aeda264e0a7bfeba89a013c008e Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:08:48 +0800 Subject: [PATCH 06/52] feat(validators): announcement validators --- .../PageSettingsModal/PageSettingsSchema.jsx | 7 +- src/utils/validators.js | 134 ++++++++++++++++-- 2 files changed, 125 insertions(+), 16 deletions(-) diff --git a/src/components/PageSettingsModal/PageSettingsSchema.jsx b/src/components/PageSettingsModal/PageSettingsSchema.jsx index 08c9dab1d..00e966617 100644 --- a/src/components/PageSettingsModal/PageSettingsSchema.jsx +++ b/src/components/PageSettingsModal/PageSettingsSchema.jsx @@ -5,7 +5,7 @@ import { permalinkRegexTest, specialCharactersRegexTest, jekyllFirstCharacterRegexTest, - dateRegexTest, + resourceDateRegexTest, PAGE_SETTINGS_PERMALINK_MIN_LENGTH, PAGE_SETTINGS_PERMALINK_MAX_LENGTH, PAGE_SETTINGS_TITLE_MIN_LENGTH, @@ -71,7 +71,10 @@ export const PageSettingsSchema = (existingTitlesArray = []) => layout ? schema .required("Date is required") - .matches(dateRegexTest, "Date must be formatted as YYYY-MM-DD") + .matches( + resourceDateRegexTest, + "Date must be formatted as YYYY-MM-DD" + ) .test( "Date cannot be in the future", "Date cannot be in the future", diff --git a/src/utils/validators.js b/src/utils/validators.js index 752ffcde0..3f0022d1f 100644 --- a/src/utils/validators.js +++ b/src/utils/validators.js @@ -35,7 +35,8 @@ export const PASSWORD_REGEX = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{12,}$" const EMAIL_REGEX = '^(([^<>()\\[\\]\\.,;:\\s@\\"]+(\\.[^<>()\\[\\]\\.,;:\\s@\\"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z-0-9]+\\.)+[a-zA-Z]{2,}))$' -const DATE_REGEX = "^([0-9]{4}-[0-9]{2}-[0-9]{2})$" +const DATE_REGEX_Y_M_D = "^([0-9]{4}-[0-9]{2}-[0-9]{2})$" +const DATE_REGEX_D_M_Y = "^([0-9]{2}/[0-9]{2}/[0-9]{4})$" const ALPHABETS_ONLY_REGEX = '^[a-zA-Z" "\\._-]+$' const ALPHANUMERICS_ONLY_REGEX = '^[a-zA-Z0-9" "\\._\\-:]+$' @@ -43,7 +44,8 @@ export const permalinkRegexTest = RegExp(PERMALINK_REGEX) export const phoneRegexTest = RegExp(PHONE_REGEX) export const tollFreePhoneRegexText = RegExp(TOLL_FREE_PHONE_REGEX) export const emailRegexTest = RegExp(EMAIL_REGEX) -export const dateRegexTest = RegExp(DATE_REGEX) +export const resourceDateRegexTest = RegExp(DATE_REGEX_Y_M_D) +export const announcementDateRegexTest = RegExp(DATE_REGEX_D_M_Y) export const alphabetsRegexTest = RegExp(ALPHABETS_ONLY_REGEX) export const alphanumericRegexTest = RegExp(ALPHANUMERICS_ONLY_REGEX) export const fileNameRegexTest = /^[a-zA-Z0-9" "_-]+$/ @@ -112,10 +114,16 @@ const HERO_TITLE_MIN_LENGTH = 0 const HERO_TITLE_MAX_LENGTH = 60 const HERO_SUBTITLE_MIN_LENGTH = 0 const HERO_SUBTITLE_MAX_LENGTH = 160 -// const HERO_BUTTON_TEXT_MIN_LENGTH = 2; -// const HERO_BUTTON_TEXT_MAX_LENGTH = 30; const HERO_DROPDOWN_MIN_LENGTH = 0 const HERO_DROPDOWN_MAX_LENGTH = 30 +// Announcement +const ANNOUNCEMENT_BLOCK_SUBTITLE_MAX_LENGTH = 30 +const ANNOUNCEMENT_BLOCK_TITLE_MAX_LENGTH = 50 +const ANNOUNCEMENT_TITLE_MIN_LENGTH = 0 +const ANNOUNCEMENT_TITLE_MAX_LENGTH = 100 +const ANNOUNCEMENT_DESCRIPTION_MIN_LENGTH = 0 +const ANNOUNCEMENT_DESCRIPTION_MAX_LENGTH = 100 +const ANNOUNCEMENT_LINK_TEXT_URL_MAX_LENGTH = 50 // Contact Us Editor // =============== @@ -127,7 +135,6 @@ const CONTACT_DESCRIPTION_MAX_LENGTH = 400 // Locations const LOCATION_TITLE_MIN_LENGTH = 1 const LOCATION_TITLE_MAX_LENGTH = 30 -// const LOCATION_ADDRESS_MIN_LENGTH = 2 const LOCATION_ADDRESS_MAX_LENGTH = 30 const LOCATION_OPERATING_DAYS_MIN_LENGTH = 2 const LOCATION_OPERATING_DAYS_MAX_LENGTH = 30 @@ -171,6 +178,20 @@ export const MEDIA_SETTINGS_TITLE_MIN_LENGTH = 6 export const MEDIA_SETTINGS_TITLE_MAX_LENGTH = 100 export const MEDIA_FILE_MAX_SIZE = 5242880 // 5MB -> 5242880B +const validateNonFutureResourceDate = (dateStr) => { + const today = new Date(moment().tz("Asia/Singapore").format("YYYY-MM-DD")) + const chosenDate = new Date(dateStr) + const daysDiff = today - chosenDate + return daysDiff >= 0 +} + +const validateNonFutureAnnouncementDate = (dateStr) => { + const today = new Date(moment().tz("Asia/Singapore").format("YYYY-MM-DD")) + const IsoDateStr = dateStr.split("/").reverse().join("-") + const chosenDate = new Date(IsoDateStr) + return today > chosenDate +} + // Homepage Editor // ========== // Returns new errors.highlights[index] object @@ -218,6 +239,57 @@ const validateHighlights = (highlightError, field, value) => { return newHighlightError } +const validateAnnouncements = (announcementError, field, value) => { + const newAnnouncementError = announcementError + let errorMessage = "" + switch (field) { + case "title": { + // Title is too short + if (value.length < ANNOUNCEMENT_TITLE_MIN_LENGTH) { + errorMessage = `The title should not be empty.` + } + // Title is too long + if (value.length > ANNOUNCEMENT_TITLE_MAX_LENGTH) { + errorMessage = `The title should be shorter than ${ANNOUNCEMENT_TITLE_MAX_LENGTH} characters.` + } + break + } + case "date": { + if (!validateNonFutureAnnouncementDate(value)) { + errorMessage = `Date cannot be in the future` + } + if (!announcementDateRegexTest.test(value)) { + errorMessage = `Date is invalid` + } + break + } + case "announcementContent": { + // Announcement content is too short + if (value.length < ANNOUNCEMENT_DESCRIPTION_MIN_LENGTH) { + errorMessage = `The announcement content should not be empty.` + } + // Announcement content is too long + if (value.length > ANNOUNCEMENT_DESCRIPTION_MAX_LENGTH) { + errorMessage = `You have exceeded the number of characters allowed (${value.length}/${ANNOUNCEMENT_DESCRIPTION_MAX_LENGTH})` + } + break + } + case "linkText": { + // Link text is too long + if (value.length > ANNOUNCEMENT_LINK_TEXT_URL_MAX_LENGTH) { + errorMessage = `The link text should be shorter than ${ANNOUNCEMENT_LINK_TEXT_URL_MAX_LENGTH} characters.` + } + break + } + + default: + break + } + + newAnnouncementError[field] = errorMessage + return newAnnouncementError +} + // Returns errors.dropdownElems[dropdownsIndex] object const validateDropdownElems = (dropdownElemError, field, value) => { const newDropdownElemError = dropdownElemError @@ -494,6 +566,37 @@ const validateInfopicSection = (sectionError, sectionType, field, value) => { return newSectionError } +const validateAnnouncementBlockSection = ( + sectionError, + sectionType, + field, + value +) => { + const newSectionError = sectionError + let errorMessage = "" + + switch (field) { + case "title": { + // Title is too long + if (value.length > ANNOUNCEMENT_BLOCK_TITLE_MAX_LENGTH) { + errorMessage = `Title should be shorter than ${ANNOUNCEMENT_BLOCK_TITLE_MAX_LENGTH} characters.` + } + break + } + case "subtitle": { + // Subtitle is too long + if (value.length > ANNOUNCEMENT_BLOCK_SUBTITLE_MAX_LENGTH) { + errorMessage = `Subtitle should be shorter than ${ANNOUNCEMENT_BLOCK_SUBTITLE_MAX_LENGTH} characters.` + } + break + } + default: + break + } + newSectionError[sectionType][field] = errorMessage + return newSectionError +} + const validateSections = (sectionError, sectionType, field, value) => { let newSectionError = sectionError switch (sectionType) { @@ -533,6 +636,15 @@ const validateSections = (sectionError, sectionType, field, value) => { ) break } + case "announcementBlock": { + newSectionError = validateAnnouncementBlockSection( + sectionError, + sectionType, + field, + value + ) + break + } default: break } @@ -803,13 +915,6 @@ const validateDayOfMonth = (month, day) => { } } -const validateNonFutureDate = (dateStr) => { - const today = new Date(moment().tz("Asia/Singapore").format("YYYY-MM-DD")) - const chosenDate = new Date(dateStr) - const daysDiff = today - chosenDate - return daysDiff >= 0 -} - const validateResourceSettings = (id, value, folderOrderArray) => { let errorMessage = "" switch (id) { @@ -836,11 +941,11 @@ const validateResourceSettings = (id, value, folderOrderArray) => { } case "date": { // Date is in the future - if (!validateNonFutureDate(value)) { + if (!validateNonFutureResourceDate(value)) { errorMessage = "Selected date is greater than today's date." } // Date is in wrong format - if (!dateRegexTest.test(value)) { + if (!resourceDateRegexTest.test(value)) { errorMessage = "The date should be in the format YYYY-MM-DD." } else { const dateTokens = value.split("-") @@ -964,6 +1069,7 @@ export { validateLocationType, validateLink, validateHighlights, + validateAnnouncements, validateDropdownElems, validateSections, validatePageSettings, From 563a2a277fe51b4899c1f20eabc3207ad4402ef8 Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:10:39 +0800 Subject: [PATCH 07/52] feat(announcement): add announcement block --- .../components/Homepage/AnnouncementBody.tsx | 225 ++++++++++++++++++ .../Homepage/AnnouncementSection.tsx | 69 ++++++ 2 files changed, 294 insertions(+) create mode 100644 src/layouts/components/Homepage/AnnouncementBody.tsx create mode 100644 src/layouts/components/Homepage/AnnouncementSection.tsx diff --git a/src/layouts/components/Homepage/AnnouncementBody.tsx b/src/layouts/components/Homepage/AnnouncementBody.tsx new file mode 100644 index 000000000..1c36fc240 --- /dev/null +++ b/src/layouts/components/Homepage/AnnouncementBody.tsx @@ -0,0 +1,225 @@ +import { Text, Box, FormControl, VStack } from "@chakra-ui/react" +import { DragDropContext } from "@hello-pangea/dnd" +import { + FormLabel, + Input, + FormErrorMessage, + Button, + DateRangePicker, + DatePicker, + Textarea, +} from "@opengovsg/design-system-react" +import _ from "lodash" +import { BiPlus } from "react-icons/bi" + +import { useEditableContext } from "contexts/EditableContext" + +import { Editable } from "layouts/components/Editable" + +import { Announcement } from "types/announcements" +import { AnnouncementOption } from "types/homepage" + +const MAX_ANNOUNCEMENTS = 5 + +interface AnnouncementBodyFormFields { + button: string + url: string +} +interface AnnouncementBodyProps extends AnnouncementBodyFormFields { + errors: AnnouncementBodyFormFields & { + announcements: AnnouncementOption[] + } + announcements: Partial[] +} + +export const AnnouncementBody = ({ + errors, + button, + url, + announcements = [], +}: AnnouncementBodyProps) => { + const { + onDragEnd, + onChange, + onCreate, + onDelete, + onDisplay, + } = useEditableContext() + console.log("in announcement body") + console.log({ errors, button, url, announcements }) + return ( + + + + + + Announcements + + + You can display up to 5 announcements at a time. Newly added + announcements are shown on the top of the list + + + onDisplay("announcement")}> + + + {announcements.map( + ( + { + title: announcementTitle, + date: announcementDate, + announcementContent, + linkText: announcementLinkText, + linkUrl: announcementLinkUrl, + }, + announcementIndex + ) => { + return ( + + + + Title + + + {errors.announcements[announcementIndex].title} + + + + Date + { + console.log(value) + onChange({ + target: { + id: `announcement-${announcementIndex}-date`, + value, + }, + }) + }} + /> + + {errors.announcements[announcementIndex].date} + + + + Announcement +