addded error codes and improved error handling

This commit is contained in:
2026-06-04 15:32:51 +02:00
parent faebe54db3
commit c42bdea047
19 changed files with 730 additions and 96 deletions
+35 -1
View File
@@ -1,13 +1,21 @@
import { useForm } from "@tanstack/react-form";
import { Input, Button } from "@mui/joy";
import { Input, Button, Alert } from "@mui/joy";
import { useMutation } from "@tanstack/react-query";
import { signInUser } from "../utils/api/auth";
import { useTranslation } from "react-i18next";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import type { AlertInterface } from "../misc/interfaces";
export const LoginCard = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const form = useForm({
defaultValues: {
@@ -29,9 +37,24 @@ export const LoginCard = () => {
}) => signInUser(username, password, t),
onSuccess: (result) => {
if (result.ok) {
setAlert({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
navigate({ to: "/app/inventory" });
}
},
onError: (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
},
});
return (
@@ -53,6 +76,17 @@ export const LoginCard = () => {
form.handleSubmit();
}}
>
{alert.isAlert && (
<Alert
variant="soft"
color={alert.type}
className="rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
<form.Field name="username">
{(field) => (
<Input
+4 -1
View File
@@ -9,9 +9,10 @@ import { useTranslation } from "react-i18next";
interface StorageRowProps {
storage: Storage;
onError: (error: unknown) => void;
}
export const StorageRow = ({ storage }: StorageRowProps) => {
export const StorageRow = ({ storage, onError }: StorageRowProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
@@ -22,6 +23,7 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["storages"] });
},
onError,
});
const deleteMutation = useMutation({
@@ -29,6 +31,7 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["storages"] });
},
onError,
});
const form = useForm({
@@ -47,7 +47,7 @@ export const AddStorageModal = (props: AddStorageModalProps) => {
isAlert: true,
type: "danger",
header: t("error"),
text: error.code ? t(`errors.${error.code}`) : t("unknown-error"),
text: error.code ? t(error.code) : t("unknown-error"),
});
},
onSuccess: () => {
@@ -0,0 +1,134 @@
import {
Modal,
ModalDialog,
DialogTitle,
Stack,
Input,
Button,
Alert,
} from "@mui/joy";
import { useForm } from "@tanstack/react-form";
import { useMutation } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import type { ChangePasswordIntf, AlertInterface } from "../../misc/interfaces";
import { mutatePassword } from "../../utils/api/auth";
import { useState } from "react";
interface ChangePasswordProps {
isOpen: boolean;
setOpen: (value: boolean) => void;
}
export const ChangePasswordModal = (props: ChangePasswordProps) => {
const { t } = useTranslation();
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const form = useForm({
defaultValues: {
currentPassword: "",
newPassword: "",
newPasswordRep: "",
},
onSubmit: async ({ value }) => {
mutate(value);
},
});
const { mutate } = useMutation({
mutationFn: (values: ChangePasswordIntf) => mutatePassword(values),
onError: (error: { code?: string }) => {
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: error.code ? t(error.code) : t("unknown-error"),
});
},
onSuccess: () => {
props.setOpen(false);
},
});
return (
<>
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
<ModalDialog className="rounded-3xl border border-white/70 bg-white/90 p-6 shadow-[0_30px_70px_rgba(12,38,78,0.2)] backdrop-blur">
<DialogTitle className="text-slate-900">
{t("new-password-title")}
</DialogTitle>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<Stack spacing={2} className="mt-4">
<form.Field name="currentPassword">
{(field) => (
<Input
type="password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("current-password")}
variant="outlined"
size="lg"
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
/>
)}
</form.Field>
<form.Field name="newPassword">
{(field) => (
<Input
type="password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("new-password")}
variant="outlined"
size="lg"
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
/>
)}
</form.Field>
<form.Field name="newPasswordRep">
{(field) => (
<Input
type="password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("new-password-rep")}
variant="outlined"
size="lg"
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
/>
)}
</form.Field>
<Button
type="submit"
size="lg"
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
>
{t("change")}
</Button>
</Stack>
</form>
{alert.isAlert && (
<Alert
variant="soft"
color={alert.type}
className="mt-4 rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
</ModalDialog>
</Modal>
</>
);
};
+6 -1
View File
@@ -60,4 +60,9 @@ export type ProductRow = {
locationDetail: string;
expiryDate: string;
refillDate: string;
};
};
export type ChangePasswordIntf = {
currentPassword: string;
newPassword: string;
};
+51 -10
View File
@@ -11,22 +11,53 @@ import {
Typography,
} from "@mui/joy";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "@tanstack/react-form";
import { createProduct } from "../utils/api/products";
import { getStorages } from "../utils/api/storages";
import type { ProductFormValues } from "../misc/interfaces";
import type {
ProductFormValues,
AlertInterface,
Storage,
} from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
import Cookies from "js-cookie";
export const AddProduct = () => {
const { t } = useTranslation();
const [success, setSuccess] = useState(false);
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const { data: storages } = useQuery({
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: storages,
isError: storagesError,
error: storagesErrorObj,
} = useQuery<Storage[], ApiError>({
queryKey: ["storages"],
queryFn: () => getStorages(),
});
useEffect(() => {
if (storagesError && storagesErrorObj) {
showError(storagesErrorObj);
}
}, [storagesError, storagesErrorObj]);
const form = useForm({
defaultValues: {
amount: 0,
@@ -48,6 +79,7 @@ export const AddProduct = () => {
onSuccess: () => {
setSuccess(true);
},
onError: showError,
});
return (
@@ -227,13 +259,11 @@ export const AddProduct = () => {
variant="outlined"
className="rounded-2xl bg-white/90"
>
{storages?.map(
(storage: { uuid: string; name: string }) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
),
)}
{storages?.map((storage) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
))}
</Select>
)}
</form.Field>
@@ -255,6 +285,17 @@ export const AddProduct = () => {
{t("save")}
</Button>
</div>
{alert.isAlert && (
<Alert
color={alert.type}
variant="soft"
className="rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
{success && (
<Alert
color="success"
+46 -2
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Typography,
Button,
@@ -8,6 +8,7 @@ import {
Avatar,
Chip,
Checkbox,
Alert,
} from "@mui/joy";
import { useNavigate } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
@@ -17,17 +18,46 @@ import { deleteSelectedProducts, getProducts } from "../utils/api/products";
import { formatDate } from "../utils/uxFncs";
import Cookies from "js-cookie";
import type { ProductRow } from "../misc/interfaces";
import type { AlertInterface } from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
export const InventoryPage = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const queryClient = useQueryClient();
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const { data: productsData, isLoading: productsIsLoading } = useQuery({
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: productsData,
isLoading: productsIsLoading,
isError: productsError,
error: productsErrorObj,
} = useQuery<any[], ApiError>({
queryKey: ["products"],
queryFn: getProducts,
});
useEffect(() => {
if (productsError && productsErrorObj) {
showError(productsErrorObj);
}
}, [productsError, productsErrorObj]);
const rows: ProductRow[] = (productsData ?? []).map(
(product: any, index: number) => ({
id: String(product?.uuid ?? index),
@@ -37,6 +67,8 @@ export const InventoryPage = () => {
imageUrl: product?.picture ?? undefined,
price: product?.price ? String(product.price) : "-",
stock: `${product?.amount ?? 0} ${t("pcs")}`,
stockLabel: String(product?.amount ?? 0),
stockStatus: (product?.amount ?? 0) > 0 ? "ok" : "missing",
location: product?.storage_location_name ?? "-",
locationDetail: "",
expiryDate: formatDate(product?.expiry_date),
@@ -52,6 +84,7 @@ export const InventoryPage = () => {
setSelected([]);
queryClient.invalidateQueries({ queryKey: ["products"] });
},
onError: showError,
});
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -94,6 +127,17 @@ export const InventoryPage = () => {
</Button>
{productsIsLoading && <CircularProgress size="sm" />}
</div>
{alert.isAlert && (
<Alert
variant="soft"
color={alert.type}
className="mt-4 rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
<Sheet
variant="outlined"
+60 -11
View File
@@ -17,8 +17,13 @@ import { useEffect, useState } from "react";
import { useForm } from "@tanstack/react-form";
import { mutateProduct, getProductDetails } from "../utils/api/products.ts";
import { toInputDate } from "../utils/uxFncs";
import type { ProductFormValues } from "../misc/interfaces";
import type { productDetailsInterface } from "../misc/interfaces";
import type {
ProductFormValues,
productDetailsInterface,
AlertInterface,
Storage,
} from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
import Cookies from "js-cookie";
import ElectricBoltIcon from "@mui/icons-material/ElectricBolt";
@@ -27,21 +32,55 @@ export const ProductQuickView = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [success, setSuccess] = useState(false);
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: productDetails,
isLoading: productDetailsLoading,
isSuccess,
} = useQuery<productDetailsInterface>({
isError: productDetailsError,
error: productDetailsErrorObj,
} = useQuery<productDetailsInterface, ApiError>({
queryKey: ["product", uuid],
queryFn: () => getProductDetails(uuid),
});
const { data: storages } = useQuery({
const {
data: storages,
isError: storagesError,
error: storagesErrorObj,
} = useQuery<Storage[], ApiError>({
queryKey: ["storages"],
queryFn: () => getStorages(),
});
useEffect(() => {
if (productDetailsError && productDetailsErrorObj) {
showError(productDetailsErrorObj);
}
}, [productDetailsError, productDetailsErrorObj]);
useEffect(() => {
if (storagesError && storagesErrorObj) {
showError(storagesErrorObj);
}
}, [storagesError, storagesErrorObj]);
const form = useForm({
defaultValues: {
amount: 0,
@@ -73,6 +112,7 @@ export const ProductQuickView = () => {
setSuccess(true);
queryClient.invalidateQueries({ queryKey: ["product", variables.uuid] });
},
onError: showError,
});
useEffect(() => {
@@ -271,13 +311,11 @@ export const ProductQuickView = () => {
variant="outlined"
className="rounded-2xl bg-white/90"
>
{storages?.map(
(storage: { uuid: string; name: string }) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
),
)}
{storages?.map((storage) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
))}
</Select>
)}
</form.Field>
@@ -299,6 +337,17 @@ export const ProductQuickView = () => {
{t("save")}
</Button>
</div>
{alert.isAlert && (
<Alert
color={alert.type}
variant="soft"
className="rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
{success && (
<Alert
color="success"
+64 -5
View File
@@ -6,31 +6,68 @@ import {
Sheet,
Chip,
Divider,
Alert,
} from "@mui/joy";
import { useForm } from "@tanstack/react-form";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import Cookies from "js-cookie";
import { useTranslation } from "react-i18next";
import type { SettingsIntf } from "../misc/interfaces";
import type { AlertInterface } from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
import { mutateSettings, fetchSettings } from "../utils/api/settings";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { ChangePasswordModal } from "../components/modals/ChangePasswordModal";
export const Settings = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [modal, setModal] = useState(false);
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: settings,
isPending: settingsPending,
isSuccess: settingsSuccess,
} = useQuery({
isError: settingsError,
error: settingsErrorObj,
} = useQuery<{ data: { value: string }[] }, ApiError>({
queryKey: ["settings"],
queryFn: fetchSettings,
});
useEffect(() => {
Cookies.set("app-name", settings?.data[0].value);
Cookies.set("currency", settings?.data[1].value);
if (settingsError && settingsErrorObj) {
showError(settingsErrorObj);
}
}, [settingsError, settingsErrorObj]);
useEffect(() => {
const appName = settings?.data?.[0]?.value;
const currency = settings?.data?.[1]?.value;
if (appName) {
Cookies.set("app-name", appName);
}
if (currency) {
Cookies.set("currency", currency);
}
}, [settingsSuccess]);
const form = useForm({
@@ -48,10 +85,12 @@ export const Settings = () => {
onSuccess() {
queryClient.invalidateQueries({ queryKey: ["settings"] });
},
onError: showError,
});
return (
<>
<ChangePasswordModal isOpen={modal} setOpen={setModal} />
<div className="space-y-6">
<div className="flex flex-wrap items-center gap-3">
<div className="space-y-1">
@@ -73,6 +112,17 @@ export const Settings = () => {
</div>
<Sheet className="mt-6 rounded-3xl border border-white/70 bg-white/80 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
{alert.isAlert && (
<Alert
variant="soft"
color={alert.type}
className="mb-6 rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
{settingsPending ? (
<div className="flex items-center justify-center py-16">
<CircularProgress size="lg" />
@@ -143,10 +193,19 @@ export const Settings = () => {
<Button
type="submit"
size="lg"
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
color="primary"
className="rounded-2xl text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
>
{t("save")}
</Button>
<Button
onClick={() => setModal(true)}
size="lg"
color="warning"
className="rounded-2xl text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
>
{t("change-password")}
</Button>
</div>
</form>
)}
+55 -4
View File
@@ -1,22 +1,58 @@
import { useQuery } from "@tanstack/react-query";
import { getStorages } from "../utils/api/storages";
import { Sheet, Table, Button, CircularProgress, Typography } from "@mui/joy";
import {
Sheet,
Table,
Button,
CircularProgress,
Typography,
Alert,
} from "@mui/joy";
import { useTranslation } from "react-i18next";
import type { Storage } from "../misc/interfaces";
import type { AlertInterface } from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
import { StorageRow } from "../components/StorageRow";
import { useState } from "react";
import { useEffect, useState } from "react";
import { AddStorageModal } from "../components/modals/AddStorageModal";
import AddIcon from "@mui/icons-material/Add";
export const Storages = () => {
const { t } = useTranslation();
const [modal, setModal] = useState(false);
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const { data: storages, isLoading } = useQuery({
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: storages,
isLoading,
isError: storagesError,
error: storagesErrorObj,
} = useQuery<Storage[], ApiError>({
queryKey: ["storages"],
queryFn: () => getStorages(),
});
useEffect(() => {
if (storagesError && storagesErrorObj) {
showError(storagesErrorObj);
}
}, [storagesError, storagesErrorObj]);
return (
<>
<div className="flex flex-col gap-4">
@@ -38,6 +74,17 @@ export const Storages = () => {
{t("add")}
</Button>
</div>
{alert.isAlert && (
<Alert
variant="soft"
color={alert.type}
className="rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
</div>
<Sheet
@@ -100,7 +147,11 @@ export const Storages = () => {
</thead>
<tbody>
{storages?.map((storage: Storage) => (
<StorageRow key={storage.uuid} storage={storage} />
<StorageRow
key={storage.uuid}
storage={storage}
onError={showError}
/>
))}
</tbody>
</Table>
+60 -11
View File
@@ -17,8 +17,13 @@ import { useEffect, useState } from "react";
import { useForm } from "@tanstack/react-form";
import { mutateProduct, getProductDetails } from "../utils/api/products.ts";
import { toInputDate } from "../utils/uxFncs";
import type { ProductFormValues } from "../misc/interfaces";
import type { productDetailsInterface } from "../misc/interfaces";
import type {
ProductFormValues,
productDetailsInterface,
AlertInterface,
Storage,
} from "../misc/interfaces";
import type { ApiError } from "../utils/api/apiError";
import QrCodeIcon from "@mui/icons-material/QrCode";
import Cookies from "js-cookie";
import QRCode from "qrcode";
@@ -32,21 +37,55 @@ export const ViewProduct = (props: ViewProductProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [success, setSuccess] = useState(false);
const [alert, setAlert] = useState<AlertInterface>({
isAlert: false,
type: "neutral",
header: "",
text: "",
});
const showError = (error: unknown) => {
const errorCode = (error as { code?: string })?.code;
setAlert({
isAlert: true,
type: "danger",
header: t("error"),
text: errorCode ? t(errorCode) : t("unknown-error"),
});
};
const {
data: productDetails,
isLoading: productDetailsLoading,
isSuccess,
} = useQuery<productDetailsInterface>({
isError: productDetailsError,
error: productDetailsErrorObj,
} = useQuery<productDetailsInterface, ApiError>({
queryKey: ["product", uuid],
queryFn: () => getProductDetails(uuid),
});
const { data: storages } = useQuery({
const {
data: storages,
isError: storagesError,
error: storagesErrorObj,
} = useQuery<Storage[], ApiError>({
queryKey: ["storages"],
queryFn: () => getStorages(),
});
useEffect(() => {
if (productDetailsError && productDetailsErrorObj) {
showError(productDetailsErrorObj);
}
}, [productDetailsError, productDetailsErrorObj]);
useEffect(() => {
if (storagesError && storagesErrorObj) {
showError(storagesErrorObj);
}
}, [storagesError, storagesErrorObj]);
const form = useForm({
defaultValues: {
amount: 0,
@@ -78,6 +117,7 @@ export const ViewProduct = (props: ViewProductProps) => {
setSuccess(true);
queryClient.invalidateQueries({ queryKey: ["product", variables.uuid] });
},
onError: showError,
});
useEffect(() => {
@@ -289,13 +329,11 @@ export const ViewProduct = (props: ViewProductProps) => {
variant="outlined"
className="rounded-2xl bg-white/90"
>
{storages?.map(
(storage: { uuid: string; name: string }) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
),
)}
{storages?.map((storage) => (
<Option key={storage.uuid} value={storage.uuid}>
{storage.name}
</Option>
))}
</Select>
)}
</form.Field>
@@ -326,6 +364,17 @@ export const ViewProduct = (props: ViewProductProps) => {
{t("save")}
</Button>
</div>
{alert.isAlert && (
<Alert
color={alert.type}
variant="soft"
className="rounded-2xl border border-rose-200/70 bg-rose-50/80 text-rose-700 shadow-[0_12px_30px_rgba(220,38,38,0.12)]"
>
{alert.header}
<br />
{alert.text}
</Alert>
)}
{success && (
<Alert
color="success"
+10
View File
@@ -0,0 +1,10 @@
export type ApiError = Error & { code?: string };
export const createApiError = (
code?: string,
message: string = "Request failed",
): ApiError => {
const error = new Error(message) as ApiError;
error.code = code || "unknown-error";
return error;
};
+26 -3
View File
@@ -1,8 +1,9 @@
import { API_BASE } from "../../config/api.config";
import Cookies from "js-cookie";
import type { TFunction } from "i18next";
import { toast } from "react-toastify";
import { fetchSettings } from "./settings";
import type { ChangePasswordIntf } from "../../misc/interfaces";
import { createApiError } from "./apiError";
export async function isAuthenticated() {
if (Cookies.get("token")) {
@@ -52,11 +53,33 @@ export async function signInUser(
}
Cookies.remove("token");
toast.error(t(response.code));
return { ok: false as const };
throw createApiError(response.code, t(response.code || "unknown-error"));
}
export function signOutUser() {
Cookies.remove("token");
return { ok: true as const };
}
export const mutatePassword = async (payload: ChangePasswordIntf) => {
const result = await fetch(`${API_BASE}/users/change-password`, {
method: "POST",
headers: {
Authorization: `Bearer ${Cookies.get("token") || ""}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
currentPassword: payload.currentPassword,
newPassword: payload.newPassword,
}),
});
const response = await result.json();
if (response.code === "su005") {
return { code: response.code };
}
throw createApiError(response.code, "Change password failed");
};
+15 -18
View File
@@ -1,6 +1,7 @@
import { API_BASE } from "../../config/api.config";
import Cookies from "js-cookie";
import type { ProductFormValues } from "../../misc/interfaces";
import { createApiError } from "./apiError";
export const getProducts = async () => {
const result = await fetch(`${API_BASE}/products/all-products`, {
@@ -12,13 +13,11 @@ export const getProducts = async () => {
});
const response = await result.json();
if (response.code === "ep002") {
return { success: false, code: response.code };
}
if (response.code === "sp002") {
return response.data;
}
throw createApiError(response.code, "Get products failed");
};
export const getProductDetails = async (uuid: string) => {
@@ -32,13 +31,11 @@ export const getProductDetails = async (uuid: string) => {
const response = await result.json();
if (response.code === "ep003") {
return { success: false, code: response.code };
}
if (response.code === "sp003") {
return response.data;
}
throw createApiError(response.code, "Get product details failed");
};
export const mutateProduct = async (
@@ -66,13 +63,11 @@ export const mutateProduct = async (
const response = await result.json();
if (response.code === "ep004") {
return { success: false, code: response.code };
}
if (response.code === "sp004") {
if (response.code === "sp005") {
return response.data;
}
throw createApiError(response.code, "Update product failed");
};
export const deleteSelectedProducts = async (uuids: string[]) => {
@@ -88,7 +83,11 @@ export const deleteSelectedProducts = async (uuids: string[]) => {
const response = await result.json();
console.log(response);
if (response.code === "sp006") {
return { success: true };
}
throw createApiError(response.code, "Delete products failed");
};
export const createProduct = async (values: ProductFormValues) => {
@@ -114,11 +113,9 @@ export const createProduct = async (values: ProductFormValues) => {
const response = await result.json();
if (response.code === "ep001") {
return { success: false, code: response.code };
}
if (response.code === "sp001") {
return response.data;
}
throw createApiError(response.code, "Create product failed");
};
+5 -8
View File
@@ -1,6 +1,7 @@
import { API_BASE } from "../../config/api.config";
import Cookies from "js-cookie";
import type { SettingsIntf } from "../../misc/interfaces";
import { createApiError } from "./apiError";
export const fetchSettings = async () => {
const result = await fetch(`${API_BASE}/users/settings`, {
@@ -14,13 +15,11 @@ export const fetchSettings = async () => {
const response = await result.json();
if (response.code === "eu005") {
return { success: false, code: response.code };
}
if (response.code === "su004") {
return { success: true, data: response.data, code: response.code };
}
throw createApiError(response.code, "Fetch settings failed");
};
export const mutateSettings = async (payload: SettingsIntf) => {
@@ -36,11 +35,9 @@ export const mutateSettings = async (payload: SettingsIntf) => {
const response = await result.json();
if (response.code === "eu004") {
return { success: false, code: response.code };
}
if (response.code === "su003") {
return { success: true, code: response.code };
}
throw createApiError(response.code, "Update settings failed");
};
+10 -17
View File
@@ -1,6 +1,7 @@
import { API_BASE } from "../../config/api.config";
import Cookies from "js-cookie";
import type { NewStorage, Storage } from "../../misc/interfaces";
import { createApiError } from "./apiError";
export const getStorages = async () => {
const result = await fetch(`${API_BASE}/storage/all-storages`, {
@@ -13,13 +14,11 @@ export const getStorages = async () => {
const response = await result.json();
if (response.code === "es001") {
return { success: false, code: response.code };
}
if (response.code === "ss001") {
return response.data;
}
throw createApiError(response.code, "Get storages failed");
};
export const mutateNewStorage = async (values: NewStorage) => {
@@ -35,13 +34,11 @@ export const mutateNewStorage = async (values: NewStorage) => {
const response = await result.json();
if (response.code === "es002") {
return { success: false, code: response.code };
}
if (response.code === "ss002") {
return response.data;
}
throw createApiError(response.code, "Create storage failed");
};
export const updateStorage = async (
@@ -63,13 +60,11 @@ export const updateStorage = async (
const response = await result.json();
if (response.code === "ep001") {
return { success: false, code: response.code };
}
if (response.code === "sp001") {
if (response.code === "ss003") {
return response.data;
}
throw createApiError(response.code, "Update storage failed");
};
export const deleteStorage = async (uuid: string) => {
@@ -84,11 +79,9 @@ export const deleteStorage = async (uuid: string) => {
const response = await result.json();
if (response.code === "es004") {
return { success: false, code: response.code };
}
if (response.code === "ss004") {
return { success: true, code: response.code };
}
throw createApiError(response.code, "Delete storage failed");
};