Contact form UI
This commit is contained in:
69
web/dist/main.css
vendored
69
web/dist/main.css
vendored
@@ -757,7 +757,8 @@ html {
|
|||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ld-check-availability button {
|
.ld-check-availability button,
|
||||||
|
.contact-form button {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
background-color: #51bc6a;
|
background-color: #51bc6a;
|
||||||
@@ -858,7 +859,7 @@ h5 {
|
|||||||
|
|
||||||
.modal h3 {
|
.modal h3 {
|
||||||
color: #575a60;
|
color: #575a60;
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: .3px;
|
letter-spacing: .3px;
|
||||||
}
|
}
|
||||||
@@ -875,6 +876,66 @@ h5 {
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close:hover {
|
.contact-form input, textarea {
|
||||||
/*background: #00d9ff;*/
|
padding: 10px;
|
||||||
|
border: 1px solid #e2e2e6;
|
||||||
|
width: 420px;
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #212126;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-email-phone input {
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-email-phone input:first-child {
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-email-phone input:last-child {
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-name,
|
||||||
|
.contact-form-message,
|
||||||
|
.contact-form-email-phone {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-alert input {
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-alert span {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-alert {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noselect {
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome and Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
input.validation-failed {
|
||||||
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,124 @@
|
|||||||
import React from 'react';
|
import React from 'react'
|
||||||
|
|
||||||
export default class ContactModal extends React.Component {
|
export default class ContactModal extends React.Component {
|
||||||
onContactCloseClick () {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onContactCloseClick (e) {
|
onContactCloseClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'CLOSE_CONTACT'
|
type: 'CLOSE_CONTACT'
|
||||||
});
|
})
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmit (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const {name, email} = this.props.contact;
|
||||||
|
|
||||||
|
if (!name || !email) {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'INVALID_CONTACT'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'SUBMIT_CONTACT'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFieldChange (field, e) {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'UPDATE_CONTACT_INFO',
|
||||||
|
action: {
|
||||||
|
field,
|
||||||
|
value: e.target.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAlertToggle (e) {
|
||||||
|
const alert = this.props.contact.alert
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'UPDATE_CONTACT_INFO',
|
||||||
|
action: {
|
||||||
|
field: 'alert',
|
||||||
|
value: !alert
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
message,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
name,
|
||||||
|
alert: doAlert,
|
||||||
|
nameInvalid,
|
||||||
|
emailInvalid
|
||||||
|
} = this.props.contact
|
||||||
|
|
||||||
|
const nameValidationClass = nameInvalid ? 'validation-failed' : ''
|
||||||
|
const emailValidationClass = emailInvalid ? 'validation-failed' : ''
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<div className="modal">
|
<div className="modal contact-form">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href="#close" title="Zatvori" className="close" onClick={this.onContactCloseClick.bind(this)}>
|
<a
|
||||||
<i className="fa fa-times" aria-hidden="true"></i>
|
href="#close"
|
||||||
|
title="Zatvori"
|
||||||
|
className="close"
|
||||||
|
onClick={this.onContactCloseClick.bind(this)}
|
||||||
|
>
|
||||||
|
<i className="fa fa-times" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<h3>Kontaktirajte prodavca</h3>
|
<form onSubmit={this.onSubmit.bind(this)}>
|
||||||
|
<h3>Kontaktirajte prodavca</h3>
|
||||||
|
<div className="contact-form-name">
|
||||||
|
<input
|
||||||
|
value={name}
|
||||||
|
className={nameValidationClass}
|
||||||
|
onChange={this.onFieldChange.bind(this, 'name')}
|
||||||
|
placeholder="Ime i prezime"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="contact-form-email-phone">
|
||||||
|
<input
|
||||||
|
value={email}
|
||||||
|
className={emailValidationClass}
|
||||||
|
onChange={this.onFieldChange.bind(this, 'email')}
|
||||||
|
placeholder="Email adresa"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
value={phone}
|
||||||
|
onChange={this.onFieldChange.bind(this, 'phone')}
|
||||||
|
placeholder="Telefon (opcionalno)"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="contact-form-message">
|
||||||
|
<textarea
|
||||||
|
onChange={this.onFieldChange.bind(this, 'message')}
|
||||||
|
value={message}
|
||||||
|
rows="14"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="contact-form-alert noselect">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
onChange={this.onAlertToggle.bind(this)}
|
||||||
|
checked={doAlert}
|
||||||
|
/>
|
||||||
|
<span onClick={this.onAlertToggle.bind(this)}>
|
||||||
|
Obavjesti me ukoliko se slična nekretnine pojavi
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="contact-form-footer">
|
||||||
|
<button>Pošalji poruku</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ export default class ListingDetails extends React.Component {
|
|||||||
<div className="ld-footer" />
|
<div className="ld-footer" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{contactFormOpen ? <ContactModal dispatch={this.props.dispatch} /> : null}
|
{contactFormOpen ? <ContactModal
|
||||||
|
contact={this.props.contact}
|
||||||
|
dispatch={this.props.dispatch} /> : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ class Main extends React.Component {
|
|||||||
filters: {
|
filters: {
|
||||||
rooms: {},
|
rooms: {},
|
||||||
category: {}
|
category: {}
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
message: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
valid: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,6 +523,7 @@ class Main extends React.Component {
|
|||||||
children.push(
|
children.push(
|
||||||
<ListingDetails
|
<ListingDetails
|
||||||
contactFormOpen={this.state.contactFormOpen}
|
contactFormOpen={this.state.contactFormOpen}
|
||||||
|
contact={this.state.contact}
|
||||||
listing={listing}
|
listing={listing}
|
||||||
imageIndex={this.state.imageIndex}
|
imageIndex={this.state.imageIndex}
|
||||||
dispatch={this.dispatch.bind(this)}
|
dispatch={this.dispatch.bind(this)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {markSeen} from './api'
|
import {markSeen} from './api'
|
||||||
|
import {defaultContactMessage, listingUrl} from './helpers'
|
||||||
|
|
||||||
const setMaxPrice = ({type, action}, component) => {
|
const setMaxPrice = ({type, action}, component) => {
|
||||||
const maxPrice = parseFloat(action.maxPrice)
|
const maxPrice = parseFloat(action.maxPrice)
|
||||||
@@ -52,8 +53,6 @@ const viewListingDetails = ({type, action}, component) => {
|
|||||||
const scrollElem = document.querySelector('.right-content')
|
const scrollElem = document.querySelector('.right-content')
|
||||||
component.savedScrollTop = scrollElem.scrollTop
|
component.savedScrollTop = scrollElem.scrollTop
|
||||||
|
|
||||||
//component.router.listingId = action.id;
|
|
||||||
|
|
||||||
component.setState(
|
component.setState(
|
||||||
{
|
{
|
||||||
listingDetails: true,
|
listingDetails: true,
|
||||||
@@ -63,7 +62,6 @@ const viewListingDetails = ({type, action}, component) => {
|
|||||||
listing: action.listing
|
listing: action.listing
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
//component.router.update();
|
|
||||||
markSeen(action.id)
|
markSeen(action.id)
|
||||||
const m = component.findMarker(action.id)
|
const m = component.findMarker(action.id)
|
||||||
if (m) {
|
if (m) {
|
||||||
@@ -221,8 +219,6 @@ const backToResults = ({type, action}, component) => {
|
|||||||
listingDetails: false
|
listingDetails: false
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
//component.router.update();
|
|
||||||
|
|
||||||
if (prevSelected) {
|
if (prevSelected) {
|
||||||
prevSelected.marker.setIcon(component.visitedMarkerIcon())
|
prevSelected.marker.setIcon(component.visitedMarkerIcon())
|
||||||
}
|
}
|
||||||
@@ -268,7 +264,6 @@ const sortChange = ({type, action}, component) => {
|
|||||||
page: 0
|
page: 0
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
//component.router.update();
|
|
||||||
component.refreshListings()
|
component.refreshListings()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -280,14 +275,59 @@ const updateRoute = ({type, action}, component) => {
|
|||||||
|
|
||||||
const openContact = ({type, action}, component) => {
|
const openContact = ({type, action}, component) => {
|
||||||
component.setState({
|
component.setState({
|
||||||
contactFormOpen: true
|
contactFormOpen: true,
|
||||||
});
|
contact: {
|
||||||
|
...component.state.contact,
|
||||||
|
message: defaultContactMessage(listingUrl(component.state.listingId)),
|
||||||
|
emailInvalid: false,
|
||||||
|
nameInvalid: false,
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeContact = ({type, action}, component) => {
|
const closeContact = ({type, action}, component) => {
|
||||||
component.setState({
|
component.setState({
|
||||||
contactFormOpen: false
|
contactFormOpen: false
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateContactInfo = ({type, action}, component) => {
|
||||||
|
|
||||||
|
let nameInvalid = component.state.contact.nameInvalid
|
||||||
|
let emailInvalid = component.state.contact.emailInvalid
|
||||||
|
|
||||||
|
if (action.field === 'name') {
|
||||||
|
nameInvalid = !action.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.field === 'email') {
|
||||||
|
emailInvalid = !action.value
|
||||||
|
}
|
||||||
|
|
||||||
|
component.setState({
|
||||||
|
contact: {
|
||||||
|
...component.state.contact,
|
||||||
|
[action.field]: action.value,
|
||||||
|
...{nameInvalid, emailInvalid}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidContact = ({type, action}, component) => {
|
||||||
|
const {name, email} = component.state.contact
|
||||||
|
|
||||||
|
component.setState({
|
||||||
|
contact: {
|
||||||
|
...component.state.contact,
|
||||||
|
...{nameInvalid: !name, emailInvalid: !email}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitContact = ({type, action}, component) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
@@ -313,7 +353,10 @@ const handlers = {
|
|||||||
SORT_CHANGE: sortChange,
|
SORT_CHANGE: sortChange,
|
||||||
UPDATE_ROUTE: updateRoute,
|
UPDATE_ROUTE: updateRoute,
|
||||||
OPEN_CONTACT: openContact,
|
OPEN_CONTACT: openContact,
|
||||||
CLOSE_CONTACT: closeContact
|
CLOSE_CONTACT: closeContact,
|
||||||
|
UPDATE_CONTACT_INFO: updateContactInfo,
|
||||||
|
SUBMIT_CONTACT: submitContact,
|
||||||
|
INVALID_CONTACT: invalidContact
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleMessage = ({type, action}, component) => {
|
export const handleMessage = ({type, action}, component) => {
|
||||||
@@ -321,6 +364,6 @@ export const handleMessage = ({type, action}, component) => {
|
|||||||
throw new `Unhandled message: ${type}`()
|
throw new `Unhandled message: ${type}`()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(type);
|
console.log(type, action);
|
||||||
return handlers[type]({type, action}, component)
|
return handlers[type]({type, action}, component)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,21 @@ export const galleryImageUrl = img =>
|
|||||||
|
|
||||||
export const listingImageUrl = img =>
|
export const listingImageUrl = img =>
|
||||||
img && img.replace('upload/', 'upload/w_205/')
|
img && img.replace('upload/', 'upload/w_205/')
|
||||||
|
|
||||||
|
export const defaultContactMessage = (url) => {
|
||||||
|
return `Pozdrav,
|
||||||
|
|
||||||
|
Našao/Našla sam vaš oglas na portalu Kivi za sljedeću nekretninu:
|
||||||
|
|
||||||
|
${url}
|
||||||
|
|
||||||
|
Želim da me kontaktirate kako bih dobio/dobila više informacija.
|
||||||
|
|
||||||
|
S poštovanjem
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listingUrl = (id) => {
|
||||||
|
// TODO: fix this once removing hardcoded values
|
||||||
|
return `http://localhost:8080/?listingId=${id}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user