Skip to content

Commit a279019

Browse files
refactor: saving portfolio items (freeCodeCamp#47635)
1 parent 147ef7b commit a279019

File tree

3 files changed

+44
-82
lines changed

3 files changed

+44
-82
lines changed

api-server/src/server/boot/settings.js

+2-11
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,14 @@ function updateMyEmail(req, res, next) {
115115
// }
116116

117117
function updateMyPortfolio(...args) {
118-
const portfolioKeys = [
119-
'id',
120-
'title',
121-
'description',
122-
'url',
123-
'image',
124-
'isSaved'
125-
];
118+
const portfolioKeys = ['id', 'title', 'description', 'url', 'image'];
126119
const buildUpdate = body => {
127120
const portfolio = body?.portfolio?.map(elem => _.pick(elem, portfolioKeys));
128121
return { portfolio };
129122
};
130123
const validate = ({ portfolio }) => portfolio?.every(isPortfolioElement);
131124
const isPortfolioElement = elem =>
132-
Object.values(elem).every(
133-
val => typeof val == 'string' || typeof val === 'boolean'
134-
);
125+
Object.values(elem).every(val => typeof val == 'string');
135126
createUpdateUserProperties(buildUpdate, validate)(...args);
136127
}
137128

client/src/components/settings/PreventableButton.jsx

-25
This file was deleted.

client/src/components/settings/portfolio.tsx

+42-46
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {
55
FormControl,
66
HelpBlock
77
} from '@freecodecamp/react-bootstrap';
8-
import { findIndex, find, isEqual, omit } from 'lodash-es';
8+
import { findIndex, find, isEqual } from 'lodash-es';
99
import { nanoid } from 'nanoid';
10-
import React, { Component, FormEvent } from 'react';
10+
import React, { Component } from 'react';
1111
import { TFunction, withTranslation } from 'react-i18next';
1212
import isURL from 'validator/lib/isURL';
1313

@@ -16,10 +16,8 @@ import { hasProtocolRE } from '../../utils';
1616
import { FullWidthRow, ButtonSpacer, Spacer } from '../helpers';
1717
import BlockSaveButton from '../helpers/form/block-save-button';
1818
import SectionHeader from './section-header';
19-
import PreventableButton from './PreventableButton';
2019

21-
type PortfolioValues = {
22-
isSaved: boolean;
20+
type PortfolioItem = {
2321
id: string;
2422
description: string;
2523
image: string;
@@ -28,38 +26,32 @@ type PortfolioValues = {
2826
};
2927

3028
type PortfolioProps = {
31-
isSaved: boolean;
3229
picture?: string;
33-
portfolio: PortfolioValues[];
30+
portfolio: PortfolioItem[];
3431
t: TFunction;
35-
updatePortfolio: (obj: { portfolio: PortfolioValues[] }) => void;
32+
updatePortfolio: (obj: { portfolio: PortfolioItem[] }) => void;
3633
username?: string;
3734
};
3835

3936
type PortfolioState = {
40-
portfolio: PortfolioValues[];
37+
portfolio: PortfolioItem[];
38+
unsavedItemId: string | null;
4139
};
4240

43-
function createEmptyPortfolio() {
41+
function createEmptyPortfolioItem(): PortfolioItem {
4442
return {
4543
id: nanoid(),
4644
title: '',
4745
description: '',
4846
url: '',
49-
image: '',
50-
isSaved: false
47+
image: ''
5148
};
5249
}
5350

5451
function createFindById(id: string) {
55-
return (p: PortfolioValues) => p.id === id;
52+
return (p: PortfolioItem) => p.id === id;
5653
}
5754

58-
const mockEvent = {
59-
// eslint-disable-next-line @typescript-eslint/no-empty-function
60-
preventDefault() {}
61-
};
62-
6355
class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
6456
static displayName: string;
6557
constructor(props: PortfolioProps) {
@@ -68,7 +60,8 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
6860
const { portfolio = [] } = props;
6961

7062
this.state = {
71-
portfolio: [...portfolio]
63+
portfolio: [...portfolio],
64+
unsavedItemId: null
7265
};
7366
}
7467

@@ -91,35 +84,33 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
9184
});
9285
};
9386

94-
handleSubmit = (e: React.FormEvent) => {
87+
handleSubmit = (e: React.FormEvent<HTMLFormElement>, id: string) => {
9588
e.preventDefault();
96-
const target = e.target as HTMLInputElement;
97-
const id = target?.id || undefined;
98-
const { updatePortfolio } = this.props;
99-
let { portfolio } = this.state;
100-
if (id) {
101-
portfolio = portfolio.map(item =>
102-
item.id === id ? { ...item, isSaved: true } : item
103-
);
104-
this.setState({
105-
portfolio
106-
});
89+
this.updateItem(id);
90+
};
91+
92+
updateItem = (id: string) => {
93+
const { portfolio, unsavedItemId } = this.state;
94+
if (unsavedItemId === id) {
95+
this.setState({ unsavedItemId: null });
10796
}
108-
return updatePortfolio({ portfolio });
97+
this.props.updatePortfolio({ portfolio });
10998
};
11099

111100
handleAdd = () => {
112-
return this.setState(state => ({
113-
portfolio: [createEmptyPortfolio(), ...state.portfolio]
101+
const item = createEmptyPortfolioItem();
102+
this.setState(state => ({
103+
portfolio: [item, ...state.portfolio],
104+
unsavedItemId: item.id
114105
}));
115106
};
116107

117108
handleRemoveItem = (id: string) => {
118-
return this.setState(
109+
this.setState(
119110
state => ({
120111
portfolio: state.portfolio.filter(p => p.id !== id)
121112
}),
122-
() => this.handleSubmit(mockEvent as FormEvent<Element>)
113+
() => this.updateItem(id)
123114
);
124115
};
125116

@@ -131,7 +122,7 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
131122
return false;
132123
}
133124
const edited = find(portfolio, createFindById(id));
134-
return isEqual(omit(original, ['isSaved']), omit(edited, ['isSaved']));
125+
return isEqual(original, edited);
135126
};
136127

137128
// TODO: Check if this function is required or not
@@ -211,9 +202,9 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
211202
}
212203

213204
renderPortfolio = (
214-
portfolio: PortfolioValues,
205+
portfolio: PortfolioItem,
215206
index: number,
216-
arr: PortfolioValues[]
207+
arr: PortfolioItem[]
217208
) => {
218209
const { t } = this.props;
219210
const { id, title, description, url, image } = portfolio;
@@ -230,7 +221,7 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
230221
return (
231222
<div key={id}>
232223
<FullWidthRow>
233-
<form id={id} onSubmit={this.handleSubmit}>
224+
<form onSubmit={e => this.handleSubmit(e, id)}>
234225
<FormGroup
235226
controlId={`${id}-title`}
236227
validationState={
@@ -327,7 +318,7 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
327318

328319
render() {
329320
const { t } = this.props;
330-
const { portfolio = [] } = this.state;
321+
const { portfolio = [], unsavedItemId } = this.state;
331322
return (
332323
<section id='portfolio-settings'>
333324
<SectionHeader>{t('settings.headings.portfolio')}</SectionHeader>
@@ -338,11 +329,16 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
338329
</FullWidthRow>
339330
<FullWidthRow>
340331
<ButtonSpacer />
341-
<PreventableButton
342-
handleAdd={this.handleAdd}
343-
t={t}
344-
portfolio={portfolio}
345-
/>
332+
<Button
333+
block={true}
334+
bsSize='lg'
335+
bsStyle='primary'
336+
disabled={unsavedItemId !== null}
337+
onClick={this.handleAdd}
338+
type='button'
339+
>
340+
{t('buttons.add-portfolio')}
341+
</Button>
346342
</FullWidthRow>
347343
<Spacer size={2} />
348344
{portfolio.length ? portfolio.map(this.renderPortfolio) : null}

0 commit comments

Comments
 (0)