Add webshop stuff
Signed-off-by: Jyri Genral <jyri.genral@protonmail.ch>
1
data/productGroups.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[{"name":"Maitotuotteet","subgroups":["Lehmänmaito","Vuohenmaito","Cheddar","Mozzarella"]},{"name":"Kukat","subgroups":["Leikkokukat","Rairuohot","Vehnä"]},{"name":"Lihatuotteet","subgroups":["Kana","Lammas","Possu","Kebabeläin"]}]
|
||||
164
src/index.tsx
|
|
@ -1,38 +1,160 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import {observable} from 'mobx';
|
||||
import {observer} from 'mobx-react';
|
||||
import { observable, action, configure } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import "antd/es/layout/style";
|
||||
import Layout from "antd/es/layout";
|
||||
|
||||
import "antd/es/menu/style";
|
||||
import Menu from "antd/es/menu";
|
||||
|
||||
import "antd/es/card/style";
|
||||
import Card from "antd/es/card";
|
||||
|
||||
import "antd/es/grid/style";
|
||||
import { Row, Col } from "antd/es/grid";
|
||||
|
||||
import "antd/es/input/style";
|
||||
import Input from "antd/es/input";
|
||||
|
||||
import { ShoppingCartOutlined } from '@ant-design/icons';
|
||||
|
||||
import * as productGroups from "../data/productGroups.json";
|
||||
|
||||
|
||||
configure({ enforceActions: 'always' });
|
||||
|
||||
|
||||
interface ProductGroup {
|
||||
name: String,
|
||||
subgroups: String[]
|
||||
}
|
||||
|
||||
const PRODUCTGROUPS: ProductGroup[] = productGroups; // from GET productgroups
|
||||
|
||||
class AppState {
|
||||
@observable timer = 0;
|
||||
@observable productCardsOnDisplay: Array<ProductCard>;
|
||||
|
||||
constructor() {
|
||||
setInterval(() => {
|
||||
this.timer += 1;
|
||||
}, 1000);
|
||||
@action
|
||||
setProductCardsOnDisplay(cards: Array<ProductCard>) {
|
||||
this.productCardsOnDisplay = cards;
|
||||
}
|
||||
|
||||
resetTimer() {
|
||||
this.timer = 0;
|
||||
async fetchProductCards() {
|
||||
try {
|
||||
const res = await fetch("http://localhost:8000/productCards");
|
||||
const cards = await res.json();
|
||||
this.setProductCardsOnDisplay(cards)
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constructor() {
|
||||
this.setProductCardsOnDisplay([]);
|
||||
this.fetchProductCards();
|
||||
}
|
||||
}
|
||||
|
||||
interface ProductCard {
|
||||
name: string,
|
||||
thumbnail: string,
|
||||
display_price: string
|
||||
}
|
||||
|
||||
@observer
|
||||
class TimerView extends React.Component<{appState: AppState}, {}> {
|
||||
class MainView extends React.Component<{ appState: AppState }, {}> {
|
||||
productRows(products: ProductCard[]) {
|
||||
const cardWidth = 180;
|
||||
let ret: Array<JSX.Element> = [];
|
||||
|
||||
// TODO: there's probably thousand better ways than the initial copy paste rinse and repeat model below to form some rows .. PRs are welcomed
|
||||
|
||||
for (let index = 0; index < products.length; index += 4) {
|
||||
let cols: Array<JSX.Element> = [
|
||||
<Col offset={1}>
|
||||
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index].thumbnail} />}>
|
||||
<Card.Meta title={products[index].name} description={products[index].display_price} />
|
||||
</Card>
|
||||
</Col>
|
||||
];
|
||||
if (index + 1 < products.length) {
|
||||
cols.push(
|
||||
<Col >
|
||||
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 1].thumbnail} />}>
|
||||
<Card.Meta title={products[index + 1].name} description={products[index + 1].display_price} />
|
||||
</Card>
|
||||
</Col>);
|
||||
}
|
||||
if (index + 2 < products.length) {
|
||||
cols.push(
|
||||
<Col>
|
||||
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 2].thumbnail} />}>
|
||||
<Card.Meta title={products[index + 2].name} description={products[index + 2].display_price} />
|
||||
</Card>
|
||||
</Col>);
|
||||
}
|
||||
if (index + 3 < products.length) {
|
||||
cols.push(
|
||||
<Col>
|
||||
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 3].thumbnail} />}>
|
||||
<Card.Meta title={products[index + 3].name} description={products[index + 3].display_price} />
|
||||
</Card>
|
||||
</Col>);
|
||||
}
|
||||
ret.push(
|
||||
<Row justify="start" gutter={[{ xs: 8, sm: 16, md: 24, lg: 32 }, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
|
||||
{cols}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.onReset}>
|
||||
Seconds passed: {this.props.appState.timer}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Layout>
|
||||
<Layout.Header style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
|
||||
<img src="/static/logo.png" alt="logo" style={{ height: "inherit" }}></img>
|
||||
|
||||
<Menu mode="horizontal" theme="dark">
|
||||
{PRODUCTGROUPS.map((group, groupindex) =>
|
||||
<Menu.SubMenu key={groupindex} title={group.name}>
|
||||
{group.subgroups.map((subgroup, subgroupindex) =>
|
||||
<Menu.Item key={subgroupindex}>{subgroup}</Menu.Item>
|
||||
)}
|
||||
</Menu.SubMenu>
|
||||
)}
|
||||
<Menu.SubMenu title="Muuta tietoa">
|
||||
<Menu.Item>Yhteystiedot</Menu.Item>
|
||||
<Menu.Item>Aukioloajat</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
<Input.Search style={{ maxWidth: "20rem" }} />
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Content>
|
||||
{this.productRows(this.props.appState.productCardsOnDisplay)}
|
||||
</Layout.Content>
|
||||
|
||||
<Layout.Sider theme="light">
|
||||
<ShoppingCartOutlined style={{ fontSize: "6rem", width: "100%" }} />
|
||||
</Layout.Sider>
|
||||
</Layout>
|
||||
<Layout.Footer>
|
||||
<h4>Foobar</h4>
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
this.props.appState.resetTimer();
|
||||
}
|
||||
};
|
||||
|
||||
const appState = new AppState();
|
||||
ReactDOM.render(<TimerView appState={appState} />, document.getElementById('root'));
|
||||
|
||||
ReactDOM.render(<MainView appState={appState} />, document.getElementById('root'));
|
||||
|
|
|
|||
BIN
static/logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/prod/karhunkieli.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/mokka.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/oatly.jpg
Normal file
|
After Width: | Height: | Size: 6.5 MiB |
BIN
static/prod/reko.jpg
Normal file
|
After Width: | Height: | Size: 7.4 MiB |
BIN
static/prod/suopa.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/t_karhunkieli.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
static/prod/t_mokka.jpg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
static/prod/t_oatly.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
static/prod/t_reko.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/prod/t_suopa.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
static/prod/turkey-180-240.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |