feat: login overlay re design (#27800)

This commit is contained in:
Udit Takkar
2026-02-11 15:12:10 +00:00
committed by GitHub
parent 4c804cdb00
commit b185cd231b
2 changed files with 129 additions and 149 deletions
@@ -13,6 +13,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent } from "@calcom/ui/components/dialog";
import { Input } from "@calcom/ui/components/form";
@@ -120,7 +121,7 @@ export default function JoinCall(props: PageProps) {
}
return callFrame;
} catch (err) {
} catch (_err) {
return DailyIframe.getCallInstance();
}
},
@@ -160,8 +161,6 @@ export default function JoinCall(props: PageProps) {
activeMeetingPassword,
activeMeetingUrl,
createCallFrame,
loggedInUserName,
overrideName,
guestCredentials,
]);
@@ -182,7 +181,7 @@ export default function JoinCall(props: PageProps) {
<div style={{ zIndex: 2, position: "relative" }}>
{calVideoLogo ? (
<img
className="min-w-16 min-h-16 fixed z-10 hidden aspect-square h-16 w-16 rounded-full sm:inline-block"
className="fixed z-10 hidden aspect-square h-16 min-h-16 w-16 min-w-16 rounded-full sm:inline-block"
src={calVideoLogo}
alt="My Org Logo"
style={{
@@ -206,6 +205,8 @@ export default function JoinCall(props: PageProps) {
<LogInOverlay
isOpen={!hideLoginModal}
bookingUid={booking.uid}
bookingTitle={booking.title}
hostName={booking.user?.name ?? ""}
loggedInUserName={loggedInUserName ?? undefined}
overrideName={overrideName}
requireEmailForGuests={requireEmailForGuests}
@@ -292,6 +293,8 @@ function ProgressBar(props: ProgressBarProps) {
interface LogInOverlayProps {
isOpen: boolean;
bookingUid: string;
bookingTitle: string;
hostName: string;
loggedInUserName?: string;
overrideName?: string;
requireEmailForGuests?: boolean;
@@ -308,6 +311,8 @@ export function LogInOverlay(props: LogInOverlayProps) {
const { t } = useLocale();
const {
bookingUid,
bookingTitle,
hostName,
isOpen: _open,
loggedInUserName,
overrideName,
@@ -441,100 +446,75 @@ export function LogInOverlay(props: LogInOverlayProps) {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent
title={t("join_video_call")}
description={t("choose_how_you_d_like_to_appear_on_the_call")}
className="p-6 sm:max-w-lg"
onInteractOutside={(e) => e.preventDefault()}>
<div className="mt-2 pb-4">
<div className="stack-y-4">
<div>
<div className="font-semibold">{t("join_as_guest")}</div>
<p className="text-subtle text-sm">{t("ideal_for_one_time_calls")}</p>
<DialogContent className="p-6 sm:max-w-md" onInteractOutside={(e) => e.preventDefault()}>
<div className="flex flex-col gap-5">
<div className="flex items-center justify-between rounded-lg border border-subtle p-3">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-subtle">
<Icon name="calendar-days" className="h-5 w-5 text-default" />
</div>
<div>
<p className="font-semibold text-emphasis text-sm">{bookingTitle}</p>
<p className="text-subtle text-xs">{t("hosted_by", { name: hostName })}</p>
</div>
</div>
{requireEmailForGuests ? (
<div className="flex flex-col gap-4">
<Input
type="text"
placeholder={t("your_name")}
className="w-full flex-1"
value={userName}
onChange={handleUserNameChange}
onKeyDown={handleKeyDown}
disabled={isLoading}
autoFocus
/>
<Input
type="email"
placeholder={t("email_address")}
className="w-full flex-1"
value={userEmail}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
disabled={isLoading}
/>
<Button
color="secondary"
className="w-fit self-end"
onClick={handleContinueAsGuest}
loading={isLoading}>
{t("continue")}
</Button>
</div>
) : (
<div className="flex gap-4">
<Input
type="text"
placeholder={t("your_name")}
className="w-full flex-1"
value={userName}
onChange={handleUserNameChange}
onKeyDown={handleKeyDown}
disabled={isLoading}
autoFocus
/>
<Button color="secondary" onClick={handleContinueAsGuest} loading={isLoading}>
{t("continue")}
</Button>
</div>
)}
<Badge variant="success" withDot>
{t("ready")}
</Badge>
</div>
{error && (
<div className="mt-4 rounded-md bg-red-50 p-3 dark:bg-red-900/20">
<div className="flex">
<div className="ml-3">
<p className="text-sm font-medium text-red-800 dark:text-red-200">{error}</p>
</div>
</div>
</div>
)}
<div>
<h3 className="font-bold text-emphasis text-lg">{t("ready_to_join")}</h3>
<p className="text-sm text-subtle">{t("enter_name_to_join_call")}</p>
</div>
{/* Divider */}
<hr className="my-6 h-0.5 border-t-0 bg-neutral-100 dark:bg-white/10" />
<div className="flex flex-col gap-3">
<Input
type="text"
placeholder={t("your_name")}
className="w-full"
value={userName}
onChange={handleUserNameChange}
onKeyDown={handleKeyDown}
disabled={isLoading}
autoFocus
/>
<div className="stack-y-4 mt-5">
<div>
<h4 className="text-base font-semibold text-black dark:text-white">
{t("sign_in_to_cal_com")}
</h4>
<p className="text-sm text-gray-500 dark:text-gray-300">
{t("track_meetings_and_manage_schedule")}
</p>
</div>
{requireEmailForGuests && (
<Input
type="email"
placeholder={t("email_address")}
className="w-full"
value={userEmail}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
disabled={isLoading}
/>
)}
<Button
color="primary"
className="w-full justify-center"
onClick={() =>
(window.location.href = `${WEBAPP_URL}/auth/login?callbackUrl=${WEBAPP_URL}/video/${bookingUid}`)
}>
{t("sign_in")}
onClick={handleContinueAsGuest}
loading={isLoading}>
{t("join_call_as_guest")}
</Button>
</div>
{error && (
<div className="rounded-md bg-error p-3">
<p className="font-medium text-error text-sm">{error}</p>
</div>
)}
<p className="text-center text-sm text-subtle">
<a
href={`${WEBAPP_URL}/auth/login?callbackUrl=${WEBAPP_URL}/video/${bookingUid}`}
className="text-emphasis underline">
{t("sign_in")}
</a>{" "}
{t("to_track_no_shows")}
</p>
</div>
</DialogContent>
</Dialog>
@@ -562,72 +542,66 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
});
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 overflow-x-hidden! bg-default w-full overflow-scroll border-r border-gray-300/20 p-4 text-white shadow-sm backdrop-blur-lg">
<h3>{t("what")}:</h3>
<p>{booking.title}</p>
<h3>{t("invitee_timezone")}:</h3>
<p>{timeZone}</p>
<h3>{t("when")}:</h3>
<p suppressHydrationWarning={true}>
{formatToLocalizedDate(startTime)} <br />
{formatToLocalizedTime({ date: startTime, timeZone })}
</p>
<h3>{t("time_left")}</h3>
<ProgressBar
key={String(open)}
endTime={endTime.toISOString()}
startTime={startTime.toISOString()}
/>
<aside
className={classNames(
"no-scrollbar fixed top-0 left-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 scroll-bar scrollbar-track-w-20 overflow-x-hidden! w-full max-w-64 overflow-scroll border-subtle border-r bg-default p-4 prose-h3:font-cal prose-a:text-emphasis prose-h3:text-emphasis text-emphasis shadow-sm backdrop-blur-lg">
<h3>{t("what")}:</h3>
<p>{booking.title}</p>
<h3>{t("invitee_timezone")}:</h3>
<p>{timeZone}</p>
<h3>{t("when")}:</h3>
<p suppressHydrationWarning={true}>
{formatToLocalizedDate(startTime)} <br />
{formatToLocalizedTime({ date: startTime, timeZone })}
</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")}
{!booking?.eventType?.hideOrganizerEmail && (
<>
: <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("who")}:</h3>
<p>
{booking?.user?.name} - {t("organizer")}
{!booking?.eventType?.hideOrganizerEmail && (
<>
<h3>{t("description")}:</h3>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Content is sanitized via markdownToSafeHTML */}
<div
className="prose-sm prose prose-invert"
dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(booking.description) }}
/>
: <a href={`mailto:${booking?.user?.email}`}>{booking?.user?.email}</a>
</>
)}
</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)}>
<Icon
name="chevron-right"
aria-hidden
className={classNames(open && "rotate-180", "w-5 transition-all duration-300 ease-in-out")}
</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>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Content is sanitized via markdownToSafeHTML */}
<div
className="prose-sm prose prose-invert"
dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(booking.description) }}
/>
</button>
</div>
</aside>
</>
</>
)}
</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-gray-300/20 border-l-0 bg-black/60 text-white shadow-sm backdrop-blur-lg"
onClick={() => setOpen(!open)}>
<Icon
name="chevron-right"
aria-hidden
className={classNames(open && "rotate-180", "w-5 transition-all duration-300 ease-in-out")}
/>
</button>
</div>
</aside>
);
}
@@ -745,6 +745,12 @@
"custom_attendee_location": "Custom attendee location",
"continue_as_guest": "Continue as guest",
"join_as_guest": "Join as guest",
"join_call_as_guest": "Join call as guest",
"ready_to_join": "Ready to join?",
"enter_name_to_join_call": "Enter your name to join the call",
"hosted_by": "Hosted by {{name}}",
"ready": "Ready",
"to_track_no_shows": "to track no-shows",
"ideal_for_one_time_calls": "Ideal for one-time calls",
"hosts_must_use_login": "Hosts must login to cal.com and then join this meeting",
"invalid_guest_email": "We don't recognize this email address for this meeting. Double-check you're using the same email that received the invitation.",