Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ae1393b
wip
ameer2468 Sep 24, 2025
792ca43
Merge branch 'main' into org-settings
ameer2468 Sep 25, 2025
12e7ae4
wip
ameer2468 Sep 25, 2025
c1a9f9a
Merge branch 'main' into org-settings
ameer2468 Sep 26, 2025
938e7b8
Merge branch 'main' into org-settings
ameer2468 Sep 26, 2025
c31a74b
wip
ameer2468 Sep 26, 2025
93a1aa0
Merge branch 'main' into org-settings
ameer2468 Sep 26, 2025
359d5cf
wip
ameer2468 Sep 26, 2025
f4f60e9
Merge branch 'main' into org-settings
ameer2468 Sep 29, 2025
9efe4ae
more settings
ameer2468 Sep 29, 2025
19dae6d
setup org-wide settings and more
ameer2468 Sep 29, 2025
235339f
cleanup
ameer2468 Sep 29, 2025
83395dd
Merge branch 'main' into org-settings
ameer2468 Oct 3, 2025
85aab08
Merge branch 'main' into org-settings
ameer2468 Oct 6, 2025
91a634f
edge cases, handle backend and stop transcript generation, ui enable/…
ameer2468 Oct 6, 2025
2e7523d
text
ameer2468 Oct 6, 2025
339548c
show download button for non-owners
ameer2468 Oct 6, 2025
ce30f6b
Merge branch 'main' into org-settings
richiemcilroy Oct 6, 2025
7eb43f3
skipped transcription status and conditionally render chapters and su…
ameer2468 Oct 7, 2025
820e86f
Merge branch 'main' into org-settings
ameer2468 Oct 7, 2025
bade5f6
Update _journal.json
ameer2468 Oct 7, 2025
10bcd08
Update pnpm-lock.yaml
ameer2468 Oct 7, 2025
0bd11fa
switches toggled by default
ameer2468 Oct 7, 2025
e319cb7
review points
ameer2468 Oct 7, 2025
a4429f7
cleanup
ameer2468 Oct 7, 2025
6f8d603
better messaging
ameer2468 Oct 7, 2025
f6614dd
description abit too long
ameer2468 Oct 7, 2025
1ee233e
fix label
ameer2468 Oct 7, 2025
734029f
make sure dialog resets, fix pro text color
ameer2468 Oct 7, 2025
86a13ec
Update SharedCaps.tsx
ameer2468 Oct 7, 2025
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
44 changes: 44 additions & 0 deletions apps/web/actions/organization/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use server";

import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import { organizations } from "@cap/database/schema";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

export async function updateOrganizationSettings(settings: {
disableSummary?: boolean;
disableCaptions?: boolean;
disableChapters?: boolean;
disableReactions?: boolean;
disableTranscript?: boolean;
disableComments?: boolean;
}) {
const user = await getCurrentUser();

if (!user) {
throw new Error("Unauthorized");
}

if (!settings) {
throw new Error("Settings are required");
}

const [organization] = await db()
.select()
.from(organizations)
.where(eq(organizations.id, user.activeOrganizationId));

if (!organization) {
throw new Error("Organization not found");
}

await db()
.update(organizations)
.set({ settings })
.where(eq(organizations.id, user.activeOrganizationId));

revalidatePath("/dashboard/caps");

return { success: true };
}
1 change: 0 additions & 1 deletion apps/web/actions/videos/generate-ai-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export async function generateAiMetadata(
const updatedAtTime = new Date(videoData.updatedAt).getTime();
const currentTime = new Date().getTime();
const tenMinutesInMs = 10 * 60 * 1000;
const minutesElapsed = Math.round((currentTime - updatedAtTime) / 60000);

if (currentTime - updatedAtTime > tenMinutesInMs) {
await db()
Expand Down
15 changes: 6 additions & 9 deletions apps/web/actions/videos/get-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { generateAiMetadata } from "./generate-ai-metadata";

const MAX_AI_PROCESSING_TIME = 10 * 60 * 1000;

type TranscriptionStatus = "PROCESSING" | "COMPLETE" | "ERROR" | "SKIPPED";

export interface VideoStatusResult {
transcriptionStatus: "PROCESSING" | "COMPLETE" | "ERROR" | null;
transcriptionStatus: TranscriptionStatus | null;
aiTitle: string | null;
aiProcessing: boolean;
summary: string | null;
Expand Down Expand Up @@ -124,10 +126,7 @@ export async function getVideoStatus(

return {
transcriptionStatus:
(updatedVideo.transcriptionStatus as
| "PROCESSING"
| "COMPLETE"
| "ERROR") || null,
(updatedVideo.transcriptionStatus as TranscriptionStatus) || null,
aiProcessing: false,
aiTitle: updatedMetadata.aiTitle || null,
summary: updatedMetadata.summary || null,
Expand Down Expand Up @@ -214,8 +213,7 @@ export async function getVideoStatus(

return {
transcriptionStatus:
(video.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") ||
null,
(video.transcriptionStatus as TranscriptionStatus) || null,
aiProcessing: true,
aiTitle: metadata.aiTitle || null,
summary: metadata.summary || null,
Expand All @@ -232,8 +230,7 @@ export async function getVideoStatus(

return {
transcriptionStatus:
(video.transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR") ||
null,
(video.transcriptionStatus as TranscriptionStatus) || null,
aiProcessing: metadata.aiProcessing || false,
aiTitle: metadata.aiTitle || null,
summary: metadata.summary || null,
Expand Down
45 changes: 45 additions & 0 deletions apps/web/actions/videos/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use server";

import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import { videos } from "@cap/database/schema";
import type { Video } from "@cap/web-domain";
import { eq } from "drizzle-orm";

export async function updateVideoSettings(
videoId: Video.VideoId,
videoSettings: {
disableSummary?: boolean;
disableCaptions?: boolean;
disableChapters?: boolean;
disableReactions?: boolean;
disableTranscript?: boolean;
disableComments?: boolean;
},
) {
const user = await getCurrentUser();

if (!user || !videoId || !videoSettings) {
throw new Error("Missing required data for updating video settings");
}

const [video] = await db()
.select()
.from(videos)
.where(eq(videos.id, videoId));

if (!video) {
throw new Error("Video not found for updating video settings");
}

if (video.ownerId !== user.id) {
throw new Error("You don't have permission to update this video settings");
}

await db()
.update(videos)
.set({ settings: videoSettings })
.where(eq(videos.id, videoId));

return { success: true };
}
11 changes: 10 additions & 1 deletion apps/web/app/(org)/dashboard/Contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import Cookies from "js-cookie";
import { usePathname } from "next/navigation";
import { createContext, useContext, useEffect, useState } from "react";
import { UpgradeModal } from "@/components/UpgradeModal";
import type { Organization, Spaces, UserPreferences } from "./dashboard-data";
import type {
Organization,
OrganizationSettings,
Spaces,
UserPreferences,
} from "./dashboard-data";

type SharedContext = {
organizationData: Organization[] | null;
activeOrganization: Organization | null;
organizationSettings: OrganizationSettings | null;
spacesData: Spaces[] | null;
userSpaces: Spaces[] | null;
sharedSpaces: Spaces[] | null;
Expand Down Expand Up @@ -50,6 +56,7 @@ export function DashboardContexts({
spacesData,
user,
isSubscribed,
organizationSettings,
userPreferences,
anyNewNotifications,
initialTheme,
Expand All @@ -62,6 +69,7 @@ export function DashboardContexts({
spacesData: SharedContext["spacesData"];
user: SharedContext["user"];
isSubscribed: SharedContext["isSubscribed"];
organizationSettings: SharedContext["organizationSettings"];
userPreferences: SharedContext["userPreferences"];
anyNewNotifications: boolean;
initialTheme: ITheme;
Expand Down Expand Up @@ -154,6 +162,7 @@ export function DashboardContexts({
spacesData,
anyNewNotifications,
userPreferences,
organizationSettings,
userSpaces,
sharedSpaces,
activeSpace,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(org)/dashboard/_components/MobileTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const MobileTab = () => {
}
});
return (
<div className="flex sticky bottom-0 z-50 flex-1 justify-between items-center px-5 w-screen h-16 border-t lg:hidden border-gray-5 bg-gray-1">
<div className="flex sticky bottom-0 z-50 flex-1 gap-5 justify-between items-center px-5 w-screen h-16 border-t lg:hidden border-gray-5 bg-gray-1">
<AnimatePresence>
{open && <OrgsMenu setOpen={setOpen} menuRef={menuRef} />}
</AnimatePresence>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
position="right"
content={activeOrg?.organization.name ?? "No organization found"}
>
<PopoverTrigger asChild>
<PopoverTrigger suppressHydrationWarning asChild>
<motion.div
transition={{
type: "easeInOut",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(org)/dashboard/caps/Caps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { CapPagination } from "./components/CapPagination";
import { EmptyCapState } from "./components/EmptyCapState";
import type { FolderDataType } from "./components/Folder";
import Folder from "./components/Folder";
import { useUploadingContext, useUploadingStatus } from "./UploadingContext";
import { useUploadingStatus } from "./UploadingContext";

export type VideoData = {
id: Video.VideoId;
Expand Down
110 changes: 62 additions & 48 deletions apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import type { VideoMetadata } from "@cap/database/types";
import { buildEnv, NODE_ENV } from "@cap/env";
import {
Expand All @@ -13,6 +15,7 @@ import {
faCopy,
faDownload,
faEllipsis,
faGear,
faLink,
faLock,
faTrash,
Expand Down Expand Up @@ -40,6 +43,7 @@ import {
import { useEffectMutation } from "@/lib/EffectRuntime";
import { withRpc } from "@/lib/Rpcs";
import { PasswordDialog } from "../PasswordDialog";
import { SettingsDialog } from "../SettingsDialog";
import { SharingDialog } from "../SharingDialog";
import { CapCardAnalytics } from "./CapCardAnalytics";
import { CapCardButton } from "./CapCardButton";
Expand Down Expand Up @@ -70,6 +74,14 @@ export interface CapCardProps extends PropsWithChildren {
hasPassword?: boolean;
hasActiveUpload: boolean | undefined;
duration?: number;
settings?: {
disableComments?: boolean;
disableSummary?: boolean;
disableCaptions?: boolean;
disableChapters?: boolean;
disableReactions?: boolean;
disableTranscript?: boolean;
};
};
analytics: number;
isLoadingAnalytics: boolean;
Expand Down Expand Up @@ -111,6 +123,7 @@ export const CapCard = ({
);
const [copyPressed, setCopyPressed] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isSettingsDialogOpen, setIsSettingsDialogOpen] = useState(false);
const { isSubscribed, setUpgradeModalOpen } = useDashboardContext();

const [confirmOpen, setConfirmOpen] = useState(false);
Expand Down Expand Up @@ -285,6 +298,12 @@ export const CapCard = ({
onSharingUpdated={handleSharingUpdated}
isPublic={cap.public}
/>
<SettingsDialog
isOpen={isSettingsDialogOpen}
settingsData={cap.settings}
capId={cap.id}
onClose={() => setIsSettingsDialogOpen(false)}
/>
<PasswordDialog
isOpen={isPasswordDialogOpen}
onClose={() => setIsPasswordDialogOpen(false)}
Expand Down Expand Up @@ -323,6 +342,31 @@ export const CapCard = ({
"top-2 right-2 flex-col gap-2 z-[51]",
)}
>
{isOwner ? (
<CapCardButton
tooltipContent="Settings"
onClick={(e) => {
e.stopPropagation();
setIsSettingsDialogOpen(true);
}}
className="delay-0"
icon={() => {
return <FontAwesomeIcon className="size-4" icon={faGear} />;
}}
/>
) : (
<CapCardButton
tooltipContent="Download Cap"
onClick={(e) => {
e.stopPropagation();
handleDownload();
}}
className="delay-0"
icon={() => (
<FontAwesomeIcon className="size-4" icon={faDownload} />
)}
/>
)}
<CapCardButton
tooltipContent="Copy link"
onClick={(e) => {
Expand Down Expand Up @@ -363,54 +407,10 @@ export const CapCard = ({
);
}}
/>
<CapCardButton
tooltipContent="Download Cap"
onClick={(e) => {
e.stopPropagation();
handleDownload();
}}
disabled={
downloadMutation.isPending ||
(enableBetaUploadProgress && cap.hasActiveUpload)
}
className="delay-25"
icon={() => {
return downloadMutation.isPending ? (
<div className="animate-spin size-3">
<svg
className="size-3"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="m2 12c0-5.523 4.477-10 10-10v3c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7c0-1.457-.447-2.808-1.208-3.926l2.4-1.6c1.131 1.671 1.808 3.677 1.808 5.526 0 5.523-4.477 10-10 10s-10-4.477-10-10z"
></path>
</svg>
</div>
) : (
<FontAwesomeIcon
className="text-gray-12 size-3"
icon={faDownload}
/>
);
}}
/>

{isOwner && (
<DropdownMenu modal={false} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild suppressHydrationWarning>
<div>
<CapCardButton
tooltipContent="More options"
Expand All @@ -421,7 +421,21 @@ export const CapCard = ({
/>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" sideOffset={5}>
<DropdownMenuContent
align="end"
sideOffset={5}
suppressHydrationWarning
>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handleDownload();
}}
className="flex gap-2 items-center rounded-lg"
>
<FontAwesomeIcon className="size-3" icon={faDownload} />
<p className="text-sm text-gray-12">Download</p>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
toast.promise(duplicateMutation.mutateAsync(), {
Expand Down Expand Up @@ -522,8 +536,8 @@ export const CapCard = ({
href={`/s/${cap.id}`}
>
{imageStatus !== "success" && uploadProgress ? (
<div className="relative inset-0 w-full h-full z-20">
<div className="overflow-hidden relative mx-auto w-full h-full rounded-t-xl border-b border-gray-3 aspect-video bg-black z-5">
<div className="relative inset-0 z-20 w-full h-full">
<div className="overflow-hidden relative mx-auto w-full h-full bg-black rounded-t-xl border-b border-gray-3 aspect-video z-5">
<div className="flex absolute inset-0 justify-center items-center rounded-t-xl">
{uploadProgress.status === "failed" ? (
<div className="flex flex-col items-center">
Expand Down
Loading