Welcome page

This commit is contained in:
Edin Dazdarevic
2017-04-21 01:55:13 +02:00
parent 344877beda
commit 8f57f91d32
9 changed files with 290 additions and 112 deletions

1
web/dist/index.html vendored
View File

@@ -11,6 +11,7 @@
<link href="https://fonts.googleapis.com/css?family=Arimo" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Arimo" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link rel="stylesheet" href="main.css"> <link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="welcome.css">
</head> </head>
<body> <body>
<div id="root"> <div id="root">

BIN
web/dist/static/images/sa-bg.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 KiB

65
web/dist/welcome.css vendored Normal file
View File

@@ -0,0 +1,65 @@
/*.welcome-container div {*/
/*border: 1px solid red;*/
/*}*/
.welcome-container h1 {
font-size: 2em;
text-align: center;
}
.welcome-container h2 {
padding-bottom: 25px;
color: #2d3138;
font-size: 26px;
font-weight: 200;
text-align: center;
letter-spacing: .59px;
}
.welcome-container-bg {
/*background-color: rgb(92, 192, 99);*/
background-image: url('static/map.jpg');
/*background-image: url('static/images/sa-bg.jpg');*/
/*background-position: center;*/
-moz-filter: blur(5px);
-o-filter: blur(5px);
-ms-filter: blur(5px);
filter: blur(5px);
content: "";
position: fixed;
left: 0;
right: 0;
z-index: -1;
width: 100%;
height: 100%;
}
.welcome-container {
position: fixed;
left: 0;
right: 0;
z-index: 0;
margin-left: 20px;
margin-right: 20px;
height: 100%;
padding: 100px;
}
.welcome-content {
/*height: 100%;*/
margin: 0 auto;
width: 600px;
background-color: hsla(0,0%,100%,.95);
box-shadow: 0 2px 4px 0 rgba(73,73,73,.1);
padding: 50px;
}
.welcome-content .gmaps-places-input-welcome {
width: 100%;
}

View File

@@ -1,28 +1,27 @@
import React from 'react' import React from "react";
import {saveContactRequest} from '../lib/api' import { saveContactRequest } from "../lib/api";
export default class ContactModal extends React.Component { export default class ContactModal extends React.Component {
onContactCloseClick (e) { onContactCloseClick(e) {
e.preventDefault() e.preventDefault();
this.props.dispatch({ this.props.dispatch({
type: 'CLOSE_CONTACT' type: "CLOSE_CONTACT"
}) });
} }
onSubmit (e) { onSubmit(e) {
e.preventDefault() e.preventDefault();
const {name, email, message, phone, alert} = this.props.contact; const { name, email, message, phone, alert } = this.props.contact;
if (!name || !email) { if (!name || !email) {
this.props.dispatch({ this.props.dispatch({
type: 'INVALID_CONTACT' type: "INVALID_CONTACT"
}) });
} else { } else {
this.props.dispatch({ this.props.dispatch({
type: 'SUBMIT_CONTACT_START' type: "SUBMIT_CONTACT_START"
}) });
saveContactRequest(this.props.listingId, { saveContactRequest(this.props.listingId, {
name, name,
@@ -30,39 +29,42 @@ export default class ContactModal extends React.Component {
phone, phone,
message, message,
alert alert
}).then(l => l.text()).then(res => { })
this.props.dispatch({ .then(l => l.text())
type: 'SUBMIT_CONTACT_END' .then(res => {
this.props.dispatch({
type: "SUBMIT_CONTACT_END"
});
})
.catch(e => {
// TODO: should we have a global view for rendering errors
console.error(e);
}); });
}).catch(e => {
// TODO: should we have a global view for rendering errors
console.error(e)
});
} }
} }
onFieldChange (field, e) { onFieldChange(field, e) {
this.props.dispatch({ this.props.dispatch({
type: 'UPDATE_CONTACT_INFO', type: "UPDATE_CONTACT_INFO",
action: { action: {
field, field,
value: e.target.value value: e.target.value
} }
}) });
} }
onAlertToggle (e) { onAlertToggle(e) {
const alert = this.props.contact.alert const alert = this.props.contact.alert;
this.props.dispatch({ this.props.dispatch({
type: 'UPDATE_CONTACT_INFO', type: "UPDATE_CONTACT_INFO",
action: { action: {
field: 'alert', field: "alert",
value: !alert value: !alert
} }
}) });
} }
render () { render() {
const { const {
message, message,
email, email,
@@ -72,10 +74,10 @@ export default class ContactModal extends React.Component {
nameInvalid, nameInvalid,
emailInvalid, emailInvalid,
sending sending
} = this.props.contact } = this.props.contact;
const nameValidationClass = nameInvalid ? 'validation-failed' : '' const nameValidationClass = nameInvalid ? "validation-failed" : "";
const emailValidationClass = emailInvalid ? 'validation-failed' : '' const emailValidationClass = emailInvalid ? "validation-failed" : "";
return ( return (
<div className="modal contact-form"> <div className="modal contact-form">
@@ -94,7 +96,7 @@ export default class ContactModal extends React.Component {
<input <input
value={name} value={name}
className={nameValidationClass} className={nameValidationClass}
onChange={this.onFieldChange.bind(this, 'name')} onChange={this.onFieldChange.bind(this, "name")}
placeholder="Ime i prezime" placeholder="Ime i prezime"
type="text" type="text"
/> />
@@ -103,32 +105,32 @@ export default class ContactModal extends React.Component {
<input <input
value={email} value={email}
className={emailValidationClass} className={emailValidationClass}
onChange={this.onFieldChange.bind(this, 'email')} onChange={this.onFieldChange.bind(this, "email")}
placeholder="Email adresa" placeholder="Email adresa"
type="email" type="email"
name="email" name="email"
/> />
<input <input
value={phone} value={phone}
onChange={this.onFieldChange.bind(this, 'phone')} onChange={this.onFieldChange.bind(this, "phone")}
placeholder="Telefon (opcionalno)" placeholder="Telefon (opcionalno)"
type="text" type="text"
/> />
</div> </div>
<div className="contact-form-message"> <div className="contact-form-message">
<textarea <textarea
onChange={this.onFieldChange.bind(this, 'message')} onChange={this.onFieldChange.bind(this, "message")}
value={message} value={message}
/> />
</div> </div>
<div className="contact-form-alert noselect"> <div className="contact-form-alert noselect">
<label> <label>
<input <input
type="checkbox" type="checkbox"
onChange={this.onAlertToggle.bind(this)} onChange={this.onAlertToggle.bind(this)}
checked={doAlert} checked={doAlert}
/> />
Javi mi kada se objavi sličan oglas Javi mi kada se objavi sličan oglas
</label> </label>
</div> </div>
<div className="contact-form-footer"> <div className="contact-form-footer">
@@ -137,6 +139,6 @@ export default class ContactModal extends React.Component {
</form> </form>
</div> </div>
</div> </div>
) );
} }
} }

View File

@@ -1,94 +1,94 @@
import React from 'react' import React from "react";
import {formatFilterNumber} from '../lib/helpers' import { formatFilterNumber } from "../lib/helpers";
import { import {
CATEGORY_FLAT, CATEGORY_FLAT,
CATEGORY_HOUSE, CATEGORY_HOUSE,
CATEGORY_OFFICE, CATEGORY_OFFICE,
CATEGORY_LAND CATEGORY_LAND
} from '../../../crawler/enums' } from "../../../crawler/enums";
export default class Filters extends React.Component { export default class Filters extends React.Component {
onCloseClick (e) { onCloseClick(e) {
if (this.props.onClose) { if (this.props.onClose) {
this.props.onClose() this.props.onClose();
} }
} }
onMaxPriceChange (e) { onMaxPriceChange(e) {
const maxPrice = e.target.value const maxPrice = e.target.value;
this.props.dispatch({ this.props.dispatch({
type: 'SET_MAX_PRICE', type: "SET_MAX_PRICE",
action: {maxPrice} action: { maxPrice }
}) });
} }
onMinPriceChange (e) { onMinPriceChange(e) {
const minPrice = e.target.value const minPrice = e.target.value;
this.props.dispatch({ this.props.dispatch({
type: 'SET_MIN_PRICE', type: "SET_MIN_PRICE",
action: {minPrice} action: { minPrice }
}) });
} }
onMaxSizeChange (e) { onMaxSizeChange(e) {
this.props.dispatch({ this.props.dispatch({
type: 'SET_MAX_SIZE', type: "SET_MAX_SIZE",
action: {maxSize: e.target.value} action: { maxSize: e.target.value }
}) });
} }
onMinSizeChange (e) { onMinSizeChange(e) {
this.props.dispatch({ this.props.dispatch({
type: 'SET_MIN_SIZE', type: "SET_MIN_SIZE",
action: {minSize: e.target.value} action: { minSize: e.target.value }
}) });
} }
onRoomsClick (rooms) { onRoomsClick(rooms) {
this.props.dispatch({ this.props.dispatch({
type: 'UPDATE_ROUTE', type: "UPDATE_ROUTE",
action: { action: {
params: {rooms} params: { rooms }
} }
}) });
this.props.dispatch({type: 'SET_ROOMS', action: {rooms}}) this.props.dispatch({ type: "SET_ROOMS", action: { rooms } });
} }
onCategoryClick (category) { onCategoryClick(category) {
this.props.dispatch({ this.props.dispatch({
type: 'UPDATE_ROUTE', type: "UPDATE_ROUTE",
action: { action: {
params: {category} params: { category }
} }
}) });
this.props.dispatch({type: 'SET_CATEGORY', action: {category}}) this.props.dispatch({ type: "SET_CATEGORY", action: { category } });
} }
onRefreshClick (closeFilters) { onRefreshClick(closeFilters) {
this.updateSearch() this.updateSearch();
if (closeFilters) { if (closeFilters) {
this.props.dispatch({ this.props.dispatch({
type: 'CLOSE_FILTERS' type: "CLOSE_FILTERS"
}) });
} }
} }
onKeyPress (e) { onKeyPress(e) {
if (e.key === 'Enter') { if (e.key === "Enter") {
this.updateSearch() this.updateSearch();
} }
} }
updateSearch () { updateSearch() {
const {minPrice, maxPrice, minSize, maxSize} = this.props.filters const { minPrice, maxPrice, minSize, maxSize } = this.props.filters;
this.props.dispatch({ this.props.dispatch({
type: 'UPDATE_ROUTE', type: "UPDATE_ROUTE",
action: { action: {
params: { params: {
minPrice, minPrice,
@@ -97,20 +97,20 @@ export default class Filters extends React.Component {
maxSize maxSize
} }
} }
}) });
this.props.dispatch({type: 'UPDATE_SEARCH'}) this.props.dispatch({ type: "UPDATE_SEARCH" });
} }
onResetSearch (e) { onResetSearch(e) {
this.props.dispatch({ this.props.dispatch({
type: 'RESET_FILTERS' type: "RESET_FILTERS"
}) });
} }
render () { render() {
const {filters} = this.props const { filters } = this.props;
const selectedRooms = val => filters.rooms[val] ? 'selected' : '' const selectedRooms = val => filters.rooms[val] ? "selected" : "";
const selectedCategory = val => filters.category[val] ? 'selected' : '' const selectedCategory = val => filters.category[val] ? "selected" : "";
return ( return (
<div className="filters"> <div className="filters">
@@ -135,7 +135,7 @@ export default class Filters extends React.Component {
onChange={this.onMinPriceChange.bind(this)} onChange={this.onMinPriceChange.bind(this)}
value={formatFilterNumber(filters.minPrice)} value={formatFilterNumber(filters.minPrice)}
/> />
{' '} {" "}
DO DO
<input <input
@@ -206,7 +206,7 @@ export default class Filters extends React.Component {
/> />
DO DO
{' '} {" "}
<input <input
onKeyPress={this.onKeyPress.bind(this)} onKeyPress={this.onKeyPress.bind(this)}
value={formatFilterNumber(filters.maxSize)} value={formatFilterNumber(filters.maxSize)}
@@ -247,8 +247,8 @@ export default class Filters extends React.Component {
3 3
</div> </div>
<div <div
onClick={this.onRoomsClick.bind(this, '4+')} onClick={this.onRoomsClick.bind(this, "4+")}
className={`filter-btn property-rooms-btn ${selectedRooms('4+')}`} className={`filter-btn property-rooms-btn ${selectedRooms("4+")}`}
> >
4+ 4+
</div> </div>
@@ -262,12 +262,18 @@ export default class Filters extends React.Component {
</div> </div>
<div className="clear-both" /> <div className="clear-both" />
<div className="filter-bottom"> <div className="filter-bottom">
<div onClick={this.onResetSearch.bind(this)} className="filter-btn">Poništi</div> <div onClick={this.onResetSearch.bind(this)} className="filter-btn">
<div onClick={this.onRefreshClick.bind(this, true)} Poništi
className="filter-btn confirm">Potvrdi</div> </div>
<div
onClick={this.onRefreshClick.bind(this, true)}
className="filter-btn confirm"
>
Potvrdi
</div>
</div> </div>
</div> </div>
) );
} }
} }

View File

@@ -0,0 +1,81 @@
import React from 'react'
import { pacSelectFirst } from '../helpers/googleMaps'
export default class Welcome extends React.Component {
constructor (props) {
super(props)
this.state = {
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 () {
this.setState({
type: 'SALE'
})
}
onRentClick () {
this.setState({
type: 'RENT'
})
}
render () {
return (
<div>
<div className='welcome-container-bg'>
</div>
<div className='welcome-container'>
<div className='welcome-content'>
<h1>KIVI</h1>
<h2>Pronađi svoj novi dom!</h2>
<button
onClick={this.onSaleClick.bind(this)}>Kupovina</button>
<button onClick={this.onRentClick.bind(this)}>Iznajmljivanje</button>
<input
type='text'
placeholder='Unesite adresu, naselje ili grad'
className='where-to'
id='gmaps-places-input-welcome'
/>
</div>
</div>
</div>
)
}
}

View File

@@ -1,9 +1,9 @@
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'
const getInitialState = url => { const getInitialState = url => {
console.log('PARSING URL:', url)
const params = window.location.search.substr(1).split('&') const params = window.location.search.substr(1).split('&')
const initialState = { const initialState = {
@@ -13,16 +13,15 @@ const getInitialState = url => {
for (const param of params) { for (const param of params) {
const [key, value] = param.split('=') const [key, value] = param.split('=')
console.log('analyzing param ', key, value)
if (key === 'rooms' && value !== '') { if (key === 'rooms' && value !== '') {
console.log("IT's ROOMS") initialState.rooms = {}
value.split(',').forEach(k => { value.split(',').forEach(k => {
console.log("IT's ROOMS", k)
initialState.rooms[parseInt(k)] = true initialState.rooms[parseInt(k)] = true
}) })
} }
if (key === 'category' && value !== '') { if (key === 'category' && value !== '') {
initialState.category = {}
value.split(',').forEach(k => { value.split(',').forEach(k => {
initialState.category[parseInt(k)] = true initialState.category[parseInt(k)] = true
}) })
@@ -40,6 +39,10 @@ const getInitialState = url => {
initialState.listingId = value initialState.listingId = value
} }
if (key === 'type') {
initialState.type = value
}
if (key === 'zoom') { if (key === 'zoom') {
initialState.zoom = parseInt(value) initialState.zoom = parseInt(value)
} }
@@ -52,6 +55,25 @@ const getInitialState = url => {
return initialState return initialState
} }
const main = <Main initialState={getInitialState(window.location)} /> const root = document.getElementById('root')
const initialState = getInitialState(window.location)
render(main, document.getElementById('root')) const renderMain = (additionalState = {}) => {
const main = <Main initialState={{...initialState, ...additionalState}} />
render(main, root)
}
if (Object.keys(initialState).length === 2 &&
window.localStorage.getItem('lastLoad') == null) {
const onSearch = ({bounds, type, location}) => {
window.location = `/?bounds=${bounds}&type=${type}`
//renderMain({
//bounds,
//type
//})
}
const welcome = <Welcome onSearch={onSearch} />
render(welcome, root)
} else {
renderMain()
}

View File

@@ -71,3 +71,4 @@ export const loadSeen = id => {
return seen return seen
//return seen.findIndex(s => s === id) !== -1; //return seen.findIndex(s => s === id) !== -1;
} }