Search query / results cards
This commit is contained in:
17
src/App.js
17
src/App.js
@@ -1,10 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import logo from "./public/logo_mint.svg";
|
|
||||||
import "./App.scss";
|
import "./App.scss";
|
||||||
import TopBar from "./components/TopBar";
|
import TopBar from "./components/TopBar";
|
||||||
|
import LogoMain from "./components/logoMain";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
import SearchPanel from "./components/SearchPanel";
|
import SearchPanel from "./components/SearchPanel";
|
||||||
|
import SearchResults from "./components/SearchResults";
|
||||||
import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
|
import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@@ -15,16 +17,21 @@ const theme = createMuiTheme({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const appMode = useSelector((store) => store.appMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<div className="main-container">
|
<div className="main-container">
|
||||||
<img src={logo} className="logo" alt="Menui logo" />
|
<LogoMain />
|
||||||
<SearchPanel />
|
<SearchPanel />
|
||||||
<p className="darkParagraph">
|
{appMode === "init" && (
|
||||||
Sprawdź co serwuje Twoja ulubiona restauracja.
|
<p className="darkParagraph">
|
||||||
</p>
|
Sprawdź co serwuje Twoja ulubiona restauracja.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<SearchResults />
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@import "./styles/TopBar.scss";
|
@import "./styles/TopBar.scss";
|
||||||
@import "./styles/SearchPanel.scss";
|
@import "./styles/SearchPanel.scss";
|
||||||
@import "./styles/Footer.scss";
|
@import "./styles/Footer.scss";
|
||||||
|
@import "./styles/SearchResults.scss";
|
||||||
|
|
||||||
.App {
|
.App {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -38,4 +39,5 @@ p {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const clearAutocomplete = () => {
|
|||||||
export const fetchAutocomplete = (input) => {
|
export const fetchAutocomplete = (input) => {
|
||||||
return function (dispatch) {
|
return function (dispatch) {
|
||||||
axios
|
axios
|
||||||
.get("http://localhost:4000/search/autocomplete?string=" + input)
|
.get(
|
||||||
|
"http://localhost:4000/search/autocomplete?string=" + encodeURI(input)
|
||||||
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const cities = Array.from(response.data.cities);
|
const cities = Array.from(response.data.cities);
|
||||||
const restaurants = Array.from(response.data.restaurants);
|
const restaurants = Array.from(response.data.restaurants);
|
||||||
@@ -30,6 +32,37 @@ export const fetchAutocomplete = (input) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchSearch = (input) => {
|
||||||
|
return function (dispatch) {
|
||||||
|
axios
|
||||||
|
.get("http://localhost:4000/search?string=" + encodeURI(input))
|
||||||
|
.then((response) => {
|
||||||
|
const data = response.data;
|
||||||
|
if (Object.keys(data).length > 0) {
|
||||||
|
dispatch(setSearchResults(data));
|
||||||
|
dispatch(setAppMode("APP_SEARCH_RESULTS"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSearchResults = (input) => {
|
||||||
|
return {
|
||||||
|
type: "SEARCH_RESULTS",
|
||||||
|
payload: input,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSearchQuery = (input) => {
|
||||||
|
return {
|
||||||
|
type: "SEARCH_QUERY_SET",
|
||||||
|
payload: input,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setAppMode = (mode) => {
|
export const setAppMode = (mode) => {
|
||||||
return {
|
return {
|
||||||
type: mode,
|
type: mode,
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ const StylizedButton = withStyles({
|
|||||||
})(Button);
|
})(Button);
|
||||||
|
|
||||||
export default function ButtonSecondary(props) {
|
export default function ButtonSecondary(props) {
|
||||||
return <StylizedButton>{props.text}</StylizedButton>;
|
return <StylizedButton onClick={props.onClick}>{props.text}</StylizedButton>;
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/components/CardRestaurant.js
Normal file
18
src/components/CardRestaurant.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function CardRestaurant(props) {
|
||||||
|
return (
|
||||||
|
<div className="card-restaurant">
|
||||||
|
<div className="card-img"></div>
|
||||||
|
<div className="card-info">
|
||||||
|
<h1>{props.name}</h1>
|
||||||
|
<hr />
|
||||||
|
<h3>Miasto: {props.city}</h3>
|
||||||
|
<h3>Godziny otwarcia: {props.hours}</h3>
|
||||||
|
<p>
|
||||||
|
Jakiś krótki opis restauracji. Coś tam że jest przytulnie i elegancko.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ import ButtonSecondary from "./ButtonSecondary";
|
|||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Autocomplete from "@material-ui/lab/Autocomplete";
|
import Autocomplete from "@material-ui/lab/Autocomplete";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { fetchAutocomplete } from "../actions";
|
import { fetchAutocomplete, setSearchQuery, fetchSearch } from "../actions";
|
||||||
|
|
||||||
export default function SearchPanel() {
|
export default function SearchPanel() {
|
||||||
let options = useSelector((store) => store.autocomplete);
|
let options = useSelector((store) => store.autocomplete);
|
||||||
|
let searchQuery = useSelector((store) => store.searchQuery);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -15,18 +16,23 @@ export default function SearchPanel() {
|
|||||||
options={options}
|
options={options}
|
||||||
style={{ width: 300 }}
|
style={{ width: 300 }}
|
||||||
noOptionsText="Brak podpowiedzi"
|
noOptionsText="Brak podpowiedzi"
|
||||||
|
onChange={(event) => dispatch(setSearchQuery(event.target.textContent))}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="Miasto, Nazwa lokalu, ..."
|
label="Miasto, Nazwa lokalu, ..."
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
onChange={(event) => dispatch(setSearchQuery(event.target.value))}
|
||||||
onInput={(event) => dispatch(fetchAutocomplete(event.target.value))}
|
onInput={(event) => dispatch(fetchAutocomplete(event.target.value))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="btnContainer">
|
<div className="btnContainer">
|
||||||
<ButtonSecondary text="Szukaj" />
|
<ButtonSecondary
|
||||||
|
onClick={() => dispatch(fetchSearch(searchQuery))}
|
||||||
|
text="Szukaj"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
28
src/components/SearchResults.js
Normal file
28
src/components/SearchResults.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
import CardRestaurant from "./CardRestaurant";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
export default function SearchResults() {
|
||||||
|
var results = useSelector((store) => store.searchResults);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="search-results">
|
||||||
|
<CardRestaurant
|
||||||
|
name="Kuchnie Świata"
|
||||||
|
city="Mikołajki"
|
||||||
|
hours="7:00 - 23:00"
|
||||||
|
/>
|
||||||
|
<CardRestaurant
|
||||||
|
name="Grzmiące Patyki"
|
||||||
|
city="Ciechanów"
|
||||||
|
hours="7:00 - 23:00"
|
||||||
|
/>
|
||||||
|
<CardRestaurant name="Naruto Sushi" city="Tokio" hours="7:00 - 23:00" />
|
||||||
|
<CardRestaurant
|
||||||
|
name="Gówno"
|
||||||
|
city="Dąbrowa górnicza"
|
||||||
|
hours="7:00 - 23:00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/components/logoMain.js
Normal file
13
src/components/logoMain.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import logo from "../public/logo_mint.svg";
|
||||||
|
import React from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
export default function LogoMain() {
|
||||||
|
let appMode = useSelector((store) => store.appMode);
|
||||||
|
|
||||||
|
if (appMode === "init") {
|
||||||
|
return <img src={logo} alt="Menui logo" className="logo" />;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/index.js
10
src/index.js
@@ -6,9 +6,15 @@ import App from "./App";
|
|||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import rootReducer from "./reducers";
|
import rootReducer from "./reducers";
|
||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import { createStore, applyMiddleware } from "redux";
|
import { createStore, applyMiddleware, compose } from "redux";
|
||||||
|
|
||||||
const store = createStore(rootReducer, applyMiddleware(thunk));
|
const store = createStore(
|
||||||
|
rootReducer,
|
||||||
|
compose(
|
||||||
|
applyMiddleware(thunk),
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|||||||
BIN
src/public/cat.jpg
Normal file
BIN
src/public/cat.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,4 @@
|
|||||||
const appModeReducer = (state = "init", action) => {
|
const appModeReducer = (state = "search results", action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "APP_INIT":
|
case "APP_INIT":
|
||||||
return (state = "init");
|
return (state = "init");
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { combineReducers } from "redux";
|
|||||||
import autoCompleteReducer from "./autoComplete";
|
import autoCompleteReducer from "./autoComplete";
|
||||||
import searchResults from "./searchResults";
|
import searchResults from "./searchResults";
|
||||||
import appMode from "./appMode";
|
import appMode from "./appMode";
|
||||||
|
import searchQuery from "./searchQuery";
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
autocomplete: autoCompleteReducer,
|
autocomplete: autoCompleteReducer,
|
||||||
appMode: appMode,
|
appMode: appMode,
|
||||||
searchResults: searchResults,
|
searchResults: searchResults,
|
||||||
|
searchQuery: searchQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|||||||
12
src/reducers/searchQuery.js
Normal file
12
src/reducers/searchQuery.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const searchQuery = (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "SEARCH_QUERY_SET":
|
||||||
|
return (state = action.payload);
|
||||||
|
case "SEARCH_QUERY_CLEAR":
|
||||||
|
return (state = "");
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default searchQuery;
|
||||||
49
src/styles/SearchResults.scss
Normal file
49
src/styles/SearchResults.scss
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
@import "./Design.scss";
|
||||||
|
|
||||||
|
.search-results {
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-restaurant {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
margin: 14px;
|
||||||
|
color: $secondary-color;
|
||||||
|
border-radius: 10px;
|
||||||
|
min-width: 70vw;
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
:hover {
|
||||||
|
background-color: #ebebeb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-image: url("../public/cat.jpg");
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
text-align: start;
|
||||||
|
flex-grow: 6;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: $main-color;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
color: $main-color;
|
||||||
|
border: solid 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user