Improves UI stability of availability page. No CLS now. (#3347)

pull/3362/head
Hariom Balhara 2022-07-14 15:58:46 +05:30 committed by GitHub
parent b1e4c23adc
commit ebd4750f2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 105 additions and 96 deletions

View File

@ -1,4 +1,4 @@
import { useEffect } from "react";
import Head from "next/head";
import { useBrandColors } from "@calcom/embed-core/embed-iframe";
@ -238,54 +238,32 @@ const BrandColor = ({
? darkVal
: "#" + darkVal
: fallBackHex(darkVal, true);
useEffect(() => {
document.documentElement.style.setProperty(
"--booking-highlight-color",
embedBrandingColors.highlightColor || "#10B981" // green--500
);
document.documentElement.style.setProperty(
"--booking-lightest-color",
embedBrandingColors.lightestColor || "#E1E1E1" // gray--200
);
document.documentElement.style.setProperty(
"--booking-lighter-color",
embedBrandingColors.lighterColor || "#ACACAC" // gray--400
);
document.documentElement.style.setProperty(
"--booking-light-color",
embedBrandingColors.lightColor || "#888888" // gray--500
);
document.documentElement.style.setProperty(
"--booking-median-color",
embedBrandingColors.medianColor || "#494949" // gray--600
);
document.documentElement.style.setProperty(
"--booking-dark-color",
embedBrandingColors.darkColor || "#313131" // gray--800
);
document.documentElement.style.setProperty(
"--booking-darker-color",
embedBrandingColors.darkerColor || "#292929" // gray--900
);
document.documentElement.style.setProperty("--brand-color", lightVal);
document.documentElement.style.setProperty("--brand-text-color", getContrastingTextColor(lightVal, true));
document.documentElement.style.setProperty("--brand-color-dark-mode", darkVal);
document.documentElement.style.setProperty(
"--brand-text-color-dark-mode",
getContrastingTextColor(darkVal, true)
);
}, [
embedBrandingColors.highlightColor,
embedBrandingColors.lightestColor,
embedBrandingColors.lighterColor,
embedBrandingColors.lightColor,
embedBrandingColors.medianColor,
embedBrandingColors.darkColor,
embedBrandingColors.darkerColor,
lightVal,
darkVal,
]);
return null;
return (
<Head>
<style>
{`body {
/* green--500*/
--booking-highlight-color: ${embedBrandingColors.highlightColor || "#10B981"};
/* gray--200 */
--booking-lightest-color: ${embedBrandingColors.lightestColor || "#E1E1E1"};
/* gray--400 */
--booking-lighter-color: ${embedBrandingColors.lighterColor || "#ACACAC"};
/* gray--500 */
--booking-light-color: ${embedBrandingColors.lightColor || "#888888"};
/* gray--600 */
--booking-median-color: ${embedBrandingColors.medianColor || "#494949"};
/* gray--800 */
--booking-dark-color: ${embedBrandingColors.darkColor || "#313131"};
/* gray--900 */
--booking-darker-color: ${embedBrandingColors.darkerColor || "#292929"};
--brand-color: ${lightVal};
--brand-text-color: ${getContrastingTextColor(lightVal, true)};
--brand-color-dark-mode: ${darkVal};
--brand-text-color-dark-mode: ${getContrastingTextColor(darkVal, true)};
`}
</style>
</Head>
);
};
export default BrandColor;

View File

@ -113,7 +113,7 @@ const useSlots = ({
eventTypeId: number;
startTime?: Dayjs;
endTime?: Dayjs;
timeZone: string;
timeZone?: string;
}) => {
const { data, isLoading, isIdle } = trpc.useQuery(
[
@ -150,7 +150,7 @@ const SlotPicker = ({
}: {
eventType: Pick<EventType, "id" | "schedulingType" | "slug">;
timeFormat: string;
timeZone: string;
timeZone?: string;
seatsPerTimeSlot?: number;
recurringEventCount?: number;
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
@ -198,12 +198,10 @@ const SlotPicker = ({
<>
<DatePicker
isLoading={isLoading}
className={
"mt-8 w-full sm:mt-0 sm:min-w-[455px] " +
(selectedDate
? "sm:w-1/2 sm:border-r sm:pl-4 sm:pr-6 sm:dark:border-gray-700 md:w-1/3 "
: "sm:pl-4")
}
className={classNames(
"mt-8 w-full sm:mt-0 sm:min-w-[455px]",
selectedDate ? "sm:w-1/2 sm:border-r sm:pl-4 sm:pr-6 sm:dark:border-gray-700 md:w-1/3 " : "sm:pl-4"
)}
includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)}
locale={isLocaleReady ? i18n.language : "en"}
selected={selectedDate}
@ -668,28 +666,20 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</div>
)*/}
</div>
{timeZone && (
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? ([
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
].indexOf(profile.weekStart) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
/>
)}
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? (["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
profile.weekStart
) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
/>
</div>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}

View File

@ -492,10 +492,13 @@ const BookingPage = ({
<AvatarGroup
border="border-2 border-white dark:border-gray-800"
size={14}
items={[{ image: profile.image || "", alt: profile.name || "" }].concat(
items={[
{ image: profile.image || "", alt: profile.name || "", title: profile.name || "" },
].concat(
eventType.users
.filter((user) => user.name !== profile.name)
.map((user) => ({
title: user.name || "",
image: user.avatar || "",
alt: user.name || "",
}))

View File

@ -15,6 +15,9 @@ export type AvatarProps = {
gravatarFallbackMd5?: string;
};
/**
* @deprecated Use AvatarSSR instead. Once, there is no usage of Avatar, AvatarSSR can be renamed.
*/
export default function Avatar(props: AvatarProps) {
const { imageSrc, gravatarFallbackMd5, size, alt, title } = props;
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);

View File

@ -3,6 +3,7 @@ import React from "react";
import classNames from "@lib/classNames";
import Avatar from "@components/ui/Avatar";
import { AvatarSSR } from "@components/ui/AvatarSSR";
export type AvatarGroupProps = {
border?: string; // this needs to be the color of the parent container background, i.e.: border-white dark:border-gray-900
@ -23,7 +24,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
if (item.image != null) {
return (
<li key={idx} className="-mr-2 inline-block">
<Avatar
<AvatarSSR
className={props.border}
imageSrc={item.image}
title={item.title}

View File

@ -1,9 +1,17 @@
import { User } from "@prisma/client";
import * as Tooltip from "@radix-ui/react-tooltip";
import classNames from "@lib/classNames";
export type AvatarProps = {
user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string };
export type AvatarProps = (
| {
user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string };
}
| {
user?: null;
imageSrc: string;
}
) & {
className?: string;
size?: number;
title?: string;
@ -16,18 +24,38 @@ function defaultAvatarSrc(md5: string) {
}
// An SSR Supported version of Avatar component.
// FIXME: title support is missing
export function AvatarSSR(props: AvatarProps) {
const { user, size } = props;
const nameOrUsername = user.name || user.username || "";
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
let imgSrc;
const alt = props.alt || nameOrUsername;
if (user.avatar) {
imgSrc = user.avatar;
} else if (user.emailMd5) {
imgSrc = defaultAvatarSrc(user.emailMd5);
const { size, title } = props;
let imgSrc = "";
let alt: string = props.alt;
if (props.user) {
const user = props.user;
const nameOrUsername = user.name || user.username || "";
alt = alt || nameOrUsername;
if (user.avatar) {
imgSrc = user.avatar;
} else if (user.emailMd5) {
imgSrc = defaultAvatarSrc(user.emailMd5);
}
} else {
imgSrc = props.imageSrc;
}
// eslint-disable-next-line @next/next/no-img-element
return imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
const avatar = imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white shadow-sm">
<Tooltip.Arrow />
{title}
</Tooltip.Content>
</Tooltip.Tooltip>
) : (
<>{avatar}</>
);
}

View File

@ -26,7 +26,6 @@ export type BookPageProps = inferSSRProps<typeof getServerSideProps>;
export default function Book(props: BookPageProps) {
const { t } = useLocale();
const locationLabels = getLocationLabels(t);
return props.away ? (
<div className="h-screen dark:bg-neutral-900">
<main className="mx-auto max-w-3xl px-4 py-24">

View File

@ -15,6 +15,7 @@ class MyDocument extends Document<Props> {
return (
<Html lang={locale} dir={dir}>
<Head>
<link rel="preload" href="/cal.ttf" as="font" type="font/ttf" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />

View File

@ -250,6 +250,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
items={type.users.map((organizer) => ({
alt: organizer.name || "",
image: `${WEBAPP_URL}/${organizer.username}/avatar.png`,
title: organizer.name || "",
}))}
/>
)}

View File

@ -66,6 +66,7 @@ function TeamPage({ team }: TeamPageProps) {
size={10}
items={type.users.map((user) => ({
alt: user.name || "",
title: user.name || "",
image: CAL_URL + "/" + user.username + "/avatar.png" || "",
}))}
/>

View File

@ -13,6 +13,8 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssgInit } from "@server/lib/ssg";
export type AvailabilityTeamPageProps = inferSSRProps<typeof getServerSideProps>;
export default function TeamType(props: AvailabilityTeamPageProps) {
@ -24,6 +26,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const typeParam = asStringOrNull(context.query.type);
const dateParam = asStringOrNull(context.query.date);
const rescheduleUid = asStringOrNull(context.query.rescheduleUid);
const ssg = await ssgInit(context);
if (!slugParam || !typeParam) {
throw new Error(`File is not named [idOrSlug]/[user]`);
@ -145,6 +148,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
workingHours,
previousPage: context.req.headers.referer ?? null,
booking,
trpcState: ssg.dehydrate(),
},
};
};