Merge pull request #449 from Monadical-SAS/webinar-ui-updates

Webinar UI updates
This commit is contained in:
2025-02-04 13:38:58 +01:00
committed by GitHub
9 changed files with 333 additions and 168 deletions

View File

@@ -24,6 +24,7 @@ COPY --from=builder /venv /venv
RUN mkdir -p /app RUN mkdir -p /app
COPY reflector /app/reflector COPY reflector /app/reflector
COPY migrations /app/migrations COPY migrations /app/migrations
COPY images /app/images
COPY alembic.ini runserver.sh /app/ COPY alembic.ini runserver.sh /app/
WORKDIR /app WORKDIR /app
CMD ["./runserver.sh"] CMD ["./runserver.sh"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

BIN
server/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -11,7 +11,7 @@ from reflector.db import database
from reflector.db.meetings import meetings_controller from reflector.db.meetings import meetings_controller
from reflector.db.rooms import rooms_controller from reflector.db.rooms import rooms_controller
from reflector.settings import settings from reflector.settings import settings
from reflector.whereby import create_meeting from reflector.whereby import create_meeting, upload_logo
router = APIRouter() router = APIRouter()
@@ -150,6 +150,7 @@ async def rooms_create_meeting(
if meeting is None: if meeting is None:
end_date = current_time + timedelta(hours=8) end_date = current_time + timedelta(hours=8)
meeting = await create_meeting("", end_date=end_date, room=room) meeting = await create_meeting("", end_date=end_date, room=room)
await upload_logo(meeting["roomName"], "./images/logo.png")
meeting = await meetings_controller.create( meeting = await meetings_controller.create(
id=meeting["meetingId"], id=meeting["meetingId"],

View File

@@ -52,3 +52,17 @@ async def get_room_sessions(room_name: str):
) )
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
async def upload_logo(room_name: str, logo_path: str):
async with httpx.AsyncClient() as client:
with open(logo_path, "rb") as f:
response = await client.put(
f"{settings.WHEREBY_API_URL}/rooms{room_name}/theme/logo",
headers={
"Authorization": f"Bearer {settings.WHEREBY_API_KEY}",
},
timeout=TIMEOUT,
files={"image": f},
)
response.raise_for_status()

View File

@@ -0,0 +1,63 @@
"use client";
import { useEffect, useRef } from "react";
import "@whereby.com/browser-sdk/embed";
import { Box, Button, HStack, useToast, Text } from "@chakra-ui/react";
interface WherebyEmbedProps {
roomUrl: string;
}
export default function WherebyEmbed({ roomUrl }: WherebyEmbedProps) {
const wherebyRef = useRef<HTMLElement>(null);
const toast = useToast();
useEffect(() => {
if (roomUrl && !localStorage.getItem("recording-notice-dismissed")) {
const toastId = toast({
position: "top",
duration: null,
render: ({ onClose }) => (
<Box p={4} bg="white" borderRadius="md" boxShadow="md">
<HStack justify="space-between" align="center">
<Text>
This webinar is being recorded. By continuing, you agree to our{" "}
<Button
as="a"
href="https://monadical.com/privacy"
variant="link"
color="blue.600"
textDecoration="underline"
target="_blank"
>
Privacy Policy
</Button>
</Text>
<Button
size="sm"
variant="ghost"
onClick={() => {
localStorage.setItem("recording-notice-dismissed", "true");
onClose();
}}
>
</Button>
</HStack>
</Box>
),
});
return () => {
toast.close(toastId);
};
}
}, [roomUrl, toast]);
return (
<whereby-embed
ref={wherebyRef}
room={roomUrl}
style={{ width: "100vw", height: "100vh" }}
/>
);
}

View File

@@ -0,0 +1,253 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { notFound } from "next/navigation";
import useRoomMeeting from "../../[roomName]/useRoomMeeting";
import dynamic from "next/dynamic";
const WherebyEmbed = dynamic(() => import("../../lib/WherebyEmbed"), {
ssr: false,
});
export type WebinarDetails = {
params: {
title: string;
};
};
export type Webinar = {
title: string;
startsAt: string;
endsAt: string;
};
enum WebinarStatus {
Upcoming = "upcoming",
Live = "live",
Ended = "ended",
}
const ROOM_NAME = "webinar";
const WEBINARS: Webinar[] = [
{
title: "ai-operational-assistant",
startsAt: "2025-02-05T17:00:00Z", // 12pm EST
endsAt: "2025-02-05T18:00:00Z",
},
{
title: "ai-operational-assistant-dry-run",
startsAt: "2025-02-04T15:00:00Z",
endsAt: "2025-02-04T16:00:00Z",
},
];
export default function WebinarPage(details: WebinarDetails) {
const title = details.params.title;
const webinar = WEBINARS.find((webinar) => webinar.title === title);
if (!webinar) {
return notFound();
}
const startDate = new Date(Date.parse(webinar.startsAt));
const endDate = new Date(Date.parse(webinar.endsAt));
const meeting = useRoomMeeting(ROOM_NAME);
const roomUrl = meeting?.response?.host_room_url
? meeting?.response?.host_room_url
: meeting?.response?.room_url;
const [status, setStatus] = useState(WebinarStatus.Ended);
const [countdown, setCountdown] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
});
useEffect(() => {
const updateCountdown = () => {
const now = new Date();
if (now < startDate) {
setStatus(WebinarStatus.Upcoming);
const difference = startDate.getTime() - now.getTime();
setCountdown({
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60),
});
} else if (now < endDate) {
setStatus(WebinarStatus.Live);
}
};
updateCountdown();
const timer = setInterval(updateCountdown, 1000);
return () => clearInterval(timer);
}, [webinar]);
if (status === WebinarStatus.Live) {
return <>{roomUrl && <WherebyEmbed roomUrl={roomUrl} />}</>;
}
return (
<div className="max-w-4xl mx-auto px-2 py-8 bg-gray-50">
<div className="bg-white rounded-3xl px-4 md:px-36 py-4 shadow-md mx-auto">
<Link href="https://www.monadical.com" target="_blank">
<img
src="/monadical-black-white 1.svg"
alt="Monadical Logo"
className="mx-auto mb-8"
width={40}
height={40}
/>
</Link>
<div className="text-center text-sky-600 text-sm font-semibold mb-4">
FREE WEBINAR
</div>
<h1 className="text-center text-4xl md:text-5xl mb-3 leading-tight">
Building AI-Powered
<br />
Operational Assistants
</h1>
<p className="text-center text-gray-600 mb-4">
From Simple Automation to Strategic Implementation
</p>
<p className="text-center font-semibold mb-4">
Wednesday, February 5th @ 12pm EST
</p>
<div className="flex justify-center gap-1 md:gap-8 mb-8">
{[
{ value: countdown.days, label: "DAYS" },
{ value: countdown.hours, label: "HOURS" },
{ value: countdown.minutes, label: "MINUTES" },
{ value: countdown.seconds, label: "SECONDS" },
].map((item, index) => (
<div
key={index}
className="text-center bg-white border border-gray-100 shadow-md rounded-lg p-4 aspect-square w-24"
>
<div className="text-5xl mb-2">{item.value}</div>
<div className="text-sky-600 text-xs">{item.label}</div>
</div>
))}
</div>
<div className="px-6 md:px-16">
<Link
href="https://www.linkedin.com/events/7286034395642179584/"
target="_blank"
className="block w-full max-w-xs mx-auto py-4 px-6 bg-sky-600 text-white text-center font-semibold rounded-full hover:bg-sky-700 transition-colors mb-8"
>
RSVP HERE
</Link>
<div className="space-y-4 mb-8 mt-24">
<p>
AI is ready to deliver value to your organization, but it's not
ready to act autonomously. The highest-value applications of AI
today are assistants, which significantly increase the efficiency
of workers in operational roles. Software companies are reporting
30% improvements in developer output across the board, and there's
no reason AI can't deliver the same kind of value to workers in
other roles.
</p>
<p>
In this session,{" "}
<Link
href="https://www.monadical.com"
target="_blank"
className="text-sky-600 hover:text-sky-700"
>
Monadical
</Link>{" "}
cofounder Max McCrea will dive into what operational assistants
are and how you can implement them in your organization to deliver
real, tangible value.
</p>
</div>
<div className="mb-8">
<h2 className="font-bold text-xl mb-4">What We'll Cover:</h2>
<ul className="space-y-4">
{[
"What an AI operational assistant is (and isn't).",
"Example use cases for how they can be implemented across your organization.",
"Key security and design considerations to avoid sharing sensitive data with outside platforms.",
"Live demos showing both entry-level and advanced implementations.",
"How you can start implementing them to immediately unlock value.",
].map((item, index) => (
<li
key={index}
className="pl-6 relative before:content-[''] before:absolute before:left-0 before:top-2 before:w-2 before:h-2 before:bg-sky-600"
>
{item}
</li>
))}
</ul>
</div>
<div className="mb-8">
<h2 className="font-bold text-xl mb-4">Who Should Attend:</h2>
<ul className="space-y-4">
{[
"Operations leaders looking to reduce manual work",
"Technical decision makers evaluating AI solutions",
"Teams concerned about data security and control",
].map((item, index) => (
<li
key={index}
className="pl-6 relative before:content-[''] before:absolute before:left-0 before:top-2 before:w-2 before:h-2 before:bg-sky-600"
>
{item}
</li>
))}
</ul>
</div>
<p className="mb-8">
Plan to walk away with a clear understanding of how to implement AI
solutions in your organization, with live demos of actual
implementations and plenty of time for Q&A to address your specific
challenges.
</p>
<Link
href="https://www.linkedin.com/events/7286034395642179584/"
target="_blank"
className="block w-full max-w-xs mx-auto py-4 px-6 bg-sky-600 text-white text-center font-semibold rounded-full hover:bg-sky-700 transition-colors mb-8"
>
RSVP HERE
</Link>
</div>
<div className="text-center text-gray-600 text-sm my-24">
POWERED BY:
<br />
<Link href="#" className="flex justify-center items-center mx-auto">
<Image
src="/reach.svg"
width={32}
height={40}
className="h-11 w-auto"
alt="Reflector"
/>
<div className="flex-col ml-3 mt-4">
<h1 className="text-[28px] font-semibold leading-tight text-left">
Reflector
</h1>
<p className="text-gray-500 text-xs tracking-tight -mt-1">
Capture the signal, not the noise
</p>
</div>
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,167 +0,0 @@
"use client";
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
export default function WebinarPage() {
const [countdown, setCountdown] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0
});
useEffect(() => {
const targetDate = new Date('2025-02-05T17:00:00Z'); // 12pm EST
const updateCountdown = () => {
const now = new Date();
const difference = targetDate.getTime() - now.getTime();
// If the target date has passed, show all zeros
if (difference < 0) {
setCountdown({
days: 0,
hours: 0,
minutes: 0,
seconds: 0
});
return;
}
setCountdown({
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60)
});
};
const timer = setInterval(updateCountdown, 1000);
return () => clearInterval(timer);
}, []);
return (
<div className="max-w-4xl mx-auto px-2 py-8 bg-gray-50">
<div className="bg-white rounded-3xl px-4 md:px-36 py-4 shadow-md mx-auto">
<Link href="https://www.monadical.com" target="_blank">
<img src="/monadical-black-white 1.svg" alt="Monadical Logo" className="mx-auto mb-8" width={40} height={40} />
</Link>
<div className="text-center text-sky-600 text-sm font-semibold mb-4">FREE WEBINAR</div>
<h1 className="text-center text-4xl md:text-5xl mb-3 leading-tight">
Building AI-Powered<br />Operational Assistants
</h1>
<p className="text-center text-gray-600 mb-4">
From Simple Automation to Strategic Implementation
</p>
<p className="text-center font-semibold mb-4">
Wednesday, February 5th @ 12pm EST
</p>
<div className="flex justify-center gap-1 md:gap-8 mb-8">
{[
{ value: countdown.days, label: 'DAYS' },
{ value: countdown.hours, label: 'HOURS' },
{ value: countdown.minutes, label: 'MINUTES' },
{ value: countdown.seconds, label: 'SECONDS' }
].map((item, index) => (
<div key={index} className="text-center bg-white border border-gray-100 shadow-md rounded-lg p-4 aspect-square w-24">
<div className="text-5xl mb-2">{item.value}</div>
<div className="text-sky-600 text-xs">{item.label}</div>
</div>
))}
</div>
<div className="px-6 md:px-16">
<Link
href="https://www.linkedin.com/events/7286034395642179584/"
target="_blank"
className="block w-full max-w-xs mx-auto py-4 px-6 bg-sky-600 text-white text-center font-semibold rounded-full hover:bg-sky-700 transition-colors mb-8"
>
RSVP HERE
</Link>
<div className="space-y-4 mb-8 mt-24">
<p>
AI is ready to deliver value to your organization, but it's not ready to act autonomously.
The highest-value applications of AI today are assistants, which significantly increase the efficiency
of workers in operational roles. Software companies are reporting 30% improvements in developer output
across the board, and there's no reason AI can't deliver the same kind of value to workers in other roles.
</p>
<p>
In this session, <Link href="https://www.monadical.com" target="_blank" className="text-sky-600 hover:text-sky-700">Monadical</Link> cofounder Max McCrea will dive into what operational assistants are and
how you can implement them in your organization to deliver real, tangible value.
</p>
</div>
<div className="mb-8">
<h2 className="font-bold text-xl mb-4">What We'll Cover:</h2>
<ul className="space-y-4">
{[
"What an AI operational assistant is (and isn't).",
"Example use cases for how they can be implemented across your organization.",
"Key security and design considerations to avoid sharing sensitive data with outside platforms.",
"Live demos showing both entry-level and advanced implementations.",
"How you can start implementing them to immediately unlock value."
].map((item, index) => (
<li key={index} className="pl-6 relative before:content-[''] before:absolute before:left-0 before:top-2 before:w-2 before:h-2 before:bg-sky-600">
{item}
</li>
))}
</ul>
</div>
<div className="mb-8">
<h2 className="font-bold text-xl mb-4">Who Should Attend:</h2>
<ul className="space-y-4">
{[
"Operations leaders looking to reduce manual work",
"Technical decision makers evaluating AI solutions",
"Teams concerned about data security and control"
].map((item, index) => (
<li key={index} className="pl-6 relative before:content-[''] before:absolute before:left-0 before:top-2 before:w-2 before:h-2 before:bg-sky-600">
{item}
</li>
))}
</ul>
</div>
<p className="mb-8">
Plan to walk away with a clear understanding of how to implement AI solutions in your organization,
with live demos of actual implementations and plenty of time for Q&A to address your specific challenges.
</p>
<Link
href="https://www.linkedin.com/events/7286034395642179584/"
target="_blank"
className="block w-full max-w-xs mx-auto py-4 px-6 bg-sky-600 text-white text-center font-semibold rounded-full hover:bg-sky-700 transition-colors mb-8"
>
RSVP HERE
</Link>
</div>
<div className="text-center text-gray-600 text-sm my-24">
POWERED BY:<br />
<Link href="#" className="flex justify-center items-center mx-auto">
<Image
src="/reach.svg"
width={32}
height={40}
className="h-11 w-auto"
alt="Reflector"
/>
<div className="flex-col ml-3 mt-4">
<h1 className="text-[28px] font-semibold leading-tight text-left">
Reflector
</h1>
<p className="text-gray-500 text-xs tracking-tight -mt-1">
Capture the signal, not the noise
</p>
</div>
</Link>
</div>
</div>
</div>
);
}