fix thumbnail issues

This commit is contained in:
wass08
2026-02-18 09:49:57 +09:00
parent f1077ad1c8
commit 1cceb50ab6
5 changed files with 32 additions and 15 deletions
@@ -5,6 +5,7 @@ import { useThree } from '@react-three/fiber'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import * as THREE from 'three' import * as THREE from 'three'
import { uploadProjectThumbnail } from '@/features/community/lib/projects/actions' import { uploadProjectThumbnail } from '@/features/community/lib/projects/actions'
import { useProjectStore } from '@/features/community/lib/projects/store'
const THUMBNAIL_WIDTH = 1920 const THUMBNAIL_WIDTH = 1920
const THUMBNAIL_HEIGHT = 1080 const THUMBNAIL_HEIGHT = 1080
@@ -70,8 +71,7 @@ export const ThumbnailGenerator = ({ projectId: propProjectId }: ThumbnailGenera
const result = await uploadProjectThumbnail(projectId, blob) const result = await uploadProjectThumbnail(projectId, blob)
if (result.success) { if (result.success) {
console.log('✅ Thumbnail uploaded successfully!') useProjectStore.getState().updateActiveThumbnail(result.data.thumbnail_url)
console.log('🔗 URL:', result.data.thumbnail_url)
} else { } else {
console.error('❌ Failed to upload thumbnail:', result.error) console.error('❌ Failed to upload thumbnail:', result.error)
} }
@@ -71,15 +71,9 @@ export function SettingsPanel() {
}; };
const handleGenerateThumbnail = () => { const handleGenerateThumbnail = () => {
if (!projectId) { if (!projectId) return;
console.error('❌ No project ID found');
return;
}
console.log('🎯 Generate thumbnail clicked for project:', projectId);
setIsGeneratingThumbnail(true); setIsGeneratingThumbnail(true);
emitter.emit('camera-controls:generate-thumbnail', { projectId }); emitter.emit('camera-controls:generate-thumbnail', { projectId });
console.log('📤 Event emitted with project ID:', projectId);
// Reset loading state after a delay (thumbnail generation is async)
setTimeout(() => setIsGeneratingThumbnail(false), 3000); setTimeout(() => setIsGeneratingThumbnail(false), 3000);
}; };
@@ -106,6 +100,16 @@ export function SettingsPanel() {
<label className="font-medium text-muted-foreground text-xs uppercase"> <label className="font-medium text-muted-foreground text-xs uppercase">
Thumbnail Thumbnail
</label> </label>
{activeProject?.thumbnail_url && (
<div className="rounded overflow-hidden border border-border aspect-video w-full bg-muted">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={activeProject.thumbnail_url}
alt="Project thumbnail"
className="w-full h-full object-cover"
/>
</div>
)}
<Button <Button
className="w-full justify-start gap-2" className="w-full justify-start gap-2"
onClick={handleGenerateThumbnail} onClick={handleGenerateThumbnail}
@@ -1020,16 +1020,14 @@ export async function uploadProjectThumbnail(
return { success: false, error: 'Not authorized to update this project' } return { success: false, error: 'Not authorized to update this project' }
} }
// Generate a unique filename const filename = `${projectId}/thumbnail.png`
const timestamp = Date.now()
const filename = `${projectId}/${timestamp}.png`
// Upload to Supabase Storage // Upload to Supabase Storage (upsert to override existing thumbnail)
const { data: uploadData, error: uploadError } = await supabase.storage const { data: uploadData, error: uploadError } = await supabase.storage
.from('project-thumbnails') .from('project-thumbnails')
.upload(filename, blob, { .upload(filename, blob, {
contentType: 'image/png', contentType: 'image/png',
upsert: false, upsert: true,
}) })
if (uploadError) { if (uploadError) {
@@ -1041,7 +1039,7 @@ export async function uploadProjectThumbnail(
.from('project-thumbnails') .from('project-thumbnails')
.getPublicUrl(uploadData.path) .getPublicUrl(uploadData.path)
const thumbnailUrl = urlData.publicUrl const thumbnailUrl = `${urlData.publicUrl}?t=${Date.now()}`
// Update the project with the new thumbnail URL // Update the project with the new thumbnail URL
const { error: updateError } = await (supabase const { error: updateError } = await (supabase
@@ -22,6 +22,7 @@ interface ProjectStore {
fetchActiveProject: () => Promise<void> fetchActiveProject: () => Promise<void>
setActiveProject: (projectId: string) => Promise<void> setActiveProject: (projectId: string) => Promise<void>
initialize: () => Promise<void> initialize: () => Promise<void>
updateActiveThumbnail: (thumbnailUrl: string) => void
} }
export const useProjectStore = create<ProjectStore>((set, get) => ({ export const useProjectStore = create<ProjectStore>((set, get) => ({
@@ -78,6 +79,15 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
} }
}, },
// Patch the active project's thumbnail URL in place (no refetch)
updateActiveThumbnail: (thumbnailUrl: string) => {
set((state) => ({
activeProject: state.activeProject
? { ...state.activeProject, thumbnail_url: thumbnailUrl }
: null,
}))
},
// Initialize - fetch both projects and active project // Initialize - fetch both projects and active project
initialize: async () => { initialize: async () => {
set({ isLoading: true }) set({ isLoading: true })
+5
View File
@@ -2,6 +2,11 @@ import type { NextConfig } from 'next'
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
transpilePackages: ['three', '@pascal-app/viewer', '@pascal-app/core'], transpilePackages: ['three', '@pascal-app/viewer', '@pascal-app/core'],
experimental: {
serverActions: {
bodySizeLimit: '10mb',
},
},
images: { images: {
remotePatterns: [ remotePatterns: [
{ {