Spaces:
Runtime error
Runtime error
Commit ·
939e209
1
Parent(s): 271988a
feat: Enable guest users to add product detections and upload images, enhance the product request dialog UI, and update .gitignore.
Browse files
.gitignore
CHANGED
|
@@ -39,3 +39,10 @@ yarn-error.log*
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
| 42 |
+
|
| 43 |
+
# drizzle migrations (use drizzle-kit generate/push only)
|
| 44 |
+
drizzle-backup/
|
| 45 |
+
*_drizzle-backup.sql
|
| 46 |
+
|
| 47 |
+
# utility scripts (keep local only)
|
| 48 |
+
scripts/
|
src/features/moderation/actions/add-detection.ts
CHANGED
|
@@ -54,11 +54,14 @@ export async function addDetection(data: AddDetectionData) {
|
|
| 54 |
return { success: false, error: 'Video not found' };
|
| 55 |
}
|
| 56 |
|
| 57 |
-
// Verify ownership
|
|
|
|
| 58 |
const isOwner = currentUserId && videoData.creatorId === currentUserId;
|
| 59 |
const isDemo = videoData.creatorId === DEMO_USER_ID;
|
|
|
|
| 60 |
|
| 61 |
-
|
|
|
|
| 62 |
return { success: false, error: 'Unauthorized' };
|
| 63 |
}
|
| 64 |
|
|
|
|
| 54 |
return { success: false, error: 'Video not found' };
|
| 55 |
}
|
| 56 |
|
| 57 |
+
// Verify ownership for logged-in users (optional check)
|
| 58 |
+
// Guests can always add products (they'll be attributed to DEMO_USER_ID initially)
|
| 59 |
const isOwner = currentUserId && videoData.creatorId === currentUserId;
|
| 60 |
const isDemo = videoData.creatorId === DEMO_USER_ID;
|
| 61 |
+
const isGuest = !currentUserId;
|
| 62 |
|
| 63 |
+
// Allow: owners, demo videos, or guests (showcase mode)
|
| 64 |
+
if (!isOwner && !isDemo && !isGuest) {
|
| 65 |
return { success: false, error: 'Unauthorized' };
|
| 66 |
}
|
| 67 |
|
src/features/moderation/actions/upload-product-image.ts
CHANGED
|
@@ -10,9 +10,8 @@ export async function uploadProductImage(formData: FormData) {
|
|
| 10 |
headers: await headers(),
|
| 11 |
});
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
}
|
| 16 |
|
| 17 |
const file = formData.get('file') as File;
|
| 18 |
if (!file) {
|
|
@@ -29,7 +28,7 @@ export async function uploadProductImage(formData: FormData) {
|
|
| 29 |
const buffer = Buffer.from(arrayBuffer);
|
| 30 |
|
| 31 |
const safeName = (file.name || 'image.png').replace(/[^a-zA-Z0-9.]/g, '_');
|
| 32 |
-
const fileName = `manual/${
|
| 33 |
|
| 34 |
const publicUrl = await uploadThumbnail(buffer, fileName, file.type);
|
| 35 |
|
|
|
|
| 10 |
headers: await headers(),
|
| 11 |
});
|
| 12 |
|
| 13 |
+
// Allow guests to upload (for showcase mode)
|
| 14 |
+
const userId = session?.user?.id || 'guest';
|
|
|
|
| 15 |
|
| 16 |
const file = formData.get('file') as File;
|
| 17 |
if (!file) {
|
|
|
|
| 28 |
const buffer = Buffer.from(arrayBuffer);
|
| 29 |
|
| 30 |
const safeName = (file.name || 'image.png').replace(/[^a-zA-Z0-9.]/g, '_');
|
| 31 |
+
const fileName = `manual/${userId}-${Date.now()}-${safeName}`;
|
| 32 |
|
| 33 |
const publicUrl = await uploadThumbnail(buffer, fileName, file.type);
|
| 34 |
|
src/features/vault/components/product-request-dialog.tsx
CHANGED
|
@@ -156,14 +156,14 @@ export function ProductRequestDialog({ videoId: initialVideoId, creatorId, video
|
|
| 156 |
Can't find a product?
|
| 157 |
</Button>
|
| 158 |
</DialogTrigger>
|
| 159 |
-
<DialogContent className="sm:max-w-[
|
| 160 |
<DialogHeader>
|
| 161 |
<DialogTitle className="text-xl font-bold tracking-tight">Request a Product</DialogTitle>
|
| 162 |
<DialogDescription className="text-muted-foreground">
|
| 163 |
Saw something in a video that's not listed? Let the creator know!
|
| 164 |
</DialogDescription>
|
| 165 |
</DialogHeader>
|
| 166 |
-
<form onSubmit={handleSubmit} className="space-y-
|
| 167 |
{!initialVideoId && videos.length > 0 && (
|
| 168 |
<div className="space-y-2">
|
| 169 |
<Label htmlFor="video-select" className="text-sm font-medium">Which video did you see it in?*</Label>
|
|
@@ -190,8 +190,7 @@ export function ProductRequestDialog({ videoId: initialVideoId, creatorId, video
|
|
| 190 |
required
|
| 191 |
/>
|
| 192 |
</div>
|
| 193 |
-
|
| 194 |
-
<div className="grid grid-cols-2 gap-4">
|
| 195 |
<div className="space-y-2">
|
| 196 |
<Label htmlFor="name" className="text-sm font-medium">Name (Optional)</Label>
|
| 197 |
<Input
|
|
@@ -214,7 +213,7 @@ export function ProductRequestDialog({ videoId: initialVideoId, creatorId, video
|
|
| 214 |
/>
|
| 215 |
</div>
|
| 216 |
</div>
|
| 217 |
-
<div className="space-y-
|
| 218 |
<ImageUploadField
|
| 219 |
label="Product Image (Optional)"
|
| 220 |
icon={<PlaySquare className="h-4 w-4 text-primary" />}
|
|
|
|
| 156 |
Can't find a product?
|
| 157 |
</Button>
|
| 158 |
</DialogTrigger>
|
| 159 |
+
<DialogContent className="sm:max-w-[540px] glass border-white/10 text-white">
|
| 160 |
<DialogHeader>
|
| 161 |
<DialogTitle className="text-xl font-bold tracking-tight">Request a Product</DialogTitle>
|
| 162 |
<DialogDescription className="text-muted-foreground">
|
| 163 |
Saw something in a video that's not listed? Let the creator know!
|
| 164 |
</DialogDescription>
|
| 165 |
</DialogHeader>
|
| 166 |
+
<form onSubmit={handleSubmit} className="space-y-5 py-4 max-h-[60vh] overflow-y-auto pr-2">
|
| 167 |
{!initialVideoId && videos.length > 0 && (
|
| 168 |
<div className="space-y-2">
|
| 169 |
<Label htmlFor="video-select" className="text-sm font-medium">Which video did you see it in?*</Label>
|
|
|
|
| 190 |
required
|
| 191 |
/>
|
| 192 |
</div>
|
| 193 |
+
<div className="space-y-4">
|
|
|
|
| 194 |
<div className="space-y-2">
|
| 195 |
<Label htmlFor="name" className="text-sm font-medium">Name (Optional)</Label>
|
| 196 |
<Input
|
|
|
|
| 213 |
/>
|
| 214 |
</div>
|
| 215 |
</div>
|
| 216 |
+
<div className="space-y-2">
|
| 217 |
<ImageUploadField
|
| 218 |
label="Product Image (Optional)"
|
| 219 |
icon={<PlaySquare className="h-4 w-4 text-primary" />}
|
src/features/vault/services/showcase.service.ts
CHANGED
|
@@ -602,8 +602,7 @@ export class ShowcaseService {
|
|
| 602 |
await db.update(youtubeChannels)
|
| 603 |
.set({
|
| 604 |
subscriberCount: subscriberCount > 0 ? subscriberCount : channel.subscriberCount,
|
| 605 |
-
thumbnailUrl: betterThumbnailUrl || channel.thumbnailUrl
|
| 606 |
-
creatorSlug: expectedSlug
|
| 607 |
})
|
| 608 |
.where(eq(youtubeChannels.id, channel.id));
|
| 609 |
} else {
|
|
|
|
| 602 |
await db.update(youtubeChannels)
|
| 603 |
.set({
|
| 604 |
subscriberCount: subscriberCount > 0 ? subscriberCount : channel.subscriberCount,
|
| 605 |
+
thumbnailUrl: betterThumbnailUrl || channel.thumbnailUrl
|
|
|
|
| 606 |
})
|
| 607 |
.where(eq(youtubeChannels.id, channel.id));
|
| 608 |
} else {
|