From 95766fd68a58d44d14a65ba60227057a4bb711d9 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 10:22:18 -0400 Subject: [PATCH 1/6] Add clear to infinite list, fixup tests --- src/redux/infinite-list.js | 10 ++++++---- test/unit/redux/infinite-list.test.js | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/redux/infinite-list.js b/src/redux/infinite-list.js index b09e10b12..90f664577 100644 --- a/src/redux/infinite-list.js +++ b/src/redux/infinite-list.js @@ -35,17 +35,16 @@ */ const InfiniteList = key => { - const initialState = { + const getInitialState = () => ({ items: [], - offset: 0, error: null, loading: true, moreToLoad: false - }; + }); const reducer = (state, action) => { if (typeof state === 'undefined') { - state = initialState; + state = getInitialState(); } switch (action.type) { @@ -88,6 +87,8 @@ const InfiniteList = key => { loading: false, moreToLoad: false }; + case `${key}_CLEAR`: + return getInitialState(); default: return state; } @@ -100,6 +101,7 @@ const InfiniteList = key => { error: error => ({type: `${key}_ERROR`, error}), loading: () => ({type: `${key}_LOADING`}), append: (items, moreToLoad) => ({type: `${key}_APPEND`, items, moreToLoad}), + clear: () => ({type: `${key}_CLEAR`}), /** * Load more action returns a thunk. It takes a function to call to get more items. diff --git a/test/unit/redux/infinite-list.test.js b/test/unit/redux/infinite-list.test.js index 9e9995a3d..991caa0ae 100644 --- a/test/unit/redux/infinite-list.test.js +++ b/test/unit/redux/infinite-list.test.js @@ -104,6 +104,19 @@ describe('Infinite List redux module', () => { }); }); + describe('CLEAR', () => { + test('resets everything back to the initial state', () => { + const state = { + error: new Error(), + items: [1, 2, 3], + loading: 'something not initial', + moreToLoad: 'something not initial' + }; + const newState = module.reducer(state, module.actions.clear()); + expect(newState).toEqual(initialState); + }); + }); + describe('ERROR', () => { let action; let error = new Error(); @@ -167,7 +180,7 @@ describe('Infinite List redux module', () => { describe('selector', () => { test('will return the slice of state defined by the key', () => { const state = { - [module.key]: module.reducer(undefined, {}) // eslint-disable-line no-undefined + [module.key]: initialState }; expect(module.selector(state)).toBe(initialState); }); From f1fde9e5f8fb410a98d3aefb801d0f3f988f5c1f Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 10:23:13 -0400 Subject: [PATCH 2/6] Ensure project ID is always a number --- src/views/studio/lib/studio-project-actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/studio/lib/studio-project-actions.js b/src/views/studio/lib/studio-project-actions.js index 0de1015df..9cca949a6 100644 --- a/src/views/studio/lib/studio-project-actions.js +++ b/src/views/studio/lib/studio-project-actions.js @@ -46,7 +46,7 @@ const loadProjects = () => ((dispatch, getState) => { */ const generateProjectListItem = (postBody, infoBody) => ({ // Fields from the POST to add the project to the studio - id: postBody.projectId, + id: parseInt(postBody.projectId, 10), actor_id: postBody.actorId, // Fields from followup GET for more project info title: infoBody.title, From 35dbcb07ba88c1bc8ac92cbc123fff30d432e46a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 10:24:21 -0400 Subject: [PATCH 3/6] Add user projects modal --- src/views/studio/lib/redux-modules.js | 4 +- src/views/studio/lib/user-projects-actions.js | 54 ++++++++ .../studio/modals/user-projects-modal.jsx | 118 ++++++++++++++++++ .../studio/modals/user-projects-modal.scss | 92 ++++++++++++++ .../studio/modals/user-projects-tile.jsx | 57 +++++++++ src/views/studio/studio-curator-inviter.jsx | 47 +++---- src/views/studio/studio-project-adder.jsx | 59 +++++---- src/views/studio/studio.jsx | 4 +- src/views/studio/studio.scss | 49 +++++--- 9 files changed, 420 insertions(+), 64 deletions(-) create mode 100644 src/views/studio/lib/user-projects-actions.js create mode 100644 src/views/studio/modals/user-projects-modal.jsx create mode 100644 src/views/studio/modals/user-projects-modal.scss create mode 100644 src/views/studio/modals/user-projects-tile.jsx diff --git a/src/views/studio/lib/redux-modules.js b/src/views/studio/lib/redux-modules.js index 05fb888d6..6cadf65d4 100644 --- a/src/views/studio/lib/redux-modules.js +++ b/src/views/studio/lib/redux-modules.js @@ -5,6 +5,8 @@ const curators = InfiniteList('curators'); const managers = InfiniteList('managers'); const activity = InfiniteList('activity'); +const userProjects = InfiniteList('user-projects'); + export { - projects, curators, managers, activity + projects, curators, managers, activity, userProjects }; diff --git a/src/views/studio/lib/user-projects-actions.js b/src/views/studio/lib/user-projects-actions.js new file mode 100644 index 000000000..1657d44f6 --- /dev/null +++ b/src/views/studio/lib/user-projects-actions.js @@ -0,0 +1,54 @@ +import keyMirror from 'keymirror'; +import api from '../../../lib/api'; +import {selectUsername} from '../../../redux/session'; +import {userProjects} from './redux-modules'; + +const Errors = keyMirror({ + NETWORK: null, + SERVER: null, + PERMISSION: null +}); + +const Filters = keyMirror({ + SHARED: null, + FAVORITED: null, + RECENT: null +}); + +const Uris = { + [Filters.SHARED]: username => `/users/${username}/projects`, + [Filters.FAVORITED]: username => `/users/${username}/favorites`, + [Filters.RECENT]: username => `/users/${username}/recent` +}; + +const normalizeError = (err, body, res) => { + if (err) return Errors.NETWORK; + if (res.statusCode === 401 || res.statusCode === 403) return Errors.PERMISSION; + if (res.statusCode !== 200) return Errors.SERVER; + return null; +}; + +const loadUserProjects = type => ((dispatch, getState) => { + const state = getState(); + const username = selectUsername(state); + const projectCount = userProjects.selector(state).items.length; + const projectsPerPage = 20; + dispatch(userProjects.actions.loading()); + api({ + uri: Uris[type](username), + params: {limit: projectsPerPage, offset: projectCount} + }, (err, body, res) => { + const error = normalizeError(err, body, res); + if (error) return dispatch(userProjects.actions.error(error)); + dispatch(userProjects.actions.append(body, body.length === projectsPerPage)); + }); +}); + +// Re-export clear so that the consumer can manage filter changes +const clearUserProjects = userProjects.actions.clear; + +export { + Filters, + loadUserProjects, + clearUserProjects +}; diff --git a/src/views/studio/modals/user-projects-modal.jsx b/src/views/studio/modals/user-projects-modal.jsx new file mode 100644 index 000000000..3fec8dfa4 --- /dev/null +++ b/src/views/studio/modals/user-projects-modal.jsx @@ -0,0 +1,118 @@ +/* eslint-disable react/jsx-no-bind */ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import {connect} from 'react-redux'; +import classNames from 'classnames'; + +import {addProject, removeProject} from '../lib/studio-project-actions'; +import {userProjects} from '../lib/redux-modules'; +import {Filters, loadUserProjects, clearUserProjects} from '../lib/user-projects-actions'; + +import Modal from '../../../components/modal/base/modal.jsx'; +import ModalTitle from '../../../components/modal/base/modal-title.jsx'; +import ModalInnerContent from '../../../components/modal/base/modal-inner-content.jsx'; +import SubNavigation from '../../../components/subnavigation/subnavigation.jsx'; +import UserProjectsTile from './user-projects-tile.jsx'; + +import './user-projects-modal.scss'; + +const UserProjectsModal = ({ + items, error, loading, moreToLoad, onLoadMore, onClear, + onAdd, onRemove, onRequestClose +}) => { + const [filter, setFilter] = useState(Filters.SHARED); + + useEffect(() => { + onClear(); + onLoadMore(filter); + }, [filter]); + + return ( + + + +
  • setFilter(Filters.SHARED)} + > + Shared +
  • +
  • setFilter(Filters.FAVORITED)} + > + Favorited +
  • +
  • setFilter(Filters.RECENT)} + > + Recent +
  • +
    + + {error &&
    Error loading {filter}: {error}
    } +
    + {items.map(project => ( + + ))} +
    + {loading ? Loading... : ( + moreToLoad ? + : + No more to load + )} +
    +
    +
    +
    + ); +}; + +UserProjectsModal.propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.id, + image: PropTypes.string, + title: PropTypes.string + })), + loading: PropTypes.bool, + error: PropTypes.object, // eslint-disable-line react/forbid-prop-types + moreToLoad: PropTypes.bool, + onLoadMore: PropTypes.func, + onClear: PropTypes.func, + onAdd: PropTypes.func, + onRemove: PropTypes.func, + onRequestClose: PropTypes.func +}; + +const mapStateToProps = state => ({ + ...userProjects.selector(state) +}); + +const mapDispatchToProps = ({ + onLoadMore: loadUserProjects, + onClear: clearUserProjects, + onAdd: addProject, + onRemove: removeProject +}); + +export default connect(mapStateToProps, mapDispatchToProps)(UserProjectsModal); diff --git a/src/views/studio/modals/user-projects-modal.scss b/src/views/studio/modals/user-projects-modal.scss new file mode 100644 index 000000000..f003b72a5 --- /dev/null +++ b/src/views/studio/modals/user-projects-modal.scss @@ -0,0 +1,92 @@ +@import "../../../colors"; +@import "../../../frameless"; + +.user-projects-modal { + .user-projects-modal-title { + box-shadow: inset 0 -1px 0 0 $ui-blue-dark; + background-color: $ui-blue; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + padding-top: .75rem; + width: 100%; + height: 3rem; + } + .user-projects-modal-nav { + padding: 6px 12px; + li { + cursor: pointer; + background: rgba(0, 0, 0, 0.15); + &.active { background: $ui-blue; } + } + } + .user-projects-modal-content { + padding: 0 30px 30px; + background: #E9F1FC; + max-height: 80vh; + overflow-y: auto; + overscroll-behavior: contain; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + } +} + + +.studio-tile-dynamic-remove, +.studio-tile-dynamic-add { + position: absolute; + top: 10px; + right: 10px; + width: 32px; + height: 32px; + background: rgba(0, 0, 0, 0.25); + border: 3px solid rgba(0, 0, 0, 0.1); + background-clip: padding-box; + color: white; + border-radius: 100%; + font-weight: bold; + margin: 0; + padding: 0; + line-height: 32px; + text-align: center; +} +.studio-tile-dynamic-remove { + background: #0FBD8C; + background-clip: padding-box; + border: 3px solid rgba(15, 189, 140, 0.2); +} + +.user-projects-modal-grid { + margin-top: 12px; + display: grid; + + grid-template-columns: repeat(3, minmax(0,1fr)); + @media #{$medium} { /* Keep 3 columns to narrower width since it is in a modal */ + & { grid-template-columns: repeat(2, minmax(0,1fr)); } + } + @media #{$small} { + & { grid-template-columns: repeat(1, minmax(0,1fr)); } + } + column-gap: 14px; + row-gap: 14px; + + .studio-projects-load-more { + grid-column: 1 / -1; + } + + .studio-project-bottom { + padding: 8px 10px 8px 10px; + } + .studio-project-avatar { + width: 32px; + height: 32px; + } + .studio-project-info { + margin: 0; + } + .studio-project-title { + font-size: 12px; + } + .studio-project-username { + font-size: 12px; + } +} \ No newline at end of file diff --git a/src/views/studio/modals/user-projects-tile.jsx b/src/views/studio/modals/user-projects-tile.jsx new file mode 100644 index 000000000..201e26cfd --- /dev/null +++ b/src/views/studio/modals/user-projects-tile.jsx @@ -0,0 +1,57 @@ +/* eslint-disable react/jsx-no-bind */ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +const UserProjectsTile = ({id, title, image, onAdd, onRemove}) => { + const [submitting, setSubmitting] = useState(false); + const [added, setAdded] = useState(false); + const [error, setError] = useState(null); + const toggle = () => { + setSubmitting(true); + setError(null); + (added ? onRemove(id) : onAdd(id)) + .then(() => { + setAdded(!added); + setSubmitting(false); + }) + .catch(e => { + setError(e); + setSubmitting(false); + }); + }; + return ( +
    e.key === 'Enter' && toggle()} + > + +
    +
    {title}
    +
    + {added ? '✔' : '+'} +
    + {error &&
    {error}
    } +
    +
    + ); +}; + +UserProjectsTile.propTypes = { + id: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, + onAdd: PropTypes.func.isRequired, + onRemove: PropTypes.func.isRequired +}; + +export default UserProjectsTile; diff --git a/src/views/studio/studio-curator-inviter.jsx b/src/views/studio/studio-curator-inviter.jsx index 6c2541de0..6c3f353b7 100644 --- a/src/views/studio/studio-curator-inviter.jsx +++ b/src/views/studio/studio-curator-inviter.jsx @@ -5,6 +5,7 @@ import {connect} from 'react-redux'; import classNames from 'classnames'; import {inviteCurator} from './lib/studio-member-actions'; +import FlexRow from '../../components/flex-row/flex-row.jsx'; const StudioCuratorInviter = ({onSubmit}) => { const [value, setValue] = useState(''); @@ -14,28 +15,30 @@ const StudioCuratorInviter = ({onSubmit}) => { return (

    ✦ Invite Curators

    - setValue(e.target.value)} - /> - - {error &&
    {error}
    } + + setValue(e.target.value)} + /> + + {error &&
    {error}
    } +
    ); }; diff --git a/src/views/studio/studio-project-adder.jsx b/src/views/studio/studio-project-adder.jsx index e57b2cd52..ba7b8f312 100644 --- a/src/views/studio/studio-project-adder.jsx +++ b/src/views/studio/studio-project-adder.jsx @@ -5,37 +5,50 @@ import {connect} from 'react-redux'; import classNames from 'classnames'; import {addProject} from './lib/studio-project-actions'; +import UserProjectsModal from './modals/user-projects-modal.jsx'; +import FlexRow from '../../components/flex-row/flex-row.jsx'; const StudioProjectAdder = ({onSubmit}) => { const [value, setValue] = useState(''); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); - + const [modalOpen, setModalOpen] = useState(false); + return (

    ✦ Add Projects

    - setValue(e.target.value)} - /> - - {error &&
    {error}
    } + + setValue(e.target.value)} + /> + + {error &&
    {error}
    } +
    + + {modalOpen && setModalOpen(false)} />} +
    ); }; diff --git a/src/views/studio/studio.jsx b/src/views/studio/studio.jsx index 001fc3655..426acb52d 100644 --- a/src/views/studio/studio.jsx +++ b/src/views/studio/studio.jsx @@ -22,7 +22,8 @@ import { projects, curators, managers, - activity + activity, + userProjects } from './lib/redux-modules'; const {getInitialState, studioReducer} = require('../../redux/studio'); @@ -85,6 +86,7 @@ render( [curators.key]: curators.reducer, [managers.key]: managers.reducer, [activity.key]: activity.reducer, + [userProjects.key]: userProjects.reducer, comments: commentsReducer, studio: studioReducer, studioMutations: studioMutationsReducer, diff --git a/src/views/studio/studio.scss b/src/views/studio/studio.scss index 1b12c50f8..2dce1e442 100644 --- a/src/views/studio/studio.scss +++ b/src/views/studio/studio.scss @@ -5,7 +5,7 @@ $radius: 8px; .studio-page { background-color: #E9F1FC; - + #view { /* Reset some defaults on width and margin */ background-color: transparent; @@ -63,7 +63,7 @@ $radius: 8px; .studio-tab-nav { border-bottom: 1px solid $active-dark-gray; padding-bottom: 8px; - li { background: $active-gray; } + li { background: rgba(0, 0, 0, 0.15); } .active > li { background: $ui-blue; } } @@ -72,12 +72,12 @@ $radius: 8px; margin-top: 20px; display: grid; - grid-template-columns: minmax(0, 1fr); - @media #{$medium} { + grid-template-columns: repeat(3, minmax(0,1fr)); + @media #{$medium-and-intermediate} { & { grid-template-columns: repeat(2, minmax(0,1fr)); } } - @media #{$big} { - & { grid-template-columns: repeat(3, minmax(0,1fr)); } + @media #{$small} { + & { grid-template-columns: repeat(1, minmax(0,1fr)); } } column-gap: 30px; row-gap: 20px; @@ -91,6 +91,9 @@ $radius: 8px; background: white; border-radius: 8px; border: 1px solid $ui-border; + position: relative; + margin: 0; + padding: 0; .studio-project-image { max-width: 100%; @@ -123,6 +126,7 @@ $radius: 8px; font-weight: 700; font-size: 14px; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } .studio-project-username { @@ -130,6 +134,7 @@ $radius: 8px; font-weight: 700; font-size: 12px; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } .studio-project-remove { @@ -143,13 +148,12 @@ $radius: 8px; .studio-members-grid { margin-top: 20px; display: grid; - - grid-template-columns: minmax(0, 1fr); - @media #{$medium} { + grid-template-columns: repeat(3, minmax(0,1fr)); + @media #{$medium-and-intermediate} { & { grid-template-columns: repeat(2, minmax(0,1fr)); } } - @media #{$big} { - & { grid-template-columns: repeat(3, minmax(0,1fr)); } + @media #{$small} { + & { grid-template-columns: repeat(1, minmax(0,1fr)); } } column-gap: 30px; row-gap: 20px; @@ -187,6 +191,7 @@ $radius: 8px; font-weight: 700; font-size: 14px; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } .studio-member-role { @@ -194,6 +199,7 @@ $radius: 8px; font-weight: 400; font-size: 12px; white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; } .studio-member-remove, .studio-member-promote { @@ -209,15 +215,19 @@ $radius: 8px; .studio-adder-section { margin-top: 20px; - display: flex; - flex-wrap: wrap; h3 { color: #4C97FF; } + .flex-row { + margin: 0 -6px; + & > * { + margin: 0 6px; + } + } + input { - flex-basis: 80%; flex-grow: 1; display: inline-block; margin: .5em 0; @@ -228,11 +238,12 @@ $radius: 8px; } button { - flex-grow: 1; + flex-grow: 0; } - input + button { - margin-inline-start: 12px; + .studio-adder-vertical-divider { + border: 1px solid $ui-border; + align-self: stretch; } } @@ -264,3 +275,7 @@ $radius: 8px; cursor: wait !important; opacity: .5; } + +.mod-clickable { + cursor: pointer; +} From 968afdb41258dc89eb628ff95eef7ec545969cb0 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 10:29:24 -0400 Subject: [PATCH 4/6] Fix whitespace --- src/views/studio/modals/user-projects-modal.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/studio/modals/user-projects-modal.scss b/src/views/studio/modals/user-projects-modal.scss index f003b72a5..66aaeab4d 100644 --- a/src/views/studio/modals/user-projects-modal.scss +++ b/src/views/studio/modals/user-projects-modal.scss @@ -1,7 +1,7 @@ @import "../../../colors"; @import "../../../frameless"; -.user-projects-modal { +.user-projects-modal { .user-projects-modal-title { box-shadow: inset 0 -1px 0 0 $ui-blue-dark; background-color: $ui-blue; @@ -89,4 +89,4 @@ .studio-project-username { font-size: 12px; } -} \ No newline at end of file +} From 43eb9e4e91e675a555ca7ac6bf00d19f9fb535ff Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 20:46:38 -0400 Subject: [PATCH 5/6] Include if user projects are already in the studio --- src/views/studio/lib/user-projects-actions.js | 9 +++++++-- src/views/studio/modals/user-projects-modal.jsx | 4 +++- src/views/studio/modals/user-projects-tile.jsx | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/views/studio/lib/user-projects-actions.js b/src/views/studio/lib/user-projects-actions.js index 1657d44f6..b932ef206 100644 --- a/src/views/studio/lib/user-projects-actions.js +++ b/src/views/studio/lib/user-projects-actions.js @@ -1,7 +1,7 @@ import keyMirror from 'keymirror'; import api from '../../../lib/api'; import {selectUsername} from '../../../redux/session'; -import {userProjects} from './redux-modules'; +import {userProjects, projects} from './redux-modules'; const Errors = keyMirror({ NETWORK: null, @@ -40,7 +40,12 @@ const loadUserProjects = type => ((dispatch, getState) => { }, (err, body, res) => { const error = normalizeError(err, body, res); if (error) return dispatch(userProjects.actions.error(error)); - dispatch(userProjects.actions.append(body, body.length === projectsPerPage)); + const moreToLoad = body.length === projectsPerPage; + const studioProjectIds = projects.selector(getState()).items.map(item => item.id); + const loadedProjects = body.map(item => Object.assign(item, { + inStudio: studioProjectIds.indexOf(item.id) !== -1 + })); + dispatch(userProjects.actions.append(loadedProjects, moreToLoad)); }); }); diff --git a/src/views/studio/modals/user-projects-modal.jsx b/src/views/studio/modals/user-projects-modal.jsx index 3fec8dfa4..0e244af3f 100644 --- a/src/views/studio/modals/user-projects-modal.jsx +++ b/src/views/studio/modals/user-projects-modal.jsx @@ -69,6 +69,7 @@ const UserProjectsModal = ({ id={project.id} title={project.title} image={project.image} + inStudio={project.inStudio} onAdd={onAdd} onRemove={onRemove} /> @@ -92,7 +93,8 @@ UserProjectsModal.propTypes = { items: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.id, image: PropTypes.string, - title: PropTypes.string + title: PropTypes.string, + inStudio: PropTypes.bool })), loading: PropTypes.bool, error: PropTypes.object, // eslint-disable-line react/forbid-prop-types diff --git a/src/views/studio/modals/user-projects-tile.jsx b/src/views/studio/modals/user-projects-tile.jsx index 201e26cfd..762899b0f 100644 --- a/src/views/studio/modals/user-projects-tile.jsx +++ b/src/views/studio/modals/user-projects-tile.jsx @@ -3,9 +3,9 @@ import React, {useState} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -const UserProjectsTile = ({id, title, image, onAdd, onRemove}) => { +const UserProjectsTile = ({id, title, image, inStudio, onAdd, onRemove}) => { const [submitting, setSubmitting] = useState(false); - const [added, setAdded] = useState(false); + const [added, setAdded] = useState(inStudio); const [error, setError] = useState(null); const toggle = () => { setSubmitting(true); @@ -50,6 +50,7 @@ UserProjectsTile.propTypes = { id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, image: PropTypes.string.isRequired, + inStudio: PropTypes.bool.isRequired, onAdd: PropTypes.func.isRequired, onRemove: PropTypes.func.isRequired }; From 6ac3012b8c2017aa5d01ede9d1179b25afdd920d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 10 May 2021 21:02:20 -0400 Subject: [PATCH 6/6] Use formatted message --- src/views/studio/l10n.json | 6 +++++- src/views/studio/modals/user-projects-modal.jsx | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/views/studio/l10n.json b/src/views/studio/l10n.json index 73b3d4811..2d192e031 100644 --- a/src/views/studio/l10n.json +++ b/src/views/studio/l10n.json @@ -25,5 +25,9 @@ "studio.inviteCurator": "Invite", "studio.curatorAcceptInvite": "Accept Invite", - "studio.commentsHeader": "Comments" + "studio.commentsHeader": "Comments", + + "studio.sharedFilter": "Shared", + "studio.favoritedFilter": "Favorited", + "studio.recentFilter": "Recent" } \ No newline at end of file diff --git a/src/views/studio/modals/user-projects-modal.jsx b/src/views/studio/modals/user-projects-modal.jsx index 0e244af3f..cf6c57051 100644 --- a/src/views/studio/modals/user-projects-modal.jsx +++ b/src/views/studio/modals/user-projects-modal.jsx @@ -3,6 +3,7 @@ import React, {useEffect, useState} from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import classNames from 'classnames'; +import {FormattedMessage} from 'react-intl'; import {addProject, removeProject} from '../lib/studio-project-actions'; import {userProjects} from '../lib/redux-modules'; @@ -45,19 +46,19 @@ const UserProjectsModal = ({ className={classNames({active: filter === Filters.SHARED})} onClick={() => setFilter(Filters.SHARED)} > - Shared +
  • setFilter(Filters.FAVORITED)} > - Favorited +
  • setFilter(Filters.RECENT)} > - Recent +
  • @@ -78,7 +79,7 @@ const UserProjectsModal = ({ {loading ? Loading... : ( moreToLoad ? : No more to load )}