customer-dashboard/src/Pages/Store/Calendar/index.js

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>
</>
);
}