Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anibel - Edges Inspiration Board #19

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
5,004 changes: 2,564 additions & 2,440 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
"eject": "react-scripts eject"
},
"devDependencies": {
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1",
"enzyme-to-json": "^3.3.5",
"gh-pages": "^1.2.0"
},
"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
},
"homepage": "http://adagold.github.io/inspiration-board"
}
54 changes: 53 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,69 @@
import React, { Component } from 'react';
import './App.css';
import Board from './components/Board';
// import axios from 'axios';

class App extends Component {
constructor() {
super();

this.state = {
value: 'Anibel',
boards: []
};
}

// componentDidMount() {
// console.log("Grabbing a board");
// axios.get("https://inspiration-board.herokuapp.com/boards")
// .then((response) => {
// const names = response.data.map((board) => {
// return board.board.name
// })
// this.setState({
// boards: names,
// });
// })
// .catch((error) => {
// this.setState({
// error: error.message
// });
// });
// }
//
// getBoardOptions = () => {
// return this.state.boards.map((board, i) => {
// return <option key={i} value={board}>{board}</option>
// });
// };
//
// changeBoardName = (event) => {
// this.setState({
// value: event.target.value
// });
// }


render() {
return (
<section>
<header className="header">
<h1 className="header__h1"><span className="header__text">Inspiration Board</span></h1>
</header>

<section>
<p>Inspiration board for {this.state.value}</p>
</section>
{// <select
// onChange={this.changeBoardName}
// value={this.state.value}>
// {this.getBoardOptions()}
// </select>
}

<Board
url="https://inspiration-board.herokuapp.com/boards/"
boardName={`Ada-Lovelace`}
boardName={this.state.value}
/>
</section>
);
Expand Down
1 change: 1 addition & 0 deletions src/App.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('App', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
console.log(div.innerHTML);
ReactDOM.unmountComponentAtNode(div);
});

Expand Down
82 changes: 74 additions & 8 deletions src/components/Board.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,95 @@ import axios from 'axios';
import './Board.css';
import Card from './Card';
import NewCardForm from './NewCardForm';
import CARD_DATA from '../data/card-data.json';
// import CARD_DATA from '../data/card-data.json';

class Board extends Component {
constructor() {
super();
constructor(props) {
super(props);

this.state = {
cards: [],
url: this.props.url,
boardName: this.props.boardName,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the URL and board name aren't going to be changed within this component, you don't need to keep them in state. If you read them from props throughout the app, it will be more clear that they're read-only.

cards: []
};
}

componentDidMount() {
const GET_INSPO_CARDS_URL = this.props.url + '/' + this.props.boardName + '/cards';

axios.get(GET_INSPO_CARDS_URL)
.then((response) => {
this.setState({
cards: response.data,
});
})
.catch((error) => {
this.setState({
error: error.message
});
});
}

deleteCard = (cardId) => {
const deleteURL = "https://inspiration-board.herokuapp.com/cards/" + cardId;

axios.delete(deleteURL)
.then((response) => {
const newCards = [...this.state.cards];
const index = newCards.findIndex(content => content.card.id === cardId);
newCards.splice(index, 1);

this.setState({
cards: newCards
});
})
.catch((error) => {
this.setState({
error: error.message
});
});
};

addCard = (cardData) => {
const POST_INSPO_CARDS_URL = this.props.url + '/' + this.props.boardName + '/cards';
axios.post(POST_INSPO_CARDS_URL, cardData)
.then((response) => {
cardData.id = response.data.card.id;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you've kept all the API interaction logic in one component - the callbacks are a little more complex, but I would say it makes the app as a whole much easier to comprehend. Whether or not you intended it, this is a great example of the container component pattern well-applied.


const updatedCardDeck = [...this.state.cards, {card: cardData}];

this.setState({
cards: updatedCardDeck
})

})
.catch((error) => {
this.setState({
error: error.message
});
});
};

render() {
const cardList = this.state.cards.map((cardContainer, i) => {
return <Card key={i}
card={cardContainer.card}
deleteCardCallback={this.deleteCard} />
});

return (
<div>
Board
<div className="board">
<NewCardForm
addCardCallback={this.addCard}/>
{ cardList }
</div>
)
}

}

Board.propTypes = {

url: PropTypes.string.isRequired,
boardName: PropTypes.string.isRequired
};

export default Board;
Empty file removed src/components/Board.test.js
Empty file.
18 changes: 16 additions & 2 deletions src/components/Card.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,30 @@ import './Card.css';

class Card extends Component {
render() {
const { id, text, emoji } = this.props.card;
const emojiLib = require("emoji-dictionary");

return (
<div className="card">
Card
<div className="card__content">
{ text &&
<h1 className='card__content-text'>{text}</h1>}
{ emoji &&
<div className='card__content-emoji'>{emojiLib.getUnicode(emoji)}</div>}
<div>
<button
className='card__delete'
onClick={() => this.props.deleteCardCallback(id)}>Delete</button>
</div>
</div>
</div>
)
}
}

Card.propTypes = {

card: PropTypes.object,
deleteCardCallback: PropTypes.func
};

export default Card;
87 changes: 87 additions & 0 deletions src/components/NewCardForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,90 @@ import emoji from 'emoji-dictionary';
import './NewCardForm.css';

const EMOJI_LIST = ["", "heart_eyes", "beer", "clap", "sparkling_heart", "heart_eyes_cat", "dog"]

class NewCardForm extends Component{

constructor() {
super();

this.state = {
text: '',
emoji: ''
}
}

onInputChange = (event) => {
const newState = {};

newState[event.target.name] = event.target.value;

this.setState(newState);
}

resetState = () => {
this.setState({
text: '',
emoji: ''

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of a helper method here. Could you call this from your constructor instead of repeating this work?

});
}

onFormSubmit = (event) => {
event.preventDefault();
// const { text, emoji} = this.state;
this.props.addCardCallback(this.state);
this.resetState();
}

render() {
const emojiLib = require("emoji-dictionary");
const emojiOptions = EMOJI_LIST.map((emoji, i) => {
return (
<option
key={i}
value={emoji}>
{emojiLib.getUnicode(emoji)}
</option>)
});

return (
<section className="new-card-form">
<section className="new-card-form__header">
<h1>Send an Inspiration!</h1>
</section>
<form
className="new-card-form__form"
onSubmit={this.onFormSubmit}>


<label htmlFor="Text" className="new-card-form__form-label">
</label>
<textarea
name="text"
type="textarea"
value={this.state.text}
onChange={this.onInputChange}
className="new-card-form__form-textarea"/>

<select
name="emoji"
className="new-card-form__form-select"
onChange={this.onInputChange}
value={this.state.emoji}>
{emojiOptions}
</select>
<input type="submit" value="Submit" className="new-card-form__form-button" />


</form>

</section>
);
}
}


NewCardForm.propTypes = {
addCardCallback: PropTypes.func,
};

export default NewCardForm;
Empty file removed src/components/NewCardForm.test.js
Empty file.
23 changes: 23 additions & 0 deletions src/components/test/Board.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import Board from '../Board';
import { shallow } from 'enzyme';

describe('Board', () => {
test('that it matches an existing snapshot', () => {
const wrapper = shallow(
<Board
url='test url'
boardName='test board name'/>);

expect(wrapper).toMatchSnapshot();
});

test('that it renders without crashing even with no url and boardName', () => {
const wrapper = shallow(
<Board
url=''
boardName=''/>);

expect(wrapper).toMatchSnapshot();
});
});
29 changes: 29 additions & 0 deletions src/components/test/Card.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import Card from '../Card';
import { shallow } from 'enzyme';

describe('Card', () => {
test('that it renders even when passed an empty object prop', () => {
const wrapper = shallow(
<Card
card={{}}/>);

expect(wrapper).toMatchSnapshot();
});

test('that it renders when passed an object prop with text and emoji fields', () => {
const wrapper = shallow(
<Card
card={
{
card: {
text: "test inspirational text",
emoji: "dog"
}
}
}
/>);

expect(wrapper).toMatchSnapshot();
});
});
14 changes: 14 additions & 0 deletions src/components/test/NewCardForm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import NewCardForm from '../NewCardForm';
import { shallow } from 'enzyme';

describe('NewCardForm', () => {
test('that it renders when passed an empty function', () => {
const wrapper = shallow(
<NewCardForm
addCardCallback={() => {}}/>);

expect(wrapper).toMatchSnapshot();
});

});
Loading