From d9341368399f8514d6c9f20a175ccce700e21117 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Wed, 20 Nov 2024 22:12:29 +0100 Subject: [PATCH] Custom fonts, better home styling, more comprehensive table --- packages/backend/src/app.ts | 15 +++- packages/frontend/index.html | 3 + packages/frontend/src/loading.ts | 53 +++++++++++++ packages/frontend/src/routes/Home.tsx | 101 ++++++++++++------------- packages/frontend/src/routes/Login.tsx | 4 +- packages/frontend/src/store.ts | 38 +--------- packages/frontend/tailwind.config.js | 6 +- 7 files changed, 124 insertions(+), 96 deletions(-) create mode 100644 packages/frontend/src/loading.ts diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index c892584..4f0a347 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -171,19 +171,28 @@ const app = new Elysia() }), }) - .get("/piece", async ({ db, user }) => { + .get("/piece", async ({ db, query, user }) => { if (user === null) { return error("Unauthorized"); } - const res = await db + let q = db .selectFrom("Piece") .selectAll() .orderBy(["name", "composer", "arranger"]) - .execute(); + .limit(100); + if (query.id !== undefined) { + q = q.where("pieceId", "=", query.id); + } + + const res = await q.execute(); return res; + }, { + query: t.Object({ + id: t.Optional(tbranded()), + }), }) .put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, user }) => { diff --git a/packages/frontend/index.html b/packages/frontend/index.html index aee5219..42329e6 100644 --- a/packages/frontend/index.html +++ b/packages/frontend/index.html @@ -4,6 +4,9 @@ Repozytorium muzyczne + + + diff --git a/packages/frontend/src/loading.ts b/packages/frontend/src/loading.ts new file mode 100644 index 0000000..35f1f74 --- /dev/null +++ b/packages/frontend/src/loading.ts @@ -0,0 +1,53 @@ +import { Treaty } from "@elysiajs/eden"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useStore } from "./store"; + +export type LoadingResult> = + | { + isLoading: true, + data: null, + error: null, + } | { + isLoading: false, + data: R[200], + error: null, + } | { + isLoading: false, + data: null, + error: Exclude extends never + ? { status: unknown, value: unknown } + : { [Status in keyof R]: { status: Status, value: R[Status] } }[Exclude], + } + ; + +export function useLoading>(fn: () => Promise>) { + + const navigate = useNavigate(); + + const setUser = useStore(state => state.setUser); + + const [result, setResult] = useState>(() => ({ isLoading: true, data: null, error: null })); + + useEffect(() => { + let cancelled = false; + + fn().then(({ error, data }) => { + if (cancelled) return; + + if (error !== null) { + if (error.status === 401) { + setUser(null); + navigate("/login"); + return; + } + } + + setResult({ isLoading: false, error, data } as LoadingResult); + }); + + return () => { cancelled = true; }; + }, []); + + return result; +} diff --git a/packages/frontend/src/routes/Home.tsx b/packages/frontend/src/routes/Home.tsx index d7e2f45..f1a7c4e 100644 --- a/packages/frontend/src/routes/Home.tsx +++ b/packages/frontend/src/routes/Home.tsx @@ -1,7 +1,8 @@ import { FormEventHandler, useEffect, useId, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { client } from "../client"; -import { Store, useStore } from "../store"; +import { useLoading } from "../loading"; +import { useStore } from "../store"; import { Button } from "../styled/Button"; import { Input } from "../styled/Input"; @@ -12,19 +13,7 @@ export function Home() { const user = useStore(state => state.user); const setUser = useStore(state => state.setUser); - const pieces = useStore(state => state.pieces); - const setPieces = useStore(state => state.setPieces); - - const loadPieces = async () => { - const { data, error } = await client.piece.get(); - - if (error !== null) { - console.error(error.value); - return; - } - - setPieces(data); - }; + const { isLoading, error, data } = useLoading(() => client.piece.get()); const init = async () => { if (user !== null) return; @@ -37,12 +26,9 @@ export function Home() { } setUser(data); - await loadPieces(); }; - useEffect(() => { - init(); - }, []); + useEffect(() => { init(); }, []); const onLogoutClick = async () => { const { error } = await client.logout.post(); @@ -55,19 +41,19 @@ export function Home() { navigate("/login"); }; - if (user === null) { + if (user === null || isLoading) { return ( -
-
Ładowanie…
+
+
Ładowanie…
); } return ( -
-
+
+
- Użytkownik: {user.username} + Użytkownik: {user.username} ({user.userId})
-
- - - - - - - - - - - {(Object.values(pieces) as Store.Piece[]).map((piece) => ( - - - - - +
+ {error !== null ? ( + `Wystąpił błąd: ${error.value}` + ) : ( +
TytułKompozytorSłowaOpracowanie
{piece.name}{piece.composer}{piece.lyricist}{piece.arranger}
+ + + + + + + + + + + - ))} - -
IDTytułKompozytorSłowaOpracowanieData dodaniaDodane przezData modyfikacjiZmodyfikowane przez
-
-
- + + + {data.map((piece) => ( + + {piece.pieceId} + {piece.name} + {piece.composer} + {piece.lyricist} + {piece.arranger} + {piece.createdAt} + {piece.createdBy} + {piece.modifiedAt ?? "\u2014"} + {piece.modifiedBy ?? "\u2014"} + + ))} + + + )} +
+ +
); @@ -116,14 +116,12 @@ function PieceForm() { const lyricistId = useId(); const arrangerId = useId(); - const addPiece = useStore(state => state.setPiece); - const autoFocusRef = useRef(null); const onSubmit: FormEventHandler = async (e) => { e.preventDefault(); - const { data, error } = await client.piece.post({ + const { error } = await client.piece.post({ name, composer: composer.length > 0 ? composer : null, lyricist: lyricist.length > 0 ? lyricist : null, @@ -140,12 +138,11 @@ function PieceForm() { setLyricist(""); setArranger(""); - addPiece(data); autoFocusRef.current?.focus(); } return ( -
+ - -
Repozytorium muzyczne
+ +
Repozytorium muzyczne
(prop: K, action: Update) = return Object.freeze({ ...object, [prop]: typeof action === "function" ? (action as (prev: T) => T)(object[prop]) : action }); }; -export const removeProp = (prop: K) => ({ [prop]: _, ...rest }: O): Readonly> => { - return Object.freeze(rest); -}; - -export const makeMap = (prop: K, array: readonly T[]): { readonly [_ in T[K]]: T } => { - return Object.freeze(Object.fromEntries(array.map((item) => [item[prop], item])) as { [k in T[K]]: T }); -}; - export namespace Store { export interface User { readonly userId: UserId; readonly username: string; readonly admin: boolean; } - - export interface Piece { - readonly pieceId: PieceId; - readonly name: string; - readonly composer: string | null; - readonly lyricist: string | null; - readonly arranger: string | null; - readonly createdBy: UserId | null; - readonly createdAt: string; - readonly modifiedBy: UserId | null; - readonly modifiedAt: string | null; - } - - export namespace Piece { - export interface Map { - readonly [_: PieceId]: Store.Piece; - } - } } export interface Store { @@ -48,16 +22,11 @@ export interface Store { readonly loginPassword: string; readonly user: Store.User | null; - readonly pieces: Store.Piece.Map; readonly setLoginUsername: Updater; readonly setLoginPassword: Updater; readonly setUser: Updater; - - readonly setPieces: (pieces: readonly Store.Piece[]) => void; - readonly setPiece: (piece: Store.Piece) => void; - readonly deletePiece: (pieceId: PieceId) => void; } let store: Store = Object.freeze({ @@ -65,16 +34,11 @@ let store: Store = Object.freeze({ loginPassword: "", user: null, - pieces: Object.freeze({}), setLoginUsername: (action) => set(mapProp("loginUsername", action)), setLoginPassword: (action) => set(mapProp("loginPassword", action)), setUser: (action) => set(mapProp("user", action)), - - setPieces: (pieces) => set(mapProp("pieces", makeMap("pieceId", pieces))), - setPiece: (piece) => set(mapProp("pieces", mapProp(piece.pieceId, piece))), - deletePiece: (pieceId) => set(mapProp("pieces", removeProp(pieceId))), }); // --- STORE IMPLEMENTATION ---------------------------------------------------- diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 94d9ddb..8d79c54 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -5,7 +5,9 @@ export default { "./src/**/*.{js,ts,jsx,tsx}", ], theme: { - extend: {}, + fontFamily: { + sans: ["Lato", "sans-serif"], + mono: ["JetBrains Mono", "monospace"], + }, }, - plugins: [], };