Deduplikace typů a sloučení kontejnerů

- Zavedení yarn workspaces
- Sloučení klienta a serveru do jednoho Docker kontejneru
- Společný dockerfile, builder
- Zbavení se nginx (není již potřeba)
This commit is contained in:
2023-07-22 19:37:03 +02:00
parent 0d0c5cb946
commit 3c0e8b2297
31 changed files with 1638 additions and 2527 deletions

View File

@@ -1,14 +1,12 @@
import { PizzaOrder } from "./Types";
import { PizzaOrder } from "./types";
import { getBaseUrl, getToken } from "./Utils";
async function request<TResponse>(
url: string,
config: RequestInit = {}
): Promise<TResponse> {
if (!config.headers) {
config.headers = {};
}
config.headers["Authorization"] = `Bearer ${getToken()}`;
config.headers = config?.headers ? new Headers(config.headers) : new Headers();
config.headers.set("Authorization", `Bearer ${getToken()}`);
return fetch(getBaseUrl() + url, config).then(response => {
if (!response.ok) {
throw new Error(response.statusText);
@@ -58,7 +56,7 @@ export const finishOrder = async () => {
return await api.post<any, any>('/api/finishOrder', undefined);
}
export const finishDelivery = async (bankAccount, bankAccountHolder) => {
export const finishDelivery = async (bankAccount?: string, bankAccountHolder?: string) => {
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ bankAccount, bankAccountHolder }));
}

View File

@@ -4,17 +4,17 @@ import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket
import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getFood, getPizzy, getQrUrl, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice, updateNote } from './Api';
import { useAuth } from './context/auth';
import Login from './Login';
import { Locations, ClientData, Pizza, PizzaOrder, State, Order, Food, Restaurants } from './Types';
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
import Header from './components/Header';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import PizzaOrderList from './components/PizzaOrderList';
import SelectSearch from 'react-select-search';
import SelectSearch, { SelectedOptionValue } from 'react-select-search';
import 'react-select-search/style.css';
import './App.css';
import { SelectSearchOption } from 'react-select-search';
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
import { useBank } from './context/bank';
import { ClientData, Restaurants, Food, Pizza, Order, Locations, PizzaOrder, PizzaDayState } from './types';
const EVENT_CONNECT = "connect"
@@ -77,7 +77,8 @@ function App() {
if (data?.choices && choiceRef.current) {
for (let entry of Object.entries(data.choices)) {
if (entry[1].includes(auth.login)) {
choiceRef.current.value = Object.values(Locations)[entry[0]]
const value = entry[0] as any as number; // TODO tohle je absurdní
choiceRef.current.value = Object.values(Locations)[value];
}
}
}
@@ -124,8 +125,11 @@ function App() {
return suggestions;
}, [pizzy]);
const handlePizzaChange = async (value) => {
const handlePizzaChange = async (value: SelectedOptionValue | SelectedOptionValue[]) => {
if (auth?.login && pizzy) {
if (!(typeof value === 'string')) {
throw Error('Nepodporovaný typ hodnoty');
}
const s = value.split('|');
const pizzaIndex = Number.parseInt(s[0]);
const pizzaSizeIndex = Number.parseInt(s[1]);
@@ -169,7 +173,7 @@ function App() {
// }
// }
const renderFoodTable = (name, food) => {
const renderFoodTable = (name: string, food: Food[]) => {
return <Col md={12} lg={4}>
<h3>{name}</h3>
<Table striped bordered hover>
@@ -268,7 +272,7 @@ function App() {
<div style={{ textAlign: 'center' }}>
<h3>Pizza day</h3>
{
data.pizzaDay.state === State.CREATED &&
data.pizzaDay.state === PizzaDayState.CREATED &&
<div>
<p>
Pizza Day je založen a spravován uživatelem {data.pizzaDay.creator}.<br />
@@ -288,7 +292,7 @@ function App() {
</div>
}
{
data.pizzaDay.state === State.LOCKED &&
data.pizzaDay.state === PizzaDayState.LOCKED &&
<div>
<p>Objednávky jsou uzamčeny uživatelem {data.pizzaDay.creator}</p>
{data.pizzaDay.creator === auth.login &&
@@ -307,7 +311,7 @@ function App() {
</div>
}
{
data.pizzaDay.state === State.ORDERED &&
data.pizzaDay.state === PizzaDayState.ORDERED &&
<div>
<p>Pizzy byly objednány uživatelem {data.pizzaDay.creator}</p>
{data.pizzaDay.creator === auth.login &&
@@ -323,13 +327,13 @@ function App() {
</div>
}
{
data.pizzaDay.state === State.DELIVERED &&
data.pizzaDay.state === PizzaDayState.DELIVERED &&
<div>
<p>Pizzy byly doručeny. Objednávku můžete uhradit pomocí QR kódu níže.</p>
</div>
}
</div>
{data.pizzaDay.state === State.CREATED &&
{data.pizzaDay.state === PizzaDayState.CREATED &&
<div style={{ textAlign: 'center' }}>
<SelectSearch
search={true}
@@ -352,7 +356,7 @@ function App() {
}
<PizzaOrderList state={data.pizzaDay.state} orders={data.pizzaDay.orders} onDelete={handlePizzaDelete} />
{
data.pizzaDay.state === State.DELIVERED && myOrder?.hasQr &&
data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr &&
<div className='qr-code'>
<h3>QR platba</h3>
<div>Částka: {myOrder.totalPrice} </div>

View File

@@ -1,84 +0,0 @@
// TODO všechno v tomto souboru jsou duplicity se serverem, ale aktuálně nevím jaký je nejlepší způsob jejich sdílení
export interface PizzaSize {
varId: number, // unikátní ID varianty pizzy
size: string, // velikost pizzy, např. "30cm"
pizzaPrice: number, // cena samotné pizzy
boxPrice: number, // cena krabice
price: number, // celková cena (pizza + krabice)
}
/** Jedna konkrétní pizza */
export interface Pizza {
name: string, // název pizzy
ingredients: string[], // seznam ingrediencí
sizes: PizzaSize[], // dostupné velikosti pizzy
}
/** Objednávka jedné konkrétní pizzy */
export interface PizzaOrder {
varId: number, // unikátní ID varianty pizzy
name: string, // název pizzy
size: string, // velikost pizzy jako string (30cm)
price: number, // cena pizzy v Kč, včetně krabice
}
/** Celková objednávka jednoho člověka */
export interface Order {
customer: string, // jméno objednatele
pizzaList: PizzaOrder[], // seznam objednaných pizz
totalPrice: number, // celková cena všech objednaných pizz a krabic
hasQr?: boolean, // zda je pro objednávku vygenerován QR kód pro platbu
note?: string, // volitelná poznámka uživatele k objednávce
}
export interface Choices {
[location: string]: string[],
}
/** Údaje o Pizza day. */
export interface PizzaDay {
state: State,
creator: string,
orders: Order[]
}
export interface ClientData {
date: string, // dnešní datum pro zobrazení
isWeekend: boolean, // příznak zda je dnešní den víkend
choices: Choices, // seznam voleb
pizzaDay?: PizzaDay, // údaje o pizza day, pokud je pro dnešek založen
}
export enum Locations {
SLADOVNICKA = 'Sladovnická',
UMOTLIKU = 'U Motlíků',
TECHTOWER = 'TechTower',
SPSE = 'SPŠE',
PIZZA = 'Pizza day',
OBJEDNAVAM = 'Budu objednávat',
NEOBEDVAM = 'Mám vlastní/neobědvám',
}
/** Jídlo z obědového menu restaurace. */
export interface Food {
amount?: string, // množství standardní porce, např. 0,33l nebo 150g
name: string, // název/popis jídla
price: string, // cena ve formátu '135 Kč'
isSoup: boolean, // příznak, zda se jedná o polévku
}
/** Výčtový typ pro restaurace, pro které umíme získat a parsovat obědové menu. */
export enum Restaurants {
SLADOVNICKA = 'sladovnicka',
UMOTLIKU = 'uMotliku',
TECHTOWER = 'techTower',
}
export enum State {
NOT_CREATED, // Pizza day nebyl založen
CREATED, // Pizza day je založen
LOCKED, // Objednávky uzamčeny
ORDERED, // Objednáno
DELIVERED, // Doručeno
}

View File

@@ -19,7 +19,7 @@ export default function Header() {
setModalOpen(false);
}
const isValidInteger = (str) => {
const isValidInteger = (str: string) => {
str = str.trim();
if (!str) {
return false;
@@ -66,7 +66,7 @@ export default function Header() {
if (sum % 11 !== 0) {
throw Error("Číslo účtu je neplatné")
}
} catch (e) {
} catch (e: any) {
alert(e.message)
return
}

View File

@@ -1,11 +1,11 @@
import React from "react";
import { Table } from "react-bootstrap";
import { Order, PizzaOrder, State } from "../Types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrashCan } from "@fortawesome/free-regular-svg-icons";
import { useAuth } from "../context/auth";
import { Order, PizzaDayState, PizzaOrder } from "../types";
export default function PizzaOrderList({ state, orders, onDelete }: { state: State, orders: Order[], onDelete: (pizzaOrder: PizzaOrder) => void }) {
export default function PizzaOrderList({ state, orders, onDelete }: { state: PizzaDayState, orders: Order[], onDelete: (pizzaOrder: PizzaOrder) => void }) {
const auth = useAuth();
if (!orders?.length) {
@@ -29,7 +29,7 @@ export default function PizzaOrderList({ state, orders, onDelete }: { state: Sta
<td>{order.pizzaList.map<React.ReactNode>((pizzaOrder, index) =>
<span key={index}>
{`${pizzaOrder.name}, ${pizzaOrder.size} (${pizzaOrder.price} Kč)`}
{auth?.login === order.customer && state === State.CREATED &&
{auth?.login === order.customer && state === PizzaDayState.CREATED &&
<FontAwesomeIcon onClick={() => {
onDelete(pizzaOrder);
}} title='Odstranit' className='trash-icon' icon={faTrashCan} />