Skip to content

Commit c292253

Browse files
authored
dd: feat: capacity-check (#478)
* dd: feat: capacity check * remove debug value * added debug flag * polish * explore capacity wall * copy + ui changes * copy * copy + fixes + css * hubspot integration
1 parent a5ed19e commit c292253

File tree

18 files changed

+1082
-191
lines changed

18 files changed

+1082
-191
lines changed

apps/app/app/(main)/BentoGrids.tsx

+8-11
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ export interface BentoGridsProps {
3535
isOverlayMode?: boolean;
3636
onTryPrompt?: (prompt: string) => void;
3737
onClipsLoaded?: (clips: any[]) => void;
38+
hasCapacity?: boolean;
3839
}
3940

4041
export function BentoGrids({
4142
initialClips,
4243
isOverlayMode = false,
4344
onTryPrompt,
4445
onClipsLoaded,
46+
hasCapacity = true,
4547
}: BentoGridsProps) {
4648
const { clips, loading, hasMore, fetchClips, page } =
4749
useClipsFetcher(initialClips);
@@ -194,6 +196,7 @@ export function BentoGrids({
194196
overallIndex={overallIndex}
195197
isOverlayMode={isOverlayMode}
196198
onTryPrompt={onTryPrompt}
199+
hasCapacity={hasCapacity}
197200
/>
198201
);
199202
})}
@@ -234,6 +237,7 @@ function GridItem({
234237
overallIndex,
235238
isOverlayMode = false,
236239
onTryPrompt,
240+
hasCapacity,
237241
}: {
238242
clipId: string;
239243
src: string;
@@ -249,6 +253,7 @@ function GridItem({
249253
isOverlayMode?: boolean;
250254
authorDetails?: Record<string, any>;
251255
onTryPrompt?: (prompt: string) => void;
256+
hasCapacity?: boolean;
252257
}) {
253258
const href = slug ? `/clip/${slug}` : `/clip/id/${clipId}`;
254259
const { isPreviewOpen } = usePreviewStore();
@@ -266,20 +271,11 @@ function GridItem({
266271
};
267272

268273
return (
269-
<TrackedLink
270-
href={href}
271-
trackingEvent="explore_clip_clicked"
272-
trackingProperties={{
273-
clip_id: clipId,
274-
clip_slug: slug,
275-
clip_author_name: authorName,
276-
location: isOverlayMode ? "overlay_bento_grid" : "explore_bento_grid",
277-
}}
274+
<div
278275
className={cn(
279276
"relative aspect-square lg:min-h-[300px] lg:aspect-auto block",
280277
className,
281278
)}
282-
onClick={isOverlayMode ? handleClick : undefined}
283279
>
284280
{isDebug && (
285281
<div className="absolute top-1 left-1 z-40 bg-black/60 text-white text-xs font-mono px-1.5 py-0.5 rounded">
@@ -302,11 +298,12 @@ function GridItem({
302298
remixCount={remixCount}
303299
isOverlayMode={isOverlayMode}
304300
onTryPrompt={onTryPrompt}
301+
hasCapacity={hasCapacity}
305302
/>
306303
</div>
307304

308305
<div className="pointer-events-none absolute inset-px rounded-xl shadow ring-1 ring-black/5 z-30"></div>
309-
</TrackedLink>
306+
</div>
310307
);
311308
}
312309

apps/app/app/(main)/Header.tsx

+44-14
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@ import { Logo } from "@/components/sidebar";
44
import { TrackedButton } from "@/components/analytics/TrackedButton";
55
import { usePreviewStore } from "@/hooks/usePreviewStore";
66
import { cn } from "@repo/design-system/lib/utils";
7-
import Link from "next/link";
87
import { useEffect, useState } from "react";
98
import { DiscordLogoIcon } from "@radix-ui/react-icons";
109
import VideoAISparkles from "components/daydream/CustomIcons/VideoAISparkles";
10+
import { useCapacityCheck } from "@/hooks/useCapacityCheck";
11+
import { CapacityNotificationModal } from "@/components/modals/capacity-notification-modal";
12+
import { useRouter } from "next/navigation";
13+
import track from "@/lib/track";
14+
import Link from "next/link";
1115

1216
export default function Header() {
1317
const { isPreviewOpen } = usePreviewStore();
1418
const [scrolled, setScrolled] = useState(false);
19+
const { hasCapacity } = useCapacityCheck();
20+
const [isCapacityModalOpen, setIsCapacityModalOpen] = useState(false);
21+
const router = useRouter();
1522

1623
useEffect(() => {
1724
const handleScroll = () => {
@@ -24,6 +31,17 @@ export default function Header() {
2431
};
2532
}, []);
2633

34+
const handleCreateClick = (e: React.MouseEvent) => {
35+
e.preventDefault();
36+
37+
if (!hasCapacity) {
38+
track("capacity_create_blocked", { location: "header" });
39+
setIsCapacityModalOpen(true);
40+
} else {
41+
router.push("/create");
42+
}
43+
};
44+
2745
return (
2846
<>
2947
{/* Custom style to override alwaysAnimatedButton for the circular floating button */}
@@ -46,26 +64,33 @@ export default function Header() {
4664

4765
<header
4866
className={cn(
49-
"bg-transparent sticky top-0 z-40 transition-colors duration-300 ease-in-out",
67+
"bg-transparent sticky top-0 z-50 transition-colors duration-300 ease-in-out",
5068
scrolled && "backdrop-filter backdrop-blur-xl bg-opacity-50",
5169
)}
5270
>
5371
<nav
5472
aria-label="Global"
55-
className="mx-auto flex items-center justify-between py-4 px-4 sm:px-6 lg:px-8"
73+
className="mx-auto flex items-center justify-between py-4 px-6 lg:px-8"
5674
>
5775
<div className="flex flex-1 items-center">
5876
<a href="#" className="-m-1.5 p-1.5 flex items-center">
5977
<span className="sr-only">Daydream by Livepeer</span>
6078
<Logo />
6179
</a>
6280
</div>
63-
81+
6482
{/* Centered Beta Badge */}
6583
<div className="flex-1 flex justify-center items-center">
66-
<a href="https://livepeer.notion.site/15f0a348568781aab037c863d91b05e2" target="_blank" rel="noopener noreferrer" className="flex items-center px-4 py-1 rounded-full border border-blue-200 bg-white/70 backdrop-blur-sm text-blue-600 text-sm font-medium gap-2 shadow-sm hover:bg-white/90 transition-colors">
84+
<a
85+
href="https://livepeer.notion.site/15f0a348568781aab037c863d91b05e2"
86+
target="_blank"
87+
rel="noopener noreferrer"
88+
className="flex items-center px-4 py-1 rounded-full border border-blue-200 bg-white/70 backdrop-blur-sm text-blue-600 text-sm font-medium gap-2 shadow-sm hover:bg-white/90 transition-colors"
89+
>
6790
<span className="inline-block w-2 h-2 rounded-full bg-blue-500"></span>
68-
<span className="sm:inline hidden">We&apos;re in beta. Send us your feedback and ideas →</span>
91+
<span className="sm:inline hidden">
92+
We&apos;re in beta. Send us your feedback and ideas →
93+
</span>
6994
<span className="sm:hidden inline">Beta</span>
7095
</a>
7196
</div>
@@ -88,7 +113,7 @@ export default function Header() {
88113
<span className="hidden sm:inline ml-2">Join Discord</span>
89114
</TrackedButton>
90115
{/* Desktop-only Create button */}
91-
<Link href="/create" className="hidden sm:block ml-4">
116+
<div className="hidden sm:block ml-4" onClick={handleCreateClick}>
92117
<TrackedButton
93118
trackingEvent="explore_header_start_creating_clicked"
94119
trackingProperties={{ location: "explore_header" }}
@@ -102,17 +127,16 @@ export default function Header() {
102127
<VideoAISparkles className={cn("text-black !w-10 !h-10")} />{" "}
103128
Create
104129
</TrackedButton>
105-
</Link>
130+
</div>
106131
</div>
107132
</nav>
108133
</header>
109134

110135
{/* Mobile-only floating Create button */}
111-
<Link
112-
href="/create"
136+
<div
113137
className={cn(
114138
"fixed bottom-6 right-6 sm:hidden z-50",
115-
isPreviewOpen && "opacity-0 pointer-events-none",
139+
(isPreviewOpen || !hasCapacity) && "opacity-0 pointer-events-none",
116140
)}
117141
>
118142
<div className="rounded-full floating-shadow">
@@ -121,15 +145,21 @@ export default function Header() {
121145
trackingProperties={{ location: "explore_header_mobile_fab" }}
122146
size="lg"
123147
className={cn(
124-
"!rounded-full h-16 w-16 text-black flex items-center justify-center p-0",
148+
"!rounded-full h-20 w-20 text-black flex items-center justify-center p-0",
125149
"alwaysAnimatedButton circular-animated-button forced-white-bg",
126150
isPreviewOpen && "opacity-0 pointer-events-none",
127151
)}
152+
onClick={handleCreateClick}
128153
>
129-
<VideoAISparkles className={cn("text-black !w-12 !h-12")} />
154+
<VideoAISparkles className={cn("text-black !w-16 !h-16")} />
130155
</TrackedButton>
131156
</div>
132-
</Link>
157+
</div>
158+
159+
<CapacityNotificationModal
160+
isOpen={isCapacityModalOpen}
161+
onClose={() => setIsCapacityModalOpen(false)}
162+
/>
133163
</>
134164
);
135165
}

apps/app/app/(main)/MainLayoutClient.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
"use client";
22

3-
import { usePathname } from "next/navigation";
43
import { BentoGrids, BentoGridsProps } from "./BentoGrids";
54
import { PageViewTracker } from "@/components/analytics/PageViewTracker";
5+
import { CapacityToast } from "@/components/modals/capacity-toast";
6+
import { useCapacityCheck } from "@/hooks/useCapacityCheck";
7+
import { useCapacityToastStore } from "@/hooks/useCapacityToast";
8+
import { usePathname } from "next/navigation";
9+
import { useEffect } from "react";
610

711
export default function MainLayoutClient({
812
initialClips,
913
children,
1014
}: BentoGridsProps & { children: React.ReactNode }) {
15+
const { hasCapacity } = useCapacityCheck();
16+
const pathname = usePathname();
17+
const { resetToastState } = useCapacityToastStore();
18+
19+
useEffect(() => {
20+
if (pathname === "/") {
21+
resetToastState();
22+
}
23+
}, [pathname, resetToastState]);
24+
1125
return (
1226
<>
1327
<PageViewTracker eventName="explore_page_viewed" />
14-
<BentoGrids initialClips={initialClips} />
28+
<CapacityToast path="/" hasCapacity={hasCapacity} />
29+
<BentoGrids initialClips={initialClips} hasCapacity={hasCapacity} />
1530
{children}
1631
</>
1732
);

0 commit comments

Comments
 (0)