| @@ -4,23 +4,24 @@ import { Helmet } from 'react-helmet-async'; | |||
| import i18next from 'i18next'; | |||
| import history from './store/utils/history'; | |||
| import Header from './components/Header/Header'; | |||
| import Sidebar from './components/Sidebar/Sidebar'; | |||
| // import Sidebar from './components/Sidebar/Sidebar'; | |||
| import AppRoutes from './AppRoutes'; | |||
| const App = () => ( | |||
| <div className="wrapper"> | |||
| <Router history={history}> | |||
| <Helmet> | |||
| <title> | |||
| {i18next.t('app.title')} | |||
| </title> | |||
| </Helmet> | |||
| <> | |||
| <Header /> | |||
| <Sidebar /> | |||
| </> | |||
| </Router> | |||
| </div> | |||
| <Router history={history}> | |||
| <Helmet> | |||
| <title> | |||
| {i18next.t('app.title')} | |||
| </title> | |||
| </Helmet> | |||
| <> | |||
| <Header /> | |||
| {/* <Sidebar /> */} | |||
| <AppRoutes /> | |||
| </> | |||
| </Router> | |||
| ); | |||
| export default App; | |||
| @@ -2,35 +2,41 @@ import React from 'react'; | |||
| import { Redirect, Route, Switch } from 'react-router-dom'; | |||
| import { | |||
| LOGIN_PAGE, | |||
| //LOGIN_PAGE, | |||
| HOME_PAGE, | |||
| FORGOT_PASSWORD_PAGE, | |||
| //FORGOT_PASSWORD_PAGE, | |||
| SCRAPE_RESULTS_PAGE, | |||
| NOT_FOUND_PAGE, | |||
| ERROR_PAGE, | |||
| BASE_PAGE, | |||
| } from './constants/pages'; | |||
| import LoginPage from './pages/LoginPage/LoginPage'; | |||
| //import LoginPage from './pages/LoginPage/LoginPage'; | |||
| import HomePage from './pages/HomePage/HomePage'; | |||
| import ScrapeResultsPage from './pages/ScrapeResults/ScrapeResultsPage'; | |||
| import NotFoundPage from './pages/ErrorPages/NotFoundPage'; | |||
| import ErrorPage from './pages/ErrorPages/ErrorPage'; | |||
| import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPage'; | |||
| import PrivateRoute from './components/Router/PrivateRoute'; | |||
| //import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPage'; | |||
| //import PrivateRoute from './components/Router/PrivateRoute'; | |||
| const AppRoutes = () => ( | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={LoginPage} /> | |||
| <Route exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> | |||
| <PrivateRoute | |||
| exact | |||
| path={HOME_PAGE} | |||
| component={HomePage} | |||
| /> | |||
| <Redirect from="*" to={NOT_FOUND_PAGE} /> | |||
| </Switch> | |||
| <div className="container content"> | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||
| <Route exact path={HOME_PAGE} component={HomePage} /> | |||
| <Route exact path={SCRAPE_RESULTS_PAGE} component={ScrapeResultsPage} /> | |||
| {/* <Route exact path={LOGIN_PAGE} component={LoginPage} /> */} | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| {/* <Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> */} | |||
| {/* <PrivateRoute | |||
| exact | |||
| path={HOME_PAGE} | |||
| component={HomePage} | |||
| /> */} | |||
| <Redirect from="*" to={NOT_FOUND_PAGE} /> | |||
| </Switch> | |||
| </div> | |||
| ); | |||
| @@ -0,0 +1,11 @@ | |||
| .content{ | |||
| margin-top: 70px; | |||
| } | |||
| .btn-outline-primary | |||
| { | |||
| height:38px; | |||
| } | |||
| .cursor-pointer | |||
| { | |||
| cursor: pointer; | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| import React, {useState} from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import Select from 'react-select' | |||
| import { prices, beds, types, lifeStyles } from '../../constants/filters'; | |||
| import './CreateScrapeRequest.scss' | |||
| const CreateScrapeRequest = () => { | |||
| const { t } = useTranslation(); | |||
| const [inputLocation, setLocation] = useState('') | |||
| const [priceFilter, setPriceFilter] = useState('') | |||
| const [bedFilter, setBedFilter] = useState('') | |||
| const [typeFilter, setTypeFilter] = useState('') | |||
| const [lifeStyleFilter, setLifeStyleFilter] = useState('') | |||
| const handleChangePriceType = async selectedOption => { | |||
| setPriceFilter(selectedOption) | |||
| }; | |||
| const handleChangeBedType = async selectedOption => { | |||
| setBedFilter(selectedOption) | |||
| }; | |||
| const handleChangeFilterType = async selectedOption => { | |||
| setTypeFilter(selectedOption) | |||
| }; | |||
| const handleChangeLifeStyleType = async selectedOption => { | |||
| setLifeStyleFilter(selectedOption) | |||
| }; | |||
| function handleSubmit (event){ | |||
| event.preventDefault() | |||
| console.log(inputLocation); | |||
| console.log(priceFilter); | |||
| console.log(bedFilter); | |||
| console.log(typeFilter); | |||
| console.log(lifeStyleFilter); | |||
| } | |||
| return ( | |||
| <div className="card card-primary"> | |||
| <div className="card-header"> | |||
| <h3 className="card-title">{t('createScrapeRequest.Title')}</h3> | |||
| </div> | |||
| <form > | |||
| <div className="card-body"> | |||
| <div className="row"> | |||
| <div className="col-md-3"> | |||
| <div className="form-group"> | |||
| <input type="text" className="form-control input-field cursor-pointer" value={inputLocation} placeholder={t('createScrapeRequest.LocationPlaceholder')} onChange={e => setLocation(e.target.value)}/> | |||
| </div> | |||
| </div> | |||
| <div className="col-md-2"> | |||
| <div className="form-group"> | |||
| <Select options={prices} onChange={handleChangePriceType} /> | |||
| </div> | |||
| </div> | |||
| <div className="col-md-2"> | |||
| <div className="form-group"> | |||
| <Select className="cursor-pointer" options={beds} onChange={handleChangeBedType}/> | |||
| </div> | |||
| </div> | |||
| <div className="col-md-2"> | |||
| <Select className="cursor-pointer" options={types} onChange={handleChangeFilterType}/> | |||
| </div> | |||
| <div className="col-md-2"> | |||
| <Select className="cursor-pointer" options={lifeStyles} onChange={handleChangeLifeStyleType}/> | |||
| </div> | |||
| <div className="col-md-1"> | |||
| <button type="submit" onClick={handleSubmit} className="btn btn-outline-primary cursor-pointer">Request</button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| ); | |||
| }; | |||
| CreateScrapeRequest.propTypes = {}; | |||
| export default CreateScrapeRequest; | |||
| @@ -0,0 +1,3 @@ | |||
| .input-field{ | |||
| height:38px; | |||
| } | |||
| @@ -1,116 +1,20 @@ | |||
| import React from 'react'; | |||
| import {Link} from 'react-router-dom'; | |||
| import './Header.scss'; | |||
| const Header = () => { | |||
| return ( | |||
| <nav className="main-header navbar navbar-expand navbar-white navbar-light"> | |||
| <ul className="navbar-nav"> | |||
| <li className="nav-item"> | |||
| <a className="nav-link" data-widget="pushmenu" href="#"><i className="fas fa-bars" /></a> | |||
| </li> | |||
| <li className="nav-item d-none d-sm-inline-block"> | |||
| <a href="index3.html" className="nav-link">Home</a> | |||
| </li> | |||
| <li className="nav-item d-none d-sm-inline-block"> | |||
| <a href="#" className="nav-link">Contact</a> | |||
| </li> | |||
| </ul> | |||
| <form className="form-inline ml-3"> | |||
| <div className="input-group input-group-sm"> | |||
| <input className="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search" /> | |||
| <div className="input-group-append"> | |||
| <button className="btn btn-navbar" type="submit"> | |||
| <i className="fas fa-search" /> | |||
| </button> | |||
| return ( | |||
| <nav className="navbar navbar-expand-md navbar-light navbar-white"> | |||
| <div className="container"> | |||
| <Link to="/" className="navbar-brand"> | |||
| <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRmiNXzLyS4xRsUFmpTynL40UuAaOtMrnHtpw&usqp=CAU" alt="Scrapper" className="brand-image img-circle elevation-3" /> | |||
| <span className="brand-text font-weight-light">Scrapper</span> | |||
| </Link> | |||
| <button className="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> | |||
| <span className="navbar-toggler-icon"></span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| <ul className="navbar-nav ml-auto"> | |||
| <li className="nav-item dropdown"> | |||
| <a className="nav-link" data-toggle="dropdown" href="#"> | |||
| <i className="far fa-comments" /> | |||
| <span className="badge badge-danger navbar-badge">3</span> | |||
| </a> | |||
| <div className="dropdown-menu dropdown-menu-lg dropdown-menu-right"> | |||
| <a href="#" className="dropdown-item"> | |||
| <div className="media"> | |||
| <img src="dist/img/user1-128x128.jpg" alt="User Avatar" className="img-size-50 mr-3 img-circle" /> | |||
| <div className="media-body"> | |||
| <h3 className="dropdown-item-title"> | |||
| <span className="float-right text-sm text-danger"><i className="fas fa-star" /></span> | |||
| </h3> | |||
| <p className="text-sm">Call me whenever you can...</p> | |||
| <p className="text-sm text-muted"><i className="far fa-clock mr-1" /> 4 Hours Ago</p> | |||
| </div> | |||
| </div> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item"> | |||
| <div className="media"> | |||
| <img src="dist/img/user8-128x128.jpg" alt="User Avatar" className="img-size-50 img-circle mr-3" /> | |||
| <div className="media-body"> | |||
| <h3 className="dropdown-item-title"> | |||
| John Pierce | |||
| <span className="float-right text-sm text-muted"><i className="fas fa-star" /></span> | |||
| </h3> | |||
| <p className="text-sm">I got your message bro</p> | |||
| <p className="text-sm text-muted"><i className="far fa-clock mr-1" /> 4 Hours Ago</p> | |||
| </div> | |||
| </div> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item"> | |||
| <div className="media"> | |||
| <img src="dist/img/user3-128x128.jpg" alt="User Avatar" className="img-size-50 img-circle mr-3" /> | |||
| <div className="media-body"> | |||
| <h3 className="dropdown-item-title"> | |||
| Nora Silvester | |||
| <span className="float-right text-sm text-warning"><i className="fas fa-star" /></span> | |||
| </h3> | |||
| <p className="text-sm">The subject goes here</p> | |||
| <p className="text-sm text-muted"><i className="far fa-clock mr-1" /> 4 Hours Ago</p> | |||
| </div> | |||
| </div> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item dropdown-footer">See All Messages</a> | |||
| </div> | |||
| </li> | |||
| <li className="nav-item dropdown"> | |||
| <a className="nav-link" data-toggle="dropdown" href="#"> | |||
| <i className="far fa-bell" /> | |||
| <span className="badge badge-warning navbar-badge">15</span> | |||
| </a> | |||
| <div className="dropdown-menu dropdown-menu-lg dropdown-menu-right"> | |||
| <span className="dropdown-item dropdown-header">15 Notifications</span> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item"> | |||
| <i className="fas fa-envelope mr-2" /> 4 new messages | |||
| <span className="float-right text-muted text-sm">3 mins</span> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item"> | |||
| <i className="fas fa-users mr-2" /> 8 friend requests | |||
| <span className="float-right text-muted text-sm">12 hours</span> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item"> | |||
| <i className="fas fa-file mr-2" /> 3 new reports | |||
| <span className="float-right text-muted text-sm">2 days</span> | |||
| </a> | |||
| <div className="dropdown-divider" /> | |||
| <a href="#" className="dropdown-item dropdown-footer">See All Notifications</a> | |||
| </div> | |||
| </li> | |||
| <li className="nav-item"> | |||
| <a className="nav-link" data-widget="control-sidebar" data-slide="true" href="#"> | |||
| <i className="fas fa-th-large" /> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </nav> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,3 @@ | |||
| .brand-image { | |||
| height:33px; | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| import React from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| const ScrapeRequest = (scrape) => { | |||
| const { t } = useTranslation(); | |||
| function handleSubmit(event) | |||
| { | |||
| event.preventDefault(); | |||
| } | |||
| return ( | |||
| <tr> | |||
| <td> | |||
| <p> | |||
| </p><h3>{scrape.Title}</h3> | |||
| <span className="text-muted">Count {scrape.Count} +</span> | |||
| <span> | </span> | |||
| <span className="text-muted">{t('scrapeRequest.EstimatedTime')} {scrape.EstimatedTime}</span> | |||
| <span> | </span> | |||
| {t('scrapeRequest.ViewScrape')} <a href="scrappe.html"> {scrape.Url}</a> | |||
| <p></p> | |||
| <p> | |||
| </p> | |||
| </td> | |||
| <td> | |||
| {/* {scrape.Filters.map(element => ( | |||
| <span key={element.id} className="badge bg-primary">{element.type}</span> | |||
| ))} */} | |||
| </td> | |||
| <td><button type="submit" className="btn btn-xs btn-block btn-primary" onClick={handleSubmit}><i className="fa fa-bell"></i>{t('common.execute')}</button> | |||
| </td> | |||
| </tr> | |||
| ); | |||
| } | |||
| export default ScrapeRequest; | |||
| @@ -0,0 +1,95 @@ | |||
| import React from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import ScrapeRequest from '../ScrapeRequest/ScrapeRequest'; | |||
| const ScrapeRequests = () => { | |||
| const { t } = useTranslation(); | |||
| const scrape = { Title : "#1 Chicago, IL", Count : "200", EstimatedTime : "20/7/2021 20:30AM", Url : "https://www.apartments.com/chicago-il/", Filters : [ {id: 1, type:"prices"}, {id:2, type : "beds"}, {id:3, type : "type"}, {id:4, type:"lifestyle"}] }; | |||
| return ( | |||
| <div className="card"> | |||
| <div className="card-header"> | |||
| <h3 className="card-title">{t('scrapeRequests.Title')}</h3> | |||
| </div> | |||
| <div classNameName="card-body p-0"> | |||
| <table className="table table-sm"> | |||
| <thead> | |||
| <tr> | |||
| <th>{t('scrapeRequests.Columns.Scrape')}</th> | |||
| <th>{t('scrapeRequests.Columns.Filters')}</th> | |||
| <th>{t('scrapeRequests.Columns.Status')}</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <ScrapeRequest scrape = {scrape} /> | |||
| {/* <tr> | |||
| <td> | |||
| <p> | |||
| </p><h3> </h3> | |||
| <span className="text-muted">Count 200+</span> | |||
| <span> | </span> | |||
| <span className="text-muted">Estimated time 20/7/2021 20:30AM</span> | |||
| <span> | </span> | |||
| view scrape origin <a href="scrappe.html">https://www.apartments.com/chicago-il/</a> | |||
| <p></p> | |||
| <p> | |||
| </p> | |||
| </td> | |||
| <td> | |||
| <span className="badge bg-primary">price</span> | |||
| <span className="badge bg-primary">beds</span> | |||
| <span className="badge bg-primary">type</span> | |||
| <span className="badge bg-primary">lifestyle</span> | |||
| </td> | |||
| <td><button className="btn btn-xs btn-block btn-primary"><i className="fa fa-bell"></i>Execute</button> | |||
| </td> | |||
| </tr> | |||
| <tr> | |||
| <td> | |||
| <h3>#2 New York, NY </h3> | |||
| <span className="text-muted">Count 200+</span> | |||
| <span> | </span> | |||
| <span className="text-muted">Estimated time 20/7/2021 20:30AM</span> | |||
| <span> | </span> | |||
| view scrape origin <a href="scrappe.html">https://www.apartments.com/chicago-il/</a> | |||
| </td> | |||
| <td> | |||
| <span className="badge bg-primary">price</span> | |||
| <span className="badge bg-primary">beds</span> | |||
| <span className="badge bg-primary">type</span> | |||
| <span className="badge bg-primary">lifestyle</span> | |||
| </td> | |||
| <td><span className="badge bg-warning">Pending</span></td> | |||
| </tr> | |||
| <tr> | |||
| <td> | |||
| <h3>#3 New York, NY </h3> | |||
| <span className="text-muted">Count 200+</span> | |||
| <span> | </span> | |||
| <span className="text-muted">Estimated time 20/7/2021 20:30AM</span> | |||
| <span> | </span> | |||
| view scrape origin <a href="scrappe.html">https://www.apartments.com/chicago-il/</a> | |||
| </td> | |||
| <td> | |||
| <span className="badge bg-primary">price</span> | |||
| <span className="badge bg-primary">beds</span> | |||
| <span className="badge bg-primary">type</span> | |||
| <span className="badge bg-primary">lifestyle</span> | |||
| </td> | |||
| <td><span className="badge bg-success">Done</span></td> | |||
| </tr> | |||
| */} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ScrapeRequests; | |||
| @@ -0,0 +1,29 @@ | |||
| export const prices = [ | |||
| { value: '$1000', label: '$1000' }, | |||
| { value: '$1100', label: '$1100' }, | |||
| { value: '$1300', label: '$1300' }, | |||
| { value: '$1500', label: '$1500' }, | |||
| { value: '$1800', label: '$1800' }, | |||
| { value: '$2000', label: '$2000' } | |||
| ]; | |||
| export const beds = [ | |||
| { value: '1', label: '1' }, | |||
| { value: '2', label: '2' }, | |||
| { value: '3', label: '3' }, | |||
| { value: '4+', label: '4+' } | |||
| ]; | |||
| export const types = [ | |||
| { value: 'apartments', label: 'apartments' }, | |||
| { value: 'houses', label: 'houses' }, | |||
| { value: 'condos', label: 'condos' }, | |||
| { value: 'townhomes', label: 'townhomes' }, | |||
| ]; | |||
| export const lifeStyles = [ | |||
| { value: 'student-housing', label: 'Student' }, | |||
| { value: 'senior-housing', label: 'Senior Housing' }, | |||
| { value: 'short-term', label: 'Short Term' }, | |||
| { value: 'military', label: 'Military Housing'}, | |||
| {value:'corporate' , label: 'Corporate Housing'} | |||
| ]; | |||
| @@ -3,4 +3,5 @@ export const LOGIN_PAGE = '/login'; | |||
| export const FORGOT_PASSWORD_PAGE = '/forgot-password'; | |||
| export const HOME_PAGE = '/home'; | |||
| export const ERROR_PAGE = '/error-page'; | |||
| export const NOT_FOUND_PAGE = '/not-found'; | |||
| export const NOT_FOUND_PAGE = '/not-found'; | |||
| export const SCRAPE_RESULTS_PAGE = '/scrape-results'; | |||
| @@ -0,0 +1,4 @@ | |||
| export function CreateUrl(){ | |||
| return ''; | |||
| } | |||
| @@ -26,6 +26,7 @@ export default { | |||
| remove: 'Remove', | |||
| invite: 'Invite', | |||
| save: 'Save', | |||
| execute:'Execute', | |||
| complete: 'Complete', | |||
| download: 'Download', | |||
| yes: 'Yes', | |||
| @@ -83,5 +84,22 @@ export default { | |||
| }, | |||
| apiErrors:{ | |||
| ClientIpAddressIsNullOrEmpty:"Client Ip address is null or empty" | |||
| }, | |||
| createScrapeRequest: { | |||
| Title: "Request new scrappe", | |||
| LocationPlaceholder : "Location or Point of Interest" | |||
| }, | |||
| scrapeRequests: { | |||
| Title: "Scrappes", | |||
| Columns: { | |||
| Scrape:"Scrappe", | |||
| Filters:"Filters", | |||
| Status:"Status" | |||
| } | |||
| }, | |||
| scrapeRequest:{ | |||
| EstimatedTime: "Estimated time:", | |||
| ViewScrape:"view scrape origin" | |||
| } | |||
| }; | |||
| @@ -20,3 +20,4 @@ | |||
| @import './assets/styles/layout'; | |||
| @import './assets/styles/overwrite'; | |||
| @import './assets/styles/utility'; | |||
| @import './assets/styles/custom'; | |||
| @@ -1,12 +1,16 @@ | |||
| import React from 'react'; | |||
| import { Link } from 'react-router-dom'; | |||
| import CreateScrapeRequest from '../../components/CreateScrapeRequest/CreateScrapeRequest'; | |||
| import ScrapeRequests from '../../components/ScrapeRequests/ScrapeRequests'; | |||
| const HomePage = () => { | |||
| return ( | |||
| <div className="c-error-page"> | |||
| <div className="c-error-page__content"> | |||
| <h1 className="c-error-page__title">Home page</h1> | |||
| </div> | |||
| </div> | |||
| <> | |||
| <CreateScrapeRequest /> | |||
| <ScrapeRequests /> | |||
| <Link to='/scrape-results' >See results</Link> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| const LoginPage = () => { | |||
| return ( | |||
| <aside className="main-sidebar"> | |||
| <section className="sidebar"> | |||
| @@ -0,0 +1,15 @@ | |||
| import React from 'react'; | |||
| const ScrapeResultsPage = () => { | |||
| return ( | |||
| <div className="c-error-page"> | |||
| <div className="c-error-page__content"> | |||
| Scrape results page | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| ScrapeResultsPage.propTypes = {}; | |||
| export default ScrapeResultsPage; | |||
| @@ -0,0 +1,2 @@ | |||
| import { all, call, put, takeLatest } from '@redux-saga/core/effects'; | |||