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:
@@ -1,3 +0,0 @@
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
build
|
||||
@@ -1,3 +0,0 @@
|
||||
# Veřejná URL, na které bude dostupný klient (typicky přes proxy).
|
||||
# Pro vývoj není potřeba, bude použita výchozí hodnota http://127.0.0.1:3001
|
||||
# PUBLIC_URL=http://example:3001
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM node:18-alpine3.18 AS builder
|
||||
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
COPY tsconfig.json .
|
||||
COPY .env.production .
|
||||
|
||||
RUN yarn install
|
||||
|
||||
COPY ./src ./src
|
||||
COPY ./public ./public
|
||||
|
||||
RUN yarn build
|
||||
|
||||
FROM node:18-alpine3.18
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /build .
|
||||
EXPOSE 3000
|
||||
RUN yarn global add serve && yarn
|
||||
CMD ["serve", "-s", "."]
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker build -t luncher-client .
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "luncher-client",
|
||||
"name": "@luncher/client",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
@@ -29,8 +30,9 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"copy-types": "cp -r ../types ./src",
|
||||
"start": "yarn copy-types && react-scripts start",
|
||||
"build": "yarn copy-types && react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
|
||||
|
||||
@@ -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} Kč</div>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -19,8 +19,5 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"client/src"
|
||||
]
|
||||
}
|
||||
}
|
||||
9726
client/yarn.lock
9726
client/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user