Skip to content

Opt-In / Opt-Out program flow#34

Open
kevinepena wants to merge 13 commits into
txstate-etc:mainfrom
kevinepena:optin-out
Open

Opt-In / Opt-Out program flow#34
kevinepena wants to merge 13 commits into
txstate-etc:mainfrom
kevinepena:optin-out

Conversation

@kevinepena
Copy link
Copy Markdown
Contributor

txstate-etc/reqquest-txstate#267

@kevinepena kevinepena requested a review from a team May 21, 2026 00:50
@saultminer saultminer changed the title #267 Optin out Opt-In / Opt-Out program flow May 21, 2026
<Loading />
{/if}

<Modal
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's use a PanelFormDialog here, it will behave a lot better long term since we don't know what downstream might want to do with their prompt. Also much easier to wire up than this was.


<Modal
bind:open
modalHeading={`${optIn ? 'Opt in to' : 'Opt out of'} ${prompt?.title}?`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't prompt?.title be the program/application title?

on:close={() => { prompt = {} }}
class='opt-out-modal'
>
{#key prompt.id}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This {#key} thing was specific to how the prompt screen was working. You won't need it here.

moot: 0 | 1
locked: 0 | 1
invalidated: 0 | 1
optOut: 0 | 1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lots of API-side code hanging around that shouldn't be necessary. More details in other comments.

}), {} as Record<string, string | undefined>)

$: optOutPrograms = applications.reduce((acc, curr) => {
const optOut = curr.requirements.flat().flatMap(r => r.prompts).find(r => r.optOut)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like you're depending on the API for which prompt is an optOut prompt and ignoring the optOut in the UI's registry. I suggest:

const optOut = curr.requirements.flat().flatMap(r => r.prompts).find(p => uiRegistry.getPrompt(p.key)?.optOut)

>
{#key prompt.id}
<Form bind:store hideFallbackMessage unsavedWarning submit={onSubmit} validate={onValidate} preloadAsDraft={!prompt.hasSavedData} preload={prompt.preloadData} let:data>
<svelte:component this={def!.formComponent} {data} appRequestId={appRequest.id} appRequestData={appRequest.data} fetched={prompt.fetchedData} configData={prompt.configurationData} gatheredConfigData={prompt.gatheredConfigData} store={store} />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see why you did it but I wouldn't add the store here without adding it to the other places that render prompt components. We have to present a consistent set of props to the downstream.

I probably wouldn't add it, as it's already in context. You can do this inside a prompt:

import { FORM_CONTEXT, type FormStore } from '@txstate-mws/svelte-forms'
const store = getContext<FormStore>(FORM_CONTEXT)

}, {} as Record<string, OptOutApplication | undefined>)

$: optedOutPrograms = applications.filter(curr => curr.requirements.flat().flatMap(r => r.prompts).find(r => r.optOut)).reduce((acc, c) => {
const optOutRequirement = c.requirements.flat().find(r => r.prompts.find(p => p.optOut))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same dependence on API here. Also r.prompts.some is a little more clear than r.prompts.find. Suggest:

const optOutRequirement = c.requirements.flat().find(r => r.prompts.some(p => uiRegistry.getPrompt(p.key)?.optOut))

description: 'Opt Out',
schema: OptOutSchema,
optOut: true,
prestage: () => ({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

oof, I see why you did this and it's going to be confusing for folks on the regular, but I think the requirement just needs to treat undefined as MET instead of trying to inject data into here.

resolve: (data, config) => {
const promptData = data['opt_out_prompt'] as OptOutData
if (promptData?.optOut) return { status: RequirementStatus.DISQUALIFYING }
if (promptData?.optOut == null) return { status: RequirementStatus.PENDING }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is what I was referring to in the earlier comment. Don't return PENDING here, just return MET or NOT_APPLICABLE if they have never opened the modal.

prestage: () => ({
optOut: false
}),
validate: (data, config) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I feel like this validate could be omitted once the requirement is updated to accept undefined as MET. No big deal to include it but I want to be sure it's not needed because that'd be an ugly thing for downstream to have to remember to do.

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.

2 participants