Skip to content
This repository was archived by the owner on Jun 28, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18,085 changes: 18,052 additions & 33 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"escape-string-regexp": "^1.0.5",
"form-serialize": "^0.7.2",
"prop-types": "^15.6.2",
"prop-types": "^15.7.2",
"react": "^16.6.3",
"react-router-dom": "^4.3.1",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.1",
"sort-by": "^1.2.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down
69 changes: 68 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,77 @@
import React, { Component } from 'react';
import ListContacts from './ListContacts'
import * as ContactsAPI from './utils/ContactsAPI'
import CreateContact from './CreateContact'
import { Route } from 'react-router-dom'

class App extends Component {
state = {
contacts: [],
// screen: 'list'
}

componentDidMount() {
ContactsAPI.getAll()
.then((contacts) => {
this.setState(() => ({
contacts
}))
})
}
removeContact = contact => {
this.setState((currentState) => ({
contacts: currentState.contacts.filter((c) => {
return c.id !== contact.id
})
}))
ContactsAPI.remove(contact)

createContact = (contact) => {
ContactsAPI.create(contact)
.then((contact)=> {
this.setState((currentState) => ({
contacts: currentState.contacts.concat([contact])
}))
})
}
}
render() {
return (
<div>
Hello World
<Route exact path='/' render={()=> (
<ListContacts
contacts={this.state.contacts}
onDeleteContact={this.removeContact}
// onNavigate={()=> {
// this.setState(()=>({
// screen: 'create'
// }))
// }}
/>
)} />
{/* <Route path='/create' component={CreateContact} /> */}
{/* {this.state.screen === 'list' && (
<ListContacts
contacts={this.state.contacts}
onDeleteContact={this.removeContact}
onNavigate={()=> {
this.setState(()=>({
screen: 'create'
}))
}}
/>
)}
{this.state.screen === 'create' && (
<CreateContact />
)} */}
<Route path='/create' render={({history}) => (
<CreateContact
onCreateContact={(contact) => {
this.createContact(contact)
history.push('/')
}}
/>
)} />
</div>
);
}
Expand Down
41 changes: 41 additions & 0 deletions src/CreateContact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import ImageInput from './ImageInput'
import serializeForm from 'form-serialize'

class CreateContact extends Component {
handleSubmit = (e) => {
e.preventDefault()
const values = serializeForm(e.target, { hash: true })

if (this.props.onCreateContact) {
this.props.onCreateContact(values)
}
}

render() {
return (
<div>
<Link
className='close-create-contact'
to='/'>
Close
</Link>
<form onSubmit={this.handleSubmit} className='create-contact-form'>
<ImageInput
className='create-contact-avatar-input'
name='avatarURL'
maxHeight={64}
/>
<div className='create-contact-details'>
<input type='text' name='name' placeholder='Name'/>
<input type='text' name='handle' placeholder='Handle'/>
<button>Add Contact</button>
</div>
</form>
</div>
)
}
}

export default CreateContact
3 changes: 3 additions & 0 deletions src/ImageInput.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// ****** NOTES ******
// The ImageInput component is a custom <input> that dynamically reads and resizes image files before submitting them to the server as data URLs. It also shows a preview of the image. We chose to give this component to you rather than build it ourselves because it contains features related to files and images on the web that aren't crucial to your education in this context. If you're curious, feel free to dive into the code, but know it's not a requirement.

import React from 'react'
import PropTypes from 'prop-types'

Expand Down
129 changes: 129 additions & 0 deletions src/ListContacts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import PropTypes from 'prop-types'

class ListContacts extends Component {
static propTypes = {
contacts: PropTypes.array.isRequired,
onDeleteContact: PropTypes.func.isRequired
}

state = {
query: ''
}

updateQuery = (query) => {
this.setState(()=> ({
query: query.trim()
}))
}

clearQuery = () => {
this.updateQuery('')
}

render() {
const { query } = this.state
const { contacts, onDeleteContact, onNavigate } = this.props

// sticking the input field onto components state to trigger re-render on input then filter that input to perform working search
const showingContacts = query === ''
? contacts
: contacts.filter(contact => (
contact.name.toLowerCase().includes(query.toLowerCase())
))

return (
<div className = 'list-contacts'>
{/* {JSON.stringify(this.state)} */}
<div className= 'list-contacts-top'>
<input
className='search-contacts'
type="text"
placeholder='Search Contacts'
value={query}
onChange={(event)=> {this.updateQuery(event.target.value)}}
// To recap how user input affects the ListContacts component's own state:
// 1. The user enters text into the input field.
// 2. The onChange event listener invokes the updateQuery() function.
// 3. updateQuery() then calls setState(), merging in the new state to update the component's internal state.
// 4. Because its state has changed, the ListContacts component re-renders.
/>
<Link
// href='#create'
to='/create'
// onClick={onNavigate}
className='add-contact'
>Add contact</Link>
</div>

{/* When a user is typing into input field aka query, the contacts.length will be different, in other words, we want to know if we're filtering out any contacts, if so, display the bar with showing # out of how many contacts */}
{/* using logical && for if condition below */}
{showingContacts.length !== contacts.length && ( // true && ... do something if true
<div className='showing-contacts'>
<span>Now showing {showingContacts.length} of {contacts.length}</span>
<button onClick={this.clearQuery}>Show All</button>
</div>
)}

<ol className='contacts-list'>
{showingContacts.map( contact => (
(
<li key={contact.id} className='contact-list-item'>
<div
className='contact-avatar'
style={{ //first bracket says lets go into JS, 2nd bracket makes it an object
backgroundImage: `url(${contact.avatarURL})`
}}
></div>
<div className='contact-details'>
<p>{contact.name}</p>
<p>{contact.handle}</p>
</div>
<button
onClick={() => onDeleteContact(contact)}
className='contact-remove'>
Remove
</button>
</li>
)
))}
</ol>
</div>
)
}
}
// const ListContacts = (props) => {
// return (
// <ol className='contacts-list'>
// {props.contacts.map( contact => (
// (
// <li key={contact.id} className='contact-list-item'>
// <div
// className='contact-avatar'
// style={{ //first bracket says lets go into JS, 2nd bracket makes it an object
// backgroundImage: `url(${contact.avatarURL})`
// }}
// ></div>
// <div className='contact-details'>
// <p>{contact.name}</p>
// <p>{contact.handle}</p>
// </div>
// <button
// onClick={() => props.onDeleteContact(contact)}
// className='contact-remove'>
// Remove
// </button>
// </li>
// )
// ))}
// </ol>
// )
// }

// ListContacts.propTypes = {
// contacts: PropTypes.array.isRequired,
// onDeleteContact: PropTypes.func.isRequired
// }

export default ListContacts
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('root'));
registerServiceWorker();
31 changes: 31 additions & 0 deletions src/utils/ContactsAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const api = process.env.REACT_APP_CONTACTS_API_URL || 'http://localhost:5001'

let token = localStorage.token

if (!token)
token = localStorage.token = Math.random().toString(36).substr(-8)

const headers = {
'Accept': 'application/json',
'Authorization': token
}

export const getAll = () =>
fetch(`${api}/contacts`, { headers })
.then(res => res.json())
.then(data => data.contacts)

export const remove = (contact) =>
fetch(`${api}/contacts/${contact.id}`, { method: 'DELETE', headers })
.then(res => res.json())
.then(data => data.contact)

export const create = (body) =>
fetch(`${api}/contacts`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}).then(res => res.json())
Loading