chore: module based file structure for /video group (#13453)
* chore: module based file structure for /video group * fix * fix
This commit is contained in:
1 parent
0af780aac9
commit
d91e74ec13
15 files changed
+422
-388
No files matched your search
@@ -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">;
|
||||
+3
@@ -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">
|
||||
​
|
||||
</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>
|
||||
);
|
||||
}
|
||||
+3
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+4
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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">
|
||||
​
|
||||
</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;
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user