Skip to content

Commit 4783e0f

Browse files
authored
Merge branch 'main' into main
2 parents bbbd18f + 9cbe09d commit 4783e0f

25 files changed

+951
-805
lines changed

.env.sample

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ STRIPE_SECRET_KEY=your_stripe_secret_key
1111
STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret
1212
STRIPE_PRICE_ID=your_stripe_price_id
1313

14-
STORAGE_TYPE=disk
15-
16-
UPLOAD_DIR=./files
17-
1814
R2_ENDPOINT=
1915
R2_ACCESS_KEY_ID=
2016
R2_SECRET_ACCESS_KEY=

package.json

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
"upload:videos": "tsx ./scripts/upload-videos-to-r2.ts"
1919
},
2020
"dependencies": {
21-
"@aws-sdk/client-s3": "^3.840.0",
21+
"@aws-sdk/client-s3": "^3.850.0",
2222
"@aws-sdk/lib-storage": "^3.850.0",
23-
"@aws-sdk/s3-request-presigner": "^3.840.0",
23+
"@aws-sdk/s3-request-presigner": "^3.850.0",
2424
"@hello-pangea/dnd": "^18.0.1",
25-
"@hookform/resolvers": "^5.1.1",
25+
"@hookform/resolvers": "^5.2.0",
2626
"@radix-ui/react-alert-dialog": "^1.1.14",
2727
"@radix-ui/react-checkbox": "^1.3.2",
2828
"@radix-ui/react-dialog": "^1.1.14",
@@ -35,47 +35,46 @@
3535
"@radix-ui/react-switch": "^1.2.5",
3636
"@radix-ui/react-toast": "^1.2.14",
3737
"@radix-ui/react-tooltip": "^1.2.7",
38-
"@stripe/stripe-js": "^7.4.0",
38+
"@stripe/stripe-js": "^7.6.1",
3939
"@tailwindcss/vite": "^4.1.11",
40-
"@tanstack/react-query": "^5.81.5",
41-
"@tanstack/react-query-devtools": "^5.81.5",
42-
"@tanstack/react-router": "^1.122.0",
43-
"@tanstack/react-router-with-query": "^1.122.0",
44-
"@tanstack/react-start": "^1.122.1",
40+
"@tanstack/react-query": "^5.83.0",
41+
"@tanstack/react-query-devtools": "^5.83.0",
42+
"@tanstack/react-router": "^1.130.2",
43+
"@tanstack/react-router-with-query": "^1.130.2",
44+
"@tanstack/react-start": "^1.130.2",
4545
"arctic": "^3.7.0",
4646
"class-variance-authority": "^0.7.1",
4747
"clsx": "^2.1.1",
4848
"cmdk": "^1.1.1",
49-
"dotenv": "^17.2.1",
50-
"drizzle-orm": "^0.44.2",
49+
"drizzle-orm": "^0.44.3",
5150
"entities": "^6.0.1",
52-
"framer-motion": "^12.19.2",
51+
"framer-motion": "^12.23.10",
5352
"js-cookie": "^3.0.5",
54-
"lucide-react": "^0.525.0",
53+
"lucide-react": "^0.528.0",
5554
"nprogress": "^0.2.0",
5655
"pg": "^8.16.3",
5756
"react": "^19.1.0",
5857
"react-confetti": "^6.4.0",
5958
"react-dom": "^19.1.0",
6059
"react-dropzone": "^14.3.8",
6160
"react-google-recaptcha": "^3.1.0",
62-
"react-hook-form": "^7.59.0",
61+
"react-hook-form": "^7.61.1",
6362
"react-markdown": "^10.1.0",
6463
"react-use": "^17.6.0",
6564
"redaxios": "^0.5.1",
66-
"sonner": "^2.0.5",
67-
"stripe": "^18.2.1",
65+
"sonner": "^2.0.6",
66+
"stripe": "^18.3.0",
6867
"tailwind-merge": "^3.3.1",
69-
"tw-animate-css": "^1.3.4",
68+
"tw-animate-css": "^1.3.6",
7069
"uuid": "^11.1.0",
71-
"vite": "^7.0.0",
72-
"zod": "^3.25.67"
70+
"vite": "^7.0.6",
71+
"zod": "^4.0.10"
7372
},
7473
"devDependencies": {
7574
"@tailwindcss/typography": "^0.5.16",
76-
"@tanstack/react-router-devtools": "^1.122.0",
75+
"@tanstack/react-router-devtools": "^1.130.2",
7776
"@types/js-cookie": "^3.0.6",
78-
"@types/node": "^24.0.7",
77+
"@types/node": "^24.1.0",
7978
"@types/nprogress": "^0.2.3",
8079
"@types/pg": "^8.15.4",
8180
"@types/react": "^19.1.8",

scripts/upload-videos-to-r2.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async function uploadVideosToR2() {
3636

3737
// Read all files in the backups/videos directory
3838
const files = await readdir(BACKUPS_DIR);
39-
const videoFiles = files.filter((file) => file.endsWith(".mp4"));
39+
const videoFiles = files.filter(file => file.endsWith(".mp4"));
4040

4141
console.log(`📁 Found ${videoFiles.length} video files to process`);
4242

@@ -88,27 +88,9 @@ async function uploadVideosToR2() {
8888
// Read the file
8989
const fileBuffer = await readFile(filePath);
9090

91-
// Upload to R2 with progress tracking
92-
let lastProgressPercentage = 0;
93-
await r2Storage.uploadWithProgress(key, fileBuffer, (progress) => {
94-
// Only update if progress has changed significantly
95-
if (progress.percentage > lastProgressPercentage) {
96-
process.stdout.write("\r");
97-
const fileProgressBar = createProgressBar(
98-
progress.percentage,
99-
100,
100-
25
101-
);
102-
const loadedMB = (progress.loaded / 1024 / 1024).toFixed(1);
103-
const totalMB = (progress.total / 1024 / 1024).toFixed(1);
104-
const progressText = `📁 File Progress: ${fileProgressBar} ${progress.percentage}% (${loadedMB}/${totalMB} MB)`;
105-
process.stdout.write(progressText);
106-
lastProgressPercentage = progress.percentage;
107-
}
108-
});
109-
110-
// Clear the progress line and move to next line
111-
process.stdout.write("\n");
91+
// Upload to R2
92+
console.log(`⏳ Uploading ${formatFileSize(fileSize)}...`);
93+
await r2Storage.upload(key, fileBuffer);
11294

11395
uploadedSize += fileSize;
11496
const sizeProgress = formatFileSize(uploadedSize);

src/components/ui/progress.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,27 @@ import * as ProgressPrimitive from "@radix-ui/react-progress";
33

44
import { cn } from "~/lib/utils";
55

6-
const Progress = React.forwardRef<
7-
React.ElementRef<typeof ProgressPrimitive.Root>,
8-
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
9-
>(({ className, value, ...props }, ref) => (
10-
<ProgressPrimitive.Root
11-
ref={ref}
12-
className={cn(
13-
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
14-
className
15-
)}
16-
{...props}
17-
>
18-
<ProgressPrimitive.Indicator
19-
className="h-full w-full flex-1 bg-primary transition-all"
20-
style={{ transform: `translateX(-${100 - (value || 0)}
6+
function Progress({
7+
className,
8+
value,
9+
...props
10+
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
11+
return (
12+
<ProgressPrimitive.Root
13+
data-slot="progress"
14+
className={cn(
15+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
16+
className
17+
)}
18+
{...props}
19+
>
20+
<ProgressPrimitive.Indicator
21+
data-slot="progress-indicator"
22+
className="bg-primary h-full w-full flex-1 transition-all"
23+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
24+
/>
25+
</ProgressPrimitive.Root>
26+
);
27+
}
28+
29+
export { Progress };

src/fn/storage.ts

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,16 @@ import { adminMiddleware } from "~/lib/auth";
33
import { z } from "zod";
44
import { getStorage } from "~/utils/storage";
55

6-
// Unused old upload server function
7-
// export const uploadVideoFn = createServerFn({ method: "POST" })
8-
// .middleware([adminMiddleware])
9-
// .validator(z.instanceof(FormData))
10-
// .handler(async ({ data: formData }) => {
11-
// const videoKey = `${generateRandomUUID()}.mp4`;
12-
// const file = formData.get("file") as File;
13-
// if (!(file instanceof File)) throw new Error("[file] not found");
14-
// const buffer = Buffer.from(await file.arrayBuffer());
15-
// await saveFile(videoKey, buffer);
16-
// return { videoKey };
17-
// });
18-
19-
export const uploadVideoChunkFn = createServerFn({ method: "POST" })
6+
export const getPresignedUploadUrlFn = createServerFn({ method: "POST" })
207
.middleware([adminMiddleware])
21-
.validator(z.instanceof(FormData))
22-
.handler(async ({ data: formData }) => {
8+
.validator(
9+
z.object({
10+
videoKey: z.string(),
11+
})
12+
)
13+
.handler(async ({ data }) => {
2314
const { storage } = getStorage();
24-
const chunkIndex = Number(formData.get("chunkIndex"));
25-
const totalChunks = Number(formData.get("totalChunks"));
26-
const videoKey = formData.get("videoKey") as string;
27-
const chunk = formData.get("chunk") as File;
28-
29-
if (isNaN(chunkIndex) || isNaN(totalChunks)) {
30-
throw new Error("Invalid chunk metadata");
31-
}
32-
if (!videoKey || typeof videoKey !== "string") {
33-
throw new Error("Invalid videoKey");
34-
}
35-
if (!(chunk instanceof File)) {
36-
throw new Error("Invalid chunk data");
37-
}
38-
39-
const chunkBuffer = Buffer.from(await chunk.arrayBuffer());
40-
41-
// Save chunk with index in filename
42-
const chunkPath = `${videoKey}.part${chunkIndex}`;
43-
await storage.upload(chunkPath, chunkBuffer);
44-
45-
// If this is the last chunk, combine all chunks and delete the chunks
46-
if (chunkIndex === totalChunks - 1) {
47-
await storage.combineChunks(
48-
videoKey,
49-
Array.from({ length: totalChunks }, (_, i) => `${videoKey}.part${i}`)
50-
);
51-
}
15+
const presignedUrl = await storage.getPresignedUploadUrl(data.videoKey);
5216

53-
return { videoKey };
17+
return { presignedUrl, videoKey: data.videoKey };
5418
});

src/hooks/use-prevent-tab-close.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useEffect } from "react";
2+
3+
export function usePreventTabClose(shouldPrevent: boolean) {
4+
useEffect(() => {
5+
function handleBeforeUnload(event: BeforeUnloadEvent) {
6+
if (shouldPrevent) {
7+
event.preventDefault();
8+
// event.returnValue = ""; // required for most browsers to show the dialog
9+
}
10+
}
11+
12+
window.addEventListener("beforeunload", handleBeforeUnload);
13+
return () => {
14+
window.removeEventListener("beforeunload", handleBeforeUnload);
15+
};
16+
}, [shouldPrevent]);
17+
}

0 commit comments

Comments
 (0)