From 606a032ac14f331b10b34df85ca318bb107d7986 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Tue, 12 May 2026 13:43:05 -0500 Subject: [PATCH 01/24] add optOut to PromptDefinition txstate-etc/reqquest-txstate#314 --- ui/src/lib/registry.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/src/lib/registry.ts b/ui/src/lib/registry.ts index 6d99aac2..dbc15768 100644 --- a/ui/src/lib/registry.ts +++ b/ui/src/lib/registry.ts @@ -97,6 +97,10 @@ export interface PromptDefinition { * An icon for the navigation. */ icon?: Component + /** + * Ability for user to opt out + */ + optOut?: boolean } export interface Terminologies { From 1e3ab0cc86175d87e378a6728a54239215f3347e Mon Sep 17 00:00:00 2001 From: kevinpena Date: Thu, 14 May 2026 10:58:00 -0500 Subject: [PATCH 02/24] adding optOut flag to prompt txstate-etc/reqquest-txstate#314 --- api/src/prompt/prompt.database.ts | 13 +++++++++++-- api/src/prompt/prompt.initialize.ts | 1 + api/src/prompt/prompt.model.ts | 8 ++++++++ api/src/registry/prompt.ts | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/api/src/prompt/prompt.database.ts b/api/src/prompt/prompt.database.ts index 5921916e..15419cc7 100644 --- a/api/src/prompt/prompt.database.ts +++ b/api/src/prompt/prompt.database.ts @@ -19,6 +19,7 @@ export interface PromptRow { moot: 0 | 1 locked: 0 | 1 invalidated: 0 | 1 + optOut: 0 | 1 invalidatedReason: string | null visibility: PromptVisibility workflowStage?: string @@ -67,7 +68,15 @@ export async function getRequirementPrompts (filter: RequirementPromptFilter, td WHERE (${where.join(') AND (')}) ORDER BY evaluationOrder `, binds) - return rows.map(row => new RequirementPrompt(row)) + // console.log(rows) + return rows.map(row => { + const r = new RequirementPrompt(row) + if (row.promptKey === 'opt_out_prompt') { + console.log(row) + console.log(r) + } + return r + }) } export async function setRequirementPromptValid (prompt: RequirementPrompt, tdb: Queryable = db) { @@ -111,7 +120,7 @@ export async function syncPromptRecords (requirement: ApplicationRequirement, db export async function updatePromptComputed (prompts: RequirementPrompt[], db: Queryable) { for (const prompt of prompts) { - await db.update('UPDATE requirement_prompts SET visibility = ?, answered = ?, moot = ?, locked = ? WHERE id = ?', [prompt.visibility, prompt.answered, prompt.moot, prompt.locked, prompt.internalId]) + await db.update('UPDATE requirement_prompts SET visibility = ?, answered = ?, optOut = ?, moot = ?, locked = ? WHERE id = ?', [prompt.visibility, prompt.answered, !!prompt.definition.optOut, prompt.moot, prompt.locked, prompt.internalId]) } } diff --git a/api/src/prompt/prompt.initialize.ts b/api/src/prompt/prompt.initialize.ts index ec6f786f..1f8409ef 100644 --- a/api/src/prompt/prompt.initialize.ts +++ b/api/src/prompt/prompt.initialize.ts @@ -15,6 +15,7 @@ export const promptMigrations: DatabaseMigration[] = [ evaluationOrder SMALLINT UNSIGNED NOT NULL DEFAULT 0, answered TINYINT UNSIGNED NOT NULL DEFAULT 0, moot TINYINT UNSIGNED NOT NULL DEFAULT 0, + optOut TINYINT UNSIGNED NOT NULL DEFAULT 0, locked TINYINT UNSIGNED NOT NULL DEFAULT 0, invalidated TINYINT UNSIGNED NOT NULL DEFAULT 0, invalidatedReason TEXT, diff --git a/api/src/prompt/prompt.model.ts b/api/src/prompt/prompt.model.ts index c6620850..554385c0 100644 --- a/api/src/prompt/prompt.model.ts +++ b/api/src/prompt/prompt.model.ts @@ -41,6 +41,7 @@ export class Prompt { this.title = definition.title this.navTitle = definition.navTitle ?? definition.title this.authorizationKeys = { prompt: promptRegistry.authorizationKeys[definition.key] ?? [] } + this.optOut = !!definition.optOut } @Field({ description: 'A human and machine readable identifier for the prompt. Will be used to match prompt data with UI and API code that handles it.' }) @@ -56,6 +57,8 @@ export class Prompt { description?: string authorizationKeys?: Record + + optOut: boolean } @ObjectType({ description: 'A RequestPrompt is an instance of a Prompt on a particular request. Once the user has answered the prompt, it contains the answer and the prompt status on that request.' }) @@ -83,6 +86,7 @@ export class RequirementPrompt extends Prompt { this.invalidatedReason = row.invalidatedReason ?? undefined this.visibility = row.visibility this.moot = !!row.moot + this.optOut = !!row.optOut this.locked = !!row.locked this.workflowStage = row.workflowStage ?? undefined this.applicationWorkflowStage = row.applicationWorkflowStage ?? undefined @@ -107,6 +111,7 @@ export class RequirementPrompt extends Prompt { moot: prompt.moot ? 1 : 0, locked: prompt.locked ? 1 : 0, invalidated: prompt.invalidated ? 1 : 0, + optOut: prompt.optOut ? 1 : 0, invalidatedReason: prompt.invalidatedReason ?? null, visibility: prompt.visibility, applicationPhase: prompt.applicationPhase, @@ -123,6 +128,9 @@ export class RequirementPrompt extends Prompt { @Field({ description: 'When true, this prompt has been invalidated by the answer to another prompt. The `answered` field will remain true so be sure to check this field as well when determining whether the prompt is complete.' }) invalidated: boolean + @Field({ description: 'When true, this makes the parent program optional for opt out.' }) + optOut: boolean + @Field({ nullable: true, description: 'If the prompt has been invalidated, this may contain a reason why. It should be displayed to the user.' }) invalidatedReason?: string diff --git a/api/src/registry/prompt.ts b/api/src/registry/prompt.ts index a68b8e9b..612b9ddc 100644 --- a/api/src/registry/prompt.ts +++ b/api/src/registry/prompt.ts @@ -412,6 +412,10 @@ export interface PromptDefinition Date: Fri, 15 May 2026 12:07:23 -0500 Subject: [PATCH 03/24] removing optout from db --- api/src/prompt/prompt.database.ts | 12 ++---------- api/src/prompt/prompt.initialize.ts | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/api/src/prompt/prompt.database.ts b/api/src/prompt/prompt.database.ts index 15419cc7..5ae40555 100644 --- a/api/src/prompt/prompt.database.ts +++ b/api/src/prompt/prompt.database.ts @@ -68,15 +68,7 @@ export async function getRequirementPrompts (filter: RequirementPromptFilter, td WHERE (${where.join(') AND (')}) ORDER BY evaluationOrder `, binds) - // console.log(rows) - return rows.map(row => { - const r = new RequirementPrompt(row) - if (row.promptKey === 'opt_out_prompt') { - console.log(row) - console.log(r) - } - return r - }) + return rows.map(row => new RequirementPrompt(row)) } export async function setRequirementPromptValid (prompt: RequirementPrompt, tdb: Queryable = db) { @@ -120,7 +112,7 @@ export async function syncPromptRecords (requirement: ApplicationRequirement, db export async function updatePromptComputed (prompts: RequirementPrompt[], db: Queryable) { for (const prompt of prompts) { - await db.update('UPDATE requirement_prompts SET visibility = ?, answered = ?, optOut = ?, moot = ?, locked = ? WHERE id = ?', [prompt.visibility, prompt.answered, !!prompt.definition.optOut, prompt.moot, prompt.locked, prompt.internalId]) + await db.update('UPDATE requirement_prompts SET visibility = ?, answered = ?, moot = ?, locked = ? WHERE id = ?', [prompt.visibility, prompt.answered, prompt.moot, prompt.locked, prompt.internalId]) } } diff --git a/api/src/prompt/prompt.initialize.ts b/api/src/prompt/prompt.initialize.ts index 1f8409ef..ec6f786f 100644 --- a/api/src/prompt/prompt.initialize.ts +++ b/api/src/prompt/prompt.initialize.ts @@ -15,7 +15,6 @@ export const promptMigrations: DatabaseMigration[] = [ evaluationOrder SMALLINT UNSIGNED NOT NULL DEFAULT 0, answered TINYINT UNSIGNED NOT NULL DEFAULT 0, moot TINYINT UNSIGNED NOT NULL DEFAULT 0, - optOut TINYINT UNSIGNED NOT NULL DEFAULT 0, locked TINYINT UNSIGNED NOT NULL DEFAULT 0, invalidated TINYINT UNSIGNED NOT NULL DEFAULT 0, invalidatedReason TEXT, From 850f9409bc3d0533badb98dcd28eb4c113d7179d Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 15:41:04 -0500 Subject: [PATCH 04/24] removing optout prompts from nav Co-authored-by: Copilot --- ui/src/routes/requests/[id]/apply/+layout.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/routes/requests/[id]/apply/+layout.svelte b/ui/src/routes/requests/[id]/apply/+layout.svelte index 8f697caa..2aa37b07 100644 --- a/ui/src/routes/requests/[id]/apply/+layout.svelte +++ b/ui/src/routes/requests/[id]/apply/+layout.svelte @@ -75,6 +75,7 @@ for (const requirement of application.requirements) { if (!submissionRequirementTypes.has(requirement.type)) continue for (const prompt of requirement.prompts) { + if (prompt.optOut) continue if (foundCurrent) { nextHref = resolve(`/requests/${appRequestForExport.id}/apply/${prompt.id}`) foundCurrent = false From 33ea1766aa75923dee5d20a0ebae01f2f16902a5 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 15:41:24 -0500 Subject: [PATCH 05/24] prompt optOut is coming from prompt registry --- api/src/prompt/prompt.resolver.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/prompt/prompt.resolver.ts b/api/src/prompt/prompt.resolver.ts index 8220fd32..cc135bd2 100644 --- a/api/src/prompt/prompt.resolver.ts +++ b/api/src/prompt/prompt.resolver.ts @@ -60,6 +60,11 @@ export class RequirementPromptResolver { async prestage (@Ctx() ctx: RQContext, @Root() requirementPrompt: RequirementPrompt) { return await ctx.svc(RequirementPromptService).requiresStaging(requirementPrompt) } + + @FieldResolver(type => Boolean) + async optOut (@Ctx() ctx: RQContext, @Root() requirementPrompt: RequirementPrompt) { + return !!promptRegistry.get(requirementPrompt.key).optOut + } } @Resolver(of => RequirementPromptActions) From 55b7f78f449000d1a43f93ac8d1325bd32147a34 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 16:10:41 -0500 Subject: [PATCH 06/24] adding optOut prompt and requirement, adding it to software dev program txstate-etc/reqquest-txstate#273 --- demos/src/rc/definitions/models/optOut.models.ts | 11 +++++++++++ demos/src/rc/definitions/programs.ts | 1 + .../prompts/softwareDevelopment.prompts.ts | 15 ++++++++++++--- .../requirements/prequal.requirements.ts | 1 - .../projectManagement.requirements.ts | 1 - .../softwareDevelopment.requirements.ts | 16 ++++++++++++++++ 6 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 demos/src/rc/definitions/models/optOut.models.ts diff --git a/demos/src/rc/definitions/models/optOut.models.ts b/demos/src/rc/definitions/models/optOut.models.ts new file mode 100644 index 00000000..bfdc6f3f --- /dev/null +++ b/demos/src/rc/definitions/models/optOut.models.ts @@ -0,0 +1,11 @@ +import type { SchemaObject } from '@txstate-mws/fastify-shared' +import type { FromSchema } from 'json-schema-to-ts' + +export const OptOutSchema = { + type: 'object', + properties: { + optOut: { type: 'boolean' } + }, + additionalProperties: false +} as const satisfies SchemaObject +export type OptOutData = FromSchema diff --git a/demos/src/rc/definitions/programs.ts b/demos/src/rc/definitions/programs.ts index 7fc24a3d..9b21fd52 100644 --- a/demos/src/rc/definitions/programs.ts +++ b/demos/src/rc/definitions/programs.ts @@ -17,6 +17,7 @@ export const softwareDevelopment: ProgramDefinition = { key: 'software_development', title: 'Software Development', requirementKeys: [ + 'opt_out_req', 'step1_prequal_req', 'data_related_puzzle_req', 'assess_data_related_puzzle_req', diff --git a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts index 23276a45..88573a6b 100644 --- a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts +++ b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts @@ -2,6 +2,18 @@ import { PromptDefinition } from '@reqquest/api' import { MutationMessageType } from '@txstate-mws/graphql-server' import { AssessCriticalThinkingPromptData, AssessCriticalThinkingSchema, AssessOutsideClassExamplePromptData, AssessOutsideClassExampleSchema, AssessPuzzleSolutionPromptData, AssessPuzzleSolutionSchema, CriticalThinkingPromptData, CriticalThinkingSchema, DataRelatedPuzzlePromptData, DataRelatedPuzzleSchema, OutsideClassExamplePromptData, OutsideClassExampleSchema } from '../models/index.js' import { fileHandler } from 'fastify-txstate' +import { OptOutData, OptOutSchema } from '../models/optOut.models.js' + +export const opt_out_prompt: PromptDefinition = { + key: 'opt_out_prompt', + title: 'Software development', + description: 'Opt Out', + schema: OptOutSchema, + optOut: true, + validate: (data, config) => { + return [] + } +} export const data_related_puzzle_prompt: PromptDefinition = { key: 'data_related_puzzle_prompt', @@ -48,15 +60,12 @@ export const outside_class_example_prompt: PromptDefinition { const messages = [] - console.log(data) - console.log(!data.outsideClassExample) if (!data.outsideClassExample) { messages.push({ type: MutationMessageType.warning, message: 'Might want to make something up', arg: 'outsideClassExample' }) } if (data.description && data.description == null) { messages.push({ type: MutationMessageType.error, message: 'Please add description.', arg: 'description' }) } - console.log(messages) return messages } } diff --git a/demos/src/rc/definitions/requirements/prequal.requirements.ts b/demos/src/rc/definitions/requirements/prequal.requirements.ts index dc749b45..fe335343 100644 --- a/demos/src/rc/definitions/requirements/prequal.requirements.ts +++ b/demos/src/rc/definitions/requirements/prequal.requirements.ts @@ -9,7 +9,6 @@ export const step1_prequal_req: RequirementDefinition = { description: 'Pre qualification requirements', promptKeys: ['pre_qual_prompt'], resolve: (data, config) => { - console.log(data) const preQualPromptData = data['pre_qual_prompt'] as PreQualPromptData if (preQualPromptData?.availability == null) return { status: RequirementStatus.PENDING } diff --git a/demos/src/rc/definitions/requirements/projectManagement.requirements.ts b/demos/src/rc/definitions/requirements/projectManagement.requirements.ts index 0d98cb91..6697189a 100644 --- a/demos/src/rc/definitions/requirements/projectManagement.requirements.ts +++ b/demos/src/rc/definitions/requirements/projectManagement.requirements.ts @@ -10,7 +10,6 @@ export const communication_req: RequirementDefinition = { promptKeys: ['communication_prompt'], resolve: (data, config) => { const writtenAutomationData = data['communication_prompt'] as CommunicationData - console.log(writtenAutomationData, '🚀🚀🚀🚀🚀🚀') if (writtenAutomationData?.describeCommunication == null) return { status: RequirementStatus.PENDING } return { status: RequirementStatus.MET } } diff --git a/demos/src/rc/definitions/requirements/softwareDevelopment.requirements.ts b/demos/src/rc/definitions/requirements/softwareDevelopment.requirements.ts index 0d5eab4a..264c3d04 100644 --- a/demos/src/rc/definitions/requirements/softwareDevelopment.requirements.ts +++ b/demos/src/rc/definitions/requirements/softwareDevelopment.requirements.ts @@ -1,5 +1,21 @@ import { RequirementDefinition, RequirementStatus, RequirementType } from '@reqquest/api' import { DataRelatedPuzzlePromptData, AssessPuzzleSolutionPromptData, OutsideClassExamplePromptData, AssessOutsideClassExamplePromptData, CriticalThinkingPromptData, AssessCriticalThinkingPromptData } from '../models' +import { OptOutData } from '../models/optOut.models' + +export const opt_out_req: RequirementDefinition = { + type: RequirementType.QUALIFICATION, + key: 'opt_out_req', + title: 'Opt Out', + navTitle: 'Opt Out', + description: 'Opt Out', + promptKeys: ['opt_out_prompt'], + 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 } + return { status: RequirementStatus.MET } + } +} export const data_related_puzzle_req: RequirementDefinition = { type: RequirementType.QUALIFICATION, From 52a5892646105d0d5b427842b24d2d232bb21a33 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 16:11:42 -0500 Subject: [PATCH 07/24] returning optOut from getAppRequest fetch txstate-etc/reqquest-txstate#268 --- ui/src/internal/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/internal/api.ts b/ui/src/internal/api.ts index 18463bd4..f455b9c7 100644 --- a/ui/src/internal/api.ts +++ b/ui/src/internal/api.ts @@ -492,7 +492,8 @@ class API extends APIBase { invalidatedReason: true, configurationData: true, gatheredConfigData: true, - prestage: true + prestage: true, + optOut: true } } }, From 0c1f297dd2d6a1cb613b656fc832d7f8a6754f64 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 16:12:29 -0500 Subject: [PATCH 08/24] adding optOut components to UI txstate-etc/reqquest-txstate#268 --- ui/src/lib/types.ts | 1 + ui/src/local/index.ts | 7 +++++++ ui/src/local/rc/FileDisplay.svelte | 1 - ui/src/local/rc/OptOut.svelte | 25 +++++++++++++++++++++++++ ui/src/local/rc/OptOutDisplay.svelte | 7 +++++++ ui/src/local/rc/PreQualPrompt.svelte | 1 - 6 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 ui/src/local/rc/OptOut.svelte create mode 100644 ui/src/local/rc/OptOutDisplay.svelte diff --git a/ui/src/lib/types.ts b/ui/src/lib/types.ts index 2f03b679..81a8a369 100644 --- a/ui/src/lib/types.ts +++ b/ui/src/lib/types.ts @@ -12,6 +12,7 @@ export interface AnsweredPrompt { invalidatedReason: string | null moot: boolean | null visibility: string + optOut?: boolean | null configurationData: Record gatheredConfigData: Record statusReasons: { diff --git a/ui/src/local/index.ts b/ui/src/local/index.ts index 3ccbcc83..40554176 100644 --- a/ui/src/local/index.ts +++ b/ui/src/local/index.ts @@ -155,6 +155,8 @@ import AssessMaintainSysDocumentationDisplay from './rc/AssessMaintainSysDocumen import ReccomendationLetterDisplay from './rc/ReccomendationLetterDisplay.svelte' import AssessReccomendationLetterDisplay from './rc/AssessReccomendationLetterDisplay.svelte' import DataRelatedPuzzleDisplay from './rc/DataRelatedPuzzleDisplay.svelte' +import OptOut from './rc/OptOut.svelte' +import OptOutDisplay from './rc/OptOutDisplay.svelte' /** RC */ @@ -527,6 +529,11 @@ function configureDemoInstanceParams () { key: 'assess_reccomendation_letter_prompt', formComponent: AssessReccomendationLetter, displayComponent: AssessReccomendationLetterDisplay + }, + { + key: 'opt_out_prompt', + formComponent: OptOut, + displayComponent: OptOutDisplay } ] } diff --git a/ui/src/local/rc/FileDisplay.svelte b/ui/src/local/rc/FileDisplay.svelte index 2ca9d789..6c4db527 100644 --- a/ui/src/local/rc/FileDisplay.svelte +++ b/ui/src/local/rc/FileDisplay.svelte @@ -38,7 +38,6 @@ const imgBlob: Blob = await res.blob() objURL = URL.createObjectURL(imgBlob); - console.log(objURL) modalOpen = true } catch (err) { diff --git a/ui/src/local/rc/OptOut.svelte b/ui/src/local/rc/OptOut.svelte new file mode 100644 index 00000000..94218591 --- /dev/null +++ b/ui/src/local/rc/OptOut.svelte @@ -0,0 +1,25 @@ + +
+ {#if initialData.optOut} +

By opting back in, you will be eligible for this program.

+ + {:else} +

By opting-out, you will not be eligible for this program unless you opt back in before submitting your request.

+ {/if} +
diff --git a/ui/src/local/rc/OptOutDisplay.svelte b/ui/src/local/rc/OptOutDisplay.svelte new file mode 100644 index 00000000..460db47d --- /dev/null +++ b/ui/src/local/rc/OptOutDisplay.svelte @@ -0,0 +1,7 @@ + + +Opted Out: {booleanToWord(data?.optOut)} diff --git a/ui/src/local/rc/PreQualPrompt.svelte b/ui/src/local/rc/PreQualPrompt.svelte index 52fc7991..ab02f9c1 100644 --- a/ui/src/local/rc/PreQualPrompt.svelte +++ b/ui/src/local/rc/PreQualPrompt.svelte @@ -3,7 +3,6 @@ import type { RCPreQual } from './types.js' export let fetched: string[] export let data: Partial - console.log(fetched) From 72203a49eb4ffd794ad5809933cce21c03960df1 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 16:52:51 -0500 Subject: [PATCH 09/24] opt out ui txstate-etc/reqquest-txstate#268 Co-authored-by: Copilot --- .../components/ApplicantOptOutModal.svelte | 78 ++++++++++++++++ .../components/ApplicantProgramList.svelte | 92 +++++++++++++++++-- 2 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 ui/src/internal/components/ApplicantOptOutModal.svelte diff --git a/ui/src/internal/components/ApplicantOptOutModal.svelte b/ui/src/internal/components/ApplicantOptOutModal.svelte new file mode 100644 index 00000000..46deefb7 --- /dev/null +++ b/ui/src/internal/components/ApplicantOptOutModal.svelte @@ -0,0 +1,78 @@ + + +{#if loading} + +{/if} + + { + await store?.submit() + open = false + loading = false + invalidateAll() + }} + on:click:button--secondary={() => { open = false }} + on:close={() => { prompt = {} }} + class='opt-out-modal' +> + {#key prompt.id} +
+ + + + + {/key} +
+ diff --git a/ui/src/internal/components/ApplicantProgramList.svelte b/ui/src/internal/components/ApplicantProgramList.svelte index 115aac98..bb26a510 100644 --- a/ui/src/internal/components/ApplicantProgramList.svelte +++ b/ui/src/internal/components/ApplicantProgramList.svelte @@ -1,17 +1,38 @@
@@ -48,11 +104,20 @@ {#each applications as application (application.id)} {@const programStatus = programButtonStatus[application.id]} {@const programFirstPrompt = programFirstPromptId[application.id]} -
{application.title}
+
+ {application.title} + {#if optedOutPrograms[application.id]} + + {:else if optOutPrograms[application.id]} + + {/if} +
{#if !viewMode}
- {#if application.completionStatus === enumApplicationStatus.INELIGIBLE} + {#if optedOutPrograms[application.id]} + + {:else if application.completionStatus === enumApplicationStatus.INELIGIBLE} {:else if ['start', 'continue'].includes(programStatus)} @@ -63,7 +128,9 @@ {/if}
- {#if programFirstPrompt && programStatus !== 'ineligible'} + {#if optedOutPrograms[application.id]} +

Opted out

+ {:else if programFirstPrompt && programStatus !== 'ineligible'} {/if} {:else} @@ -89,6 +156,11 @@ {/each}
+ +{#if optOutPrompt?.key} + +{/if} + From e79f1f8d1b22f8ddd27793ea929db6558e1a910e Mon Sep 17 00:00:00 2001 From: kevinpena Date: Wed, 20 May 2026 16:52:59 -0500 Subject: [PATCH 10/24] needed for not opting out --- .../src/rc/definitions/prompts/softwareDevelopment.prompts.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts index 88573a6b..244c179b 100644 --- a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts +++ b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts @@ -10,6 +10,9 @@ export const opt_out_prompt: PromptDefinition = { description: 'Opt Out', schema: OptOutSchema, optOut: true, + prestage: () => ({ + optOut: false + }), validate: (data, config) => { return [] } From 79b732d036845eaa23a56e9e34cea43ef8812b1d Mon Sep 17 00:00:00 2001 From: kevinpena Date: Thu, 28 May 2026 08:05:25 -0500 Subject: [PATCH 11/24] removing optout from api and moving it to UI --- api/src/prompt/prompt.database.ts | 1 - api/src/prompt/prompt.model.ts | 5 ----- .../rc/definitions/prompts/softwareDevelopment.prompts.ts | 4 ---- ui/src/lib/registry.ts | 2 +- ui/src/local/index.ts | 3 ++- 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/api/src/prompt/prompt.database.ts b/api/src/prompt/prompt.database.ts index 5ae40555..5921916e 100644 --- a/api/src/prompt/prompt.database.ts +++ b/api/src/prompt/prompt.database.ts @@ -19,7 +19,6 @@ export interface PromptRow { moot: 0 | 1 locked: 0 | 1 invalidated: 0 | 1 - optOut: 0 | 1 invalidatedReason: string | null visibility: PromptVisibility workflowStage?: string diff --git a/api/src/prompt/prompt.model.ts b/api/src/prompt/prompt.model.ts index 554385c0..11f40e9e 100644 --- a/api/src/prompt/prompt.model.ts +++ b/api/src/prompt/prompt.model.ts @@ -86,7 +86,6 @@ export class RequirementPrompt extends Prompt { this.invalidatedReason = row.invalidatedReason ?? undefined this.visibility = row.visibility this.moot = !!row.moot - this.optOut = !!row.optOut this.locked = !!row.locked this.workflowStage = row.workflowStage ?? undefined this.applicationWorkflowStage = row.applicationWorkflowStage ?? undefined @@ -111,7 +110,6 @@ export class RequirementPrompt extends Prompt { moot: prompt.moot ? 1 : 0, locked: prompt.locked ? 1 : 0, invalidated: prompt.invalidated ? 1 : 0, - optOut: prompt.optOut ? 1 : 0, invalidatedReason: prompt.invalidatedReason ?? null, visibility: prompt.visibility, applicationPhase: prompt.applicationPhase, @@ -128,9 +126,6 @@ export class RequirementPrompt extends Prompt { @Field({ description: 'When true, this prompt has been invalidated by the answer to another prompt. The `answered` field will remain true so be sure to check this field as well when determining whether the prompt is complete.' }) invalidated: boolean - @Field({ description: 'When true, this makes the parent program optional for opt out.' }) - optOut: boolean - @Field({ nullable: true, description: 'If the prompt has been invalidated, this may contain a reason why. It should be displayed to the user.' }) invalidatedReason?: string diff --git a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts index 244c179b..90033fb9 100644 --- a/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts +++ b/demos/src/rc/definitions/prompts/softwareDevelopment.prompts.ts @@ -9,10 +9,6 @@ export const opt_out_prompt: PromptDefinition = { title: 'Software development', description: 'Opt Out', schema: OptOutSchema, - optOut: true, - prestage: () => ({ - optOut: false - }), validate: (data, config) => { return [] } diff --git a/ui/src/lib/registry.ts b/ui/src/lib/registry.ts index dbc15768..627b748b 100644 --- a/ui/src/lib/registry.ts +++ b/ui/src/lib/registry.ts @@ -98,7 +98,7 @@ export interface PromptDefinition { */ icon?: Component /** - * Ability for user to opt out + * Updating UI to show opt out */ optOut?: boolean } diff --git a/ui/src/local/index.ts b/ui/src/local/index.ts index 40554176..f7ec4317 100644 --- a/ui/src/local/index.ts +++ b/ui/src/local/index.ts @@ -533,7 +533,8 @@ function configureDemoInstanceParams () { { key: 'opt_out_prompt', formComponent: OptOut, - displayComponent: OptOutDisplay + displayComponent: OptOutDisplay, + optOut: true } ] } From befc5938b8782e20d64116340fa897c586d1dd11 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Thu, 28 May 2026 08:12:33 -0500 Subject: [PATCH 12/24] updating UI to look for optOut flag in prompt registry Co-authored-by: Copilot --- .../components/ApplicantProgramList.svelte | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ui/src/internal/components/ApplicantProgramList.svelte b/ui/src/internal/components/ApplicantProgramList.svelte index bb26a510..c04ae274 100644 --- a/ui/src/internal/components/ApplicantProgramList.svelte +++ b/ui/src/internal/components/ApplicantProgramList.svelte @@ -10,6 +10,7 @@ import { api } from '$internal/api.js' import { stagedprompts } from '$internal/prompt-utils.js' import ApplicantOptOutModal from './ApplicantOptOutModal.svelte' + import { uiRegistry } from '../../local/index.js' export let appRequest: { phase: string, closedAt?: string | null, id: string, dataVersion: number } export let applications: ApplicationForDetails[] @@ -62,8 +63,12 @@ }), {} as Record) $: optOutPrograms = applications.reduce((acc, curr) => { - const optOut = curr.requirements.flat().flatMap(r => r.prompts).find(r => r.optOut) - + const optOut = curr.requirements.flat().flatMap(r => r.prompts).find(p => { + const prompt = uiRegistry.getPrompt(p.key) + // console.log(prompt) + return prompt?.optOut + }) + // console.log(optOut) if (!optOut) return acc return { @@ -75,8 +80,13 @@ } }, {} as Record) - $: 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)) + $: optedOutPrograms = applications.reduce((acc, c) => { + const optOutRequirement = c.requirements.flat().find(p => { + return p.prompts.find(p => { + const prompt = uiRegistry.getPrompt(p.key) + return prompt?.optOut + }) + }) return { ...acc, [c.id]: optOutRequirement?.status === enumRequirementStatus.DISQUALIFYING From 1f8423ea24cba54833f41bb6286e3fff6249d268 Mon Sep 17 00:00:00 2001 From: kevinpena Date: Thu, 28 May 2026 08:13:01 -0500 Subject: [PATCH 13/24] updating Modal to PanelFormDialog --- .../components/ApplicantOptOutModal.svelte | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/ui/src/internal/components/ApplicantOptOutModal.svelte b/ui/src/internal/components/ApplicantOptOutModal.svelte index 46deefb7..5c89c7a8 100644 --- a/ui/src/internal/components/ApplicantOptOutModal.svelte +++ b/ui/src/internal/components/ApplicantOptOutModal.svelte @@ -1,5 +1,5 @@ +{#if wasOptedOut} +

By opting back in, you will be eligible for this program.

+{:else} +

By opting-out, you will not be eligible for this program unless you opt back in before submitting your request.

+{/if}
- {#if initialData.optOut} -

By opting back in, you will be eligible for this program.

- - {:else} -

By opting-out, you will not be eligible for this program unless you opt back in before submitting your request.

- {/if} -
From 63ab1fc8020dff9f43d19a32e6c13b9ce49abe0d Mon Sep 17 00:00:00 2001 From: kevinpena Date: Thu, 28 May 2026 08:37:06 -0500 Subject: [PATCH 18/24] clean up --- ui/src/local/rc/OptOut.svelte | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/local/rc/OptOut.svelte b/ui/src/local/rc/OptOut.svelte index b6c5c1b3..904fe0b6 100644 --- a/ui/src/local/rc/OptOut.svelte +++ b/ui/src/local/rc/OptOut.svelte @@ -1,8 +1,5 @@