From a73a7e6e6f2f06b19cb07b685ff70d90c1714a08 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 29 Nov 2018 15:27:27 -0800 Subject: [PATCH] feat(experiences): add support for deleting experiences --- src/actions/experiences.js | 35 ++++++++++++++- src/constants/experiences.js | 1 + src/constants/form.js | 2 + src/lib/api/experiences.js | 17 +++++++ src/lib/api/experiences.test.js | 15 +++++++ src/lib/api/index.js | 4 +- src/pages/Dashboard/Dashboard.js | 45 ++++++++++++++++--- .../__snapshots__/Dashboard.test.js.snap | 2 +- src/reducers/experiences.js | 25 ++++++++++- 9 files changed, 136 insertions(+), 10 deletions(-) diff --git a/src/actions/experiences.js b/src/actions/experiences.js index a703993..e68cff6 100644 --- a/src/actions/experiences.js +++ b/src/actions/experiences.js @@ -8,12 +8,14 @@ import { call, put, takeLatest } from 'redux-saga/effects'; import { EXPERIENCES_FETCH_FOR_USER, EXPERIENCES_CREATE, - EXPERIENCES_EDIT + EXPERIENCES_EDIT, + EXPERIENCES_DELETE } from '../constants'; import { experiencesFetchForUser as getExperiencesForUser, experiencesCreate as postExperiences, - experiencesEdit as patchExperiences + experiencesEdit as patchExperiences, + experiencesRemove as removeExperiences } from '../lib/api'; import actionGenerator from '../lib/actionGenerator'; @@ -125,8 +127,37 @@ export function* experiencesEdit({ ); } +/** + * Deletes an existing experience. + * + * @param {object} payload - Payload for this saga action. + * @param {string} payload.id - Id of the experience to be removed + * @param {function} payload.successHandler + * Function to be executed if/when this action succeeds. + * @param {object} payload.user - Object containing user data. + * @param {object} payload.user.authentication - Object containing auth data. + * @param {string} payload.user.authentication.accessToken + * Access token for the current user. + * @param {string} payload.user.authentication.csrfToken + * CSRF token for the current user. + */ +export function* experiencesDelete({ user, id, successHandler = () => {} }) { + yield* actionGenerator( + EXPERIENCES_DELETE, + function* experienceRemoveHandler() { + yield call(removeExperiences, id, user); + yield put({ + type: `${EXPERIENCES_DELETE}_SUCCESS`, + payload: { id } + }); + }, + successHandler + ); +} + export function* watchExperiencesActions() { yield takeLatest(EXPERIENCES_FETCH_FOR_USER, experiencesFetchForUser); yield takeLatest(EXPERIENCES_CREATE, experiencesCreate); yield takeLatest(EXPERIENCES_EDIT, experiencesEdit); + yield takeLatest(EXPERIENCES_DELETE, experiencesDelete); } diff --git a/src/constants/experiences.js b/src/constants/experiences.js index 0cd9884..9e5a9e1 100644 --- a/src/constants/experiences.js +++ b/src/constants/experiences.js @@ -6,3 +6,4 @@ export const EXPERIENCES_FETCH_FOR_USER = 'EXPERIENCES_FETCH_FOR_USER'; export const EXPERIENCES_CREATE = 'EXPERIENCES_CREATE'; export const EXPERIENCES_EDIT = 'EXPERIENCES_EDIT'; +export const EXPERIENCES_DELETE = 'EXPERIENCES_DELETE'; diff --git a/src/constants/form.js b/src/constants/form.js index c391b83..03ee252 100644 --- a/src/constants/form.js +++ b/src/constants/form.js @@ -7,6 +7,8 @@ export const FORM_BUTTON_INSERT_UPDATE = 'Save'; export const FORM_BUTTON_DELETE = 'Delete'; export const FORM_MESSAGE_DELETE_CONFIRM = 'Are you sure you want to delete this component?'; +export const FORM_MESSAGE_DELETE_EXPERIENCE_CONFIRM = + 'Are you sure you want to delete this entire experience? \nAll scenes, components it contains will be permanently removed. This action cannot be undone!'; export const FORM_BUTTON_REGISTER = 'Register'; export const FORM_BUTTON_RESET_PASSWORD = 'Reset Password'; export const FORM_BUTTON_FORGOT_PASSWORD = 'Forgot Password'; diff --git a/src/lib/api/experiences.js b/src/lib/api/experiences.js index 28123a4..2383fd8 100644 --- a/src/lib/api/experiences.js +++ b/src/lib/api/experiences.js @@ -94,3 +94,20 @@ export const experiencesEdit = async ( } } }); + +/** + * Delete a given experience via the API. + * + * @param {string} id + * ID of experience. + * @param {object} user + * Object containing information about the current user. + * @param {object} user.authentication + * Object containing auth data. + * @param {string} user.authentication.accessToken + * Access token for the current user. + * @param {string} user.authentication.csrfToken + * CSRF token for the current user. + */ +export const experiencesRemove = async (id, { authentication }) => + axiosInstance(authentication).delete(`${API_ENDPOINT_EXPERIENCE}/${id}`); diff --git a/src/lib/api/experiences.test.js b/src/lib/api/experiences.test.js index d81eed7..bb43815 100644 --- a/src/lib/api/experiences.test.js +++ b/src/lib/api/experiences.test.js @@ -9,6 +9,7 @@ import mockAxios from 'jest-mock-axios'; import { experiencesFetchForUser, experiencesCreate, + experiencesRemove, experiencesEdit } from './experiences'; import { clientId } from '../../config'; @@ -70,6 +71,20 @@ describe('api->experiences', () => { }); }); + it('experiences->experiencesRemove()', () => { + const id = '42'; + experiencesRemove(id, { + authentication: { + accessToken: 'test', + csrfToken: 'test' + } + }); + + expect(mockAxios.delete).toHaveBeenCalledWith( + `${API_ENDPOINT_EXPERIENCE}/${id}` + ); + }); + it('experiences->experiencesEdit()', () => { const id = '10'; const title = 'test'; diff --git a/src/lib/api/index.js b/src/lib/api/index.js index 1b93bba..b4198a3 100644 --- a/src/lib/api/index.js +++ b/src/lib/api/index.js @@ -13,7 +13,8 @@ import { import { experiencesFetchForUser, experiencesCreate, - experiencesEdit + experiencesEdit, + experiencesRemove } from './experiences'; import { sceneCreate, sceneEdit, sceneAttachComponent } from './scene'; import { fileImageCreate, fileVideoCreate, fileCreate } from './file'; @@ -32,6 +33,7 @@ export { resetUserPassword, experiencesFetchForUser, experiencesCreate, + experiencesRemove, sceneCreate, sceneEdit, sceneAttachComponent, diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index 2069f3f..5fc3ee0 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -15,11 +15,15 @@ import { Tooltip, withStyles } from '@material-ui/core'; -import { AddBox, Edit, OpenInBrowser } from '@material-ui/icons'; +import { AddBox, Edit, OpenInBrowser, DeleteForever } from '@material-ui/icons'; import { DashboardLayout } from '../../layouts'; import { Message } from '../../components'; -import { EXPERIENCES_FETCH_FOR_USER } from '../../constants'; +import { + EXPERIENCES_FETCH_FOR_USER, + EXPERIENCES_DELETE, + FORM_MESSAGE_DELETE_EXPERIENCE_CONFIRM +} from '../../constants'; import DashboardStyles from './Dashboard.style'; class Dashboard extends Component { @@ -68,6 +72,19 @@ class Dashboard extends Component { }); } + /** + * Dispatches an action to delete the specified experience. + */ + removeExperience = experienceID => { + const { dispatch, user } = this.props; + + dispatch({ + type: EXPERIENCES_DELETE, + id: experienceID, + user + }); + }; + /** * {@inheretdoc} */ @@ -80,8 +97,9 @@ class Dashboard extends Component { return ( - On this page you can create an experience, or open one of your - existing experiences for editing by clicking the Open button. + On this page you can create an experience, open one of your existing + experiences for editing by clicking the Open button, or delete an + experience by clicking the delete button.
+ + + ) diff --git a/src/pages/Dashboard/__snapshots__/Dashboard.test.js.snap b/src/pages/Dashboard/__snapshots__/Dashboard.test.js.snap index b42c808..76f31aa 100644 --- a/src/pages/Dashboard/__snapshots__/Dashboard.test.js.snap +++ b/src/pages/Dashboard/__snapshots__/Dashboard.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` Matches its snapshot 1`] = `"
\\"EditVR

Experiences

On this page you can create an experience, or open one of your existing experiences for editing by clicking the Open button.

test experience

this is a test experience

"`; +exports[` Matches its snapshot 1`] = `"
\\"EditVR

Experiences

On this page you can create an experience, open one of your existing experiences for editing by clicking the Open button, or delete an experience by clicking the delete button.

test experience

this is a test experience

"`; diff --git a/src/reducers/experiences.js b/src/reducers/experiences.js index d6bd751..8d96812 100644 --- a/src/reducers/experiences.js +++ b/src/reducers/experiences.js @@ -6,7 +6,8 @@ import { EXPERIENCES_FETCH_FOR_USER, EXPERIENCES_CREATE, - EXPERIENCES_EDIT + EXPERIENCES_EDIT, + EXPERIENCES_DELETE } from '../constants'; /** @@ -132,6 +133,28 @@ export default function experiences(state = defaultState, action) { }; } + /** + * Reducer that handles experience deletion success actions. + */ + case `${EXPERIENCES_DELETE}_SUCCESS`: { + const { id } = action.payload; + + // Filter out the deleted experience. + const newItems = [...state.items].filter(item => { + if (item.id === id) { + return false; + } + + return true; + }); + + return { + loading: false, + error: null, + items: newItems + }; + } + /** * Reducer that handles experience edit failure actions. */