diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f5cf2b2b27149237b6d777af535f4a000cb4f62..95bbab8c22a244657af378fc042d640d4dd8fd3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -117,6 +117,7 @@ "profiletype", "PROFILETYPE", "pseudonymisées", + "reduxjs", "Reinit", "SHARAPOWATT", "splashscreen", @@ -125,8 +126,8 @@ "Téléo", "testid", "Tétris", - "TIMESTEP", "timestep", + "TIMESTEP", "UNSTARTED", "usageevent", "Usain", diff --git a/package.json b/package.json index 9050cef98323fe8d93f402f055be73a7ea965497..273e7bd393fad2c8c561b84654d1b77aa811e388 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@cozy/minilog": "^1.0.0", "@material-ui/core": "~4.12.0", "@material-ui/styles": "^4.11.3", + "@reduxjs/toolkit": "^1.9.3", "@sentry/react": "^7.21.1", "@sentry/tracing": "^7.21.1", "@simbathesailor/use-what-changed": "^2.0.0", diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index cfef2245f554a0da3adbb4faffc3a5a6314c4244..10cf6a7cd8270383fffdcd384df4cb9bfc41ee03 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -4,7 +4,7 @@ import React, { Dispatch, useCallback, useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { AppActionsTypes, AppStore } from 'store' import { changeScreenType } from 'store/global/global.actions' -import { openFeedbackModal } from 'store/modal/modal.actions' +import { openFeedbackModal } from 'store/modal/modal.slice' import './content.scss' interface ContentProps { children?: React.ReactNode diff --git a/src/components/Header/CozyBar.spec.tsx b/src/components/Header/CozyBar.spec.tsx index da3522ebfbeaa608af401197e4d4144179ed7e0d..7349825fd278a291da9d10deb0bcea752467dfb2 100644 --- a/src/components/Header/CozyBar.spec.tsx +++ b/src/components/Header/CozyBar.spec.tsx @@ -4,7 +4,7 @@ import { mount } from 'enzyme' import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import * as ModalAction from 'store/modal/modal.actions' +import * as ModalAction from 'store/modal/modal.slice' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' import { modalStateData } from '../../../tests/__mocks__/modalStateData.mock' diff --git a/src/components/Header/CozyBar.tsx b/src/components/Header/CozyBar.tsx index 891266275d9d3111ed92d26c3c938e9f353e5783..a05b4b974e3c412a827b5fb1d0867cf05d3be538 100644 --- a/src/components/Header/CozyBar.tsx +++ b/src/components/Header/CozyBar.tsx @@ -7,7 +7,7 @@ import React, { Dispatch, useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' import { AppActionsTypes, AppStore } from 'store' -import { openFeedbackModal } from 'store/modal/modal.actions' +import { openFeedbackModal } from 'store/modal/modal.slice' declare const cozy: { bar: { diff --git a/src/components/Header/Header.spec.tsx b/src/components/Header/Header.spec.tsx index 68c619a57a15e2f928c36a9d6ef08296172c96b7..d7f0f27c3d10b2c58c263a479ec0dd7f963ac682 100644 --- a/src/components/Header/Header.spec.tsx +++ b/src/components/Header/Header.spec.tsx @@ -5,7 +5,7 @@ import { mount } from 'enzyme' import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import * as ModalAction from 'store/modal/modal.actions' +import * as ModalAction from 'store/modal/modal.slice' import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' jest.mock('cozy-ui/transpiled/react/I18n', () => { diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 81cab9dfae017377a5e07033bd492474a4cd557f..d2e574b89b3f42c68a538f9d406921bd757ed06a 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -8,7 +8,7 @@ import React, { Dispatch, useCallback, useEffect, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' import { AppActionsTypes, AppStore } from 'store' -import { openFeedbackModal } from 'store/modal/modal.actions' +import { openFeedbackModal } from 'store/modal/modal.slice' import './header.scss' interface HeaderProps { diff --git a/src/components/Home/ConsumptionView.tsx b/src/components/Home/ConsumptionView.tsx index 5d01492efad055387d8ae96f5e7b2bb0bf8d7c04..d5fe2a2b6bc98296719abc8213ed62a16104e0fe 100644 --- a/src/components/Home/ConsumptionView.tsx +++ b/src/components/Home/ConsumptionView.tsx @@ -22,14 +22,14 @@ import ProfileService from 'services/profile.service' import { AppActionsTypes, AppStore } from 'store' import { setCurrentTimeStep, setLoading } from 'store/chart/chart.actions' import { setCustomPopup, showReleaseNotes } from 'store/global/global.actions' -import { openPartnersModal } from 'store/modal/modal.actions' +import { openPartnersModal } from 'store/modal/modal.slice' import { getKonnectorUpdateError, getTodayDate, isKonnectorActive, } from 'utils/utils' -import './consumptionView.scss' import ReleaseNotesModal from './ReleaseNotesModal' +import './consumptionView.scss' interface ConsumptionViewProps { fluidType: FluidType diff --git a/src/components/Options/HelpLink/HelpLink.tsx b/src/components/Options/HelpLink/HelpLink.tsx index ee6ac458eec091d2c476caf410cfa221f7bf22e3..e42ea407a4b7a131f4ccfb2525a43e8a15ca8560 100644 --- a/src/components/Options/HelpLink/HelpLink.tsx +++ b/src/components/Options/HelpLink/HelpLink.tsx @@ -5,7 +5,7 @@ import { useI18n } from 'cozy-ui/transpiled/react/I18n' import React, { Dispatch } from 'react' import { useDispatch } from 'react-redux' import { AppActionsTypes } from 'store' -import { openFeedbackModal } from 'store/modal/modal.actions' +import { openFeedbackModal } from 'store/modal/modal.slice' import './HelpLink.scss' const HelpLink: React.FC = () => { diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 69e08c6382fcc8cd2e8c7543fd2dfcdbaa29da13..b3d25a58309a61f3897c1c1f719f75e4094dc352 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -53,15 +53,15 @@ import { toggleChallengeExplorationNotification, updateTermValidation, } from 'store/global/global.actions' -import { openPartnersModal } from 'store/modal/modal.actions' +import { openPartnersModal } from 'store/modal/modal.slice' import { updateProfile } from 'store/profile/profile.actions' import { updateProfileEcogestureSuccess } from 'store/profileEcogesture/profileEcogesture.actions' import { updateProfileType } from 'store/profileType/profileType.actions' import logApp from 'utils/logger' import { getTodayDate } from 'utils/utils' -import './splashRoot.scss' import SplashScreen from './SplashScreen' import SplashScreenError from './SplashScreenError' +import './splashRoot.scss' interface SplashRootProps { fadeTimer?: number diff --git a/src/store/index.ts b/src/store/index.ts index 42611fc1fa84555572335a6a4d16893a273c4280..0f372642928a24fd1e6a6f136d015c39bf68cb5a 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -10,22 +10,21 @@ import { import { ChartState } from 'models/chart.model' import { ProfileEcogesture } from 'models/profileEcogesture.model' import { + Store, applyMiddleware, combineReducers, compose, createStore, - Store, } from 'redux' import { composeWithDevTools } from 'redux-devtools-extension' import thunkMiddleware from 'redux-thunk' import { globalReducer } from 'store/global/global.reducer' -import { modalReducer } from 'store/modal/modal.reducer' import { ChallengeActionTypes } from './challenge/challenge.actions' import { challengeReducer } from './challenge/challenge.reducer' import { ChartActionTypes } from './chart/chart.actions' import { chartReducer } from './chart/chart.reducer' import { GlobalActionTypes } from './global/global.actions' -import { ModalActionTypes } from './modal/modal.actions' +import { ModalActionTypes, modalSlice } from './modal/modal.slice' import { ProfileActionTypes } from './profile/profile.actions' import { profileReducer } from './profile/profile.reducer' import { ProfileEcogestureActionTypes } from './profileEcogesture/profileEcogesture.actions' @@ -49,7 +48,7 @@ const ecolyoReducer = combineReducers({ challenge: challengeReducer, chart: chartReducer, global: globalReducer, - modal: modalReducer, + modal: modalSlice.reducer, profile: profileReducer, profileEcogesture: profileEcogestureReducer, profileType: profileTypeReducer, @@ -60,6 +59,7 @@ export interface AppStore { cozy: unknown } +// todo refactor types ? export type AppActionsTypes = | ChallengeActionTypes | ChartActionTypes diff --git a/src/store/modal/modal.action.spec.ts b/src/store/modal/modal.action.spec.ts deleted file mode 100644 index 7a4fec68aed008ecb60f8e6eef099d032f6ac48c..0000000000000000000000000000000000000000 --- a/src/store/modal/modal.action.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { openFeedbackModal, OPEN_FEEDBACK_MODAL } from './modal.actions' - -describe('modal actions', () => { - it('should create an action to update isFeedbacksOpen', () => { - const isOpen = true - const expectedAction = { - type: OPEN_FEEDBACK_MODAL, - payload: isOpen, - } - expect(openFeedbackModal(isOpen)).toEqual(expectedAction) - }) -}) diff --git a/src/store/modal/modal.actions.ts b/src/store/modal/modal.actions.ts deleted file mode 100644 index fe1ea172b7b15172883d3ff6294aa38d60a69547..0000000000000000000000000000000000000000 --- a/src/store/modal/modal.actions.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { defaultAction } from 'store' - -export const OPEN_PARTNERS_MODAL = 'OPEN_PARTNERS_ISSUE' -export const OPEN_FEEDBACK_MODAL = 'OPEN_FEEDBACK_MODAL' - -interface OpenFeedbackModal { - type: typeof OPEN_FEEDBACK_MODAL - payload?: boolean -} - -interface OpenPartnersModal { - type: typeof OPEN_PARTNERS_MODAL - payload?: { - egl: boolean - enedis: boolean - grdf: boolean - } -} - -export type ModalActionTypes = - | OpenFeedbackModal - | OpenPartnersModal - | typeof defaultAction - -export function openFeedbackModal(isOpen: boolean): OpenFeedbackModal { - return { - type: OPEN_FEEDBACK_MODAL, - payload: isOpen, - } -} - -export function openPartnersModal(openPartnersModal: { - egl: boolean - enedis: boolean - grdf: boolean -}): OpenPartnersModal { - return { - type: OPEN_PARTNERS_MODAL, - payload: openPartnersModal, - } -} diff --git a/src/store/modal/modal.reducer.ts b/src/store/modal/modal.reducer.ts deleted file mode 100644 index 10b2a2715e0bc90697838ddeaf5c96ffba415521..0000000000000000000000000000000000000000 --- a/src/store/modal/modal.reducer.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ModalState } from 'models' -import { Reducer } from 'redux' -import { - ModalActionTypes, - OPEN_FEEDBACK_MODAL, - OPEN_PARTNERS_MODAL, -} from 'store/modal/modal.actions' - -const initialState: ModalState = { - isFeedbacksOpen: false, - partnersIssueModal: { - enedis: false, - egl: false, - grdf: false, - }, -} - -export const modalReducer: Reducer<ModalState, ModalActionTypes> = ( - state = initialState, - action -) => { - if (action.payload == undefined) return state - - const updateState = (updates: Partial<ModalState>): ModalState => ({ - ...state, - ...updates, - }) - - switch (action.type) { - case OPEN_FEEDBACK_MODAL: - return updateState({ isFeedbacksOpen: action.payload }) - - case OPEN_PARTNERS_MODAL: - return updateState({ partnersIssueModal: action.payload }) - - default: - return state - } -} diff --git a/src/store/modal/modal.reducer.spec.ts b/src/store/modal/modal.slice.spec.ts similarity index 65% rename from src/store/modal/modal.reducer.spec.ts rename to src/store/modal/modal.slice.spec.ts index b52662c6fd8e473eafc2b65911080e6408289f0d..e2e52d06b89e04b0b7703df5e9f99e51d30e471d 100644 --- a/src/store/modal/modal.reducer.spec.ts +++ b/src/store/modal/modal.slice.spec.ts @@ -1,29 +1,28 @@ import { ModalState } from 'models' -import { defaultAction } from 'store' import { mockInitialModalState } from '../../../tests/__mocks__/store' -import { OPEN_FEEDBACK_MODAL, OPEN_PARTNERS_MODAL } from './modal.actions' -import { modalReducer } from './modal.reducer' +import { modalSlice, openFeedbackModal, openPartnersModal } from './modal.slice' describe('modal reducer', () => { it('should return the initial state', () => { - const state = modalReducer(undefined, { ...defaultAction }) - expect(state).toEqual(mockInitialModalState) + const initialState = modalSlice.reducer(undefined, { type: undefined }) + expect(initialState).toEqual(mockInitialModalState) }) describe('Feedback Modal', () => { it('should handle UPDATE_FEEDBACK_MODAL with payload', () => { - const state = modalReducer(mockInitialModalState, { - type: OPEN_FEEDBACK_MODAL, - payload: true, - }) + const state = modalSlice.reducer( + mockInitialModalState, + openFeedbackModal(true) + ) expect(state).toEqual({ ...mockInitialModalState, isFeedbacksOpen: true, }) }) + // to remove ? not very useful it('should handle UPDATE_FEEDBACK_MODAL without payload', () => { - const state = modalReducer(mockInitialModalState, { - type: OPEN_FEEDBACK_MODAL, + const state = modalSlice.reducer(mockInitialModalState, { + type: undefined, }) expect(state).toEqual(mockInitialModalState) }) @@ -36,7 +35,9 @@ describe('modal reducer', () => { grdf: true, } it('should have all partners to false by default', () => { - const state = modalReducer(mockInitialModalState, { ...defaultAction }) + const state = modalSlice.reducer(mockInitialModalState, { + type: undefined, + }) const expectedResult: ModalState = { ...mockInitialModalState, partnersIssueModal: { @@ -48,10 +49,10 @@ describe('modal reducer', () => { expect(state).toEqual(expectedResult) }) it('should handle OPEN_PARTNERS_MODAL to set all partners to true', () => { - const state = modalReducer(mockInitialModalState, { - type: OPEN_PARTNERS_MODAL, - payload: { ...partnersModalAllTrue }, - }) + const state = modalSlice.reducer( + mockInitialModalState, + openPartnersModal({ egl: true, enedis: true, grdf: true }) + ) const expectedResult: ModalState = { ...mockInitialModalState, partnersIssueModal: { @@ -61,17 +62,14 @@ describe('modal reducer', () => { expect(state).toEqual(expectedResult) }) it('should handle OPEN_PARTNERS_MODAL to set some partners to false', () => { - const state = modalReducer( + const state = modalSlice.reducer( { ...mockInitialModalState, partnersIssueModal: { ...partnersModalAllTrue, }, }, - { - type: OPEN_PARTNERS_MODAL, - payload: { egl: true, enedis: false, grdf: false }, - } + openPartnersModal({ egl: true, enedis: false, grdf: false }) ) const expectedResult: ModalState = { ...mockInitialModalState, diff --git a/src/store/modal/modal.slice.ts b/src/store/modal/modal.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..96eb360cb6a10301cc6cc3dcb4943755e574a21f --- /dev/null +++ b/src/store/modal/modal.slice.ts @@ -0,0 +1,35 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit' +import { ModalState } from 'models' + +const initialState: ModalState = { + isFeedbacksOpen: false, + partnersIssueModal: { + enedis: false, + egl: false, + grdf: false, + }, +} + +type OpenFeedbackModalAction = PayloadAction<boolean> +type OpenPartnersModalAction = PayloadAction<{ + egl: boolean + enedis: boolean + grdf: boolean +}> + +export type ModalActionTypes = OpenFeedbackModalAction | OpenPartnersModalAction + +export const modalSlice = createSlice({ + name: 'modal', + initialState, + reducers: { + openFeedbackModal: (state, action: OpenFeedbackModalAction) => { + state.isFeedbacksOpen = action.payload + }, + openPartnersModal: (state, action: OpenPartnersModalAction) => { + state.partnersIssueModal = action.payload + }, + }, +}) + +export const { openFeedbackModal, openPartnersModal } = modalSlice.actions diff --git a/yarn.lock b/yarn.lock index c062f94d85f70039eedc09dfd3f42f8e96734c99..387d82e497d63491d1a0fbc0d3e014957b7c68cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2338,6 +2338,16 @@ "@react-spring/core" "9.0.0-rc.3" "@react-spring/shared" "9.0.0-rc.3" +"@reduxjs/toolkit@^1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.3.tgz#27e1a33072b5a312e4f7fa19247fec160bbb2df9" + integrity sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg== + dependencies: + immer "^9.0.16" + redux "^4.2.0" + redux-thunk "^2.4.2" + reselect "^4.1.7" + "@remix-run/router@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.2.1.tgz#812edd4104a15a493dda1ccac0b352270d7a188c" @@ -9413,6 +9423,11 @@ immediate@3.0.6: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== +immer@^9.0.16: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + immutable@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" @@ -14421,6 +14436,11 @@ redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== +redux-thunk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" + integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== + "redux@3 || 4", redux@^4.0.0, redux@^4.0.5: version "4.2.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" @@ -14435,6 +14455,13 @@ redux@4.1.2: dependencies: "@babel/runtime" "^7.9.2" +redux@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -14672,6 +14699,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +reselect@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" + integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"