diff --git a/manifest.webapp b/manifest.webapp index bb2c081bf0..d92bd2da27 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -13,6 +13,7 @@ }, "version": "1.85.0", "licence": "AGPL-3.0", + "offline_support": true, "permissions": { "home": { "description": "Required to manage default redirection update", diff --git a/src/components/Announcements/AnnouncementsDialogContent.tsx b/src/components/Announcements/AnnouncementsDialogContent.tsx index 2a8e79f099..df60fdfa87 100644 --- a/src/components/Announcements/AnnouncementsDialogContent.tsx +++ b/src/components/Announcements/AnnouncementsDialogContent.tsx @@ -25,17 +25,16 @@ const AnnouncementsDialogContent: FC = ({ const { isMobile } = useBreakpoints() const { t, f } = useI18n() const primaryImage = useAnnouncementsImage( - announcement.attributes.primary_image.data.attributes.formats.small?.url ?? - announcement.attributes.primary_image.data.attributes.url + announcement.primary_image.data.attributes.formats.small?.url ?? + announcement.primary_image.data.attributes.url ) const secondaryImage = useAnnouncementsImage( - announcement.attributes.secondary_image.data?.attributes.formats.thumbnail - .url + announcement.secondary_image.data?.attributes.formats.thumbnail.url ) const handleMainAction = (): void => { - if (announcement.attributes.main_action?.link) { - window.open(announcement.attributes.main_action.link, '_blank') + if (announcement.main_action?.link) { + window.open(announcement.main_action.link, '_blank') } } @@ -44,10 +43,7 @@ const AnnouncementsDialogContent: FC = ({ {primaryImage ? ( { = ({ /> ) : null} - {announcement.attributes.title} + {announcement.title} = ({ className="u-mb-1" variant="body2" > - {f( - announcement.attributes.start_at, - t('AnnouncementsDialogContent.dateFormat') - )} + {f(announcement.start_at, t('AnnouncementsDialogContent.dateFormat'))}
= ({ 'u-ta-center u-maw-100' )} > - +
- {announcement.attributes.main_action ? ( + {announcement.main_action ? ( ) : null} @@ -97,10 +90,7 @@ const AnnouncementsDialogContent: FC = ({ {secondaryImage ? ( { { +export const setupAppContext = memoize(intent => { const lang = document.documentElement.getAttribute('lang') || 'en' const context = window.context || 'cozy' const root = document.querySelector('[role=application]') const data = root.dataset + const shouldUseWebFlagshipLink = isFlagshipApp() && isFlagshipOfflineSupported() + // New improvements must be done with CozyClient const cozyClient = new CozyClient({ uri: `${window.location.protocol}//${data.cozyDomain}`, @@ -48,7 +55,10 @@ export const setupAppContext = memoize(() => { 'home.store.persist' ) ? true - : false + : false, + links: shouldUseWebFlagshipLink + ? new WebFlagshipLink({ webviewIntent: intent }) + : null }) cozyClient.registerPlugin(flag.plugin) @@ -103,7 +113,21 @@ const ThemeProvider = ({ children }) => { * for an app */ const AppWrapper = ({ children }) => { - const appContext = setupAppContext() + const webviewIntent = useWebviewIntent() + const [appContext, setAppContext] = useState(undefined) + + useEffect(() => { + if (isFlagshipApp() && !webviewIntent) return + + const newAppContext = setupAppContext(webviewIntent) + + setAppContext(newAppContext) + }, [webviewIntent]) + + if (!appContext) { + return null + } + const { store, cozyClient, context, lang, persistor } = appContext return ( diff --git a/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.jsx b/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.jsx index 721ca0e56d..dff7237cbc 100644 --- a/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.jsx +++ b/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' -import { useClient, useQuery } from 'cozy-client' +import { useSettings } from 'cozy-client' import { useWebviewIntent } from 'cozy-intent' import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' import { makeStyles } from 'cozy-ui/transpiled/react/styles' @@ -10,13 +10,12 @@ import Button from 'cozy-ui/transpiled/react/Buttons' import Icon from 'cozy-ui/transpiled/react/Icon' import LightbulbIcon from 'cozy-ui/transpiled/react/Icons/Lightbulb' -import { instanceSettingsConn, homeSettingsConn } from 'queries' import { - shouldShowDefaultRedirectionSnackbar, - disableDefaultRedirectionSnackbar, - setDefaultRedirectionToHome -} from './helpers' + HOME_DEFAULT_REDIRECTION, + useShouldShowDefaultRedirectionSnackbar +} from './useShouldShowDefaultRedirectionSnackbar' import useIncrementDefaultRedirectionViewCount from './useIncrementDefaultRedirectionViewCount' +import { isFlagshipApp } from 'cozy-device-helper' const useStyles = makeStyles(theme => ({ snackbar: { @@ -29,39 +28,39 @@ const useStyles = makeStyles(theme => ({ const DefaultAppSnackbar = () => { const { t } = useI18n() - const client = useClient() const classes = useStyles() const [isOpen, setIsOpen] = useState(true) const webviewIntent = useWebviewIntent() - const instanceSettingsResult = useQuery( - instanceSettingsConn.query, - instanceSettingsConn - ) + const { save: saveHome } = useSettings('home', [ + 'default_redirection_snackbar_disabled' + ]) - const homeSettingsResult = useQuery(homeSettingsConn.query, homeSettingsConn) + const { save: saveGlobal } = useSettings('instance', ['default_redirection']) - useIncrementDefaultRedirectionViewCount( - instanceSettingsResult, - homeSettingsResult - ) + useIncrementDefaultRedirectionViewCount() - const showDefaultAppSnackbar = shouldShowDefaultRedirectionSnackbar( - instanceSettingsResult, - homeSettingsResult, - isOpen - ) + const showDefaultAppSnackbar = useShouldShowDefaultRedirectionSnackbar(isOpen) const onRefuse = () => { setIsOpen(false) - disableDefaultRedirectionSnackbar(client, homeSettingsResult) + saveHome({ + default_redirection_snackbar_disabled: true + }) } const onAccept = () => { setIsOpen(false) - disableDefaultRedirectionSnackbar(client, homeSettingsResult) - setDefaultRedirectionToHome(client, instanceSettingsResult, webviewIntent) + saveHome({ + default_redirection_snackbar_disabled: true + }) + saveGlobal({ + default_redirection: HOME_DEFAULT_REDIRECTION + }) + if (isFlagshipApp()) { + webviewIntent.call('setDefaultRedirection', HOME_DEFAULT_REDIRECTION) + } } return ( diff --git a/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.spec.jsx b/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.spec.jsx index 614eccbf6f..ba1c0a50fc 100644 --- a/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.spec.jsx +++ b/src/components/DefaultRedirectionSnackbar/DefaultRedirectionSnackbar.spec.jsx @@ -1,22 +1,21 @@ import React from 'react' import { render, fireEvent } from '@testing-library/react' +import { useSettings } from 'cozy-client' + import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme' import AppLike from 'test/AppLike' import DefaultRedirectionSnackbar from './DefaultRedirectionSnackbar' -import { - shouldShowDefaultRedirectionSnackbar, - disableDefaultRedirectionSnackbar, - setDefaultRedirectionToHome -} from './helpers' +import { useShouldShowDefaultRedirectionSnackbar } from './useShouldShowDefaultRedirectionSnackbar' jest.mock('cozy-client', () => ({ ...jest.requireActual('cozy-client'), useQuery: jest.fn(), - useClient: jest.fn() + useClient: jest.fn(), + useSettings: jest.fn() })) -jest.mock('./helpers') +jest.mock('./useShouldShowDefaultRedirectionSnackbar') jest.mock('./useIncrementDefaultRedirectionViewCount') const setup = () => { @@ -36,7 +35,26 @@ describe('DefaultRedirectionSnackbar', () => { }) it('should display default redirection snackbar', () => { - shouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + useShouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + const mockSaveInstance = jest.fn() + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + }, + save: mockSaveInstance + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + }, + save: mockSaveHome + } + } + }) const { root } = setup() @@ -45,24 +63,70 @@ describe('DefaultRedirectionSnackbar', () => { }) it('should disable default redirection snackbar and set default redirection to home when accepting', () => { - shouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + useShouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + + const mockSaveInstance = jest.fn() + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + }, + save: mockSaveInstance + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + }, + save: mockSaveHome + } + } + }) const { root } = setup() fireEvent.click(root.queryByText('OK')) - expect(disableDefaultRedirectionSnackbar).toHaveBeenCalled() - expect(setDefaultRedirectionToHome).toHaveBeenCalled() + expect(mockSaveInstance).toHaveBeenCalledWith({ + default_redirection: 'home/' + }) + expect(mockSaveHome).toHaveBeenCalledWith({ + default_redirection_snackbar_disabled: true + }) }) it('should disable default redirection snackbar when refusing', () => { - shouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + useShouldShowDefaultRedirectionSnackbar.mockReturnValue(true) + + const mockSaveInstance = jest.fn() + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + }, + save: mockSaveInstance + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + }, + save: mockSaveHome + } + } + }) const { root } = setup() fireEvent.click(root.queryByText('No, thank you')) - expect(disableDefaultRedirectionSnackbar).toHaveBeenCalled() - expect(setDefaultRedirectionToHome).not.toHaveBeenCalled() + expect(mockSaveHome).toHaveBeenCalledWith({ + default_redirection_snackbar_disabled: true + }) + expect(mockSaveInstance).not.toHaveBeenCalled() }) }) diff --git a/src/components/DefaultRedirectionSnackbar/helpers.js b/src/components/DefaultRedirectionSnackbar/helpers.js deleted file mode 100644 index 561bb6e355..0000000000 --- a/src/components/DefaultRedirectionSnackbar/helpers.js +++ /dev/null @@ -1,107 +0,0 @@ -import { deconstructRedirectLink, hasQueryBeenLoaded } from 'cozy-client' -import { isFlagshipApp } from 'cozy-device-helper' - -export const VIEW_COUNT_THRESHOLD = 3 - -export const HOME_DEFAULT_REDIRECTION = 'home/' - -export const incrementDefaultRedirectionViewCount = async ( - client, - homeSettings -) => { - const { default_redirection_view_count } = homeSettings - const newHomeSettings = { - _type: 'io.cozy.home.settings', - ...homeSettings, - default_redirection_view_count: default_redirection_view_count - ? default_redirection_view_count + 1 - : 1 - } - - return await client.save(newHomeSettings) -} - -export const disableDefaultRedirectionSnackbar = async ( - client, - homeSettingsResult -) => { - const homeSettings = - (homeSettingsResult.data && homeSettingsResult.data[0]) || {} - - const newHomeSettings = { - _type: 'io.cozy.home.settings', - ...homeSettings, - default_redirection_snackbar_disabled: true - } - - return await client.save(newHomeSettings) -} - -export const setDefaultRedirectionToHome = async ( - client, - instanceSettingsResult, - webviewIntent -) => { - const instanceSettings = { - _id: instanceSettingsResult.data._id, - _type: instanceSettingsResult.data._type, - _rev: instanceSettingsResult.data.meta.rev, - data: { - ...instanceSettingsResult.data, - attributes: { - ...instanceSettingsResult.data.attributes, - default_redirection: HOME_DEFAULT_REDIRECTION - } - } - } - - const res = await client.save(instanceSettings) - - if (isFlagshipApp()) { - webviewIntent.call('setDefaultRedirection', HOME_DEFAULT_REDIRECTION) - } - - return res -} - -export const shouldShowDefaultRedirectionSnackbar = ( - instanceSettingsResult, - homeSettingsResult, - isOpen -) => { - if ( - !hasQueryBeenLoaded(instanceSettingsResult) || - !hasQueryBeenLoaded(homeSettingsResult) - ) - return false - - const { - data: { - attributes: { default_redirection } - } - } = instanceSettingsResult - - const homeSettings = - (homeSettingsResult.data && homeSettingsResult.data[0]) || {} - - const { - default_redirection_snackbar_disabled, - default_redirection_view_count - } = homeSettings - - const { slug } = deconstructRedirectLink(default_redirection) - - const isDefaultRedirectionAppHomeApp = slug === 'home' - - const isShowThresholdReached = - default_redirection_view_count >= VIEW_COUNT_THRESHOLD - - const isDisabled = default_redirection_snackbar_disabled - - return ( - !isDefaultRedirectionAppHomeApp && - isShowThresholdReached && - !isDisabled && - isOpen - ) -} diff --git a/src/components/DefaultRedirectionSnackbar/helpers.spec.js b/src/components/DefaultRedirectionSnackbar/helpers.spec.js deleted file mode 100644 index f320bf3d04..0000000000 --- a/src/components/DefaultRedirectionSnackbar/helpers.spec.js +++ /dev/null @@ -1,292 +0,0 @@ -import { deconstructRedirectLink, hasQueryBeenLoaded } from 'cozy-client' - -import { - HOME_DEFAULT_REDIRECTION, - incrementDefaultRedirectionViewCount, - disableDefaultRedirectionSnackbar, - setDefaultRedirectionToHome, - shouldShowDefaultRedirectionSnackbar -} from './helpers' - -jest.mock('cozy-client') - -const client = { - save: v => v -} - -const FOO_INSTANCE = { - foo: 'foo' -} - -const FOO_BAR_INSTANCE = { - data: { - foo: 'foo', - attributes: { - bar: 'bar' - } - } -} - -describe('incrementDefaultRedirectionViewCount', () => { - it('should set default_redirection_view_count to 1 if previously undefined', async () => { - const homeSettings = {} - - const { default_redirection_view_count } = - await incrementDefaultRedirectionViewCount(client, homeSettings) - - expect(default_redirection_view_count).toBe(1) - }) - - it('should set default_redirection_view_count to 2 if previously 1', async () => { - const homeSettings = { - default_redirection_view_count: 1 - } - - const { default_redirection_view_count } = - await incrementDefaultRedirectionViewCount(client, homeSettings) - - expect(default_redirection_view_count).toBe(2) - }) - - it('should not erase previous data', async () => { - const { foo } = await incrementDefaultRedirectionViewCount( - client, - FOO_INSTANCE - ) - - expect(foo).toBe(FOO_INSTANCE.foo) - }) -}) - -describe('disableDefaultRedirectionSnackbar', () => { - it('should set default_redirection_snackbar_disabled to true', async () => { - const homeSettingsResult = { - data: [{}] - } - - const { default_redirection_snackbar_disabled } = - await disableDefaultRedirectionSnackbar(client, homeSettingsResult) - - expect(default_redirection_snackbar_disabled).toBe(true) - }) - - it('should not erase previous data', async () => { - const { foo } = await incrementDefaultRedirectionViewCount( - client, - FOO_INSTANCE - ) - - expect(foo).toBe(FOO_INSTANCE.foo) - }) -}) - -describe('setDefaultRedirectionToHome', () => { - it('should set default_redirection to home', async () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: {} - } - } - - const { data } = await setDefaultRedirectionToHome(client, instance) - - expect(data.attributes.default_redirection).toBe(HOME_DEFAULT_REDIRECTION) - }) - - it('should not erase previous data', async () => { - const { data } = await incrementDefaultRedirectionViewCount( - client, - FOO_BAR_INSTANCE - ) - - expect(data.foo).toBe(FOO_BAR_INSTANCE.data.foo) - expect(data.attributes.bar).toBe(FOO_BAR_INSTANCE.data.attributes.bar) - }) -}) - -describe('shouldShowDefaultRedirectionSnackbar', () => { - it('should return true when everything is good', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'drive/#/folder' - } - } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 4 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(true) - deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - true - ) - - expect(showDefaultRedirectionSnackbar).toBe(true) - }) - - it('should return false when query has not been loaded', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'drive/#/folder' - } - } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 4 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(false) - deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - true - ) - - expect(showDefaultRedirectionSnackbar).toBe(false) - }) - - it('should return false when default redirection app is home app', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'home/' - } - } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 4 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(true) - deconstructRedirectLink.mockReturnValue({ slug: 'home' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - true - ) - - expect(showDefaultRedirectionSnackbar).toBe(false) - }) - - it('should return false when show threshold is not reached', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'drive/#/folder' - } - } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 2 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(true) - deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - true - ) - - expect(showDefaultRedirectionSnackbar).toBe(false) - }) - - it('should return false when default redirection snackbar is disabled', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'drive/#/folder' - } - } - } - const homeSettings = { - data: [ - { - default_redirection_snackbar_disabled: true, - default_redirection_view_count: 4 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(true) - deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - true - ) - - expect(showDefaultRedirectionSnackbar).toBe(false) - }) - - it('should return false when open is false', () => { - const instance = { - data: { - _id: '_id', - _type: '_type', - meta: { rev: 'rev' }, - attributes: { - default_redirection: 'drive/#/folder', - default_redirection_view_count: 4 - } - } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 4 - } - ] - } - hasQueryBeenLoaded.mockReturnValue(true) - deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) - - const showDefaultRedirectionSnackbar = shouldShowDefaultRedirectionSnackbar( - instance, - homeSettings, - false - ) - - expect(showDefaultRedirectionSnackbar).toBe(false) - }) -}) diff --git a/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.js b/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.js index 343c17a509..9df4934ec4 100644 --- a/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.js +++ b/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.js @@ -2,25 +2,34 @@ import { useState, useEffect } from 'react' import { useClient, + useSettings, deconstructRedirectLink, hasQueryBeenLoaded } from 'cozy-client' -import { - VIEW_COUNT_THRESHOLD, - incrementDefaultRedirectionViewCount -} from './helpers' +import { VIEW_COUNT_THRESHOLD } from './useShouldShowDefaultRedirectionSnackbar' import useHomeAppOpened from './useHomeAppOpened' -const useIncrementDefaultRedirectionViewCount = ( - instanceSettingsResult, - homeSettingsResult -) => { +const useIncrementDefaultRedirectionViewCount = () => { const client = useClient() const [hasIncremented, setHasIncremented] = useState(false) const { homeJustOpenedOnFlagshipApp, homeJustQuitOnFlagshipApp } = useHomeAppOpened() + const { + values: valueHome, + save: saveHome, + query: queryHome + } = useSettings('home', [ + 'default_redirection_view_count', + 'default_redirection_snackbar_disabled' + ]) + + const { values: valueInstance, query: queryInstance } = useSettings( + 'instance', + ['default_redirection'] + ) + useEffect(() => { if (hasIncremented && homeJustQuitOnFlagshipApp) { setHasIncremented(false) @@ -28,35 +37,18 @@ const useIncrementDefaultRedirectionViewCount = ( }, [hasIncremented, homeJustQuitOnFlagshipApp]) useEffect(() => { - if ( - !hasQueryBeenLoaded(instanceSettingsResult) || - !hasQueryBeenLoaded(homeSettingsResult) - ) { + if (!hasQueryBeenLoaded(queryHome) || !hasQueryBeenLoaded(queryInstance)) { return } - const { - data: { - attributes: { default_redirection } - } - } = instanceSettingsResult - - const homeSettings = - (homeSettingsResult.data && homeSettingsResult.data[0]) || {} - - const { - default_redirection_snackbar_disabled, - default_redirection_view_count - } = homeSettings - - const { slug } = deconstructRedirectLink(default_redirection) + const { slug } = deconstructRedirectLink(valueInstance.default_redirection) const isDefaultRedirectionAppHomeApp = slug === 'home' const isShowThresholdReached = - default_redirection_view_count >= VIEW_COUNT_THRESHOLD + valueHome.default_redirection_view_count >= VIEW_COUNT_THRESHOLD - const isDisabled = default_redirection_snackbar_disabled + const isDisabled = valueHome.default_redirection_snackbar_disabled if ( !hasIncremented && @@ -65,15 +57,21 @@ const useIncrementDefaultRedirectionViewCount = ( !isShowThresholdReached && !isDisabled ) { - incrementDefaultRedirectionViewCount(client, homeSettings) + saveHome({ + default_redirection_view_count: + (valueHome.default_redirection_view_count ?? 0) + 1 + }) setHasIncremented(true) } }, [ client, hasIncremented, homeJustOpenedOnFlagshipApp, - instanceSettingsResult, - homeSettingsResult + valueInstance, + valueHome, + queryHome, + queryInstance, + saveHome ]) } diff --git a/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.spec.js b/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.spec.js index 9de5000a9c..d1b4e83a16 100644 --- a/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.spec.js +++ b/src/components/DefaultRedirectionSnackbar/useIncrementDefaultRedirectionViewCount.spec.js @@ -1,13 +1,16 @@ import { renderHook } from '@testing-library/react-hooks' -import { deconstructRedirectLink, hasQueryBeenLoaded } from 'cozy-client' +import { + deconstructRedirectLink, + hasQueryBeenLoaded, + useSettings +} from 'cozy-client' -import { incrementDefaultRedirectionViewCount } from './helpers' import useIncrementDefaultRedirectionViewCount from './useIncrementDefaultRedirectionViewCount' import useHomeAppOpened from './useHomeAppOpened' jest.mock('cozy-client') -jest.mock('./helpers') +jest.mock('./useShouldShowDefaultRedirectionSnackbar') jest.mock('./useHomeAppOpened') describe('useIncrementDefaultRedirectionViewCount', () => { @@ -15,21 +18,86 @@ describe('useIncrementDefaultRedirectionViewCount', () => { jest.resetAllMocks() }) + it('should increment by 1', () => { + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 1 + }, + save: mockSaveHome + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + useHomeAppOpened.mockReturnValue({ + homeJustOpenedOnFlagshipApp: true, + homeJustQuitOnFlagshipApp: false + }) + + renderHook(() => useIncrementDefaultRedirectionViewCount()) + + expect(mockSaveHome).toHaveBeenNthCalledWith(1, { + default_redirection_view_count: 2 + }) + }) + + it('should set view_count to 1 when incrementing if initial value is undefined', () => { + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: {}, + save: mockSaveHome + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + useHomeAppOpened.mockReturnValue({ + homeJustOpenedOnFlagshipApp: true, + homeJustQuitOnFlagshipApp: false + }) + + renderHook(() => useIncrementDefaultRedirectionViewCount()) + + expect(mockSaveHome).toHaveBeenNthCalledWith(1, { + default_redirection_view_count: 1 + }) + }) + it('should increment only 1 time when everything is good', () => { - const instance = { - data: { - attributes: { - default_redirection: 'drive/#/folder' + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 2 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 2 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(true) deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) useHomeAppOpened.mockReturnValue({ @@ -38,32 +106,34 @@ describe('useIncrementDefaultRedirectionViewCount', () => { }) const { rerender } = renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) + useIncrementDefaultRedirectionViewCount() ) - expect(incrementDefaultRedirectionViewCount).toHaveBeenCalledTimes(1) + expect(mockSaveHome).toHaveBeenCalledTimes(1) rerender() - expect(incrementDefaultRedirectionViewCount).toHaveBeenCalledTimes(1) + expect(mockSaveHome).toHaveBeenCalledTimes(1) }) it('should not increment when query has not been loaded', () => { - const instance = { - data: { - attributes: { - default_redirection: 'drive/#/folder', - default_redirection_view_count: 2 + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 2 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 2 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(false) deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) useHomeAppOpened.mockReturnValue({ @@ -71,28 +141,29 @@ describe('useIncrementDefaultRedirectionViewCount', () => { homeJustQuitOnFlagshipApp: false }) - renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) - ) + renderHook(() => useIncrementDefaultRedirectionViewCount()) - expect(incrementDefaultRedirectionViewCount).not.toHaveBeenCalled() + expect(mockSaveHome).not.toHaveBeenCalled() }) it('should not increment when default redirection app is home app', () => { - const instance = { - data: { - attributes: { - default_redirection: 'home/' + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'home/' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 2 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 2 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(true) deconstructRedirectLink.mockReturnValue({ slug: 'home' }) useHomeAppOpened.mockReturnValue({ @@ -100,28 +171,29 @@ describe('useIncrementDefaultRedirectionViewCount', () => { homeJustQuitOnFlagshipApp: false }) - renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) - ) + renderHook(() => useIncrementDefaultRedirectionViewCount()) - expect(incrementDefaultRedirectionViewCount).not.toHaveBeenCalled() + expect(mockSaveHome).not.toHaveBeenCalled() }) it('should not increment when show threshold is reached', () => { - const instance = { - data: { - attributes: { - default_redirection: 'drive/#/folder' + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 4 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(true) deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) useHomeAppOpened.mockReturnValue({ @@ -129,29 +201,30 @@ describe('useIncrementDefaultRedirectionViewCount', () => { homeJustQuitOnFlagshipApp: false }) - renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) - ) + renderHook(() => useIncrementDefaultRedirectionViewCount()) - expect(incrementDefaultRedirectionViewCount).not.toHaveBeenCalled() + expect(mockSaveHome).not.toHaveBeenCalled() }) it('should not increment when default redirection snackbar is disabled', () => { - const instance = { - data: { - attributes: { - default_redirection: 'drive/#/folder' + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_snackbar_disabled: true, + default_redirection_view_count: 2 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_snackbar_disabled: true, - default_redirection_view_count: 2 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(true) deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) useHomeAppOpened.mockReturnValue({ @@ -159,28 +232,30 @@ describe('useIncrementDefaultRedirectionViewCount', () => { homeJustQuitOnFlagshipApp: false }) - renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) - ) + renderHook(() => useIncrementDefaultRedirectionViewCount()) - expect(incrementDefaultRedirectionViewCount).not.toHaveBeenCalled() + expect(mockSaveHome).not.toHaveBeenCalled() }) it('should not increment when home not just opened on flagship app', () => { - const instance = { - data: { - attributes: { - default_redirection: 'drive/#/folder' + const mockSaveHome = jest.fn() + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_snackbar_disabled: true, + default_redirection_view_count: 2 + }, + save: mockSaveHome } } - } - const homeSettings = { - data: [ - { - default_redirection_view_count: 2 - } - ] - } + }) hasQueryBeenLoaded.mockReturnValue(true) deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) useHomeAppOpened.mockReturnValue({ @@ -188,10 +263,8 @@ describe('useIncrementDefaultRedirectionViewCount', () => { homeJustQuitOnFlagshipApp: false }) - renderHook(() => - useIncrementDefaultRedirectionViewCount(instance, homeSettings) - ) + renderHook(() => useIncrementDefaultRedirectionViewCount()) - expect(incrementDefaultRedirectionViewCount).not.toHaveBee + expect(mockSaveHome).not.toHaveBeenCalled() }) }) diff --git a/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.js b/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.js new file mode 100644 index 0000000000..6d2fd86e9b --- /dev/null +++ b/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.js @@ -0,0 +1,41 @@ +import { + deconstructRedirectLink, + hasQueryBeenLoaded, + useSettings +} from 'cozy-client' + +export const VIEW_COUNT_THRESHOLD = 3 + +export const HOME_DEFAULT_REDIRECTION = 'home/' + +export const useShouldShowDefaultRedirectionSnackbar = isOpen => { + const { values: valueHome, query: queryHome } = useSettings('home', [ + 'default_redirection_view_count', + 'default_redirection_snackbar_disabled' + ]) + + const { values: valueInstance, query: queryInstance } = useSettings( + 'instance', + ['default_redirection'] + ) + + if (!hasQueryBeenLoaded(queryHome) || !hasQueryBeenLoaded(queryInstance)) { + return false + } + + const { slug } = deconstructRedirectLink(valueInstance.default_redirection) + + const isDefaultRedirectionAppHomeApp = slug === 'home' + + const isShowThresholdReached = + valueHome.default_redirection_view_count >= VIEW_COUNT_THRESHOLD + + const isDisabled = valueHome.default_redirection_snackbar_disabled + + return ( + !isDefaultRedirectionAppHomeApp && + isShowThresholdReached && + !isDisabled && + isOpen + ) +} diff --git a/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.spec.js b/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.spec.js new file mode 100644 index 0000000000..f88e067296 --- /dev/null +++ b/src/components/DefaultRedirectionSnackbar/useShouldShowDefaultRedirectionSnackbar.spec.js @@ -0,0 +1,173 @@ +import { renderHook } from '@testing-library/react-hooks' + +import { + deconstructRedirectLink, + hasQueryBeenLoaded, + useSettings +} from 'cozy-client' + +import { useShouldShowDefaultRedirectionSnackbar } from './useShouldShowDefaultRedirectionSnackbar' + +jest.mock('cozy-client') + +describe('shouldShowDefaultRedirectionSnackbar', () => { + it('should return true when everything is good', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + } + } + } + }) + + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(true) + ) + + expect(result.current).toBe(true) + }) + + it('should return false when query has not been loaded', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + } + } + } + }) + + hasQueryBeenLoaded.mockReturnValue(false) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(true) + ) + + expect(result.current).toBe(false) + }) + + it('should return false when default redirection app is home app', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'home/' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 4 + } + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'home' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(true) + ) + + expect(result.current).toBe(false) + }) + + it('should return false when show threshold is not reached', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_view_count: 2 + } + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(true) + ) + + expect(result.current).toBe(false) + }) + + it('should return false when default redirection snackbar is disabled', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_snackbar_disabled: true, + default_redirection_view_count: 4 + } + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(true) + ) + + expect(result.current).toBe(false) + }) + + it('should return false when open is false', () => { + useSettings.mockImplementation((...args) => { + if (args[0] === 'instance') { + return { + values: { + default_redirection: 'drive/#/folder' + } + } + } else if (args[0] === 'home') { + return { + values: { + default_redirection_snackbar_disabled: true, + default_redirection_view_count: 4 + } + } + } + }) + hasQueryBeenLoaded.mockReturnValue(true) + deconstructRedirectLink.mockReturnValue({ slug: 'drive' }) + + const { result } = renderHook(() => + useShouldShowDefaultRedirectionSnackbar(false) + ) + + expect(result.current).toBe(false) + }) +}) diff --git a/src/components/Sections/queries/konnectors.ts b/src/components/Sections/queries/konnectors.ts index 66506e78d3..ad58c32ffb 100644 --- a/src/components/Sections/queries/konnectors.ts +++ b/src/components/Sections/queries/konnectors.ts @@ -1,6 +1,6 @@ import memoize from 'lodash/memoize' -import CozyClient from 'cozy-client' +import CozyClient, { Q } from 'cozy-client' import { IOCozyKonnector } from 'cozy-client/types/types' import { KonnectorFromRegistry } from 'components/Sections/SectionsTypes' @@ -36,10 +36,8 @@ export const fetchAllKonnectors = async ( client: CozyClient, channel = 'stable' ): Promise<{ [key: string]: IOCozyKonnector[] }> => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - const { data } = (await client.stackClient.fetchJSON( - 'GET', - `/registry?versionsChannel=${channel}&filter[type]=konnector&limit=300` + const { data } = (await client.query( + Q('io.cozy.apps_registry').getById(`konnectors/${channel}`) )) as { data: KonnectorFromRegistry[] } return memoizedGroupByCategory(data) diff --git a/src/components/ShortcutLink.jsx b/src/components/ShortcutLink.jsx index b91183ce05..3d6a9d99b4 100644 --- a/src/components/ShortcutLink.jsx +++ b/src/components/ShortcutLink.jsx @@ -26,7 +26,7 @@ export const ShortcutLink = ({ const computedSize = isMobile ? 32 : desktopSize const { filename } = CozyFile.splitFilename(file) - const url = get(shortcutInfos, 'data.attributes.url', '#') + const url = get(shortcutInfos, 'data.url', '#') /** * If we don't have iconMimeType, we consider that the icon is a binary svg. diff --git a/src/containers/IntentService.jsx b/src/containers/IntentService.jsx index 4a8d2037a4..2898828141 100644 --- a/src/containers/IntentService.jsx +++ b/src/containers/IntentService.jsx @@ -41,8 +41,8 @@ const IntentService = ({ data, service }) => { service.hideCross() } if ( - intent.attributes.action === 'VIEW' && - intent.attributes.type === 'io.cozy.accounts' + intent.action === 'VIEW' && + intent.type === 'io.cozy.accounts' ) { navigate(`/${service.getData().slug}/accounts/${data.accountId}`) } else { diff --git a/src/hooks/useAnnouncements.tsx b/src/hooks/useAnnouncements.tsx index 8f0acdf09b..082319f65b 100644 --- a/src/hooks/useAnnouncements.tsx +++ b/src/hooks/useAnnouncements.tsx @@ -68,7 +68,7 @@ const useAnnouncements = ({ if (rawData !== null && values && !hasStartedFiltering) { setHasStartedFiltering(true) const uuidsSeen = values.seen ?? [] - const uuidsFromApi = rawData.map(({ attributes }) => attributes.uuid) + const uuidsFromApi = rawData.map(data => data.uuid) // we only keep the announcements seen that are still returned by the API // to limit the size of the seen array diff --git a/src/hooks/useInstanceSettings.ts b/src/hooks/useInstanceSettings.ts index 1686bdfa1e..5e7e4f4bc4 100644 --- a/src/hooks/useInstanceSettings.ts +++ b/src/hooks/useInstanceSettings.ts @@ -47,12 +47,11 @@ const getLocalAttributes = ( ): InstanceAttributes | undefined => (Array.isArray(instanceSettings.data) && instanceSettings.data.length > 0 && - (instanceSettings.data[0] as { attributes?: InstanceAttributes }) - .attributes) || + (instanceSettings.data[0] as InstanceAttributes)) || undefined type FetchAttributesResponse = { - data?: { attributes?: InstanceAttributes } + data?: InstanceAttributes } | void const fetchAttributes = async ( @@ -62,7 +61,7 @@ const fetchAttributes = async ( Q('io.cozy.settings').getById('io.cozy.settings.instance') )) as FetchAttributesResponse - return res?.data?.attributes + return res?.data } export const useInstanceSettings = (client: CozyClient): InstanceSettings => { diff --git a/src/lib/konnectors.js b/src/lib/konnectors.js index e26754c79e..becdb9d855 100644 --- a/src/lib/konnectors.js +++ b/src/lib/konnectors.js @@ -18,7 +18,7 @@ export const TWO_FA_ERRORS = [ ] function patchFolderPermission(cozy, konnector, folderId = null) { - const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug + const slug = konnector.slug const saveFolder = folderId ? { type: 'io.cozy.files', values: [folderId] } : {}