diff --git a/.github/docs/error/README.md b/.github/docs/error/README.md new file mode 100644 index 0000000..7522988 --- /dev/null +++ b/.github/docs/error/README.md @@ -0,0 +1,107 @@ +# Errors + +Here you will find all Error codes and its meaning. Error or status codes that are starting with an s are success codes. + +## Error codes + +### `eu006` + +**Meaning:** There is an error while changing the password. _HTTP-Code: **406**_ + +**Solution:** Make sure that you have entered the correct previous password and that the double check passwords are matching. + +### `eu004` + +**Meaning:** There is an error while updating the settings. _HTTP-Code: **500**_ + +**Solution:** This error should not occur in the frontend. If so, please create an issue in this repository. + +### `eu005` + +**Meaning:** There is an error while fetching the app settings. _HTTP-Code: **500**_ + +**Solution:** This error should not occur in the frontend. If so, please create an issue in this repository. + +### `eu001` + +**Meaning:** Username or password is wrong. _HTTP-Code: **404**_ + +**Solution:** Check the username and password and try again. + +### `eu002` + +**Meaning:** The user is deactivated. _HTTP-Code: **403**_ + +**Solution:** Contact an admin to reactivate the account. + +### `eu003` + +**Meaning:** There is an error while updating the last login timestamp. _HTTP-Code: **500**_ + +**Solution:** This error should not occur in the frontend. If so, please create an issue in this repository. + +### `ep001` + +**Meaning:** There is an error while creating a product. _HTTP-Code: **406**_ + +**Solution:** Verify that all required fields are provided and valid, then try again. + +### `ep002` + +**Meaning:** There is an error while fetching products. _HTTP-Code: **406**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. + +### `ep003` + +**Meaning:** There is an error while fetching a product. _HTTP-Code: **406**_ + +**Solution:** Ensure the product exists and try again. + +### `ep004` + +**Meaning:** There is an error while updating the product amount. _HTTP-Code: **406**_ + +**Solution:** Check the amount value and try again. + +### `ep005` + +**Meaning:** There is an error while updating a product. _HTTP-Code: **406**_ + +**Solution:** Verify the product data and try again. + +### `ep006` + +**Meaning:** There is an error while deleting products. _HTTP-Code: **500**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. + +### `es001` + +**Meaning:** There is an error while fetching storage locations. _HTTP-Code: **500**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. + +### `es000` + +**Meaning:** The request body is invalid. _HTTP-Code: **400**_ + +**Solution:** Provide a storage name. The description is optional. + +### `es002` + +**Meaning:** There is an error while creating a storage location. _HTTP-Code: **500**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. + +### `es003` + +**Meaning:** There is an error while updating a storage location. _HTTP-Code: **500**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. + +### `es004` + +**Meaning:** There is an error while deleting a storage location. _HTTP-Code: **500**_ + +**Solution:** Try again later. If the error persists, create an issue in this repository. diff --git a/backend/routes/app/database/users.database.js b/backend/routes/app/database/users.database.js index fb8b6ff..aec4ea4 100644 --- a/backend/routes/app/database/users.database.js +++ b/backend/routes/app/database/users.database.js @@ -72,3 +72,20 @@ export const getSettings = async () => { return { code: "eu005" }; } }; + +export const changePassword = async ( + username, + currentPasswordUser, + newPassword, +) => { + const [result] = await pool.query( + `UPDATE users SET password = ? WHERE username = ? AND password = ?;`, + [newPassword, username, currentPasswordUser], + ); + + if (result.affectedRows > 0) { + return { code: "su005" }; + } else { + return { code: "eu006" }; + } +}; diff --git a/backend/routes/app/users.route.js b/backend/routes/app/users.route.js index f4899f9..94cec80 100644 --- a/backend/routes/app/users.route.js +++ b/backend/routes/app/users.route.js @@ -6,6 +6,7 @@ import { loginUser, updateSettings, getSettings, + changePassword, } from "./database/users.database.js"; dotenv.config(); const router = express.Router(); @@ -18,8 +19,6 @@ router.post("/update-app-settings", authenticate, async (req, res) => { const appName = req.body.appName; const currency = req.body.currency; - console.log(req.body); - const result = await updateSettings(req.body); if (result.code === "su003") { @@ -91,7 +90,7 @@ router.post("/login", async (req, res) => { const token = await generateToken(result.data); const login = await loginUser(result.data.username); - if (login.code === "e003") { + if (login.code === "eu003") { res.status(500).json({ success: false, code: "eu003", @@ -111,4 +110,26 @@ router.post("/login", async (req, res) => { } }); +router.post("/change-password", authenticate, async (req, res) => { + const currentPassword = req.body.currentPassword; + const newPassword = req.body.newPassword; + const username = req.user.username; + + const result = await changePassword(username, currentPassword, newPassword); + + if (result.code === "su005") { + res.status(202).json({ + success: true, + code: result.code, + }); + } + + if (result.code === "eu006") { + res.status(406).json({ + success: false, + code: result.code, + }); + } +}); + export default router; diff --git a/frontend/src/components/LoginCard.tsx b/frontend/src/components/LoginCard.tsx index 8dd51df..11da27f 100644 --- a/frontend/src/components/LoginCard.tsx +++ b/frontend/src/components/LoginCard.tsx @@ -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({ + 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.header} +
+ {alert.text} +
+ )} {(field) => ( 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({ diff --git a/frontend/src/components/modals/AddStorageModal.tsx b/frontend/src/components/modals/AddStorageModal.tsx index df376d1..f689865 100644 --- a/frontend/src/components/modals/AddStorageModal.tsx +++ b/frontend/src/components/modals/AddStorageModal.tsx @@ -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: () => { diff --git a/frontend/src/components/modals/ChangePasswordModal.tsx b/frontend/src/components/modals/ChangePasswordModal.tsx new file mode 100644 index 0000000..9d7b4f6 --- /dev/null +++ b/frontend/src/components/modals/ChangePasswordModal.tsx @@ -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({ + 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 ( + <> + props.setOpen(false)}> + + + {t("new-password-title")} + +
{ + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => ( + 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)]" + /> + )} + + + {(field) => ( + 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)]" + /> + )} + + + {(field) => ( + 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)]" + /> + )} + + + +
+ {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} +
+
+ + ); +}; diff --git a/frontend/src/misc/interfaces.ts b/frontend/src/misc/interfaces.ts index ea82231..2bec59a 100644 --- a/frontend/src/misc/interfaces.ts +++ b/frontend/src/misc/interfaces.ts @@ -60,4 +60,9 @@ export type ProductRow = { locationDetail: string; expiryDate: string; refillDate: string; -}; \ No newline at end of file +}; + +export type ChangePasswordIntf = { + currentPassword: string; + newPassword: string; +}; diff --git a/frontend/src/pages/AddProduct.tsx b/frontend/src/pages/AddProduct.tsx index cf2818d..9be100e 100644 --- a/frontend/src/pages/AddProduct.tsx +++ b/frontend/src/pages/AddProduct.tsx @@ -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({ + 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({ 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 }) => ( - - ), - )} + {storages?.map((storage) => ( + + ))} )}
@@ -255,6 +285,17 @@ export const AddProduct = () => { {t("save")} + {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} {success && ( { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); + const [alert, setAlert] = useState({ + 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({ 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) => { @@ -94,6 +127,17 @@ export const InventoryPage = () => { {productsIsLoading && } + {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} { const { t } = useTranslation(); const queryClient = useQueryClient(); const [success, setSuccess] = useState(false); + const [alert, setAlert] = useState({ + 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({ + isError: productDetailsError, + error: productDetailsErrorObj, + } = useQuery({ queryKey: ["product", uuid], queryFn: () => getProductDetails(uuid), }); - const { data: storages } = useQuery({ + const { + data: storages, + isError: storagesError, + error: storagesErrorObj, + } = useQuery({ 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 }) => ( - - ), - )} + {storages?.map((storage) => ( + + ))} )} @@ -299,6 +337,17 @@ export const ProductQuickView = () => { {t("save")} + {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} {success && ( { const { t } = useTranslation(); const queryClient = useQueryClient(); + const [modal, setModal] = useState(false); + const [alert, setAlert] = useState({ + 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 ( <> +
@@ -73,6 +112,17 @@ export const Settings = () => {
+ {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} {settingsPending ? (
@@ -143,10 +193,19 @@ export const Settings = () => { +
)} diff --git a/frontend/src/pages/Storages.tsx b/frontend/src/pages/Storages.tsx index 0cbd1c2..e383ba8 100644 --- a/frontend/src/pages/Storages.tsx +++ b/frontend/src/pages/Storages.tsx @@ -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({ + 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({ queryKey: ["storages"], queryFn: () => getStorages(), }); + useEffect(() => { + if (storagesError && storagesErrorObj) { + showError(storagesErrorObj); + } + }, [storagesError, storagesErrorObj]); + return ( <>
@@ -38,6 +74,17 @@ export const Storages = () => { {t("add")}
+ {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )}
{ {storages?.map((storage: Storage) => ( - + ))} diff --git a/frontend/src/pages/ViewProduct.tsx b/frontend/src/pages/ViewProduct.tsx index 8c87772..2861d85 100644 --- a/frontend/src/pages/ViewProduct.tsx +++ b/frontend/src/pages/ViewProduct.tsx @@ -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({ + 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({ + isError: productDetailsError, + error: productDetailsErrorObj, + } = useQuery({ queryKey: ["product", uuid], queryFn: () => getProductDetails(uuid), }); - const { data: storages } = useQuery({ + const { + data: storages, + isError: storagesError, + error: storagesErrorObj, + } = useQuery({ 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 }) => ( - - ), - )} + {storages?.map((storage) => ( + + ))} )} @@ -326,6 +364,17 @@ export const ViewProduct = (props: ViewProductProps) => { {t("save")}
+ {alert.isAlert && ( + + {alert.header} +
+ {alert.text} +
+ )} {success && ( { + const error = new Error(message) as ApiError; + error.code = code || "unknown-error"; + return error; +}; diff --git a/frontend/src/utils/api/auth.ts b/frontend/src/utils/api/auth.ts index 961be76..b68b32b 100644 --- a/frontend/src/utils/api/auth.ts +++ b/frontend/src/utils/api/auth.ts @@ -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"); +}; diff --git a/frontend/src/utils/api/products.ts b/frontend/src/utils/api/products.ts index 262abab..5ecf03e 100644 --- a/frontend/src/utils/api/products.ts +++ b/frontend/src/utils/api/products.ts @@ -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"); }; diff --git a/frontend/src/utils/api/settings.ts b/frontend/src/utils/api/settings.ts index 9785da2..41bb6a5 100644 --- a/frontend/src/utils/api/settings.ts +++ b/frontend/src/utils/api/settings.ts @@ -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"); }; diff --git a/frontend/src/utils/api/storages.ts b/frontend/src/utils/api/storages.ts index 9d4ff4b..2baf1ca 100644 --- a/frontend/src/utils/api/storages.ts +++ b/frontend/src/utils/api/storages.ts @@ -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"); };