fetch error message handling

master
alex 2024-01-25 20:38:59 +01:00
parent 260bebf8dc
commit a8f6f75d28
18 changed files with 766 additions and 1768 deletions

View File

@ -0,0 +1 @@
# Zeit Adler Dashboard

View File

@ -54,6 +54,20 @@
"passwordMinLength": "Passwort muss mindestens {{minLength}} Zeichen lang sein", "passwordMinLength": "Passwort muss mindestens {{minLength}} Zeichen lang sein",
"calendarMaxFutureBookingDaysRequired": "Maximaler Buchungszeitraum ist erforderlich", "calendarMaxFutureBookingDaysRequired": "Maximaler Buchungszeitraum ist erforderlich",
"calendarMinEarliestBookingTimeRequired": "Minimaler frühester Buchungszeitpunkt ist erforderlich" "calendarMinEarliestBookingTimeRequired": "Minimaler frühester Buchungszeitpunkt ist erforderlich"
},
"request": {
"inputsInvalid": {
"title": "Eingaben ungültig",
"description": "Bitte überprüfen Sie Ihre Eingaben."
},
"failed": {
"title": "Ein Fehler ist aufgetreten",
"description": "Bitte versuchen Sie es erneut."
},
"failedInternetProblem": {
"title": "Anfrage fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut."
}
} }
}, },
"pageNotFound": { "pageNotFound": {
@ -98,7 +112,13 @@
}, },
"login": { "login": {
"login": "Anmelden", "login": "Anmelden",
"signUp": "Registrieren" "signUp": "Registrieren",
"request": {
"400": {
"title": "Anmeldung fehlgeschlagen",
"description": "Bitte überprüfen Sie Ihre Eingaben."
}
}
}, },
"storeServices": { "storeServices": {
"pageTitle": "Dienstleistungen", "pageTitle": "Dienstleistungen",

View File

@ -54,6 +54,20 @@
"passwordMinLength": "Password must be at least {{minLength}} characters", "passwordMinLength": "Password must be at least {{minLength}} characters",
"calendarMaxFutureBookingDaysRequired": "Please enter the max. future booking days", "calendarMaxFutureBookingDaysRequired": "Please enter the max. future booking days",
"calendarMinEarliestBookingTimeRequired": "Please enter the min. earliest booking time" "calendarMinEarliestBookingTimeRequired": "Please enter the min. earliest booking time"
},
"request": {
"inputsInvalid": {
"title": "Invalid inputs",
"description": "Please check your inputs and try again."
},
"failed": {
"title": "An error has occurred",
"description": "The request failed. Please try again."
},
"failedInternetProblem": {
"title": "Request failed",
"description": "The request failed. Please check your internet connection and try again."
}
} }
}, },
"pageNotFound": { "pageNotFound": {
@ -98,7 +112,13 @@
}, },
"login": { "login": {
"login": "Login", "login": "Login",
"signUp": "Sign up" "signUp": "Sign up",
"request": {
"400": {
"title": "Login failed",
"description": "Please check your inputs and try again."
}
}
}, },
"services": { "services": {
"pageTitle": "Services" "pageTitle": "Services"

View File

@ -24,7 +24,10 @@ export default function App() {
useEffect(() => { useEffect(() => {
if (!userSession) return; if (!userSession) return;
myFetch("/user", "GET") myFetch({
url: "/user",
method: "GET",
})
.then((data) => { .then((data) => {
setAppUserData(data); setAppUserData(data);
}) })

View File

@ -17,9 +17,7 @@ const StoreWebsite = lazy(() => import("../../Pages/Store/Website"));
//const Feedback = lazy(() => import("../../Pages/Feedback")); //const Feedback = lazy(() => import("../../Pages/Feedback"));
const UserProfile = lazy(() => import("../../Pages/UserProfile")); const UserProfile = lazy(() => import("../../Pages/UserProfile"));
export default function AppRoutes({ userSession, setUserSession }) { export default function AppRoutes({ setUserSession }) {
//const appContext = useAppContext();
return ( return (
<Routes> <Routes>
<Route <Route
@ -111,10 +109,7 @@ export default function AppRoutes({ userSession, setUserSession }) {
path={Constants.ROUTE_PATHS.USER_PROFILE} path={Constants.ROUTE_PATHS.USER_PROFILE}
element={ element={
<MySupsenseFallback> <MySupsenseFallback>
<UserProfile <UserProfile setUserSession={setUserSession} />
userSession={userSession}
setUserSession={setUserSession}
/>
</MySupsenseFallback> </MySupsenseFallback>
} }
/> />

View File

@ -1,65 +1,11 @@
import { import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
BellOutlined, import { Button } from "antd";
CheckCircleOutlined,
CloseCircleOutlined,
CloseOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
InboxOutlined,
InfoCircleOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
QuestionCircleOutlined,
} from "@ant-design/icons";
import { Badge, Button, Drawer, List, Popconfirm, Typography } from "antd";
import { Header } from "antd/es/layout/layout"; import { Header } from "antd/es/layout/layout";
import { useEffect, useState } from "react";
import { useHeaderContext } from "../../Contexts/HeaderContext";
import { myFetch } from "../../utils";
import { useWebSocketContext } from "../../Contexts/WebSocketContext";
import { SentMessagesCommands } from "../../Handlers/WebSocketMessageHandler";
import { useTranslation } from "react-i18next";
import LiveTimeAgo from "../LiveTimeAgo";
import MyPagination from "../MyPagination";
export default function HeaderMenu({ export default function HeaderMenu({
isSideMenuCollapsed, isSideMenuCollapsed,
setIsSideMenuCollapsed, setIsSideMenuCollapsed,
}) { }) {
//const webSocketContext = useWebSocketContext();
//const headerContext = useHeaderContext();
//const { t } = useTranslation();
const [isNotificationDrawerOpen, setIsNotificationDrawerOpen] =
useState(false);
/*
const fetchNotifications = (page = 1) => {
myFetch(`/notifications?page=${page}`, "GET").then((data) =>
headerContext.setNotificationResponse(data)
);
};
const onPaginationChange = (page) => {
headerContext.setPaginationPage(page);
headerContext.paginationPageRef.current = page;
}; */
/*
useEffect(() => {
// fetch will only be called if the drawer is open and there are no notifications
// further notifications will be fetched by the websocket
if (!isNotificationDrawerOpen || headerContext.notficationResponse !== null)
return;
fetchNotifications(1);
}, [isNotificationDrawerOpen]);
useEffect(() => {
if (!isNotificationDrawerOpen) return;
fetchNotifications(headerContext.paginationPage);
}, [headerContext.paginationPage]); */
return ( return (
<Header <Header
style={{ style={{
@ -85,112 +31,3 @@ export default function HeaderMenu({
</Header> </Header>
); );
} }
/*
<Button
type="text"
icon={
<Badge count={headerContext.totalNotifications} offset={[2, -2]}>
<BellOutlined style={{ fontSize: "16px" }} />
</Badge>
}
onClick={() => setIsNotificationDrawerOpen(true)}
style={{ fontSize: "16px", width: 64, height: 64 }}
/>
<Drawer
title={t("header.notificationDrawer.title")}
placement="right"
open={isNotificationDrawerOpen}
onClose={() => setIsNotificationDrawerOpen(false)}
extra={
headerContext.totalNotifications > 0 && (
<Popconfirm
title={t("header.notificationDrawer.deleteAllPopconfirm.title")}
okText={t("common.button.confirm")}
cancelText={t("common.button.cancel")}
onConfirm={() => {
webSocketContext.SendSocketMessage(
SentMessagesCommands.DeleteAllNotifications,
{}
);
setIsNotificationDrawerOpen(false);
}}
>
<Button type="link" icon={<DeleteOutlined />}>
{t("header.notificationDrawer.deleteAllButtonText")}
</Button>
</Popconfirm>
)
}
>
{isNotificationDrawerOpen && (
<>
{headerContext.totalNotifications === 0 ||
headerContext.notficationResponse === null ? (
<div style={{ textAlign: "center" }}>
<InboxOutlined style={{ fontSize: 32, marginBottom: 10 }} />
<Typography.Title level={5}>
{t("header.notificationDrawer.noNotifications")}
</Typography.Title>
</div>
) : (
<List
dataSource={headerContext.notficationResponse.Notifications.sort(
(a, b) => {
return new Date(b.CreatedAt) - new Date(a.CreatedAt);
}
)}
footer={
<MyPagination
paginationPage={headerContext.paginationPage}
setPaginationPage={(page) => onPaginationChange(page)}
totalPages={headerContext.notficationResponse.TotalPages}
size="small"
/>
}
renderItem={(item) => (
<List.Item>
<List.Item.Meta
avatar={<NotificationTypeIcon type={item.Type} />}
title={item.Title}
description={<LiveTimeAgo startTime={item.CreatedAt} />}
/>
<CloseOutlined
onClick={() => {
/*webSocketContext.SendSocketMessage(
SentMessagesCommands.DeleteOneNotification,
{
notificationId: item.Id,
}
)
}}
/>
</List.Item>
)}
/>
)}
</>
)}
</Drawer>
*/
function NotificationTypeIcon({ type }) {
switch (type) {
case 1:
return <CheckCircleOutlined color="#33a834" style={{ fontSize: 16 }} />;
case 2:
return <InfoCircleOutlined color="#0c69d7" style={{ fontSize: 16 }} />;
case 3:
return (
<ExclamationCircleOutlined color="#dd9433" style={{ fontSize: 16 }} />
);
case 4:
return <CloseCircleOutlined color="#e5444b" style={{ fontSize: 16 }} />;
default:
return <QuestionCircleOutlined color="#fff" style={{ fontSize: 16 }} />;
}
}

View File

@ -187,7 +187,11 @@ export function MyAvailableCheckFormInput({
body[fetchParameter] = value; // like accountName: value body[fetchParameter] = value; // like accountName: value
myFetch(fetchUrl, "POST", body) myFetch({
url: fetchUrl,
method: "POST",
body: body,
})
.then(() => { .then(() => {
resolve(); resolve();
}) })

View File

@ -4,7 +4,7 @@ import {
PlusOutlined, PlusOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Popconfirm, Tooltip } from "antd"; import { Popconfirm } from "antd";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -79,14 +79,14 @@ export function MyPlusIcon({ onClick }) {
export function MyIcon({ export function MyIcon({
popconfirmDisabled, popconfirmDisabled,
propsPopconfirm, propsPopconfirm,
propsTooltip, // propsTooltip,
onConfirm, onConfirm,
onFetchSuccess, onFetchSuccess,
onCancel, onCancel,
popConfirmTitle, popConfirmTitle,
popConfirmDescription, popConfirmDescription,
popConfirmOkText, popConfirmOkText,
tooltipTitle, //tooltipTitle,
icon, icon,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -3,8 +3,8 @@ import { Button, Form, Modal, Tabs, notification } from "antd";
import { import {
EncodeStringToBase64, EncodeStringToBase64,
myFetch, myFetch,
myFetchContentType,
setUserSessionToLocalStorage, setUserSessionToLocalStorage,
showInputsInvalidNotification,
} from "../../utils"; } from "../../utils";
import { useState } from "react"; import { useState } from "react";
import { import {
@ -19,31 +19,27 @@ export default function Login() {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [api, contextHolder] = notification.useNotification(); const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [selectedMethod, setSelectedMethod] = useState("1"); const [selectedMethod, setSelectedMethod] = useState("1");
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const showErrorNotification = (errStatus) => { const showErrorNotification = (errStatus) => {
if (errStatus === 401) { if (errStatus === 400) {
api["error"]({ notificationApi["error"]({
message: "Account deactivated", message: t("login.request.400.title"),
description: "Please contact an administrator", description: t("login.request.400.description"),
}); });
return;
} }
api["error"]({
message: "Login failed",
description: "Please check your accountName and password!",
});
}; };
return ( return (
<> <>
{contextHolder} {notificationContextHolder}
<Modal <Modal
open={true} open={true}
mask={false}
closable={false} closable={false}
centered centered
keyboard={false} keyboard={false}
@ -68,18 +64,16 @@ export default function Login() {
body.username = values.username; body.username = values.username;
} }
myFetch( myFetch({
`/user/auth/${selectedMethod === "1" ? "login" : "signup"}`, url: `/user/auth/${
"POST", selectedMethod === "1" ? "login" : "signup"
body, }`,
{}, method: "POST",
myFetchContentType.JSON, body: body,
"", notificationApi: notificationApi,
true t: t,
) })
.then((data) => { .then((data) => {
console.log(data.XAuthorization);
setUserSessionToLocalStorage(data.XAuthorization); setUserSessionToLocalStorage(data.XAuthorization);
window.location.href = "/"; window.location.href = "/";
}) })
@ -88,9 +82,7 @@ export default function Login() {
setIsRequesting(false); setIsRequesting(false);
}); });
}) })
.catch((info) => { .catch(() => showInputsInvalidNotification(notificationApi, t));
console.log("Validate Failed:", info);
});
}} }}
> >
{selectedMethod === "1" ? t("login.login") : t("login.signUp")} {selectedMethod === "1" ? t("login.login") : t("login.signUp")}
@ -119,9 +111,7 @@ export default function Login() {
}, },
]} ]}
centered centered
onChange={(activeKey) => { onChange={(activeKey) => setSelectedMethod(activeKey)}
setSelectedMethod(activeKey);
}}
/> />
<Form form={form} layout="vertical" requiredMark={false}> <Form form={form} layout="vertical" requiredMark={false}>

View File

@ -1,20 +1,27 @@
import { Button, Result, Spin } from "antd"; import { Button, Result, Spin, notification } from "antd";
import { Link, useParams, useNavigate } from "react-router-dom"; import { Link, useParams, useNavigate } from "react-router-dom";
import { Constants, myFetch } from "../../../../utils"; import { Constants, myFetch } from "../../../../utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import MyCenteredContainer from "../../../../Components/MyContainer"; import MyCenteredContainer from "../../../../Components/MyContainer";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { MySupsenseFallback } from "../../../../Components/MySupsenseFallback";
export default function StoreCalendarAuth() { export default function StoreCalendarAuth() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const { status } = useParams(); const { status } = useParams();
const [isRequesting, setIsRequesting] = useState(true); const [isRequesting, setIsRequesting] = useState(true);
const [storeId, setStoreId] = useState(""); const [storeId, setStoreId] = useState("");
useEffect(() => { useEffect(() => {
myFetch("/calendar/store", "GET") myFetch({
url: "/calendar/store",
method: "GET",
notificationApi: notificationApi,
t: t,
})
.then((res) => { .then((res) => {
setIsRequesting(false); setIsRequesting(false);
setStoreId(res.storeId); setStoreId(res.storeId);
@ -33,32 +40,36 @@ export default function StoreCalendarAuth() {
} }
return ( return (
<Result <>
status={status === "finish" ? "success" : "error"} {notificationContextHolder}
title={
status === "finish"
? t("calendar.authFinish.title")
: t("calendar.authFailed.title")
}
subTitle={
status === "finish"
? t("calendar.authFinish.description")
: t("calendar.authFailed.description")
}
extra={[
<div key="1">
<Link to={`${Constants.ROUTE_PATHS.STORE.CALENDAR}/${storeId}`}>
<Button>
{status === "finish"
? t("calendar.authFinish.button")
: t("calendar.authFailed.button")}
</Button>
</Link>
{status === "finish" && <CountdownRedirect storeId={storeId} />} <Result
</div>, status={status === "finish" ? "success" : "error"}
]} title={
/> status === "finish"
? t("calendar.authFinish.title")
: t("calendar.authFailed.title")
}
subTitle={
status === "finish"
? t("calendar.authFinish.description")
: t("calendar.authFailed.description")
}
extra={[
<div key="1">
<Link to={`${Constants.ROUTE_PATHS.STORE.CALENDAR}/${storeId}`}>
<Button>
{status === "finish"
? t("calendar.authFinish.button")
: t("calendar.authFailed.button")}
</Button>
</Link>
{status === "finish" && <CountdownRedirect storeId={storeId} />}
</div>,
]}
/>
</>
); );
} }

View File

@ -10,6 +10,7 @@ import {
Spin, Spin,
Switch, Switch,
Typography, Typography,
notification,
} from "antd"; } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
@ -38,6 +39,9 @@ const { useBreakpoint } = Grid;
export default function StoreCalendar() { export default function StoreCalendar() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const { storeId } = useParams(); const { storeId } = useParams();
const [calendarSettings, setCalendarSettings] = useState({}); const [calendarSettings, setCalendarSettings] = useState({});
@ -47,7 +51,12 @@ export default function StoreCalendar() {
// delete session cookie // delete session cookie
document.cookie = `session=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; document.cookie = `session=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
myFetch("/calendar/settings", "GET") myFetch({
url: "/calendar/settings",
method: "GET",
notificationApi: notificationApi,
t: t,
})
.then((res) => { .then((res) => {
setIsRequesting(false); setIsRequesting(false);
setCalendarSettings(res); setCalendarSettings(res);
@ -67,6 +76,8 @@ export default function StoreCalendar() {
return ( return (
<> <>
{notificationContextHolder}
{calendarSettings.connected === false ? ( {calendarSettings.connected === false ? (
<MyCenteredContainer> <MyCenteredContainer>
<Result <Result
@ -135,6 +146,7 @@ function CalendarFrame({ storeId }) {
</div> </div>
)} )}
<iframe <iframe
title="calendar"
onLoad={() => setIsLoading(false)} onLoad={() => setIsLoading(false)}
style={{ border: 0, borderRadius: 12 }} style={{ border: 0, borderRadius: 12 }}
width="100%" width="100%"
@ -147,6 +159,9 @@ function CalendarFrame({ storeId }) {
function CardPersonalCalendarSettings({ settings }) { function CardPersonalCalendarSettings({ settings }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [formUnlinkCalendar] = Form.useForm(); const [formUnlinkCalendar] = Form.useForm();
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
@ -189,8 +204,14 @@ function CardPersonalCalendarSettings({ settings }) {
} }
delayTimeout.current = setTimeout(() => { delayTimeout.current = setTimeout(() => {
myFetch("/calendar/settings/personal", "POST", { myFetch({
calendarUsingPrimaryCalendar: usingPrimaryCalendar, url: "/calendar/settings/personal",
method: "POST",
body: {
calendarUsingPrimaryCalendar: usingPrimaryCalendar,
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => setRequestState(RequestState.SUCCESS)) .then(() => setRequestState(RequestState.SUCCESS))
.catch((errStatus) => { .catch((errStatus) => {
@ -239,6 +260,8 @@ function CardPersonalCalendarSettings({ settings }) {
return ( return (
<> <>
{notificationContextHolder}
<Card <Card
title={ title={
screenBreakpoint.xl ? ( screenBreakpoint.xl ? (
@ -290,8 +313,14 @@ function CardPersonalCalendarSettings({ settings }) {
formUnlinkCalendar.validateFields().then((values) => { formUnlinkCalendar.validateFields().then((values) => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/calendar/settings/personal/unlink", "POST", { myFetch({
password: EncodeStringToBase64(values.password), url: "/calendar/settings/personal/unlink",
method: "POST",
body: {
password: EncodeStringToBase64(values.password),
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => window.location.reload()) .then(() => window.location.reload())
.catch((errStatus) => { .catch((errStatus) => {
@ -321,6 +350,9 @@ function CardPersonalCalendarSettings({ settings }) {
function CardStoreCalendarSettings({ settings }) { function CardStoreCalendarSettings({ settings }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [requestState, setRequestState] = useState(RequestState.INIT); const [requestState, setRequestState] = useState(RequestState.INIT);
@ -369,9 +401,15 @@ function CardStoreCalendarSettings({ settings }) {
} }
delayTimeout.current = setTimeout(() => { delayTimeout.current = setTimeout(() => {
myFetch("/calendar/settings/store", "POST", { myFetch({
calendarMaxFutureBookingDays, url: "/calendar/settings/store",
calendarMinEarliestBookingTime, method: "POST",
body: {
calendarMaxFutureBookingDays,
calendarMinEarliestBookingTime,
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => setRequestState(RequestState.SUCCESS)) .then(() => setRequestState(RequestState.SUCCESS))
.catch((errStatus) => { .catch((errStatus) => {
@ -382,22 +420,26 @@ function CardStoreCalendarSettings({ settings }) {
}, [calendarMaxFutureBookingDays, calendarMinEarliestBookingTime]); }, [calendarMaxFutureBookingDays, calendarMinEarliestBookingTime]);
return ( return (
<Card <>
title={t("calendar.cardStoreCalendarSettings.title")} {notificationContextHolder}
extra={
<RequestStateItem
state={requestState}
setRequestState={setRequestState}
/>
}
>
<Form form={form} requiredMark={false}>
<>
<MyCalendarMaxFutureBookingDaysFormInput />
<MyCalendarMinEarliestBookingTimeFormInput /> <Card
</> title={t("calendar.cardStoreCalendarSettings.title")}
</Form> extra={
</Card> <RequestStateItem
state={requestState}
setRequestState={setRequestState}
/>
}
>
<Form form={form} requiredMark={false}>
<>
<MyCalendarMaxFutureBookingDaysFormInput />
<MyCalendarMinEarliestBookingTimeFormInput />
</>
</Form>
</Card>
</>
); );
} }

View File

@ -1,5 +1,13 @@
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
import { Button, Checkbox, Form, Grid, Popconfirm, Space } from "antd"; import {
Button,
Checkbox,
Form,
Grid,
Popconfirm,
Space,
notification,
} from "antd";
import MyModal, { import MyModal, {
MyModalCloseCreateButtonFooter, MyModalCloseCreateButtonFooter,
MyModalCloseSaveButtonFooter, MyModalCloseSaveButtonFooter,
@ -21,6 +29,9 @@ const { useBreakpoint } = Grid;
export default function StoreEmployees() { export default function StoreEmployees() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
const { storeId } = useParams(); const { storeId } = useParams();
@ -85,8 +96,12 @@ export default function StoreEmployees() {
onConfirm={() => { onConfirm={() => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/users", "DELETE", { myFetch({
userId: record.key, url: "/users",
method: "DELETE",
body: { userId: record.key },
notificationApi: notificationApi,
t: t,
}) })
.then(() => fetchEmployees()) .then(() => fetchEmployees())
.catch((errStatus) => { .catch((errStatus) => {
@ -123,7 +138,12 @@ export default function StoreEmployees() {
const fetchEmployees = () => { const fetchEmployees = () => {
setIsRequesting(true); setIsRequesting(true);
myFetch(`/users/${storeId}`, "GET") myFetch({
url: `/users/${storeId}`,
method: "GET",
notificationApi: notification,
t: t,
})
.then((data) => { .then((data) => {
setIsRequesting(false); setIsRequesting(false);
setRequestData(data); setRequestData(data);
@ -137,6 +157,8 @@ export default function StoreEmployees() {
return ( return (
<> <>
{notificationContextHolder}
<div <div
style={{ style={{
display: "flex", display: "flex",
@ -271,7 +293,13 @@ function ModalAddEditEmployee({
values.calendarMinEarliestBookingTime; values.calendarMinEarliestBookingTime;
} }
myFetch("/users", "POST", body) myFetch({
url: "/users",
method: "POST",
body: body,
notificationApi: notification,
t: t,
})
.then(() => { .then(() => {
setIsRequesting(false); setIsRequesting(false);
handleModalClose(); handleModalClose();
@ -357,7 +385,13 @@ function ModalAddEditEmployee({
.then(() => { .then(() => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/users/update", "POST", body) myFetch({
url: "/users/update",
method: "POST",
body: body,
notificationApi: notification,
t: t,
})
.then(() => { .then(() => {
setIsRequesting(false); setIsRequesting(false);
handleModalClose(); handleModalClose();

View File

@ -11,6 +11,7 @@ import {
Spin, Spin,
Tooltip, Tooltip,
Typography, Typography,
notification,
} from "antd"; } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -33,6 +34,8 @@ const { useBreakpoint } = Grid;
export default function StoreServices() { export default function StoreServices() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
const { storeId } = useParams(); const { storeId } = useParams();
@ -58,7 +61,12 @@ export default function StoreServices() {
const fetchServices = () => { const fetchServices = () => {
setIsRequestingServices(true); setIsRequestingServices(true);
myFetch(`/store/services/${storeId}`, "GET") myFetch({
url: `/store/services/${storeId}`,
method: "GET",
notificationApi: notificationApi,
t: t,
})
.then((data) => { .then((data) => {
setIsRequestingServices(false); setIsRequestingServices(false);
setServicesData(data); setServicesData(data);
@ -72,6 +80,8 @@ export default function StoreServices() {
return ( return (
<> <>
{notificationContextHolder}
<div <div
style={{ style={{
display: "flex", display: "flex",
@ -148,6 +158,8 @@ function Service({
fetchServices, fetchServices,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [isRequestingActivities, setIsRequestingActivities] = useState(false); const [isRequestingActivities, setIsRequestingActivities] = useState(false);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -159,10 +171,12 @@ function Service({
setIsRequestingActivities(true); setIsRequestingActivities(true);
myFetch( myFetch({
`/store/services/activities/${storeId}/${service.service_id}`, url: `/store/services/activities/${storeId}/${service.service_id}`,
"GET" method: "GET",
) notificationApi: notificationApi,
t: t,
})
.then((data) => { .then((data) => {
setIsRequestingActivities(false); setIsRequestingActivities(false);
setServiceActivities(data.activities); setServiceActivities(data.activities);
@ -175,24 +189,27 @@ function Service({
useEffect(() => fetchServiceActivities(), [isOpen]); useEffect(() => fetchServiceActivities(), [isOpen]);
return ( return (
<Collapse <>
key={service.service_id} {notificationContextHolder}
onChange={(e) => setIsOpen(e.length !== 0)}
items={[
{
key: "1",
label: (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>{service.name}</span>
<Space> <Collapse
{/* key={service.service_id}
onChange={(e) => setIsOpen(e.length !== 0)}
items={[
{
key: "1",
label: (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>{service.name}</span>
<Space>
{/*
<ArrowUpOutlined <ArrowUpOutlined
disabled disabled
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
@ -202,192 +219,202 @@ function Service({
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
*/} */}
<MyPlusIcon <MyPlusIcon
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setAddEditServiceActivityModalOptions({ setAddEditServiceActivityModalOptions({
mode: "add", mode: "add",
isOpen: true, isOpen: true,
service: service, service: service,
}); });
}} }}
/> />
<MyEditIcon <MyEditIcon
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setAddEditServiceModalOptions({ setAddEditServiceModalOptions({
mode: "edit", mode: "edit",
isOpen: true, isOpen: true,
service: service, service: service,
}); });
}} }}
/> />
<MyDeleteIcon <MyDeleteIcon
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
propsPopconfirm={{ propsPopconfirm={{
placement: "left", placement: "left",
}} }}
onConfirm={() => { onConfirm={() => {
return myFetch( return myFetch({
`/store/services/${service.service_id}`, url: `/store/services/${service.service_id}`,
"DELETE" method: "DELETE",
); notificationApi: notificationApi,
}} t: t,
onFetchSuccess={fetchServices} });
popConfirmTitle={t( }}
"storeServices.popConfirmDeleteService.title" onFetchSuccess={fetchServices}
)} popConfirmTitle={t(
popConfirmDescription={t( "storeServices.popConfirmDeleteService.title"
"storeServices.popConfirmDeleteService.description" )}
)} popConfirmDescription={t(
/> "storeServices.popConfirmDeleteService.description"
</Space> )}
</div> />
), </Space>
children: ( </div>
<Space ),
key={`space-${service.service_id}`} children: (
direction="vertical" <Space
style={{ width: "100%" }} key={`space-${service.service_id}`}
> direction="vertical"
{isRequestingActivities ? ( style={{ width: "100%" }}
<Skeleton active> >
<Card title="loading"> {isRequestingActivities ? (
<p>loading</p> <Skeleton active>
<p>loading</p> <Card title="loading">
<p>loading</p> <p>loading</p>
</Card> <p>loading</p>
</Skeleton> <p>loading</p>
) : ( </Card>
<> </Skeleton>
{serviceActivities.length === 0 ? ( ) : (
<MyEmpty /> <>
) : ( {serviceActivities.length === 0 ? (
serviceActivities.map((activity) => { <MyEmpty />
let userList = []; ) : (
serviceActivities.map((activity) => {
let userList = [];
if (activity.StoreServiceActivityUsers.length > 0) { if (activity.StoreServiceActivityUsers.length > 0) {
// StoreServiceActivityUsers is only an array of user_ids // StoreServiceActivityUsers is only an array of user_ids
// we need to get the user object from the users array // we need to get the user object from the users array
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
for ( for (
let j = 0; let j = 0;
j < activity.StoreServiceActivityUsers.length; j < activity.StoreServiceActivityUsers.length;
j++ j++
) {
if (
users[i].user_id ===
activity.StoreServiceActivityUsers[j].user_id
) { ) {
userList.push(users[i]); if (
users[i].user_id ===
activity.StoreServiceActivityUsers[j].user_id
) {
userList.push(users[i]);
}
} }
} }
} else {
// if there are no users assigned to this activity, we just use the whole users array
userList = users;
} }
} else {
// if there are no users assigned to this activity, we just use the whole users array
userList = users;
}
return ( return (
<Card <Card
key={activity.activity_id} key={activity.activity_id}
title={activity.name} title={activity.name}
extra={ extra={
<Space> <Space>
<Avatar.Group maxCount={2} size="small"> <Avatar.Group maxCount={2} size="small">
{userList.map((user) => ( {userList.map((user) => (
<Tooltip <Tooltip
key={user.user_id} key={user.user_id}
title={user.username} title={user.username}
>
<Avatar
size="small"
style={{ backgroundColor: "#6878d6" }}
> >
{user.username.charAt(0)} <Avatar
</Avatar> size="small"
</Tooltip> style={{ backgroundColor: "#6878d6" }}
))} >
</Avatar.Group> {user.username.charAt(0)}
</Avatar>
</Tooltip>
))}
</Avatar.Group>
{/* {/*
<ArrowUpOutlined disabled /> <ArrowUpOutlined disabled />
<ArrowDownOutlined disabled /> <ArrowDownOutlined disabled />
*/} */}
<MyEditIcon <MyEditIcon
onClick={() => { onClick={() => {
setAddEditServiceActivityModalOptions({ setAddEditServiceActivityModalOptions({
mode: "edit", mode: "edit",
isOpen: true, isOpen: true,
activity: activity, activity: activity,
}); });
}} }}
/> />
<MyDeleteIcon <MyDeleteIcon
propsPopconfirm={{ propsPopconfirm={{
placement: "left", placement: "left",
}} }}
onConfirm={() => { onConfirm={() => {
return myFetch( return myFetch({
`/store/services/activity/${activity.activity_id}`, url: `/store/services/activity/${activity.activity_id}`,
"DELETE" method: "DELETE",
); notificationApi: notificationApi,
}} t: t,
onFetchSuccess={fetchServiceActivities} });
popConfirmTitle={t( }}
"storeServices.popConfirmDeleteServiceActivity.title" onFetchSuccess={fetchServiceActivities}
popConfirmTitle={t(
"storeServices.popConfirmDeleteServiceActivity.title"
)}
popConfirmDescription={t(
"storeServices.popConfirmDeleteServiceActivity.description"
)}
/>
</Space>
}
>
<Typography.Title level={5}>
{t("storeServices.serviceActivityDescription")}
</Typography.Title>
<Typography.Paragraph>
{activity.description}
</Typography.Paragraph>
<Typography.Title level={5}>
{t("storeServices.serviceActivityPrice")}
</Typography.Title>
<p>{activity.price} </p>
<Typography.Title level={5}>
{t(
"storeServices.serviceActivityDurationMinutes"
)}
</Typography.Title>
<Typography.Paragraph>
{activity.duration}{" "}
{activity.duration === 1
? t("common.unit.minute")
: t("common.unit.minutes")}{" "}
<Typography.Text type="secondary">
{durationToHoursAndMinutes(
t,
activity.duration
)} )}
popConfirmDescription={t( </Typography.Text>
"storeServices.popConfirmDeleteServiceActivity.description" </Typography.Paragraph>
)} </Card>
/> );
</Space> })
} )}
> </>
<Typography.Title level={5}> )}
{t("storeServices.serviceActivityDescription")} </Space>
</Typography.Title> ),
},
<Typography.Paragraph> ]}
{activity.description} />
</Typography.Paragraph> </>
<Typography.Title level={5}>
{t("storeServices.serviceActivityPrice")}
</Typography.Title>
<p>{activity.price} </p>
<Typography.Title level={5}>
{t("storeServices.serviceActivityDurationMinutes")}
</Typography.Title>
<Typography.Paragraph>
{activity.duration}{" "}
{activity.duration === 1
? t("common.unit.minute")
: t("common.unit.minutes")}{" "}
<Typography.Text type="secondary">
{durationToHoursAndMinutes(t, activity.duration)}
</Typography.Text>
</Typography.Paragraph>
</Card>
);
})
)}
</>
)}
</Space>
),
},
]}
/>
); );
} }
@ -430,6 +457,8 @@ function ModalAddEditService({
setAddEditServiceModalOptions, setAddEditServiceModalOptions,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -456,6 +485,8 @@ function ModalAddEditService({
return ( return (
<> <>
{notificationContextHolder}
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
@ -489,9 +520,15 @@ function ModalAddEditService({
.then((values) => { .then((values) => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/store/services", "POST", { myFetch({
storeId: storeId, url: "/store/services",
name: values.serviceName, method: "POST",
body: {
storeId: storeId,
name: values.serviceName,
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => { .then(() => {
setIsRequesting(false); setIsRequesting(false);
@ -527,9 +564,16 @@ function ModalAddEditService({
.then(() => { .then(() => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/store/services/update", "POST", { myFetch({
serviceId: addEditServiceModalOptions.service.service_id, url: "/store/services/update",
name: formServiceName, method: "POST",
body: {
serviceId:
addEditServiceModalOptions.service.service_id,
name: formServiceName,
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => { .then(() => {
setIsRequesting(false); setIsRequesting(false);
@ -564,7 +608,8 @@ function ModalAddEditServiceActivity({
users, users,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const [selectedEmployeesRowKeys, setSelectedEmployeesRowKeys] = useState([]); const [selectedEmployeesRowKeys, setSelectedEmployeesRowKeys] = useState([]);
@ -629,186 +674,202 @@ function ModalAddEditServiceActivity({
}, [addEditServiceActivityModalOptions.isOpen]); }, [addEditServiceActivityModalOptions.isOpen]);
return ( return (
<MyModal <>
title={ {notificationContextHolder}
addEditServiceActivityModalOptions.mode === "add"
? t("storeServices.modalAddServiceActivity.title")
: t("storeServices.modalEditServiceActivity.title")
}
isOpen={addEditServiceActivityModalOptions.isOpen}
onCancel={handleModalClose}
footer={
addEditServiceActivityModalOptions.mode === "add" ? (
<MyModalCloseCreateButtonFooter
onCancel={handleModalClose}
isCreateButtonLoading={isRequesting}
onCreate={() => {
form
.validateFields()
.then((values) => {
setIsRequesting(true);
myFetch("/store/services/activity", "POST", { <MyModal
serviceId: title={
addEditServiceActivityModalOptions.service.service_id, addEditServiceActivityModalOptions.mode === "add"
name: values.serviceActivityName, ? t("storeServices.modalAddServiceActivity.title")
description: values.serviceActivityDescription, : t("storeServices.modalEditServiceActivity.title")
price: values.serviceActivityPrice, }
duration: values.serviceActivityDurationMinutes, isOpen={addEditServiceActivityModalOptions.isOpen}
userIds: onCancel={handleModalClose}
selectedEmployeesRowKeys.length === users.length footer={
? [] addEditServiceActivityModalOptions.mode === "add" ? (
: selectedEmployeesRowKeys, <MyModalCloseCreateButtonFooter
onCancel={handleModalClose}
isCreateButtonLoading={isRequesting}
onCreate={() => {
form
.validateFields()
.then((values) => {
setIsRequesting(true);
myFetch({
url: "/store/services/activity",
method: "POST",
body: {
serviceId:
addEditServiceActivityModalOptions.service.service_id,
name: values.serviceActivityName,
description: values.serviceActivityDescription,
price: values.serviceActivityPrice,
duration: values.serviceActivityDurationMinutes,
userIds:
selectedEmployeesRowKeys.length === users.length
? []
: selectedEmployeesRowKeys,
},
notificationApi: notificationApi,
t: t,
})
.then(() => {
setIsRequesting(false);
handleModalClose();
fetchServices();
})
.catch((errStatus) => {
console.log(errStatus);
});
}) })
.then(() => { .catch((info) => {
setIsRequesting(false); console.log("Validate Failed:", info);
handleModalClose(); });
}}
/>
) : (
<MyModalCloseSaveButtonFooter
onCancel={handleModalClose}
isSaveButtonLoading={isRequesting}
onSave={() => {
const formServiceActivityName = form.getFieldValue(
"serviceActivityName"
);
const formServiceActivityDescription = form.getFieldValue(
"serviceActivityDescription"
);
const formServiceActivityPrice = form.getFieldValue(
"serviceActivityPrice"
);
const formServiceActivityDurationMinutes = form.getFieldValue(
"serviceActivityDurationMinutes"
);
fetchServices(); // if the service didn't change, don't send a request
if (
addEditServiceActivityModalOptions.activity.name ===
formServiceActivityName &&
addEditServiceActivityModalOptions.activity.description ===
formServiceActivityDescription &&
addEditServiceActivityModalOptions.activity.price ===
formServiceActivityPrice &&
addEditServiceActivityModalOptions.activity.duration ===
formServiceActivityDurationMinutes &&
(selectedEmployeesRowKeys.length === users.length ||
addEditServiceActivityModalOptions.activity
.StoreServiceActivityUsers.length ===
selectedEmployeesRowKeys.length)
) {
handleModalClose();
return;
}
let validateFields = [];
let body = {
activityId:
addEditServiceActivityModalOptions.activity.activity_id,
};
if (
formServiceActivityName !==
addEditServiceActivityModalOptions.activity.name
) {
validateFields.push("serviceActivityName");
body.name = formServiceActivityName;
}
if (
formServiceActivityDescription !==
addEditServiceActivityModalOptions.activity.description
) {
validateFields.push("serviceActivityDescription");
body.description = formServiceActivityDescription;
}
if (
formServiceActivityPrice !==
addEditServiceActivityModalOptions.activity.price
) {
validateFields.push("serviceActivityPrice");
body.price = formServiceActivityPrice;
}
let formDuration = formServiceActivityDurationMinutes;
if (
formDuration !==
addEditServiceActivityModalOptions.activity.duration
) {
validateFields.push("serviceActivityDurationMinutes");
body.duration = formDuration;
}
body.userIds = selectedEmployeesRowKeys;
form
.validateFields()
.then(() => {
setIsRequesting(true);
myFetch({
url: "/store/services/activity/update",
method: "POST",
body: body,
notificationApi: notificationApi,
t: t,
}) })
.catch((errStatus) => { .then(() => {
console.log(errStatus); setIsRequesting(false);
}); handleModalClose();
})
.catch((info) => {
console.log("Validate Failed:", info);
});
}}
/>
) : (
<MyModalCloseSaveButtonFooter
onCancel={handleModalClose}
isSaveButtonLoading={isRequesting}
onSave={() => {
const formServiceActivityName = form.getFieldValue(
"serviceActivityName"
);
const formServiceActivityDescription = form.getFieldValue(
"serviceActivityDescription"
);
const formServiceActivityPrice = form.getFieldValue(
"serviceActivityPrice"
);
const formServiceActivityDurationMinutes = form.getFieldValue(
"serviceActivityDurationMinutes"
);
// if the service didn't change, don't send a request fetchServices();
if ( })
addEditServiceActivityModalOptions.activity.name === .catch((errStatus) => {
formServiceActivityName && console.log(errStatus);
addEditServiceActivityModalOptions.activity.description === });
formServiceActivityDescription && })
addEditServiceActivityModalOptions.activity.price === .catch((info) => {
formServiceActivityPrice && console.log("Validate Failed:", info);
addEditServiceActivityModalOptions.activity.duration === });
formServiceActivityDurationMinutes && }}
(selectedEmployeesRowKeys.length === users.length || />
addEditServiceActivityModalOptions.activity )
.StoreServiceActivityUsers.length === }
selectedEmployeesRowKeys.length) >
) { <Form form={form} layout="vertical" requiredMark={false}>
handleModalClose(); <ServiceActivityNameFormInput formItemName="serviceActivityName" />
return;
}
let validateFields = []; <ServiceActivityDescriptionFormInput formItemName="serviceActivityDescription" />
let body = {
activityId:
addEditServiceActivityModalOptions.activity.activity_id,
};
if ( <ServiceActivityPriceFormInput formItemName="serviceActivityPrice" />
formServiceActivityName !==
addEditServiceActivityModalOptions.activity.name
) {
validateFields.push("serviceActivityName");
body.name = formServiceActivityName;
}
if ( <ServiceActivityDurationMinutesFormInput formItemName="serviceActivityDurationMinutes" />
formServiceActivityDescription !==
addEditServiceActivityModalOptions.activity.description
) {
validateFields.push("serviceActivityDescription");
body.description = formServiceActivityDescription;
}
if ( <Space direction="vertical" style={{ width: "100%" }}>
formServiceActivityPrice !== <Typography.Text>
addEditServiceActivityModalOptions.activity.price {t("storeServices.serviceActivityResponsible")}
) { </Typography.Text>
validateFields.push("serviceActivityPrice");
body.price = formServiceActivityPrice;
}
let formDuration = formServiceActivityDurationMinutes; <MyTable
props={{
if ( rowSelection: {
formDuration !== selectedRowKeys: selectedEmployeesRowKeys,
addEditServiceActivityModalOptions.activity.duration onChange: (newSelectedRowKeys) =>
) { setSelectedEmployeesRowKeys(newSelectedRowKeys),
validateFields.push("serviceActivityDurationMinutes"); },
body.duration = formDuration; loading: isRequesting,
} columns: getTableColumns(),
dataSource: getTableItems(),
body.userIds = selectedEmployeesRowKeys; size: "small",
pagination: false,
form }}
.validateFields() />
.then(() => { </Space>
setIsRequesting(true); </Form>
</MyModal>
myFetch("/store/services/activity/update", "POST", body) </>
.then(() => {
setIsRequesting(false);
handleModalClose();
fetchServices();
})
.catch((errStatus) => {
console.log(errStatus);
});
})
.catch((info) => {
console.log("Validate Failed:", info);
});
}}
/>
)
}
>
<Form form={form} layout="vertical" requiredMark={false}>
<ServiceActivityNameFormInput formItemName="serviceActivityName" />
<ServiceActivityDescriptionFormInput formItemName="serviceActivityDescription" />
<ServiceActivityPriceFormInput formItemName="serviceActivityPrice" />
<ServiceActivityDurationMinutesFormInput formItemName="serviceActivityDurationMinutes" />
<Space direction="vertical" style={{ width: "100%" }}>
<Typography.Text>
{t("storeServices.serviceActivityResponsible")}
</Typography.Text>
<MyTable
props={{
rowSelection: {
selectedRowKeys: selectedEmployeesRowKeys,
onChange: (newSelectedRowKeys) =>
setSelectedEmployeesRowKeys(newSelectedRowKeys),
},
loading: isRequesting,
columns: getTableColumns(),
dataSource: getTableItems(),
size: "small",
pagination: false,
}}
/>
</Space>
</Form>
</MyModal>
); );
} }

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { myFetch } from "../../../utils"; import { myFetch } from "../../../utils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Card, Form } from "antd"; import { Card, Form, notification } from "antd";
import { MyFormInput } from "../../../Components/MyFormInputs"; import { MyFormInput } from "../../../Components/MyFormInputs";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
@ -11,6 +11,8 @@ import {
export default function StoreSettings() { export default function StoreSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const { storeId } = useParams(); const { storeId } = useParams();
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -26,7 +28,12 @@ export default function StoreSettings() {
const address = Form.useWatch("address", form); const address = Form.useWatch("address", form);
useEffect(() => { useEffect(() => {
myFetch(`/store/${storeId}`, "GET") myFetch({
url: `/store/${storeId}`,
method: "GET",
notificationApi: notificationApi,
t: t,
})
.then((data) => { .then((data) => {
setStoreData(data.store); setStoreData(data.store);
@ -65,11 +72,17 @@ export default function StoreSettings() {
} }
delayTimeout.current = setTimeout(() => { delayTimeout.current = setTimeout(() => {
myFetch(`/store/${storeId}`, "POST", { myFetch({
name: companyName, url: `/store/${storeId}`,
phoneNumber, method: "POST",
email, body: {
address, name: companyName,
phoneNumber,
email,
address,
},
notificationApi: notificationApi,
t: t,
}) })
.then(() => setRequestState(RequestState.SUCCESS)) .then(() => setRequestState(RequestState.SUCCESS))
.catch((errStatus) => { .catch((errStatus) => {
@ -81,6 +94,8 @@ export default function StoreSettings() {
return ( return (
<> <>
{notificationContextHolder}
<Card <Card
title={t("storeSettings.pageTitle")} title={t("storeSettings.pageTitle")}
loading={isRequesting} loading={isRequesting}

View File

@ -2,7 +2,7 @@ import { lazy, useEffect, useState } from "react";
import { isDevelopmentEnv, myFetch } from "../../../utils"; import { isDevelopmentEnv, myFetch } from "../../../utils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import MyCenteredContainer from "../../../Components/MyContainer"; import MyCenteredContainer from "../../../Components/MyContainer";
import { Button, Card, Result, Spin, Tabs } from "antd"; import { Button, Card, Result, Spin, Tabs, notification } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { MySupsenseFallback } from "../../../Components/MySupsenseFallback"; import { MySupsenseFallback } from "../../../Components/MySupsenseFallback";
import PageInDevelopment from "../../PageInDevelopment"; import PageInDevelopment from "../../PageInDevelopment";
@ -20,6 +20,8 @@ function SuspenseFallback({ children }) {
export default function Website() { export default function Website() {
const { storeId } = useParams(); const { storeId } = useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [isRequesting, setIsRequesting] = useState(true); const [isRequesting, setIsRequesting] = useState(true);
const [website, setWebsite] = useState({}); const [website, setWebsite] = useState({});
@ -48,10 +50,13 @@ export default function Website() {
}); });
useEffect(() => { useEffect(() => {
myFetch(`/website/${storeId}`, "GET") myFetch({
.then((res) => { url: `/website/${storeId}`,
setIsRequesting(false); method: "GET",
}) notificationApi: notificationApi,
t: t,
})
.then(() => setIsRequesting(false))
.catch((err) => { .catch((err) => {
setIsRequesting(false); setIsRequesting(false);
@ -76,7 +81,9 @@ export default function Website() {
} }
return ( return (
<div> <>
{notificationContextHolder}
<Card> <Card>
<Tabs <Tabs
type="card" type="card"
@ -86,12 +93,15 @@ export default function Website() {
onChange={(key) => setActiveTab(key)} onChange={(key) => setActiveTab(key)}
/> />
</Card> </Card>
</div> </>
); );
} }
function NoWebsiteCreateOne({ setWebsite }) { function NoWebsiteCreateOne({ setWebsite }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const { storeId } = useParams(); const { storeId } = useParams();
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
@ -99,8 +109,14 @@ function NoWebsiteCreateOne({ setWebsite }) {
const handleCreateWebsite = () => { const handleCreateWebsite = () => {
setIsRequesting(true); setIsRequesting(true);
myFetch("/website", "POST", { myFetch({
storeId, url: "/website",
method: "POST",
body: {
storeId,
},
notificationApi: notificationApi,
t: t,
}) })
.then((res) => { .then((res) => {
console.log(res); console.log(res);
@ -116,6 +132,8 @@ function NoWebsiteCreateOne({ setWebsite }) {
return ( return (
<MyCenteredContainer> <MyCenteredContainer>
{notificationContextHolder}
<Result <Result
status="404" status="404"
title={t("storeWebsite.noWebsite.title")} title={t("storeWebsite.noWebsite.title")}

View File

@ -1,28 +1,39 @@
import { Button, Card, Select, Typography } from "antd"; import { Button, Card, Select, Typography, notification } from "antd";
import { Constants } from "../../utils"; import { myFetch } from "../../utils";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState } from "react";
export default function UserProfile({ userSession, setUserSession }) { export default function UserProfile({ setUserSession }) {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [isRequestingLogout, setIsRequestingLogout] = useState(false);
return ( return (
<> <>
{notificationContextHolder}
<Card <Card
title={t("userProfile.title")} title={t("userProfile.title")}
extra={ extra={
<Button <Button
type="primary" type="primary"
loading={isRequestingLogout}
onClick={() => { onClick={() => {
setUserSession(); setIsRequestingLogout(true);
window.location.href = "/";
fetch(`${Constants.API_ADDRESS}/user/auth/logout`, { myFetch({
url: "/user/auth/logout",
method: "DELETE", method: "DELETE",
headers: { notificationApi: notificationApi,
"Content-Type": "application/json", t: t,
"X-Authorization": userSession, })
}, .then(() => {
}).catch(console.error); setUserSession();
window.location.href = "/";
})
.catch(() => setIsRequestingLogout(false));
}} }}
> >
{t("common.button.logout")} {t("common.button.logout")}

View File

@ -6,6 +6,7 @@ body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
} }
code { code {

File diff suppressed because it is too large Load Diff