dvijaykrishnan commited on
Commit
792119a
·
1 Parent(s): 566383f

feat: Enable users to claim demo videos from the showcase page during sign-up.

Browse files
src/actions/claim-video.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use server';
2
+
3
+ import { db } from '@/lib/db';
4
+ import { youtubeVideos, youtubeChannels, detectedObjects } from '@/lib/db/schema';
5
+ import { eq, and } from 'drizzle-orm';
6
+ import { auth } from '@/lib/auth';
7
+ import { headers } from 'next/headers';
8
+ import { DEMO_USER_ID } from '@/features/moderation/utils/demo';
9
+
10
+ export async function claimVideo(videoId: string) {
11
+ try {
12
+ const session = await auth.api.getSession({
13
+ headers: await headers(),
14
+ });
15
+
16
+ if (!session?.user) {
17
+ return { success: false, error: 'Unauthorized' };
18
+ }
19
+
20
+ const userId = session.user.id;
21
+
22
+ // 1. Find the video
23
+ const video = await db.query.youtubeVideos.findFirst({
24
+ where: eq(youtubeVideos.videoId, videoId),
25
+ with: {
26
+ channel: true
27
+ }
28
+ });
29
+
30
+ if (!video) {
31
+ return { success: false, error: 'Video not found' };
32
+ }
33
+
34
+ // 2. Verify it's claimable (owned by demo/shadow user)
35
+ if (video.channel.creatorId !== DEMO_USER_ID && !video.channel.channelId.startsWith('shadow-')) {
36
+ return { success: false, error: 'Video already owned' };
37
+ }
38
+
39
+ // 3. Find or Create a channel for the NEW user
40
+ // For simplicity in this flow, we'll re-assign the EXISTING shadow channel to the new user
41
+ // In a real app, we might merge this into their existing channel if they have one.
42
+
43
+ await db.update(youtubeChannels)
44
+ .set({
45
+ creatorId: userId,
46
+ // We keep the channelId as is (shadow-...) or real ID if we found it
47
+ // But now it belongs to the real user
48
+ })
49
+ .where(eq(youtubeChannels.id, video.channelId));
50
+
51
+ // 4. Update any manually added detections to be "addedBy" the new user
52
+ await db.update(detectedObjects)
53
+ .set({
54
+ moderationStatus: 'APPROVED',
55
+ moderationMetadata: {
56
+ manuallyAdded: true,
57
+ addedBy: userId,
58
+ addedAt: new Date().toISOString(), // effectively "adopted" at this time
59
+ autoApproved: true, // Mark as auto-approved during claim
60
+ }
61
+ })
62
+ .where(and(
63
+ eq(detectedObjects.videoId, video.id),
64
+ // We could filter by "addedBy == DEMO_USER_ID" if we store that for guests
65
+ ));
66
+
67
+ return { success: true };
68
+ } catch (error: any) {
69
+ console.error('Claim video error:', error);
70
+ return { success: false, error: error.message };
71
+ }
72
+ }
src/app/showcase/[videoId]/page.tsx CHANGED
@@ -47,7 +47,7 @@ export default async function ShowcasePage({ params }: ShowcasePageProps) {
47
  </div>
48
  <div className="flex items-center gap-2">
49
  <Button asChild size="sm" className="rounded-full shadow-lg shadow-primary/20">
50
- <Link href="/register">Claim Your Vault</Link>
51
  </Button>
52
  </div>
53
  </div>
 
47
  </div>
48
  <div className="flex items-center gap-2">
49
  <Button asChild size="sm" className="rounded-full shadow-lg shadow-primary/20">
50
+ <Link href={`/register?videoId=${videoId}`}>Claim Your Vault</Link>
51
  </Button>
52
  </div>
53
  </div>
src/components/auth/sign-up-form.tsx CHANGED
@@ -1,6 +1,7 @@
1
  "use client";
2
 
3
  import { useState } from "react";
 
4
  import { signUp } from "@/lib/auth-client";
5
  import { Button } from "@/components/ui/button";
6
  import { Input } from "@/components/ui/input";
@@ -13,6 +14,9 @@ export function SignUpForm() {
13
  const [password, setPassword] = useState("");
14
  const [loading, setLoading] = useState(false);
15
  const [error, setError] = useState("");
 
 
 
16
 
17
  const handleSubmit = async (e: React.FormEvent) => {
18
  e.preventDefault();
@@ -27,9 +31,18 @@ export function SignUpForm() {
27
  onRequest: () => {
28
  setLoading(true);
29
  },
30
- onSuccess: () => {
 
 
 
 
 
 
 
 
 
31
  setLoading(false);
32
- // Redirect handled by callbackURL
33
  },
34
  onError: (ctx) => {
35
  setLoading(false);
 
1
  "use client";
2
 
3
  import { useState } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
  import { signUp } from "@/lib/auth-client";
6
  import { Button } from "@/components/ui/button";
7
  import { Input } from "@/components/ui/input";
 
14
  const [password, setPassword] = useState("");
15
  const [loading, setLoading] = useState(false);
16
  const [error, setError] = useState("");
17
+ const router = useRouter();
18
+ const searchParams = useSearchParams();
19
+ const videoId = searchParams.get("videoId");
20
 
21
  const handleSubmit = async (e: React.FormEvent) => {
22
  e.preventDefault();
 
31
  onRequest: () => {
32
  setLoading(true);
33
  },
34
+ onSuccess: async () => {
35
+ if (videoId) {
36
+ try {
37
+ const { claimVideo } = await import("@/actions/claim-video");
38
+ await claimVideo(videoId);
39
+ } catch (err) {
40
+ console.error("Failed to claim video:", err);
41
+ // Non-blocking error, user is still signed in
42
+ }
43
+ }
44
  setLoading(false);
45
+ router.push("/dashboard");
46
  },
47
  onError: (ctx) => {
48
  setLoading(false);