Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 2 additions & 9 deletions apps/web/app/s/[videoId]/Share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ShareVideo } from "./_components/ShareVideo";
import { Sidebar } from "./_components/Sidebar";
import SummaryChapters from "./_components/SummaryChapters";
import { Toolbar } from "./_components/Toolbar";
import type { VideoData } from "./types";

type CommentWithAuthor = typeof commentsSchema.$inferSelect & {
authorName: string | null;
Expand All @@ -34,16 +35,8 @@ export type CommentType = typeof commentsSchema.$inferSelect & {
sending?: boolean;
};

type VideoWithOrganizationInfo = typeof videos.$inferSelect & {
organizationMembers?: string[];
organizationId?: string;
sharedOrganizations?: { id: string; name: string }[];
hasPassword?: boolean;
orgSettings?: OrganizationSettings | null;
};

interface ShareProps {
data: VideoWithOrganizationInfo;
data: VideoData;
comments: MaybePromise<CommentWithAuthor[]>;
views: MaybePromise<number>;
customDomain: string | null;
Expand Down
112 changes: 0 additions & 112 deletions apps/web/app/s/[videoId]/_components/ImageViewer.tsx

This file was deleted.

29 changes: 12 additions & 17 deletions apps/web/app/s/[videoId]/_components/ShareHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use client";

import type { userSelectProps } from "@cap/database/auth/session";
import type { videos } from "@cap/database/schema";
import { buildEnv, NODE_ENV } from "@cap/env";
import { Button } from "@cap/ui";
import { userIsPro } from "@cap/utils";
import type { ImageUpload } from "@cap/web-domain";
import { type ImageUpload, User } from "@cap/web-domain";
import { faChevronDown, faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Check, Copy, Globe2 } from "lucide-react";
Expand All @@ -21,6 +19,7 @@ import { useCurrentUser } from "@/app/Layout/AuthContext";
import { SignedImageUrl } from "@/components/SignedImageUrl";
import { UpgradeModal } from "@/components/UpgradeModal";
import { usePublicEnv } from "@/utils/public-env";
import type { VideoData } from "../types";

export const ShareHeader = ({
data,
Expand All @@ -30,11 +29,7 @@ export const ShareHeader = ({
sharedSpaces = [],
spacesData = null,
}: {
data: typeof videos.$inferSelect & {
ownerName?: string | null;
ownerImage?: ImageUpload.ImageUrl | null;
ownerIsPro?: boolean;
};
data: VideoData;
customDomain?: string | null;
domainVerified?: boolean;
sharedOrganizations?: { id: string; name: string }[];
Expand Down Expand Up @@ -65,7 +60,7 @@ export const ShareHeader = ({
const contextSharedSpaces = contextData?.sharedSpaces || null;
const effectiveSharedSpaces = contextSharedSpaces || sharedSpaces;

const isOwner = user && user.id === data.ownerId;
const isOwner = user && user.id === data.owner.id;

const { webUrl } = usePublicEnv();

Expand Down Expand Up @@ -132,8 +127,6 @@ export const ShareHeader = ({
}
};

const isVideoOwnerPro: boolean = data.ownerIsPro ?? false;

const handleSharingUpdated = () => {
refresh();
};
Expand Down Expand Up @@ -182,9 +175,11 @@ export const ShareHeader = ({
}
};

const userIsOwnerAndNotPro = user?.id === data.owner.id && !data.owner.isPro;

return (
<>
{isOwner && !isVideoOwnerPro && (
{userIsOwnerAndNotPro && (
<div className="flex sticky flex-col sm:flex-row inset-x-0 top-0 z-10 gap-4 justify-center items-center px-3 py-2 mx-auto w-[calc(100%-20px)] max-w-fit rounded-b-xl border bg-gray-4 border-gray-6">
<p className="text-center text-gray-12">
Shareable links are limited to 5 mins on the free plan.
Expand Down Expand Up @@ -238,16 +233,16 @@ export const ShareHeader = ({
</div>
<div className="flex gap-7 items-center">
<div className="flex gap-2 items-center">
{data.ownerName && (
{data.name && (
<SignedImageUrl
name={data.ownerName}
image={data.ownerImage}
name={data.name}
image={data.owner.image}
className="size-8"
letterClass="text-base"
/>
)}
<div className="flex flex-col text-left">
<p className="text-sm text-gray-12">{data.ownerName}</p>
<p className="text-sm text-gray-12">{data.name}</p>
<p className="text-xs text-gray-10">
Comment on lines 235 to 246
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix owner display: using video title instead of owner name

SignedImageUrl and the text below use data.name (video title). They should use the owner’s name and image.

- {data.name && (
+ {data.owner?.name && (
    <SignedImageUrl
-     name={data.name}
-     image={data.owner.image}
+     name={data.owner.name}
+     image={data.owner.image}
      className="size-8"
      letterClass="text-base"
    />
  )}
...
- <p className="text-sm text-gray-12">{data.name}</p>
+ <p className="text-sm text-gray-12">{data.owner?.name ?? "Unknown user"}</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex gap-2 items-center">
{data.ownerName && (
{data.name && (
<SignedImageUrl
name={data.ownerName}
image={data.ownerImage}
name={data.name}
image={data.owner.image}
className="size-8"
letterClass="text-base"
/>
)}
<div className="flex flex-col text-left">
<p className="text-sm text-gray-12">{data.ownerName}</p>
<p className="text-sm text-gray-12">{data.name}</p>
<p className="text-xs text-gray-10">
<div className="flex gap-2 items-center">
{data.owner?.name && (
<SignedImageUrl
name={data.owner.name}
image={data.owner.image}
className="size-8"
letterClass="text-base"
/>
)}
<div className="flex flex-col text-left">
<p className="text-sm text-gray-12">{data.owner?.name ?? "Unknown user"}</p>
<p className="text-xs text-gray-10">
🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/_components/ShareHeader.tsx around lines 235 to 246,
the owner UI is incorrectly using data.name (the video title); change the
SignedImageUrl component prop and the text elements to use the owner's name
(data.owner.name) instead, and add a safe guard (e.g. data.owner?.name and
data.owner?.image) so the image and name render only when owner exists.

{moment(data.createdAt).fromNow()}
</p>
Expand Down Expand Up @@ -285,7 +280,7 @@ export const ShareHeader = ({
)}
</Button>
</div>
{!isVideoOwnerPro && (
{userIsOwnerAndNotPro && (
<button
type="button"
className="flex items-center mt-2 mb-3 text-sm text-gray-400 duration-200 cursor-pointer hover:text-blue-500"
Expand Down
15 changes: 7 additions & 8 deletions apps/web/app/s/[videoId]/_components/ShareVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "react";
import type { OrganizationSettings } from "@/app/(org)/dashboard/dashboard-data";
import { UpgradeModal } from "@/components/UpgradeModal";
import type { VideoData } from "../types";
import { CapVideoPlayer } from "./CapVideoPlayer";
import { HLSVideoPlayer } from "./HLSVideoPlayer";
import {
Expand All @@ -36,10 +37,8 @@ type CommentWithAuthor = typeof commentsSchema.$inferSelect & {
export const ShareVideo = forwardRef<
HTMLVideoElement,
{
data: typeof videos.$inferSelect & {
ownerIsPro?: boolean;
data: VideoData & {
hasActiveUpload?: boolean;
orgSettings?: OrganizationSettings | null;
};
comments: MaybePromise<CommentWithAuthor[]>;
chapters?: { title: string; start: number }[];
Expand Down Expand Up @@ -166,19 +165,19 @@ export const ShareVideo = forwardRef<
let enableCrossOrigin = false;

if (data.source.type === "desktopMP4") {
videoSrc = `/api/playlist?userId=${data.ownerId}&videoId=${data.id}&videoType=mp4`;
videoSrc = `/api/playlist?userId=${data.owner.id}&videoId=${data.id}&videoType=mp4`;
// Start with CORS enabled for desktopMP4, but CapVideoPlayer will dynamically disable if needed
enableCrossOrigin = true;
} else if (
NODE_ENV === "development" ||
((data.skipProcessing === true || data.jobStatus !== "COMPLETE") &&
data.source.type === "MediaConvert")
) {
videoSrc = `/api/playlist?userId=${data.ownerId}&videoId=${data.id}&videoType=master`;
videoSrc = `/api/playlist?userId=${data.owner.id}&videoId=${data.id}&videoType=master`;
} else if (data.source.type === "MediaConvert") {
videoSrc = `/api/playlist?userId=${data.ownerId}&videoId=${data.id}&videoType=video`;
videoSrc = `/api/playlist?userId=${data.owner.id}&videoId=${data.id}&videoType=video`;
} else {
videoSrc = `/api/playlist?userId=${data.ownerId}&videoId=${data.id}&videoType=video`;
videoSrc = `/api/playlist?userId=${data.owner.id}&videoId=${data.id}&videoType=video`;
}

return (
Expand Down Expand Up @@ -221,7 +220,7 @@ export const ShareVideo = forwardRef<
)}
</div>

{!data.ownerIsPro && (
{!data.owner.isPro && (
<div className="absolute top-4 left-4 z-30">
<div
className="block cursor-pointer"
Expand Down
20 changes: 7 additions & 13 deletions apps/web/app/s/[videoId]/_components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AnimatePresence, motion } from "framer-motion";
import { forwardRef, Suspense, useState } from "react";
import type { OrganizationSettings } from "@/app/(org)/dashboard/dashboard-data";
import { useCurrentUser } from "@/app/Layout/AuthContext";
import type { VideoData } from "../types";
import { Activity } from "./tabs/Activity";
import { Settings } from "./tabs/Settings";
import { Summary } from "./tabs/Summary";
Expand All @@ -19,14 +20,8 @@ type CommentType = typeof commentsSchema.$inferSelect & {
authorImage?: ImageUpload.ImageUrl | null;
};

type VideoWithOrganizationInfo = typeof videos.$inferSelect & {
organizationMembers?: string[];
organizationId?: string;
orgSettings?: OrganizationSettings | null;
};

interface SidebarProps {
data: VideoWithOrganizationInfo;
data: VideoData;
commentsData: CommentType[];
optimisticComments: CommentType[];
handleCommentSuccess: (comment: CommentType) => void;
Expand Down Expand Up @@ -91,10 +86,9 @@ export const Sidebar = forwardRef<{ scrollToBottom: () => void }, SidebarProps>(
) => {
const user = useCurrentUser();

const isOwnerOrMember: boolean = Boolean(
user?.id === data.ownerId ||
(data.organizationId &&
data.organizationMembers?.includes(user?.id ?? "")),
const isOwnerOrMember = Boolean(
user?.id === data.owner.id ||
(user && data.organizationMembers?.includes(user.id)),
);

const defaultTab = !(
Expand All @@ -106,7 +100,7 @@ export const Sidebar = forwardRef<{ scrollToBottom: () => void }, SidebarProps>(
: !(
videoSettings?.disableTranscript ??
data.orgSettings?.disableTranscript
)
)
? "transcript"
: "activity";

Expand Down Expand Up @@ -182,7 +176,7 @@ export const Sidebar = forwardRef<{ scrollToBottom: () => void }, SidebarProps>(
/>
);
case "transcript":
return <Transcript data={data} onSeek={onSeek} user={user} />;
return <Transcript data={data} onSeek={onSeek} />;
case "settings":
return <Settings />;
default:
Expand Down
10 changes: 3 additions & 7 deletions apps/web/app/s/[videoId]/_components/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import type { userSelectProps } from "@cap/database/auth/session";
import type { videos } from "@cap/database/schema";
import { Button } from "@cap/ui";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Likely missing "use client" directive for a hook-using component

This file uses useEffect and DOM APIs; it should be a Client Component. Add the directive at the top to avoid build/runtime errors.

+ "use client";

Run the script below to confirm which of these components are missing the directive.


🏁 Script executed:

#!/bin/bash
rg -n --glob 'apps/web/app/s/**/_components/{Toolbar,Sidebar,ShareVideo}.tsx' -C0 -e '^\s*"use client";' || true

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find all _components files with matching names
fd -t f '\.(Toolbar|Sidebar|ShareVideo)\.tsx$' apps/web/app/s

Length of output: 0


🏁 Script executed:

#!/bin/bash
# If files exist, check their first 10 lines for "use client"
for file in $(fd -t f '\.(Toolbar|Sidebar|ShareVideo)\.tsx$' apps/web/app/s 2>/dev/null || true); do
  echo "=== File: $file ==="
  head -5 "$file"
  echo ""
done

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for the components more broadly
fd -t f 'Toolbar\.tsx|Sidebar\.tsx|ShareVideo\.tsx' apps/web/

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Also check if the directory structure exists
ls -la apps/web/app/s/ 2>&1 || echo "Directory does not exist"

Length of output: 294


🏁 Script executed:

#!/bin/bash
# Find all _components directories
fd -t d '_components' apps/web/app/

Length of output: 197


🏁 Script executed:

#!/bin/bash
# Check first 5 lines of each component file for "use client" directive
for file in apps/web/app/s/[videoId]/_components/Toolbar.tsx apps/web/app/s/[videoId]/_components/Sidebar.tsx apps/web/app/s/[videoId]/_components/ShareVideo.tsx; do
  echo "=== $file ==="
  head -5 "$file"
  echo ""
done

Length of output: 981


🏁 Script executed:

#!/bin/bash
# Check for React hook imports in all three files
for file in apps/web/app/s/[videoId]/_components/Toolbar.tsx apps/web/app/s/[videoId]/_components/Sidebar.tsx apps/web/app/s/[videoId]/_components/ShareVideo.tsx; do
  echo "=== $file ==="
  grep -E "import.*\{.*use[A-Z]" "$file" || echo "(no hook imports found)"
  echo ""
done

Length of output: 466


🏁 Script executed:

#!/bin/bash
# Also check the parent page component to understand the hierarchy
cat apps/web/app/s/[videoId]/page.tsx

Length of output: 22149


Add "use client" directive to all three Client Components using React hooks

All three components use React hooks and are missing the directive:

  • Toolbar.tsx: uses useEffect, useState, useCurrentUser
  • Sidebar.tsx: uses useState, useCurrentUser
  • ShareVideo.tsx: uses useTranscript

Add "use client"; at the top of each file:

+ "use client";
  import { Button } from "@cap/ui";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Button } from "@cap/ui";
"use client";
import { Button } from "@cap/ui";
🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/_components/Toolbar.tsx around line 1, add the React
client directive: insert "use client"; as the very first line of the file
(before any imports) because this component uses useEffect, useState and
useCurrentUser; similarly add "use client"; as the first line of
apps/web/app/s/[videoId]/_components/Sidebar.tsx (it uses useState and
useCurrentUser) and as the first line of
apps/web/app/s/[videoId]/_components/ShareVideo.tsx (it uses useTranscript) so
all three are explicit client components and hooks work correctly.

import { Comment, User } from "@cap/web-domain";
import { Comment } from "@cap/web-domain";
import { AnimatePresence, motion } from "motion/react";
import { startTransition, useEffect, useState } from "react";
import { newComment } from "@/actions/videos/new-comment";
import type { OrganizationSettings } from "@/app/(org)/dashboard/dashboard-data";
import { useCurrentUser } from "@/app/Layout/AuthContext";
import type { CommentType } from "../Share";
import type { VideoData } from "../types";
import { AuthOverlay } from "./AuthOverlay";

const MotionButton = motion.create(Button);

// million-ignore
interface ToolbarProps {
data: typeof videos.$inferSelect & {
orgSettings?: OrganizationSettings | null;
};
data: VideoData;
onOptimisticComment?: (comment: CommentType) => void;
onCommentSuccess?: (comment: CommentType) => void;
disableReactions?: boolean;
Expand Down
Loading
Loading