diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index e81aad4..151984c 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -1,6 +1,6 @@ import { Database as BunSqliteDatabase } from "bun:sqlite"; import { AttachmentId, PieceId, RequestId, SessionId, Sha256, UserId } from "common"; -import { ColumnType, CompiledQuery, CreateTableBuilder, Kysely } from "kysely"; +import { ColumnType, CompiledQuery, CreateTableBuilder, Kysely, Selectable } from "kysely"; import { BunSqliteDialect } from "kysely-bun-sqlite"; export function generateSessionId(byteLength: number = 32): SessionId { @@ -35,14 +35,14 @@ export interface AccessLogTable { ip: ColumnType; } -export interface Attachment { +export interface AttachmentData { pieceId: ColumnType; sha256: Sha256; filename: string; mediaType: string; } -export interface AttachmentTable extends Attachment, SystemInformation { +export interface AttachmentTable extends AttachmentData, SystemInformation { attachmentId: ColumnType; } @@ -51,22 +51,22 @@ export interface FileTable { data: ColumnType; } -export interface Piece { +export interface PieceData { name: string; composer: string | null; lyricist: string | null; arranger: string | null; } -export interface PieceTable extends Piece, SystemInformation { +export interface PieceTable extends PieceData, SystemInformation { pieceId: ColumnType; } -export interface Session { +export interface SessionData { userId: UserId; } -export interface SessionTable extends Session { +export interface SessionTable extends SessionData { sessionId: ColumnType; expiresAt: string; } @@ -78,6 +78,13 @@ export interface UserTable { admin: number; } +export type AccessLog = Selectable; +export type Attachment = Selectable; +export type File = Selectable; +export type Piece = Selectable; +export type Session = Selectable; +export type User = Selectable; + function systemInformation(schema: CreateTableBuilder) { return schema .addColumn("createdBy", "text", (c) => c.references("User.userId").onDelete("set null").onUpdate("cascade")) diff --git a/packages/frontend/src/app.tsx b/packages/frontend/src/app.tsx index 9ca12b6..77a43aa 100644 --- a/packages/frontend/src/app.tsx +++ b/packages/frontend/src/app.tsx @@ -3,17 +3,29 @@ import { createRoot } from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { Home } from "./routes/Home"; import { Login } from "./routes/Login"; +import { Piece } from "./routes/Piece"; +import { Root } from "./routes/Root"; import "./style.css"; const router = createBrowserRouter([ { path: "/", - element: , + Component: Root, + children: [ + { + index: true, + Component: Home, + }, + { + path: "piece/:pieceId", + Component: Piece, + }, + ], }, { path: "/login", - element: , - } + Component: Login, + }, ]); const rootElement = document.getElementById("root") as HTMLDivElement; diff --git a/packages/frontend/src/routes/Home.tsx b/packages/frontend/src/routes/Home.tsx index f1a7c4e..b8b8cbf 100644 --- a/packages/frontend/src/routes/Home.tsx +++ b/packages/frontend/src/routes/Home.tsx @@ -1,47 +1,15 @@ -import { FormEventHandler, useEffect, useId, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { FormEventHandler, useId, useRef, useState } from "react"; import { client } from "../client"; import { useLoading } from "../loading"; -import { useStore } from "../store"; import { Button } from "../styled/Button"; import { Input } from "../styled/Input"; +import { Link } from "react-router-dom"; export function Home() { - const navigate = useNavigate(); + const { isLoading, error, data } = useLoading(() => client.piece.get({ query: {} })); - const user = useStore(state => state.user); - const setUser = useStore(state => state.setUser); - - const { isLoading, error, data } = useLoading(() => client.piece.get()); - - const init = async () => { - if (user !== null) return; - - const { data, error } = await client.me.get(); - - if (error !== null) { - navigate("/login"); - return; - } - - setUser(data); - }; - - useEffect(() => { init(); }, []); - - const onLogoutClick = async () => { - const { error } = await client.logout.post(); - - if (error !== null) { - console.error("Response was not ok"); - } - - setUser(null); - navigate("/login"); - }; - - if (user === null || isLoading) { + if (isLoading) { return (
Ładowanie…
@@ -50,55 +18,43 @@ export function Home() { } return ( -
-
-
- Użytkownik: {user.username} ({user.userId}) -
-
- -
-
-
- {error !== null ? ( - `Wystąpił błąd: ${error.value}` - ) : ( - - - - - - - - - - - - +
+ {error !== null ? ( + `Wystąpił błąd: ${error.value}` + ) : ( +
IDTytułKompozytorSłowaOpracowanieData dodaniaDodane przezData modyfikacjiZmodyfikowane przez
+ + + + + + + + + + + + + + + {data.map((piece) => ( + + + + + + + + + + - - - {data.map((piece) => ( - - - - - - - - - - - - ))} - -
IDTytułKompozytorSłowaOpracowanieData dodaniaDodane przezData modyfikacjiZmodyfikowane przez
{piece.pieceId}{piece.name}{piece.composer}{piece.lyricist}{piece.arranger}{piece.createdAt}{piece.createdBy}{piece.modifiedAt ?? "\u2014"}{piece.modifiedBy ?? "\u2014"}
{piece.pieceId}{piece.name}{piece.composer}{piece.lyricist}{piece.arranger}{piece.createdAt}{piece.createdBy}{piece.modifiedAt ?? "\u2014"}{piece.modifiedBy ?? "\u2014"}
- )} -
- -
+ ))} + + + )} +
+
); diff --git a/packages/frontend/src/routes/Piece.tsx b/packages/frontend/src/routes/Piece.tsx new file mode 100644 index 0000000..4b89b00 --- /dev/null +++ b/packages/frontend/src/routes/Piece.tsx @@ -0,0 +1,119 @@ +import type { Piece } from "backend/database"; +import { PieceId } from "common"; +import { FormEventHandler, useId, useRef, useState } from "react"; +import { useParams } from "react-router-dom"; +import { client } from "../client"; +import { useLoading } from "../loading"; +import { Button } from "../styled/Button"; +import { Input } from "../styled/Input"; + +export function Piece() { + + const id = PieceId(useParams().pieceId!); + + const { isLoading, error, data } = useLoading(() => client.piece.get({ query: { id } })); + + if (isLoading) { + return ( +
+
Ładowanie…
+
+ ); + } + + return ( +
+ {error !== null ? ( + `Wystąpił błąd: ${error.value}` + ) : data[0] === undefined ? ( + "Utwór nie istnieje" + ) : ( + + )} +
+ ); +} + +namespace PieceForm { + export interface Props { + readonly data: Piece; + } +} + +function PieceForm(props: PieceForm.Props) { + + const [name, setName] = useState(props.data.name); + const [composer, setComposer] = useState(props.data.composer ?? ""); + const [lyricist, setLyricist] = useState(props.data.lyricist ?? ""); + const [arranger, setArranger] = useState(props.data.arranger ?? ""); + + const nameId = useId(); + const composerId = useId(); + const lyricistId = useId(); + const arrangerId = useId(); + + const autoFocusRef = useRef(null); + + const onSubmit: FormEventHandler = async (e) => { + e.preventDefault(); + if (props.data === null) { + return; + } + + const { error } = await client.piece({ pieceId: props.data.pieceId }).put({ + name, + composer: composer.length > 0 ? composer : null, + lyricist: lyricist.length > 0 ? lyricist : null, + arranger: arranger.length > 0 ? arranger : null, + }); + + if (error) { + console.error(error.value); + return; + } + + autoFocusRef.current?.focus(); + } + + return ( +
+ + setName(e.target.value)} + /> + + setComposer(e.target.value)} + /> + + setLyricist(e.target.value)} + /> + + setArranger(e.target.value)} + /> + +
+ ); +} diff --git a/packages/frontend/src/routes/Root.tsx b/packages/frontend/src/routes/Root.tsx new file mode 100644 index 0000000..bca071a --- /dev/null +++ b/packages/frontend/src/routes/Root.tsx @@ -0,0 +1,65 @@ +import { useEffect } from "react"; +import { Outlet, useNavigate } from "react-router-dom"; +import { client } from "../client"; +import { useStore } from "../store"; +import { Button } from "../styled/Button"; +import { timeout } from "../utils"; + +export function Root() { + + const navigate = useNavigate(); + + const user = useStore(state => state.user); + const setUser = useStore(state => state.setUser); + + const init = async () => { + if (user !== null) return; + + const { data, error } = await client.me.get(); + await timeout(1000); + + if (error !== null) { + navigate("/login"); + return; + } + + setUser(data); + }; + + useEffect(() => { init(); }, []); + + const onLogoutClick = async () => { + const { error } = await client.logout.post(); + + if (error !== null) { + console.error("Response was not ok"); + } + + setUser(null); + navigate("/login"); + }; + + if (user === null) { + return ( +
+
Ładowanie…
+
+ ); + } + + return ( +
+
+
+ Użytkownik: {user.username} ({user.userId}) +
+
+ +
+
+ +
+ ); +} diff --git a/packages/frontend/src/utils.ts b/packages/frontend/src/utils.ts new file mode 100644 index 0000000..ddfee26 --- /dev/null +++ b/packages/frontend/src/utils.ts @@ -0,0 +1,3 @@ +export function timeout(timeMs: number): Promise { + return new Promise((resolve) => setTimeout(resolve, timeMs)); +}