basic frontend demo with react and redux

This commit is contained in:
egradanin
2019-01-12 14:09:17 +01:00
parent 204985db42
commit a431c58763
53 changed files with 19185 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import React from "react";
import Select from "react-select";
import { connect } from "react-redux";
import { CATEGORY_SELECT, ITEMS_CHANGED } from "constants/actionTypes";
import { hoc } from "utils/hoc";
import { createOlxLink } from "utils/createOlxLink";
import axios from "axios";
import Vozila from "./categories/Vozila";
import Nekretnine from "./categories/Nekretnine";
import ItemsContainer from "./items/itemscontainer/ItemsContainer";
const options = [
{ value: "Vozila", label: "Vozila" },
{ value: "Nekretnine", label: "Nekretnine" }
];
const mapStateToProps = state => {
return {
category: state.category,
options: state.options,
subcategory: state.subcategory,
items: state.items
};
};
const mapDispatchToProps = dispatch => ({
onCategoryChanged: option => dispatch({ type: CATEGORY_SELECT, option }),
onItemsChanged: items => dispatch({ type: ITEMS_CHANGED, items })
});
class App extends React.Component {
handleChange = selectedOption => {
this.props.onCategoryChanged(selectedOption);
};
getDataFromOlx = () => {
const { category, options, subcategory, onItemsChanged } = this.props;
let url = createOlxLink(category, subcategory, options);
url = encodeURI(url);
axios
.get(`/api/${url}`)
.then(response => onItemsChanged(response.data))
.catch(error => console.log(error));
};
render() {
const { category } = this.props;
return (
<div>
<Select
value={category}
onChange={this.handleChange}
options={options}
/>
{hoc(category && category.value, {
Vozila: <Vozila />,
Nekretnine: <Nekretnine />
})}
<button onClick={this.getDataFromOlx}>Get Data from OLX </button>
<ItemsContainer />
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

View File

@@ -0,0 +1,38 @@
import React from "react";
import Select from "react-select";
import { subcategorywrapper } from "utils/subcategorywrapper";
import { hoc } from "utils/hoc";
import Stanovi from "../subcategories/nekretnine/Stanovi";
import Kuce from "../subcategories/nekretnine/Kuce";
const options = [
{ value: "Stanovi", label: "Stanovi" },
{ value: "Kuce", label: "Kuce" }
];
class Nekretnine extends React.Component {
handleChange = selectedOption => {
this.props.onSubCategoryChanged(selectedOption);
};
render() {
const { subcategory } = this.props;
return (
<div>
<Select
value={subcategory}
onChange={this.handleChange}
options={options}
/>
{hoc(subcategory && subcategory.value, {
Stanovi: <Stanovi />,
Kuce: <Kuce />
})}
</div>
);
}
}
export default subcategorywrapper(Nekretnine);

View File

@@ -0,0 +1,37 @@
import React from "react";
import Select from "react-select";
import Automobili from "../subcategories/vozila/Automobili";
import Motocikli from "../subcategories/vozila/Motocikli";
import { subcategorywrapper } from "utils/subcategorywrapper";
import { hoc } from "utils/hoc";
const options = [
{ value: "Automobili", label: "Automobili" },
{ value: "Motocikli", label: "Motocikli" }
];
class Vozila extends React.Component {
handleChange = selectedOption => {
this.props.onSubCategoryChanged(selectedOption);
};
render() {
const { subcategory } = this.props;
return (
<div>
<Select
value={subcategory}
onChange={this.handleChange}
options={options}
/>
{hoc(subcategory && subcategory.value, {
Automobili: <Automobili />,
Motocikli: <Motocikli />
})}
</div>
);
}
}
export default subcategorywrapper(Vozila);

View File

@@ -0,0 +1,6 @@
export const rangeOptions = {
min: 0,
max: 100000,
defaultValues: [0, 100000],
step: 100
};

View File

@@ -0,0 +1,59 @@
import React from "react";
import CheckboxAndRadioWrapper from "components/widgets/CheckboxAndRadioWrapper";
const elements = [
{
type: "checkbox",
name: "Uknjizeno",
optionName: "uknjizeno"
},
{
type: "checkbox",
name: "Namjesteno",
optionName: "namjesteno"
},
{
type: "checkbox",
name: "Nedavno adaptirano",
optionName: "nedavno_adaptirano"
},
{
type: "checkbox",
name: "Garaza",
optionName: "garaza"
},
{
type: "checkbox",
name: "Balkon",
optionName: "balkon"
},
{
type: "checkbox",
name: "Voda",
optionName: "voda"
},
{
type: "checkbox",
name: "Plin",
optionName: "plin"
},
{
type: "checkbox",
name: "Bazen",
optionName: "bazen"
}
];
class KuceFilter extends React.Component {
render() {
return (
<div>
<span>Dodatno za kuce</span>
<CheckboxAndRadioWrapper
componentName="dodatno-za-kuce"
elements={elements}
/>
</div>
);
}
}
export default KuceFilter;

View File

@@ -0,0 +1,8 @@
export const lokacijaOptions = {
choices: [
{ value: "9", label: "Sarajevo" },
{ value: "2", label: "Posavski" }
],
value: "kanton",
optionName: "kanton"
};

View File

@@ -0,0 +1,65 @@
import React from "react";
import CheckboxAndRadioWrapper from "components/widgets/CheckboxAndRadioWrapper";
const elements = [
{
type: "checkbox",
name: "Novogradnja",
optionName: "novogradnja"
},
{
type: "checkbox",
name: "Namjesten",
optionName: "namjesten"
},
{
type: "checkbox",
name: "Nedavno adaptiran",
optionName: "Nedavno_adaptiran"
},
{
type: "checkbox",
name: "Uknjizeno",
optionName: "uknjizeno"
},
{
type: "checkbox",
name: "Lift",
optionName: "lift"
},
{
type: "checkbox",
name: "Balkon",
optionName: "balkon"
},
{
type: "checkbox",
name: "Parking",
optionName: "parking"
},
{
type: "checkbox",
name: "Plin",
optionName: "plin"
},
{
type: "checkbox",
name: "kablovska",
optionName: "kablovska"
}
];
class StanoviFilter extends React.Component {
render() {
return (
<div>
<span>Dodatno za stan</span>
<CheckboxAndRadioWrapper
componentName="dodatno-za-stan"
elements={elements}
/>
</div>
);
}
}
export default StanoviFilter;

View File

@@ -0,0 +1,6 @@
export const rangeOptions = {
min: 0,
max: 1000,
defaultValues: [0, new Date().getFullYear()],
step: 1
};

View File

@@ -0,0 +1,26 @@
export const elements = [
{
value: "",
name: "Sve",
type: "radio",
optionName: "vrsta"
},
{
value: "samoprodaja",
name: "Prodaja",
type: "radio",
optionName: "vrsta"
},
{
value: "samoizdavanje",
name: "Iznajmljivanje",
type: "radio",
optionName: "vrsta"
},
{
value: "samopotraznja",
name: "Potražnja",
type: "radio",
optionName: "vrsta"
}
];

View File

@@ -0,0 +1,25 @@
import React from "react";
import * as Vrsta from "./Vrsta";
import * as Lokacija from "./Lokacija";
import * as Cijena from "./Cijena";
import * as Velicina from "./Velicina";
import CheckboxAndRadioWrapper from "components/widgets/CheckboxAndRadioWrapper";
import RangeWrapper from "components/widgets/RangeWrapper";
import SelectWrapper from "components/widgets/SelectWrapper";
class NekretnineFilter extends React.Component {
render() {
return (
<div>
<CheckboxAndRadioWrapper
componentName="vrsta"
elements={Vrsta.elements}
/>
<SelectWrapper {...Lokacija.lokacijaOptions} />
<RangeWrapper {...Cijena.rangeOptions} optionName="cijena" />
<RangeWrapper {...Velicina.rangeOptions} optionName="velicina" />
</div>
);
}
}
export default NekretnineFilter;

View File

@@ -0,0 +1,6 @@
export const rangeOptions = {
min: 0,
max: 100000,
defaultValues: [0, 100000],
step: 100
};

View File

@@ -0,0 +1,6 @@
export const rangeOptions = {
min: 1960,
max: new Date().getFullYear(),
defaultValues: [1960, new Date().getFullYear()],
step: 1
};

View File

@@ -0,0 +1,27 @@
export const elements = [
{
name: "Dizel",
optionName: "gorivo_select_dizel",
type: "checkbox"
},
{
name: "Benzin",
optionName: "gorivo_select_benzin",
type: "checkbox"
},
{
name: "Plin",
optionName: "gorivo_select_plin",
type: "checkbox"
},
{
name: "Hibrid",
optionName: "gorivo_select_hibrid",
type: "checkbox"
},
{
name: "Elektro",
optionName: "gorivo_select_elektro",
type: "checkbox"
}
];

View File

@@ -0,0 +1,15 @@
export const kilometrazaOptions = {
kilometraMin: {
choices: [{ value: 5000, label: "5000" }, { value: 10000, label: "10000" }],
value: "kilometrazaMin",
optionName: "kilometrazaMin"
},
kilometraMax: {
choices: [
{ value: 15000, label: "15000" },
{ value: 200000, label: "200000" }
],
value: "kilometrazaMax",
optionName: "kilometrazaMax"
}
};

View File

@@ -0,0 +1,8 @@
export const lokacijaOptions = {
choices: [
{ value: "9", label: "Sarajevo" },
{ value: "2", label: "Posavski" }
],
value: "kanton",
optionName: "kanton"
};

View File

@@ -0,0 +1,5 @@
export const proizvodacOptions = {
choices: [{ value: "1900", label: "Audi" }, { value: "9000", label: "Ford" }],
value: "proizvodac",
optionName: "proizvodac"
};

View File

@@ -0,0 +1,25 @@
export const elements = [
{
type: "radio",
value: "",
name: "Nova i polovna vozila",
optionName: "stanje"
},
{
type: "radio",
value: 1,
name: "Nova",
optionName: "stanje"
},
{
type: "radio",
value: 2,
name: "Polovna vozila",
optionName: "stanje"
},
{
type: "checkbox",
name: "Udarena vozila",
optionName: "udaren_checkbox"
}
];

View File

@@ -0,0 +1,35 @@
import React from "react";
import * as Stanje from "./Stanje";
import * as Proizvodac from "./Proizvodac";
import * as Cijena from "./Cijena";
import * as Lokacija from "./Lokacija";
import * as Godiste from "./Godiste";
import * as Kilometraza from "./Kilometraza";
import * as Gorivo from "./Gorivo";
import CheckboxAndRadioWrapper from "components/widgets/CheckboxAndRadioWrapper";
import RangeWrapper from "components/widgets/RangeWrapper";
import SelectWrapper from "components/widgets/SelectWrapper";
class VozilaFilter extends React.Component {
render() {
return (
<div>
<CheckboxAndRadioWrapper
componentName="stanje"
elements={Stanje.elements}
/>
<SelectWrapper {...Proizvodac.proizvodacOptions} />
<RangeWrapper {...Cijena.rangeOptions} optionName="cijena" />
<SelectWrapper {...Lokacija.lokacijaOptions} />
<RangeWrapper {...Godiste.rangeOptions} optionName="godiste" />
<SelectWrapper {...Kilometraza.kilometrazaOptions.kilometraMin} />
<SelectWrapper {...Kilometraza.kilometrazaOptions.kilometraMax} />
<CheckboxAndRadioWrapper
componentName="gorivo"
elements={Gorivo.elements}
/>
</div>
);
}
}
export default VozilaFilter;

View File

@@ -0,0 +1,36 @@
.item {
display: grid;
border: solid #877ad2 1px;
border-radius: 10px;
max-width: 360px;
min-height: 300px;
grid-template-areas:
"image"
"title";
margin: 3%;
background-color: #dbdbd7;
}
.item > a > img {
width: 70%;
height: 60%;
max-height: 200px;
grid-area: image;
padding: 8% 8% 0 8%;
}
.item > a {
text-decoration: none;
}
.basic-info {
grid-area: title;
width: 85%;
margin: 0 auto;
}
.basic-info > h3 {
font-size: 90%;
margin: 5%;
margin-bottom: 0px;
color: #332390;
overflow-wrap: break-word;
}

View File

@@ -0,0 +1,20 @@
import React, { Component } from "react";
import "./ItemCard.css";
class ItemCard extends Component {
render() {
const { url, price, image } = this.props;
return (
<div className="item">
<a href={url}>
<img alt={image} src={image} />
<div className="basic-info">
<h3>{price}</h3>
</div>
</a>
</div>
);
}
}
export default ItemCard;

View File

@@ -0,0 +1,8 @@
.items-list {
margin: 5%;
margin-top: 8%;
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
grid-template-rows: repeat(2, 320px);
}

View File

@@ -0,0 +1,29 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import ItemCard from "../itemcard/ItemCard";
import "./ItemsContainer.css";
const mapStateToProps = state => {
return {
items: state.items
};
};
class ItemContainer extends Component {
renderItems() {
let items = this.props.items;
return items.map((item, index) => {
return <ItemCard {...item} key={index} />;
});
}
render() {
return (
<div>
<div className="items-list">{this.renderItems()}</div>
</div>
);
}
}
export default connect(mapStateToProps)(ItemContainer);

View File

@@ -0,0 +1,15 @@
import React from "react";
import NekretnineFilter from "components/filters/NekretnineFilter/index";
import KuceFilter from "components/filters/NekretnineFilter/KuceFilter/index";
class Kuce extends React.Component {
render() {
return (
<div>
<span>Kuce</span>
<NekretnineFilter />
<KuceFilter />
</div>
);
}
}
export default Kuce;

View File

@@ -0,0 +1,16 @@
import React from "react";
import NekretnineFilter from "components/filters/NekretnineFilter/index";
import StanoviFilter from "components/filters/NekretnineFilter/StanoviFilter/index";
class Stanovi extends React.Component {
render() {
return (
<div>
<span>Stanovi</span>
<NekretnineFilter />
<StanoviFilter />
</div>
);
}
}
export default Stanovi;

View File

@@ -0,0 +1,14 @@
import React from "react";
import VozilaFilter from "components/filters/VozilaFilter/index";
class Automobili extends React.Component {
render() {
return (
<div>
<span>Auto</span>
<VozilaFilter />
</div>
);
}
}
export default Automobili;

View File

@@ -0,0 +1,14 @@
import React from "react";
import VozilaFilter from "components/filters/VozilaFilter/index";
class Motocikli extends React.Component {
render() {
return (
<div>
<span>Motocikli</span>
<VozilaFilter />
</div>
);
}
}
export default Motocikli;

View File

@@ -0,0 +1,48 @@
import React from "react";
import { optionchangewrapper } from "utils/optionchangewrapper";
class CheckboxAndRadioWrapper extends React.Component {
optionChange = (option, optionName, type) => {
const optionTypePicker = {
radio: option.target.value,
checkbox: option.target.checked
};
const { onOptionChanged } = this.props;
onOptionChanged({
optionName,
optionValue: optionTypePicker[type]
});
};
isChecked = (type, value, optionName) => {
const { options } = this.props;
return type === "checkbox"
? value
: options.hasOwnProperty(optionName) &&
options[optionName] === String(value);
};
renderElements = (elements, componentName) => {
return elements.map(({ type, value, name, optionName } = {}) => (
<label key={name + type}>
<input
name={type === "radio" ? `${componentName}-radio` : ""}
type={type}
value={value}
checked={this.isChecked(type, value, optionName)}
onChange={option => this.optionChange(option, optionName, type)}
/>
{name}
</label>
));
};
render() {
const { elements, componentName } = this.props;
return (
<div>
<span>{componentName}</span>
{this.renderElements(elements, componentName)}
</div>
);
}
}
export default optionchangewrapper(CheckboxAndRadioWrapper);

View File

@@ -0,0 +1,52 @@
import React from "react";
import { Range } from "rc-slider";
import { optionchangewrapper } from "utils/optionchangewrapper";
import "rc-slider/assets/index.css";
class RangeWrapper extends React.Component {
handleRangeChange = ([min, max] = this.props.defaultValues) => {
this.inputMin.value = min;
this.inputMax.value = max;
const { onOptionChanged, optionName } = this.props;
onOptionChanged({
optionName,
optionValue: [min, max]
});
};
handleInputChange = () => {
const { onOptionChanged, optionName } = this.props;
onOptionChanged({
optionName,
optionValue: [this.inputMin.value, this.inputMax.value]
});
};
render() {
const { step, defaultValues, min, max } = this.props;
return (
<div>
<Range
min={min}
max={max}
defaultValue={defaultValues}
step={step}
onAfterChange={this.handleRangeChange}
/>
<input
ref={node => {
this.inputMin = node;
}}
onChange={this.handleInputChange}
/>
<input
ref={node => {
this.inputMax = node;
}}
onChange={this.handleInputChange}
/>
</div>
);
}
}
export default optionchangewrapper(RangeWrapper);

View File

@@ -0,0 +1,25 @@
import React from "react";
import Select from "react-select";
import { optionchangewrapper } from "utils/optionchangewrapper";
class SelectWrapper extends React.Component {
handleOptionChange = selectedOption => {
const { onOptionChanged, optionName } = this.props;
onOptionChanged({
optionName,
optionValue: selectedOption
});
};
render() {
let { value, options, choices } = this.props;
return (
<Select
value={options[value]}
onChange={this.handleOptionChange}
options={choices}
/>
);
}
}
export default optionchangewrapper(SelectWrapper);