From 90b24ec865476e85cf2fba2f9a0c8854c0cb9d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Fri, 9 May 2025 17:07:48 +0200 Subject: [PATCH] Create loader --- web/src/hooks/useLoader.ts | 34 +++++++++++++++++++ web/src/pages/LoadFilePage/LoadFileForm.tsx | 15 ++++---- web/src/pages/LoadFilePage/LoadFilePage.tsx | 8 +++-- .../PrepareTransactionsPage/ImportBar.tsx | 14 +++++--- .../PrepareTransactionsPage.tsx | 5 +-- web/src/pages/ResultPage/ResultPage.tsx | 2 +- web/src/services/api.service.ts | 6 ++-- 7 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 web/src/hooks/useLoader.ts diff --git a/web/src/hooks/useLoader.ts b/web/src/hooks/useLoader.ts new file mode 100644 index 0000000..1523fbd --- /dev/null +++ b/web/src/hooks/useLoader.ts @@ -0,0 +1,34 @@ +import { useCallback, useState } from "react"; + +export type UseLoaderReturnType = { + fn: (...args: T) => Promise, + loading: boolean; + completed: boolean; + error?: unknown; +}; + +export function useLoader(fn: (...args: T) => Promise, deps?: React.DependencyList) { + const [loading, setLoading] = useState(false); + const [completed, setCompleted] = useState(false); + const [error, setError] = useState(undefined); + + const callback = useCallback(async (...args: T) => { + try { + setLoading(true); + const value = await fn(...args); + setCompleted(true); + return value; + } catch(e: unknown) { + setError(e); + } finally { + setLoading(false); + } + }, [setLoading, setCompleted, setError, ...(deps ?? [])]); + + return { + fn: callback, + loading, + completed, + error + }; +} \ No newline at end of file diff --git a/web/src/pages/LoadFilePage/LoadFileForm.tsx b/web/src/pages/LoadFilePage/LoadFileForm.tsx index 2ee0b3f..5f2764b 100644 --- a/web/src/pages/LoadFilePage/LoadFileForm.tsx +++ b/web/src/pages/LoadFilePage/LoadFileForm.tsx @@ -1,8 +1,10 @@ +import classNames from "classnames"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; export type LoadFileFormProps = { profiles: string[]; servers: string[]; + loading: boolean; defaultProfile?: string; defaultServer?: string; onSubmit?: (csvFile: File, profile: string, server: string) => Promise; @@ -14,7 +16,7 @@ type Form = { server?: string; }; -export default function LoadFileForm({ profiles, servers, defaultProfile, defaultServer, onSubmit }: LoadFileFormProps) { +export default function LoadFileForm({ profiles, servers, defaultProfile, defaultServer, onSubmit, loading }: LoadFileFormProps) { const fileInput = useRef(null); const [formData, setFormData] = useState
({}); @@ -42,7 +44,7 @@ export default function LoadFileForm({ profiles, servers, defaultProfile, defaul if (!selectedFile || !formData.profile || !formData.server) { return; } - + onSubmit?.(selectedFile, formData.profile, formData.server); }, [formData, formData, selectedFile, onSubmit]); @@ -50,8 +52,8 @@ export default function LoadFileForm({ profiles, servers, defaultProfile, defaul
-
-
+
+
diff --git a/web/src/pages/LoadFilePage/LoadFilePage.tsx b/web/src/pages/LoadFilePage/LoadFilePage.tsx index 0eb26e3..6af1ede 100644 --- a/web/src/pages/LoadFilePage/LoadFilePage.tsx +++ b/web/src/pages/LoadFilePage/LoadFilePage.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import LoadFileForm from './LoadFileForm'; import { fetchConfig, loadTransactions } from '../../services/api.service'; import { useStore } from '../../store/AppStore'; +import { useLoader } from '../../hooks/useLoader'; export default function LoadFilePage() { const { dispatch } = useStore(); @@ -27,9 +28,9 @@ export default function LoadFilePage() { useEffect(() => { loadConfig(); - }, [loadConfig]); + }, [loadConfig]); - const handleSubmit = useCallback(async (csvFile: File, profile: string, server: string) => { + const { fn: handleSubmit, loading } = useLoader(async (csvFile: File, profile: string, server: string) => { const data = await loadTransactions(csvFile, profile, server); dispatch({ @@ -47,7 +48,7 @@ export default function LoadFilePage() { offset: 1 } }); - }, []); + }); return (
@@ -55,6 +56,7 @@ export default function LoadFilePage() {

Import Transactions

void; + loading: boolean; }; -export default function ImportBar({ onSubmit }: ImportBarProps) { +export default function ImportBar({ onSubmit, loading }: ImportBarProps) { const [formData, setFormData] = useState({ mode: 'import' }); @@ -34,12 +36,12 @@ export default function ImportBar({ onSubmit }: ImportBarProps) {
@@ -69,7 +71,9 @@ export default function ImportBar({ onSubmit }: ImportBarProps) {
+ disabled={loading} + className={classNames("button", "is-link", "is-fullwidth", { 'is-loading': loading })}>Submit transactions +
diff --git a/web/src/pages/PrepareTransactionsPage/PrepareTransactionsPage.tsx b/web/src/pages/PrepareTransactionsPage/PrepareTransactionsPage.tsx index 3f4290e..5dab79a 100644 --- a/web/src/pages/PrepareTransactionsPage/PrepareTransactionsPage.tsx +++ b/web/src/pages/PrepareTransactionsPage/PrepareTransactionsPage.tsx @@ -5,6 +5,7 @@ import { SkippedLines } from "./SkippedLines"; import ImportBar from "./ImportBar"; import type { ImportOptions } from "../../types/api"; import { submitTransactions } from "../../services/api.service"; +import { useLoader } from "../../hooks/useLoader"; export default function PrepareTransactionsPage() { @@ -12,7 +13,7 @@ export default function PrepareTransactionsPage() { const desiredTransactions = useMemo(() => state.transactions.filter((_, i) => !state.filteredOut.includes(i)), [state, state.filteredOut]); - const handleSubmit = useCallback(async (opts: ImportOptions) => { + const { fn: handleSubmit, loading } = useLoader(async (opts: ImportOptions) => { if(!state.server) { return; } @@ -63,7 +64,7 @@ export default function PrepareTransactionsPage() {
- +
diff --git a/web/src/pages/ResultPage/ResultPage.tsx b/web/src/pages/ResultPage/ResultPage.tsx index 4fc12cc..fb787dc 100644 --- a/web/src/pages/ResultPage/ResultPage.tsx +++ b/web/src/pages/ResultPage/ResultPage.tsx @@ -25,7 +25,7 @@ export default function ResultPage() { return (
-
+

Import Result

diff --git a/web/src/services/api.service.ts b/web/src/services/api.service.ts index 616b4f2..db83674 100644 --- a/web/src/services/api.service.ts +++ b/web/src/services/api.service.ts @@ -1,7 +1,7 @@ import type { ConfigResponse, ImportOptions, PrepareResponse, SubmitResponse, Transaction } from "../types/api"; export async function fetchConfig(): Promise { - const response = await fetch("/config") + const response = await fetch("http://localhost:3000/config") const data = await response.json(); return data as ConfigResponse; } @@ -12,7 +12,7 @@ export async function loadTransactions(csvFile: File, profile: string, server: s payload.append("profile", profile); payload.append("server", server); - const response = await fetch("/prepare", { + const response = await fetch("http://localhost:3000/prepare", { method: "POST", body: payload }); @@ -28,7 +28,7 @@ export async function submitTransactions(transactions: Transaction[], server: st opts }; - const response = await fetch("/submit", { + const response = await fetch("http://localhost:3000/submit", { method: "POST", body: JSON.stringify(payload), headers: {