Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions apps/web/app/(app)/[emailAccountId]/assistant/AssistantTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,6 @@ export function AssistantTabs() {
)}
</TabsList>
</div>

{/* <div className="flex items-center gap-2">
<Button asChild variant="outline">
<Link
href={prefixPath(
emailAccountId,
"/assistant/onboarding",
)}
>
Set Up
</Link>
</Button>

<OnboardingModal
title="Getting started with AI Personal Assistant"
description={
<>
Learn how to use the AI Personal Assistant to
automatically label, archive, and more.
</>
}
videoId="SoeNDVr7ve4"
/>
</div> */}

<CloseArtifactButton />
</TabsToolbar>

Expand Down
5 changes: 2 additions & 3 deletions apps/web/app/(app)/[emailAccountId]/automation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default async function AutomationPage({
title: "Getting started with AI Personal Assistant",
description:
"Learn how to use the AI Personal Assistant to automatically label, archive, and more.",
videoId: "SoeNDVr7ve4",
muxPlaybackId: "VwIP7UAw4MXDjkvmLjJzGsY00ee9jxIZVI952DoBBfp8",
}}
/>
</div>
Expand Down Expand Up @@ -131,8 +131,7 @@ export default async function AutomationPage({
description={
"Learn how to use the AI Assistant to automatically label, archive, and more."
}
videoSrc="https://www.youtube.com/embed/SoeNDVr7ve4"
thumbnailSrc="https://img.youtube.com/vi/SoeNDVr7ve4/0.jpg"
muxPlaybackId="VwIP7UAw4MXDjkvmLjJzGsY00ee9jxIZVI952DoBBfp8"
storageKey="ai-assistant-onboarding-video"
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export function BulkUnsubscribe() {
.
</>
),
videoId: "T1rnooV4OYc",
youtubeVideoId: "T1rnooV4OYc",
}}
/>

Expand Down
71 changes: 48 additions & 23 deletions apps/web/components/OnboardingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useLocalStorage, useWindowSize } from "usehooks-ts";
import { PlayIcon } from "lucide-react";
import { useModal } from "@/hooks/useModal";
import { YouTubeVideo } from "@/components/YouTubeVideo";
import { MuxVideo } from "@/components/MuxVideo";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -17,12 +18,14 @@ import {
export function OnboardingModal({
title,
description,
videoId,
youtubeVideoId,
muxPlaybackId,
buttonProps,
}: {
title: string;
description: React.ReactNode;
videoId: string;
youtubeVideoId?: string;
muxPlaybackId?: string;
buttonProps?: React.ComponentProps<typeof Button>;
}) {
const { isModalOpen, openModal, setIsModalOpen } = useModal();
Expand All @@ -39,7 +42,8 @@ export function OnboardingModal({
setIsModalOpen={setIsModalOpen}
title={title}
description={description}
videoId={videoId}
youtubeVideoId={youtubeVideoId}
muxPlaybackId={muxPlaybackId}
/>
</>
);
Expand All @@ -50,20 +54,23 @@ export function OnboardingModalDialog({
setIsModalOpen,
title,
description,
videoId,
youtubeVideoId,
muxPlaybackId,
}: {
isModalOpen: boolean;
setIsModalOpen: (open: boolean) => void;
title: string;
description: React.ReactNode;
videoId: string;
youtubeVideoId?: string;
muxPlaybackId?: string;
}) {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<OnboardingDialogContent
title={title}
description={description}
videoId={videoId}
youtubeVideoId={youtubeVideoId}
muxPlaybackId={muxPlaybackId}
/>
</Dialog>
);
Expand All @@ -72,37 +79,55 @@ export function OnboardingModalDialog({
export function OnboardingDialogContent({
title,
description,
videoId,
youtubeVideoId,
muxPlaybackId,
}: {
title: string;
description: React.ReactNode;
videoId: string;
youtubeVideoId?: string;
muxPlaybackId?: string;
}) {
const { width } = useWindowSize();

const videoWidth = Math.min(width * 0.75, 1200);
const videoHeight = videoWidth * (675 / 1200);

return (
<DialogContent className="min-w-[350px] sm:min-w-[600px] md:min-w-[750px] lg:min-w-[880px] xl:min-w-[1280px]">
<DialogHeader>
<DialogContent className="max-w-6xl border-0 bg-transparent p-0 overflow-hidden">
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>

<YouTubeVideo
videoId={videoId}
title={`Onboarding video - ${title}`}
iframeClassName="mx-auto"
opts={{
height: `${videoHeight}`,
width: `${videoWidth}`,
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1,
},
}}
/>
{muxPlaybackId ? (
<div className="relative aspect-video w-full overflow-hidden rounded-lg">
<MuxVideo
playbackId={muxPlaybackId}
title={`Onboarding video - ${title}`}
className="size-full"
/>
</div>
) : youtubeVideoId ? (
<div className="bg-background rounded-lg p-6">
<div className="mb-4">
<h2 className="text-xl font-semibold">{title}</h2>
<p className="text-muted-foreground">{description}</p>
</div>
<YouTubeVideo
videoId={youtubeVideoId}
title={`Onboarding video - ${title}`}
iframeClassName="mx-auto"
opts={{
height: `${videoHeight}`,
width: `${videoWidth}`,
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1,
},
}}
/>
</div>
) : null}
</DialogContent>
);
}
Expand Down
10 changes: 7 additions & 3 deletions apps/web/components/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { PlayIcon } from "lucide-react";
type Video = {
title: string;
description: React.ReactNode;
videoId: string;
youtubeVideoId?: string;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both youtubeVideoId and muxPlaybackId are optional, allowing a video entry with neither ID; the dialog then renders nothing, resulting in a blank modal. Enforce at least one ID (union/discriminated type) or gate rendering of WatchVideo when no ID is present.

Prompt for AI agents
Address the following comment on apps/web/components/PageHeader.tsx at line 10:

<comment>Both youtubeVideoId and muxPlaybackId are optional, allowing a video entry with neither ID; the dialog then renders nothing, resulting in a blank modal. Enforce at least one ID (union/discriminated type) or gate rendering of WatchVideo when no ID is present.</comment>

<file context>
@@ -7,7 +7,8 @@ import { PlayIcon } from &quot;lucide-react&quot;;
   title: string;
   description: React.ReactNode;
-  videoId: string;
+  youtubeVideoId?: string;
+  muxPlaybackId?: string;
 };
</file context>

✅ Addressed in 99f610b

muxPlaybackId?: string;
};

export function PageHeader({
Expand All @@ -24,7 +25,9 @@ export function PageHeader({
<PageHeading>{title}</PageHeading>
<div className="flex items-center mt-1">
<PageSubHeading>{description}</PageSubHeading>
{video && <WatchVideo video={video} />}
{video && (video.youtubeVideoId || video.muxPlaybackId) && (
<WatchVideo video={video} />
)}
</div>
</div>
);
Expand All @@ -42,7 +45,8 @@ function WatchVideo({ video }: { video: Video }) {
<OnboardingDialogContent
title={video.title}
description={video.description}
videoId={video.videoId}
youtubeVideoId={video.youtubeVideoId}
muxPlaybackId={video.muxPlaybackId}
/>
</Dialog>
);
Expand Down
45 changes: 33 additions & 12 deletions apps/web/components/VideoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, { useState, useEffect } from "react";
import type { ComponentProps } from "react";
import Image from "next/image";
import MuxPlayer from "@mux/mux-player-react";
import { PlayIcon, X } from "lucide-react";
import { CardGreen } from "@/components/ui/card";
import {
Expand All @@ -12,6 +13,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { ClientOnly } from "@/components/ClientOnly";

type VideoCardProps = ComponentProps<typeof VideoCard> & {
storageKey: string;
Expand Down Expand Up @@ -45,8 +47,9 @@ const VideoCard = React.forwardRef<
icon?: React.ReactNode;
title: string;
description: string;
videoSrc: string;
thumbnailSrc: string;
videoSrc?: string;
thumbnailSrc?: string;
muxPlaybackId?: string;
onClose?: () => void;
}
>(
Expand All @@ -58,6 +61,7 @@ const VideoCard = React.forwardRef<
description,
videoSrc,
thumbnailSrc,
muxPlaybackId,
onClose,
...props
},
Expand Down Expand Up @@ -112,7 +116,11 @@ const VideoCard = React.forwardRef<
>
<div className="relative w-32 h-20 rounded-lg overflow-hidden bg-gray-100 dark:bg-gray-800">
<Image
src={thumbnailSrc}
src={
muxPlaybackId
? `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg`
: thumbnailSrc || "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="
}
alt={title}
fill
className="object-cover transition-all duration-200 group-hover:scale-105"
Expand All @@ -126,16 +134,29 @@ const VideoCard = React.forwardRef<
</div>
</button>
</DialogTrigger>
<DialogContent className="max-w-4xl border-0 bg-transparent p-0">
<DialogContent className="max-w-6xl border-0 bg-transparent p-0 overflow-hidden">
<DialogTitle className="sr-only">Video: {title}</DialogTitle>
<div className="relative aspect-video w-full">
<iframe
src={`${videoSrc}${videoSrc.includes("?") ? "&" : "?"}autoplay=1&rel=0`}
className="size-full rounded-lg"
title={`Video: ${title}`}
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
<div className="relative aspect-video w-full overflow-hidden rounded-lg">
{muxPlaybackId ? (
<ClientOnly>
<MuxPlayer
playbackId={muxPlaybackId}
metadata={{ video_title: title }}
accentColor="#3b82f6"
className="size-full rounded-lg"
style={{ overflow: "hidden" }}
autoPlay
/>
</ClientOnly>
) : (
<iframe
src={`${videoSrc}${videoSrc?.includes("?") ? "&" : "?"}autoplay=1&rel=0`}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If videoSrc is undefined, iframe src becomes an invalid URL (e.g., "undefined?autoplay=1..."), breaking playback.

Prompt for AI agents
Address the following comment on apps/web/components/VideoCard.tsx at line 153:

<comment>If videoSrc is undefined, iframe src becomes an invalid URL (e.g., &quot;undefined?autoplay=1...&quot;), breaking playback.</comment>

<file context>
@@ -126,16 +134,29 @@ const VideoCard = React.forwardRef&lt;
+                      &lt;/ClientOnly&gt;
+                    ) : (
+                      &lt;iframe
+                        src={`${videoSrc}${videoSrc?.includes(&quot;?&quot;) ? &quot;&amp;&quot; : &quot;?&quot;}autoplay=1&amp;rel=0`}
+                        className=&quot;size-full rounded-lg&quot;
+                        title={`Video: ${title}`}
</file context>
Fix with Cubic

className="size-full rounded-lg"
title={`Video: ${title}`}
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
)}
</div>
</DialogContent>
</Dialog>
Expand Down
4 changes: 4 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "img.youtube.com",
},
{
protocol: "https",
hostname: "image.mux.com",
},
{
protocol: "https",
hostname: "ph-avatars.imgix.net",
Expand Down
Loading