From 5587dcaf4c5eee6af27de07cb476c6fc25de8cde Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Mon, 15 Jul 2024 16:35:28 +0200 Subject: [PATCH] feat: Replace deprecated alerter by actual one --- src/declarations.d.ts | 13 -- src/photos/components/Alerter.jsx | 158 ------------------ src/photos/components/Layout.jsx | 8 +- .../ducks/albums/components/AlbumPhotos.jsx | 41 ++++- src/photos/ducks/albums/index.jsx | 124 ++++++++++---- .../ducks/timeline/components/Timeline.jsx | 25 ++- src/photos/ducks/upload/index.jsx | 93 ++++++----- src/photos/targets/browser/index.jsx | 19 ++- src/photos/targets/public/index.jsx | 29 ++-- test/components/AppLike.jsx | 5 +- 10 files changed, 224 insertions(+), 291 deletions(-) delete mode 100644 src/photos/components/Alerter.jsx diff --git a/src/declarations.d.ts b/src/declarations.d.ts index fc7d2b02d..1ac2a6fed 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1,10 +1,6 @@ declare module 'cozy-ui/*' declare module 'cozy-ui/transpiled/react' { - export const Alerter: { - error: (message: string) => void - } - export const logger: { info: (message: string, ...rest: unknown[]) => void } @@ -15,12 +11,3 @@ declare module 'cozy-ui/transpiled/react/providers/I18n' { t: (key: string, options?: Record) => string } } - -declare module 'cozy-ui/transpiled/react/deprecated/Alerter' { - const Alerter: { - error: (message: string, options?: Record) => void - success: (message: string, options?: Record) => void - } - - export default Alerter -} diff --git a/src/photos/components/Alerter.jsx b/src/photos/components/Alerter.jsx deleted file mode 100644 index 618a15899..000000000 --- a/src/photos/components/Alerter.jsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { Component } from 'react' - -import styles from '../styles/alerter.styl' -import classNames from 'classnames' - -const createStore = () => { - let notifications = [] - let listeners = [] - - const dispatch = notification => { - notification.id = notifications.length - notifications.push(notification) - listeners.forEach(listener => listener(notification)) - } - - const subscribe = listener => { - listeners.push(listener) - } - - return { dispatch, subscribe } -} - -const store = createStore() - -class Alert extends Component { - constructor() { - super() - this.state = { - hidden: true - } - } - - componentDidMount() { - this.close = this.close.bind(this) - this.closeTimer = setTimeout(() => { - this.beginClosing() - }, 2000) - // Delay to trigger CSS transition after the first render. - // Totally open for a better way to achieve this. - setTimeout(() => { - this.setState({ hidden: false }) - }, 20) - } - - componentWillUnmount() { - // this.base.removeEventListener('transitionend', this.close, false) - this.setState({ hidden: false }) - if (this.closeTimer) { - clearTimeout(this.closeTimer) - } - } - - beginClosing() { - // this.base.addEventListener('transitionend', this.close, false) - this.setState({ hidden: true }) - } - - close() { - this.props.onClose() - } - - render() { - const { message, type, buttonText, buttonAction } = this.props - const { hidden } = this.state - return ( -
- - {type === 'error' && ( - - ) - } -} - -export default class Alerter extends Component { - constructor(props, context) { - super(props, context) - this.state = { - notifications: [] - } - } - - static info(msg, options) { - store.dispatch({ type: 'info', msg, options }) - } - - static success(msg, options) { - store.dispatch({ type: 'success', msg, options }) - } - - static error(msg, options) { - store.dispatch({ type: 'error', msg, options }) - } - - componentDidMount() { - this.setState({ mounted: true }) - store.subscribe(this.notify.bind(this)) - } - - notify(notification) { - this.setState({ - notifications: [...this.state.notifications, notification] - }) - } - - handleClose(id) { - let idx = this.state.notifications.findIndex(n => n.id === id) - this.setState({ - notifications: [ - ...this.state.notifications.slice(0, idx), - ...this.state.notifications.slice(idx + 1) - ] - }) - } - - render() { - const t = this.props.t - return ( -
- {this.state.notifications.map(notif => ( - - ))} -
- ) - } -} diff --git a/src/photos/components/Layout.jsx b/src/photos/components/Layout.jsx index 251f231fa..a45014246 100755 --- a/src/photos/components/Layout.jsx +++ b/src/photos/components/Layout.jsx @@ -2,7 +2,7 @@ import React from 'react' import { Outlet, NavLink as RouterLink } from 'react-router-dom' import { BarComponent } from 'cozy-bar' -import { useI18n, translate } from 'cozy-ui/transpiled/react/providers/I18n' +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' import Sprite from 'cozy-ui/transpiled/react/Icon/Sprite' import { Layout as LayoutUI, Main } from 'cozy-ui/transpiled/react/Layout' import Sidebar from 'cozy-ui/transpiled/react/Sidebar' @@ -18,7 +18,6 @@ import { isFlagshipApp } from 'cozy-device-helper' import flag from 'cozy-flags' import ButtonClient from 'components/pushClient/Button' -import Alerter from 'cozy-ui/transpiled/react/deprecated/Alerter' import { UploadQueue } from '../ducks/upload' import PushBanner from 'components/PushBanner' @@ -113,7 +112,7 @@ const getNavLinks = () => { return } -export const Layout = ({ t }) => ( +export const Layout = () => ( @@ -121,7 +120,6 @@ export const Layout = ({ t }) => ( {!isFlagshipApp() && } -
@@ -133,4 +131,4 @@ export const Layout = ({ t }) => ( ) -export default translate()(Layout) +export default Layout diff --git a/src/photos/ducks/albums/components/AlbumPhotos.jsx b/src/photos/ducks/albums/components/AlbumPhotos.jsx index 36fb68c66..cab9b32f2 100644 --- a/src/photos/ducks/albums/components/AlbumPhotos.jsx +++ b/src/photos/ducks/albums/components/AlbumPhotos.jsx @@ -2,13 +2,13 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import PropTypes from 'prop-types' import { Outlet, useNavigate, useLocation } from 'react-router-dom' +import flow from 'lodash/flow' import { withClient } from 'cozy-client' import { showModal } from 'react-cozy-helpers' import { translate } from 'cozy-ui/transpiled/react/providers/I18n' -import Alerter from 'cozy-ui/transpiled/react/deprecated/Alerter' +import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert' import { ShareModal } from 'cozy-sharing' -import flow from 'lodash/flow' import styles from '../../../styles/layout.styl' @@ -38,7 +38,10 @@ class AlbumPhotos extends Component { renameAlbum = name => { if (name.trim() === '') { - Alerter.error('Error.album_rename_abort') + this.props.showAlert({ + message: this.props.t('Error.album_rename_abort'), + severity: 'error' + }) return } else if (name === this.props.album.name) { this.setState({ editing: false }) @@ -54,7 +57,10 @@ class AlbumPhotos extends Component { this.setState({ editing: false }) }) .catch(() => { - Alerter.error('Error.generic') + this.props.showAlert({ + message: this.props.t('Error.generic'), + severity: 'error' + }) }) } // !TODO Hack. We should not use 99999 as limit. @@ -88,7 +94,7 @@ class AlbumPhotos extends Component { }) } renderDestroyConfirm = () => { - const { t, navigate, album, deleteAlbum } = this.props + const { t, navigate, album, deleteAlbum, showAlert } = this.props return ( { navigate('/albums') - Alerter.success('Albums.remove_album.success', { - name: album.name + showAlert({ + message: t('Albums.remove_album.success', { + name: album.name + }), + severity: 'success' }) }) - .catch(() => Alerter.error('Albums.remove_album.error.generic')) + .catch(() => + showAlert({ + message: t('Albums.remove_album.error.generic'), + severity: 'error' + }) + ) } /> ) @@ -230,7 +244,16 @@ AlbumPhotos.propTypes = { const AlbumPhotosWrapper = props => { const { pathname } = useLocation() const navigate = useNavigate() - return + const { showAlert } = useAlert() + + return ( + + ) } export default flow( diff --git a/src/photos/ducks/albums/index.jsx b/src/photos/ducks/albums/index.jsx index c193eef94..14617bb40 100644 --- a/src/photos/ducks/albums/index.jsx +++ b/src/photos/ducks/albums/index.jsx @@ -1,7 +1,8 @@ import React from 'react' import { Query, withMutations, useQuery, useClient } from 'cozy-client' -import { Alerter } from 'cozy-ui/transpiled/react/' +import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert' +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' import AlbumsView from './components/AlbumsView' import AlbumPhotos from './components/AlbumPhotos' @@ -24,56 +25,78 @@ const ALBUMS_QUERY = client => .include(['photos']) .sortBy([{ created_at: 'desc' }]) -const addPhotos = async (album, photos) => { +const addPhotos = async (album, photos, showAlert, t) => { try { const photoCountBefore = album.photos.data.length await album.photos.addById(photos.map(({ _id }) => _id)) const photoCountAfter = album.photos.data.length if (photoCountBefore + photos.length !== photoCountAfter) { - Alerter.info('Alerter.photos.already_added_photo') + showAlert({ + message: t('Alerter.photos.already_added_photo'), + severity: 'secondary' + }) } else { - Alerter.success('Albums.add_photos.success', { - name: album.name, - smart_count: photos.length + showAlert({ + message: t('Albums.add_photos.success', { + name: album.name, + smart_count: photos.length + }), + severity: 'success' }) } } catch (error) { - Alerter.error('Albums.add_photos.error.reference') + showAlert({ + message: t('Albums.add_photos.error.reference'), + severity: 'error' + }) } } -const ALBUM_MUTATIONS = client => ({ +const ALBUM_MUTATIONS = (client, showAlert, t) => ({ updateAlbum: async album => { const unique = await client .collection(DOCTYPE_ALBUMS) .checkUniquenessOf('name', album.name) if (unique !== true) { - Alerter.error('Albums.create.error.already_exists', { name }) + showAlert({ + message: t('Albums.create.error.already_exists', { name }), + severity: 'error' + }) return } else { return client.save(album) } }, deleteAlbum: album => client.destroy(album), - addPhotos, + addPhotos: async (album, photos) => addPhotos(album, photos, showAlert, t), removePhotos: async (album, photos, clearSelection) => { try { await album.photos.removeById(photos.map(({ _id }) => _id)) - Alerter.success('Albums.remove_photos.success', { - album_name: album.name + showAlert({ + message: t('Albums.remove_photos.success', { + album_name: album.name + }), + severity: 'success' }) clearSelection() } catch (e) { - Alerter.error('Albums.remove_photos.error.generic') + showAlert({ + message: t('Albums.remove_photos.error.generic'), + severiy: 'error' + }) } } }) -const ALBUMS_MUTATIONS = client => ({ - addPhotos, + +const ALBUMS_MUTATIONS = (showAlert, t) => client => ({ + addPhotos: async (album, photos) => addPhotos(album, photos, showAlert, t), createAlbum: async (name, photos, created_at = new Date()) => { try { if (!name) { - Alerter.error('Albums.create.error.name_missing') + showAlert({ + message: t('Albums.create.error.name_missing'), + severity: 'error' + }) return } const album = { _type: DOCTYPE_ALBUMS, name, created_at } @@ -82,7 +105,10 @@ const ALBUMS_MUTATIONS = client => ({ .collection(DOCTYPE_ALBUMS) .checkUniquenessOf('name', album.name) if (unique !== true) { - Alerter.error('Albums.create.error.already_exists', { name }) + showAlert({ + message: t('Albums.create.error.already_exists', { name }), + severity: 'error' + }) return } const resp = await client.create( @@ -95,13 +121,19 @@ const ALBUMS_MUTATIONS = client => ({ } } ) - Alerter.success('Albums.create.success', { - name: album.name, - smart_count: photos.length + showAlert({ + message: t('Albums.create.success', { + name: album.name, + smart_count: photos.length + }), + severity: 'success' }) return resp.data } catch (error) { - Alerter.error('Albums.create.error.generic') + showAlert({ + message: t('Albums.create.error.generic'), + severity: 'error' + }) } } }) @@ -114,22 +146,37 @@ const ConnectedAlbumsView = props => ( ) -const ConnectedAddToAlbumModal = props => ( - - {(result, { createAlbum, addPhotos }) => ( - - )} - -) +const ConnectedAddToAlbumModal = props => { + const { t } = useI18n() + const { showAlert } = useAlert() + + return ( + + {(result, { createAlbum, addPhotos }) => ( + + )} + + ) +} export const AlbumPhotosWithLoader = () => { const client = useClient() - const { updateAlbum, deleteAlbum, removePhotos } = ALBUM_MUTATIONS(client) + const { t } = useI18n() + const { showAlert } = useAlert() + const { updateAlbum, deleteAlbum, removePhotos } = ALBUM_MUTATIONS( + client, + showAlert, + t + ) const { albumId } = useParams() @@ -160,10 +207,13 @@ export const AlbumPhotosWithLoader = () => { } } -const CreateAlbumPicker = withMutations(ALBUMS_MUTATIONS)(PhotosPicker) +const CreateAlbumPicker = ({ showAlert, t }) => + withMutations(ALBUMS_MUTATIONS(showAlert, t))(PhotosPicker) const ConnectedPhotosPicker = ({ ...props }) => { const { albumId } = useParams() + const { t } = useI18n() + const { showAlert } = useAlert() if (albumId) { const albumsQuery = buildAlbumsQuery(albumId) @@ -171,7 +221,7 @@ const ConnectedPhotosPicker = ({ ...props }) => { {({ data }, { addPhotos }) => ( @@ -181,7 +231,7 @@ const ConnectedPhotosPicker = ({ ...props }) => { ) } - return + return } export { diff --git a/src/photos/ducks/timeline/components/Timeline.jsx b/src/photos/ducks/timeline/components/Timeline.jsx index cdd057a17..46ea22d79 100644 --- a/src/photos/ducks/timeline/components/Timeline.jsx +++ b/src/photos/ducks/timeline/components/Timeline.jsx @@ -1,5 +1,6 @@ import React, { Component } from 'react' -import { translate } from 'cozy-ui/transpiled/react/providers/I18n' +import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' +import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert' import PropTypes from 'prop-types' import styles from '../../../styles/layout.styl' @@ -19,7 +20,7 @@ import { REF_PHOTOS, REF_UPLOAD } from 'folder-references' -import { withClient } from 'cozy-client' +import { useClient } from 'cozy-client' import { Outlet } from 'react-router-dom' const getUploadDir = async (client, t) => { @@ -59,12 +60,16 @@ class Timeline extends Component { } uploadPhotos = async photos => { - const { uploadPhoto } = this.props - const { client, t } = this.props + const { uploadPhoto, client, t, showAlert } = this.props const uploadDirId = await getUploadDir(client, t) this.dispatch( - addToUploadQueue(photos, photo => uploadPhoto(photo, uploadDirId)) + addToUploadQueue({ + files: photos, + callback: photo => uploadPhoto(photo, uploadDirId), + showAlert, + t + }) ) } @@ -165,4 +170,12 @@ class Timeline extends Component { } } -export default translate()(withClient(Timeline)) +const TimelineWrapper = props => { + const client = useClient() + const { t } = useI18n() + const { showAlert } = useAlert() + + return +} + +export default TimelineWrapper diff --git a/src/photos/ducks/upload/index.jsx b/src/photos/ducks/upload/index.jsx index f6060941d..699f6e6d1 100644 --- a/src/photos/ducks/upload/index.jsx +++ b/src/photos/ducks/upload/index.jsx @@ -2,8 +2,6 @@ import React from 'react' import { combineReducers } from 'redux' import logger from 'lib/logger' -import Alerter from 'cozy-ui/transpiled/react/deprecated/Alerter' - import UploadQueue from './UploadQueue' import { showModal } from 'react-cozy-helpers' import QuotaAlert from 'components/QuotaAlert' @@ -63,62 +61,73 @@ const queue = (state = [], action) => { } export default combineReducers({ queue }) -const processNextFile = callback => async (dispatch, getState) => { - const item = getUploadQueue(getState()).find(i => i.status === PENDING) - if (!item) { - return dispatch(onQueueEmpty()) - } - const file = item.file - try { - dispatch({ type: UPLOAD_FILE, file }) - await callback(file) - dispatch({ type: RECEIVE_UPLOAD_SUCCESS, file }) - } catch (error) { - logger.log(error) - const statusError = { - 409: CONFLICT, - 413: QUOTA +const processNextFile = + (callback, showAlert, t) => async (dispatch, getState) => { + const item = getUploadQueue(getState()).find(i => i.status === PENDING) + if (!item) { + return dispatch(onQueueEmpty(showAlert, t)) } - // Photo doesn't have a status QUOTA. So - // we just use FAILED as it seems to do the job - const status = - statusError[error.status] || - /Failed to fetch$/.exec(error.toString()) || - FAILED - dispatch({ - type: RECEIVE_UPLOAD_ERROR, - file, - status: status === CONFLICT ? CONFLICT : FAILED - }) - if (status === QUOTA) { - dispatch(showModal( {}} />)) + const file = item.file + try { + dispatch({ type: UPLOAD_FILE, file }) + await callback(file) + dispatch({ type: RECEIVE_UPLOAD_SUCCESS, file }) + } catch (error) { + logger.log(error) + const statusError = { + 409: CONFLICT, + 413: QUOTA + } + // Photo doesn't have a status QUOTA. So + // we just use FAILED as it seems to do the job + const status = + statusError[error.status] || + /Failed to fetch$/.exec(error.toString()) || + FAILED + dispatch({ + type: RECEIVE_UPLOAD_ERROR, + file, + status: status === CONFLICT ? CONFLICT : FAILED + }) + if (status === QUOTA) { + dispatch(showModal( {}} />)) + } } + dispatch(processNextFile(callback, showAlert, t)) } - dispatch(processNextFile(callback)) -} -export const addToUploadQueue = (files, callback) => async dispatch => { - dispatch({ type: ADD_TO_UPLOAD_QUEUE, files }) - dispatch(processNextFile(callback)) -} +export const addToUploadQueue = + ({ files, callback, showAlert, t }) => + async dispatch => { + dispatch({ type: ADD_TO_UPLOAD_QUEUE, files }) + dispatch(processNextFile(callback, showAlert, t)) + } export const purgeUploadQueue = () => ({ type: PURGE_UPLOAD_QUEUE }) -export const onQueueEmpty = () => (dispatch, getState) => { +export const onQueueEmpty = (showAlert, t) => (dispatch, getState) => { const queue = getUploadQueue(getState()) const conflicts = getConflicts(queue) const errors = getErrors(queue) const loaded = getLoaded(queue) if (!conflicts.length && !errors.length) { - Alerter.success('UploadQueue.alert.success', { smart_count: loaded.length }) + showAlert({ + message: t('UploadQueue.alert.success', { + smart_count: loaded.length + }), + severity: 'success' + }) } else if (conflicts.length && !errors.length) { - Alerter.info('UploadQueue.alert.success_conflicts', { - smart_count: loaded.length, - conflictNumber: conflicts.length + showAlert({ + message: t('UploadQueue.alert.success_conflicts', { + smart_count: loaded.length, + conflictNumber: conflicts.length + }), + severity: 'secondary' }) } else { - Alerter.error('UploadQueue.alert.errors') + showAlert({ message: t('UploadQueue.alert.errors'), severity: 'error' }) } } diff --git a/src/photos/targets/browser/index.jsx b/src/photos/targets/browser/index.jsx index ea2054c8b..95b423870 100755 --- a/src/photos/targets/browser/index.jsx +++ b/src/photos/targets/browser/index.jsx @@ -22,6 +22,7 @@ import { I18n } from 'cozy-ui/transpiled/react/providers/I18n' import SharingProvider from 'cozy-sharing' import { WebviewIntentProvider } from 'cozy-intent' import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme' +import AlertProvider from 'cozy-ui/transpiled/react/providers/Alert' import { DOCTYPE_ALBUMS } from 'lib/doctypes' @@ -101,13 +102,17 @@ const App = props => { - - - {props.children} - + + + + + {props.children} + + + diff --git a/src/photos/targets/public/index.jsx b/src/photos/targets/public/index.jsx index 34583e785..c8b5671e8 100644 --- a/src/photos/targets/public/index.jsx +++ b/src/photos/targets/public/index.jsx @@ -14,6 +14,7 @@ import { HashRouter, Routes, Route, Navigate } from 'react-router-dom' import CozyClient, { CozyProvider, RealTimeQueries } from 'cozy-client' import { RealtimePlugin } from 'cozy-realtime' import flag from 'cozy-flags' +import AlertProvider from 'cozy-ui/transpiled/react/providers/Alert' import { BarProvider } from 'cozy-bar' import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints' @@ -74,21 +75,23 @@ async function init() { - - - - }> + + + + + }> + } + /> + } + path="*" + element={} /> - - } - /> - - + + + diff --git a/test/components/AppLike.jsx b/test/components/AppLike.jsx index d6b1f8dae..54c6fb951 100644 --- a/test/components/AppLike.jsx +++ b/test/components/AppLike.jsx @@ -3,6 +3,7 @@ import { CozyProvider } from 'cozy-client' import { Provider } from 'react-redux' import { createStore } from 'redux' +import AlertProvider from 'cozy-ui/transpiled/react/providers/Alert' import { I18n } from 'cozy-ui/transpiled/react/providers/I18n' import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme' import { SharingContext } from 'cozy-sharing' @@ -51,7 +52,9 @@ const AppLike = ({ > - {children} + + {children} +