chore: module based file structure for /video group (#13453)

* chore: module based file structure for /video group

* fix

* fix
This commit is contained in:
Benny Joo authored and GitHub committed 2024-01-29 18:59:24 +00:00
1 parent 0af780aac9
commit d91e74ec13
15 files changed
+422 -388

No files matched your search

+4 -4
View File
@@ -1,11 +1,11 @@
import Page, { type JoinCallPageProps } from "@pages/video/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { APP_NAME } from "@calcom/lib/constants";
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
import VideosSingleView from "~/videos/views/videos-single-view";
import { getServerSideProps, type PageProps } from "~/videos/views/videos-single-view.getServerSideProps";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -13,6 +13,6 @@ export const generateMetadata = async () =>
(t) => t("quick_video_meeting")
);
const getData = withAppDirSsr<JoinCallPageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: VideosSingleView, getLayout: null })<"P">;
@@ -1,9 +1,9 @@
import Page from "@pages/video/meeting-ended/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
import MeetingEnded from "~/videos/views/videos-meeting-ended-single-view";
import { getServerSideProps } from "~/videos/views/videos-meeting-ended-single-view.getServerSideProps";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -13,4 +13,4 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: MeetingEnded, getLayout: null })<"P">;
@@ -1,4 +1,3 @@
import Page from "@pages/video/meeting-not-started/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
@@ -6,7 +5,8 @@ import { WithLayout } from "app/layoutHOC";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
import MeetingNotStarted from "~/videos/views/videos-meeting-not-started-single-view";
import { getServerSideProps } from "~/videos/views/videos-meeting-not-started-single-view.getServerSideProps";
export const generateMetadata = async ({ params }: PageProps) => {
const booking = await prisma.booking.findUnique({
@@ -24,4 +24,4 @@ export const generateMetadata = async ({ params }: PageProps) => {
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: MeetingNotStarted, getLayout: null })<"P">;
@@ -1,10 +1,11 @@
import LegacyPage from "@pages/video/no-meeting-found";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import type { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
import NoMeetingFound from "~/videos/views/videos-no-meeting-found-single-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("no_meeting_found"),
@@ -20,4 +21,4 @@ const getData = async (context: GetServerSidePropsContext) => {
};
};
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
export default WithLayout({ getData, Page: NoMeetingFound, getLayout: null })<"P">;
@@ -2,6 +2,9 @@ import type { GetServerSidePropsContext } from "next";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { type inferSSRProps } from "@lib/types/inferSSRProps";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const booking = await prisma.booking.findUnique({
where: {
@@ -0,0 +1,62 @@
"use client";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { Button, HeadSeo } from "@calcom/ui";
import { ArrowRight, Calendar, X } from "@calcom/ui/components/icon";
import { type PageProps } from "./videos-meeting-ended-single-view.getServerSideProps";
export default function MeetingUnavailable(props: PageProps) {
const { t } = useLocale();
return (
<div>
<HeadSeo title="Meeting Unavailable" description="Meeting Unavailable" />
<main className="mx-auto my-24 max-w-3xl">
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
&#8203;
</span>
<div
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div>
<div className="bg-error mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<X className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-emphasis text-lg font-medium leading-6" id="modal-headline">
This meeting is in the past.
</h3>
</div>
<div className="mt-4 border-b border-t py-4">
<h2 className="font-cal text-default mb-2 text-center text-lg font-medium">
{props.booking.title}
</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</div>
</div>
<div className="mt-5 text-center sm:mt-6">
<div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back")}
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
@@ -1,6 +1,9 @@
import type { GetServerSidePropsContext } from "next";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
// change the type
export async function getServerSideProps(context: GetServerSidePropsContext) {
@@ -0,0 +1,38 @@
"use client";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { Button, HeadSeo, EmptyScreen } from "@calcom/ui";
import { ArrowRight, Calendar, Clock } from "@calcom/ui/components/icon";
import { type PageProps } from "./videos-meeting-not-started-single-view.getServerSideProps";
export default function MeetingNotStarted(props: PageProps) {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("this_meeting_has_not_started_yet")} description={props.booking.title} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={Clock}
headline={t("this_meeting_has_not_started_yet")}
description={
<>
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</>
}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back")}
</Button>
}
/>
</main>
</>
);
}
@@ -0,0 +1,27 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, EmptyScreen, HeadSeo } from "@calcom/ui";
import { X, ArrowRight } from "@calcom/ui/components/icon";
export default function NoMeetingFound() {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("no_meeting_found")} description={t("no_meeting_found")} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={X}
headline={t("no_meeting_found")}
description={t("no_meeting_found_description")}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back_home")}
</Button>
}
/>
</main>
</>
);
}
@@ -5,8 +5,12 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { type inferSSRProps } from "@lib/types/inferSSRProps";
import { ssrInit } from "@server/lib/ssr";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export async function getServerSideProps(context: GetServerSidePropsContext) {
@@ -0,0 +1,243 @@
"use client";
import DailyIframe from "@daily-co/daily-js";
import Head from "next/head";
import { useState, useEffect, useRef } from "react";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, SEO_IMG_OGIMG_VIDEO, WEBSITE_URL } from "@calcom/lib/constants";
import { formatToLocalizedDate, formatToLocalizedTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { ChevronRight } from "@calcom/ui/components/icon";
import { type PageProps } from "./videos-single-view.getServerSideProps";
export default function JoinCall(props: PageProps) {
const { t } = useLocale();
const { meetingUrl, meetingPassword, booking } = props;
useEffect(() => {
const callFrame = DailyIframe.createFrame({
theme: {
colors: {
accent: "#FFF",
accentText: "#111111",
background: "#111111",
backgroundAccent: "#111111",
baseText: "#FFF",
border: "#292929",
mainAreaBg: "#111111",
mainAreaBgAccent: "#1A1A1A",
mainAreaText: "#FFF",
supportiveText: "#FFF",
},
},
showLeaveButton: true,
iframeStyle: {
position: "fixed",
width: "100%",
height: "100%",
},
url: meetingUrl,
...(typeof meetingPassword === "string" && { token: meetingPassword }),
});
callFrame.join();
return () => {
callFrame.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const title = `${APP_NAME} Video`;
return (
<>
<Head>
<title>{title}</title>
<meta name="description" content={t("quick_video_meeting")} />
<meta property="og:image" content={SEO_IMG_OGIMG_VIDEO} />
<meta property="og:type" content="website" />
<meta property="og:url" content={`${WEBSITE_URL}/video`} />
<meta property="og:title" content={`${APP_NAME} Video`} />
<meta property="og:description" content={t("quick_video_meeting")} />
<meta property="twitter:image" content={SEO_IMG_OGIMG_VIDEO} />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={`${WEBSITE_URL}/video`} />
<meta property="twitter:title" content={`${APP_NAME} Video`} />
<meta property="twitter:description" content={t("quick_video_meeting")} />
</Head>
<div style={{ zIndex: 2, position: "relative" }}>
{booking?.user?.organization?.calVideoLogo ? (
<img
className="min-w-16 min-h-16 fixed z-10 hidden aspect-square h-16 w-16 rounded-full sm:inline-block"
src={booking.user.organization.calVideoLogo}
alt="My Org Logo"
style={{
top: 32,
left: 32,
}}
/>
) : (
<img
className="fixed z-10 hidden sm:inline-block"
src={`${WEBSITE_URL}/cal-logo-word-dark.svg`}
alt="Logo"
style={{
top: 32,
left: 32,
}}
/>
)}
</div>
<VideoMeetingInfo booking={booking} />
</>
);
}
interface ProgressBarProps {
startTime: string;
endTime: string;
}
function ProgressBar(props: ProgressBarProps) {
const { t } = useLocale();
const { startTime, endTime } = props;
const currentTime = dayjs().second(0).millisecond(0);
const startingTime = dayjs(startTime).second(0).millisecond(0);
const isPast = currentTime.isAfter(startingTime);
const currentDifference = dayjs().diff(startingTime, "minutes");
const startDuration = dayjs(endTime).diff(startingTime, "minutes");
const [duration, setDuration] = useState(() => {
if (currentDifference >= 0 && isPast) {
return startDuration - currentDifference;
} else {
return startDuration;
}
});
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
const now = dayjs();
const remainingMilliseconds = (60 - now.get("seconds")) * 1000 - now.get("milliseconds");
timeoutRef.current = setTimeout(() => {
const past = dayjs().isAfter(startingTime);
if (past) {
setDuration((prev) => prev - 1);
}
intervalRef.current = setInterval(() => {
if (dayjs().isAfter(startingTime)) {
setDuration((prev) => prev - 1);
}
}, 60000);
}, remainingMilliseconds);
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const prev = startDuration - duration;
const percentage = prev * (100 / startDuration);
return (
<div>
<p>
{duration} {t("minutes")}
</p>
<div className="relative h-2 max-w-xl overflow-hidden rounded-full">
<div className="absolute h-full w-full bg-gray-500/10" />
<div className={classNames("relative h-full bg-green-500")} style={{ width: `${percentage}%` }} />
</div>
</div>
);
}
interface VideoMeetingInfo {
booking: PageProps["booking"];
}
export function VideoMeetingInfo(props: VideoMeetingInfo) {
const [open, setOpen] = useState(false);
const { booking } = props;
const { t } = useLocale();
const endTime = new Date(booking.endTime);
const startTime = new Date(booking.startTime);
return (
<>
<aside
className={classNames(
"no-scrollbar fixed left-0 top-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
open ? "translate-x-0" : "-translate-x-[232px]"
)}>
<main className="prose-sm prose max-w-64 prose-a:text-white prose-h3:text-white prose-h3:font-cal scroll-bar scrollbar-track-w-20 w-full overflow-scroll overflow-x-hidden border-r border-gray-300/20 bg-black/80 p-4 text-white shadow-sm backdrop-blur-lg">
<h3>{t("what")}:</h3>
<p>{booking.title}</p>
<h3>{t("invitee_timezone")}:</h3>
<p>{booking.user?.timeZone}</p>
<h3>{t("when")}:</h3>
<p suppressHydrationWarning={true}>
{formatToLocalizedDate(startTime)} <br />
{formatToLocalizedTime(startTime)}
</p>
<h3>{t("time_left")}</h3>
<ProgressBar
key={String(open)}
endTime={endTime.toISOString()}
startTime={startTime.toISOString()}
/>
<h3>{t("who")}:</h3>
<p>
{booking?.user?.name} - {t("organizer")}:{" "}
<a href={`mailto:${booking?.user?.email}`}>{booking?.user?.email}</a>
</p>
{booking.attendees.length
? booking.attendees.map((attendee) => (
<p key={attendee.id}>
{attendee.name} – <a href={`mailto:${attendee.email}`}>{attendee.email}</a>
</p>
))
: null}
{booking.description && (
<>
<h3>{t("description")}:</h3>
<div
className="prose-sm prose prose-invert"
dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(booking.description) }}
/>
</>
)}
</main>
<div className="flex items-center justify-center">
<button
aria-label={`${open ? "close" : "open"} booking description sidebar`}
className="h-20 w-6 rounded-r-md border border-l-0 border-gray-300/20 bg-black/60 text-white shadow-sm backdrop-blur-lg"
onClick={() => setOpen(!open)}>
<ChevronRight
aria-hidden
className={classNames(open && "rotate-180", "w-5 transition-all duration-300 ease-in-out")}
/>
</button>
</div>
</aside>
</>
);
}
+6 -246
View File
@@ -1,251 +1,11 @@
"use client";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import DailyIframe from "@daily-co/daily-js";
import Head from "next/head";
import { useState, useEffect, useRef } from "react";
import VideosSingleView from "~/videos/views/videos-single-view";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, SEO_IMG_OGIMG_VIDEO, WEBSITE_URL } from "@calcom/lib/constants";
import { formatToLocalizedDate, formatToLocalizedTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { ChevronRight } from "@calcom/ui/components/icon";
export { getServerSideProps, type PageProps } from "~/videos/views/videos-single-view.getServerSideProps";
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
const VideosSinglePage = VideosSingleView as unknown as CalPageWrapper;
import PageWrapper from "@components/PageWrapper";
VideosSinglePage.PageWrapper = PageWrapper;
export type JoinCallPageProps = Omit<inferSSRProps<typeof getServerSideProps>, "trpcState">;
export default function JoinCall(props: JoinCallPageProps) {
const { t } = useLocale();
const { meetingUrl, meetingPassword, booking } = props;
useEffect(() => {
const callFrame = DailyIframe.createFrame({
theme: {
colors: {
accent: "#FFF",
accentText: "#111111",
background: "#111111",
backgroundAccent: "#111111",
baseText: "#FFF",
border: "#292929",
mainAreaBg: "#111111",
mainAreaBgAccent: "#1A1A1A",
mainAreaText: "#FFF",
supportiveText: "#FFF",
},
},
showLeaveButton: true,
iframeStyle: {
position: "fixed",
width: "100%",
height: "100%",
},
url: meetingUrl,
...(typeof meetingPassword === "string" && { token: meetingPassword }),
});
callFrame.join();
return () => {
callFrame.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const title = `${APP_NAME} Video`;
return (
<>
<Head>
<title>{title}</title>
<meta name="description" content={t("quick_video_meeting")} />
<meta property="og:image" content={SEO_IMG_OGIMG_VIDEO} />
<meta property="og:type" content="website" />
<meta property="og:url" content={`${WEBSITE_URL}/video`} />
<meta property="og:title" content={`${APP_NAME} Video`} />
<meta property="og:description" content={t("quick_video_meeting")} />
<meta property="twitter:image" content={SEO_IMG_OGIMG_VIDEO} />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={`${WEBSITE_URL}/video`} />
<meta property="twitter:title" content={`${APP_NAME} Video`} />
<meta property="twitter:description" content={t("quick_video_meeting")} />
</Head>
<div style={{ zIndex: 2, position: "relative" }}>
{booking?.user?.organization?.calVideoLogo ? (
<img
className="min-w-16 min-h-16 fixed z-10 hidden aspect-square h-16 w-16 rounded-full sm:inline-block"
src={booking.user.organization.calVideoLogo}
alt="My Org Logo"
style={{
top: 32,
left: 32,
}}
/>
) : (
<img
className="fixed z-10 hidden sm:inline-block"
src={`${WEBSITE_URL}/cal-logo-word-dark.svg`}
alt="Logo"
style={{
top: 32,
left: 32,
}}
/>
)}
</div>
<VideoMeetingInfo booking={booking} />
</>
);
}
interface ProgressBarProps {
startTime: string;
endTime: string;
}
function ProgressBar(props: ProgressBarProps) {
const { t } = useLocale();
const { startTime, endTime } = props;
const currentTime = dayjs().second(0).millisecond(0);
const startingTime = dayjs(startTime).second(0).millisecond(0);
const isPast = currentTime.isAfter(startingTime);
const currentDifference = dayjs().diff(startingTime, "minutes");
const startDuration = dayjs(endTime).diff(startingTime, "minutes");
const [duration, setDuration] = useState(() => {
if (currentDifference >= 0 && isPast) {
return startDuration - currentDifference;
} else {
return startDuration;
}
});
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
const now = dayjs();
const remainingMilliseconds = (60 - now.get("seconds")) * 1000 - now.get("milliseconds");
timeoutRef.current = setTimeout(() => {
const past = dayjs().isAfter(startingTime);
if (past) {
setDuration((prev) => prev - 1);
}
intervalRef.current = setInterval(() => {
if (dayjs().isAfter(startingTime)) {
setDuration((prev) => prev - 1);
}
}, 60000);
}, remainingMilliseconds);
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const prev = startDuration - duration;
const percentage = prev * (100 / startDuration);
return (
<div>
<p>
{duration} {t("minutes")}
</p>
<div className="relative h-2 max-w-xl overflow-hidden rounded-full">
<div className="absolute h-full w-full bg-gray-500/10" />
<div className={classNames("relative h-full bg-green-500")} style={{ width: `${percentage}%` }} />
</div>
</div>
);
}
interface VideoMeetingInfo {
booking: JoinCallPageProps["booking"];
}
export function VideoMeetingInfo(props: VideoMeetingInfo) {
const [open, setOpen] = useState(false);
const { booking } = props;
const { t } = useLocale();
const endTime = new Date(booking.endTime);
const startTime = new Date(booking.startTime);
return (
<>
<aside
className={classNames(
"no-scrollbar fixed left-0 top-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
open ? "translate-x-0" : "-translate-x-[232px]"
)}>
<main className="prose-sm prose max-w-64 prose-a:text-white prose-h3:text-white prose-h3:font-cal scroll-bar scrollbar-track-w-20 w-full overflow-scroll overflow-x-hidden border-r border-gray-300/20 bg-black/80 p-4 text-white shadow-sm backdrop-blur-lg">
<h3>{t("what")}:</h3>
<p>{booking.title}</p>
<h3>{t("invitee_timezone")}:</h3>
<p>{booking.user?.timeZone}</p>
<h3>{t("when")}:</h3>
<p suppressHydrationWarning={true}>
{formatToLocalizedDate(startTime)} <br />
{formatToLocalizedTime(startTime)}
</p>
<h3>{t("time_left")}</h3>
<ProgressBar
key={String(open)}
endTime={endTime.toISOString()}
startTime={startTime.toISOString()}
/>
<h3>{t("who")}:</h3>
<p>
{booking?.user?.name} - {t("organizer")}:{" "}
<a href={`mailto:${booking?.user?.email}`}>{booking?.user?.email}</a>
</p>
{booking.attendees.length
? booking.attendees.map((attendee) => (
<p key={attendee.id}>
{attendee.name} – <a href={`mailto:${attendee.email}`}>{attendee.email}</a>
</p>
))
: null}
{booking.description && (
<>
<h3>{t("description")}:</h3>
<div
className="prose-sm prose prose-invert"
dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(booking.description) }}
/>
</>
)}
</main>
<div className="flex items-center justify-center">
<button
aria-label={`${open ? "close" : "open"} booking description sidebar`}
className="h-20 w-6 rounded-r-md border border-l-0 border-gray-300/20 bg-black/60 text-white shadow-sm backdrop-blur-lg"
onClick={() => setOpen(!open)}>
<ChevronRight
aria-hidden
className={classNames(open && "rotate-180", "w-5 transition-all duration-300 ease-in-out")}
/>
</button>
</div>
</aside>
</>
);
}
export { getServerSideProps };
JoinCall.PageWrapper = PageWrapper;
export default VideosSinglePage;
+9 -63
View File
@@ -1,68 +1,14 @@
"use client";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { Button, HeadSeo } from "@calcom/ui";
import { ArrowRight, Calendar, X } from "@calcom/ui/components/icon";
import MeetingEnded from "~/videos/views/videos-meeting-ended-single-view";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
export {
getServerSideProps,
type PageProps,
} from "~/videos/views/videos-meeting-ended-single-view.getServerSideProps";
import PageWrapper from "@components/PageWrapper";
const MeetingEndedPage = MeetingEnded as unknown as CalPageWrapper;
export default function MeetingUnavailable(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
MeetingEndedPage.PageWrapper = PageWrapper;
return (
<div>
<HeadSeo title="Meeting Unavailable" description="Meeting Unavailable" />
<main className="mx-auto my-24 max-w-3xl">
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
&#8203;
</span>
<div
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div>
<div className="bg-error mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<X className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-emphasis text-lg font-medium leading-6" id="modal-headline">
This meeting is in the past.
</h3>
</div>
<div className="mt-4 border-b border-t py-4">
<h2 className="font-cal text-default mb-2 text-center text-lg font-medium">
{props.booking.title}
</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</div>
</div>
<div className="mt-5 text-center sm:mt-6">
<div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back")}
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
export { getServerSideProps };
MeetingUnavailable.PageWrapper = PageWrapper;
export default MeetingEndedPage;
@@ -1,45 +1,14 @@
"use client";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, HeadSeo, EmptyScreen } from "@calcom/ui";
import { ArrowRight, Calendar, Clock } from "@calcom/ui/components/icon";
import MeetingNotStarted from "~/videos/views/videos-meeting-not-started-single-view";
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
export {
getServerSideProps,
type PageProps,
} from "~/videos/views/videos-meeting-not-started-single-view.getServerSideProps";
import PageWrapper from "@components/PageWrapper";
const MeetingNotStartedPage = MeetingNotStarted as unknown as CalPageWrapper;
export default function MeetingNotStarted(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("this_meeting_has_not_started_yet")} description={props.booking.title} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={Clock}
headline={t("this_meeting_has_not_started_yet")}
description={
<>
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</>
}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back")}
</Button>
}
/>
</main>
</>
);
}
MeetingNotStartedPage.PageWrapper = PageWrapper;
export { getServerSideProps };
MeetingNotStarted.PageWrapper = PageWrapper;
export default MeetingNotStartedPage;
+5 -27
View File
@@ -1,31 +1,9 @@
"use client";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, EmptyScreen, HeadSeo } from "@calcom/ui";
import { X, ArrowRight } from "@calcom/ui/components/icon";
import NoMeetingFound from "~/videos/views/videos-no-meeting-found-single-view";
import PageWrapper from "@components/PageWrapper";
const NoMeetingFoundPage = NoMeetingFound as unknown as CalPageWrapper;
export default function NoMeetingFound() {
const { t } = useLocale();
NoMeetingFoundPage.PageWrapper = PageWrapper;
return (
<>
<HeadSeo title={t("no_meeting_found")} description={t("no_meeting_found")} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={X}
headline={t("no_meeting_found")}
description={t("no_meeting_found_description")}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
{t("go_back_home")}
</Button>
}
/>
</main>
</>
);
}
NoMeetingFound.PageWrapper = PageWrapper;
export default NoMeetingFoundPage;