From aff0bf8c12a6e1a43f133e3424b6c974e5ad9e24 Mon Sep 17 00:00:00 2001 From: iacopolea Date: Fri, 27 Mar 2026 13:03:34 +0100 Subject: [PATCH 01/12] feat: add Environment module with validation and localization support --- src/common/schema.ts | 8 + src/features/api/index.ts | 9 + src/locales/en/translation.json | 7 + src/pages/Plan/modules/Factory/index.tsx | 2 + .../Factory/modules/Environment/Component.tsx | 164 ++++++++++++++++++ .../Factory/modules/Environment/index.tsx | 16 ++ .../Factory/modules/Environment/useIcon.tsx | 17 ++ .../modules/Environment/useSubtitle.tsx | 8 + .../Factory/modules/Environment/useTitle.tsx | 8 + 9 files changed, 239 insertions(+) create mode 100644 src/pages/Plan/modules/Factory/modules/Environment/Component.tsx create mode 100644 src/pages/Plan/modules/Factory/modules/Environment/index.tsx create mode 100644 src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx create mode 100644 src/pages/Plan/modules/Factory/modules/Environment/useSubtitle.tsx create mode 100644 src/pages/Plan/modules/Factory/modules/Environment/useTitle.tsx diff --git a/src/common/schema.ts b/src/common/schema.ts index d281d88b5..2d4b91d16 100644 --- a/src/common/schema.ts +++ b/src/common/schema.ts @@ -1112,6 +1112,7 @@ export interface components { | components["schemas"]["ModuleTouchpoints"] | components["schemas"]["ModuleAdditionalTarget"] | components["schemas"]["ModuleEmployment"] + | components["schemas"]["ModuleEnvironment"] | components["schemas"]["ModuleLocality"] | components["schemas"]["ModuleBank"] | components["schemas"]["ModuleElettricitySupply"] @@ -1185,6 +1186,13 @@ export interface components { type: "employment"; variant: string; }; + /** ModuleEnvironment */ + ModuleEnvironment: { + output: string; + /** @enum {string} */ + type: "environment"; + variant: string; + }; /** ModuleGasSupply */ ModuleGasSupply: { output: components["schemas"]["OutputServiceProviders"]; diff --git a/src/features/api/index.ts b/src/features/api/index.ts index 5594adc37..adec9bd0f 100644 --- a/src/features/api/index.ts +++ b/src/features/api/index.ts @@ -2728,6 +2728,14 @@ export type ModuleEmployment = { type: 'employment'; variant: string; }; +export type ModuleEnvironment = { + output: { + envType: 'production' | 'staging' | 'prototype' | 'app-beta' | 'other'; + description?: string; + }; + type: 'environment'; + variant: string; +}; export type OutputModuleLocality = { type: string; values: string[]; @@ -2805,6 +2813,7 @@ export type Module = | ModuleTouchpoints | ModuleAdditionalTarget | ModuleEmployment + | ModuleEnvironment | ModuleLocality | ModuleBank | ModuleElettricitySupply diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2e3468f54..7007bae75 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -37,6 +37,7 @@ "__ASIDE_NAVIGATION_MODULE_AGE_SUBTITLE": "Participant age groups", "__ASIDE_NAVIGATION_MODULE_BROWSER_SUBTITLE": "Compatible browsers", "__ASIDE_NAVIGATION_MODULE_DIGITAL_LITERACY_ACCORDION_SUBTITLE": "Participant technical proficiency", + "__ASIDE_NAVIGATION_MODULE_ENVIRONMENT_SUBTITLE": "Product state", "__ASIDE_NAVIGATION_MODULE_GENDER_ACCORDION_SUBTITLE": "Participant gender criteria", "__ASIDE_NAVIGATION_MODULE_GOAL_SUBTITLE": "Activity objective", "__ASIDE_NAVIGATION_MODULE_INSTRUCTIONS_NOTE_BLOCK_SUBTITLE": " ", @@ -922,6 +923,11 @@ "__PLAN_PAGE_MODULE_EMPLOYMENT_REMOVE_TOOLTIP_BUTTON": "Delete", "__PLAN_PAGE_MODULE_EMPLOYMENT_SELECT_PLACEHOLDER": "Select professional categories", "__PLAN_PAGE_MODULE_EMPLOYMENT_TITLE": "Employment status", + "__PLAN_PAGE_MODULE_ENVIRONMENT_ALERT": "Describe the current state of the product to test (live site, staging environment, prototype, etc.). This helps generate more relevant test tasks.", + "__PLAN_PAGE_MODULE_ENVIRONMENT_LABEL": "Specify the product state", + "__PLAN_PAGE_MODULE_ENVIRONMENT_PLACEHOLDER": "e.g., Live site, Staging, Figma prototype, Beta app", + "__PLAN_PAGE_MODULE_ENVIRONMENT_REMOVE_TOOLTIP_BUTTON": "Delete environment", + "__PLAN_PAGE_MODULE_ENVIRONMENT_TITLE": "Environment", "__PLAN_PAGE_MODULE_GAS_LABEL": "Gas Provider", "__PLAN_PAGE_MODULE_GAS_REMOVE_TOOLTIP_BUTTON": "Delete", "__PLAN_PAGE_MODULE_GAS_TITLE": "Select participants' gas providers", @@ -1269,6 +1275,7 @@ "__PLAN_SAVE_TEMPLATE_CTA": "Save as template", "__PLAN_SETUP_NOTE_SIZE_ERROR_EMPTY": "Required: please enter a text to continue", "__PLAN_TARGET_NOTE_SIZE_ERROR_EMPTY": "Please enter a text to continue", + "__PLAN_ENVIRONMENT_ERROR_REQUIRED": "Please specify the product environment", "__PLAN_TARGET_SIZE_ERROR_REQUIRED": "Please enter at least one user to include in the activity", "__PLAN_TITLE_ERROR_EMPTY": "Required: please enter a title to continue.", "__PLAN_TITLE_ERROR_MAX_LENGTH": "Character limit exceeded: Please reduce your text to 64 characters.", diff --git a/src/pages/Plan/modules/Factory/index.tsx b/src/pages/Plan/modules/Factory/index.tsx index a365763b9..2d2e54379 100644 --- a/src/pages/Plan/modules/Factory/index.tsx +++ b/src/pages/Plan/modules/Factory/index.tsx @@ -7,6 +7,7 @@ import { BrowserModule } from './modules/Browser'; import { DatesModule } from './modules/Dates'; import { ElectricityModule } from './modules/Electricity'; import { EmploymentModule } from './modules/Employment'; +import { EnvironmentModule } from './modules/Environment'; import { GasModule } from './modules/GasSupply'; import { GenderModule } from './modules/Gender'; import { GoalModule } from './modules/Goal'; @@ -47,6 +48,7 @@ const modules: Record< [TargetSizeModule.slug]: TargetSizeModule, [LiteracyModule.slug]: LiteracyModule, [TouchpointsModule.slug]: TouchpointsModule, + [EnvironmentModule.slug]: EnvironmentModule, [BankModule.slug]: BankModule, [InternetMobileModule.slug]: InternetMobileModule, [ElectricityModule.slug]: ElectricityModule, diff --git a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx new file mode 100644 index 000000000..92ccc286a --- /dev/null +++ b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx @@ -0,0 +1,164 @@ +import { + AccordionNew, + Alert, + FormField, + IconButton, + Input, + Label, + SM, + Span, + Tooltip, +} from '@appquality/unguess-design-system'; +import { ChangeEvent, useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { appTheme } from 'src/app/theme'; +import { ReactComponent as AlertIcon } from 'src/assets/icons/alert-icon.svg'; +import { ReactComponent as TrashIcon } from 'src/assets/icons/trash-stroke.svg'; +import { components } from 'src/common/schema'; +import { useModule } from 'src/features/modules/useModule'; +import { useModuleConfiguration } from 'src/features/modules/useModuleConfiguration'; +import { useValidation } from 'src/features/modules/useModuleValidation'; +import useWindowSize from 'src/hooks/useWindowSize'; +import { DeleteModuleConfirmationModal } from 'src/pages/Plan/modules/modal/DeleteModuleConfirmationModal'; +import styled from 'styled-components'; +import { useIconWithValidation } from './useIcon'; + +const StyledInfoBox = styled.div` + display: flex; + align-items: center; + margin-top: ${appTheme.space.sm}; + gap: ${appTheme.space.xxs}; +`; + +const Environment = () => { + const { value, setOutput, remove } = useModule('environment'); + const { t } = useTranslation(); + const { getPlanStatus } = useModuleConfiguration(); + const [currentValue, setCurrentValue] = useState( + value?.output + ); + const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false); + const Icon = useIconWithValidation(); + const { width } = useWindowSize(); + const breakpointSm = parseInt(appTheme.breakpoints.sm, 10); + const isMobile = width < breakpointSm; + + useEffect(() => { + setOutput(currentValue || ''); + }, [currentValue]); + + const validation = ( + module: components['schemas']['Module'] & { type: 'environment' } + ) => { + let error; + if (!module.output || module.output.trim().length === 0) { + error = t('__PLAN_ENVIRONMENT_ERROR_REQUIRED'); + } + return error || true; + }; + + const { error, validate } = useValidation({ + type: 'environment', + validate: validation, + }); + + const handleBlur = () => { + validate(); + }; + + const handleChange = (e: ChangeEvent) => { + const inputValue = e.target.value; + setCurrentValue(inputValue); + }; + + const handleDelete = () => { + setIsOpenDeleteModal(true); + }; + + return ( + <> + + + + + {!isMobile && getPlanStatus() === 'draft' && ( + + + { + handleDelete(); + e.stopPropagation(); + }} + > + + + + + )} + + +
+ + + + + {error && typeof error === 'string' && ( + <> + + + {error} + + + )} + + + {t('__PLAN_PAGE_MODULE_ENVIRONMENT_ALERT')} + + +
+
+
+
+ {isOpenDeleteModal && ( + setIsOpenDeleteModal(false)} + onConfirm={remove} + /> + )} + + ); +}; + +export default Environment; diff --git a/src/pages/Plan/modules/Factory/modules/Environment/index.tsx b/src/pages/Plan/modules/Factory/modules/Environment/index.tsx new file mode 100644 index 000000000..9196ebabb --- /dev/null +++ b/src/pages/Plan/modules/Factory/modules/Environment/index.tsx @@ -0,0 +1,16 @@ +import { createModuleDefinition } from '../../ModuleDefinition'; +import Component from './Component'; +import useIcon from './useIcon'; +import useSubtitle from './useSubtitle'; +import useTitle from './useTitle'; + +export const EnvironmentModule = createModuleDefinition({ + slug: 'environment', + Component, + useTitle, + useIcon, + useSubtitle, + defaultData: '', + defaultVariant: 'default', + tab: 'setup', +}); diff --git a/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx b/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx new file mode 100644 index 000000000..fec4b2374 --- /dev/null +++ b/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx @@ -0,0 +1,17 @@ +import { ReactComponent as GlobeIcon } from '@zendeskgarden/svg-icons/src/16/globe-fill.svg'; +import useIconColor from '../../useIconColor'; + +const useIcon = (withValidation: boolean = true) => { + const color = useIconColor({ + module_type: 'environment', + withValidation, + }); + + return ; +}; + +export const useIconWithValidation = () => useIcon(true); + +export const useIconWithoutValidation = () => useIcon(false); + +export default useIcon; diff --git a/src/pages/Plan/modules/Factory/modules/Environment/useSubtitle.tsx b/src/pages/Plan/modules/Factory/modules/Environment/useSubtitle.tsx new file mode 100644 index 000000000..7af32f985 --- /dev/null +++ b/src/pages/Plan/modules/Factory/modules/Environment/useSubtitle.tsx @@ -0,0 +1,8 @@ +import { useTranslation } from 'react-i18next'; + +const useSubtitle = () => { + const { t } = useTranslation(); + return t('__ASIDE_NAVIGATION_MODULE_ENVIRONMENT_SUBTITLE'); +}; + +export default useSubtitle; diff --git a/src/pages/Plan/modules/Factory/modules/Environment/useTitle.tsx b/src/pages/Plan/modules/Factory/modules/Environment/useTitle.tsx new file mode 100644 index 000000000..7f2a28424 --- /dev/null +++ b/src/pages/Plan/modules/Factory/modules/Environment/useTitle.tsx @@ -0,0 +1,8 @@ +import { useTranslation } from 'react-i18next'; + +const useTitle = () => { + const { t } = useTranslation(); + return t('__PLAN_PAGE_MODULE_ENVIRONMENT_TITLE'); +}; + +export default useTitle; From d8e82cb4bf538a8718030d412ac2b7f829fd2d30 Mon Sep 17 00:00:00 2001 From: iacopolea Date: Fri, 27 Mar 2026 14:42:01 +0100 Subject: [PATCH 02/12] chore: update api schema --- src/common/schema.ts | 51 +++--- src/features/api/index.ts | 354 +++++++++++++++++++------------------- 2 files changed, 207 insertions(+), 198 deletions(-) diff --git a/src/common/schema.ts b/src/common/schema.ts index 2d4b91d16..dd21cbcd3 100644 --- a/src/common/schema.ts +++ b/src/common/schema.ts @@ -1095,32 +1095,32 @@ export interface components { value: number; }; Module: - | components["schemas"]["ModuleTitle"] - | components["schemas"]["ModuleDate"] - | components["schemas"]["ModuleTask"] + | components["schemas"]["ModuleACNSaver"] + | components["schemas"]["ModuleAdditionalTarget"] | components["schemas"]["ModuleAge"] - | components["schemas"]["ModuleLanguage"] - | components["schemas"]["ModuleLiteracy"] - | components["schemas"]["ModuleTarget"] - | components["schemas"]["ModuleGoal"] - | components["schemas"]["ModuleGender"] - | components["schemas"]["ModuleOutOfScope"] + | components["schemas"]["ModuleAnnualIncomeRange"] + | components["schemas"]["ModuleBank"] | components["schemas"]["ModuleBrowser"] - | components["schemas"]["ModuleTargetNote"] - | components["schemas"]["ModuleInstructionNote"] - | components["schemas"]["ModuleSetupNote"] - | components["schemas"]["ModuleTouchpoints"] - | components["schemas"]["ModuleAdditionalTarget"] + | components["schemas"]["ModuleDate"] + | components["schemas"]["ModuleElettricitySupply"] | components["schemas"]["ModuleEmployment"] | components["schemas"]["ModuleEnvironment"] + | components["schemas"]["ModuleGasSupply"] + | components["schemas"]["ModuleGender"] + | components["schemas"]["ModuleGoal"] + | components["schemas"]["ModuleHomeInternet"] + | components["schemas"]["ModuleInstructionNote"] + | components["schemas"]["ModuleLanguage"] + | components["schemas"]["ModuleLiteracy"] | components["schemas"]["ModuleLocality"] - | components["schemas"]["ModuleBank"] - | components["schemas"]["ModuleElettricitySupply"] | components["schemas"]["ModuleMobileInternet"] - | components["schemas"]["ModuleHomeInternet"] - | components["schemas"]["ModuleGasSupply"] - | components["schemas"]["ModuleAnnualIncomeRange"] - | components["schemas"]["ModuleACNSaver"]; + | components["schemas"]["ModuleOutOfScope"] + | components["schemas"]["ModuleSetupNote"] + | components["schemas"]["ModuleTarget"] + | components["schemas"]["ModuleTargetNote"] + | components["schemas"]["ModuleTask"] + | components["schemas"]["ModuleTitle"] + | components["schemas"]["ModuleTouchpoints"]; /** ModuleAdditionalTarget */ ModuleAdditionalTarget: { output: string; @@ -1188,9 +1188,9 @@ export interface components { }; /** ModuleEnvironment */ ModuleEnvironment: { - output: string; + output: components["schemas"]["OutputModuleEnvironment"]; /** @enum {string} */ - type: "environment"; + type: "environments"; variant: string; }; /** ModuleGasSupply */ @@ -1333,6 +1333,12 @@ export interface components { name: "firefox" | "edge" | "chrome" | "safari"; percentage: number; }[]; + /** OutputModuleEnvironment */ + OutputModuleEnvironment: { + /** @enum {string} */ + envType: "production" | "staging" | "prototype" | "other" | "app-beta"; + description?: string; + }; /** OutputModuleGender */ OutputModuleGender: { /** @enum {string} */ @@ -1989,6 +1995,7 @@ export interface components { | "ACN.INVESTITORE SOFISTICATO" | "ACN.SOCIALE COLLABORATIVO" | "ACN.CONSERVATORE PRUDENTE" + | "TRYBER.EXPERT_BUG_HUNTER" )[]; /** @enum {undefined} */ type: "acn_saver_personas"; diff --git a/src/features/api/index.ts b/src/features/api/index.ts index adec9bd0f..de91baca8 100644 --- a/src/features/api/index.ts +++ b/src/features/api/index.ts @@ -2517,9 +2517,59 @@ export type SubcomponentTaskVideo = { title: string; url?: string; }; -export type ModuleTitle = { +export type ModuleAcnSaver = { + output: ( + | 'ACN.PRAGMATICO DIGITALE' + | 'ACN.EMERGENTE ASPIRAZIONALE' + | 'ACN.INVESTITORE SOFISTICATO' + | 'ACN.SOCIALE COLLABORATIVO' + | 'ACN.CONSERVATORE PRUDENTE' + | 'TRYBER.EXPERT_BUG_HUNTER' + )[]; + type: 'acn_saver_personas'; + variant: string; +}; +export type ModuleAdditionalTarget = { output: string; - type: 'title'; + type: 'additional_target'; + variant: string; +}; +export type OutputModuleAge = { + max: number; + min: number; + percentage: number; +}[]; +export type ModuleAge = { + output: OutputModuleAge; + type: 'age'; + variant: string; +}; +export type OutputModuleIncomeRange = { + max: number; + min: number; + percentage: number; +}[]; +export type ModuleAnnualIncomeRange = { + output: OutputModuleIncomeRange; + type: 'annual_income_range'; + variant: string; +}; +export type OutputServiceProviders = { + isOther?: number; + name: string; +}[]; +export type ModuleBank = { + output: OutputServiceProviders; + type: 'bank'; + variant: string; +}; +export type OutputModuleBrowser = { + name: 'firefox' | 'edge' | 'chrome' | 'safari'; + percentage: number; +}[]; +export type ModuleBrowser = { + output: OutputModuleBrowser; + type: 'browser'; variant: string; }; export type ModuleDate = { @@ -2529,6 +2579,110 @@ export type ModuleDate = { type: 'dates'; variant: string; }; +export type ModuleElettricitySupply = { + output: OutputServiceProviders; + type: 'elettricity_supply'; + variant: string; +}; +export type ModuleEmployment = { + /** cuf values of cuf employment */ + output: ( + | 'EMPLOYEE' + | 'FREELANCER' + | 'RETIRED' + | 'STUDENT' + | 'UNEMPLOYED' + | 'HOMEMAKER' + )[]; + type: 'employment'; + variant: string; +}; +export type OutputModuleEnvironment = { + envType: 'production' | 'staging' | 'prototype' | 'other' | 'app-beta'; + description?: string; +}; +export type ModuleEnvironment = { + output: OutputModuleEnvironment; + type: 'environments'; + variant: string; +}; +export type ModuleGasSupply = { + output: OutputServiceProviders; + type: 'gas_supply'; + variant: string; +}; +export type OutputModuleGender = { + gender: 'male' | 'female'; + percentage: number; +}[]; +export type ModuleGender = { + output: OutputModuleGender; + type: 'gender'; + variant: string; +}; +export type ModuleGoal = { + output: string; + type: 'goal'; + variant: string; +}; +export type ModuleHomeInternet = { + output: OutputServiceProviders; + type: 'home_internet'; + variant: string; +}; +export type ModuleInstructionNote = { + output: string; + type: 'instruction_note'; + variant: string; +}; +export type ModuleLanguage = { + output: string; + type: 'language'; + variant: string; +}; +export type OutputModuleLiteracy = { + level: 'beginner' | 'intermediate' | 'expert'; + percentage: number; +}[]; +export type ModuleLiteracy = { + output: OutputModuleLiteracy; + type: 'literacy'; + variant: string; +}; +export type OutputModuleLocality = { + type: string; + values: string[]; +}[]; +export type ModuleLocality = { + output: OutputModuleLocality; + type: 'locality'; + variant: string; +}; +export type ModuleMobileInternet = { + output: OutputServiceProviders; + type: 'mobile_internet'; + variant: string; +}; +export type ModuleOutOfScope = { + output: string; + type: 'out_of_scope'; + variant: string; +}; +export type ModuleSetupNote = { + output: string; + type: 'setup_note'; + variant: string; +}; +export type ModuleTarget = { + output: number; + type: 'target'; + variant: string; +}; +export type ModuleTargetNote = { + output: string; + type: 'target_note'; + variant: string; +}; export type SubcomponentTaskBug = { description?: string; id?: string; @@ -2576,76 +2730,9 @@ export type ModuleTask = { type: 'tasks'; variant: string; }; -export type OutputModuleAge = { - max: number; - min: number; - percentage: number; -}[]; -export type ModuleAge = { - output: OutputModuleAge; - type: 'age'; - variant: string; -}; -export type ModuleLanguage = { - output: string; - type: 'language'; - variant: string; -}; -export type OutputModuleLiteracy = { - level: 'beginner' | 'intermediate' | 'expert'; - percentage: number; -}[]; -export type ModuleLiteracy = { - output: OutputModuleLiteracy; - type: 'literacy'; - variant: string; -}; -export type ModuleTarget = { - output: number; - type: 'target'; - variant: string; -}; -export type ModuleGoal = { - output: string; - type: 'goal'; - variant: string; -}; -export type OutputModuleGender = { - gender: 'male' | 'female'; - percentage: number; -}[]; -export type ModuleGender = { - output: OutputModuleGender; - type: 'gender'; - variant: string; -}; -export type ModuleOutOfScope = { - output: string; - type: 'out_of_scope'; - variant: string; -}; -export type OutputModuleBrowser = { - name: 'firefox' | 'edge' | 'chrome' | 'safari'; - percentage: number; -}[]; -export type ModuleBrowser = { - output: OutputModuleBrowser; - type: 'browser'; - variant: string; -}; -export type ModuleTargetNote = { - output: string; - type: 'target_note'; - variant: string; -}; -export type ModuleInstructionNote = { - output: string; - type: 'instruction_note'; - variant: string; -}; -export type ModuleSetupNote = { +export type ModuleTitle = { output: string; - type: 'setup_note'; + type: 'title'; variant: string; }; export type OutputModuleTouchpointsAppDesktop = { @@ -2710,118 +2797,33 @@ export type ModuleTouchpoints = { type: 'touchpoints'; variant: string; }; -export type ModuleAdditionalTarget = { - output: string; - type: 'additional_target'; - variant: string; -}; -export type ModuleEmployment = { - /** cuf values of cuf employment */ - output: ( - | 'EMPLOYEE' - | 'FREELANCER' - | 'RETIRED' - | 'STUDENT' - | 'UNEMPLOYED' - | 'HOMEMAKER' - )[]; - type: 'employment'; - variant: string; -}; -export type ModuleEnvironment = { - output: { - envType: 'production' | 'staging' | 'prototype' | 'app-beta' | 'other'; - description?: string; - }; - type: 'environment'; - variant: string; -}; -export type OutputModuleLocality = { - type: string; - values: string[]; -}[]; -export type ModuleLocality = { - output: OutputModuleLocality; - type: 'locality'; - variant: string; -}; -export type OutputServiceProviders = { - isOther?: number; - name: string; -}[]; -export type ModuleBank = { - output: OutputServiceProviders; - type: 'bank'; - variant: string; -}; -export type ModuleElettricitySupply = { - output: OutputServiceProviders; - type: 'elettricity_supply'; - variant: string; -}; -export type ModuleMobileInternet = { - output: OutputServiceProviders; - type: 'mobile_internet'; - variant: string; -}; -export type ModuleHomeInternet = { - output: OutputServiceProviders; - type: 'home_internet'; - variant: string; -}; -export type ModuleGasSupply = { - output: OutputServiceProviders; - type: 'gas_supply'; - variant: string; -}; -export type OutputModuleIncomeRange = { - max: number; - min: number; - percentage: number; -}[]; -export type ModuleAnnualIncomeRange = { - output: OutputModuleIncomeRange; - type: 'annual_income_range'; - variant: string; -}; -export type ModuleAcnSaver = { - output: ( - | 'ACN.PRAGMATICO DIGITALE' - | 'ACN.EMERGENTE ASPIRAZIONALE' - | 'ACN.INVESTITORE SOFISTICATO' - | 'ACN.SOCIALE COLLABORATIVO' - | 'ACN.CONSERVATORE PRUDENTE' - )[]; - type: 'acn_saver_personas'; - variant: string; -}; export type Module = - | ModuleTitle - | ModuleDate - | ModuleTask + | ModuleAcnSaver + | ModuleAdditionalTarget | ModuleAge - | ModuleLanguage - | ModuleLiteracy - | ModuleTarget - | ModuleGoal - | ModuleGender - | ModuleOutOfScope + | ModuleAnnualIncomeRange + | ModuleBank | ModuleBrowser - | ModuleTargetNote - | ModuleInstructionNote - | ModuleSetupNote - | ModuleTouchpoints - | ModuleAdditionalTarget + | ModuleDate + | ModuleElettricitySupply | ModuleEmployment | ModuleEnvironment + | ModuleGasSupply + | ModuleGender + | ModuleGoal + | ModuleHomeInternet + | ModuleInstructionNote + | ModuleLanguage + | ModuleLiteracy | ModuleLocality - | ModuleBank - | ModuleElettricitySupply | ModuleMobileInternet - | ModuleHomeInternet - | ModuleGasSupply - | ModuleAnnualIncomeRange - | ModuleAcnSaver; + | ModuleOutOfScope + | ModuleSetupNote + | ModuleTarget + | ModuleTargetNote + | ModuleTask + | ModuleTitle + | ModuleTouchpoints; export type Authentication = { email: string; exp?: number; From 9a7d4b2cbb825e5ac7d731c409740e38257759bf Mon Sep 17 00:00:00 2001 From: iacopolea Date: Fri, 27 Mar 2026 15:19:12 +0100 Subject: [PATCH 03/12] feat: update Environment module to support multiple environment types and improve localization --- src/locales/en/translation.json | 12 ++- src/pages/Plan/modules/Factory/index.tsx | 4 +- .../Factory/modules/Environment/Component.tsx | 94 ++++++++++++------- .../Factory/modules/Environment/index.tsx | 6 +- .../Factory/modules/Environment/useIcon.tsx | 2 +- 5 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 7007bae75..768c89cca 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -923,10 +923,14 @@ "__PLAN_PAGE_MODULE_EMPLOYMENT_REMOVE_TOOLTIP_BUTTON": "Delete", "__PLAN_PAGE_MODULE_EMPLOYMENT_SELECT_PLACEHOLDER": "Select professional categories", "__PLAN_PAGE_MODULE_EMPLOYMENT_TITLE": "Employment status", - "__PLAN_PAGE_MODULE_ENVIRONMENT_ALERT": "Describe the current state of the product to test (live site, staging environment, prototype, etc.). This helps generate more relevant test tasks.", - "__PLAN_PAGE_MODULE_ENVIRONMENT_LABEL": "Specify the product state", - "__PLAN_PAGE_MODULE_ENVIRONMENT_PLACEHOLDER": "e.g., Live site, Staging, Figma prototype, Beta app", + "__PLAN_PAGE_MODULE_ENVIRONMENT_LABEL": "Select the product environment type", + "__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_APP_BETA": "App Beta", + "__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_OTHER": "Other", + "__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_PRODUCTION": "Production (live)", + "__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_PROTOTYPE": "Prototype", + "__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_STAGING": "Staging", "__PLAN_PAGE_MODULE_ENVIRONMENT_REMOVE_TOOLTIP_BUTTON": "Delete environment", + "__PLAN_PAGE_MODULE_ENVIRONMENT_SELECT_PLACEHOLDER": "Select environment type", "__PLAN_PAGE_MODULE_ENVIRONMENT_TITLE": "Environment", "__PLAN_PAGE_MODULE_GAS_LABEL": "Gas Provider", "__PLAN_PAGE_MODULE_GAS_REMOVE_TOOLTIP_BUTTON": "Delete", @@ -1275,7 +1279,7 @@ "__PLAN_SAVE_TEMPLATE_CTA": "Save as template", "__PLAN_SETUP_NOTE_SIZE_ERROR_EMPTY": "Required: please enter a text to continue", "__PLAN_TARGET_NOTE_SIZE_ERROR_EMPTY": "Please enter a text to continue", - "__PLAN_ENVIRONMENT_ERROR_REQUIRED": "Please specify the product environment", + "__PLAN_ENVIRONMENT_ERROR_REQUIRED": "Please select an environment type", "__PLAN_TARGET_SIZE_ERROR_REQUIRED": "Please enter at least one user to include in the activity", "__PLAN_TITLE_ERROR_EMPTY": "Required: please enter a title to continue.", "__PLAN_TITLE_ERROR_MAX_LENGTH": "Character limit exceeded: Please reduce your text to 64 characters.", diff --git a/src/pages/Plan/modules/Factory/index.tsx b/src/pages/Plan/modules/Factory/index.tsx index 2d2e54379..4b025b190 100644 --- a/src/pages/Plan/modules/Factory/index.tsx +++ b/src/pages/Plan/modules/Factory/index.tsx @@ -7,7 +7,7 @@ import { BrowserModule } from './modules/Browser'; import { DatesModule } from './modules/Dates'; import { ElectricityModule } from './modules/Electricity'; import { EmploymentModule } from './modules/Employment'; -import { EnvironmentModule } from './modules/Environment'; +import { EnvironmentsModule } from './modules/Environment'; import { GasModule } from './modules/GasSupply'; import { GenderModule } from './modules/Gender'; import { GoalModule } from './modules/Goal'; @@ -48,7 +48,7 @@ const modules: Record< [TargetSizeModule.slug]: TargetSizeModule, [LiteracyModule.slug]: LiteracyModule, [TouchpointsModule.slug]: TouchpointsModule, - [EnvironmentModule.slug]: EnvironmentModule, + [EnvironmentsModule.slug]: EnvironmentsModule, [BankModule.slug]: BankModule, [InternetMobileModule.slug]: InternetMobileModule, [ElectricityModule.slug]: ElectricityModule, diff --git a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx index 92ccc286a..68efbb111 100644 --- a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx +++ b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx @@ -1,15 +1,14 @@ import { AccordionNew, - Alert, FormField, IconButton, - Input, Label, + Select, SM, Span, Tooltip, } from '@appquality/unguess-design-system'; -import { ChangeEvent, useEffect, useState } from 'react'; +import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { appTheme } from 'src/app/theme'; import { ReactComponent as AlertIcon } from 'src/assets/icons/alert-icon.svg'; @@ -30,47 +29,61 @@ const StyledInfoBox = styled.div` gap: ${appTheme.space.xxs}; `; +type EnvType = 'production' | 'staging' | 'prototype' | 'other' | 'app-beta'; + const Environment = () => { - const { value, setOutput, remove } = useModule('environment'); + const { value, setOutput, remove } = useModule('environments'); const { t } = useTranslation(); const { getPlanStatus } = useModuleConfiguration(); - const [currentValue, setCurrentValue] = useState( - value?.output - ); const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false); const Icon = useIconWithValidation(); const { width } = useWindowSize(); const breakpointSm = parseInt(appTheme.breakpoints.sm, 10); const isMobile = width < breakpointSm; - useEffect(() => { - setOutput(currentValue || ''); - }, [currentValue]); + const options: Array<{ value: EnvType; label: string }> = [ + { + value: 'production', + label: t('__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_PRODUCTION'), + }, + { + value: 'staging', + label: t('__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_STAGING'), + }, + { + value: 'prototype', + label: t('__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_PROTOTYPE'), + }, + { + value: 'other', + label: t('__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_OTHER'), + }, + { + value: 'app-beta', + label: t('__PLAN_PAGE_MODULE_ENVIRONMENT_OPTION_APP_BETA'), + }, + ]; const validation = ( - module: components['schemas']['Module'] & { type: 'environment' } + module: components['schemas']['Module'] & { type: 'environments' } ) => { let error; - if (!module.output || module.output.trim().length === 0) { + if (!module.output?.envType) { error = t('__PLAN_ENVIRONMENT_ERROR_REQUIRED'); } return error || true; }; const { error, validate } = useValidation({ - type: 'environment', + type: 'environments', validate: validation, }); - const handleBlur = () => { + const handleChange = (item: EnvType) => { + setOutput({ envType: item }); validate(); }; - const handleChange = (e: ChangeEvent) => { - const inputValue = e.target.value; - setCurrentValue(inputValue); - }; - const handleDelete = () => { setIsOpenDeleteModal(true); }; @@ -78,7 +91,7 @@ const Environment = () => { return ( <> { - { + const selected = options.find( + (opt) => opt.value === value?.output?.envType + ); + return ( + selected?.label || + t('__PLAN_PAGE_MODULE_ENVIRONMENT_SELECT_PLACEHOLDER') + ); + }} + onSelect={(item) => handleChange(item as EnvType)} validation={error ? 'error' : undefined} - placeholder={t('__PLAN_PAGE_MODULE_ENVIRONMENT_PLACEHOLDER')} - /> + > + {options.map((option) => ( + + ))} + {error && typeof error === 'string' && ( <> {error} )} - - {t('__PLAN_PAGE_MODULE_ENVIRONMENT_ALERT')} - diff --git a/src/pages/Plan/modules/Factory/modules/Environment/index.tsx b/src/pages/Plan/modules/Factory/modules/Environment/index.tsx index 9196ebabb..4ce383e59 100644 --- a/src/pages/Plan/modules/Factory/modules/Environment/index.tsx +++ b/src/pages/Plan/modules/Factory/modules/Environment/index.tsx @@ -4,13 +4,13 @@ import useIcon from './useIcon'; import useSubtitle from './useSubtitle'; import useTitle from './useTitle'; -export const EnvironmentModule = createModuleDefinition({ - slug: 'environment', +export const EnvironmentsModule = createModuleDefinition({ + slug: 'environments', Component, useTitle, useIcon, useSubtitle, - defaultData: '', + defaultData: { envType: 'production' }, defaultVariant: 'default', tab: 'setup', }); diff --git a/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx b/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx index fec4b2374..f43403d8a 100644 --- a/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx +++ b/src/pages/Plan/modules/Factory/modules/Environment/useIcon.tsx @@ -3,7 +3,7 @@ import useIconColor from '../../useIconColor'; const useIcon = (withValidation: boolean = true) => { const color = useIconColor({ - module_type: 'environment', + module_type: 'environments', withValidation, }); From 2f08199dfdead2869bb2fe13079767ca71e82832 Mon Sep 17 00:00:00 2001 From: iacopolea Date: Fri, 27 Mar 2026 15:30:03 +0100 Subject: [PATCH 04/12] feat: add 'environments' module to technical requirements group in Plan page --- src/pages/Plan/common/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Plan/common/constants.ts b/src/pages/Plan/common/constants.ts index 131200246..bafb480cd 100644 --- a/src/pages/Plan/common/constants.ts +++ b/src/pages/Plan/common/constants.ts @@ -20,7 +20,7 @@ export const MODULE_GROUPS: Record = { { id: 'technicalRequirements', title: '__PLAN_PAGE_GROUP_TITLE_TECHNICAL_REQUIREMENTS', - modules: ['touchpoints', 'browser'], + modules: ['touchpoints', 'environments', 'browser'], }, ], target: [ From 45c8ae018a9d998fe482761534771a89d6e53e9e Mon Sep 17 00:00:00 2001 From: Gianpaolo Date: Fri, 27 Mar 2026 15:43:08 +0100 Subject: [PATCH 05/12] fix: correct 'environments' to 'environment' in ModuleEnvironment type definition --- src/common/schema.ts | 2 +- src/features/api/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/schema.ts b/src/common/schema.ts index dd21cbcd3..5ad8efa63 100644 --- a/src/common/schema.ts +++ b/src/common/schema.ts @@ -1190,7 +1190,7 @@ export interface components { ModuleEnvironment: { output: components["schemas"]["OutputModuleEnvironment"]; /** @enum {string} */ - type: "environments"; + type: "environment"; variant: string; }; /** ModuleGasSupply */ diff --git a/src/features/api/index.ts b/src/features/api/index.ts index de91baca8..352797128 100644 --- a/src/features/api/index.ts +++ b/src/features/api/index.ts @@ -2603,7 +2603,7 @@ export type OutputModuleEnvironment = { }; export type ModuleEnvironment = { output: OutputModuleEnvironment; - type: 'environments'; + type: 'environment'; variant: string; }; export type ModuleGasSupply = { From 3ec5099e2e453613dccaed41df221ce2bd2621c3 Mon Sep 17 00:00:00 2001 From: iacopolea Date: Mon, 30 Mar 2026 10:38:41 +0200 Subject: [PATCH 06/12] fix: standardize 'environments' to 'environment' across module definitions and references --- src/pages/Plan/common/constants.ts | 2 +- .../modules/Factory/modules/Environment/Component.tsx | 8 ++++---- .../Plan/modules/Factory/modules/Environment/index.tsx | 2 +- .../Plan/modules/Factory/modules/Environment/useIcon.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/Plan/common/constants.ts b/src/pages/Plan/common/constants.ts index bafb480cd..2dcde2947 100644 --- a/src/pages/Plan/common/constants.ts +++ b/src/pages/Plan/common/constants.ts @@ -20,7 +20,7 @@ export const MODULE_GROUPS: Record = { { id: 'technicalRequirements', title: '__PLAN_PAGE_GROUP_TITLE_TECHNICAL_REQUIREMENTS', - modules: ['touchpoints', 'environments', 'browser'], + modules: ['touchpoints', 'environment', 'browser'], }, ], target: [ diff --git a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx index 68efbb111..396977f76 100644 --- a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx +++ b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx @@ -32,7 +32,7 @@ const StyledInfoBox = styled.div` type EnvType = 'production' | 'staging' | 'prototype' | 'other' | 'app-beta'; const Environment = () => { - const { value, setOutput, remove } = useModule('environments'); + const { value, setOutput, remove } = useModule('environment'); const { t } = useTranslation(); const { getPlanStatus } = useModuleConfiguration(); const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false); @@ -65,7 +65,7 @@ const Environment = () => { ]; const validation = ( - module: components['schemas']['Module'] & { type: 'environments' } + module: components['schemas']['Module'] & { type: 'environment' } ) => { let error; if (!module.output?.envType) { @@ -75,7 +75,7 @@ const Environment = () => { }; const { error, validate } = useValidation({ - type: 'environments', + type: 'environment', validate: validation, }); @@ -91,7 +91,7 @@ const Environment = () => { return ( <> { const color = useIconColor({ - module_type: 'environments', + module_type: 'environment', withValidation, }); From 7dd5197b3257a452acc820cc9043bdf4e52820e6 Mon Sep 17 00:00:00 2001 From: iacopolea Date: Mon, 30 Mar 2026 11:08:57 +0200 Subject: [PATCH 07/12] feat: append listbox to document body for improved accessibility in Environment select --- src/pages/Plan/modules/Factory/modules/Environment/Component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx index 396977f76..3763579e3 100644 --- a/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx +++ b/src/pages/Plan/modules/Factory/modules/Environment/Component.tsx @@ -136,6 +136,7 @@ const Environment = () => {