diff --git a/README.md b/README.md index 1eadfdd..9029a20 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # Contact List A starter repo for the ACA full stack contact list project. +This is for Danny Solis diff --git a/db.json b/db.json index 85304a7..445b63d 100644 --- a/db.json +++ b/db.json @@ -1,34 +1,29 @@ { "contacts": [ { - "_id": 1, - "name": "Dale Cooper", - "occupation": "FBI Agent", - "avatar": "https://upload.wikimedia.org/wikipedia/en/5/50/Agentdalecooper.jpg" + "name": "Soap McTavish", + "avatar": "http://vignette4.wikia.nocookie.net/callofduty/images/b/b7/Soap_MW3_model.png/revision/latest?cb=20120122010801", + "occupation": "British SAS", + "_id": 6 }, { - "_id": 2, - "name": "Spike Spiegel", - "occupation": "Bounty Hunter", - "avatar": "http://vignette4.wikia.nocookie.net/deadliestfiction/images/d/de/Spike_Spiegel_by_aleztron.jpg/revision/latest?cb=20130920231337" + "name": "Lilith", + "avatar": "http://vignette3.wikia.nocookie.net/talesfromtheborderlands/images/7/7e/Lilith.png/revision/latest?cb=20150722060031", + "occupation": "Siren", + "_id": 8 }, { - "_id": 3, - "name": "Wirt", - "occupation": "adventurer", - "avatar": "http://66.media.tumblr.com/5ea59634756e3d7c162da2ef80655a39/tumblr_nvasf1WvQ61ufbniio1_400.jpg" + "name": "Master Chief", + "avatar": "http://img.mota.ru/upload/wallpapers/2011/05/30/10/04/25864/mota_ru_1053028-preview.jpg", + "occupation": "Spartan", + "_id": 10 }, { - "_id": 4, - "name": "Michael Myers", - "occupation": "Loving little brother", - "avatar": "http://vignette2.wikia.nocookie.net/villains/images/e/e3/MMH.jpg/revision/latest?cb=20150810215746" - }, - { - "_id": 5, - "name": "Dana Scully", - "occupation": "FBI Agent", - "avatar": "https://pbs.twimg.com/profile_images/718881904834056192/WnMTb__R.jpg" + "name": "Cortana", + "avatar": "https://content.halocdn.com/media/Default/encyclopedia/characters/cortana/cortana-square-542x542-0234af18940e48b1827c1423b5a8ed40-620b1e678f634dd099bba9f3533c560d.jpg", + "occupation": "AI", + "_id": 9 } - ] -} + ], + "favorites": [] +} \ No newline at end of file diff --git a/db.json.ORIGINAL b/db.json.ORIGINAL new file mode 100644 index 0000000..85304a7 --- /dev/null +++ b/db.json.ORIGINAL @@ -0,0 +1,34 @@ +{ + "contacts": [ + { + "_id": 1, + "name": "Dale Cooper", + "occupation": "FBI Agent", + "avatar": "https://upload.wikimedia.org/wikipedia/en/5/50/Agentdalecooper.jpg" + }, + { + "_id": 2, + "name": "Spike Spiegel", + "occupation": "Bounty Hunter", + "avatar": "http://vignette4.wikia.nocookie.net/deadliestfiction/images/d/de/Spike_Spiegel_by_aleztron.jpg/revision/latest?cb=20130920231337" + }, + { + "_id": 3, + "name": "Wirt", + "occupation": "adventurer", + "avatar": "http://66.media.tumblr.com/5ea59634756e3d7c162da2ef80655a39/tumblr_nvasf1WvQ61ufbniio1_400.jpg" + }, + { + "_id": 4, + "name": "Michael Myers", + "occupation": "Loving little brother", + "avatar": "http://vignette2.wikia.nocookie.net/villains/images/e/e3/MMH.jpg/revision/latest?cb=20150810215746" + }, + { + "_id": 5, + "name": "Dana Scully", + "occupation": "FBI Agent", + "avatar": "https://pbs.twimg.com/profile_images/718881904834056192/WnMTb__R.jpg" + } + ] +} diff --git a/package.json b/package.json index ea83a61..949ac7a 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "react-scripts": "0.7.0" }, "dependencies": { + "axios": "^0.15.3", "foreman": "^2.0.0", "json-server": "^0.9.4", "react": "^15.3.2", - "react-dom": "^15.3.2" + "react-dom": "^15.3.2", + "react-router": "^4.0.0-beta.6" }, "scripts": { "start": "nf start", @@ -22,5 +24,6 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" - } + }, + "proxy": "http://localhost:4000" } diff --git a/public/index.html b/public/index.html index aab5e3b..ca7fe53 100644 --- a/public/index.html +++ b/public/index.html @@ -27,5 +27,6 @@ To begin the development, run `npm start`. To create a production bundle, use `npm run build`. --> + diff --git a/src/Activity.js b/src/Activity.js new file mode 100644 index 0000000..8b1e55b --- /dev/null +++ b/src/Activity.js @@ -0,0 +1,16 @@ +import React from 'react'; + +const Activity = props => { + return ( +
+

Recent Activity

+ +
+ ) +} + +export default Activity; diff --git a/src/App.js b/src/App.js index b25a228..9b1706d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,20 @@ -import React, { Component } from 'react'; +import React, {Component} from "react"; +import Profile from "./Profile"; +import Contacts from "./Contacts"; +import {BrowserRouter, Route, Switch} from "react-router-dom"; class App extends Component { render() { return ( -
-

- Contact List! -

-
+ +
+ + + +

Not Found!

} /> +
+
+
); } } diff --git a/src/Contact.js b/src/Contact.js new file mode 100644 index 0000000..26d2321 --- /dev/null +++ b/src/Contact.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +class Contact extends React.Component { + constructor() { + super(); + + this.state = { + confirmDelete: false + }; + } + + render() { + return ( + +
  • +
    + avatar +
    + {this.state.confirmDelete ?
    +

    Delete this contact? this.props.handleDelete(this.props.id, this.props.listName.toString().toLowerCase())}>Yes / this.setState({confirmDelete: false})}>No

    +
    :
    +

    {this.props.name}

    + {this.props.occupation} +
    + } + +
    this.props.handleFav(this.props.id)}> + +
    +
    this.setState({confirmDelete: true})}> + +
    +
  • + + ); + } +} + +Contact.propTypes = { + id: React.PropTypes.number.isRequired, + avatar: React.PropTypes.string.isRequired, + name: React.PropTypes.string.isRequired, + occupation: React.PropTypes.string.isRequired, +} + +export default Contact; diff --git a/src/ContactForm.js b/src/ContactForm.js new file mode 100644 index 0000000..e366441 --- /dev/null +++ b/src/ContactForm.js @@ -0,0 +1,90 @@ +import React from 'react'; + +class ContactForm extends React.Component { + constructor() { + super(); + + this.state = { + name: '', + avatar: '', + occupation: '' + } + } + + handleNameChange(event) { + this.setState({ + name: event.target.value + }); + } + + handleOccupationChange(event) { + this.setState({ + occupation: event.target.value + }); + } + + handleAvatarChange(event) { + this.setState({ + avatar: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + + const { name, avatar, occupation } = this.state; + this.props.onSubmit({ name, avatar, occupation }, 'contacts'); + + this.setState({ + name: '', + avatar: '', + occupation: '' + }); + } + + render() { + return ( +
    + this.props.hideForm()}> +
    + + + + + + + + +
    +
    + ); + } +} + +ContactForm.propTypes = { + onSubmit: React.PropTypes.func.isRequired +}; + +export default ContactForm; diff --git a/src/ContactList.js b/src/ContactList.js new file mode 100644 index 0000000..d6516e5 --- /dev/null +++ b/src/ContactList.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Contact from './Contact'; + +const ContactList = props => { + return ( +
    + +
    + ); +} + +export default ContactList; diff --git a/src/Contacts.js b/src/Contacts.js new file mode 100644 index 0000000..45ba48e --- /dev/null +++ b/src/Contacts.js @@ -0,0 +1,182 @@ +import React, {Component} from 'react'; +import ContactList from './ContactList'; +import SearchBar from './SearchBar'; +import ContactForm from './ContactForm'; +import Activity from './Activity'; +import axios from 'axios'; + +class App extends Component { + constructor() { + super(); + + this.state = { + searchText: '', + favorites: [], + contacts: [], + activity: [], + contactFormVisible: false + }; + } + + componentDidMount() { + axios.get('/contacts') + .then(resp => { + this.setState({ + searchText: this.state.searchText, + contacts: resp.data + }) + }) + .catch(err => { + console.log(`Error! ${err}`) + }) + axios.get('/favorites') + .then(resp => { + this.setState({ + favorites: resp.data + }) + }) + .catch(err => { + console.log(`Error! ${err}`) + }) + } + + handleChange(event) { + this.setState({ + searchText: event.target.value + }); + } + + addToFavorites(_id) { + let addCon = this.state.contacts.filter(contact => contact._id === _id); + //let newActivity = this.state.activity.push(addCon.name + " added to favorites"); + console.log(addCon); + + this.handleAddFavorite(...addCon); + this.handleDeleteContact(_id); + } + + removeFromFavorite(_id) { + let remCon = this.state.favorites.filter(contact => contact._id === _id); + + console.log(remCon); + + this.handleAddContact(...remCon); + this.handleDeleteFavorite(_id); + } + + getFilteredContacts(list) { + const TERM = this.state.searchText.trim().toLowerCase(); + const CONTACTS = list; + + if (!TERM) { + return CONTACTS; + } + + return CONTACTS.filter(contact => { + return contact.name.toLowerCase().indexOf(TERM) >= 0; + }); + } + + handleAddContact(attributes) { + axios.post('/contacts', attributes) + .then((resp) => { + + console.log(resp.data.name); + + this.setState({ + contacts: [...this.state.contacts, resp.data], + activity: [...this.state.activity, resp.data.name + " added to contacts"] + }); + }) + .catch(err => console.log(err)); + } + + handleAddFavorite(attributes) { + axios.post('/favorites', attributes) + .then((resp) => { + + console.log(resp.data.name); + + this.setState({ + favorites: [...this.state.favorites, resp.data], + activity: [...this.state.activity, resp.data.name + " added to favorites"] + }); + }) + .catch(err => console.log(err)); + } + + handleDeleteContact(_id) { + axios.delete(`/contacts/${_id}`) + .then((resp) => { + const newContacts = this.state.contacts.filter(contact => contact._id !== _id); + + this.setState({ + contacts: newContacts + }); + }) + .catch(err => console.log(`ERROR! ${err}`)); + } + + handleDeleteFavorite(_id) { + axios.delete(`/favorites/${_id}`) + .then(() => { + const newFavorites = this.state.favorites.filter(contact => contact._id !== _id); + + this.setState({ + favorites: newFavorites + }); + }) + .catch(err => console.log(`ERROR! ${err}`)); + } + + showContactForm() { + this.setState({ + contactFormVisible: true + }); + } + + hideContactForm() { + this.setState({ + contactFormVisible: false + }); + } + + render() { + return ( +
    +
    +

    React Contact List

    +
    + + +
    + + { this.state.contactFormVisible ? : null } +
    + + +
    +
    + ); + } +} + +export default App; diff --git a/src/Profile.js b/src/Profile.js new file mode 100644 index 0000000..14cc5d1 --- /dev/null +++ b/src/Profile.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react'; +import axios from 'axios'; + +class Profile extends Component { + constructor() { + super(); + + this.state = { + contact: null + }; + } + + componentDidMount() { + axios.get(`/contacts/${this.props.match.params.id}`) + .then(resp => { + this.setState({ + contact: resp.data + }) + }) + .catch(err => console.log(`Error! ${err}`)); + } + + renderProfile() { + return ( +
    +
    + avatar +
    +
    +

    Name: {this.state.contact.name}

    + Occupation: {this.state.contact.occupation} +
    +
    + ); + } + + render() { + if (!this.state.contact) { + return

    Loading...

    + } + + return this.renderProfile(); + } +} + +export default Profile; diff --git a/src/SearchBar.js b/src/SearchBar.js new file mode 100644 index 0000000..70ee32c --- /dev/null +++ b/src/SearchBar.js @@ -0,0 +1,36 @@ +import React from 'react'; + +class SearchBar extends React.Component { + constructor() { + super(); + + this.state = { + value: '' + }; + } + + handleChange(event) { + this.setState({ + value: event.target.value + }); + } + + render() { + return ( + this.props.onChange(event)} + /> + ); + } +} + +SearchBar.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +export default SearchBar; diff --git a/src/index.css b/src/index.css index 80e4b4a..bad0a47 100644 --- a/src/index.css +++ b/src/index.css @@ -6,34 +6,79 @@ html, body, #root, .router { body { margin: 0; padding: 0; - font-family: sans-serif; + font-family: Arial; } .App { - text-align: center; - width: 500px; - margin: 0 auto; - padding-top: 50px; + /*text-align: center; + width: 1000px; + margin: 0 auto;*/ + padding: 50px 50px; +} + +.App h1 { + box-sizing: border-box; + display: inline-block; + margin-right: 20px; +} + +.activity-log { + width: 45%; + margin-bottom: 20px; +} + +.activity-items { + height: 60px; + width: 90%; + border: 1px solid #ddd; + padding: 10px 5px; + border-radius: 5px; + overflow-y: scroll; +} + +.activity-items li { + padding: 3px 0; + border-bottom: 1px solid #ddd; } .search-bar { height: 35px; - width: 100%; + width: 40%; border: 1px solid #ddd; border-radius: 5px; font-size: 18px; padding: 0 10px; box-sizing: border-box; + display: inline-block; +} + +#show-form { + height: 35px; + width: 35px; + text-align: center; + border-radius: 5px; + background-color: #39ac39; + display: inline-block; +} + +#show-form span { + line-height: 35px; +} + +.contact-container { + text-align: center; + margin: 5 auto; + float: left; } .contact-list { + margin: 0 20px; padding: 0; } .contact { list-style-type: none; display: flex; - display: flex; padding: 15px 0; border-bottom: 1px solid #ddd; } @@ -66,13 +111,31 @@ body { width: auto; } -.contact .contact-info { +.contact .contact-info, .contact .contact-alert { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; - padding-left: 35px; + padding: 0 35px; +} + +.contact .contact-alert { + background-color: #39ac39; + border-radius: 5px; +} + +.contact .fa { + color: #39ac39; +} + +.add-start { + height: 20px; + width: 50px; +} + +.add-start:hover, #form-container span:hover, .new-contact-form input[type="submit"]:hover, #show-form:hover, .contact-alert span { + cursor: pointer; } .profile { @@ -99,29 +162,39 @@ body { width: auto; } -.new-contact-form { - margin-bottom: 10px; +#form-container { + width: 50%; + border: 1px solid #ddd; + border-radius: 5px; + margin-bottom: 20px; + position: relative; } -.new-contact-form label { - width: 100%; - display: inline-block; - text-align: left; +#form-container span { + position: absolute; + top: 10px; + right: 10px; + color: #39ac39; +} + +.new-contact-form { + margin: 10px 10px; } .new-contact-form input { margin: 5px 0; height: 35px; - width: 100%; + width: 80%; border: 1px solid #ddd; border-radius: 5px; font-size: 18px; padding: 0 10px; box-sizing: border-box; + display: block; } .new-contact-form input[type="submit"] { - background: black; + background: #39ac39; color: white; } diff --git a/src/index.js b/src/index.js index 54c5ef1..9eec847 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +//version 2 import React from 'react'; import ReactDOM from 'react-dom'; import App from './App';