Skip to content

Commit f6a5263

Browse files
feat(experiences): fked-26 - fix api patch issue, experience editing
1 parent db7f318 commit f6a5263

File tree

8 files changed

+177
-21
lines changed

8 files changed

+177
-21
lines changed

src/actions/experiences.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55

66
import { call, put, takeLatest } from 'redux-saga/effects';
77

8-
import { EXPERIENCES_FETCH_FOR_USER, EXPERIENCES_CREATE } from '../constants';
8+
import {
9+
EXPERIENCES_FETCH_FOR_USER,
10+
EXPERIENCES_CREATE,
11+
EXPERIENCES_EDIT
12+
} from '../constants';
913
import {
1014
experiencesFetchForUser as getExperiencesForUser,
11-
experiencesCreate as postExperiences
15+
experiencesCreate as postExperiences,
16+
experiencesEdit as patchExperiences
1217
} from '../lib/api';
1318
import actionGenerator from '../lib/actionGenerator';
1419

@@ -77,7 +82,51 @@ export function* experiencesCreate({
7782
);
7883
}
7984

85+
/**
86+
* Edits an existing experience
87+
*
88+
* @param {object} payload - Payload for this saga action.
89+
* @param {object} payload.id - Unique ID of experience being updated.
90+
* @param {string} payload.title - Title of this experience.
91+
* @param {string} payload.body - Description for this experience.
92+
* @param {string} payload.field_experience_path - URL slug for this experience.
93+
* @param {function} payload.successHandler
94+
* Function to be executed if/when this action succeeds.
95+
* @param {object} payload.user - Object containing user data.
96+
* @param {object} payload.user.authentication - Object containing auth data.
97+
* @param {string} payload.user.authentication.accessToken
98+
* Access token for the current user.
99+
* @param {string} payload.user.authentication.csrfToken
100+
* CSRF token for the current user.
101+
*/
102+
export function* experiencesEdit({
103+
id,
104+
user,
105+
title,
106+
body = '',
107+
field_experience_path,
108+
successHandler = () => {}
109+
}) {
110+
yield* actionGenerator(
111+
EXPERIENCES_EDIT,
112+
function* experienceEditHandler() {
113+
const experience = yield call(
114+
patchExperiences,
115+
{ id, title, body, field_experience_path },
116+
user
117+
);
118+
119+
yield put({
120+
type: `${EXPERIENCES_EDIT}_SUCCESS`,
121+
payload: experience
122+
});
123+
},
124+
successHandler
125+
);
126+
}
127+
80128
export function* watchExperiencesActions() {
81129
yield takeLatest(EXPERIENCES_FETCH_FOR_USER, experiencesFetchForUser);
82130
yield takeLatest(EXPERIENCES_CREATE, experiencesCreate);
131+
yield takeLatest(EXPERIENCES_EDIT, experiencesEdit);
83132
}

src/components/ExperienceForm/ExperienceForm.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { TextField, Button, withStyles } from '@material-ui/core';
99

1010
import { Message } from '../';
1111
import ExperienceFormStyles from './ExperienceForm.style';
12-
import { EXPERIENCES_CREATE } from '../../constants';
12+
import { EXPERIENCES_CREATE, EXPERIENCES_EDIT } from '../../constants';
1313

1414
class ExperienceForm extends Component {
1515
static propTypes = {
@@ -54,20 +54,35 @@ class ExperienceForm extends Component {
5454
};
5555

5656
/**
57-
* Handles a creating an experience.
57+
* Fetches the experience that belongs to the given slug.
58+
*/
59+
getExperience = () => {
60+
const {
61+
experienceSlug,
62+
experiences: { items }
63+
} = this.props;
64+
return experienceSlug
65+
? items.find(item => item.field_experience_path === experienceSlug)
66+
: false;
67+
};
68+
69+
/**
70+
* Handles dispatching create or update actions.
5871
*
5972
* @param {object} event - Submit event object.
6073
*/
61-
handleCreate = event => {
74+
handleSubmit = event => {
6275
event.preventDefault();
6376
const {
6477
dispatch,
6578
user,
66-
history: { push }
79+
history: { push },
80+
experienceSlug
6781
} = this.props;
6882
dispatch({
69-
type: EXPERIENCES_CREATE,
83+
type: experienceSlug ? EXPERIENCES_EDIT : EXPERIENCES_CREATE,
7084
user,
85+
id: this.getExperience().id,
7186
title: event.target[0].value,
7287
field_experience_path: event.target[1].value,
7388
body: event.target[2].value,
@@ -83,16 +98,13 @@ class ExperienceForm extends Component {
8398
classes,
8499
submitHandler,
85100
experienceSlug,
86-
experiences: { error, items }
101+
experiences: { error }
87102
} = this.props;
88103

89-
// If this is an editorial form, grab the existing experience.
90-
const experience = experienceSlug
91-
? items.find(item => item.field_experience_path === experienceSlug)
92-
: false;
104+
const experience = this.getExperience();
93105

94106
return (
95-
<form onSubmit={submitHandler || this.handleCreate}>
107+
<form onSubmit={submitHandler || this.handleSubmit}>
96108
{error && <Message>{error}</Message>}
97109
<TextField
98110
id="title"
@@ -131,7 +143,7 @@ class ExperienceForm extends Component {
131143
type="submit"
132144
className={classes.button}
133145
>
134-
Create
146+
{experienceSlug ? 'Save' : 'Create'}
135147
</Button>
136148
</form>
137149
);

src/components/ExperienceForm/__snapshots__/ExperienceForm.test.js.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ exports[`<ExperienceForm /> Matches its snapshot 1`] = `
3131
autoComplete={undefined}
3232
autoFocus={undefined}
3333
className="MuiInput-input-25"
34-
defaultValue={undefined}
34+
defaultValue=""
3535
disabled={false}
3636
id="title"
3737
name={undefined}
@@ -82,7 +82,7 @@ exports[`<ExperienceForm /> Matches its snapshot 1`] = `
8282
autoComplete={undefined}
8383
autoFocus={undefined}
8484
className="MuiInput-input-25"
85-
defaultValue={undefined}
85+
defaultValue=""
8686
disabled={false}
8787
id="field_experience_path"
8888
name={undefined}
@@ -133,7 +133,7 @@ exports[`<ExperienceForm /> Matches its snapshot 1`] = `
133133
autoComplete={undefined}
134134
autoFocus={undefined}
135135
className="MuiInput-input-25 MuiInput-inputMultiline-27"
136-
defaultValue={undefined}
136+
defaultValue=""
137137
disabled={false}
138138
id="body"
139139
name={undefined}

src/constants/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
export const API_ENDPOINT_USER_LOGIN = 'oauth/token';
77
export const API_ENDPOINT_XCSRF_TOKEN = 'rest/session/token';
88
export const API_ENDPOINT_EXPERIENCES = 'node/experience';
9-
export const API_TYPE_EXPERIENCES = 'node--experiences';
9+
export const API_TYPE_EXPERIENCES = 'node--experience';

src/constants/experiences.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
export const EXPERIENCES_FETCH_FOR_USER = 'EXPERIENCES_FETCH_FOR_USER';
77
export const EXPERIENCES_CREATE = 'EXPERIENCES_CREATE';
8+
export const EXPERIENCES_EDIT = 'EXPERIENCES_EDIT';

src/lib/api/experiences.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,39 @@ export const experiencesCreate = async (
6161
}
6262
}
6363
});
64+
65+
/**
66+
* Takes new experience data and POSTs it to the API.
67+
*
68+
* @param {object} experience
69+
* Object containing information about the experience thats being updated.
70+
* @param {object} experience.id - Unique ID of experience being updated.
71+
* @param {string} experience.title - Title of this experience.
72+
* @param {string} experience.body - Description for this experience.
73+
* @param {string} experience.field_experience_path - URL slug for this experience.
74+
* @param {object} user - Object containing information about the current user.
75+
* @param {object} user.authentication - Object containing auth data.
76+
* @param {string} user.authentication.accessToken
77+
* Access token for the current user.
78+
* @param {string} user.authentication.csrfToken
79+
* CSRF token for the current user.
80+
*/
81+
export const experiencesEdit = async (
82+
{ id, title, field_experience_path, body = '' },
83+
{ authentication }
84+
) =>
85+
axiosInstance(authentication).patch(`${API_ENDPOINT_EXPERIENCES}/${id}`, {
86+
data: {
87+
id,
88+
type: API_TYPE_EXPERIENCES,
89+
attributes: {
90+
title,
91+
field_experience_path,
92+
body: {
93+
value: body,
94+
format: 'plain_text',
95+
summary: ''
96+
}
97+
}
98+
}
99+
});

src/lib/api/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55

66
import axiosInstance from './axiosInstance';
77
import { getAccessToken, getCsrfToken } from './user';
8-
import { experiencesFetchForUser, experiencesCreate } from './experiences';
8+
import {
9+
experiencesFetchForUser,
10+
experiencesCreate,
11+
experiencesEdit
12+
} from './experiences';
913

1014
export {
1115
axiosInstance,
1216
getAccessToken,
1317
getCsrfToken,
1418
experiencesFetchForUser,
15-
experiencesCreate
19+
experiencesCreate,
20+
experiencesEdit
1621
};

src/reducers/experiences.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
* Exports reducers pertaining to experience state.
44
*/
55

6-
import { EXPERIENCES_FETCH_FOR_USER, EXPERIENCES_CREATE } from '../constants';
6+
import {
7+
EXPERIENCES_FETCH_FOR_USER,
8+
EXPERIENCES_CREATE,
9+
EXPERIENCES_EDIT
10+
} from '../constants';
711

812
/**
913
* Default experience state.
@@ -89,6 +93,55 @@ export default function experiences(state = defaultState, action) {
8993
items: [...state.items]
9094
};
9195
}
96+
97+
/**
98+
* Reducer that handles experience edit success actions.
99+
*/
100+
case `${EXPERIENCES_EDIT}_SUCCESS`: {
101+
const { payload: experience } = action;
102+
103+
// Filter out the outdated object, and nab it's index.
104+
let index = null;
105+
const newItems = [...state.items].filter((item, i) => {
106+
if (item.id === experience.id) {
107+
index = i;
108+
return false;
109+
}
110+
111+
return true;
112+
});
113+
114+
// Splice the new updated object in to the array.
115+
newItems.splice(index, 0, experience);
116+
117+
return {
118+
loading: false,
119+
error: null,
120+
items: newItems
121+
};
122+
}
123+
124+
/**
125+
* Reducer that handles experience edit loading actions.
126+
*/
127+
case `${EXPERIENCES_EDIT}_LOADING`: {
128+
return {
129+
loading: true,
130+
error: null,
131+
items: [...state.items]
132+
};
133+
}
134+
135+
/**
136+
* Reducer that handles experience edit failure actions.
137+
*/
138+
case `${EXPERIENCES_EDIT}_FAIL`: {
139+
return {
140+
loading: false,
141+
error: action.payload.error,
142+
items: [...state.items]
143+
};
144+
}
92145
default:
93146
return state;
94147
}

0 commit comments

Comments
 (0)