diff --git a/package.json b/package.json index dacbbac7e09d9af506ac48a3667df0f626eca833..35989c102cd083d90c4a41fcf3ad28b98904c67d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/node-fetch": "^2.5.7", "@types/react": "^16.9.15", "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.11", "@types/react-router-dom": "^5.1.3", "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", @@ -109,9 +110,11 @@ "react": "16.12.0", "react-dom": "16.12.0", "react-lottie": "^1.2.3", + "react-redux": "^7.2.2", "react-router-dom": "5.2.0", "react-swipeable-views": "0.13.4", "recoil": "^0.0.13", + "redux-devtools-extension": "^2.13.8", "sass-loader": "^8.0.0" }, "husky": { diff --git a/src/atoms/modal.state.ts b/src/atoms/modal.state.ts deleted file mode 100644 index 8bf6adf56bdad5ab134017bc3954ad665adc14bf..0000000000000000000000000000000000000000 --- a/src/atoms/modal.state.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ModalState } from 'models/modal.model' -import { atom } from 'recoil' - -export const modalState = atom<ModalState>({ - key: 'modalState', - default: { feedbackModal: false }, -}) diff --git a/src/components/App.jsx b/src/components/App.tsx similarity index 100% rename from src/components/App.jsx rename to src/components/App.tsx diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index abbfb9033b8ee3e2ba4090157cce57c1bfc62cb3..b5b0e5f4ce294871fc6a415aed239fef6a66b9e3 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -1,13 +1,15 @@ import React, { useCallback, useEffect } from 'react' import { useClient } from 'cozy-client' +import { useSelector, useDispatch } from 'react-redux' +import { EcolyoState } from 'store' +import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions' import { useRecoilState } from 'recoil' import { history } from 'components/App' import MailService from 'services/mail.service' import { ScreenType } from 'enum/screen.enum' -import { ModalState, UserProfile } from 'models' +import { UserProfile } from 'models' import { userProfileState } from 'atoms/userProfile.state' import { screenTypeState } from 'atoms/screenType.state' -import { modalState } from 'atoms/modal.state' import UserProfileService from 'services/userProfile.service' import get from 'lodash/get' @@ -36,8 +38,8 @@ const Content: React.FC<ContentProps> = ({ const [userProfile, setUserProfile] = useRecoilState<UserProfile>( userProfileState ) - - const [modal, setModalState] = useRecoilState<ModalState>(modalState) + const modalState = useSelector((state: EcolyoState) => state.modal) + const dispatch = useDispatch() const setWelcomeModalViewed = useCallback(async () => { let username = '' @@ -83,7 +85,7 @@ const Content: React.FC<ContentProps> = ({ }, [setUserProfile]) const handleFeedbackModalClose = () => { - setModalState((prev: ModalState) => ({ ...prev, feedbackModal: false })) + dispatch(updateModalIsFeedbacksOpen(false)) } useEffect(() => { @@ -119,7 +121,7 @@ const Content: React.FC<ContentProps> = ({ handleCloseClick={setWelcomeModalViewed} /> <FeedbackModal - open={modal.feedbackModal} + open={modalState.isFeedbacksOpen} handleCloseClick={handleFeedbackModalClose} /> <FavoriteModal diff --git a/src/components/Header/CozyBar.tsx b/src/components/Header/CozyBar.tsx index 6e487e1d67aa9a006c4f06352e41f0a378b82776..d05ef4dd1a92223c613b01b7af76efca09769b66 100644 --- a/src/components/Header/CozyBar.tsx +++ b/src/components/Header/CozyBar.tsx @@ -1,11 +1,11 @@ import React from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useRecoilValue, useSetRecoilState } from 'recoil' +import { useDispatch } from 'react-redux' +import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions' +import { useRecoilValue } from 'recoil' import { history } from 'components/App' import { ScreenType } from 'enum/screen.enum' -import { ModalState } from 'models' -import { modalState } from 'atoms/modal.state' import { screenTypeState } from 'atoms/screenType.state' import BackArrowIcon from 'assets/icons/ico/back-arrow.svg' @@ -24,14 +24,14 @@ const CozyBar = ({ const { t } = useI18n() const { BarLeft, BarCenter, BarRight } = cozy.bar const screenType = useRecoilValue(screenTypeState) - const setModalState = useSetRecoilState(modalState) + const dispatch = useDispatch() const handleClickBack = () => { history.goBack() } const handleClickFeedbacks = () => { - setModalState((prev: ModalState) => ({ ...prev, feedbackModal: true })) + dispatch(updateModalIsFeedbacksOpen(true)) } const cozyBarCustom = (screen?: ScreenType) => { diff --git a/src/cozy-client.d.ts b/src/cozy-client.d.ts index 7f090e3d2df79daf6ecbbf498c4b50d605063f73..d7983de8398199634428a1661c9f80d777798a3b 100644 --- a/src/cozy-client.d.ts +++ b/src/cozy-client.d.ts @@ -8,7 +8,7 @@ declare module 'cozy-client' { * @typedef {object} HydratedDocument */ - export const CozyProvider: React.FC<{ client: Client }> + export const CozyProvider: React.FC<{ client: Client; store: any }> export function useClient(): Client export function Q(doctype: TDoctype): QueryDefinition @@ -92,6 +92,9 @@ declare module 'cozy-client' { schema: unknown plugins: { realtime: TRealtimePlugin; [key: string]: unknown } + reducer(): any + setStore(store: any) + /** * A plugin is a class whose constructor receives the client as first argument. * The main mean of interaction with the client should be with events diff --git a/src/cozy-ui.d.ts b/src/cozy-ui.d.ts index 1308207968725576a43e2e7e95338ec5a8348449..9c0cb4f275420c5fe7aed4f849b4e23ac14ccad6 100644 --- a/src/cozy-ui.d.ts +++ b/src/cozy-ui.d.ts @@ -28,9 +28,7 @@ declare module 'cozy-ui/transpiled/react/I18n' { props: IPropsIcon ): React.CElement<any, React.Component<any, any, any>> export const I18n: any -} -declare module 'cozy-ui/transpiled/react/I18n' { export function initTranslation( userLocal: string, cb: (lang: string) => string diff --git a/src/models/modal.model.ts b/src/models/modal.model.ts index 9eb06e785b45b03a26d9dcbc51a7703a7cbbed60..90dd1a34fd3564201a82261c751fd0ecce4db3dc 100644 --- a/src/models/modal.model.ts +++ b/src/models/modal.model.ts @@ -1,3 +1,3 @@ export interface ModalState { - feedbackModal: boolean + isFeedbacksOpen: boolean } diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..625802664aeada972ea401dbce56b75eb5c581a2 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,8 @@ +import { ModalState } from 'models' +import { modalReducer } from 'store/modal/modal.reducer' + +export interface EcolyoState { + modal: ModalState +} + +export const ecolyoReducer = { modal: modalReducer } diff --git a/src/store/modal/modal.actions.ts b/src/store/modal/modal.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea74f5ee5c9390638695f051bb6eaf4e8f157044 --- /dev/null +++ b/src/store/modal/modal.actions.ts @@ -0,0 +1,15 @@ +export const UPDATE_MODAL_ISFEEDBACKSOPEN = 'UPDATE_MODAL_ISFEEDBACKSOPEN' + +interface UpdateModalFeedbacks { + type: typeof UPDATE_MODAL_ISFEEDBACKSOPEN + payload?: boolean +} + +export type ModalActionTypes = UpdateModalFeedbacks + +export function updateModalIsFeedbacksOpen(isOpen: boolean): ModalActionTypes { + return { + type: UPDATE_MODAL_ISFEEDBACKSOPEN, + payload: isOpen, + } +} diff --git a/src/store/modal/modal.reducer.ts b/src/store/modal/modal.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..9833fb12463075a8322aa54aa4efe8ba3ae44130 --- /dev/null +++ b/src/store/modal/modal.reducer.ts @@ -0,0 +1,27 @@ +import { Reducer } from 'redux' +import { + UPDATE_MODAL_ISFEEDBACKSOPEN, + ModalActionTypes, +} from 'store/modal/modal.actions' +import { ModalState } from 'models' + +const initialState: ModalState = { + isFeedbacksOpen: false, +} + +export const modalReducer: Reducer<ModalState> = ( + state = initialState, + action: ModalActionTypes +): ModalState => { + switch (action.type) { + case UPDATE_MODAL_ISFEEDBACKSOPEN: + const isOpen = + action.payload != undefined ? action.payload : state.isFeedbacksOpen + return { + ...state, + isFeedbacksOpen: isOpen, + } + default: + return state + } +} diff --git a/src/targets/browser/index.tsx b/src/targets/browser/index.tsx index 5d226dbce0d749d535a15aa85a883db22842ae87..0a8b55c85a0636feb4d6c28de52bc920cdc6f93a 100644 --- a/src/targets/browser/index.tsx +++ b/src/targets/browser/index.tsx @@ -3,25 +3,30 @@ import '../../styles/index.scss' import React from 'react' -import CozyClient, { CozyProvider, Client } from 'cozy-client' import ReactDOM, { render } from 'react-dom' +import CozyClient, { CozyProvider, Client } from 'cozy-client' +import { combineReducers, createStore } from 'redux' +import { Provider } from 'react-redux' +import { composeWithDevTools } from 'redux-devtools-extension' import { Document } from 'cozy-doctypes' -import { I18n } from 'cozy-ui/transpiled/react/I18n' -import { initTranslation } from 'cozy-ui/transpiled/react/I18n' +import { I18n, initTranslation } from 'cozy-ui/transpiled/react/I18n' import { handleOAuthResponse } from 'cozy-harvest-lib/dist/helpers/oauth' import schema from 'doctypes' +import { ecolyoReducer } from 'store' const manifest = require('../../../manifest.webapp') -const renderApp = (polyglot: any, lang: string, client: Client) => { +const renderApp = (polyglot: any, lang: string, client: Client, store: any) => { if (handleOAuthResponse()) return const App = require('components/App').default render( - <CozyProvider client={client}> - <I18n lang={lang} polyglot={polyglot}> - <App /> - </I18n> - </CozyProvider>, + <Provider store={store}> + <CozyProvider client={client} store={store}> + <I18n lang={lang} polyglot={polyglot}> + <App /> + </I18n> + </CozyProvider> + </Provider>, document.querySelector('[role=application]') ) } @@ -77,22 +82,33 @@ const initApp = () => { Document.registerClient(client) } + const store = createStore( + combineReducers({ + ...ecolyoReducer, + cozy: client.reducer(), + }), + composeWithDevTools() + ) + // necessary to initialize the bar with the correct React instance window.React = React window.ReactDOM = ReactDOM - cozy.bar.init({ - appName, - appNamePrefix, - iconPath, - lang, - replaceTitleOnMobile: true, - cozyClient: client, - }) + // This timeout fix the problem for setStore (param store in CozyProvider), why ? why not + setTimeout(() => { + cozy.bar.init({ + appName, + appNamePrefix, + iconPath, + lang, + replaceTitleOnMobile: true, + cozyClient: client, + }) - const { setTheme } = cozy.bar - setTheme('primary', { primaryColor: 'transparent' }) + const { setTheme } = cozy.bar + setTheme('primary', { primaryColor: 'transparent' }) + }, 0) - renderApp(polyglot, lang, client) + renderApp(polyglot, lang, client, store) } // initial rendering of the application diff --git a/yarn.lock b/yarn.lock index 8a2b1730edb4b344fc2eb07b264110cdfbeab81b..42a1ec912a0ecc8a7b6700351c8d17b264794498 100644 --- a/yarn.lock +++ b/yarn.lock @@ -828,6 +828,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.1": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.2", "@babel/runtime@^7.6.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" @@ -1604,6 +1611,14 @@ version "4.7.5" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -1712,6 +1727,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.11": + version "7.1.11" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.11.tgz#a18e8ab3651e8e8cc94798934927937c66021217" + integrity sha512-OjaFlmqy0CRbYKBoaWF84dub3impqnLJUrz4u8PRjDzaa4n1A2cVmjMV81shwXyAD5x767efhA8STFGJz/r1Zg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-router-dom@^5.1.3": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" @@ -7134,7 +7159,7 @@ hoist-non-react-statics@^2.3.1: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" dependencies: @@ -11578,7 +11603,7 @@ react-input-autosize@^2.2.1: dependencies: prop-types "^15.5.8" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -11669,6 +11694,17 @@ react-redux@^7.2.0: prop-types "^15.7.2" react-is "^16.9.0" +react-redux@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" + integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== + dependencies: + "@babel/runtime" "^7.12.1" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + react-router-dom@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -11989,6 +12025,11 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" +redux-devtools-extension@^2.13.8: + version "2.13.8" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" + integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== + redux-mock-store@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.3.tgz#1f10528949b7ce8056c2532624f7cafa98576c6d" @@ -12014,6 +12055,14 @@ redux@3.7.2, redux@^3.7.2: loose-envify "^1.1.0" symbol-observable "^1.0.3" +redux@^4.0.0: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -13542,7 +13591,7 @@ sweetalert@^2.1.2: es6-object-assign "^1.1.0" promise-polyfill "^6.0.2" -symbol-observable@1.2.0, symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@1.2.0, symbol-observable@^1.0.3, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==