diff --git a/backend/database.scheme.sql b/backend/database.scheme.sql
index eab27ea..ecc6552 100644
--- a/backend/database.scheme.sql
+++ b/backend/database.scheme.sql
@@ -36,4 +36,14 @@ CREATE TABLE IF NOT EXISTS products (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (storage_location) REFERENCES storage_locations(uuid) ON DELETE CASCADE
-);
\ No newline at end of file
+);
+
+CREATE TABLE IF NOT EXISTS app_settings (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL UNIQUE,
+ value VARCHAR(500) DEFAULT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+INSERT INTO app_settings (name, value) VALUES ("app-name", null), ("currency", null);
\ No newline at end of file
diff --git a/backend/routes/app/database/users.database.js b/backend/routes/app/database/users.database.js
index ddc1668..fb8b6ff 100644
--- a/backend/routes/app/database/users.database.js
+++ b/backend/routes/app/database/users.database.js
@@ -40,3 +40,35 @@ export const loginUser = async (username) => {
return { code: "eu003" };
}
};
+
+export const updateSettings = async (payload) => {
+ const appName = payload["app-name"];
+ const currency = payload.currency;
+
+ const [result] = await pool.query(
+ `UPDATE app_settings
+ SET value = CASE name
+ WHEN "app-name" THEN ?
+ WHEN "currency" THEN ?
+ ELSE value
+ END
+ WHERE name IN ("app-name", "currency");`,
+ [appName, currency],
+ );
+
+ if (result.affectedRows > 0) {
+ return { code: "su003" };
+ } else {
+ return { code: "eu004" };
+ }
+};
+
+export const getSettings = async () => {
+ const [result] = await pool.query(`SELECT * FROM app_settings;`);
+
+ if (result.length > 0) {
+ return { code: "su004", result };
+ } else {
+ return { code: "eu005" };
+ }
+};
diff --git a/backend/routes/app/users.route.js b/backend/routes/app/users.route.js
index 019f85b..f4899f9 100644
--- a/backend/routes/app/users.route.js
+++ b/backend/routes/app/users.route.js
@@ -1,7 +1,12 @@
import express from "express";
import dotenv from "dotenv";
import { authenticate, generateToken } from "../../services/tokenService.js";
-import { findUser, loginUser } from "./database/users.database.js";
+import {
+ findUser,
+ loginUser,
+ updateSettings,
+ getSettings,
+} from "./database/users.database.js";
dotenv.config();
const router = express.Router();
@@ -9,6 +14,55 @@ router.post("/verify-token", authenticate, async (req, res) => {
res.sendStatus(200);
});
+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") {
+ res.status(201).json({
+ success: true,
+ code: "su003",
+ data: result.data,
+ message: null,
+ });
+ }
+
+ if (result.code === "eu004") {
+ res.status(500).json({
+ success: false,
+ code: "eu004",
+ data: null,
+ message: "Unexpected server error",
+ });
+ }
+});
+
+router.get("/settings", authenticate, async (req, res) => {
+ const result = await getSettings();
+
+ if (result.code === "su004") {
+ res.status(201).json({
+ success: true,
+ code: "su004",
+ data: result.result,
+ message: null,
+ });
+ }
+
+ if (result.code === "eu005") {
+ res.status(500).json({
+ success: false,
+ code: "eu005",
+ data: null,
+ message: "Unexpected server error",
+ });
+ }
+});
+
router.post("/login", async (req, res) => {
const username = req.body.username;
const password = req.body.password;
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 15e203e..488c3ab 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -3,8 +3,9 @@ import { Button, Typography } from "@mui/joy";
import InventoryIcon from "@mui/icons-material/Inventory";
import AddBoxIcon from "@mui/icons-material/AddBox";
import StorageIcon from "@mui/icons-material/Storage";
-import AccountBoxIcon from "@mui/icons-material/AccountBox";
+import SettingsIcon from "@mui/icons-material/Settings";
import { useNavigate, useMatchRoute } from "@tanstack/react-router";
+import Cookies from "js-cookie";
export const Sidebar = () => {
const { t } = useTranslation();
@@ -30,7 +31,7 @@ export const Sidebar = () => {
level="body-lg"
className="text-sm font-medium text-slate-500"
>
- {t("app-subtitle")}
+ {Cookies.get("app-name") ? Cookies.get("app-name") : ""}
@@ -60,12 +61,12 @@ export const Sidebar = () => {
{t("storages")}
diff --git a/frontend/src/misc/interfaces.ts b/frontend/src/misc/interfaces.ts
index fc13ddb..8bd9859 100644
--- a/frontend/src/misc/interfaces.ts
+++ b/frontend/src/misc/interfaces.ts
@@ -40,3 +40,8 @@ export interface AlertInterface {
header: string;
text: string;
}
+
+export interface SettingsIntf {
+ ["app-name"]: string;
+ currency: string;
+}
diff --git a/frontend/src/pages/AddProduct.tsx b/frontend/src/pages/AddProduct.tsx
index 154a4f4..532d088 100644
--- a/frontend/src/pages/AddProduct.tsx
+++ b/frontend/src/pages/AddProduct.tsx
@@ -15,6 +15,7 @@ import { useState } from "react";
import { useForm } from "@tanstack/react-form";
import { createProduct, getStorages } from "../utils/uxFncs";
import type { ProductFormValues } from "../misc/interfaces";
+import Cookies from "js-cookie";
export const AddProduct = () => {
const { t } = useTranslation();
@@ -205,7 +206,7 @@ export const AddProduct = () => {
)}
- {t("currency")}
+ {Cookies.get("currency")}
diff --git a/frontend/src/pages/Inventory.tsx b/frontend/src/pages/Inventory.tsx
index 36afa23..2ee72aa 100644
--- a/frontend/src/pages/Inventory.tsx
+++ b/frontend/src/pages/Inventory.tsx
@@ -29,6 +29,7 @@ import { useQuery } from "@tanstack/react-query";
import { getProducts } from "../utils/uxFncs";
import { visuallyHidden } from "@mui/utils";
import { formatDate } from "../utils/uxFncs";
+import Cookies from "js-cookie";
type Order = "asc" | "desc";
@@ -427,7 +428,7 @@ export const InventoryPage = () => {
{row.price}
- {t("currency")}
+ {Cookies.get("currency")}
|
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx
new file mode 100644
index 0000000..f3c84e1
--- /dev/null
+++ b/frontend/src/pages/Settings.tsx
@@ -0,0 +1,77 @@
+import { Input, Button, CircularProgress } 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 { mutateSettings, fetchSettings } from "../utils/uxFncs";
+import { useEffect } from "react";
+
+export const Settings = () => {
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+
+ const {
+ data: settings,
+ isPending: settingsPending,
+ isSuccess: settingsSuccess,
+ } = useQuery({
+ queryKey: ["settings"],
+ queryFn: fetchSettings,
+ });
+
+ useEffect(() => {
+ Cookies.set("app-name", settings?.data[0].value);
+ Cookies.set("currency", settings?.data[1].value);
+ }, [settingsSuccess]);
+
+ const form = useForm({
+ defaultValues: {
+ "app-name": settings?.data[0].value ?? "",
+ currency: settings?.data[1].value ?? "",
+ },
+ onSubmit: async ({ value }) => {
+ mutate(value);
+ },
+ });
+
+ const { mutate } = useMutation({
+ mutationFn: (values: SettingsIntf) => mutateSettings(values),
+ onSuccess() {
+ queryClient.invalidateQueries({ queryKey: ["settings"] });
+ },
+ });
+
+ return (
+ <>
+ {settingsPending ? (
+
+ ) : (
+
+ {(field) => (
+ field.handleChange(e.target.value)}
+ />
+ )}
+
+
+ {(field) => (
+ field.handleChange(e.target.value)}
+ />
+ )}
+
+
+
+ )}
+ >
+ );
+};
diff --git a/frontend/src/pages/ViewProduct.tsx b/frontend/src/pages/ViewProduct.tsx
index 5acaa1a..975b823 100644
--- a/frontend/src/pages/ViewProduct.tsx
+++ b/frontend/src/pages/ViewProduct.tsx
@@ -19,6 +19,7 @@ import { mutateProduct } from "../utils/uxFncs";
import { toInputDate } from "../utils/uxFncs";
import type { ProductFormValues } from "../misc/interfaces";
import type { productDetailsInterface } from "../misc/interfaces";
+import Cookies from "js-cookie";
interface ViewProductProps {
uuid: string;
@@ -256,7 +257,7 @@ export const ViewProduct = (props: ViewProductProps) => {
)}
- {t("currency")}
+ {Cookies.get("currency")}
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts
index 1e6db25..870569d 100644
--- a/frontend/src/routeTree.gen.ts
+++ b/frontend/src/routeTree.gen.ts
@@ -14,8 +14,8 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as AppHiddenLayoutRouteImport } from './routes/app/_hiddenLayout'
import { Route as AppHiddenLayoutViewProductRouteImport } from './routes/app/_hiddenLayout/view-product'
import { Route as AppHiddenLayoutStoragesRouteImport } from './routes/app/_hiddenLayout/storages'
-import { Route as AppHiddenLayoutProfileRouteImport } from './routes/app/_hiddenLayout/profile'
import { Route as AppHiddenLayoutInventoryRouteImport } from './routes/app/_hiddenLayout/inventory'
+import { Route as AppHiddenLayoutAppSettingsRouteImport } from './routes/app/_hiddenLayout/app-settings'
import { Route as AppHiddenLayoutAddProductRouteImport } from './routes/app/_hiddenLayout/add-product'
const LoginRoute = LoginRouteImport.update({
@@ -44,17 +44,18 @@ const AppHiddenLayoutStoragesRoute = AppHiddenLayoutStoragesRouteImport.update({
path: '/storages',
getParentRoute: () => AppHiddenLayoutRoute,
} as any)
-const AppHiddenLayoutProfileRoute = AppHiddenLayoutProfileRouteImport.update({
- id: '/profile',
- path: '/profile',
- getParentRoute: () => AppHiddenLayoutRoute,
-} as any)
const AppHiddenLayoutInventoryRoute =
AppHiddenLayoutInventoryRouteImport.update({
id: '/inventory',
path: '/inventory',
getParentRoute: () => AppHiddenLayoutRoute,
} as any)
+const AppHiddenLayoutAppSettingsRoute =
+ AppHiddenLayoutAppSettingsRouteImport.update({
+ id: '/app-settings',
+ path: '/app-settings',
+ getParentRoute: () => AppHiddenLayoutRoute,
+ } as any)
const AppHiddenLayoutAddProductRoute =
AppHiddenLayoutAddProductRouteImport.update({
id: '/add-product',
@@ -67,8 +68,8 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/app': typeof AppHiddenLayoutRouteWithChildren
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/app-settings': typeof AppHiddenLayoutAppSettingsRoute
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
- '/app/profile': typeof AppHiddenLayoutProfileRoute
'/app/storages': typeof AppHiddenLayoutStoragesRoute
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
}
@@ -77,8 +78,8 @@ export interface FileRoutesByTo {
'/login': typeof LoginRoute
'/app': typeof AppHiddenLayoutRouteWithChildren
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/app-settings': typeof AppHiddenLayoutAppSettingsRoute
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
- '/app/profile': typeof AppHiddenLayoutProfileRoute
'/app/storages': typeof AppHiddenLayoutStoragesRoute
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
}
@@ -88,8 +89,8 @@ export interface FileRoutesById {
'/login': typeof LoginRoute
'/app/_hiddenLayout': typeof AppHiddenLayoutRouteWithChildren
'/app/_hiddenLayout/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/_hiddenLayout/app-settings': typeof AppHiddenLayoutAppSettingsRoute
'/app/_hiddenLayout/inventory': typeof AppHiddenLayoutInventoryRoute
- '/app/_hiddenLayout/profile': typeof AppHiddenLayoutProfileRoute
'/app/_hiddenLayout/storages': typeof AppHiddenLayoutStoragesRoute
'/app/_hiddenLayout/view-product': typeof AppHiddenLayoutViewProductRoute
}
@@ -100,8 +101,8 @@ export interface FileRouteTypes {
| '/login'
| '/app'
| '/app/add-product'
+ | '/app/app-settings'
| '/app/inventory'
- | '/app/profile'
| '/app/storages'
| '/app/view-product'
fileRoutesByTo: FileRoutesByTo
@@ -110,8 +111,8 @@ export interface FileRouteTypes {
| '/login'
| '/app'
| '/app/add-product'
+ | '/app/app-settings'
| '/app/inventory'
- | '/app/profile'
| '/app/storages'
| '/app/view-product'
id:
@@ -120,8 +121,8 @@ export interface FileRouteTypes {
| '/login'
| '/app/_hiddenLayout'
| '/app/_hiddenLayout/add-product'
+ | '/app/_hiddenLayout/app-settings'
| '/app/_hiddenLayout/inventory'
- | '/app/_hiddenLayout/profile'
| '/app/_hiddenLayout/storages'
| '/app/_hiddenLayout/view-product'
fileRoutesById: FileRoutesById
@@ -169,13 +170,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppHiddenLayoutStoragesRouteImport
parentRoute: typeof AppHiddenLayoutRoute
}
- '/app/_hiddenLayout/profile': {
- id: '/app/_hiddenLayout/profile'
- path: '/profile'
- fullPath: '/app/profile'
- preLoaderRoute: typeof AppHiddenLayoutProfileRouteImport
- parentRoute: typeof AppHiddenLayoutRoute
- }
'/app/_hiddenLayout/inventory': {
id: '/app/_hiddenLayout/inventory'
path: '/inventory'
@@ -183,6 +177,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppHiddenLayoutInventoryRouteImport
parentRoute: typeof AppHiddenLayoutRoute
}
+ '/app/_hiddenLayout/app-settings': {
+ id: '/app/_hiddenLayout/app-settings'
+ path: '/app-settings'
+ fullPath: '/app/app-settings'
+ preLoaderRoute: typeof AppHiddenLayoutAppSettingsRouteImport
+ parentRoute: typeof AppHiddenLayoutRoute
+ }
'/app/_hiddenLayout/add-product': {
id: '/app/_hiddenLayout/add-product'
path: '/add-product'
@@ -195,16 +196,16 @@ declare module '@tanstack/react-router' {
interface AppHiddenLayoutRouteChildren {
AppHiddenLayoutAddProductRoute: typeof AppHiddenLayoutAddProductRoute
+ AppHiddenLayoutAppSettingsRoute: typeof AppHiddenLayoutAppSettingsRoute
AppHiddenLayoutInventoryRoute: typeof AppHiddenLayoutInventoryRoute
- AppHiddenLayoutProfileRoute: typeof AppHiddenLayoutProfileRoute
AppHiddenLayoutStoragesRoute: typeof AppHiddenLayoutStoragesRoute
AppHiddenLayoutViewProductRoute: typeof AppHiddenLayoutViewProductRoute
}
const AppHiddenLayoutRouteChildren: AppHiddenLayoutRouteChildren = {
AppHiddenLayoutAddProductRoute: AppHiddenLayoutAddProductRoute,
+ AppHiddenLayoutAppSettingsRoute: AppHiddenLayoutAppSettingsRoute,
AppHiddenLayoutInventoryRoute: AppHiddenLayoutInventoryRoute,
- AppHiddenLayoutProfileRoute: AppHiddenLayoutProfileRoute,
AppHiddenLayoutStoragesRoute: AppHiddenLayoutStoragesRoute,
AppHiddenLayoutViewProductRoute: AppHiddenLayoutViewProductRoute,
}
diff --git a/frontend/src/routes/app/_hiddenLayout/app-settings.tsx b/frontend/src/routes/app/_hiddenLayout/app-settings.tsx
new file mode 100644
index 0000000..d5a542e
--- /dev/null
+++ b/frontend/src/routes/app/_hiddenLayout/app-settings.tsx
@@ -0,0 +1,10 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { Settings } from "../../../pages/Settings";
+
+export const Route = createFileRoute("/app/_hiddenLayout/app-settings")({
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ return ;
+}
diff --git a/frontend/src/routes/app/_hiddenLayout/profile.tsx b/frontend/src/routes/app/_hiddenLayout/profile.tsx
deleted file mode 100644
index 0ebec38..0000000
--- a/frontend/src/routes/app/_hiddenLayout/profile.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/app/_hiddenLayout/profile')({
- component: RouteComponent,
-})
-
-function RouteComponent() {
- return Hello "/app/_hiddenLayout/profile"!
-}
diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts
index 6ed64c5..9c2b70a 100644
--- a/frontend/src/utils/auth.ts
+++ b/frontend/src/utils/auth.ts
@@ -2,6 +2,7 @@ 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 "./uxFncs";
export async function isAuthenticated() {
if (Cookies.get("token")) {
@@ -39,10 +40,14 @@ export async function signInUser(
});
const response = await result.json();
- console.log(response);
if (result.status === 202) {
Cookies.set("token", response.data.token);
+
+ const settings = await fetchSettings();
+ Cookies.set("app-name", settings?.data[0].value);
+ Cookies.set("currency", settings?.data[1].value);
+
return { ok: true as const };
}
diff --git a/frontend/src/utils/uxFncs.ts b/frontend/src/utils/uxFncs.ts
index 2b75f51..e3cba07 100644
--- a/frontend/src/utils/uxFncs.ts
+++ b/frontend/src/utils/uxFncs.ts
@@ -3,6 +3,7 @@ import Cookies from "js-cookie";
import type {
NewStorage,
ProductFormValues,
+ SettingsIntf,
Storage,
} from "../misc/interfaces";
@@ -229,3 +230,46 @@ export const deleteStorage = async (uuid: string) => {
return { success: true, code: response.code };
}
};
+
+export const mutateSettings = async (payload: SettingsIntf) => {
+ const result = await fetch(`${API_BASE}/users/update-app-settings`, {
+ method: "POST",
+ body: JSON.stringify(payload),
+ headers: {
+ Authorization: `Bearer ${Cookies.get("token") || ""}`,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ });
+
+ 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 };
+ }
+};
+
+export const fetchSettings = async () => {
+ const result = await fetch(`${API_BASE}/users/settings`, {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${Cookies.get("token") || ""}`,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ });
+
+ 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 };
+ }
+};
|