Skip to content

Conversation

@harrim91
Copy link
Contributor

@harrim91 harrim91 commented Jan 6, 2026

Description

Adds new page to show preview of message plan in production. Page looks visually similar to "choose templates" page, but with different logic - e.g. the "card" elements have different content, conditional templates which aren't set are not shown (choose templates always shows slots for all conditional templates even if they are empty) - so also refactored a lot of the components there to be composable, and have lifted the choose templates specific logic out of the components and into the page.

Context

Type of changes

  • Refactoring (non-breaking change)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would change existing functionality)
  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I am familiar with the contributing guidelines
  • I have followed the code style of the project
  • I have added tests to cover my changes
  • I have updated the documentation accordingly
  • This PR is a result of pair or mob programming
  • If I have used the 'skip-trivy-package' label I have done so responsibly and in the knowledge that this is being fixed as part of a separate ticket/PR.

Sensitive Information Declaration

To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including PII (Personal Identifiable Information) / PID (Personal Identifiable Data) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter.

  • I confirm that neither PII/PID nor sensitive data are included in this PR and the codebase changes.

@harrim91 harrim91 requested a review from a team as a code owner January 6, 2026 17:55
@harrim91 harrim91 marked this pull request as draft January 6, 2026 17:55
@harrim91 harrim91 marked this pull request as ready for review January 19, 2026 17:19
@harrim91 harrim91 changed the title CCM-12038: WIP preview production message plan CCM-12038: preview production message plan Jan 20, 2026
},
ctas: {
primary: {
href: '/message-plans/get-ready-to-move/{{routingConfigId}}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put most of the choose templates stuff into page.tsx which is server rendered. The CTA now uses a server action to do the validation / redirect, rather than having a link with an event listener / e.preventDefault etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should still have the href tho for accessibility purposes surely? so its clear from the markup that the button will take them to a different page, even if the href isnt going to be responsible for the navigation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, I thought that this was better for accessibility. With the link/href, you'd expect clicking that to navigate you somewhere. But it actually has some client-side pseudo form validation, and if the validation failed, then it didn't navigate and you get a form error. It felt like a form masquerading as a link.

This way, it's a form with a submit button. Its not uncommon for submitting a form to navigate you somewhere different - probably most of our forms do it - and it's not clear where you're going to end up because none of the form submit buttons have an href attribute. So I figured this is in line with how we do forms elsewhere. The difference I guess is that the form doesn't do anything in the sense of performing a backend action - but it does do form validation, it can return form error state. So it felt right to have it as a form.

<FormContext.Provider value={[state, action, isPending]}>
<NhsNotifyErrorSummary errorState={state.errorState} />
<NhsNotifyErrorSummary
hint={errorSummaryHint}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how was this working before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't an instance of the FormProvider being used to render an error summary with a hint.

renderSMSMarkdown,
} from './markdownit';

export function renderTemplateMarkdown(template: TemplateDto): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function renderTemplateMarkdown(template: TemplateDto): string {
export function renderDigitalTemplateMarkdown(template: DigitalTemplate): string {

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had wondered this.

@@ -0,0 +1,11 @@
'use client';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to add a comment to explain this - will add one now.

It's basically like a proxy which allows you to import components from nhsuk-react-components into server side code.

Comment on lines -338 to -341
await expect(chooseTemplatesPage.moveToProductionButton).toHaveAttribute(
'href',
`/templates/message-plans/get-ready-to-move/${routingConfigIds.valid}`
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned above - it's not a link anymore, it's a button which triggers a server action.

{children}
</SummaryList.Value>
);
}
Copy link
Contributor

@chris-elliott-nhsd chris-elliott-nhsd Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure what's being gained from this level of splitting everything out. this seems to be a 57-line file where all it does is append a class name to an existing nhsuk-react-components component, which would just be a few characters if it wasn't split out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an existing component (the nhsuk-react-components component with customised styling) which is now being reused in a few places. It's just the styling though, I guess I could just import the scss module into the places where this new component is being used, and just render the proper nhsuk component and apply the styles to it in each place. But having an scss module without a component feels odd - it feels to me to worth having it encapsulated into a proper component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could just put our style overrides in app.scss?


await expect(page).toHaveURL(
`${baseURL}/templates/message-plans/choose-templates/${dbEntry.id}`
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about checking the status once we get here? i.e. to confirm its not got "production" status?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure what the value is - why would it have changed? the db storage helper doesn't have any functionality for getting data, so presumably we're not doing anything like that anywhere else either. that sounds a bit lazy - not against adding it, just not sure if it's worth it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well I more meant to clearly differentiate between the production ones going to the preview page and it being the choose templates when its not production
so not testing because it would have changed, but because we expect that and expect it not to be production
but if you dont think there's value, thats fair

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what you mean, it's just because the config is created explicitly by the test with a status of DRAFT. Checking the template status is not really testing the behaviour of the page, it's just ensuring that there's no random process outside of the test that has altered that status in the background in the time it took to create the item and load the page. Or that the act of redirecting from one page to the other doesn't change the status.

I guess I could check that the status tag on the choose templates page that it's redirected to says "Draft"


await expect(links[index]).toHaveAttribute(
'href',
`/templates/preview-submitted-letter-template/${templates[language].id}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could click the last one? once we're at the end of the test, just so we've interacted with at least one of the links?

});
});

test('letter only with no conditional templates', async ({ page }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a comment about this but a question - if its app only, is the preview already expanded and the toggle button also hidden? or is that just for letter because its links only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second one - it's just hidden if the cascade is letter only because there are no template previews, just links

constructor(page: Page) {
super(page);

this.messagePlanId = page.getByTestId('plan-id');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, feel free to ignore but isnt this referred to/displayed on the page as routing config ID ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called routing plan id actually - new terminology which is a mash up of both 😆 I asked Alex and he said that it's right though. I'm really confused about the two names and where to use what.

remove: 'Remove{{templateCount|| all}}',
templateWord: '{{templateCount|template|templates}}',
},
optional: '(optional)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you say you weren't using this anymore?

@@ -0,0 +1,92 @@
'use client';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm surprised you've had to create this vs using from the react lib?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants