fix: RTL issues on booking pages + email confirmation (#10526)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>pull/10538/head^2
parent
471b2687c4
commit
99e9425257
|
@ -314,7 +314,7 @@ export default function Success(props: SuccessProps) {
|
|||
<Link
|
||||
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
||||
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
||||
<ChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
|
||||
<ChevronLeft className="h-5 w-5 rtl:rotate-180" /> {t("back_to_bookings")}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
@ -394,7 +394,7 @@ export default function Success(props: SuccessProps) {
|
|||
</h4>
|
||||
)}
|
||||
|
||||
<div className="border-subtle text-default mt-8 grid grid-cols-3 border-t pt-8 text-left">
|
||||
<div className="border-subtle text-default mt-8 grid grid-cols-3 border-t pt-8 text-left rtl:text-right">
|
||||
{(isCancelled || reschedule) && cancellationReason && (
|
||||
<>
|
||||
<div className="font-medium">
|
||||
|
@ -715,7 +715,7 @@ export default function Success(props: SuccessProps) {
|
|||
</div>
|
||||
{isGmail && (
|
||||
<Alert
|
||||
className="main -mb-20 mt-4 inline-block text-left sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
|
||||
className="main -mb-20 mt-4 inline-block ltr:text-right rtl:text-right sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
|
||||
severity="warning"
|
||||
message={
|
||||
<div>
|
||||
|
|
|
@ -497,6 +497,70 @@
|
|||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
<td style="direction: rtl; font-size: 0px; padding: 0px; text-align: center">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div
|
||||
class="mj-column-per-100 mj-outlook-group-fix"
|
||||
style="
|
||||
font-size: 0px;
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
">
|
||||
<table
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="vertical-align: top"
|
||||
width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
padding-top: 32px;
|
||||
word-break: break-word;
|
||||
">
|
||||
<table
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="border-collapse: collapse; border-spacing: 0px">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 89px">
|
||||
<a href="{{base_url}}" target="_blank">
|
||||
<img
|
||||
height="19"
|
||||
src="https://app.cal.com/emails/logo.png"
|
||||
style="
|
||||
border: 0;
|
||||
display: block;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
"
|
||||
width="89" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -207,7 +207,7 @@ const BookerComponent = ({
|
|||
<BookerSection
|
||||
area="header"
|
||||
className={classNames(
|
||||
layout === BookerLayouts.MONTH_VIEW && "fixed right-4 top-4 z-10",
|
||||
layout === BookerLayouts.MONTH_VIEW && "fixed top-4 z-10 ltr:right-4 rtl:left-4",
|
||||
(layout === BookerLayouts.COLUMN_VIEW || layout === BookerLayouts.WEEK_VIEW) &&
|
||||
"bg-default dark:bg-muted sticky top-0 z-10"
|
||||
)}>
|
||||
|
@ -280,7 +280,7 @@ const BookerComponent = ({
|
|||
layout === BookerLayouts.COLUMN_VIEW
|
||||
}
|
||||
className={classNames(
|
||||
"border-subtle flex h-full w-full flex-col px-5 py-3 pb-0 md:border-l",
|
||||
"border-subtle rtl:border-default flex h-full w-full flex-col px-5 py-3 pb-0 rtl:border-r ltr:md:border-l",
|
||||
layout === BookerLayouts.MONTH_VIEW &&
|
||||
"scroll-bar h-full overflow-auto md:w-[var(--booker-timeslots-width)]",
|
||||
layout !== BookerLayouts.MONTH_VIEW && "sticky top-0"
|
||||
|
|
|
@ -71,7 +71,7 @@ export const EventMeta = () => {
|
|||
<div dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(event.description) }} />
|
||||
</EventMetaBlock>
|
||||
)}
|
||||
<div className="space-y-4 font-medium">
|
||||
<div className="space-y-4 font-medium rtl:-mr-2">
|
||||
{rescheduleUid && bookingData && (
|
||||
<EventMetaBlock icon={Calendar}>
|
||||
{t("former_time")}
|
||||
|
|
|
@ -21,7 +21,7 @@ export function Header({
|
|||
isMobile: boolean;
|
||||
enabledLayouts: BookerLayouts[];
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
|
||||
const selectedDateString = useBookerStore((state) => state.selectedDate);
|
||||
const setSelectedDate = useBookerStore((state) => state.setSelectedDate);
|
||||
|
@ -66,13 +66,13 @@ export function Header({
|
|||
const isSameYear = () => {
|
||||
return selectedDate.format("YYYY") === endDate.format("YYYY");
|
||||
};
|
||||
|
||||
const formattedMonth = new Intl.DateTimeFormat(i18n.language, { month: "short" });
|
||||
const FormattedSelectedDateRange = () => {
|
||||
return (
|
||||
<h3 className="min-w-[150px] text-base font-semibold leading-4">
|
||||
{selectedDate.format("MMM D")}
|
||||
{formattedMonth.format(selectedDate.toDate())} {selectedDate.format("D")}
|
||||
{!isSameYear() && <span className="text-subtle">, {selectedDate.format("YYYY")} </span>}-{" "}
|
||||
{isSameMonth() ? endDate.format("D") : endDate.format("MMM D")},{" "}
|
||||
{!isSameMonth() && formattedMonth.format(endDate.toDate())} {endDate.format("D")},{" "}
|
||||
<span className="text-subtle">
|
||||
{isSameYear() ? selectedDate.format("YYYY") : endDate.format("YYYY")}
|
||||
</span>
|
||||
|
@ -81,11 +81,12 @@ export function Header({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="border-default relative z-10 flex border-b border-l px-5 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="border-default relative z-10 flex border-b px-5 py-4 ltr:border-l rtl:border-r">
|
||||
<div className="flex items-center gap-5 rtl:flex-grow">
|
||||
<FormattedSelectedDateRange />
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
className="group rtl:ml-1 rtl:rotate-180"
|
||||
variant="icon"
|
||||
color="minimal"
|
||||
StartIcon={ChevronLeft}
|
||||
|
@ -93,6 +94,7 @@ export function Header({
|
|||
onClick={() => addToSelectedDate(-extraDays - 1)}
|
||||
/>
|
||||
<Button
|
||||
className="group rtl:mr-1 rtl:rotate-180"
|
||||
variant="icon"
|
||||
color="minimal"
|
||||
StartIcon={ChevronRight}
|
||||
|
@ -101,7 +103,7 @@ export function Header({
|
|||
/>
|
||||
{selectedDateMin3DaysDifference && (
|
||||
<Button
|
||||
className="capitalize"
|
||||
className="capitalize ltr:ml-2 rtl:mr-2"
|
||||
color="secondary"
|
||||
onClick={() => setSelectedDate(today.format("YYYY-MM-DD"))}>
|
||||
{t("today")}
|
||||
|
@ -111,15 +113,15 @@ export function Header({
|
|||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<TimeFormatToggle />
|
||||
<div className="fixed right-4 top-4">
|
||||
<div className="fixed top-4 ltr:right-4 rtl:left-4">
|
||||
<LayoutToggleWithData />
|
||||
</div>
|
||||
{/*
|
||||
This second layout toggle is hidden, but needed to reserve the correct spot in the DIV
|
||||
for the fixed toggle above to fit into. If we wouldn't make it fixed in this view, the transition
|
||||
would be really weird, because the element is positioned fixed in the month view, and then
|
||||
when switching layouts wouldn't anymmore, causing it to animate from the center to the top right,
|
||||
while it actuall already was on place. That's why we have this element twice.
|
||||
when switching layouts wouldn't anymore, causing it to animate from the center to the top right,
|
||||
while it actually already was on place. That's why we have this element twice.
|
||||
*/}
|
||||
<div className="pointer-events-none opacity-0" aria-hidden>
|
||||
<LayoutToggleWithData />
|
||||
|
|
|
@ -69,7 +69,7 @@ export const AvailableTimes = ({
|
|||
</span>
|
||||
|
||||
{showTimeFormatToggle && (
|
||||
<div className="ml-auto">
|
||||
<div className="ml-auto rtl:mr-auto">
|
||||
<TimeFormatToggle />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -112,7 +112,7 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje
|
|||
</Tooltip>
|
||||
</div>
|
||||
) : filteredLocations.length === 1 ? (
|
||||
<div className="text-default mr-6 flex w-full flex-col space-y-4 break-words text-sm">
|
||||
<div className="text-default mr-6 flex w-full flex-col space-y-4 break-words text-sm rtl:mr-2">
|
||||
{filteredLocations}
|
||||
</div>
|
||||
) : null;
|
||||
|
|
|
@ -86,7 +86,7 @@ export const EventMetaBlock = ({
|
|||
)}
|
||||
/>
|
||||
) : (
|
||||
<>{!!Icon && <Icon className="relative z-20 mr-2 mt-[2px] h-4 w-4 flex-shrink-0" />}</>
|
||||
<>{!!Icon && <Icon className="relative z-20 mr-2 mt-[2px] h-4 w-4 flex-shrink-0 rtl:ml-2" />}</>
|
||||
)}
|
||||
<div className={classNames("relative z-10 max-w-full break-words", contentClassName)}>{children}</div>
|
||||
</div>
|
||||
|
@ -128,9 +128,9 @@ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: Even
|
|||
case EventDetailBlocks.LOCATION:
|
||||
if (!event?.locations?.length) return null;
|
||||
return (
|
||||
<React.Fragment key={block}>
|
||||
<EventMetaBlock key={block}>
|
||||
<AvailableEventLocations locations={event.locations} />
|
||||
</React.Fragment>
|
||||
</EventMetaBlock>
|
||||
);
|
||||
|
||||
case EventDetailBlocks.REQUIRES_CONFIRMATION:
|
||||
|
|
|
@ -241,7 +241,7 @@ const DatePicker = ({
|
|||
<div className="flex">
|
||||
<Button
|
||||
className={classNames(
|
||||
"group p-1 opacity-70 hover:opacity-100",
|
||||
"group p-1 opacity-70 hover:opacity-100 rtl:rotate-180",
|
||||
!browsingDate.isAfter(dayjs()) &&
|
||||
"disabled:text-bookinglighter hover:bg-background hover:opacity-70"
|
||||
)}
|
||||
|
@ -253,7 +253,7 @@ const DatePicker = ({
|
|||
StartIcon={ChevronLeft}
|
||||
/>
|
||||
<Button
|
||||
className="group p-1 opacity-70 hover:opacity-100"
|
||||
className="group p-1 opacity-70 hover:opacity-100 rtl:rotate-180"
|
||||
onClick={() => changeMonth(+1)}
|
||||
data-testid="incrementMonth"
|
||||
color="minimal"
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
type Props = {
|
||||
days: dayjs.Dayjs[];
|
||||
|
@ -9,10 +10,14 @@ type Props = {
|
|||
};
|
||||
|
||||
export function DateValues({ days, containerNavRef }: Props) {
|
||||
const { i18n } = useLocale();
|
||||
const formatDate = (date: dayjs.Dayjs): string => {
|
||||
return new Intl.DateTimeFormat(i18n.language, { weekday: "short" }).format(date.toDate());
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={containerNavRef}
|
||||
className="bg-default dark:bg-muted border-b-subtle sticky top-[var(--calendar-dates-sticky-offset,0px)] z-[80] flex-none border-b sm:pr-8">
|
||||
className="bg-default dark:bg-muted border-b-subtle rtl:border-r-default sticky top-[var(--calendar-dates-sticky-offset,0px)] z-[80] flex-none border-b border-r sm:pr-8">
|
||||
<div className="text-subtle flex text-sm leading-6 sm:hidden" data-dayslength={days.length}>
|
||||
{days.map((day) => {
|
||||
const isToday = dayjs().isSame(day, "day");
|
||||
|
@ -34,7 +39,7 @@ export function DateValues({ days, containerNavRef }: Props) {
|
|||
})}
|
||||
</div>
|
||||
<div className="text-subtle -mr-px hidden auto-cols-fr text-sm leading-6 sm:flex ">
|
||||
<div className="border-default col-end-1 w-14 border-l" />
|
||||
<div className="border-default col-end-1 w-14 ltr:border-l" />
|
||||
{days.map((day) => {
|
||||
const isToday = dayjs().isSame(day, "day");
|
||||
return (
|
||||
|
@ -45,7 +50,7 @@ export function DateValues({ days, containerNavRef }: Props) {
|
|||
isToday && "font-bold"
|
||||
)}>
|
||||
<span>
|
||||
{day.format("ddd")}{" "}
|
||||
{formatDate(day)}{" "}
|
||||
<span
|
||||
className={classNames(
|
||||
"items-center justify-center p-1",
|
||||
|
|
|
@ -26,14 +26,14 @@ export const HorizontalLines = ({
|
|||
<div className="row-end-1 h-[--calendar-offset-top] " ref={containerOffsetRef} />
|
||||
{hours.map((hour) => (
|
||||
<div key={`${id}-${hour.get("hour")}`}>
|
||||
<div className="text-muted sticky left-0 z-20 -ml-14 -mt-2.5 w-14 pr-2 text-right text-xs leading-5">
|
||||
<div className="text-muted sticky left-0 z-20 -ml-14 -mt-2.5 w-14 pr-2 text-right text-xs leading-5 rtl:-mr-14">
|
||||
{/* We need to force the minute to zero, because otherwise in ex GMT+5.5, it would show :30 minute times (but at the positino of :00) */}
|
||||
{hour.minute(0).format(timeFormat)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div key={`${id}-${finalHour}`}>
|
||||
<div className="text-muted sticky left-0 z-20 -ml-14 -mt-2.5 w-14 pr-2 text-right text-xs leading-5">
|
||||
<div className="text-muted sticky left-0 z-20 -ml-14 -mt-2.5 w-14 pr-2 text-right text-xs leading-5 rtl:-mr-14">
|
||||
{finalHour}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
import type dayjs from "@calcom/dayjs";
|
||||
|
||||
export const VeritcalLines = ({ days }: { days: dayjs.Dayjs[] }) => {
|
||||
const isRTL = () => {
|
||||
const userLocale = navigator.language;
|
||||
const userLanguage = new Intl.Locale(userLocale).language;
|
||||
return ["ar", "he", "fa", "ur"].includes(userLanguage);
|
||||
};
|
||||
|
||||
const direction = isRTL() ? "rtl" : "ltr";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="divide-default pointer-events-none relative z-[60] col-start-1 col-end-2 row-start-1 grid
|
||||
auto-cols-auto grid-rows-1 divide-x sm:pr-8">
|
||||
auto-cols-auto grid-rows-1 divide-x sm:pr-8"
|
||||
dir={direction}
|
||||
style={{
|
||||
direction: direction,
|
||||
}}>
|
||||
{days.map((_, i) => (
|
||||
<div
|
||||
key={`Key_vertical_${i}`}
|
||||
className="row-span-full"
|
||||
style={{
|
||||
gridColumnStart: i + 1,
|
||||
gridColumnStart: isRTL() ? days.length - i : i + 1,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -83,7 +83,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
|
|||
</div>
|
||||
)}
|
||||
<div className="flex flex-grow flex-col sm:flex-row">
|
||||
<div className="ml-3 ">
|
||||
<div className="ltr:ml-3 rtl:mr-3">
|
||||
<h3 className="text-sm font-medium">{props.title}</h3>
|
||||
<div className="text-sm">{props.message}</div>
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@ export const ToggleGroup = ({ options, onValueChange, isFullWidth, ...props }: T
|
|||
{...props}
|
||||
onValueChange={onValueChange}
|
||||
className={classNames(
|
||||
"min-h-9 border-default bg-default relative inline-flex gap-0.5 rounded-md border p-1",
|
||||
"min-h-9 border-default bg-default relative inline-flex gap-0.5 rounded-md border p-1 rtl:flex-row-reverse",
|
||||
props.className,
|
||||
isFullWidth && "w-full"
|
||||
)}>
|
||||
|
|
Loading…
Reference in New Issue