1 Commits

Author SHA1 Message Date
Bilal
aeba6fdc2f Merge pull request #2 from edazdarevic/rental-crawler
Rental crawler
2017-11-02 21:37:01 +01:00
21 changed files with 154 additions and 27864 deletions

View File

@@ -752,7 +752,7 @@ router.get('/search/listings', function () {
//Get only ads with location //Get only ads with location
query = Object.assign(query, { query = Object.assign(query, {
hasMap: true has_map: true
}); });
//AND //AND

View File

@@ -97,7 +97,7 @@ router.get ('/search/listings', async (req, res, next) => {
//Get only ads with location //Get only ads with location
query = Object.assign (query, { query = Object.assign (query, {
hasMap: true, has_map: true,
}); });
//AND //AND

View File

@@ -1 +0,0 @@
export const BASE_URL = '138.68.67.31';

View File

@@ -1 +0,0 @@
RENTAL_FROM_PAGE=1 RENTAL_TO_PAGE=45 PROSTOR_FROM_PAGE=1 PROSTOR_TO_PAGE=26 MONGO_URL=mongodb://localhost:27017/kivi node /home/bilal/kivi/crawler/build/crawler.js > /home/bilal/crawler.log

27652
web/dist/app.bundle.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

172
web/dist/welcome.css vendored
View File

@@ -5,25 +5,25 @@
/*}*/ /*}*/
.welcome-container h1 { .welcome-container h1 {
font-size: 1.2em; font-size: 2em;
text-align: center; text-align: center;
} }
.welcome-container h2 { .welcome-container h2 {
/*padding-bottom: 25px;*/ padding-bottom: 25px;
color: #2d3138; color: #2d3138;
font-size: 1em; font-size: 26px;
font-weight: 200; font-weight: 200;
text-align: center; text-align: center;
letter-spacing: .59px; letter-spacing: .59px;
} }
.welcome-container-bg { .welcome-container-bg {
/*background-color: rgb(92, 192, 99);*/ /*background-color: rgb(92, 192, 99);*/
/*background-image: url('static/map.jpg');*/ background-image: url('static/map.jpg');
background-image: url('static/images/sa-bg.jpg'); /*background-image: url('static/images/sa-bg.jpg');*/
background-size: auto 100%;
/*background-position: center;*/ /*background-position: center;*/
-moz-filter: blur(5px); -moz-filter: blur(5px);
-o-filter: blur(5px); -o-filter: blur(5px);
@@ -40,154 +40,26 @@
} }
.welcome-container { .welcome-container {
position: absolute; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
top:0;
bottom:0;
z-index: 0; z-index: 0;
/*margin-left: 20px; margin-left: 20px;
margin-right: 20px;*/ margin-right: 20px;
height: 100%; height: 100%;
/*padding: 100px;*/ padding: 100px;
} }
.welcome-content { .welcome-content {
/*height: 100%;*/ /*height: 100%;*/
/*margin: 0 auto;*/ margin: 0 auto;
width: 240px; width: 600px;
background-color: hsla(0,0%,100%,.95); background-color: hsla(0,0%,100%,.95);
box-shadow: 0 2px 4px 0 rgba(73,73,73,.1);
margin-left: auto;
margin-right: auto;
margin-top:40%;
/*padding: 50px;*/
}
.buy-button-active {
height: 80px;
width: 80px;
background:url('static/images/sale_1_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 10%;
margin-right: 5%;
}
.buy-button-inactive {
height: 80px;
width: 80px;
background:url('static/images/sale_0_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 10%;
margin-right: 5%;
}
.rent-button-active{
height: 80px;
width: 80px;
background:url('static/images/rent_1_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 5%;
}
.rent-button-inactive{
height: 80px;
width: 80px;
background:url('static/images/rent_0_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 5%;
}
.search-button{
background-color: #b6d53b;
margin: 10px;
border: none;
text-align: center;
font-size: 1em;
width: 90%;
margin-left:5%;
margin-right: 5%;
}
@media (min-width: 550px) {
.welcome-container h1 {
font-size: 2em;
text-align: center;
}
.welcome-container h2 {
/*padding-bottom: 25px;*/
color: #2d3138;
font-size: 1.4em;
font-weight: 200;
text-align: center;
letter-spacing: .59px;
}
.welcome-content {
/*height: 100%;*/
/*margin: 0 auto;*/
width: 500px;
background-color: hsla(0,0%,100%,.95);
box-shadow: 0 2px 4px 0 rgba(73,73,73,.1); box-shadow: 0 2px 4px 0 rgba(73,73,73,.1);
margin-left: auto; padding: 50px;
margin-right: auto; }
margin-top:10%;
/*padding: 50px;*/ .welcome-content .gmaps-places-input-welcome {
}
width: 100%;
.buy-button-active {
height: 150px;
width: 150px;
background:url('static/images/sale_1_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 15%;
margin-right: 5%;
}
.buy-button-inactive {
height: 150px;
width: 150px;
background:url('static/images/sale_0_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 15%;
margin-right: 5%;
}
.rent-button-active{
height: 150px;
width: 150px;
background:url('static/images/rent_1_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 5%;
}
.rent-button-inactive{
height: 150px;
width: 150px;
background:url('static/images/rent_0_mobile.png') no-repeat;
background-size: contain;
border: none;
margin-left: 5%;
}
.search-button{
background-color: #b6d53b;
margin: 10px;
border: none;
text-align: center;
font-size: 1.4em;
width: 90%;
margin-left:5%;
margin-right: 5%;
}
} }

View File

@@ -22,8 +22,7 @@ class Main extends React.Component {
filters: { filters: {
rooms: {}, rooms: {},
category: {}, category: {},
status : {}, status : {}
adType: 0
}, },
mobileView: 'MAP', mobileView: 'MAP',
contact: { contact: {
@@ -35,8 +34,6 @@ class Main extends React.Component {
} }
} }
console.log("Props : ");
console.log(props.initialState);
if (props.initialState) { if (props.initialState) {
props.initialState.sort = props.initialState.sort || state.sort props.initialState.sort = props.initialState.sort || state.sort
state.filters.rooms = props.initialState.rooms state.filters.rooms = props.initialState.rooms
@@ -52,7 +49,6 @@ class Main extends React.Component {
state.filters.maxSize = props.initialState.maxSize state.filters.maxSize = props.initialState.maxSize
state.filters.minPrice = props.initialState.minPrice state.filters.minPrice = props.initialState.minPrice
state.filters.maxPrice = props.initialState.maxPrice state.filters.maxPrice = props.initialState.maxPrice
state.filters.adType = props.initialState.adType
} }
this.state = state this.state = state
@@ -238,8 +234,7 @@ class Main extends React.Component {
maxSize, maxSize,
minPrice, minPrice,
maxPrice, maxPrice,
category, category
adType
} = this.state.filters } = this.state.filters
const bounds = map.getBounds() const bounds = map.getBounds()
@@ -251,7 +246,6 @@ class Main extends React.Component {
minPrice, minPrice,
maxPrice, maxPrice,
category, category,
adType,
page: this.state.page, page: this.state.page,
pins: true pins: true
}) })
@@ -410,8 +404,7 @@ class Main extends React.Component {
maxSize, maxSize,
minPrice, minPrice,
maxPrice, maxPrice,
category, category
adType
} = this.state.filters } = this.state.filters
const bounds = map.getBounds() const bounds = map.getBounds()
@@ -423,7 +416,6 @@ class Main extends React.Component {
minPrice, minPrice,
maxPrice, maxPrice,
category, category,
adType,
page: this.state.page, page: this.state.page,
sort: this.state.sort sort: this.state.sort
}) })

View File

@@ -1,25 +1,55 @@
import React from 'react' import React from 'react'
import {AD_TYPE_SALE, AD_TYPE_RENT} from '../../../common/enums'; import { pacSelectFirst } from '../helpers/googleMaps'
export default class Welcome extends React.Component { export default class Welcome extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
type: AD_TYPE_SALE, type: 'SALE'
} }
} }
componentDidMount () {
const options = {
componentRestrictions: { country: 'BA' },
types: ['geocode']
}
const input = document.getElementById('gmaps-places-input-welcome')
const searchBox = new google.maps.places.Autocomplete(input, options)
pacSelectFirst(input)
input.addEventListener('focus', e => {
e.target.value = ''
})
searchBox.addListener('place_changed', () => {
const place = searchBox.getPlace()
if (place.geometry.viewport) {
const bounds = place.geometry.viewport.toUrlValue()
this.props.onSearch({
bounds,
type: this.state.type
})
} else {
const location = place.geometry.location
this.props.onSearch({
location,
type: this.state.type
})
}
})
}
onSaleClick () { onSaleClick () {
this.setState({ this.setState({
type: AD_TYPE_SALE, type: 'SALE'
}) })
} }
onRentClick () { onRentClick () {
this.setState({ this.setState({
type: AD_TYPE_RENT, type: 'RENT'
}) })
} }
@@ -33,11 +63,15 @@ export default class Welcome extends React.Component {
<div className='welcome-content'> <div className='welcome-content'>
<h1>KIVI</h1> <h1>KIVI</h1>
<h2>Pronađi svoj novi dom!</h2> <h2>Pronađi svoj novi dom!</h2>
<div> <button
<button className={this.state.type===AD_TYPE_SALE?'buy-button-active':'buy-button-inactive'} onClick={this.onSaleClick.bind(this)}></button> onClick={this.onSaleClick.bind(this)}>Kupovina</button>
<button className={this.state.type===AD_TYPE_RENT?'rent-button-active':'rent-button-inactive'} onClick={this.onRentClick.bind(this)}></button> <button onClick={this.onRentClick.bind(this)}>Iznajmljivanje</button>
</div> <input
<button className='search-button' onClick={()=>this.props.onSearch({adType: this.state.type})} >TRAŽI</button> type='text'
placeholder='Unesite adresu, naselje ili grad'
className='where-to'
id='gmaps-places-input-welcome'
/>
</div> </div>
</div> </div>

View File

@@ -1,84 +1,84 @@
import React from 'react'; import React from 'react'
import {render} from 'react-dom'; import {render} from 'react-dom'
import Main from './components/Main'; import Main from './components/Main'
import Welcome from './components/Welcome'; import Welcome from './components/Welcome'
const getInitialState = url => { const getInitialState = url => {
const params = window.location.search.substr (1).split ('&'); const params = window.location.search.substr(1).split('&')
const initialState = { const initialState = {
rooms: {}, rooms: {},
category: {}, category: {}
}; }
for (const param of params) { for (const param of params) {
const [key, value] = param.split ('='); const [key, value] = param.split('=')
if (key === 'rooms' && value !== '') { if (key === 'rooms' && value !== '') {
initialState.rooms = {}; initialState.rooms = {}
value.split (',').forEach (k => { value.split(',').forEach(k => {
initialState.rooms[parseInt (k)] = true; initialState.rooms[parseInt(k)] = true
}); })
} }
if (key === 'category' && value !== '') { if (key === 'category' && value !== '') {
initialState.category = {}; initialState.category = {}
value.split (',').forEach (k => { value.split(',').forEach(k => {
initialState.category[parseInt (k)] = true; initialState.category[parseInt(k)] = true
}); })
} }
if (key === 'sort') { if (key === 'sort') {
initialState.sort = value; initialState.sort = value
} }
if (key === 'bounds') { if (key === 'bounds') {
initialState.bounds = value; initialState.bounds = value
} }
if (key === 'listingId') { if (key === 'listingId') {
initialState.listingId = value; initialState.listingId = value
} }
if (key === 'adType') { if (key === 'type') {
initialState.adType = value; initialState.type = value
} }
if (key === 'zoom') { if (key === 'zoom') {
initialState.zoom = parseInt (value); initialState.zoom = parseInt(value)
} }
if (['minSize', 'maxSize', 'minPrice', 'maxPrice'].includes (key)) { if (['minSize', 'maxSize', 'minPrice', 'maxPrice'].includes(key)) {
initialState[key] = parseFloat (value); initialState[key] = parseFloat(value)
} }
} }
return initialState; return initialState
}; }
const root = document.getElementById ('root'); const root = document.getElementById('root')
const initialState = getInitialState (window.location); const initialState = getInitialState(window.location)
const renderMain = (additionalState = {}) => { const renderMain = (additionalState = {}) => {
const main = <Main initialState={{...initialState, ...additionalState}} />; const main = <Main initialState={{...initialState, ...additionalState}} />
render (main, root); render(main, root)
}; }
//renderMain (); renderMain()
// disable temp // disable temp
/*
if ( if (Object.keys(initialState).length === 2 &&
Object.keys (initialState).length === 2 && window.localStorage.getItem('lastLoad') == null) {
window.localStorage.getItem ('lastLoad') == null const onSearch = ({bounds, type, location}) => {
) { window.location = `/?bounds=${bounds}&type=${type}`
const onSearch = ({adType}) => { //renderMain({
//bounds,
console.log("onSearch()"); //type
//window.location = `/?adType=${adType}`; //})
renderMain({adType}) }
}; const welcome = <Welcome onSearch={onSearch} />
const welcome = <Welcome onSearch={onSearch} />; render(welcome, root)
render (welcome, root);
} else { } else {
renderMain (); renderMain()
} }
*/

View File

@@ -1,9 +1,11 @@
import fetch from 'isomorphic-fetch' import fetch from 'isomorphic-fetch'
import {BASE_URL} from '../../../common/config'
const BASE_URL = 'localhost';
//const BASE_URL = '192.168.0.13';
export const saveContactRequest = (listingId, params) => { export const saveContactRequest = (listingId, params) => {
let url = `http://${BASE_URL}/api/contact/${listingId}` let url = `http://${BASE_URL}:3001/api/contact/${listingId}`
return fetch(url, { return fetch(url, {
method: 'POST', method: 'POST',
@@ -16,7 +18,7 @@ export const saveContactRequest = (listingId, params) => {
} }
export const loadListing = id => { export const loadListing = id => {
let url = `http://${BASE_URL}/api/search/listings/${id}` let url = `http://${BASE_URL}:3001/api/search/listings/${id}`
return fetch( return fetch(
url, url,
@@ -35,7 +37,6 @@ export const loadProperties = (
maxSize = '', maxSize = '',
rooms = {}, rooms = {},
category = {}, category = {},
adType=1,
page = 1, page = 1,
pins = false, pins = false,
sort = '' sort = ''
@@ -49,7 +50,7 @@ export const loadProperties = (
// TODO: handle errors // TODO: handle errors
//return fetch(process.env.API_URL + '/api/search', { //return fetch(process.env.API_URL + '/api/search', {
let url = `http://${BASE_URL}/api/search/listings?bounds=${bounds}&minPrice=${minPrice}&maxPrice=${maxPrice}&rooms=${allRooms}&minSize=${minSize}&maxSize=${maxSize}&adType=${adType}&category=${allCategories}&page=${page}&pins=${pins}&sort=${sort}` let url = `http://${BASE_URL}:3001/api/search/listings?bounds=${bounds}&minPrice=${minPrice}&maxPrice=${maxPrice}&rooms=${allRooms}&minSize=${minSize}&maxSize=${maxSize}&category=${allCategories}&page=${page}&pins=${pins}&sort=${sort}`
return fetch( return fetch(
url, url,

View File

@@ -1,5 +1,3 @@
import {BASE_URL} from '../../../common/config'
export const formatPrice = p => { export const formatPrice = p => {
if (isNaN(p)) { if (isNaN(p)) {
return 'Po dogovoru' return 'Po dogovoru'
@@ -35,7 +33,8 @@ S poštovanjem
} }
export const listingUrl = (id) => { export const listingUrl = (id) => {
return `http://${BASE_URL}/?listingId=${id}` // TODO: fix this once removing hardcoded values
return `http://localhost:8080/?listingId=${id}`
} }
export const isMobile = () => window.matchMedia("(max-width: 768px)").matches export const isMobile = () => window.matchMedia("(max-width: 768px)").matches

View File

@@ -59,15 +59,13 @@ export default class Router {
sort, sort,
rooms = {}, rooms = {},
category = {}, category = {},
zoom, zoom
adType
} = this.state } = this.state
if (listingId) { if (listingId) {
params.push(`listingId=${listingId}`) params.push(`listingId=${listingId}`)
} }
params.push(`adType=${adType}`);
params.push(`sort=${sort}`) params.push(`sort=${sort}`)
params.push(`bounds=${bounds}`) params.push(`bounds=${bounds}`)
params.push(`zoom=${zoom}`) params.push(`zoom=${zoom}`)

View File

@@ -3,13 +3,7 @@ module.exports = {
output: { output: {
path: __dirname + "/dist", path: __dirname + "/dist",
filename: "app.bundle.js", filename: "app.bundle.js",
publicPath: "http://138.68.67.31:8080/" publicPath: "http://0.0.0.0:8080/"
},
devServer: {
// .. rest of devserver options
host: '0.0.0.0',
disableHostCheck: true
}, },
module: { module: {
loaders: [ loaders: [