Added missing translation and refactor dialog to self component

This commit is contained in:
Alan 2022-04-11 13:26:32 -06:00
parent 2176f9e945
commit 60c7791dfd
6 changed files with 113 additions and 92 deletions

View File

@ -2,109 +2,24 @@ import { BanIcon, CheckIcon, ClockIcon, XIcon } from "@heroicons/react/outline";
import { PaperAirplaneIcon } from "@heroicons/react/outline";
import { BookingStatus } from "@prisma/client";
import dayjs from "dayjs";
import { Dispatch, SetStateAction, useState } from "react";
import { useState } from "react";
import { useMutation } from "react-query";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { TextArea } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import * as fetchWrapper from "@lib/core/http/fetch-wrapper";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { useMeQuery } from "@components/Shell";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
import TableActions, { ActionType } from "@components/ui/TableActions";
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
interface IRescheduleDialog {
isOpenDialog: boolean;
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
bookingUId: string;
}
const RescheduleDialog = (props: IRescheduleDialog) => {
const { t } = useLocale();
const utils = trpc.useContext();
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
const [rescheduleReason, setRescheduleReason] = useState("");
const [isLoading, setIsLoading] = useState(false);
const rescheduleApi = useMutation(
async () => {
setIsLoading(true);
try {
const result = await fetchWrapper.post<any, any>("/api/book/request-reschedule", {
bookingId,
rescheduleReason,
});
if (result) {
showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false);
}
} catch (error) {
showToast(t("unexpected_error_try_again"), "error");
// @TODO: notify sentry
}
setIsLoading(false);
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.bookings"]);
},
}
);
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
<DialogClose asChild>
<div className="fixed top-1 right-1 flex h-8 w-8 justify-center rounded-full hover:bg-gray-200">
<XIcon className="w-4" />
</div>
</DialogClose>
<div style={{ display: "flex", flexDirection: "row" }}>
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<ClockIcon className="m-auto h-6 w-6"></ClockIcon>
</div>
<div className="px-4 pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="-mt-8 text-sm text-gray-500">{t("reschedule_modal_description")}</p>
<p className="mt-6 mb-2 text-sm font-bold text-black">
{t("reason_for_reschedule_request")}
<span className="font-normal text-gray-500"> (Optional)</span>
</p>
<TextArea
name={t("reschedule_reason")}
value={rescheduleReason}
onChange={(e) => setRescheduleReason(e.target.value)}
className="mb-5 sm:mb-6"
/>
<DialogFooter>
<DialogClose>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<Button
disabled={isLoading}
onClick={() => {
rescheduleApi.mutate();
}}>
{t("send_reschedule_request")}
</Button>
</DialogFooter>
</div>
</div>
</DialogContent>
</Dialog>
);
};
function BookingListItem(booking: BookingItem) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();

View File

@ -265,7 +265,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
{booking?.startTime && rescheduleUid && (
<div>
{/* Add translation */}
<p className="mt-8 mb-2 text-gray-600 dark:text-white">Former time</p>
<p className="mt-8 mb-2 text-gray-600 dark:text-white">{t("former_time")}</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}

View File

@ -367,7 +367,7 @@ const BookingPage = ({
{booking?.startTime && rescheduleUid && (
<div>
{/* Add translation */}
<p className="mt-8 mb-2 text-gray-600 dark:text-white">Former time</p>
<p className="mt-8 mb-2 text-gray-600 dark:text-white">{t("former_time")}</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}

View File

@ -0,0 +1,101 @@
import { ClockIcon, XIcon } from "@heroicons/react/outline";
import { RescheduleResponse } from "pages/api/book/request-reschedule";
import React, { useState, Dispatch, SetStateAction } from "react";
import { useMutation } from "react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Attendee, Booking } from "@calcom/prisma/client";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { TextArea } from "@calcom/ui/form/fields";
import * as fetchWrapper from "@lib/core/http/fetch-wrapper";
import { trpc } from "@lib/trpc";
interface IRescheduleDialog {
isOpenDialog: boolean;
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
bookingUId: string;
}
export const RescheduleDialog = (props: IRescheduleDialog) => {
const { t } = useLocale();
const utils = trpc.useContext();
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
const [rescheduleReason, setRescheduleReason] = useState("");
const [isLoading, setIsLoading] = useState(false);
const rescheduleApi = useMutation(
async () => {
setIsLoading(true);
try {
const result = await fetchWrapper.post<
{ bookingId: string; rescheduleReason: string },
RescheduleResponse
>("/api/book/request-reschedule", {
bookingId,
rescheduleReason,
});
if (result) {
showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false);
}
} catch (error) {
showToast(t("unexpected_error_try_again"), "error");
// @TODO: notify sentry
}
setIsLoading(false);
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.bookings"]);
},
}
);
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
<DialogClose asChild>
<div className="fixed top-1 right-1 flex h-8 w-8 justify-center rounded-full hover:bg-gray-200">
<XIcon className="w-4" />
</div>
</DialogClose>
<div style={{ display: "flex", flexDirection: "row" }}>
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<ClockIcon className="m-auto h-6 w-6"></ClockIcon>
</div>
<div className="px-4 pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="-mt-8 text-sm text-gray-500">{t("reschedule_modal_description")}</p>
<p className="mt-6 mb-2 text-sm font-bold text-black">
{t("reason_for_reschedule_request")}
<span className="font-normal text-gray-500"> (Optional)</span>
</p>
<TextArea
name={t("reschedule_reason")}
value={rescheduleReason}
onChange={(e) => setRescheduleReason(e.target.value)}
className="mb-5 sm:mb-6"
/>
<DialogFooter>
<DialogClose>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<Button
disabled={isLoading}
onClick={() => {
rescheduleApi.mutate();
}}>
{t("send_reschedule_request")}
</Button>
</DialogFooter>
</div>
</div>
</DialogContent>
</Dialog>
);
};

View File

@ -1,4 +1,4 @@
import { BookingStatus, User, SchedulingType } from "@prisma/client";
import { BookingStatus, User, SchedulingType, Booking, Attendee } from "@prisma/client";
import dayjs from "dayjs";
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
@ -13,12 +13,16 @@ import { Person } from "@calcom/types/Calendar";
import { sendRequestRescheduleEmail } from "@lib/emails/email-manager";
import prisma from "@lib/prisma";
export type RescheduleResponse = Booking & {
attendees: Attendee[];
};
const rescheduleSchema = z.object({
bookingId: z.string(),
rescheduleReason: z.string().optional(),
});
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<RescheduleResponse> => {
const session = await getSession({ req });
const { bookingId, rescheduleReason: cancellationReason } = req.body;
type PersonAttendee = Pick<User, "id" | "email" | "name" | "locale" | "timeZone" | "username">;

View File

@ -734,5 +734,6 @@
"reason_for_reschedule_request": "Reason for reschedule request",
"send_reschedule_request": "Send reschedule request",
"edit_booking": "Edit booking",
"reschedule_booking": "Reschedule booking"
"reschedule_booking": "Reschedule booking",
"former_time": "Former time"
}