Skip to content

Commit 80dbf8d

Browse files
committed
Add openShareUrlInNewTab and recorder UI
1 parent 00fa871 commit 80dbf8d

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-3
lines changed

apps/web/__tests__/unit/web-recorder-utils.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { describe, expect, it } from "vitest";
1+
import { describe, expect, it, vi } from "vitest";
22
import {
3+
openShareUrlInNewTab,
34
selectRecordingPipelineFromSupport,
45
shouldPreferStreamingUpload,
56
} from "@/app/(org)/dashboard/caps/components/web-recorder-dialog/web-recorder-utils";
@@ -124,3 +125,37 @@ describe("shouldPreferStreamingUpload", () => {
124125
).toBe(false);
125126
});
126127
});
128+
129+
describe("openShareUrlInNewTab", () => {
130+
it("opens the share url in a new tab", () => {
131+
const open = vi.fn(() => ({}));
132+
vi.stubGlobal("window", {
133+
open,
134+
});
135+
136+
expect(openShareUrlInNewTab("https://cap.so/s/test-video")).toBe(true);
137+
expect(open).toHaveBeenCalledWith(
138+
"https://cap.so/s/test-video",
139+
"_blank",
140+
"noopener,noreferrer",
141+
);
142+
143+
vi.unstubAllGlobals();
144+
});
145+
146+
it("returns false when the browser blocks the popup", () => {
147+
vi.stubGlobal("window", {
148+
open: vi.fn(() => null),
149+
});
150+
151+
expect(openShareUrlInNewTab("https://cap.so/s/test-video")).toBe(false);
152+
153+
vi.unstubAllGlobals();
154+
});
155+
156+
it("does not navigate when the share url is missing", () => {
157+
expect(openShareUrlInNewTab(null)).toBe(false);
158+
expect(openShareUrlInNewTab(undefined)).toBe(false);
159+
expect(openShareUrlInNewTab("")).toBe(false);
160+
});
161+
});

apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/useWebRecorder.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import type {
5454
import {
5555
detectCapabilities,
5656
isUserCancellationError,
57+
openShareUrlInNewTab,
5758
type RecorderCapabilities,
5859
type RecordingPipeline,
5960
selectRecordingPipeline,
@@ -133,6 +134,9 @@ export const useWebRecorder = ({
133134
const [chunkUploads, setChunkUploads] = useState<ChunkUploadState[]>([]);
134135
const [errorDownload, setErrorDownload] =
135136
useState<RecordingFailureDownload | null>(null);
137+
const [completedShareUrl, setCompletedShareUrl] = useState<string | null>(
138+
null,
139+
);
136140
const [recoveredDownloads, setRecoveredDownloads] = useState<
137141
RecoveredRecordingDownload[]
138142
>([]);
@@ -452,9 +456,8 @@ export const useWebRecorder = ({
452456

453457
const openShareUrl = useCallback((shareUrl?: string | null) => {
454458
if (!shareUrl || shareUrlOpenedRef.current) return;
455-
if (typeof window === "undefined") return;
459+
if (!openShareUrlInNewTab(shareUrl)) return;
456460
shareUrlOpenedRef.current = true;
457-
window.open(shareUrl, "_blank", "noopener,noreferrer");
458461
}, []);
459462
const queryClient = useQueryClient();
460463
const deleteVideo = useEffectMutation({
@@ -561,6 +564,7 @@ export const useWebRecorder = ({
561564
setChunkUploads([]);
562565
setHasAudioTrack(false);
563566
replaceErrorDownload(null);
567+
setCompletedShareUrl(null);
564568
shareUrlOpenedRef.current = false;
565569

566570
const pendingInstantVideoId = pendingInstantVideoIdRef.current;
@@ -1342,6 +1346,7 @@ export const useWebRecorder = ({
13421346
await disposeRecordingSpool();
13431347

13441348
setUploadStatus(undefined);
1349+
setCompletedShareUrl(creationResult.shareUrl);
13451350
updatePhase("completed");
13461351
toast.success(
13471352
pipeline.mode === "streaming-webm"
@@ -1379,6 +1384,7 @@ export const useWebRecorder = ({
13791384
"Upload confirmation was interrupted. Open the video to verify processing before retrying.",
13801385
);
13811386
openShareUrl(videoCreationRef.current?.shareUrl ?? null);
1387+
setCompletedShareUrl(videoCreationRef.current?.shareUrl ?? null);
13821388
router.refresh();
13831389
return;
13841390
}
@@ -1512,6 +1518,7 @@ export const useWebRecorder = ({
15121518
hasAudioTrack,
15131519
chunkUploads,
15141520
errorDownload,
1521+
completedShareUrl,
15151522
recoveredDownloads,
15161523
isSettingUp,
15171524
isRecording: isRecordingActive,
@@ -1522,6 +1529,7 @@ export const useWebRecorder = ({
15221529
pauseRecording,
15231530
resumeRecording,
15241531
stopRecording,
1532+
openCompletedShareUrl: () => openShareUrl(completedShareUrl),
15251533
restartRecording,
15261534
resetState,
15271535
dismissRecoveredDownload,

apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/web-recorder-dialog.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export const WebRecorderDialog = () => {
134134
hasAudioTrack,
135135
chunkUploads,
136136
errorDownload,
137+
completedShareUrl,
137138
recoveredDownloads,
138139
isRecording,
139140
isBusy,
@@ -148,6 +149,7 @@ export const WebRecorderDialog = () => {
148149
pauseRecording,
149150
resumeRecording,
150151
stopRecording,
152+
openCompletedShareUrl,
151153
restartRecording,
152154
resetState,
153155
dismissRecoveredDownload,
@@ -341,6 +343,22 @@ export const WebRecorderDialog = () => {
341343
{unsupportedReason}
342344
</div>
343345
)}
346+
{phase === "completed" && completedShareUrl && (
347+
<div className="rounded-md border border-green-6 bg-green-3/70 px-3 py-3 text-xs text-green-12">
348+
<div className="font-medium">Share link ready</div>
349+
<div className="mt-1 leading-snug">
350+
If it did not open automatically, open it here.
351+
</div>
352+
<Button
353+
variant="blue"
354+
size="sm"
355+
className="mt-3 w-full"
356+
onClick={openCompletedShareUrl}
357+
>
358+
Open Share Link
359+
</Button>
360+
</div>
361+
)}
344362
{phase === "idle" && recoveredDownloads.length > 0 && (
345363
<div className="rounded-md border border-blue-6 bg-blue-3/60 px-3 py-2">
346364
<div className="text-xs font-medium text-blue-12">

apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/web-recorder-utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ export const shouldPreferStreamingUpload = (
129129
return brandMatch || /(chrome|chromium|edg|opr|opera|brave)/i.test(userAgent);
130130
};
131131

132+
export const openShareUrlInNewTab = (shareUrl?: string | null) => {
133+
if (!shareUrl || typeof window === "undefined") {
134+
return false;
135+
}
136+
137+
return window.open(shareUrl, "_blank", "noopener,noreferrer") !== null;
138+
};
139+
132140
export const selectRecordingPipelineFromSupport = (
133141
hasAudio: boolean,
134142
isMimeSupported: (candidate: string) => boolean,

0 commit comments

Comments
 (0)