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
17 changes: 17 additions & 0 deletions apps/desktop/src/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,23 @@
.gray-button-shadow {
box-shadow: 0 1.5px 0 0 rgba(255, 255, 255, 0.4) inset;
}

.dark .gray-button-border {
@apply border-gray-2;
}

.gray-button-border {
@apply border-gray-8;
}

.dark .dark-button-border {
@apply border-gray-2;
}

.dark-button-border {
@apply border-gray-12;
}

[data-transparent-window] {
background: transparent !important;
}
Expand Down
16 changes: 16 additions & 0 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ html {
box-shadow: 0 1.5px 0 0 rgba(255, 255, 255, 0.1) inset;
}

.dark .gray-button-border {
@apply border-gray-2;
}

.gray-button-border {
@apply border-gray-8;
}

.dark .dark-button-border {
@apply border-gray-2;
}

.dark-button-border {
@apply border-gray-12;
}

.gray-button-shadow {
box-shadow: 0 1.5px 0 0 rgba(255, 255, 255, 0.4) inset;
}
Expand Down
4 changes: 4 additions & 0 deletions apps/web/app/s/[videoId]/Share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type VideoWithOrganizationInfo = typeof videos.$inferSelect & {
organizationId?: string;
sharedOrganizations?: { id: string; name: string }[];
hasPassword?: boolean;
owner?: {
stripeSubscriptionStatus: string | null;
thirdPartyStripeSubscriptionId: string | null;
} | null;
};

interface ShareProps {
Expand Down
11 changes: 9 additions & 2 deletions apps/web/app/s/[videoId]/_components/ShareVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ type CommentWithAuthor = typeof commentsSchema.$inferSelect & {
export const ShareVideo = forwardRef<
HTMLVideoElement,
{
data: typeof videos.$inferSelect;
data: typeof videos.$inferSelect & {
owner?: {
stripeSubscriptionStatus: string | null;
thirdPartyStripeSubscriptionId: string | null;
} | null;
};
user: typeof userSelectProps | null;
comments: MaybePromise<CommentWithAuthor[]>;
chapters?: { title: string; start: number }[];
Expand All @@ -50,6 +55,8 @@ export const ShareVideo = forwardRef<
const [subtitleUrl, setSubtitleUrl] = useState<string | null>(null);
const [chaptersUrl, setChaptersUrl] = useState<string | null>(null);

const isVideoOwnerPro = userIsPro(data.owner);

const { data: transcriptContent, error: transcriptError } = useTranscript(
data.id,
data.transcriptionStatus,
Expand Down Expand Up @@ -167,7 +174,7 @@ export const ShareVideo = forwardRef<
)}
</div>

{!userIsPro(user) && (
{!isVideoOwnerPro && (
<div className="absolute top-4 left-4 z-30">
<div
className="block cursor-pointer"
Expand Down
19 changes: 19 additions & 0 deletions apps/web/app/s/[videoId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ type VideoWithOrganization = typeof videos.$inferSelect & {
sharedOrganizations?: { id: string; name: string }[];
password?: string | null;
hasPassword?: boolean;
owner?: {
stripeSubscriptionStatus: string | null;
thirdPartyStripeSubscriptionId: string | null;
} | null;
};
Comment on lines +111 to 115
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Block PII: don’t serialize subscription identifiers to the client; expose only a boolean.

These fields flow into Share/ShareHeader (client). Sending thirdPartyStripeSubscriptionId (and even the raw status string) publicly is unnecessary for watermark gating and is sensitive. Compute a server-side boolean and pass that instead.

Apply:

-	owner?: {
-		stripeSubscriptionStatus: string | null;
-		thirdPartyStripeSubscriptionId: string | null;
-	} | null;
+	ownerIsPro?: boolean;

Also convert the numeric SQL flag to boolean at construction time (outside this hunk): in the object built at Lines 666-674, add
ownerIsPro: video.ownerIsPro === 1,.

Run to find downstream usages to update to the boolean flag:


🏁 Script executed:

#!/bin/bash
# Replace client-side checks that read owner.* with a boolean ownerIsPro
rg -n --type=ts --type=tsx -C2 $'isVideoOwnerPro\\(|thirdPartyStripeSubscriptionId|owner\\s*:\\s*{\\s*stripeSubscriptionStatus' apps/web

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Search for any downstream usages of the old owner fields or checks against them
rg -n --type=ts --type=tsx -C2 \
  -e 'isVideoOwnerPro\(' \
  -e 'thirdPartyStripeSubscriptionId' \
  -e 'owner\s*:\s*\{\s*stripeSubscriptionStatus' apps/web

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Retry search without --type flags; include .ts/.tsx extensions explicitly
rg -n -C2 -g '*.ts' -g '*.tsx' \
  -e 'isVideoOwnerPro\(' \
  -e 'thirdPartyStripeSubscriptionId' \
  -e 'stripeSubscriptionStatus' \
  apps/web

Length of output: 12969


Block PII: replace raw subscription fields with a boolean ownerIsPro everywhere

  • In apps/web/app/s/[videoId]/page.tsx (lines 111–115), replace:
    -owner?: {
    -  stripeSubscriptionStatus: string | null;
    -  thirdPartyStripeSubscriptionId: string | null;
    -} | null;
    +ownerIsPro?: boolean;
  • When constructing the video payload (e.g. the .select(...) at lines 296–300), add:
    ownerIsPro: video.ownerIsPro === 1,
  • Refactor all downstream usages of stripeSubscriptionStatus and thirdPartyStripeSubscriptionId, including but not limited to:
    • apps/web/app/s/[videoId]/Share.tsx
    • apps/web/app/s/[videoId]/_components/ShareVideo.tsx
    • apps/web/utils/flags.ts (FeatureFlagUser)
    • Any .select({ …, stripeSubscriptionStatus, thirdPartyStripeSubscriptionId }) and runtime checks in apps/web/actions and apps/web/app/api
  • To locate all occurrences, run:
    rg -l -e 'stripeSubscriptionStatus' -e 'thirdPartyStripeSubscriptionId' apps/web
🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/page.tsx around lines 111–115 and when constructing
the video payload at ~lines 296–300, remove the raw PII fields
stripeSubscriptionStatus and thirdPartyStripeSubscriptionId from the owner type
and instead expose a boolean ownerIsPro (set ownerIsPro: video.ownerIsPro === 1
in the .select(...) that builds the payload); then refactor all downstream
usages to consume ownerIsPro (replace checks against
stripeSubscriptionStatus/thirdPartyStripeSubscriptionId in
apps/web/app/s/[videoId]/Share.tsx,
apps/web/app/s/[videoId]/_components/ShareVideo.tsx, apps/web/utils/flags.ts
(FeatureFlagUser) and any actions or api code that .selects or reads those
fields), remove those fields from any .select({...}) calls, and run rg -l -e
'stripeSubscriptionStatus' -e 'thirdPartyStripeSubscriptionId' apps/web to find
and update remaining references.


const ALLOWED_REFERRERS = [
Expand Down Expand Up @@ -288,9 +292,15 @@ export default async function ShareVideoPage(props: Props) {
sharedOrganization: {
organizationId: sharedVideos.organizationId,
},
owner: {
stripeSubscriptionStatus: users.stripeSubscriptionStatus,
thirdPartyStripeSubscriptionId:
users.thirdPartyStripeSubscriptionId,
},
Comment on lines +295 to +299
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Select a computed pro flag; avoid leaking subscription IDs/status.

Replace the nested owner object with a single computed flag. Keep the users left-join.

-					owner: {
-						stripeSubscriptionStatus: users.stripeSubscriptionStatus,
-						thirdPartyStripeSubscriptionId:
-							users.thirdPartyStripeSubscriptionId,
-					},
+					ownerIsPro: sql<number>`IF(
+            ${users.stripeSubscriptionStatus} IN ('active','trialing')
+            OR ${users.thirdPartyStripeSubscriptionId} IS NOT NULL,
+            1, 0
+          )`,

Also applies to: 303-303

})
.from(videos)
.leftJoin(sharedVideos, eq(videos.id, sharedVideos.videoId))
.leftJoin(users, eq(videos.ownerId, users.id))
.where(eq(videos.id, videoId)),
).pipe(Policy.withPublicPolicy(videosPolicy.canView(videoId)));

Expand Down Expand Up @@ -340,6 +350,10 @@ async function AuthorizedContent({
video: Omit<InferSelectModel<typeof videos>, "folderId" | "password"> & {
sharedOrganization: { organizationId: string } | null;
hasPassword: number;
owner: {
stripeSubscriptionStatus: string | null;
thirdPartyStripeSubscriptionId: string | null;
} | null;
Comment on lines +353 to +356
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align prop type with boolean flag to prevent client exposure.

-		owner: {
-			stripeSubscriptionStatus: string | null;
-			thirdPartyStripeSubscriptionId: string | null;
-		} | null;
+		ownerIsPro: number;

Note: This remains numeric here because it comes from SQL; convert to boolean before passing to client props (see prior comment).

📝 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
owner: {
stripeSubscriptionStatus: string | null;
thirdPartyStripeSubscriptionId: string | null;
} | null;
ownerIsPro: number;
🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/page.tsx around lines 353 to 356, the prop
stripeSubscriptionStatus is currently typed as string | null but should be a
boolean (or null) so we don't expose raw numeric/SQL values to the client;
change the type to boolean | null and, where you build the props, convert the
numeric SQL value (e.g., 1/0 or '1'/'0') to a boolean (true/false) before
passing it to the client; leave thirdPartyStripeSubscriptionId as string | null
if it is truly a string.

};
searchParams: { [key: string]: string | string[] | undefined };
}) {
Expand Down Expand Up @@ -437,6 +451,10 @@ async function AuthorizedContent({
id: videos.id,
name: videos.name,
ownerId: videos.ownerId,
owner: {
stripeSubscriptionStatus: users.stripeSubscriptionStatus,
thirdPartyStripeSubscriptionId: users.thirdPartyStripeSubscriptionId,
},
Comment on lines +454 to +457
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mirror the computed flag in the post-transcription refresh query.

-				owner: {
-					stripeSubscriptionStatus: users.stripeSubscriptionStatus,
-					thirdPartyStripeSubscriptionId: users.thirdPartyStripeSubscriptionId,
-				},
+				ownerIsPro: sql<number>`IF(
+          ${users.stripeSubscriptionStatus} IN ('active','trialing')
+          OR ${users.thirdPartyStripeSubscriptionId} IS NOT NULL,
+          1, 0
+        )`,

Also applies to: 480-480

🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/page.tsx around lines 454-457 (and also at line
480), the post-transcription refresh query omits the computed subscription flag
that the main query exposes; update the owner object in both locations to
include the same computed flag key and value used in the main query (mirror the
exact expression/variable name and value logic), so the refresh response
contains the identical computed flag as the original query.

createdAt: videos.createdAt,
updatedAt: videos.updatedAt,
awsRegion: videos.awsRegion,
Expand All @@ -459,6 +477,7 @@ async function AuthorizedContent({
})
.from(videos)
.leftJoin(sharedVideos, eq(videos.id, sharedVideos.videoId))
.leftJoin(users, eq(videos.ownerId, users.id))
.where(eq(videos.id, videoId))
.execute();

Expand Down
4 changes: 2 additions & 2 deletions packages/ui-solid/src/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const styles = cva(
white:
"bg-gray-1 border border-gray-6 text-gray-12 hover:bg-gray-3 disabled:bg-gray-8",
ghost: "hover:bg-white/20 hover:text-white",
gray: "bg-gray-5 data-[selected=true]:!bg-gray-8 dark:data-[selected=true]:!bg-gray-9 hover:bg-gray-7 border border-gray-6 gray-button-shadow text-gray-12 disabled:bg-gray-8 disabled:text-gray-9",
dark: "bg-gray-12 dark-button-shadow hover:bg-gray-11 border border-gray-12 text-gray-1 disabled:cursor-not-allowed disabled:text-gray-10 disabled:bg-gray-7 disabled:border-gray-8",
gray: "bg-gray-5 data-[selected=true]:!bg-gray-8 dark:data-[selected=true]:!bg-gray-9 hover:bg-gray-7 border gray-button-border gray-button-shadow text-gray-12 disabled:bg-gray-8 disabled:text-gray-9",
dark: "bg-gray-12 dark-button-border dark-button-shadow hover:bg-gray-11 border text-gray-1 disabled:cursor-not-allowed disabled:text-gray-10 disabled:bg-gray-7 disabled:border-gray-8",
darkgradient:
"bg-gradient-to-t button-gradient-border from-[#0f0f0f] to-[#404040] shadow-[0_0_0_1px] hover:brightness-110 shadow-[#383838] text-gray-50 hover:bg-[#383838] disabled:bg-[#383838] border-transparent",
radialblue:
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const buttonVariants = cva(
white:
"bg-gray-1 border border-gray-6 text-gray-12 hover:bg-gray-3 disabled:bg-gray-8",
ghost: "hover:bg-white/20 hover:text-white",
gray: "bg-gray-5 hover:bg-gray-7 border border-gray-6 gray-button-shadow text-gray-12 disabled:border-gray-7 disabled:bg-gray-8 disabled:text-gray-11",
dark: "bg-gray-12 dark-button-shadow hover:bg-gray-11 border border-gray-12 text-gray-1 disabled:cursor-not-allowed disabled:text-gray-10 disabled:bg-gray-7 disabled:border-gray-8",
gray: "bg-gray-5 hover:bg-gray-7 border gray-button-border gray-button-shadow text-gray-12 disabled:border-gray-7 disabled:bg-gray-8 disabled:text-gray-11",
dark: "bg-gray-12 dark-button-shadow hover:bg-gray-11 border dark-button-border text-gray-1 disabled:cursor-not-allowed disabled:text-gray-10 disabled:bg-gray-7 disabled:border-gray-8",
darkgradient:
"bg-gradient-to-t button-gradient-border from-[#0f0f0f] to-[#404040] shadow-[0_0_0_1px] hover:brightness-110 shadow-[#383838] text-gray-50 hover:bg-[#383838] disabled:bg-[#383838] border-transparent",
radialblue:
Expand Down
Loading