From 259c749fa2be64255fe90c417488bb91e141e5d9 Mon Sep 17 00:00:00 2001 From: Davide Bizzi Date: Wed, 9 Apr 2025 12:00:03 +0200 Subject: [PATCH 1/6] fix: enhance update logic to handle URL updates correctly in useModuleTasks hook --- src/pages/Plan/modules/Tasks/hooks/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/Plan/modules/Tasks/hooks/index.ts b/src/pages/Plan/modules/Tasks/hooks/index.ts index 7c4883fa2..964f14a44 100644 --- a/src/pages/Plan/modules/Tasks/hooks/index.ts +++ b/src/pages/Plan/modules/Tasks/hooks/index.ts @@ -159,7 +159,17 @@ const useModuleTasks = () => { const update = (k: number, v: Partial<(typeof output)[number]>) => { setOutput( output - .map((o) => (o.key === k ? { ...o, ...v } : o)) + .map((o) => { + if (o.key !== k) return o; + + if (v.url !== '') return { ...o, ...v }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { url: __, ...rest } = o; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { url: _, ...restWithoutUrl } = v; + return { ...rest, ...restWithoutUrl }; + }) .map((o) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { key, ...rest } = o; From e5931ab0528432705a70b958043b27b57c74b99c Mon Sep 17 00:00:00 2001 From: "Luca Cannarozzo (@cannarocks)" Date: Wed, 9 Apr 2025 12:16:57 +0200 Subject: [PATCH 2/6] chore(api) update definition types and extend orderBy options in API definitions --- src/features/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/api/index.ts b/src/features/api/index.ts index 774ab507f..d0d567c71 100644 --- a/src/features/api/index.ts +++ b/src/features/api/index.ts @@ -1833,7 +1833,7 @@ export type GetWorkspacesByWidTemplatesApiArg = { /** Start pagination parameter */ start?: number; /** Orders results */ - orderBy?: 'updated_at' | 'id'; + orderBy?: 'updated_at' | 'id' | 'order'; /** Order value (ASC, DESC) */ order?: string; /** filterBy[]= */ From cfc9ed2919da7c1d068bce4c6eabc251365a3b14 Mon Sep 17 00:00:00 2001 From: "Luca Cannarozzo (@cannarocks)" Date: Wed, 9 Apr 2025 12:19:46 +0200 Subject: [PATCH 3/6] fix: add orderBy and order parameters to workspace queries in Promo and Templates contexts --- src/pages/Dashboard/PromoContext.tsx | 2 ++ src/pages/Templates/Context.tsx | 2 ++ src/pages/Templates/index.tsx | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/pages/Dashboard/PromoContext.tsx b/src/pages/Dashboard/PromoContext.tsx index ae7f0af14..3dc346c9c 100644 --- a/src/pages/Dashboard/PromoContext.tsx +++ b/src/pages/Dashboard/PromoContext.tsx @@ -36,6 +36,8 @@ export const PromoContextProvider = ({ children }: { children: ReactNode }) => { filterBy: { isPromo: 1, }, + orderBy: 'order', + order: 'asc', }, { skip: !activeWorkspace, diff --git a/src/pages/Templates/Context.tsx b/src/pages/Templates/Context.tsx index 90efe3ee2..f9b42a9dd 100644 --- a/src/pages/Templates/Context.tsx +++ b/src/pages/Templates/Context.tsx @@ -40,6 +40,8 @@ export const TemplatesContextProvider = ({ const { data } = useGetWorkspacesByWidTemplatesQuery( { wid: activeWorkspace?.id.toString() || '', + orderBy: 'order', + order: 'asc', }, { skip: !activeWorkspace, diff --git a/src/pages/Templates/index.tsx b/src/pages/Templates/index.tsx index bc8c36b1c..b8c334bdc 100644 --- a/src/pages/Templates/index.tsx +++ b/src/pages/Templates/index.tsx @@ -56,6 +56,8 @@ const Templates = () => { const { data, isLoading, isError } = useGetWorkspacesByWidTemplatesQuery( { wid: activeWorkspace?.id.toString() || '', + orderBy: 'order', + order: 'asc', }, { skip: !activeWorkspace, From c2a4b1ae6e254d5687a477ac62378c0dbacfad7d Mon Sep 17 00:00:00 2001 From: Marco Bonomo Date: Wed, 9 Apr 2025 12:25:47 +0200 Subject: [PATCH 4/6] fix: update translations and improve error handling in Touchpoint components --- src/locales/en/translation.json | 7 ++- src/locales/it/translation.json | 3 +- .../empty-state/projectEmptyState.tsx | 2 +- .../Touchpoints/parts/TouchpointItem.tsx | 44 +++---------------- .../utils/getIconFromTouchpoint.tsx | 12 +++-- src/pages/Templates/index.tsx | 10 ++--- 6 files changed, 24 insertions(+), 54 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 4369d718c..ead3e2e90 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -594,7 +594,6 @@ "__DASHBOARD_PLAN_COUNTER": "Setup Activities", "__DASHBOARD_SEARCH_INPUT_PLACEHOLDER": "Start typing...", "__DIGITAL_LITERACY_ERROR_REQUIRED": "No digital literacy level selected: Choose at least one level to continue", - "__EMPTY_SPACE_NO_FEATURE": "", "__ERROR_PAGE_BUTTON": "Refresh the page", "__ERROR_PAGE_SUBTITLE": "Let's try to solve it with the simplest trick.", "__ERROR_PAGE_TITLE": "This is unexpected.", @@ -1071,7 +1070,7 @@ "__PLAN_PAGE_MODULE_LANGUAGE_TITLE": "Spoken language", "__PLAN_PAGE_MODULE_MODULE_GENDER_FEMALE_HINT": " ", "__PLAN_PAGE_MODULE_MODULE_GENDER_FEMALE_LABEL": "Female", - "__PLAN_PAGE_MODULE_OUT_OF_SCOPE_INFO": "Enter description (max 256 characters)", + "__PLAN_PAGE_MODULE_OUT_OF_SCOPE_INFO": "Enter description (max 512 characters)", "__PLAN_PAGE_MODULE_OUT_OF_SCOPE_LABEL": "Specify areas that should be excluded from testing ", "__PLAN_PAGE_MODULE_OUT_OF_SCOPE_PLACEHOLDER": "Insert any sections that do not fit within the perimeter of the test.", "__PLAN_PAGE_MODULE_OUT_OF_SCOPE_REMOVE_BUTTON": "Delete", @@ -1081,7 +1080,6 @@ "__PLAN_PAGE_MODULE_SETUP_NOTE_DESCRIPTION_EDITOR_PLACEHOLDER": "In this section, you can set up the manual to guide testers during the activity.", "__PLAN_PAGE_MODULE_SETUP_NOTE_REMOVE_BUTTON": "Delete", "__PLAN_PAGE_MODULE_SETUP_NOTE_TITLE": "Before starting", - "__PLAN_PAGE_MODULE_TARGET_INFO": " ", "__PLAN_PAGE_MODULE_TARGET_LABEL": "Enter number of participants", "__PLAN_PAGE_MODULE_TARGET_NOTE_BLOCK_TITLE": "Before Starting", "__PLAN_PAGE_MODULE_TARGET_NOTE_DESCRIPTION_EDITOR_HEADER_TITLE": "Write here", @@ -1129,7 +1127,7 @@ "__PLAN_PAGE_MODULE_TASKS_TASK_ERROR_REQUIRED": "Add at least one task to proceed", "__PLAN_PAGE_MODULE_TASKS_TASK_LINK_HINT": "Add accessible URL with http:// or https:// prefix", "__PLAN_PAGE_MODULE_TASKS_TASK_LINK_LABEL": "Link to the area being tested", - "__PLAN_PAGE_MODULE_TASKS_TASK_LINK_PLACEHOLDER": "www.example.com", + "__PLAN_PAGE_MODULE_TASKS_TASK_LINK_PLACEHOLDER": "https://www.example.com", "__PLAN_PAGE_MODULE_TASKS_TASK_OPTIONAL_LABEL": "(optional)", "__PLAN_PAGE_MODULE_TASKS_TASK_TITLE_DESCRIPTION": "Define the function or experience participants will evaluate", "__PLAN_PAGE_MODULE_TASKS_TASK_TITLE_ERROR_MAX_LENGTH": "The title cannot exceed 64 characters", @@ -1260,6 +1258,7 @@ "__PROJECT_FORM_NAME_MAX": "This name is a bit long. We advise you to stay within 64 characters including spaces.", "__PROJECT_FORM_NAME_REQUIRED": "Choose a name before creating the project", "__PROJECT_PAGE_ARCHIVE_DESCRIPTION": "The Archive helps you keep your projects organized. You can restore a activity at any time or leave it here for future reference", + "__PROJECT_PAGE_EMPTY_STATE_NO_FEATURE": "Manage your own projects and test campaigns!", "__PROJECT_PAGE_UPDATE_PROJECT_DESCRIPTION_PLACEHOLDER": "Write a description", "__PROJECT_PAGE_UPDATE_PROJECT_NAME_ERROR": "The project name is already taken. Choose another name.", "__PROJECT_SETTINGS_CTA_TEXT": "Invite", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 2e244efd6..3f60a44fd 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -620,7 +620,6 @@ "__DASHBOARD_PLAN_COUNTER": "Attività in bozza", "__DASHBOARD_SEARCH_INPUT_PLACEHOLDER": "Cerca...", "__DIGITAL_LITERACY_ERROR_REQUIRED": "", - "__EMPTY_SPACE_NO_FEATURE": "", "__ERROR_PAGE_BUTTON": "Aggiorna la pagina", "__ERROR_PAGE_SUBTITLE": "Ecco un consiglio banale che il più delle volte funziona.", "__ERROR_PAGE_TITLE": "Non è così che doveva andare.", @@ -1110,7 +1109,6 @@ "__PLAN_PAGE_MODULE_SETUP_NOTE_DESCRIPTION_EDITOR_PLACEHOLDER": "", "__PLAN_PAGE_MODULE_SETUP_NOTE_REMOVE_BUTTON": "", "__PLAN_PAGE_MODULE_SETUP_NOTE_TITLE": "", - "__PLAN_PAGE_MODULE_TARGET_INFO": "", "__PLAN_PAGE_MODULE_TARGET_LABEL": "", "__PLAN_PAGE_MODULE_TARGET_NOTE_BLOCK_TITLE": "", "__PLAN_PAGE_MODULE_TARGET_NOTE_DESCRIPTION_EDITOR_HEADER_TITLE": "", @@ -1289,6 +1287,7 @@ "__PROJECT_FORM_NAME_MAX": "", "__PROJECT_FORM_NAME_REQUIRED": "", "__PROJECT_PAGE_ARCHIVE_DESCRIPTION": "", + "__PROJECT_PAGE_EMPTY_STATE_NO_FEATURE": "", "__PROJECT_PAGE_UPDATE_PROJECT_DESCRIPTION_PLACEHOLDER": "Aggiungi una descrizione", "__PROJECT_PAGE_UPDATE_PROJECT_NAME_ERROR": "Il nome del progetto è già in uso. Scegline un altro.", "__PROJECT_SETTINGS_CTA_TEXT": "Invita", diff --git a/src/pages/Dashboard/empty-state/projectEmptyState.tsx b/src/pages/Dashboard/empty-state/projectEmptyState.tsx index b1219e01d..482a994e2 100644 --- a/src/pages/Dashboard/empty-state/projectEmptyState.tsx +++ b/src/pages/Dashboard/empty-state/projectEmptyState.tsx @@ -149,7 +149,7 @@ export const ProjectEmptyState = () => { - {t('__EMPTY_SPACE_NO_FEATURE')} + {t('__PROJECT_PAGE_EMPTY_STATE_NO_FEATURE')} diff --git a/src/pages/Plan/modules/Touchpoints/parts/TouchpointItem.tsx b/src/pages/Plan/modules/Touchpoints/parts/TouchpointItem.tsx index 82bd3ce92..68a0a9b3d 100644 --- a/src/pages/Plan/modules/Touchpoints/parts/TouchpointItem.tsx +++ b/src/pages/Plan/modules/Touchpoints/parts/TouchpointItem.tsx @@ -33,43 +33,11 @@ const TouchpointItem = ({ form_factor.charAt(0).toUpperCase() + form_factor.slice(1); const formattedKind = kind.toUpperCase(); - const linuxError = - error && typeof error === 'object' && `touchpoints.${key}.os.linux` in error - ? error[`touchpoints.${key}.os.linux`] - : false; - const macosError = - error && typeof error === 'object' && `touchpoints.${key}.os.macos` in error - ? error[`touchpoints.${key}.os.macos`] - : false; - const windowsError = - error && - typeof error === 'object' && - `touchpoints.${key}.os.windows` in error - ? error[`touchpoints.${key}.os.windows`] - : false; - const iosError = - error && typeof error === 'object' && `touchpoints.${key}.os.ios` in error - ? error[`touchpoints.${key}.os.ios`] - : false; - const androidError = - error && - typeof error === 'object' && - `touchpoints.${key}.os.android` in error - ? error[`touchpoints.${key}.os.android`] - : false; - - const lengthError = - error && typeof error === 'object' && `touchpoints.${key}.length` in error - ? error[`touchpoints.${key}.length`] - : false; - - const hasError = - macosError || - windowsError || - linuxError || - iosError || - androidError || - lengthError; + const hasErrors = + (error && + typeof error === 'object' && + Object.keys(error).some((k) => k.startsWith(`touchpoints.${key}`))) ?? + false; return ( <> @@ -78,7 +46,7 @@ const TouchpointItem = ({ id={`touchpoint-${index}`} key={`touchpoint-${index}`} hasBorder - type={hasError ? 'danger' : 'default'} + type={hasErrors ? 'danger' : 'default'} > diff --git a/src/pages/Plan/modules/Touchpoints/utils/getIconFromTouchpoint.tsx b/src/pages/Plan/modules/Touchpoints/utils/getIconFromTouchpoint.tsx index a56aaa65a..85414c8d2 100644 --- a/src/pages/Plan/modules/Touchpoints/utils/getIconFromTouchpoint.tsx +++ b/src/pages/Plan/modules/Touchpoints/utils/getIconFromTouchpoint.tsx @@ -9,18 +9,22 @@ import { ReactComponent as WebSmartphoneIcon } from 'src/assets/icons/touchpoint import { components } from 'src/common/schema'; import { useModuleTouchpoints } from '../hooks'; -const getIconColor = () => { +const getIconColor = ( + touchpoint: components['schemas']['OutputModuleTouchpoints'] & { key: number } +) => { + const { key, os } = touchpoint; const { error } = useModuleTouchpoints(); const hasErrors = (error && typeof error === 'object' && - Object.keys(error).some((k) => k.startsWith('touchpoints'))) ?? + Object.keys(error).some((k) => k.startsWith(`touchpoints.${key}`))) ?? false; - // TODO: Handle grey color + const hasOs = Object.keys(os).length > 0; if (hasErrors) return getColor(appTheme.colors.dangerHue, 900); + if (!hasErrors && !hasOs) return getColor(appTheme.palette.grey, 600); return getColor(appTheme.colors.primaryHue, 600); }; @@ -28,7 +32,7 @@ const getIconFromTouchpointOutput = ( touchpoint: components['schemas']['OutputModuleTouchpoints'] & { key: number } ) => { const { kind, form_factor } = touchpoint; - const color = getIconColor(); + const color = getIconColor(touchpoint); switch (form_factor) { case 'desktop': diff --git a/src/pages/Templates/index.tsx b/src/pages/Templates/index.tsx index bc8c36b1c..f9189f1bd 100644 --- a/src/pages/Templates/index.tsx +++ b/src/pages/Templates/index.tsx @@ -53,7 +53,7 @@ const Templates = () => { const { status } = useAppSelector((state) => state.user); const canViewTemplates = useCanAccessToActiveWorkspace(); - const { data, isLoading, isError } = useGetWorkspacesByWidTemplatesQuery( + const { isLoading, isError } = useGetWorkspacesByWidTemplatesQuery( { wid: activeWorkspace?.id.toString() || '', }, @@ -62,16 +62,16 @@ const Templates = () => { } ); - if (!data || isLoading || status === 'loading') { - return ; - } - if (!canViewTemplates || isError) { navigate(notFoundRoute, { state: { from: location.pathname }, }); } + if (isLoading || status === 'loading') { + return ; + } + return ( } From a21ef7d2fcaa7c2d4e2aa5e478ea23bb353445aa Mon Sep 17 00:00:00 2001 From: Marco Bonomo Date: Wed, 9 Apr 2025 12:56:32 +0200 Subject: [PATCH 5/6] fix: remove access check for viewing templates when handling errors --- src/pages/Templates/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/Templates/index.tsx b/src/pages/Templates/index.tsx index 59829b973..90d88b968 100644 --- a/src/pages/Templates/index.tsx +++ b/src/pages/Templates/index.tsx @@ -8,7 +8,6 @@ import PlanCreationInterface from 'src/common/components/PlanCreationInterface'; import { useGetWorkspacesByWidTemplatesQuery } from 'src/features/api'; import { Page } from 'src/features/templates/Page'; import { useActiveWorkspace } from 'src/hooks/useActiveWorkspace'; -import { useCanAccessToActiveWorkspace } from 'src/hooks/useCanAccessToActiveWorkspace'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; import Body from './Body'; import { CategoriesNav } from './CategoriesNav'; @@ -51,7 +50,6 @@ const Templates = () => { const location = useLocation(); const { activeWorkspace } = useActiveWorkspace(); const { status } = useAppSelector((state) => state.user); - const canViewTemplates = useCanAccessToActiveWorkspace(); const { isLoading, isError } = useGetWorkspacesByWidTemplatesQuery( { @@ -64,7 +62,7 @@ const Templates = () => { } ); - if (!canViewTemplates || isError) { + if (isError) { navigate(notFoundRoute, { state: { from: location.pathname }, }); From e9386d37846c777f4b51db079d36538af929683b Mon Sep 17 00:00:00 2001 From: "Luca Cannarozzo (@cannarocks)" Date: Wed, 9 Apr 2025 13:47:44 +0200 Subject: [PATCH 6/6] fix: add mock for templates retrieval without permissions --- tests/e2e/templates/index.spec.ts | 2 +- tests/fixtures/pages/Templates.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/e2e/templates/index.spec.ts b/tests/e2e/templates/index.spec.ts index 305e101e4..bf378f721 100644 --- a/tests/e2e/templates/index.spec.ts +++ b/tests/e2e/templates/index.spec.ts @@ -133,7 +133,7 @@ test.describe("If i don't have workspace access", () => { // shared workspaces means no workspace access right now await templates.mocksharedWorkspacesList(); - await templates.mockGetTemplates(); + await templates.mockGetTemplatesWithoutPermissions(); await templates.mockGetProjects(); await templates.mockPostPlans(); }); diff --git a/tests/fixtures/pages/Templates.ts b/tests/fixtures/pages/Templates.ts index 9f80373de..993cb2748 100644 --- a/tests/fixtures/pages/Templates.ts +++ b/tests/fixtures/pages/Templates.ts @@ -66,6 +66,21 @@ export class Templates extends UnguessPage { }); } + async mockGetTemplatesWithoutPermissions() { + await this.page.route('*/**/api/workspaces/1/templates*', async (route) => { + if (route.request().method() === 'GET') { + await route.fulfill({ + status: 403, + body: JSON.stringify({ + message: "Workspace doesn't exist or not accessible", + }), + }); + } else { + await route.continue(); + } + }); + } + async mockPostPlans() { await this.page.route('*/**/api/workspaces/1/plans', async (route) => { if (route.request().method() === 'POST') {