<repo_name>saqemlas/NewsApp<file_sep>/src/components/Header.js
import React from 'react'
import { NavLink } from 'react-router-dom'
import { Transition } from 'semantic-ui-react'

const Header = (props) => {
  return (
      <NavLink exact to="/about" onClick={props.onClick}>
        <div className="ui centered grid" style={{margin: 10}}>
          <h2 className="ui icon header">
            <Transition animation={'shake'} duration={500} visible={props.visible}>
              <i className="globe icon" />
            </Transition>
            <div className="content">
              Global News
              <div className="sub header">
                For all your current information needs
              </div>
            </div>
          </h2>
        </div>
      </NavLink>
)}

export default Header
<file_sep>/src/components/topheadlines/TopHeadlinesList.js
import React from 'react'
import HeadlineCard from './HeadlineCard'

const TopHeadlinesList = (props) => {
  return (
    <div className="ui centered equal width grid" style={{ margin: 10 }} >
      {props.headlines.map((headline, index) => (
        <HeadlineCard
          key={index}
          headline={headline}
          selectHeadline={props.selectHeadline}
        />
      ))}
    </div>
)}

export default TopHeadlinesList
<file_sep>/src/components/sources/SourceList.js
import React from 'react'
import SourceCard from './SourceCard'

const SourceList = (props) => {
  return (
    <div className="ui centered equal width grid" style={{ margin: 10 }}>
      {props.sources.map((source, index) => (
        <SourceCard
          key={index}
          source={source}
        />
      ))}
    </div>
)}

export default SourceList
<file_sep>/src/components/InitialSearch.js
import React from 'react'

const InitialSearch = (props) => {
  return (
    <>
      <div className="ui labeled input" style={{ width: '50%' }}>
        <div className="ui label">
          <i className="quote left icon" />
        </div>
        <input
          className="prompt"
          type="text"
          name="initialSearch"
          placeholder="Search..."
          value={props.searchTerm}
          onChange={props.updateSearchTerm}
          style={{ fontWeight: 'bold' }}
        />
        <div className="ui label">
          <i className="quote right icon" />
        </div>
        <button
          className="ui teal right labeled icon button"
          onClick={() => props.fetchData()}>
          <i className="sync alternate icon" />
          Initial Search
        </button>
      </div>
    </>
)}

export default InitialSearch
<file_sep>/README.md
<a href="https://newsapp.saqemlas.now.sh/"> Live Demo </a>

<a href="https://github.com/saqemlas/NewsApp-api"> Global News API </a>

# Global News

An application for people to read and share news articles.

Users are able to sign in, search news articles by sources, top headlines, and all news. Users are then able to share news articles via various social media mediums and save articles to their account.

User and article information stored in mySQL database, with email/password authentication to implement Sign Up and Log In for users. React Router navigation to allow users to bookmark components, and React Pose animation on components for added user experience.

<table>
  <tr>
    <th>
      Frontend
    </th>
    <th>
      Backend
    </th>
  </tr>

  <tr>
    <td>
      React - version 16.8.6
    </td>
    <td>
      Express - version 4.17.1
    </td>
  </tr>

  <tr>
    <td>
      React Router - version 5.0.1
    </td>
    <td>
      MySQL2 - version 1.6.5
    </td>
  </tr>

  <tr>
    <td>
      React Pose - version 4.0.8
    </td>
    <td>
      Sequelize - version 5.8.9
    </td>
  </tr>

  <tr>
    <td>
      Semantic UI - version 0.87.1
    </td>
    <td>
      JSONwebtokens - version 8.5.1
    </td>
  </tr>

</table>
<file_sep>/src/components/everything/EverythingList.js
import React from 'react'
import EverythingCard from './EverythingCard'

const EverythingList = (props) => {
  return (
    <div className="ui centered equal width grid" style={{ margin: 10 }} >
      {props.everything.map((article, index) => (
        <EverythingCard
          key={index}
          article={article}
          selectArticle={props.selectArticle}
        />
      ))}
    </div>
)}

export default EverythingList
<file_sep>/src/components/everything/EverythingCard.js
import React from 'react'
import { Link } from 'react-router-dom'
import posed from 'react-pose'

const Button = posed.div({
  pressable: true,
  init: { scale: 1 },
  press: { scale: 0.8 }
})

const CardContainer = posed.div({
  hoverable: true,
  init: {
    scale: 0.95,
  },
  hover: {
    scale: 1.05,
  },
})

const EverythingCard = (props) => {

const datetimeFormat = () => {
  const data = props.article.publishedAt
  const datetime = data.slice(0, -1).split('T')
  const result = 'At : ' + datetime[1] + ', ' + datetime[0]
    return result
}
  return (
    <CardContainer className="box">
    <div className="ui card" style={{ margin: 10}}>
      <div className="image">
        <img
          alt={props.article.title}
          src={props.article.urlToImage || "https://www.albertadoctors.org/images/ama-master/feature/News.jpg"}
        />
      </div>
      <div className="content">
        <div className="header">
          {props.article.title}
        </div>
        <div className="meta">
          {props.article.source.name || "Unknown"}
        </div>
        <div className="description" style={{ fontStyle: 'italic' }}>
          {datetimeFormat()}
        </div>
      </div>
      <div className="extra content">
        <div className="ui centered grid">
        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <Link
            key={props.article.title}
            to={{
              pathname: `/everything/${props.article.title}`,
              state: {
                article: props.article
              }}}
          >
            <button className="ui teal button">
                Details
            </button>
          </Link>
        </Button>
        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <button className="ui secondary button">
            <a href={props.article.url} style={{color: 'white'}}>
              Visit Article
            </a>
          </button>
        </Button>
        </div>
      </div>
    </div>
    </CardContainer >

)}

export default EverythingCard
<file_sep>/src/containers/TopHeadlines.js
import React, { Component } from 'react'
import SearchBar from '../components/SearchBar'
import TopHeadlinesList from '../components/topheadlines/TopHeadlinesList'
import InitialSearch from '../components/InitialSearch'
import NoResults from '../components/NoResults'

const apiKey = process.env.REACT_APP_NEWS_API_KEY

export default class TopHeadlines extends Component {

  state = {
    headlines: [],
    initialSearch: "",
    searchTerm: "",
  }

fetchHeadlines = () => {
  fetch('https://newsapi.org/v2/top-headlines?pageSize=100&q=' + this.state.initialSearch + '&apiKey=' + apiKey)
  .then(response => response.json())
  .then(data => {
    this.setState({
      headlines: data.articles })
  })
}

updateInput = e => {
  this.setState({
    [e.target.name]: e.target.value })
}

filterHeadlines = () => {
  const searchFilter = this.state.headlines.filter(headline =>
    headline.title.toLowerCase().includes(this.state.searchTerm.toLowerCase()))
  const filtered = searchFilter
    return filtered
}

render(){
  return (
    <div style={{ marginTop: '1%'}}>
      <div
        className="ui centered grid"
        style={{ marginTop: 10, marginBottom: 10 }}>
        <InitialSearch
          searchTerm={this.state.initialSearch}
          updateSearchTerm={this.updateInput}
          fetchData={this.fetchHeadlines}
        />
      </div>
      { this.state.headlines.length === 0
      ?
        < NoResults />
      :
        <>
          <div
            className="ui centered grid"
            style={{ marginTop: 10, marginBottom: 10}}>
            <SearchBar
              searchTerm={this.state.searchTerm}
              updateSearchTerm={this.updateInput}
            />
          </div>
          < TopHeadlinesList
            headlines={this.filterHeadlines()}
          />
        </>
      }
    </div>
)}

}
<file_sep>/src/components/SearchBar.js
import React from 'react'
import posed from 'react-pose'

const Input = posed.input({
  focusable: true,
  init: {
    scale: 1
  },
  focus: {
    scale: 1.1
  }
})

const SearchBar = (props) => {
  return (
    <div className="ui search" style={{ width: '40%' }}>
      <div className="ui fluid icon input">
        <Input
          className="prompt"
          type="text"
          name="searchTerm"
          placeholder="Filter results..."
          value={props.searchTerm}
          onChange={props.updateSearchTerm}
        />
        <i
          className="search icon"
        />
      </div>
    </div>
)}

export default SearchBar
<file_sep>/src/App.js
import React, { Component } from 'react'
import { Route, Switch, Redirect, withRouter } from 'react-router-dom'
import ErrorHandler from './components/ErrorHandler'
import Toolbar from './components/Toolbar'
import MainNavigation from './components/MainNavigation'
import Header from './components/Header'
import LoginPage from './containers/Auth/Login'
import SignupPage from './containers/Auth/Signup'
import NavBar from './components/NavBar'
import About from './containers/About'
import Saved from './containers/Saved'
import Sources from './containers/Sources'
import Everything from './containers/Everything'
import TopHeadlines from './containers/TopHeadlines'
import HeadlineDetails from './components/topheadlines/HeadlineDetails'
import EverythingDetails from './components/everything/EverythingDetails'
import SavedDetails from './components/saved/SavedDetails'

class App extends Component {

  state = {
    visible: true,
    isAuth: false,
    token: null,
    userId: null,
    authLoading: false,
    error: null
  }

componentDidMount() {
  const token = localStorage.getItem('token')
  const expiryDate = localStorage.getItem('expiryDate')
  if (!token || !expiryDate) {
    return
  }
  if (new Date(expiryDate) <= new Date()) {
    this.logoutHandler()
    return
  }
  const userId = localStorage.getItem('userId')
  const remainingMilliseconds =
    new Date(expiryDate).getTime() - new Date().getTime()
  this.setState({ isAuth: true, token: token, userId: userId })
  this.setAutoLogout(remainingMilliseconds)
}

logoutHandler = () => {
  this.setState({ isAuth: false, token: null });
  localStorage.removeItem('token');
  localStorage.removeItem('expiryDate');
  localStorage.removeItem('userId');
};

loginHandler = (event, authData) => {
  event.preventDefault();
  this.setState({ authLoading: true });
  fetch('https://newsappapi.saqemlas.now.sh/api/auth/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: authData.email,
      password: <PASSWORD>
    })
  })
  .then(res => {
    if (res.status === 422) {
      throw new Error('Validation failed.');
    }
    if (res.status !== 200 && res.status !== 201) {
      console.log('Error!');
      throw new Error('Could not authenticate you!');
    }
    return res.json();
  })
  .then(resData => {
    this.setState({
      isAuth: true,
      token: resData.token,
      authLoading: false,
      userId: resData.userId
    });
    localStorage.setItem('token', resData.token);
    localStorage.setItem('userId', resData.userId);
    const remainingMilliseconds = 60 * 60 * 1000;
    const expiryDate = new Date(
      new Date().getTime() + remainingMilliseconds
    );
    localStorage.setItem('expiryDate', expiryDate.toISOString());
    this.setAutoLogout(remainingMilliseconds);
  })
  .catch(err => {
    console.log(err);
    this.setState({
      isAuth: false,
      authLoading: false,
      error: err
    })
  })
}

signupHandler = (event, authData) => {
  event.preventDefault();
  this.setState({ authLoading: true });
  fetch('https://newsappapi.saqemlas.now.sh/api/auth/signup', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: authData.signupForm.email.value,
      password: <PASSWORD>,
      name: authData.signupForm.name.value
    })
  })
    .then(res => {
      if (res.status === 422) {
        throw new Error(
          "Validation failed. Make sure the email address isn't used yet!"
        );
      }
      if (res.status !== 200 && res.status !== 201) {
        console.log('Error!');
        throw new Error('Creating a user failed!');
      }
      return res.json();
    })
    .then(resData => {
      console.log(resData);
      this.setState({ isAuth: false, authLoading: false });
      this.props.history.replace('/');
    })
    .catch(err => {
      console.log(err);
      this.setState({
        isAuth: false,
        authLoading: false,
        error: err
      });
    });
};

setAutoLogout = milliseconds => {
  setTimeout(() => {
    this.logoutHandler()
  }, milliseconds)
}

errorHandler = () => {
  this.setState({ error: null })
}


onClick = () => {
  this.setState({
    visible: !this.state.visible })
}

render() {
  let routes = (
    <Switch>
      <Route
        path="/"
        exact
        render={props => (
          <LoginPage
            {...props}
            onLogin={this.loginHandler}
            loading={this.state.authLoading}
          />
        )}
      />
      <Route
        path="/signup"
        exact
        render={props => (
          <SignupPage
            {...props}
            onSignup={this.signupHandler}
            loading={this.state.authLoading}
          />
        )}
      />
      <Redirect to="/" />
    </Switch>
  )
    if (this.state.isAuth) {
      routes = (
        <>
          <NavBar
            onClick={this.onClick}
          />
          <Switch>
            <Route exact path="/about" component={About} />
            <Route exact path="/saved" component={props => (
              < Saved userId={this.state.userId} token={this.state.token} /> )} />
            <Route exact path="/sources" component={Sources} />
            <Route exact path="/topheadlines" component={TopHeadlines} />
            <Route exact path="/everything" component={Everything} />
            <Route exact path={"/topheadlines/:headline"} component={props => (
              < HeadlineDetails userId={this.state.userId} token={this.state.token} /> )} />
            <Route exact path={"/everything/:article"} component={props => (
              < EverythingDetails userId={this.state.userId} token={this.state.token} /> )} />
            <Route exact path={"/saved/:article"} component={props => (
              < SavedDetails userId={this.state.userId} token={this.state.token} /> )} />
            <Redirect to="/" />
          </Switch>
        </>
      );
    }
  return (
    <div>
      <>
        <Toolbar>
          <MainNavigation
            onLogout={this.logoutHandler}
            isAuth={this.state.isAuth}
          />
        </Toolbar>
        <ErrorHandler
          error={this.state.error}
          onHandle={this.errorHandler}
        />
        <Header
        visible={this.state.visible}
        onClick={this.onClick}
        />
      </>
      <div>
        {routes}
      </div>
    </div>
)}

}

export default withRouter(App)
<file_sep>/src/components/NoResults.js
import React from 'react'

const NoResults = () => {
  return (
    <div className="ui centered grid" style={{marginTop: '2%'}}>
      <h1>
        Please Submit Search
      </h1>
      <span>
        <i className="angle double up big icon" />
      </span>
    </div>
)}

export default NoResults
<file_sep>/src/components/saved/SavedDetails.js
import React, { Component } from 'react'
import { withRouter, Link } from 'react-router-dom'
import {
  FacebookShareButton, FacebookIcon,
  LinkedinShareButton, LinkedinIcon,
  TwitterShareButton, TwitterIcon,
  WhatsappShareButton, WhatsappIcon,
  RedditShareButton, RedditIcon,
  EmailShareButton, EmailIcon
} from 'react-share'
import posed from 'react-pose'

const Button = posed.div({
  pressable: true,
  init: { scale: 1 },
  press: { scale: 0.9 }
})

class SavedDetails extends Component {

deleteHandler = postId => {
  const userId = this.props.userId
  fetch(`http://localhost:3000/api/feed/post/${postId}/${userId}`, {
    method: 'DELETE',
    headers: {
      Authorization: 'Bearer ' + this.props.token
    },
  })
  .then(res => {
    if (res.status !== 200 && res.status !== 201) {
      throw new Error('Deleting a post failed!')
    }
    return res.json()
  })
  .catch(err => {
    console.log(err)
  })
}


render() {
  const { saved } = this.props.location.state
  return (
    <div className="ui centered grid" >
    <div className="ui fluid card" style={{ margin: 10, width: '50%'}}>
      <div className="image">
        <img
          alt={saved.title}
          src={saved.urlToImage || "https://www.albertadoctors.org/images/ama-master/feature/News.jpg"}
        />
      </div>
      <div className="content">
        <div className="header">
          {saved.title}
        </div>
        <div className="meta">
          <div className="left floated">
            Author : {saved.author || "Unknown"}
          </div>
          <span>
            <div className="right floated">
              Source : {saved.source.name || "Unknown"}
            </div>
          </span>
        </div>
        <div className="description" style={{ fontStyle: 'italic'}}>
          {saved.description}
        </div>
        <div className="description">
          {saved.content}
        </div>
      </div>
      <div className="extra content">
        <div className="ui centered grid">
        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <button
            className="ui teal button"
            onClick={() => this.props.history.goBack()}>
              Go Back
          </button>
        </Button>
        <Link to="/saved">
          <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
            <button
              className="ui red button"
              onClick={() => this.deleteHandler(saved.id)}>
                Delete
            </button>
          </Button>
        </Link>
        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <button className="ui secondary button">
            <a href={saved.url} style={{color: 'white'}}>
              Visit Article
            </a>
          </button>
        </Button>
        </div>
      </div>
      <div className="extra content">
        Share!
        <div className="ui centered grid" style={{ marginTop: '1%', marginBottom: '1%'}}>
        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <FacebookShareButton url={saved.url} quote={saved.title} >
            <FacebookIcon size={35} round />
          </FacebookShareButton>
        </Button>

        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <LinkedinShareButton url={saved.url} quote={saved.title}>
            <LinkedinIcon size={35} round />
          </LinkedinShareButton>
        </Button>

        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <TwitterShareButton url={saved.url} quote={saved.title}>
            <TwitterIcon size={35} round />
          </TwitterShareButton>
        </Button>

        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <RedditShareButton url={saved.url} quote={saved.title}>
            <RedditIcon size={35} round />
          </RedditShareButton>
        </Button>

        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <WhatsappShareButton url={saved.url} quote={saved.title}>
            <WhatsappIcon size={35} round />
          </WhatsappShareButton>
        </Button>

        <Button className="box" style={{marginTop: 5, marginBottom: 5}}>
          <EmailShareButton url={saved.url} quote={saved.title}>
            <EmailIcon size={35} round />
          </EmailShareButton>
        </Button>
        </div>
      </div>
    </div>
    </div>

)}

}

export default withRouter(SavedDetails)
<file_sep>/src/containers/Everything.js
import React, { Component } from 'react'
import SearchBar from '../components/SearchBar'
import InitialSearch from '../components/InitialSearch'
import EverythingList from '../components/everything/EverythingList'
import NoResults from '../components/NoResults'

const apiKey = process.env.REACT_APP_NEWS_API_KEY

export default class Everything extends Component {

  state = {
    everything: [],
    initialSearch: "",
    searchTerm: "",
  }

fetchEverything = () => {
  fetch('https://newsapi.org/v2/everything?q=' + this.state.initialSearch + '&apiKey=' + apiKey)
  .then(response => response.json())
  .then(data => {
    this.setState({
      everything: data.articles })
  })
}

updateInput = e => {
  this.setState({
    [e.target.name]: e.target.value })
}

filterEverything = () => {
  const filtered = this.state.everything.filter(article =>
    article.title.toLowerCase().includes(this.state.searchTerm.toLowerCase()))
  return filtered
}

render(){
  return (
    <div style={{ marginTop: '1%'}}>
        <div
          className="ui centered grid"
          style={{ marginTop: 10, marginBottom: 10 }}>
          <InitialSearch
            searchTerm={this.state.initialSearch}
            updateSearchTerm={this.updateInput}
            fetchData={this.fetchEverything}
          />
        </div>
        { this.state.everything.length === 0
        ?
          < NoResults />
        :
          <>
            <div
              className="ui centered grid"
              style={{ marginTop: 10, marginBottom: 10 }}>
              <SearchBar
                searchTerm={this.state.searchTerm}
                updateSearchTerm={this.updateInput}
              />
            </div>
            <EverythingList
              everything={this.filterEverything()}
            />
          </>
        }
    </div>
)}

}
<file_sep>/src/components/CategoryFilter.js
import React from 'react'
import { Dropdown } from 'semantic-ui-react'
import categoryIcons from './CategoryIcons'

const categories = ["general", "business", "entertainment", "health", "science", "sports", "technology"]

const categoryOptions = categories
  .map((category, index) => ({
    key: index,
    text: category,
    value: category,
    icon: categoryIcons(category)
}))

const CategoryFilter = (props) => {
  return (
    <Dropdown
      fluid
      selection
      search
      clearable
      floating
      name="category"
      placeholder="Select Category"
      options={categoryOptions}
      value={props.category}
      onChange={props.updateCategory}
      style={{ width: '40%', marginLeft: '1%', marginRight: '1%' }}
    />
  )
}

export default CategoryFilter
<file_sep>/src/containers/About.js
import React from 'react'

const About = () => {
  return (
    <div
      className="ui center aligned header"
      style={{ marginTop: '3%'}}>
      <h3>
        Global News uses the Google News API to search articles from over 30,000 news sources and blogs!
      </h3>
      <h3>
        With Global News at your disposal, find the right article for your daily news needs.
      </h3>
    </div>
)}

export default About
<file_sep>/src/components/saved/SavedList.js
import React from 'react'
import SavedCard from './SavedCard'

const SavedList = (props) => {
  return (
    <div className="ui centered equal width grid" style={{ margin: 10 }} >
      {props.saved.map((article, index) => (
        <SavedCard
          key={index}
          article={article}
          selectArticle={props.selectArticle}
        />
      ))}
    </div>
)}

export default SavedList
