diff --git a/CHANGELOG.md b/CHANGELOG.md
index 915ed4caa5..8d7c99fa2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,14 +4,24 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
+#### [v6.271.0](https://github.com/opengovsg/FormSG/compare/v6.270.0...v6.271.0)
+
+- feat: respondent copy v3 (phase 1) [`#8854`](https://github.com/opengovsg/FormSG/pull/8854)
+- fix: remove noisy chromatic diffs due to dynamic dates and form link [`#8903`](https://github.com/opengovsg/FormSG/pull/8903)
+- build: release al2 6.270.0 back to develop [`#8904`](https://github.com/opengovsg/FormSG/pull/8904)
+- build: release v6.270.0 [`#8902`](https://github.com/opengovsg/FormSG/pull/8902)
+
#### [v6.270.0](https://github.com/opengovsg/FormSG/compare/v6.269.0...v6.270.0)
+> 14 November 2025
+
- feat: remove un-neeeded dd tracing and browser installation in the setup [`#8901`](https://github.com/opengovsg/FormSG/pull/8901)
- feat: remove signature beta label [`#8898`](https://github.com/opengovsg/FormSG/pull/8898)
- feat: enable save draft instrumentation basic again [`#8899`](https://github.com/opengovsg/FormSG/pull/8899)
- fix(copy): fixed copy for toast message on form open [`#8900`](https://github.com/opengovsg/FormSG/pull/8900)
- build: merge v6.269.0 back to develop [`#8897`](https://github.com/opengovsg/FormSG/pull/8897)
- build: release v6.269.0 [`#8896`](https://github.com/opengovsg/FormSG/pull/8896)
+- chore: bump version to v6.270.0 [`f2df60b`](https://github.com/opengovsg/FormSG/commit/f2df60b99a67cc54c8abaeb8c824a339a85934aa)
#### [v6.269.0](https://github.com/opengovsg/FormSG/compare/v6.268.1...v6.269.0)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1094967870..603a2f5299 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "form-frontend",
- "version": "6.270.0",
+ "version": "6.271.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "form-frontend",
- "version": "6.270.0",
+ "version": "6.271.0",
"hasInstallScript": true,
"dependencies": {
"@chakra-ui/react": "^2.8.2",
diff --git a/frontend/package.json b/frontend/package.json
index b3b5c5c04f..fdb00218e1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "form-frontend",
- "version": "6.270.0",
+ "version": "6.271.0",
"homepage": ".",
"type": "module",
"private": true,
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditEmail/EditEmail.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditEmail/EditEmail.tsx
index 0d170ac342..364e18df70 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditEmail/EditEmail.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditEmail/EditEmail.tsx
@@ -145,7 +145,9 @@ export const EditEmail = ({ field }: EditEmailProps): JSX.Element => {
form?.responseMode === FormResponseMode.Email || isPaymentDisabledForm
const pdfResponseToggleDescription = isPdfResponseEnabled
- ? undefined
+ ? t(
+ 'features.adminForm.sidebar.fields.email.emailConfirmation.includeResponseDescription',
+ )
: t(
'features.adminForm.sidebar.fields.email.emailConfirmation.includePdfResponseWarning',
)
@@ -185,52 +187,6 @@ export const EditEmail = ({ field }: EditEmailProps): JSX.Element => {
)}
/>
-
-
-
-
-
-
-
- {watchedHasAllowedEmailDomains && (
-
-
- {t(
- 'features.adminForm.sidebar.fields.email.restrictEmailDomains.inputLabel',
- )}
-
-
-
- {errors?.allowedEmailDomains?.message}
-
-
- )}
-
{
{watchedHasAutoReply && (
<>
+
+
+
Subject
@@ -265,9 +233,8 @@ export const EditEmail = ({ field }: EditEmailProps): JSX.Element => {
)}
@@ -280,23 +247,66 @@ export const EditEmail = ({ field }: EditEmailProps): JSX.Element => {
-
-
-
>
)}
+
+
+
+
+
+
+
+ {watchedHasAllowedEmailDomains && (
+
+
+ {t(
+ 'features.adminForm.sidebar.fields.email.restrictEmailDomains.inputLabel',
+ )}
+
+
+
+ {errors?.allowedEmailDomains?.message}
+
+
+ )}
+
- {window.location.origin}/{formId}
+ {/* RATIONALE: Prevent noisy diffs from being detected during storybook snapshot comparisons due to dynamic form link. */}
+ {isTest
+ ? MOCK_CONSTANT_FORM_LINK
+ : `${window.location.origin}/${formId}`}
diff --git a/frontend/src/features/public-form/PublicFormProvider.tsx b/frontend/src/features/public-form/PublicFormProvider.tsx
index 3cf995d5ab..36ed420e75 100644
--- a/frontend/src/features/public-form/PublicFormProvider.tsx
+++ b/frontend/src/features/public-form/PublicFormProvider.tsx
@@ -106,6 +106,9 @@ interface PublicFormProviderProps {
isPublicFormPage?: boolean
}
+const DATE_TIME_FORMAT_STRING = 'do MMM yyyy, h:mm:ss a'
+const MOCK_CONSTANT_LAST_SAVED_DATETIME = '2025-11-14T12:00:00.000Z'
+
export function useCommonFormProvider(formId: string) {
// For mobile section sidebar
const {
@@ -1388,6 +1391,16 @@ export const PublicFormProvider = ({
return
}
+ // RATIONALE: Prevent noisy diffs from being detected during storybook snapshot comparisons due to dynamic date time.
+ const draftLastSavedDateTime =
+ draftSubmission?.lastUpdated && isTest
+ ? MOCK_CONSTANT_LAST_SAVED_DATETIME
+ : draftSubmission?.lastUpdated
+
+ const draftLastSavedDateTimeString = draftLastSavedDateTime
+ ? format(new Date(draftLastSavedDateTime), DATE_TIME_FORMAT_STRING)
+ : undefined
+
return (
{
const { t } = useTranslation()
+ const { errors } = useFormState({ name: schema._id })
// TODO: decide how to combine with field-validations en-sg.ts
const validationErrorMessages = t(
'features.publicForm.components.fields.email.validation',
@@ -51,6 +55,7 @@ export const EmailFieldInput = ({
)
const { control } = useFormContext()
+ const error = !!get(errors, schema._id)
return (
(
- {
- const value = event.target.value.trim().toLowerCase()
- return handleInputChange
- ? handleInputChange(onChange)(value)
- : onChange({ value })
- }}
- isHighContrast={isHighContrast}
- preventDefaultOnEnter
- {...field}
- {...inputProps}
- />
+
+ {
+ const value = event.target.value.trim().toLowerCase()
+ return handleInputChange
+ ? handleInputChange(onChange)(value)
+ : onChange({ value })
+ }}
+ isHighContrast={isHighContrast}
+ preventDefaultOnEnter
+ {...field}
+ {...inputProps}
+ />
+ {schema.autoReplyOptions?.hasAutoReply &&
+ schema.autoReplyOptions?.includeFormSummary &&
+ !schema.disabled &&
+ !error ? (
+
+ {t(
+ 'features.publicForm.components.fields.email.respondentCopyHelperText',
+ )}
+
+ ) : null}
+
)}
/>
)
diff --git a/package-lock.json b/package-lock.json
index 2148affca9..2aa9769115 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "FormSG",
- "version": "6.270.0",
+ "version": "6.271.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "FormSG",
- "version": "6.270.0",
+ "version": "6.271.0",
"hasInstallScript": true,
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.758.0",
diff --git a/package.json b/package.json
index cc7a3d3537..b60d103db0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "FormSG",
"description": "Form Manager for Government",
- "version": "6.270.0",
+ "version": "6.271.0",
"homepage": "https://form.gov.sg",
"authors": [
"FormSG "
diff --git a/shared/constants/mail.ts b/shared/constants/mail.ts
new file mode 100644
index 0000000000..bf499665be
--- /dev/null
+++ b/shared/constants/mail.ts
@@ -0,0 +1,5 @@
+export const DEFAULT_RESPONDENT_COPY_EMAIL = {
+ subject: 'Thank you for submitting {formTitle}',
+ content:
+ 'To whom it may concern,\n\nThank you for submitting this form.\n\nRegards,\n{agencyName}',
+}
diff --git a/src/app/services/mail/__tests__/mail.service.spec.ts b/src/app/services/mail/__tests__/mail.service.spec.ts
index 2bab3daf7c..361d00da9c 100644
--- a/src/app/services/mail/__tests__/mail.service.spec.ts
+++ b/src/app/services/mail/__tests__/mail.service.spec.ts
@@ -724,9 +724,10 @@ describe('mail.service', () => {
email: MOCK_VALID_EMAIL_2,
},
],
+ isUseLambdaOutput: false,
}
const DEFAULT_AUTO_REPLY_BODY =
- `Dear Sir or Madam,\n\nThank you for submitting this form.\n\nRegards,\n${MOCK_AUTOREPLY_PARAMS.form.admin.agency.fullName}`.split(
+ `To whom it may concern,\n\nThank you for submitting this form.\n\nRegards,\n${MOCK_AUTOREPLY_PARAMS.form.admin.agency.fullName}`.split(
'\n',
)
diff --git a/src/app/services/mail/mail.service.ts b/src/app/services/mail/mail.service.ts
index 2116e90962..721ce7ff23 100644
--- a/src/app/services/mail/mail.service.ts
+++ b/src/app/services/mail/mail.service.ts
@@ -7,6 +7,7 @@ import Mail from 'nodemailer/lib/mailer'
import promiseRetry from 'promise-retry'
import validator from 'validator'
+import { DEFAULT_RESPONDENT_COPY_EMAIL } from '../../../../shared/constants/mail'
import { FormResponseMode, PaymentChannel } from '../../../../shared/types'
import { centsToDollars } from '../../../../shared/utils/payments'
import { getPaymentInvoiceDownloadUrlPath } from '../../../../shared/utils/urls'
@@ -291,15 +292,22 @@ export class MailService {
MailSendError | MailGenerationError
> => {
const emailSubject =
- autoReplyMailData.subject || `Thank you for submitting ${form.title}`
+ autoReplyMailData.subject ||
+ DEFAULT_RESPONDENT_COPY_EMAIL.subject.replace('{formTitle}', form.title)
+
// Sender's name appearing after "("" symbol gets truncated. Escaping it
// solves the problem.
const emailSender = (
autoReplyMailData.sender || form.admin.agency.fullName
).replace('(', '\\(')
- const defaultBody = `Dear Sir or Madam,\n\nThank you for submitting this form.\n\nRegards,\n${form.admin.agency.fullName}`
- const autoReplyBody = (autoReplyMailData.body || defaultBody).split('\n')
+ const autoReplyBody = (
+ autoReplyMailData.body ||
+ DEFAULT_RESPONDENT_COPY_EMAIL.content.replace(
+ '{agencyName}',
+ form.admin.agency.fullName,
+ )
+ ).split('\n')
const templateData = {
submissionId: submission.id,