diff --git a/package.json b/package.json index 43816d726..69b54862e 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,16 @@ "@analytics/google-tag-manager": "^0.6.0", "@analytics/segment": "^2.1.0", "@appquality/languages": "1.4.3", - "@appquality/unguess-design-system": "4.0.42", + "@appquality/unguess-design-system": "4.0.46", + "@atlaskit/pragmatic-drag-and-drop": "^1.7.4", + "@atlaskit/pragmatic-drag-and-drop-flourish": "^2.0.3", + "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0", "@headwayapp/react-widget": "^0.0.4", "@reduxjs/toolkit": "^1.8.0", "@sentry/react": "^8.32.0", "@zendeskgarden/svg-icons": "^7.2.0", - "comuni-province-regioni": "^0.4.3", "analytics": "^0.8.16", + "comuni-province-regioni": "^0.4.3", "date-fns": "^2.28.0", "formik": "^2.2.9", "i18n-iso-countries": "^7.3.0", diff --git a/src/app/store.ts b/src/app/store.ts index 72ee872c1..3ac230c89 100644 --- a/src/app/store.ts +++ b/src/app/store.ts @@ -6,13 +6,11 @@ import bugsPageReducer from '../features/bugsPage/bugsPageSlice'; import filterReducer from '../features/campaignsFilter/campaignsFilterSlice'; import navigationReducer from '../features/navigation/navigationSlice'; import planModulesReducer from '../features/planModules'; -import userReducer from '../features/user/userSlice'; import uxFilterReducer from '../features/uxFilters'; import workspaceReducer from '../features/workspaces/workspaceSlice'; export const store = configureStore({ reducer: { - user: userReducer, navigation: navigationReducer, workspaces: workspaceReducer, filters: filterReducer, diff --git a/src/assets/icons/key.svg b/src/assets/icons/key.svg new file mode 100644 index 000000000..6d09b5d68 --- /dev/null +++ b/src/assets/icons/key.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/template.svg b/src/assets/icons/template.svg new file mode 100644 index 000000000..870b325f5 --- /dev/null +++ b/src/assets/icons/template.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg new file mode 100644 index 000000000..4309fcffb --- /dev/null +++ b/src/assets/icons/user.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/common/Pages.tsx b/src/common/Pages.tsx index c1411416c..315a43685 100644 --- a/src/common/Pages.tsx +++ b/src/common/Pages.tsx @@ -23,6 +23,7 @@ import Manual from 'src/pages/Manual'; import MediaNotFound from 'src/pages/NotFound/MediaNotFound'; import NotFound from 'src/pages/NotFound/NotFound'; import Plan from 'src/pages/Plan'; +import Profile from 'src/pages/Profile'; import Template from 'src/pages/Template'; import Templates from 'src/pages/Templates'; import Video from 'src/pages/Video'; @@ -45,7 +46,11 @@ const Pages = () => { } + errorElement={ + process.env.NODE_ENV === 'development' ? undefined : ( + + ) + } > { /> } /> } /> + } + /> ))} diff --git a/src/common/Track.tsx b/src/common/Track.tsx index 5782fe16d..4c3363b8b 100644 --- a/src/common/Track.tsx +++ b/src/common/Track.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { Helmet } from 'react-helmet'; import { useLocation } from 'react-router-dom'; -import { useAppSelector } from 'src/app/hooks'; +import { useGetUsersMeQuery } from 'src/features/api'; import { useActiveWorkspace } from 'src/hooks/useActiveWorkspace'; import { useAnalytics } from 'use-analytics'; @@ -26,7 +26,7 @@ export const Track = ({ title: string; children: React.ReactNode; }) => { - const { userData } = useAppSelector((state) => state.user); + const { data: userData, isLoading, isSuccess } = useGetUsersMeQuery(); const { activeWorkspace } = useActiveWorkspace(); const { track, identify, page } = useAnalytics(); const location = useLocation(); @@ -44,7 +44,7 @@ export const Track = ({ ); useEffect(() => { - if (userData?.role && activeWorkspace?.company) { + if (!isLoading && isSuccess && userData?.role && activeWorkspace?.company) { identify(`profile_${userData.profile_id}`, { role: userData.role, customer_role: userData.customer_role, diff --git a/src/pages/JoinPage/Steps/PasswordRequirements.tsx b/src/common/components/PasswordRequirements.tsx similarity index 71% rename from src/pages/JoinPage/Steps/PasswordRequirements.tsx rename to src/common/components/PasswordRequirements.tsx index 795147968..41aec355b 100644 --- a/src/pages/JoinPage/Steps/PasswordRequirements.tsx +++ b/src/common/components/PasswordRequirements.tsx @@ -1,9 +1,7 @@ import { MD, theme } from '@appquality/unguess-design-system'; import { ReactComponent as X } from 'src/assets/icons/password-check-x.svg'; import { ReactComponent as Check } from 'src/assets/icons/password-check-v.svg'; -import { useFormikContext } from 'formik'; import { useTranslation } from 'react-i18next'; -import { JoinFormValues } from '../valuesType'; const PasswordRequirement = ({ check, @@ -29,30 +27,23 @@ const PasswordRequirement = ({ ); -const PasswordRequirements = () => { - const { values } = useFormikContext(); +const PasswordRequirements = ({ password }: { password: string }) => { const { t } = useTranslation(); return (
{t('PASSWORD_VALIDATOR_PASSWORD_REQUIREMENTS')}
    - values.password.length >= 6}> + password.length >= 6}> {t('PASSWORD_VALIDATOR_MINIMUM_OF_6_CHARACTERS')} - values.password.match(/[A-Z]/) !== null} - > + password.match(/[A-Z]/) !== null}> {t('PASSWORD_VALIDATOR_CONTAIN_AN_UPPERCASE_LETTER')} - values.password.match(/[a-z]/) !== null} - > + password.match(/[a-z]/) !== null}> {t('PASSWORD_VALIDATOR_CONTAIN_A_LOWERCASE_LETTER')} - values.password.match(/[0-9]/) !== null} - > + password.match(/[0-9]/) !== null}> {t('PASSWORD_VALIDATOR_CONTAIN_A_NUMBER')}
diff --git a/src/common/components/inviteUsers/userItem.tsx b/src/common/components/inviteUsers/userItem.tsx index 254c0a77f..cd0232849 100644 --- a/src/common/components/inviteUsers/userItem.tsx +++ b/src/common/components/inviteUsers/userItem.tsx @@ -9,9 +9,11 @@ import { } from '@appquality/unguess-design-system'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAppSelector } from 'src/app/hooks'; import { appTheme } from 'src/app/theme'; -import { GetWorkspacesByWidUsersApiResponse } from 'src/features/api'; +import { + GetWorkspacesByWidUsersApiResponse, + useGetUsersMeQuery, +} from 'src/features/api'; import styled from 'styled-components'; import { getInitials } from '../navigation/header/utils'; import RemoveConfirmModal from './modals/RemoveConfirmModal'; @@ -40,7 +42,8 @@ export const UserItem = ({ showRemoveConfirm?: boolean; }) => { const { t } = useTranslation(); - const { userData } = useAppSelector((state) => state.user); + const { data: userData, isLoading, isSuccess } = useGetUsersMeQuery(); + const [showRemoveConfirmModal, setShowRemoveConfirmModal] = useState(false); const handleRemoveUser = () => { @@ -49,103 +52,111 @@ export const UserItem = ({ const isMe = userData?.email === user.email; + if (isLoading || !isSuccess || !userData) return null; + return ( - <> - - - {getInitials(user.name.length ? user.name : user.email)} - -
- - - {user.name.length ? user.name : user.email}{' '} - {isMe && t('__WORKSPACE_SETTINGS_CURRENT_MEMBER_YOU_LABEL')} - - - {user.name.length > 0 && ( - + + + {getInitials(user.name.length ? user.name : user.email)} + +
+ - {user.email} - - )} -
- {onResendInvite && onRemoveUser ? ( -
- {!isMe && ( - + {user.name.length ? user.name : user.email}{' '} + {isMe && t('__WORKSPACE_SETTINGS_CURRENT_MEMBER_YOU_LABEL')} + + + {user.name.length > 0 && ( + { - if (value === 'invite') { - onResendInvite(); - return; + > + {user.email} + + )} +
+ {onResendInvite && onRemoveUser ? ( +
+ {!isMe && ( + { + if (value === 'invite') { + onResendInvite(); + return; + } - if (value === 'remove') { - if (showRemoveConfirm) { - handleRemoveUser(); - } else { - onRemoveUser(); + if (value === 'remove') { + if (showRemoveConfirm) { + handleRemoveUser(); + } else { + onRemoveUser(); + } } - } - }} - > - {user.invitationPending && ( - - {t('__WORKSPACE_SETTINGS_MEMBER_RESEND_INVITE_ACTION')} + }} + > + {user.invitationPending && ( + + {t('__WORKSPACE_SETTINGS_MEMBER_RESEND_INVITE_ACTION')} + + )} + + + {t('__WORKSPACE_SETTINGS_MEMBER_REMOVE_USER_ACTION')} + - )} - - - {t('__WORKSPACE_SETTINGS_MEMBER_REMOVE_USER_ACTION')} - - - - )} -
- ) : ( - <> - {user.invitationPending && ( - - {t('__WORKSPACE_SETTINGS_MEMBER_INVITATION_PENDING_LABEL')} - - )} - {!user.invitationPending && ( - - {t('__WORKSPACE_SETTINGS_MEMBER_ACTIONS_LABEL')} - - )} - + + )} +
+ ) : ( + <> + {user.invitationPending && ( + + {t('__WORKSPACE_SETTINGS_MEMBER_INVITATION_PENDING_LABEL')} + + )} + {!user.invitationPending && ( + + {t('__WORKSPACE_SETTINGS_MEMBER_ACTIONS_LABEL')} + + )} + + )} +
+ {showRemoveConfirmModal && onRemoveUser && ( + onRemoveUser(includeShared)} + handleCancel={() => setShowRemoveConfirmModal(false)} + /> )} - - {showRemoveConfirmModal && onRemoveUser && ( - onRemoveUser(includeShared)} - handleCancel={() => setShowRemoveConfirmModal(false)} - /> - )} - + + ) ); }; diff --git a/src/common/components/navigation/header/profileAvatar.tsx b/src/common/components/navigation/header/profileAvatar.tsx index b1de9a3f1..9644b8008 100644 --- a/src/common/components/navigation/header/profileAvatar.tsx +++ b/src/common/components/navigation/header/profileAvatar.tsx @@ -8,6 +8,7 @@ import { import { useAppDispatch, useAppSelector } from 'src/app/hooks'; import { prepareGravatar } from 'src/common/utils'; import { toggleProfileModal } from 'src/features/navigation/navigationSlice'; +import { useGetUsersMeQuery } from 'src/features/api'; import { ReactComponent as ChevronIcon } from 'src/assets/icons/chevron-down-stroke.svg'; import styled from 'styled-components'; import { getInitials } from './utils'; @@ -20,7 +21,9 @@ const ChevronButton = styled(IconButton)` export const ProfileAvatar = () => { const dispatch = useAppDispatch(); - const { userData: user } = useAppSelector((state) => state.user); + + const { data: user, isLoading, isError } = useGetUsersMeQuery(); + const { isProfileModalOpen } = useAppSelector((state) => state.navigation); const toggleProfileModalState = () => { @@ -30,7 +33,7 @@ export const ProfileAvatar = () => { return ( - {!user ? ( + {!user || isLoading || isError ? ( + + + + + + + + diff --git a/src/common/schema.ts b/src/common/schema.ts index dede54d87..b9cc440fb 100644 --- a/src/common/schema.ts +++ b/src/common/schema.ts @@ -415,6 +415,8 @@ export interface paths { }; "/users/me": { get: operations["get-users-me"]; + /** Update one or multiple user data */ + patch: operations["patch-users-me"]; }; "/users/me/preferences": { get: operations["get-users-me-preferences"]; @@ -576,6 +578,7 @@ export interface paths { }; "/workspaces/{wid}/templates": { get: operations["get-workspaces-templates"]; + post: operations["post-workspaces-wid-templates"]; parameters: { path: { /** Workspace (company, customer) id */ @@ -1186,6 +1189,7 @@ export interface components { /** OutputModuleTaskAccessibility */ OutputModuleTaskAccessibility: { description?: string; + id?: string; /** @enum {string} */ kind: "accessibility"; title: string; @@ -1195,6 +1199,7 @@ export interface components { /** SubcomponentTaskBug */ OutputModuleTaskBug: { description?: string; + id?: string; /** @enum {string} */ kind: "bug"; title: string; @@ -1204,6 +1209,7 @@ export interface components { /** OutputModuleTaskExplorativeBug */ OutputModuleTaskExplorativeBug: { description?: string; + id?: string; /** @enum {string} */ kind: "explorative-bug"; title: string; @@ -1213,6 +1219,7 @@ export interface components { /** OutputModuleTaskModerateVideo */ OutputModuleTaskModerateVideo: { description?: string; + id?: string; /** @enum {string} */ kind: "moderate-video"; title: string; @@ -1222,6 +1229,7 @@ export interface components { /** SubcomponentTaskSurvey */ OutputModuleTaskSurvey: { description?: string; + id?: string; /** @enum {string} */ kind: "survey"; title: string; @@ -1231,6 +1239,7 @@ export interface components { /** SubcomponentTaskVideo */ OutputModuleTaskVideo: { description?: string; + id?: string; /** @enum {string} */ kind: "video"; title: string; @@ -1504,8 +1513,10 @@ export interface components { /** Format: email */ email: string; features?: components["schemas"]["Feature"][]; + first_name: string; /** @description This is the main id of the user. Currently is equal to tryber_wp_user_id */ id: number; + last_name: string; name: string; picture?: string; profile_id: number; @@ -3560,6 +3571,36 @@ export interface operations { 500: components["responses"]["Error"]; }; }; + /** Update one or multiple user data */ + "patch-users-me": { + responses: { + /** OK */ + 200: { + content: { + "application/json": { + name?: string; + role?: string; + surname?: string; + }; + }; + }; + 401: components["responses"]["Error"]; + 403: components["responses"]["Error"]; + }; + requestBody: { + content: { + "application/json": { + name?: string; + password?: { + current: string; + new: string; + }; + roleId?: number; + surname?: string; + }; + }; + }; + }; "get-users-me-preferences": { responses: { /** OK */ @@ -4164,6 +4205,37 @@ export interface operations { 500: components["responses"]["Error"]; }; }; + "post-workspaces-wid-templates": { + parameters: { + path: { + /** Workspace (company, customer) id */ + wid: string; + }; + }; + responses: { + /** Created */ + 201: { + content: { + "application/json": { + id: number; + }; + }; + }; + /** Forbidden */ + 403: unknown; + /** Not Found */ + 404: unknown; + }; + requestBody: { + content: { + "application/json": { + description?: string; + from_plan: number; + name: string; + }; + }; + }; + }; "get-workspaces-wid-templates-tid": { parameters: { path: { diff --git a/src/common/wpapi.ts b/src/common/wpapi.ts index 49252c715..442f2cb69 100644 --- a/src/common/wpapi.ts +++ b/src/common/wpapi.ts @@ -98,6 +98,16 @@ const WPAPI = { console.error('error', error); }); }, + destroyOtherSessions: () => + fetch( + `${process.env.REACT_APP_CROWD_WP_URL}/wp-admin/admin-ajax.php?action=destroy_other_sessions`, + { + method: 'GET', + } + ).catch((e) => { + // eslint-disable-next-line no-console + console.error(e.message); + }), }; export default WPAPI; diff --git a/src/features/api/api.ts b/src/features/api/api.ts index 0f2071819..1f7258d88 100644 --- a/src/features/api/api.ts +++ b/src/features/api/api.ts @@ -35,6 +35,7 @@ export const apiSlice = createApi({ 'Translation', 'Archive', 'Plans', + 'Users', ], endpoints: () => ({}), }); diff --git a/src/features/api/apiTags.ts b/src/features/api/apiTags.ts index 817e60976..e930f9002 100644 --- a/src/features/api/apiTags.ts +++ b/src/features/api/apiTags.ts @@ -1,4 +1,5 @@ -import { unguessApi } from '.'; +import * as uuid from 'uuid'; +import { GetPlansByPidApiResponse, ModuleTask, unguessApi } from '.'; unguessApi.enhanceEndpoints({ endpoints: { @@ -92,9 +93,15 @@ unguessApi.enhanceEndpoints({ deleteCampaignsByCidBugsAndBidCommentsCmid: { invalidatesTags: ['BugComments'], }, + getUsersMe: { + providesTags: ['Users'], + }, getUsersMePreferences: { providesTags: ['Preferences'], }, + patchUsersMe: { + invalidatesTags: ['Users'], + }, putUsersMePreferencesBySlug: { invalidatesTags: ['Preferences'], }, @@ -266,6 +273,33 @@ unguessApi.enhanceEndpoints({ }, getPlansByPid: { providesTags: ['Plans'], + transformResponse: (response: GetPlansByPidApiResponse) => { + if (response && response.config) { + // find the task module if any + const taskModule = response.config.modules.find( + (module) => module.type === 'tasks' + ) as ModuleTask | undefined; + if (taskModule && taskModule?.output) { + // add an id to each task for better identification + const mappedTasks = taskModule.output.map((task) => ({ + ...task, + id: task.id || uuid.v4(), // generate a new UUID for each task + })); + taskModule.output = mappedTasks; + // now we can safely return the response + response.config.modules = response.config.modules.map((module) => { + if (module.type === 'tasks') { + return { + ...module, + output: mappedTasks, + }; + } + return module; + }); + } + } + return response; + }, }, patchPlansByPid: { invalidatesTags: ['Plans'], diff --git a/src/features/api/index.ts b/src/features/api/index.ts index 13440433e..4d2911195 100644 --- a/src/features/api/index.ts +++ b/src/features/api/index.ts @@ -541,6 +541,13 @@ const injectedRtkApi = api.injectEndpoints({ getUsersMe: build.query({ query: () => ({ url: `/users/me` }), }), + patchUsersMe: build.mutation({ + query: (queryArg) => ({ + url: `/users/me`, + method: 'PATCH', + body: queryArg.body, + }), + }), getUsersMePreferences: build.query< GetUsersMePreferencesApiResponse, GetUsersMePreferencesApiArg @@ -747,6 +754,16 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + postWorkspacesByWidTemplates: build.mutation< + PostWorkspacesByWidTemplatesApiResponse, + PostWorkspacesByWidTemplatesApiArg + >({ + query: (queryArg) => ({ + url: `/workspaces/${queryArg.wid}/templates`, + method: 'POST', + body: queryArg.body, + }), + }), deleteWorkspacesByWidTemplatesAndTid: build.mutation< DeleteWorkspacesByWidTemplatesAndTidApiResponse, DeleteWorkspacesByWidTemplatesAndTidApiArg @@ -1606,6 +1623,22 @@ export type HeadUsersByEmailByEmailApiArg = { }; export type GetUsersMeApiResponse = /** status 200 */ User; export type GetUsersMeApiArg = void; +export type PatchUsersMeApiResponse = /** status 200 OK */ { + name?: string; + role?: string; + surname?: string; +}; +export type PatchUsersMeApiArg = { + body: { + name?: string; + password?: { + current: string; + new: string; + }; + roleId?: number; + surname?: string; + }; +}; export type GetUsersMePreferencesApiResponse = /** status 200 OK */ { items?: UserPreference[]; }; @@ -1862,6 +1895,19 @@ export type GetWorkspacesByWidTemplatesApiArg = { /** filterBy[]= */ filterBy?: any; }; +export type PostWorkspacesByWidTemplatesApiResponse = + /** status 201 Created */ { + id: number; + }; +export type PostWorkspacesByWidTemplatesApiArg = { + /** Workspace (company, customer) id */ + wid: string; + body: { + description?: string; + from_plan: number; + name: string; + }; +}; export type DeleteWorkspacesByWidTemplatesAndTidApiResponse = /** status 200 OK */ {}; export type DeleteWorkspacesByWidTemplatesAndTidApiArg = { @@ -2375,36 +2421,42 @@ export type ModuleDate = { }; export type SubcomponentTaskVideo = { description?: string; + id?: string; kind: 'video'; title: string; url?: string; }; export type SubcomponentTaskBug = { description?: string; + id?: string; kind: 'bug'; title: string; url?: string; }; export type SubcomponentTaskSurvey = { description?: string; + id?: string; kind: 'survey'; title: string; url?: string; }; export type OutputModuleTaskModerateVideo = { description?: string; + id?: string; kind: 'moderate-video'; title: string; url?: string; }; export type OutputModuleTaskExplorativeBug = { description?: string; + id?: string; kind: 'explorative-bug'; title: string; url?: string; }; export type OutputModuleTaskAccessibility = { description?: string; + id?: string; kind: 'accessibility'; title: string; url?: string; @@ -2674,8 +2726,10 @@ export type User = { customer_role: string; email: string; features?: Feature[]; + first_name: string; /** This is the main id of the user. Currently is equal to tryber_wp_user_id */ id: number; + last_name: string; name: string; picture?: string; profile_id: number; @@ -2832,6 +2886,7 @@ export const { usePostUsersMutation, useHeadUsersByEmailByEmailMutation, useGetUsersMeQuery, + usePatchUsersMeMutation, useGetUsersMePreferencesQuery, usePutUsersMePreferencesBySlugMutation, useGetUsersRolesQuery, @@ -2854,6 +2909,7 @@ export const { useGetWorkspacesByWidProjectsAndPidQuery, useGetWorkspacesByWidProjectsAndPidCampaignsQuery, useGetWorkspacesByWidTemplatesQuery, + usePostWorkspacesByWidTemplatesMutation, useDeleteWorkspacesByWidTemplatesAndTidMutation, useGetWorkspacesByWidTemplatesAndTidQuery, useDeleteWorkspacesByWidUsersMutation, diff --git a/src/features/navigation/Navigation/NavigationProfileModal.tsx b/src/features/navigation/Navigation/NavigationProfileModal.tsx index 8f75a58f9..045d376f4 100644 --- a/src/features/navigation/Navigation/NavigationProfileModal.tsx +++ b/src/features/navigation/Navigation/NavigationProfileModal.tsx @@ -4,12 +4,14 @@ import { useToast, } from '@appquality/unguess-design-system'; import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; import { useAppDispatch, useAppSelector } from 'src/app/hooks'; import { isDev } from 'src/common/isDevEnvironment'; import { prepareGravatar } from 'src/common/utils'; import WPAPI from 'src/common/wpapi'; import { useGetUsersMePreferencesQuery, + useGetUsersMeQuery, usePutUsersMePreferencesBySlugMutation, } from 'src/features/api'; import { useActiveWorkspace } from 'src/hooks/useActiveWorkspace'; @@ -20,12 +22,14 @@ export const NavigationProfileModal = () => { const isProfileModalOpen = useAppSelector( (state) => state.navigation.isProfileModalOpen ); - const { userData: user } = useAppSelector((state) => state.user); + const { data: user, isLoading, error: dataError } = useGetUsersMeQuery(); + const { activeWorkspace } = useActiveWorkspace(); const { addToast } = useToast(); const { t, i18n } = useTranslation(); const dispatch = useAppDispatch(); + const navigate = useNavigate(); const { data: preferences } = useGetUsersMePreferencesQuery(); @@ -62,6 +66,8 @@ export const NavigationProfileModal = () => { }); }; + if (dataError || !user || isLoading) return null; + const profileModal = { user: { name: user.name, @@ -90,6 +96,13 @@ export const NavigationProfileModal = () => { currentLanguage: i18n.language, feedbackTitle: t('__PROFILE_MODAL_FEEDBACK_TITLE'), feedbackSubTitle: t('__PROFILE_MODAL_FEEDBACK_SUBTITLE'), + profile: { + title: t('__PROFILE_MODAL_GO_TO_PROFILE'), + onClick: () => { + navigate('/profile'); + dispatch(setProfileModalOpen(false)); + }, + }, csmTitle: t('__PROFILE_MODAL_CSM_TITLE'), csmContactLabel: t('__PROFILE_MODAL_CSM_CONTACT_LABEL'), languageTitle: t('__PROFILE_MODAL_LANGUAGES_TITLE'), diff --git a/src/features/planModules/index.tsx b/src/features/planModules/index.tsx index 39be38232..c8f416cd7 100644 --- a/src/features/planModules/index.tsx +++ b/src/features/planModules/index.tsx @@ -103,6 +103,7 @@ const planModuleSlice = createSlice({ state.currentModules = state.currentModules.filter( (module) => module !== action.payload ); + // Remove errors related to the removed module const newErrors = { ...state.errors }; Object.keys(newErrors).forEach((key) => { if (key.startsWith(`${action.payload}.`) || key === action.payload) { @@ -110,6 +111,10 @@ const planModuleSlice = createSlice({ } }); state.errors = newErrors; + // remove the validation function for the removed module + if (state.validationFunctions[action.payload]) { + delete state.validationFunctions[action.payload]; + } }, setStatus: (state, action: PayloadAction) => { state.status = action.payload; diff --git a/src/features/templates/Logged.tsx b/src/features/templates/Logged.tsx index 0b41ead8d..4d56b165d 100644 --- a/src/features/templates/Logged.tsx +++ b/src/features/templates/Logged.tsx @@ -3,11 +3,11 @@ import { Chrome, Body, Main, Anchor } from '@appquality/unguess-design-system'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; import { useNavigate, useLocation } from 'react-router-dom'; import { appTheme } from 'src/app/theme'; -import { useAppSelector } from 'src/app/hooks'; import styled from 'styled-components'; import { PageLoader } from 'src/common/components/PageLoader'; import * as Sentry from '@sentry/react'; import { Navigation } from '../navigation/Navigation'; +import { useGetUsersMeQuery } from '../api'; const StyledMain = styled(Main)` background-color: ${({ theme }) => theme.palette.grey[100]}; @@ -37,17 +37,17 @@ export const Logged = ({ const loginRoute = useLocalizeRoute('login'); const navigate = useNavigate(); - const { status, userData } = useAppSelector((state) => state.user); + const { data: userData, isLoading, error, isFetching } = useGetUsersMeQuery(); useEffect(() => { - if (status === 'failed') { + if (error) { navigate(loginRoute, { state: { from: locationState?.from ?? pathname }, }); } - }, [status]); + }, [error]); - if (status === 'idle' || status === 'loading') { + if (isLoading || isFetching || !userData) { return ; } diff --git a/src/features/user/userSlice.ts b/src/features/user/userSlice.ts deleted file mode 100644 index af1ecee06..000000000 --- a/src/features/user/userSlice.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { fetchUser } from './actions/fetchUser'; -import { UserState } from './types'; - -const initialState: UserState = { - status: 'idle', - userData: { - id: 0, - email: '', - role: 'none', - name: '', - profile_id: 0, - customer_role: 'Not set', - tryber_wp_user_id: 0, - unguess_wp_user_id: 0, - }, -}; - -const userSlice = createSlice({ - name: 'user', - initialState, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(fetchUser.pending, (state) => { - state.status = 'loading'; - }); - builder.addCase(fetchUser.fulfilled, (state, action) => { - state.userData = action.payload; - state.status = 'logged'; - }); - builder.addCase(fetchUser.rejected, (state) => { - state.status = 'failed'; - }); - }, -}); - -export default userSlice.reducer; diff --git a/src/hooks/useActiveWorkspace.ts b/src/hooks/useActiveWorkspace.ts index e9db17977..6ecd73c56 100644 --- a/src/hooks/useActiveWorkspace.ts +++ b/src/hooks/useActiveWorkspace.ts @@ -6,10 +6,11 @@ import { saveWorkspaceToLs, } from 'src/features/navigation/cachedStorage'; -export const useActiveWorkspace = () => { - const { data: workspaces, isLoading } = useGetWorkspacesQuery({ - orderBy: 'company', - }); +export const useActiveWorkspace = ({ skip = false } = {}) => { + const { data: workspaces, isLoading } = useGetWorkspacesQuery( + { orderBy: 'company' }, + { skip } + ); const activeWorkspaceFromRedux = useAppSelector( (state) => state.navigation.activeWorkspace diff --git a/src/hooks/useFeatureFlag.ts b/src/hooks/useFeatureFlag.ts index 0cd2cc39f..e984cfd3c 100644 --- a/src/hooks/useFeatureFlag.ts +++ b/src/hooks/useFeatureFlag.ts @@ -1,23 +1,25 @@ -import { shallowEqual } from 'react-redux'; -import { useAppSelector } from 'src/app/hooks'; +import { useGetUsersMeQuery } from 'src/features/api'; export const useFeatureFlag = () => { - const { role, features } = useAppSelector( - (state) => ({ - role: state.user.userData.role, - features: state.user.userData.features, - }), - shallowEqual - ); + const { + data: userData, + isLoading: isUserDataLoading, + isFetching: isUserFetching, + isError, + } = useGetUsersMeQuery(); + + if (isUserDataLoading || isUserFetching || isError || !userData) { + return { hasFeatureFlag: () => false }; + } + + const { role, features = [] } = userData; const hasFeatureFlag = (slug?: string) => { if (role === 'administrator') { return true; } - if (features) { - return features.some((feature) => feature.slug === slug); - } - return false; + + return features.some((feature) => feature.slug === slug); }; return { hasFeatureFlag }; diff --git a/src/hooks/useGTMevent.ts b/src/hooks/useGTMevent.ts index 181da662e..471a6e277 100644 --- a/src/hooks/useGTMevent.ts +++ b/src/hooks/useGTMevent.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; -import { shallowEqual } from 'react-redux'; -import { useAppSelector } from 'src/app/hooks'; +import { useGetUsersMeQuery } from 'src/features/api'; import { useAnalytics } from 'use-analytics'; import { useActiveWorkspace } from './useActiveWorkspace'; @@ -15,20 +14,23 @@ interface GTMEventData { export const useSendGTMevent = ({ loggedUser = true, }: { loggedUser?: boolean } = {}) => { - const user = useAppSelector( - (state) => ({ - role: state.user.userData.role, - customer_role: state.user.userData.customer_role, - tryber_wp_user_id: state.user.userData.tryber_wp_user_id, - id: state.user.userData.id, - name: state.user.userData.name, - email: state.user.userData.email, - }), - shallowEqual - ); - const { activeWorkspace } = useActiveWorkspace(); + const { data: userData } = useGetUsersMeQuery(undefined, { + skip: !loggedUser, + }); + const { activeWorkspace } = useActiveWorkspace({ + skip: !loggedUser, + }); const { track } = useAnalytics(); + const user = userData || { + role: undefined, + customer_role: undefined, + tryber_wp_user_id: undefined, + id: undefined, + name: undefined, + email: undefined, + }; + const callback = useCallback( (data: GTMEventData) => { if ( diff --git a/src/index.tsx b/src/index.tsx index 599020200..dff9bbb4b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,14 +1,12 @@ -import { getWorkspaces } from 'src/features/workspaces/actions'; import { createRoot } from 'react-dom/client'; +import { getWorkspaces } from 'src/features/workspaces/actions'; import App from './app/App'; import { store } from './app/store'; -import { fetchUser } from './features/user/actions/fetchUser'; import reportWebVitals from './reportWebVitals'; const container = document.getElementById('root'); const root = createRoot(container!); // createRoot(container!) if you use TypeScript -store.dispatch(fetchUser()); store.dispatch(getWorkspaces()); root.render(); diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ba310e231..274223efd 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -25,7 +25,6 @@ "__ASIDE_NAVIGATION_MODULE_ADDITIONAL_TARGET_SUBTITLE": " ", "__ASIDE_NAVIGATION_MODULE_AGE_SUBTITLE": "Participant age groups", "__ASIDE_NAVIGATION_MODULE_BROWSER_SUBTITLE": "Compatible browsers", - "__ASIDE_NAVIGATION_MODULE_DATES_BLOCK_SUBTITLE": " ", "__ASIDE_NAVIGATION_MODULE_DIGITAL_LITERACY_ACCORDION_SUBTITLE": "Participant technical proficiency", "__ASIDE_NAVIGATION_MODULE_GENDER_ACCORDION_SUBTITLE": "Participant gender criteria", "__ASIDE_NAVIGATION_MODULE_GOAL_SUBTITLE": "Activity objective", @@ -34,7 +33,6 @@ "__ASIDE_NAVIGATION_MODULE_LOCALITY_SUBTITLE": "Participant location criteria", "__ASIDE_NAVIGATION_MODULE_OUT_OF_SCOPE_SUBTITLE": " ", "__ASIDE_NAVIGATION_MODULE_SETUP_NOTE_BLOCK_SUBTITLE": " ", - "__ASIDE_NAVIGATION_MODULE_SUBTITLE_BLOCK_SUBTITLE": " ", "__ASIDE_NAVIGATION_MODULE_TARGET_NOTE_BLOCK_SUBTITLE": " ", "__ASIDE_NAVIGATION_MODULE_TARGET_SUBTITLE": "Participant number", "__ASIDE_NAVIGATION_MODULE_TASKS_SUBTITLE": " ", @@ -342,6 +340,7 @@ "__CAMPAIGN_PAGE_DEVICE_AND_BUG_TYPES_SECTION_SUBTITLE": "Check all the isolated bugs organized for devices and OS plus all the bug typologies", "__CAMPAIGN_PAGE_DEVICE_AND_BUG_TYPES_SECTION_TITLE": "Devices and bug types", "__CAMPAIGN_PAGE_DOTS_MENU_ARCHIVE_CAMPAIGN_BUTTON": "Archive", + "__CAMPAIGN_PAGE_DOTS_MENU_GO_TO_PLAN_BUTTON": "Go to Activity Setup", "__CAMPAIGN_PAGE_DOTS_MENU_MOVE_CAMPAIGN_BUTTON": "Move to", "__CAMPAIGN_PAGE_EXP_OVERVIEW_SECTION_SUBTITLE": "The overall user experience with the product", "__CAMPAIGN_PAGE_EXP_OVERVIEW_SECTION_TITLE": "Overview", @@ -713,6 +712,14 @@ "__PAGE_NOT_ACCESIBLE_BUTTON_LOGOUT": "Logout", "__PAGE_NOT_ACCESIBLE_DESCRIPTION": "It seems that your access is no longer active. You can log out or contact our support center. We are here to help you!", "__PAGE_NOT_ACCESIBLE_TITLE": "You cannot access this page at the moment.", + "__PAGE_PROFILE_CONFIRM_PASSWORD_LABEL": "Confirm new password", + "__PAGE_PROFILE_CONFIRM_PASSWORD_PLACEHOLDER": "Confirm your new password", + "__PAGE_PROFILE_CURRENT_PASSWORD_LABEL": "Current password", + "__PAGE_PROFILE_CURRENT_PASSWORD_PLACEHOLDER": "Enter your current password", + "__PAGE_PROFILE_FORGOT_PASSWORD": "Forgot password?", + "__PAGE_PROFILE_NEW_PASSWORD_LABEL": "New password", + "__PAGE_PROFILE_NEW_PASSWORD_PLACEHOLDER": "Enter your new password", + "__PAGE_PROFILE_SAVE_CHANGES_BUTTON": "Save changes", "__PAGE_TITLE_BUGS_COLLECTION": "Bug collection", "__PAGE_TITLE_JOIN": "join page", "__PAGE_TITLE_LOGIN": "Log in", @@ -745,6 +752,7 @@ "__PLAN_DATE_ERROR_REQUIRED": "Required field: enter a date to continue", "__PLAN_DATE_IN_FUTURE_ERROR": "Date must be at least one business day in the future", "__PLAN_DELETE_PLAN_CTA": "Delete draft", + "__PLAN_DELETE_PLAN_TOOLTIP": "You can’t delete this activity because it’s no longer in draft", "__PLAN_EMPLOYMENT_SIZE_ERROR_REQUIRED": "Please select at least one category to continue", "__PLAN_GOAL_SIZE_ERROR_REQUIRED": "Required field: enter a goal to continue", "__PLAN_GOAL_SIZE_ERROR_TOO_LONG": "Character limit exceeded: Please reduce your text to 256 characters", @@ -808,7 +816,6 @@ "__PLAN_PAGE_MODULE_BROWSER_LABEL": "Browser selection", "__PLAN_PAGE_MODULE_BROWSER_REMOVE_BUTTON": "Delete", "__PLAN_PAGE_MODULE_BROWSER_TITLE": "Choose the browser you want to include in the activity", - "__PLAN_PAGE_MODULE_DATES_BLOCK_TITLE": "Dates item", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ACCORDION_LABEL": "Digital skills", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ALL_LABEL": "All levels", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ALL_LABEL_HINT": "Participants equally divided by literacy level", @@ -984,7 +991,6 @@ "__PLAN_PAGE_MODULE_TASKS_TASK_TITLE_PLACEHOLDER_EMPTY": "Task title", "__PLAN_PAGE_MODULE_TASKS_TASK_URL_LINK_ERROR_INVALID_URL": "Task link should be a valid URL", "__PLAN_PAGE_MODULE_TASKS_TITLE": "Tasks", - "__PLAN_PAGE_MODULE_TITLE_BLOCK_TITLE": "Title item", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_APP_TAB": "App based", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_BUTTON": "Add touchpoint", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_MODAL_DEFAULT_TAB": "All", @@ -1044,6 +1050,18 @@ "__PLAN_PAGE_MODULE_TOUCHPOINTS_TOUCHPOINT_WEB_LINK_LABEL": "Test link", "__PLAN_PAGE_MODULE_TOUCHPOINTS_TOUCHPOINT_WEB_LINK_PLACEHOLDER": "https://example.com", "__PLAN_PAGE_NAV_GENERIC_MODULE_ERROR": "This item has some errors", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CANCEL": "Cancel", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CONFIRM": "Save template", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CONTINUE_SETUP": "Continue setup", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_VIEW_TEMPLATES": "View templates", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_DESCRIPTION_MAX": "This description is a bit long. We advise you to stay within 512 characters including spaces.", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_HEADER": "Did you find the perfect match?This template will help you launch similar activities more quickly.", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_NAME_MAX": "This name is a bit long. We advise you to stay within 64 characters including spaces.", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_NAME_REQUIRED": "Choose a name before saving the template", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_TITLE": "Save as template", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_QUOTE_DISCLAIMER": "The final price may vary based on your activities settings", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_QUOTE_TITLE": "STARTING PRICE", + "__PLAN_PAGE_SAVE_PLAN_MODAL_ERROR": "The plan could not be saved: please try again later.", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_APPROVED_DESCRIPTION": "The quotation are currently being processed. Some details may vary or need confirmation", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_AWAITING_DESCRIPTION": "Your request has been processed. Please review and confirm these details.", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_DATE_LABEL": "Start date", @@ -1063,11 +1081,17 @@ "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_DESCRIPTION": "Your quotation is confirmed.<1><0>Approve now to <3>secure your date and begin collecting valuable insights:<2><0>Start on the scheduled date<1>Collect valuable user feedback for your digital product<2>Receive notifications when first results become availableYou can also <4>return to draft status if you need to make changes.", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_GO_TO_CAMPAIGN_CTA": "Go to dashboard", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_REFUSE_CTA": "Return to draft", + "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_SAVE_TEMPLATE_CTA": "Save as template", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_TITLE": "Ready to confirm and launch your activity?", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_WARNING_DESCRIPTION": "Returning to draft will require a new quotation process and may affect your start date and pricing.", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_WARNING_TITLE": "Important:", "__PLAN_PAGE_SUMMARY_TAB_GO_TO_DASHBOARD_CARD_DESCRIPTION": "<0>We'll notify you by email when the first results are available. In the meantime, you can:<1><0>Set up email notifications for key milestones;<1>Invite team members who should have access to results", "__PLAN_PAGE_SUMMARY_TAB_GO_TO_DASHBOARD_CARD_TITLE": "What can you do now?", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_CARD_DESCRIPTION": "Save this configuration as a template and launch future campaigns in no time.", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_CARD_TITLE": "Did you find the perfect match?", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_MORE_one": "...and {{count}} more", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_MORE_other": "...and {{count}} more", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_TITLE": "Latest templates saved from this activity:", "__PLAN_PAGE_TAB_INSTRUCTIONS_TAB_TITLE": "Assign task", "__PLAN_PAGE_TAB_SETUP_TAB_TITLE": "Set up", "__PLAN_PAGE_TAB_SUMMARY_TAB_TITLE": "Get expert feedback", @@ -1077,6 +1101,7 @@ "__PLAN_SAVE_CONFIGURATION_CTA": "Save Draft", "__PLAN_SAVE_DRAFT_TOAST_ERROR": "We couldn't save your draft: please try again later.", "__PLAN_SAVE_DRAFT_TOAST_SUCCESS": "Activity draft saved! You can safely continue editing.", + "__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_TARGET_SIZE_ERROR_REQUIRED": "Please enter at least one user to include in the activity", @@ -1089,6 +1114,7 @@ "__PROFILE_MODAL_CURRENT_LANGUAGE_LABEL": "Now:", "__PROFILE_MODAL_FEEDBACK_SUBTITLE": "Help us improve UNGUESS!", "__PROFILE_MODAL_FEEDBACK_TITLE": "Give us your feedback", + "__PROFILE_MODAL_GO_TO_PROFILE": "Edit profile", "__PROFILE_MODAL_LANGUAGES_TITLE": "Change Language", "__PROFILE_MODAL_LOGOUT_TITLE": "Log Out", "__PROFILE_MODAL_NOTIFICATIONS_INTRO": "Manage the notifications we send you by email.", @@ -1101,6 +1127,27 @@ "__PROFILE_MODAL_NOTIFICATIONS_TOGGLE_TITLE": "Allow notifications", "__PROFILE_MODAL_NOTIFICATIONS_UPDATED": "Changes saved", "__PROFILE_MODAL_PRIVACY_ITEM_LABEL": "Privacy settings", + "__PROFILE_PAGE_CONFIRM_PASSWORD_MUST_MATCH_NEW_PASSWORD": "The confirmation password must match the new password", + "__PROFILE_PAGE_NAME_REQUIRED_ERROR": "Name is required", + "__PROFILE_PAGE_NAV_ITEM_PASSWORD": "Password settings", + "__PROFILE_PAGE_NAV_ITEM_PROFILE": "Profile settings", + "__PROFILE_PAGE_NAV_SECTION_PASSWORD": "PASSWORD", + "__PROFILE_PAGE_NEW_PASSWORD_REQUIRED_ERROR": "New password is required", + "__PROFILE_PAGE_PASSWORD_ACCORDION_LABEL": "Password settings", + "__PROFILE_PAGE_ROLE_REQUIRED_ERROR": "Role is required", + "__PROFILE_PAGE_SURNAME_REQUIRED_ERROR": "Surname is required", + "__PROFILE_PAGE_TITLE": "My Profile", + "__PROFILE_PAGE_TOAST_ERROR_INVALID_CURRENT_PASSWORD": "Invalid current password. Please try again.", + "__PROFILE_PAGE_TOAST_ERROR_UPDATING_PASSWORD": "An error occurred while updating the password. Please try again.", + "__PROFILE_PAGE_TOAST_ERROR_UPDATING_PROFILE": "An error occurred while updating your profile. Please try again.", + "__PROFILE_PAGE_TOAST_SUCCESS_PASSWORD_UPDATED": "Password updated successfully", + "__PROFILE_PAGE_UPDATE_SUCCESS": "Profile updated successfully", + "__PROFILE_PAGE_USER_CARD_EMAIL_LABEL": "Work email", + "__PROFILE_PAGE_USER_CARD_LABEL": "Profile settings", + "__PROFILE_PAGE_USER_CARD_NAME_LABEL": "First name", + "__PROFILE_PAGE_USER_CARD_ROLE_LABEL": "Job role", + "__PROFILE_PAGE_USER_CARD_ROLE_PLACEHOLDER": "Select a Job role", + "__PROFILE_PAGE_USER_CARD_SURNAME_LABEL": "Last name", "__PROJECT_FORM_DESCRIPTION_MAX": "This description is a bit long. It is recommended not to exceed 234 characters, including spaces.", "__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", @@ -1255,6 +1302,7 @@ "_CAMPAIGN_WIDGET_UX_TEST_PROGRESS_MONITORING_HEADER": "Test Progress", "_CAMPAIGN_WIDGET_UX_USER_ANALYSIS_DESCRIPTION_HEADER": "There are observations on", "_CAMPAIGN_WIDGET_UX_USER_ANALYSIS_HEADER": "Analyzed User Contributions", + "_PAGE_PROFILE_HEADER_TEXT": "Keep your profile up to date: edit your name, role, or password anytime to make UNGUESS truly yours.", "_PLAN_PAGE_MODULE_LANGUAGE_DESCRIPTION": "You’ll receive feedback in the language you’re selecting", "_PLAN_PAGE_MODULE_LANGUAGE_SUBTITLE": "Select participants' preferred language", "_PROJECT_PAGE_PLANS_GROUP_SEE_ALL": "View more", @@ -1291,6 +1339,13 @@ "PLAN_GLOBAL_ALERT_AWATING_STATE_TITLE": "Your quotation is ready!", "PLAN_GLOBAL_ALERT_SUBMITTED_STATE_MESSAGE": "You'll receive a notification once your quotation is available.", "PLAN_GLOBAL_ALERT_SUBMITTED_STATE_TITLE": "Activity submitted for review", + "SAVE_AS_TEMPLATE_FORM_DESCRIPTION": "Template description (optional)", + "SAVE_AS_TEMPLATE_FORM_DESCRIPTION_PLACEHOLDER": "Write the template description", + "SAVE_AS_TEMPLATE_FORM_TITLE": "Template name", + "SAVE_AS_TEMPLATE_FORM_TITLE_PLACEHOLDER": "Write the template name", + "SAVE_AS_TEMPLATE_SUCCESS_TEXT_1": "You’ve saved a template from this setup.", + "SAVE_AS_TEMPLATE_SUCCESS_TEXT_2": "It’s now available on your Templates page and can be reused anytime to quickly launch similar activities.", + "SAVE_AS_TEMPLATE_SUCCESS_TITLE": "Template saved successfully!", "Severity ({{count}})_one": "Severity ({{count}})", "Severity ({{count}})_other": "Severities ({{count}})", "SIGNUP_FORM_CTA_RETURN_TO_UNGUESS_LANDING": "Go to the UNGUESS website", @@ -1318,6 +1373,7 @@ "SIGNUP_FORM_RETURN_TO_STEP_2": "Back", "SIGNUP_FORM_ROLE_IS_REQUIRED": "This field is required", "SIGNUP_FORM_ROLE_LABEL": "Job title", + "SIGNUP_FORM_ROLE_PLACEHOLDER": "Select a Job role", "SIGNUP_FORM_STEP_1_TITLE": "Welcome!
Get started with your account!", "SIGNUP_FORM_STEP_2_DESCRIPTION": "So we can tailor the experience to fit your role and needs.", "SIGNUP_FORM_STEP_2_TITLE": "Tell us a bit about yourself", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index e21a5a7e7..e4a9da1c7 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -25,7 +25,6 @@ "__ASIDE_NAVIGATION_MODULE_ADDITIONAL_TARGET_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_AGE_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_BROWSER_SUBTITLE": "", - "__ASIDE_NAVIGATION_MODULE_DATES_BLOCK_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_DIGITAL_LITERACY_ACCORDION_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_GENDER_ACCORDION_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_GOAL_SUBTITLE": "", @@ -34,7 +33,6 @@ "__ASIDE_NAVIGATION_MODULE_LOCALITY_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_OUT_OF_SCOPE_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_SETUP_NOTE_BLOCK_SUBTITLE": "", - "__ASIDE_NAVIGATION_MODULE_SUBTITLE_BLOCK_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_TARGET_NOTE_BLOCK_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_TARGET_SUBTITLE": "", "__ASIDE_NAVIGATION_MODULE_TASKS_SUBTITLE": "", @@ -361,6 +359,7 @@ "__CAMPAIGN_PAGE_DEVICE_AND_BUG_TYPES_SECTION_SUBTITLE": "Visualizza i bug divisi per dispositivi e sistemi operativi e le tipologie di tutti i bug segnalati", "__CAMPAIGN_PAGE_DEVICE_AND_BUG_TYPES_SECTION_TITLE": "Dispositivi e tipologie bug", "__CAMPAIGN_PAGE_DOTS_MENU_ARCHIVE_CAMPAIGN_BUTTON": "Archivia", + "__CAMPAIGN_PAGE_DOTS_MENU_GO_TO_PLAN_BUTTON": "", "__CAMPAIGN_PAGE_DOTS_MENU_MOVE_CAMPAIGN_BUTTON": "", "__CAMPAIGN_PAGE_EXP_OVERVIEW_SECTION_SUBTITLE": "Com’è stata l’esperienza complessiva degli utenti con il prodotto", "__CAMPAIGN_PAGE_EXP_OVERVIEW_SECTION_TITLE": "Panoramica", @@ -743,6 +742,14 @@ "__PAGE_NOT_ACCESIBLE_BUTTON_LOGOUT": "Logout", "__PAGE_NOT_ACCESIBLE_DESCRIPTION": "Sembra che tu non abbia più accesso al workspace. Se hai bisogno di aiuto, contatta il nostro centro assistenza. ", "__PAGE_NOT_ACCESIBLE_TITLE": "Al momento non è possibile accedere a questa pagina.", + "__PAGE_PROFILE_CONFIRM_PASSWORD_LABEL": "", + "__PAGE_PROFILE_CONFIRM_PASSWORD_PLACEHOLDER": "", + "__PAGE_PROFILE_CURRENT_PASSWORD_LABEL": "", + "__PAGE_PROFILE_CURRENT_PASSWORD_PLACEHOLDER": "", + "__PAGE_PROFILE_FORGOT_PASSWORD": "", + "__PAGE_PROFILE_NEW_PASSWORD_LABEL": "", + "__PAGE_PROFILE_NEW_PASSWORD_PLACEHOLDER": "", + "__PAGE_PROFILE_SAVE_CHANGES_BUTTON": "", "__PAGE_TITLE_BUGS_COLLECTION": "Raccolta bug", "__PAGE_TITLE_JOIN": "", "__PAGE_TITLE_LOGIN": "Accedi", @@ -775,6 +782,7 @@ "__PLAN_DATE_ERROR_REQUIRED": "", "__PLAN_DATE_IN_FUTURE_ERROR": "", "__PLAN_DELETE_PLAN_CTA": "", + "__PLAN_DELETE_PLAN_TOOLTIP": "", "__PLAN_EMPLOYMENT_SIZE_ERROR_REQUIRED": "", "__PLAN_GOAL_SIZE_ERROR_REQUIRED": "", "__PLAN_GOAL_SIZE_ERROR_TOO_LONG": "", @@ -838,7 +846,6 @@ "__PLAN_PAGE_MODULE_BROWSER_LABEL": "", "__PLAN_PAGE_MODULE_BROWSER_REMOVE_BUTTON": "", "__PLAN_PAGE_MODULE_BROWSER_TITLE": "", - "__PLAN_PAGE_MODULE_DATES_BLOCK_TITLE": "", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ACCORDION_LABEL": "", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ALL_LABEL": "", "__PLAN_PAGE_MODULE_DIGITAL_LITERACY_ALL_LABEL_HINT": "", @@ -1014,7 +1021,6 @@ "__PLAN_PAGE_MODULE_TASKS_TASK_TITLE_PLACEHOLDER_EMPTY": "", "__PLAN_PAGE_MODULE_TASKS_TASK_URL_LINK_ERROR_INVALID_URL": "", "__PLAN_PAGE_MODULE_TASKS_TITLE": "", - "__PLAN_PAGE_MODULE_TITLE_BLOCK_TITLE": "", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_APP_TAB": "", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_BUTTON": "", "__PLAN_PAGE_MODULE_TOUCHPOINTS_ADD_TOUCHPOINT_MODAL_DEFAULT_TAB": "", @@ -1074,6 +1080,18 @@ "__PLAN_PAGE_MODULE_TOUCHPOINTS_TOUCHPOINT_WEB_LINK_LABEL": "", "__PLAN_PAGE_MODULE_TOUCHPOINTS_TOUCHPOINT_WEB_LINK_PLACEHOLDER": "", "__PLAN_PAGE_NAV_GENERIC_MODULE_ERROR": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CANCEL": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CONFIRM": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_CONTINUE_SETUP": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_BUTTON_VIEW_TEMPLATES": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_DESCRIPTION_MAX": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_HEADER": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_NAME_MAX": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_NAME_REQUIRED": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_MODAL_TITLE": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_QUOTE_DISCLAIMER": "", + "__PLAN_PAGE_SAVE_AS_TEMPLATE_QUOTE_TITLE": "", + "__PLAN_PAGE_SAVE_PLAN_MODAL_ERROR": "", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_APPROVED_DESCRIPTION": "", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_AWAITING_DESCRIPTION": "", "__PLAN_PAGE_SUMMARY_TAB_ACTIVITY_INFO_DATE_LABEL": "", @@ -1093,11 +1111,18 @@ "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_DESCRIPTION": "", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_GO_TO_CAMPAIGN_CTA": "", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_REFUSE_CTA": "", + "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_SAVE_TEMPLATE_CTA": "", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_TITLE": "", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_WARNING_DESCRIPTION": "", "__PLAN_PAGE_SUMMARY_TAB_CONFIRMATION_CARD_WARNING_TITLE": "", "__PLAN_PAGE_SUMMARY_TAB_GO_TO_DASHBOARD_CARD_DESCRIPTION": "<0>We'll notify you by email when the first results are available. In the meantime, you can:<1><0>Set up email notifications for key milestones;<1>Invite team members who should have access to results", "__PLAN_PAGE_SUMMARY_TAB_GO_TO_DASHBOARD_CARD_TITLE": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_CARD_DESCRIPTION": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_CARD_TITLE": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_MORE_one": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_MORE_many": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_MORE_other": "", + "__PLAN_PAGE_SUMMARY_TAB_SAVE_TEMPLATE_TEMPLATE_LIST_TITLE": "", "__PLAN_PAGE_TAB_INSTRUCTIONS_TAB_TITLE": "", "__PLAN_PAGE_TAB_SETUP_TAB_TITLE": "", "__PLAN_PAGE_TAB_SUMMARY_TAB_TITLE": "", @@ -1107,6 +1132,7 @@ "__PLAN_SAVE_CONFIGURATION_CTA": "", "__PLAN_SAVE_DRAFT_TOAST_ERROR": "", "__PLAN_SAVE_DRAFT_TOAST_SUCCESS": "", + "__PLAN_SAVE_TEMPLATE_CTA": "", "__PLAN_SETUP_NOTE_SIZE_ERROR_EMPTY": "", "__PLAN_TARGET_NOTE_SIZE_ERROR_EMPTY": "", "__PLAN_TARGET_SIZE_ERROR_REQUIRED": "", @@ -1119,6 +1145,7 @@ "__PROFILE_MODAL_CURRENT_LANGUAGE_LABEL": "Adesso:", "__PROFILE_MODAL_FEEDBACK_SUBTITLE": "Aiutaci a migliorare UNGUESS!", "__PROFILE_MODAL_FEEDBACK_TITLE": "Dacci il tuo feedback", + "__PROFILE_MODAL_GO_TO_PROFILE": "", "__PROFILE_MODAL_LANGUAGES_TITLE": "Cambia Lingua", "__PROFILE_MODAL_LOGOUT_TITLE": "Esci", "__PROFILE_MODAL_NOTIFICATIONS_INTRO": "Gestisci le notifiche che ti mandiamo per email.", @@ -1131,6 +1158,27 @@ "__PROFILE_MODAL_NOTIFICATIONS_TOGGLE_TITLE": "Vuoi ricevere le notifiche?", "__PROFILE_MODAL_NOTIFICATIONS_UPDATED": "Modifiche salvate", "__PROFILE_MODAL_PRIVACY_ITEM_LABEL": "Opzioni Privacy", + "__PROFILE_PAGE_CONFIRM_PASSWORD_MUST_MATCH_NEW_PASSWORD": "", + "__PROFILE_PAGE_NAME_REQUIRED_ERROR": "", + "__PROFILE_PAGE_NAV_ITEM_PASSWORD": "", + "__PROFILE_PAGE_NAV_ITEM_PROFILE": "", + "__PROFILE_PAGE_NAV_SECTION_PASSWORD": "", + "__PROFILE_PAGE_NEW_PASSWORD_REQUIRED_ERROR": "", + "__PROFILE_PAGE_PASSWORD_ACCORDION_LABEL": "", + "__PROFILE_PAGE_ROLE_REQUIRED_ERROR": "", + "__PROFILE_PAGE_SURNAME_REQUIRED_ERROR": "", + "__PROFILE_PAGE_TITLE": "Profilo", + "__PROFILE_PAGE_TOAST_ERROR_INVALID_CURRENT_PASSWORD": "", + "__PROFILE_PAGE_TOAST_ERROR_UPDATING_PASSWORD": "", + "__PROFILE_PAGE_TOAST_ERROR_UPDATING_PROFILE": "", + "__PROFILE_PAGE_TOAST_SUCCESS_PASSWORD_UPDATED": "", + "__PROFILE_PAGE_UPDATE_SUCCESS": "", + "__PROFILE_PAGE_USER_CARD_EMAIL_LABEL": "", + "__PROFILE_PAGE_USER_CARD_LABEL": "", + "__PROFILE_PAGE_USER_CARD_NAME_LABEL": "", + "__PROFILE_PAGE_USER_CARD_ROLE_LABEL": "", + "__PROFILE_PAGE_USER_CARD_ROLE_PLACEHOLDER": "", + "__PROFILE_PAGE_USER_CARD_SURNAME_LABEL": "", "__PROJECT_FORM_DESCRIPTION_MAX": "", "__PROJECT_FORM_NAME_MAX": "", "__PROJECT_FORM_NAME_REQUIRED": "", @@ -1292,6 +1340,7 @@ "_CAMPAIGN_WIDGET_UX_TEST_PROGRESS_MONITORING_HEADER": "Andamento Test", "_CAMPAIGN_WIDGET_UX_USER_ANALYSIS_DESCRIPTION_HEADER": "Hai aggiunto osservazioni su", "_CAMPAIGN_WIDGET_UX_USER_ANALYSIS_HEADER": "Contributi utente analizzati", + "_PAGE_PROFILE_HEADER_TEXT": "", "_PLAN_PAGE_MODULE_LANGUAGE_DESCRIPTION": "", "_PLAN_PAGE_MODULE_LANGUAGE_SUBTITLE": "", "_PROJECT_PAGE_PLANS_GROUP_SEE_ALL": "Vedi tutti", @@ -1329,6 +1378,13 @@ "PLAN_GLOBAL_ALERT_AWATING_STATE_TITLE": "", "PLAN_GLOBAL_ALERT_SUBMITTED_STATE_MESSAGE": "", "PLAN_GLOBAL_ALERT_SUBMITTED_STATE_TITLE": "", + "SAVE_AS_TEMPLATE_FORM_DESCRIPTION": "", + "SAVE_AS_TEMPLATE_FORM_DESCRIPTION_PLACEHOLDER": "", + "SAVE_AS_TEMPLATE_FORM_TITLE": "", + "SAVE_AS_TEMPLATE_FORM_TITLE_PLACEHOLDER": "", + "SAVE_AS_TEMPLATE_SUCCESS_TEXT_1": "", + "SAVE_AS_TEMPLATE_SUCCESS_TEXT_2": "", + "SAVE_AS_TEMPLATE_SUCCESS_TITLE": "", "Severity ({{count}})_one": "Gravità ({{count}})", "Severity ({{count}})_many": "", "Severity ({{count}})_other": "Gravità ({{count}})", @@ -1357,6 +1413,7 @@ "SIGNUP_FORM_RETURN_TO_STEP_2": "", "SIGNUP_FORM_ROLE_IS_REQUIRED": "", "SIGNUP_FORM_ROLE_LABEL": "", + "SIGNUP_FORM_ROLE_PLACEHOLDER": "", "SIGNUP_FORM_STEP_1_TITLE": "", "SIGNUP_FORM_STEP_2_DESCRIPTION": "", "SIGNUP_FORM_STEP_2_TITLE": "", diff --git a/src/pages/Bug/Chat.tsx b/src/pages/Bug/Chat.tsx index b6a426658..97f4dc7d9 100644 --- a/src/pages/Bug/Chat.tsx +++ b/src/pages/Bug/Chat.tsx @@ -9,12 +9,12 @@ import { } from '@appquality/unguess-design-system'; import { t } from 'i18next'; import { useEffect, useRef, useState } from 'react'; -import { useAppSelector } from 'src/app/hooks'; import defaultBkg from 'src/assets/bg-chat.svg'; import { getInitials } from 'src/common/components/navigation/header/utils'; import { useDeleteCampaignsByCidBugsAndBidCommentsCmidMutation, useGetCampaignsByCidBugsAndBidCommentsQuery, + useGetUsersMeQuery, } from 'src/features/api'; import i18n from 'src/i18n'; import { styled } from 'styled-components'; @@ -59,7 +59,13 @@ export const ChatBox = ({ setIsSubmitting: (state: boolean) => void; }) => { const { triggerSave, editor, clearInput } = useChatContext(); - const { userData: user } = useAppSelector((state) => state.user); + const { + data: user, + isLoading: userDataLoading, + isSuccess, + isError: isUserError, + } = useGetUsersMeQuery(); + const [isModalOpen, setIsModalOpen] = useState(false); const [commentToDelete, setCommentToDelete] = useState(''); const { addToast } = useToast(); @@ -127,6 +133,8 @@ export const ChatBox = ({ }; }, [comments]); + if (!user || isUserError || userDataLoading) return null; + return ( <> @@ -171,16 +179,17 @@ export const ChatBox = ({ <>
{(comment.creator.id === user.profile_id || - user.role === 'administrator') && ( - - )} + user.role === 'administrator') && + isSuccess && ( + + )} ))} diff --git a/src/pages/Bug/hooks/getMentionableUsers.ts b/src/pages/Bug/hooks/getMentionableUsers.ts index ee05d41c6..f6618b811 100644 --- a/src/pages/Bug/hooks/getMentionableUsers.ts +++ b/src/pages/Bug/hooks/getMentionableUsers.ts @@ -4,6 +4,7 @@ import { Tenant, useGetCampaignsByCidUsersQuery, useGetProjectsByPidUsersQuery, + useGetUsersMeQuery, useGetWorkspacesByWidUsersQuery, } from 'src/features/api'; import { useGetCampaignWithWorkspaceQuery } from 'src/features/api/customEndpoints/getCampaignWithWorkspace'; @@ -19,7 +20,7 @@ export const useGetMentionableUsers = () => { const { data: { campaign } = {} } = useGetCampaignWithWorkspaceQuery({ cid: campaignId || '0', }); - const { userData } = useAppSelector((state) => state.user); + const { data: userData } = useGetUsersMeQuery(); const { data: workspaceUsers, @@ -78,7 +79,7 @@ export const useGetMentionableUsers = () => { // Remove user himself const users = filteredUsers.filter( - (u) => u.profile_id !== userData.profile_id + (u) => u.profile_id !== userData?.profile_id ); return { diff --git a/src/pages/Campaign/pageHeader/Meta/index.tsx b/src/pages/Campaign/pageHeader/Meta/index.tsx index 022f6dd9c..aef29272b 100644 --- a/src/pages/Campaign/pageHeader/Meta/index.tsx +++ b/src/pages/Campaign/pageHeader/Meta/index.tsx @@ -1,16 +1,18 @@ import { Button, - DotsMenu, + ButtonMenu, IconButton, MD, Skeleton, Tooltip, } from '@appquality/unguess-design-system'; import { ReactComponent as InsightsIcon } from '@zendeskgarden/svg-icons/src/16/lightbulb-stroke.svg'; +import { ReactComponent as ExternalLinkIcon } from '@zendeskgarden/svg-icons/src/16/new-window-stroke.svg'; +import { ReactComponent as DotsIcon } from '@zendeskgarden/svg-icons/src/16/overflow-vertical-stroke.svg'; import { ReactComponent as VideoListIcon } from '@zendeskgarden/svg-icons/src/16/play-circle-stroke.svg'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { appTheme } from 'src/app/theme'; import { ReactComponent as EditRedoStroke } from 'src/assets/icons/move-icon.svg'; import { ReactComponent as InboxFill } from 'src/assets/icons/project-archive.svg'; @@ -23,6 +25,7 @@ import { FEATURE_FLAG_TAGGING_TOOL } from 'src/constants'; import { CampaignWithOutput, useGetCampaignsByCidMetaQuery, + useGetCampaignsByCidQuery, } from 'src/features/api'; import { useActiveWorkspaceProjects } from 'src/hooks/useActiveWorkspaceProjects'; import { useCanAccessToActiveWorkspace } from 'src/hooks/useCanAccessToActiveWorkspace'; @@ -96,6 +99,7 @@ export const Metas = ({ const videoDashboardRoute = useLocalizeRoute( `campaigns/${campaign.id}/videos` ); + const navigate = useNavigate(); const insightsRoute = useLocalizeRoute(`campaigns/${campaign.id}/insights`); const hasWorkspaceAccess = useCanAccessToActiveWorkspace(); @@ -104,6 +108,10 @@ export const Metas = ({ isLoading, isFetching, } = useGetCampaignsByCidMetaQuery({ cid: campaign.id.toString() }); + const { data: campaignData } = useGetCampaignsByCidQuery({ + cid: campaign.id.toString(), + }); + const plan = campaignData?.plan; const { sorted: videos, isLoading: isLoadingVideos, @@ -206,42 +214,50 @@ export const Metas = ({ !isLoadingProjects && !isFetchingProjects && !isArchived && ( - { + if (value === 'move_campaign') { + setIsMoveModalOpen(true); + } else if (value === 'archive_campaign') { + setIsArchiveModalOpen(true); + } else if (value === 'go_to_plan') { + navigate(`/plans/${plan}`); + } }} + label={(props) => ( + + + + )} > - - - - + + {!!plan && ( + <> + + } + > + {t('__CAMPAIGN_PAGE_DOTS_MENU_GO_TO_PLAN_BUTTON')} + + + )} + )} diff --git a/src/pages/Campaign/pageHeader/useCampaign.tsx b/src/pages/Campaign/pageHeader/useCampaign.tsx index 70282c435..3094e39c8 100644 --- a/src/pages/Campaign/pageHeader/useCampaign.tsx +++ b/src/pages/Campaign/pageHeader/useCampaign.tsx @@ -1,12 +1,13 @@ -import { useAppSelector } from 'src/app/hooks'; import { useGetCampaignsByCidQuery, useGetProjectsByPidQuery, + useGetUsersMeQuery, } from 'src/features/api'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; export const useCampaign = (campaignId: number) => { - const { status: userStatus } = useAppSelector((state) => state.user); + const { isLoading: isUserLoading, isFetching: isUserFetching } = + useGetUsersMeQuery(); const { isLoading: isCampaignLoading, @@ -50,7 +51,7 @@ export const useCampaign = (campaignId: number) => { return { isLoading: false as const, isError: false as const, - isUserLoading: userStatus === 'idle' || userStatus === 'loading', + isUserLoading: isUserLoading || isUserFetching, campaign, project: { ...(campaign && { ...campaign.project, hasAccess: false }), diff --git a/src/pages/Dashboard/CampaignItem.tsx b/src/pages/Dashboard/CampaignItem.tsx index 1e6920e48..5978e7fdf 100644 --- a/src/pages/Dashboard/CampaignItem.tsx +++ b/src/pages/Dashboard/CampaignItem.tsx @@ -25,6 +25,7 @@ export const CampaignItem = ({ return ( { isReadOnly groups={groupesCampaigns} columns={columns} - style={{ backgroundColor: 'white' }} + style={{ + backgroundColor: 'white', + wordBreak: 'break-word', + whiteSpace: 'normal', + }} /> ); }; diff --git a/src/pages/Dashboard/headerContent.tsx b/src/pages/Dashboard/headerContent.tsx index d8fe37fc7..d16bc1697 100644 --- a/src/pages/Dashboard/headerContent.tsx +++ b/src/pages/Dashboard/headerContent.tsx @@ -1,10 +1,10 @@ import { Button, PageHeader } from '@appquality/unguess-design-system'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { useAppSelector } from 'src/app/hooks'; import { appTheme } from 'src/app/theme'; import { LayoutWrapper } from 'src/common/components/LayoutWrapper'; import { PageTitle } from 'src/common/components/PageTitle'; +import { useGetUsersMeQuery } from 'src/features/api'; import { useCanAccessToActiveWorkspace } from 'src/hooks/useCanAccessToActiveWorkspace'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; import { Counters } from './Counters'; @@ -17,12 +17,14 @@ export const DashboardHeaderContent = ({ handleOpenModal: () => void; }) => { const { t } = useTranslation(); - const { status } = useAppSelector((state) => state.user); + const { isLoading: isUserLoading, isFetching: isUserFetching } = + useGetUsersMeQuery(); + const hasWorksPacePermission = useCanAccessToActiveWorkspace(); const navigate = useNavigate(); const templatesRoute = useLocalizeRoute('templates'); - return status === 'idle' || status === 'loading' ? null : ( + return isUserFetching || isUserLoading ? null : ( diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 89f2c655f..03943c440 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -1,8 +1,9 @@ import { Grid } from '@appquality/unguess-design-system'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAppDispatch, useAppSelector } from 'src/app/hooks'; +import { useAppDispatch } from 'src/app/hooks'; import { LayoutWrapper } from 'src/common/components/LayoutWrapper'; +import { useGetUsersMeQuery } from 'src/features/api'; import { resetFilters } from 'src/features/campaignsFilter/campaignsFilterSlice'; import { Page } from 'src/features/templates/Page'; import { useSendGTMevent } from 'src/hooks/useGTMevent'; @@ -16,10 +17,11 @@ import { SuggestedCampaigns } from './SuggestedCampaigns'; const Dashboard = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const status = useAppSelector((state) => state.user.status); + const { data: userData, isLoading, isFetching } = useGetUsersMeQuery(); + const sendGTMEvent = useSendGTMevent(); - if (status === 'logged') dispatch(resetFilters()); // Reset filters + if (!isFetching && !isLoading && userData) dispatch(resetFilters()); // Reset filters const [openCreateProjectModal, setOpenCreateProjectModal] = useState(false); diff --git a/src/pages/Dashboard/project-items/table.tsx b/src/pages/Dashboard/project-items/table.tsx index 780d57ac5..e1a35915a 100644 --- a/src/pages/Dashboard/project-items/table.tsx +++ b/src/pages/Dashboard/project-items/table.tsx @@ -34,7 +34,11 @@ export const TableList = ({ return ( diff --git a/src/pages/Dashboard/projectPageHeader.tsx b/src/pages/Dashboard/projectPageHeader.tsx index 2c9ae041e..76256c8ce 100644 --- a/src/pages/Dashboard/projectPageHeader.tsx +++ b/src/pages/Dashboard/projectPageHeader.tsx @@ -10,11 +10,10 @@ import { ReactComponent as DeleteIcon } from '@zendeskgarden/svg-icons/src/16/tr import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useAppSelector } from 'src/app/hooks'; import { appTheme } from 'src/app/theme'; import { ProjectSettings } from 'src/common/components/inviteUsers/projectSettings'; import { LayoutWrapper } from 'src/common/components/LayoutWrapper'; -import { useGetProjectsByPidQuery } from 'src/features/api'; +import { useGetProjectsByPidQuery, useGetUsersMeQuery } from 'src/features/api'; import { useCanAccessToActiveWorkspace } from 'src/hooks/useCanAccessToActiveWorkspace'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; import styled from 'styled-components'; @@ -51,7 +50,9 @@ export const ProjectPageHeader = ({ projectId }: { projectId: number }) => { const navigate = useNavigate(); const notFoundRoute = useLocalizeRoute('oops'); const location = useLocation(); - const { status } = useAppSelector((state) => state.user); + const { isLoading: isUserLoading, isFetching: isUserFetching } = + useGetUsersMeQuery(); + const templatesRoute = useLocalizeRoute('templates'); const [deleteModalOpen, setDeleteModalOpen] = useState(false); @@ -97,7 +98,8 @@ export const ProjectPageHeader = ({ projectId }: { projectId: number }) => { {isLoading || isLoadingPlans || isFetching || - status === 'loading' ? ( + isUserLoading || + isUserFetching ? ( ) : ( titleContent @@ -107,7 +109,8 @@ export const ProjectPageHeader = ({ projectId }: { projectId: number }) => { {isLoading || isLoadingPlans || isFetching || - status === 'loading' ? ( + isUserLoading || + isUserFetching ? ( ) : ( descriptionContent diff --git a/src/pages/Insights/index.tsx b/src/pages/Insights/index.tsx index 8e33c9424..b65240f86 100644 --- a/src/pages/Insights/index.tsx +++ b/src/pages/Insights/index.tsx @@ -3,7 +3,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useGetCampaignWithWorkspaceQuery } from 'src/features/api/customEndpoints/getCampaignWithWorkspace'; import { Page } from 'src/features/templates/Page'; import { useLocalizeRoute } from 'src/hooks/useLocalizedRoute'; -import { useAppDispatch, useAppSelector } from 'src/app/hooks'; +import { useAppDispatch } from 'src/app/hooks'; import { useCampaignAnalytics } from 'src/hooks/useCampaignAnalytics'; import { useEffect } from 'react'; import { @@ -12,6 +12,7 @@ import { setWorkspace, } from 'src/features/navigation/navigationSlice'; import { FEATURE_FLAG_TAGGING_TOOL } from 'src/constants'; +import { useGetUsersMeQuery } from 'src/features/api'; import { useFeatureFlag } from 'src/hooks/useFeatureFlag'; import InsightsPageContent from './Content'; import InsightsPageHeader from './PageHeader'; @@ -24,7 +25,12 @@ const InsightsPage = () => { const { campaignId } = useParams(); const dispatch = useAppDispatch(); const location = useLocation(); - const { status } = useAppSelector((state) => state.user); + const { + isLoading: isUserLoading, + isFetching: isUserFetching, + data: userData, + } = useGetUsersMeQuery(); + const { hasFeatureFlag } = useFeatureFlag(); const hasTaggingToolFeature = hasFeatureFlag(FEATURE_FLAG_TAGGING_TOOL); @@ -67,14 +73,14 @@ const InsightsPage = () => { } useEffect(() => { - if (status === 'idle' || status === 'loading') return; + if (isUserFetching || isUserLoading) return; - if (!hasTaggingToolFeature && status === 'logged') { + if (!hasTaggingToolFeature && userData) { navigate(notFoundRoute, { state: { from: location.pathname }, }); } - }, [status, hasTaggingToolFeature]); + }, [isUserFetching, isUserLoading, userData, hasTaggingToolFeature]); return ( diff --git a/src/pages/JoinPage/Steps/Step1.tsx b/src/pages/JoinPage/Steps/Step1.tsx index 50b43ec90..a1aea1a53 100644 --- a/src/pages/JoinPage/Steps/Step1.tsx +++ b/src/pages/JoinPage/Steps/Step1.tsx @@ -16,10 +16,10 @@ import { Field, FieldProps, useFormikContext } from 'formik'; import { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { appTheme } from 'src/app/theme'; +import { PasswordRequirements } from 'src/common/components/PasswordRequirements'; import { useSendGTMevent } from 'src/hooks/useGTMevent'; import { JoinFormValues } from '../valuesType'; import { ButtonContainer } from './ButtonContainer'; -import { PasswordRequirements } from './PasswordRequirements'; export const Step1 = () => { const { setFieldValue, validateForm, setTouched, status, values } = @@ -138,13 +138,13 @@ export const Step1 = () => { title="Password" end={ inputType === 'password' ? ( - ) : ( - { ); }} - +