683 lines
18 KiB
JavaScript
683 lines
18 KiB
JavaScript
import {
|
|
Button,
|
|
Card,
|
|
Col,
|
|
Flex,
|
|
Form,
|
|
Grid,
|
|
QRCode,
|
|
Result,
|
|
Row,
|
|
Space,
|
|
Spin,
|
|
Switch,
|
|
Typography,
|
|
notification,
|
|
} from "antd";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
Constants,
|
|
EncodeStringToBase64,
|
|
getUserSessionFromLocalStorage,
|
|
myFetch,
|
|
showInputsInvalidNotification,
|
|
showPasswordIncorrectNotification,
|
|
} from "../../../utils";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import MyCenteredContainer from "../../../Components/MyContainer";
|
|
import {
|
|
MyCalendarMaxFutureBookingDaysFormInput,
|
|
MyCalendarMinEarliestBookingTimeFormInput,
|
|
MyPasswordFormInput,
|
|
} from "../../../Components/MyFormInputs";
|
|
import {
|
|
RequestState,
|
|
RequestStateItem,
|
|
} from "../../../Components/MyRequestStateItem";
|
|
import MyModal, {
|
|
MyModalCloseConfirmButtonFooter,
|
|
MyModalOnlyCloseButtonFooter,
|
|
} from "../../../Components/MyModal";
|
|
import { useParams } from "react-router-dom";
|
|
import {
|
|
DownloadOutlined,
|
|
MailOutlined,
|
|
ShareAltOutlined,
|
|
WhatsAppOutlined,
|
|
} from "@ant-design/icons";
|
|
import Paragraph from "antd/es/typography/Paragraph";
|
|
import { useSideBarContext } from "../../../Contexts/SideBarContext";
|
|
|
|
const { useBreakpoint } = Grid;
|
|
|
|
export default function StoreCalendar() {
|
|
const { t } = useTranslation();
|
|
const [notificationApi, notificationContextHolder] =
|
|
notification.useNotification();
|
|
|
|
const { storeId } = useParams();
|
|
|
|
const [calendarSettings, setCalendarSettings] = useState({});
|
|
const [isRequesting, setIsRequesting] = useState(true);
|
|
const timer = useRef();
|
|
|
|
useEffect(() => {
|
|
// delete session cookie
|
|
document.cookie = `session=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
|
|
|
|
myFetch({
|
|
url: "/calendar/settings",
|
|
method: "GET",
|
|
notificationApi: notificationApi,
|
|
t: t,
|
|
})
|
|
.then((res) => {
|
|
setIsRequesting(false);
|
|
setCalendarSettings(res);
|
|
})
|
|
.catch(() => {});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// if status is pending, check again after
|
|
|
|
if (calendarSettings.status === "PENDING") {
|
|
timer.current = setTimeout(() => {
|
|
myFetch({
|
|
url: "/calendar/settings",
|
|
method: "GET",
|
|
notificationApi: notificationApi,
|
|
t: t,
|
|
})
|
|
.then((res) => setCalendarSettings(res))
|
|
.catch(() => {});
|
|
}, 2000);
|
|
}
|
|
|
|
return () => {
|
|
clearTimeout(timer.current);
|
|
};
|
|
}, [calendarSettings]);
|
|
|
|
if (isRequesting) {
|
|
return (
|
|
<MyCenteredContainer>
|
|
<Spin size="large" />
|
|
</MyCenteredContainer>
|
|
);
|
|
}
|
|
|
|
const ConnectCalendarButton = ({ text }) => (
|
|
<Button
|
|
type="primary"
|
|
onClick={() => {
|
|
document.cookie = `session=${getUserSessionFromLocalStorage()}; path=/;`;
|
|
|
|
window.location.href = `${Constants.API_ADDRESS}/calendar/auth/google`;
|
|
}}
|
|
>
|
|
{text || t("calendar.googleCalendarNotConnected.button")}
|
|
</Button>
|
|
);
|
|
|
|
if (calendarSettings.status === "NOT_CONNECTED") {
|
|
return (
|
|
<MyCenteredContainer>
|
|
<Result
|
|
status="warning"
|
|
title={t("calendar.googleCalendarNotConnected.title")}
|
|
subTitle={t("calendar.googleCalendarNotConnected.subTitle")}
|
|
extra={<ConnectCalendarButton />}
|
|
/>
|
|
</MyCenteredContainer>
|
|
);
|
|
}
|
|
|
|
if (calendarSettings.status === "PENDING") {
|
|
return (
|
|
<MyCenteredContainer>
|
|
<Result
|
|
status="info"
|
|
title={t("calendar.googleCalendarPending.title")}
|
|
subTitle={t("calendar.googleCalendarPending.subTitle")}
|
|
extra={<Spin size="large" />}
|
|
/>
|
|
</MyCenteredContainer>
|
|
);
|
|
}
|
|
|
|
if (calendarSettings.status === "NOPERM") {
|
|
return (
|
|
<MyCenteredContainer>
|
|
<Result
|
|
status="error"
|
|
title={t("calendar.googleCalendarNoPerm.title")}
|
|
subTitle={t("calendar.googleCalendarNoPerm.subTitle")}
|
|
extra={
|
|
<ConnectCalendarButton
|
|
text={t("calendar.googleCalendarNoPerm.button")}
|
|
/>
|
|
}
|
|
/>
|
|
</MyCenteredContainer>
|
|
);
|
|
}
|
|
|
|
if (calendarSettings.status === "ERROR") {
|
|
return (
|
|
<MyCenteredContainer>
|
|
<Result
|
|
status="error"
|
|
title={t("calendar.googleCalendarError.title")}
|
|
subTitle={t("calendar.googleCalendarError.subTitle")}
|
|
extra={
|
|
<ConnectCalendarButton
|
|
text={t("calendar.googleCalendarError.button")}
|
|
/>
|
|
}
|
|
/>
|
|
</MyCenteredContainer>
|
|
);
|
|
}
|
|
|
|
// status OK
|
|
return (
|
|
<>
|
|
{notificationContextHolder}
|
|
|
|
<h1>{t("calendar.pageTitle")}</h1>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24} md={12}>
|
|
<CardPersonalCalendarSettings
|
|
settings={calendarSettings.userSettings}
|
|
/>
|
|
</Col>
|
|
<Col xs={24} md={12}>
|
|
{calendarSettings.storeSettings && (
|
|
<CardStoreCalendarSettings
|
|
settings={calendarSettings.storeSettings}
|
|
/>
|
|
)}
|
|
</Col>
|
|
|
|
<Col xs={24}>
|
|
<CalendarFrameCard storeId={storeId} />
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function CalendarFrameCard({ storeId }) {
|
|
const { t } = useTranslation();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
return (
|
|
<Card
|
|
title={t("calendar.calendarFrameCustomerView")}
|
|
extra={<ModalShareCalendarLink storeId={storeId} />}
|
|
>
|
|
{isLoading && (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
height: "50vh",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Spin size="large" />
|
|
</div>
|
|
)}
|
|
<iframe
|
|
title="calendar"
|
|
onLoad={() => setIsLoading(false)}
|
|
style={{ border: 0, borderRadius: 12 }}
|
|
width="100%"
|
|
height={530}
|
|
src={`${Constants.EMBED_CALENDAR_ADDRESS}${storeId}`}
|
|
/>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function ModalShareCalendarLink({ storeId }) {
|
|
const { t } = useTranslation();
|
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const sideBarContext = useSideBarContext();
|
|
|
|
const handleModalClose = () => setIsModalOpen(false);
|
|
|
|
const embedPreContent = `<button onclick="KKInnovation.openPopup({url: '${Constants.EMBED_CALENDAR_ADDRESS}${storeId}'});">
|
|
Termin Buchen
|
|
</button>
|
|
|
|
<script src="${Constants.EMBED_CALENDAR_SCRIPT_ADDRESS}" type="text/javascript" async></script>`;
|
|
|
|
const downloadQRCode = () => {
|
|
const canvas = document
|
|
.getElementById("calendar-qrcode")
|
|
?.querySelector("canvas");
|
|
if (canvas) {
|
|
const url = canvas.toDataURL();
|
|
const a = document.createElement("a");
|
|
a.download = "QRCode.png";
|
|
a.href = url;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<ShareAltOutlined onClick={() => setIsModalOpen(true)} />
|
|
|
|
<MyModal
|
|
title={t("calendar.modalShareCalendarLink.title")}
|
|
isOpen={isModalOpen}
|
|
onCancel={handleModalClose}
|
|
footer={<MyModalOnlyCloseButtonFooter onCancel={handleModalClose} />}
|
|
>
|
|
<Space style={{ paddingBottom: 12 }}>
|
|
<MailOutlined
|
|
style={{ fontSize: 20 }}
|
|
onClick={() =>
|
|
window.open(
|
|
`mailto:?subject=${t(
|
|
"calendar.modalShareCalendarLink.shareButtons.emailSubject"
|
|
)}&body=${encodeURIComponent(
|
|
t("calendar.modalShareCalendarLink.shareButtons.emailBody", {
|
|
link: `<a href="${Constants.EMBED_CALENDAR_ADDRESS}${storeId}">${Constants.EMBED_CALENDAR_ADDRESS}${storeId}</a>`,
|
|
username: sideBarContext.username,
|
|
})
|
|
)}`,
|
|
"_self"
|
|
)
|
|
}
|
|
/>
|
|
<WhatsAppOutlined
|
|
style={{ fontSize: 20 }}
|
|
onClick={() =>
|
|
window.open(
|
|
`https://web.whatsapp.com/send?text=${encodeURIComponent(
|
|
t(
|
|
"calendar.modalShareCalendarLink.shareButtons.whatsAppBody",
|
|
{
|
|
link: `${Constants.EMBED_CALENDAR_ADDRESS}${storeId}`,
|
|
username: sideBarContext.username,
|
|
}
|
|
)
|
|
)}`
|
|
)
|
|
}
|
|
/>
|
|
</Space>
|
|
|
|
<div
|
|
id="calendar-qrcode"
|
|
style={{ paddingBottom: 12 }}
|
|
onClick={downloadQRCode}
|
|
>
|
|
<QRCode
|
|
value={`${Constants.EMBED_CALENDAR_ADDRESS}${storeId}`}
|
|
bgColor="#fff"
|
|
/>
|
|
</div>
|
|
|
|
<Flex vertical>
|
|
<Typography.Text strong>
|
|
{t("calendar.modalShareCalendarLink.info")}
|
|
</Typography.Text>
|
|
|
|
<Paragraph
|
|
style={{
|
|
display: "flex",
|
|
alignContent: "center",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}}
|
|
copyable={{
|
|
text: `${Constants.EMBED_CALENDAR_ADDRESS}${storeId}`,
|
|
}}
|
|
>
|
|
<pre style={{ flex: 1 }}>
|
|
{`${Constants.EMBED_CALENDAR_ADDRESS}${storeId}`}
|
|
</pre>
|
|
</Paragraph>
|
|
|
|
<Typography.Text strong>
|
|
{t("calendar.modalShareCalendarLink.embed")}
|
|
</Typography.Text>
|
|
|
|
<Typography.Text>
|
|
{t("calendar.modalShareCalendarLink.embedInfo")}
|
|
</Typography.Text>
|
|
|
|
<Paragraph
|
|
copyable={{
|
|
text: embedPreContent,
|
|
}}
|
|
style={{
|
|
display: "flex",
|
|
alignContent: "center",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}}
|
|
>
|
|
<pre style={{ flex: 1 }}>{embedPreContent}</pre>
|
|
</Paragraph>
|
|
</Flex>
|
|
</MyModal>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function CardPersonalCalendarSettings({ settings }) {
|
|
const { t } = useTranslation();
|
|
const [notificationApi, notificationContextHolder] =
|
|
notification.useNotification();
|
|
|
|
const [form] = Form.useForm();
|
|
const [formUnlinkCalendar] = Form.useForm();
|
|
const screenBreakpoint = useBreakpoint();
|
|
|
|
const [requestState, setRequestState] = useState(RequestState.INIT);
|
|
const delayTimeout = useRef();
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [isRequesting, setIsRequesting] = useState(false);
|
|
|
|
const usingPrimaryCalendar = Form.useWatch(
|
|
"calendarUsingPrimaryCalendar",
|
|
form
|
|
);
|
|
const maxFutureBookingDays = Form.useWatch(
|
|
"personalCalendarMaxFutureBookingDays",
|
|
form
|
|
);
|
|
const minEarliestBookingTime = Form.useWatch(
|
|
"personalCalendarMinEarliestBookingTime",
|
|
form
|
|
);
|
|
|
|
const handleModalClose = () => setIsModalOpen(false);
|
|
useEffect(() => {
|
|
if (!settings) return;
|
|
|
|
form.setFieldsValue({
|
|
calendarUsingPrimaryCalendar: settings.calendar_using_primary_calendar,
|
|
personalCalendarMaxFutureBookingDays:
|
|
settings.calendar_max_future_booking_days,
|
|
personalCalendarMinEarliestBookingTime:
|
|
settings.calendar_min_earliest_booking_time,
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
usingPrimaryCalendar === undefined ||
|
|
maxFutureBookingDays === undefined ||
|
|
minEarliestBookingTime === undefined
|
|
)
|
|
return;
|
|
|
|
if (RequestState.INIT === requestState) {
|
|
setRequestState(RequestState.NOTHING);
|
|
return;
|
|
}
|
|
|
|
setRequestState(RequestState.REQUESTING);
|
|
|
|
if (delayTimeout.current) {
|
|
clearTimeout(delayTimeout.current);
|
|
}
|
|
|
|
delayTimeout.current = setTimeout(() => {
|
|
myFetch({
|
|
url: "/calendar/settings/personal",
|
|
method: "POST",
|
|
body: {
|
|
calendarUsingPrimaryCalendar: usingPrimaryCalendar,
|
|
calendarMaxFutureBookingDays: maxFutureBookingDays,
|
|
calendarMinEarliestBookingTime: minEarliestBookingTime,
|
|
},
|
|
notificationApi: notificationApi,
|
|
t: t,
|
|
})
|
|
.then(() => setRequestState(RequestState.SUCCESS))
|
|
.catch(() => setRequestState(RequestState.FAILED));
|
|
}, 500);
|
|
}, [usingPrimaryCalendar, maxFutureBookingDays, minEarliestBookingTime]);
|
|
|
|
useEffect(() => {
|
|
if (!isModalOpen) return;
|
|
|
|
formUnlinkCalendar.resetFields();
|
|
}, [isModalOpen]);
|
|
|
|
const CardTitleContent = () => {
|
|
return (
|
|
<>
|
|
<Typography.Title
|
|
level={5}
|
|
style={{
|
|
padding: 0,
|
|
margin: 0,
|
|
}}
|
|
>
|
|
{t("calendar.cardPersonalCalendarSettings.title")}
|
|
</Typography.Title>
|
|
|
|
<Space>
|
|
<RequestStateItem
|
|
state={requestState}
|
|
setRequestState={setRequestState}
|
|
/>
|
|
|
|
<Button
|
|
danger
|
|
onClick={() => setIsModalOpen(true)}
|
|
style={{ paddingBottom: 10 }}
|
|
>
|
|
{t("calendar.unlinkGoogleCalendar.button")}
|
|
</Button>
|
|
</Space>
|
|
</>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{notificationContextHolder}
|
|
|
|
<Card
|
|
title={
|
|
screenBreakpoint.xl ? (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
justifyItems: "center",
|
|
alignContent: "center",
|
|
}}
|
|
>
|
|
<CardTitleContent />
|
|
</div>
|
|
) : (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
paddingTop: 10,
|
|
paddingBottom: 10,
|
|
}}
|
|
>
|
|
<CardTitleContent />
|
|
</div>
|
|
)
|
|
}
|
|
>
|
|
<Form form={form} requiredMark={false}>
|
|
<Form.Item
|
|
name="calendarUsingPrimaryCalendar"
|
|
label={t("common.calendarUsingPrimaryCalendar")}
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
|
|
<MyCalendarMaxFutureBookingDaysFormInput formItemName="personalCalendarMaxFutureBookingDays" />
|
|
|
|
<MyCalendarMinEarliestBookingTimeFormInput formItem="personalCalendarMinEarliestBookingTime" />
|
|
</Form>
|
|
</Card>
|
|
|
|
<MyModal
|
|
title={t("calendar.unlinkGoogleCalendar.title")}
|
|
isOpen={isModalOpen}
|
|
onCancel={handleModalClose}
|
|
footer={
|
|
<MyModalCloseConfirmButtonFooter
|
|
onCancel={handleModalClose}
|
|
isConfirmButtonLoading={isRequesting}
|
|
onConfirm={() => {
|
|
formUnlinkCalendar
|
|
.validateFields()
|
|
.then((values) => {
|
|
setIsRequesting(true);
|
|
|
|
myFetch({
|
|
url: "/calendar/settings/personal/unlink",
|
|
method: "POST",
|
|
body: {
|
|
password: EncodeStringToBase64(values.password),
|
|
},
|
|
notificationApi: notificationApi,
|
|
t: t,
|
|
})
|
|
.then(() => window.location.reload())
|
|
.catch((errStatus) => {
|
|
setIsRequesting(false);
|
|
|
|
if (errStatus === 400) {
|
|
showPasswordIncorrectNotification(notificationApi, t);
|
|
}
|
|
});
|
|
})
|
|
.catch(() => showInputsInvalidNotification(notificationApi, t));
|
|
}}
|
|
/>
|
|
}
|
|
>
|
|
<>
|
|
<Form
|
|
form={formUnlinkCalendar}
|
|
requiredMark={false}
|
|
layout="vertical"
|
|
>
|
|
<p>{t("calendar.unlinkGoogleCalendar.description")}</p>
|
|
<MyPasswordFormInput />
|
|
</Form>
|
|
</>
|
|
</MyModal>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function CardStoreCalendarSettings({ settings }) {
|
|
const { t } = useTranslation();
|
|
const [notificationApi, notificationContextHolder] =
|
|
notification.useNotification();
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
const [requestState, setRequestState] = useState(RequestState.INIT);
|
|
const delayTimeout = useRef();
|
|
|
|
const storeCalendarMaxFutureBookingDays = Form.useWatch(
|
|
"storeCalendarMaxFutureBookingDays",
|
|
form
|
|
);
|
|
|
|
const storeCalendarMinEarliestBookingTime = Form.useWatch(
|
|
"storeCalendarMinEarliestBookingTime",
|
|
form
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!settings) return;
|
|
|
|
form.setFieldsValue({
|
|
storeCalendarMaxFutureBookingDays:
|
|
settings.calendar_max_future_booking_days,
|
|
storeCalendarMinEarliestBookingTime:
|
|
settings.calendar_min_earliest_booking_time,
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// undefined on first render
|
|
// null when input is empty
|
|
if (
|
|
storeCalendarMaxFutureBookingDays === undefined ||
|
|
storeCalendarMinEarliestBookingTime === undefined ||
|
|
storeCalendarMaxFutureBookingDays === null ||
|
|
storeCalendarMinEarliestBookingTime === null
|
|
)
|
|
return;
|
|
|
|
if (RequestState.INIT === requestState) {
|
|
setRequestState(RequestState.NOTHING);
|
|
return;
|
|
}
|
|
|
|
setRequestState(RequestState.REQUESTING);
|
|
|
|
if (delayTimeout.current) {
|
|
clearTimeout(delayTimeout.current);
|
|
}
|
|
|
|
delayTimeout.current = setTimeout(() => {
|
|
myFetch({
|
|
url: "/calendar/settings/store",
|
|
method: "POST",
|
|
body: {
|
|
calendarMaxFutureBookingDays: storeCalendarMaxFutureBookingDays,
|
|
calendarMinEarliestBookingTime: storeCalendarMinEarliestBookingTime,
|
|
},
|
|
notificationApi: notificationApi,
|
|
t: t,
|
|
})
|
|
.then(() => setRequestState(RequestState.SUCCESS))
|
|
.catch(() => setRequestState(RequestState.FAILED));
|
|
}, 500);
|
|
}, [storeCalendarMaxFutureBookingDays, storeCalendarMinEarliestBookingTime]);
|
|
|
|
return (
|
|
<>
|
|
{notificationContextHolder}
|
|
|
|
<Card
|
|
title={t("calendar.cardStoreCalendarSettings.title")}
|
|
extra={
|
|
<RequestStateItem
|
|
state={requestState}
|
|
setRequestState={setRequestState}
|
|
/>
|
|
}
|
|
>
|
|
<Form form={form} requiredMark={false}>
|
|
<>
|
|
<MyCalendarMaxFutureBookingDaysFormInput formItemName="storeCalendarMaxFutureBookingDays" />
|
|
|
|
<MyCalendarMinEarliestBookingTimeFormInput formItem="storeCalendarMinEarliestBookingTime" />
|
|
</>
|
|
</Form>
|
|
</Card>
|
|
</>
|
|
);
|
|
}
|