From d44a4b260e75720e764e0056c6d9b7267ba33eb0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 19 Jan 2020 13:07:33 +0100 Subject: [PATCH] Finished component to allow metadata to be edited on existing short URLs --- src/short-urls/helpers/EditMetaModal.js | 43 +++++++++++++------ src/short-urls/helpers/ShortUrlVisitsCount.js | 2 +- src/short-urls/reducers/shortUrlMeta.js | 26 +++++------ src/short-urls/reducers/shortUrlsList.js | 29 ++++++------- src/short-urls/services/provideServices.js | 10 ++--- src/utils/utils.js | 2 + 6 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/short-urls/helpers/EditMetaModal.js b/src/short-urls/helpers/EditMetaModal.js index 611bb0db..31251dcb 100644 --- a/src/short-urls/helpers/EditMetaModal.js +++ b/src/short-urls/helpers/EditMetaModal.js @@ -1,11 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input } from 'reactstrap'; +import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { ExternalLink } from 'react-external-link'; import moment from 'moment'; import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlEditMetaType } from '../reducers/shortUrlMeta'; import DateInput from '../../utils/DateInput'; +import { formatIsoDate } from '../../utils/utils'; const propTypes = { isOpen: PropTypes.bool.isRequired, @@ -13,8 +16,6 @@ const propTypes = { shortUrl: shortUrlType.isRequired, shortUrlMeta: shortUrlEditMetaType, editShortUrlMeta: PropTypes.func, - shortUrlMetaEdited: PropTypes.func, - resetShortUrlMeta: PropTypes.func, }; const dateOrUndefined = (shortUrl, dateName) => { @@ -24,21 +25,29 @@ const dateOrUndefined = (shortUrl, dateName) => { }; const EditMetaModal = ( - { isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta, shortUrlMetaEdited, resetShortUrlMeta } + { isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta } ) => { - const { saving, error } = editShortUrlMeta; + const { saving, error } = shortUrlMeta; const url = shortUrl && (shortUrl.shortUrl || ''); - const validSince = dateOrUndefined(shortUrl, 'validSince'); - const validUntil = dateOrUndefined(shortUrl, 'validUntil'); - - console.log(shortUrlMeta, shortUrlMetaEdited, resetShortUrlMeta, useEffect, useState); + const [ validSince, setValidSince ] = useState(dateOrUndefined(shortUrl, 'validSince')); + const [ validUntil, setValidUntil ] = useState(dateOrUndefined(shortUrl, 'validUntil')); + const [ maxVisits, setMaxVisits ] = useState(shortUrl && shortUrl.meta && shortUrl.meta.maxVisits); + const doEdit = () => editShortUrlMeta(shortUrl.shortCode, { + maxVisits: maxVisits && parseInt(maxVisits), + validSince: validSince && formatIsoDate(validSince), + validUntil: validUntil && formatIsoDate(validUntil), + }).then(toggle); return ( - Edit metadata for + Edit metadata for + +

Using these metadata properties, you can limit when and how many times your short URL can be visited.

+

If any of the params is not met, the URL will behave as if it was an invalid short URL.

+
-
+ e.preventDefault() || doEdit()}> @@ -54,10 +64,17 @@ const EditMetaModal = ( selected={validUntil} minDate={validSince} isClearable + onChange={setValidUntil} /> - + setMaxVisits(e.target.value)} + /> {error && (
diff --git a/src/short-urls/helpers/ShortUrlVisitsCount.js b/src/short-urls/helpers/ShortUrlVisitsCount.js index 458ce16e..6a5c9359 100644 --- a/src/short-urls/helpers/ShortUrlVisitsCount.js +++ b/src/short-urls/helpers/ShortUrlVisitsCount.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { UncontrolledTooltip } from 'reactstrap'; -import { shortUrlMetaType } from '../reducers/shortUrlsList'; +import { shortUrlMetaType } from '../reducers/shortUrlMeta'; import './ShortUrlVisitsCount.scss'; const propTypes = { diff --git a/src/short-urls/reducers/shortUrlMeta.js b/src/short-urls/reducers/shortUrlMeta.js index 4dee593d..724b3b6a 100644 --- a/src/short-urls/reducers/shortUrlMeta.js +++ b/src/short-urls/reducers/shortUrlMeta.js @@ -1,15 +1,18 @@ -import { createAction, handleActions } from 'redux-actions'; +import { handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -import { shortUrlMetaType } from './shortUrlsList'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; export const EDIT_SHORT_URL_META_ERROR = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_ERROR'; -export const EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META'; -export const RESET_EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/RESET_EDIT_SHORT_URL_META'; export const SHORT_URL_META_EDITED = 'shlink/shortUrlMeta/SHORT_URL_META_EDITED'; /* eslint-enable padding-line-between-statements */ +export const shortUrlMetaType = PropTypes.shape({ + validSince: PropTypes.string, + validUntil: PropTypes.string, + maxVisits: PropTypes.number, +}); + export const shortUrlEditMetaType = PropTypes.shape({ shortCode: PropTypes.string, meta: shortUrlMetaType.isRequired, @@ -27,8 +30,7 @@ const initialState = { export default handleActions({ [EDIT_SHORT_URL_META_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_META_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [EDIT_SHORT_URL_META]: (state, { shortCode, meta }) => ({ shortCode, meta, saving: false, error: false }), - [RESET_EDIT_SHORT_URL_META]: () => initialState, + [SHORT_URL_META_EDITED]: (state, { shortCode, meta }) => ({ shortCode, meta, saving: false, error: false }), }, initialState); export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, meta) => async (dispatch, getState) => { @@ -37,16 +39,10 @@ export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, meta) => a try { await updateShortUrlMeta(shortCode, meta); - dispatch({ shortCode, meta, type: EDIT_SHORT_URL_META }); + dispatch({ shortCode, meta, type: SHORT_URL_META_EDITED }); } catch (e) { dispatch({ type: EDIT_SHORT_URL_META_ERROR }); + + throw e; } }; - -export const resetShortUrlMeta = createAction(RESET_EDIT_SHORT_URL_META); - -export const shortUrlMetaEdited = (shortCode, meta) => ({ - meta, - shortCode, - type: SHORT_URL_META_EDITED, -}); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index c77462b9..6eadeca2 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -3,6 +3,7 @@ import { assoc, assocPath, propEq, reject } from 'ramda'; import PropTypes from 'prop-types'; import { SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; +import { SHORT_URL_META_EDITED, shortUrlMetaType } from './shortUrlMeta'; /* eslint-disable padding-line-between-statements */ export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; @@ -10,12 +11,6 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; /* eslint-enable padding-line-between-statements */ -export const shortUrlMetaType = PropTypes.shape({ - validSince: PropTypes.string, - validUntil: PropTypes.string, - maxVisits: PropTypes.number, -}); - export const shortUrlType = PropTypes.shape({ shortCode: PropTypes.string, shortUrl: PropTypes.string, @@ -31,23 +26,25 @@ const initialState = { error: false, }; +const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, [prop]: propValue }) => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls.data.map( + (shortUrl) => shortUrl.shortCode === shortCode ? assoc(prop, propValue, shortUrl) : shortUrl + ), + state +); + export default handleActions({ [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), [LIST_SHORT_URLS]: (state, { shortUrls }) => ({ loading: false, error: false, shortUrls }), [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: {} }), - [SHORT_URL_TAGS_EDITED]: (state, action) => { // eslint-disable-line object-shorthand - const { data } = state.shortUrls; - - return assocPath([ 'shortUrls', 'data' ], data.map((shortUrl) => - shortUrl.shortCode === action.shortCode - ? assoc('tags', action.tags, shortUrl) - : shortUrl), state); - }, - [SHORT_URL_DELETED]: (state, action) => assocPath( + [SHORT_URL_DELETED]: (state, { shortCode }) => assocPath( [ 'shortUrls', 'data' ], - reject(propEq('shortCode', action.shortCode), state.shortUrls.data), + reject(propEq('shortCode', shortCode), state.shortUrls.data), state, ), + [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), + [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), }, initialState); export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => { diff --git a/src/short-urls/services/provideServices.js b/src/short-urls/services/provideServices.js index 5ca54cad..5642f784 100644 --- a/src/short-urls/services/provideServices.js +++ b/src/short-urls/services/provideServices.js @@ -8,13 +8,14 @@ import ShortUrlsRowMenu from '../helpers/ShortUrlsRowMenu'; import CreateShortUrl from '../CreateShortUrl'; import DeleteShortUrlModal from '../helpers/DeleteShortUrlModal'; import EditTagsModal from '../helpers/EditTagsModal'; +import EditMetaModal from '../helpers/EditMetaModal'; import CreateShortUrlResult from '../helpers/CreateShortUrlResult'; import { listShortUrls } from '../reducers/shortUrlsList'; import { createShortUrl, resetCreateShortUrl } from '../reducers/shortUrlCreation'; import { deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted } from '../reducers/shortUrlDeletion'; import { editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited } from '../reducers/shortUrlTags'; +import { editShortUrlMeta } from '../reducers/shortUrlMeta'; import { resetShortUrlParams } from '../reducers/shortUrlsListParams'; -import EditMetaModal from '../helpers/EditMetaModal'; const provideServices = (bottle, connect) => { // Components @@ -56,10 +57,7 @@ const provideServices = (bottle, connect) => { )); bottle.serviceFactory('EditMetaModal', () => EditMetaModal); - bottle.decorator('EditMetaModal', connect( - [ 'shortUrlMeta' ], - [ 'editShortUrlMeta', 'shortUrlMetaEdited', 'resetShortUrlMeta' ] - )); + bottle.decorator('EditMetaModal', connect([ 'shortUrlMeta' ], [ 'editShortUrlMeta' ])); // Actions bottle.serviceFactory('editShortUrlTags', editShortUrlTags, 'buildShlinkApiClient'); @@ -75,6 +73,8 @@ const provideServices = (bottle, connect) => { bottle.serviceFactory('deleteShortUrl', deleteShortUrl, 'buildShlinkApiClient'); bottle.serviceFactory('resetDeleteShortUrl', () => resetDeleteShortUrl); bottle.serviceFactory('shortUrlDeleted', () => shortUrlDeleted); + + bottle.serviceFactory('editShortUrlMeta', editShortUrlMeta, 'buildShlinkApiClient'); }; export default provideServices; diff --git a/src/utils/utils.js b/src/utils/utils.js index 917873f9..97595ddc 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -70,3 +70,5 @@ export const versionIsValidSemVer = (version) => { }; export const formatDate = (format = 'YYYY-MM-DD') => (date) => date && date.format ? date.format(format) : date; + +export const formatIsoDate = (date) => date && date.format ? date.format() : date;