diff --git a/myfavstuff/app/edit-item/[id]/actions.js b/myfavstuff/app/edit-item/[id]/actions.js new file mode 100644 index 0000000..03d3e8b --- /dev/null +++ b/myfavstuff/app/edit-item/[id]/actions.js @@ -0,0 +1,61 @@ +// app/edit-item/[id]/actions.js +'use server'; + +import { supabase } from '@/lib/supabaseClient'; +import { revalidatePath } from 'next/cache'; + +export async function updateItem(formData) { + if (!supabase) { + return { error: 'Supabase client is not initialized. Cannot update item.' }; + } + + const id = formData.get('id'); + if (!id) { + return { error: 'Item ID is required for updating.' }; + } + + // The picture_url is now received directly from the client + // after client-side upload to Supabase Storage if a new image was selected + + const updatedItem = { + title: formData.get('title'), + type: formData.get('type'), + rating: formData.get('rating') ? parseInt(formData.get('rating'), 10) : null, + notes: formData.get('notes'), + picture_url: formData.get('picture_url'), + }; + + // Basic validation + if (!updatedItem.title || !updatedItem.type) { + return { error: 'Title and Type are required.' }; + } + if (updatedItem.rating !== null && (updatedItem.rating < 1 || updatedItem.rating > 5)) { + return { error: 'Rating must be between 1 and 5.' }; + } + + try { + const { data, error } = await supabase + .from('items') + .update(updatedItem) + .eq('id', id) + .select(); // .select() to get the updated data back + + if (error) { + console.error('Supabase update error:', error); + return { error: `Failed to update item: ${error.message}` }; + } + + console.log('Item updated successfully:', data); + revalidatePath('/'); // Revalidate the homepage to show the updated item + + return { + success: true, + message: 'Item updated successfully!', + updatedItem: data ? data[0] : null + }; + + } catch (e) { + console.error('Error in updateItem action:', e); + return { error: 'An unexpected error occurred.' }; + } +} diff --git a/myfavstuff/app/edit-item/[id]/page.js b/myfavstuff/app/edit-item/[id]/page.js new file mode 100644 index 0000000..305b041 --- /dev/null +++ b/myfavstuff/app/edit-item/[id]/page.js @@ -0,0 +1,325 @@ +'use client'; + +import { useState, useEffect, useTransition, useRef } from 'react'; +import { useRouter } from 'next/navigation'; +import { supabase } from '@/lib/supabaseClient'; +import { updateItem } from './actions'; + +export default function EditItemPage({ params }) { + const router = useRouter(); + const { id } = params; + const formRef = useRef(null); + + const [isPending, startTransition] = useTransition(); + const [isLoading, setIsLoading] = useState(true); + const [isUploading, setIsUploading] = useState(false); + const [item, setItem] = useState(null); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(''); + + // Fetch the item data + useEffect(() => { + async function fetchItem() { + setIsLoading(true); + setError(null); + + if (!supabase) { + setError('Could not connect to the database.'); + setIsLoading(false); + return; + } + + try { + const { data, error } = await supabase + .from('items') + .select('*') + .eq('id', id) + .single(); + + if (error) { + throw error; + } + + if (!data) { + throw new Error('Item not found'); + } + + setItem(data); + } catch (err) { + console.error('Error fetching item:', err); + setError(err.message || 'Failed to load item'); + } finally { + setIsLoading(false); + } + } + + if (id) { + fetchItem(); + } + }, [id]); + + const handleSubmit = async (event) => { + event.preventDefault(); + setError(null); + setSuccessMessage(''); + setIsUploading(true); + + try { + const formData = new FormData(event.currentTarget); + const pictureFile = formData.get('picture_file'); + let pictureUrl = item.picture_url; // Keep existing URL by default + + // Handle image upload directly from client if a new file is selected + if (pictureFile && pictureFile.size > 0) { + // Upload to Supabase Storage directly from client + const BUCKET_NAME = 'item_images'; + const fileName = `public/${Date.now()}-${pictureFile.name.replace(/[^a-zA-Z0-9.]/g, '_')}`; + + const { data: uploadData, error: uploadError } = await supabase.storage + .from(BUCKET_NAME) + .upload(fileName, pictureFile); + + if (uploadError) { + throw new Error(`Failed to upload image: ${uploadError.message}`); + } + + const { data: publicUrlData } = supabase.storage + .from(BUCKET_NAME) + .getPublicUrl(fileName); + + if (!publicUrlData || !publicUrlData.publicUrl) { + throw new Error('Failed to get image public URL after upload.'); + } + + pictureUrl = publicUrlData.publicUrl; + } + + // Create a new FormData without the file to reduce payload size + const serverFormData = new FormData(); + serverFormData.append('id', id); + serverFormData.append('title', formData.get('title')); + serverFormData.append('type', formData.get('type')); + + if (formData.get('rating')) { + serverFormData.append('rating', formData.get('rating')); + } + + if (formData.get('notes')) { + serverFormData.append('notes', formData.get('notes')); + } + + // Send the image URL instead of the file + if (pictureUrl) { + serverFormData.append('picture_url', pictureUrl); + } + + // Call server action with the form data (minus the image file) + startTransition(async () => { + const result = await updateItem(serverFormData); + if (result.error) { + setError(result.error); + } else if (result.success) { + setSuccessMessage(result.message); + // Redirect after a delay + setTimeout(() => router.push('/'), 1500); + } + }); + } catch (err) { + setError(err.message); + } finally { + setIsUploading(false); + } + }; + + const handleDeleteClick = () => { + if (confirm('Are you sure you want to delete this item? This cannot be undone.')) { + startTransition(async () => { + try { + const { error } = await supabase + .from('items') + .delete() + .eq('id', id); + + if (error) { + setError(`Failed to delete: ${error.message}`); + return; + } + + setSuccessMessage('Item deleted successfully'); + setTimeout(() => router.push('/'), 1500); + } catch (err) { + setError(err.message || 'Failed to delete item'); + } + }); + } + }; + + if (isLoading) { + return ( +
+
+
+
+
+
+
+
+ ); + } + + if (error && !item) { + return ( +
+
+

Error

+

{error}

+ +
+
+ ); + } + + return ( +
+
+

Edit Item

+

+ Update information about "{item?.title || 'this item'}" +

+
+ + {error && ( +
+

Error:

+

{error}

+
+ )} + + {successMessage && ( +
+

Success:

+

{successMessage}

+
+ )} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + {item?.picture_url && ( +
+

Current image:

+ {item.title +

Upload a new image to replace the current one

+
+ )} + + +
+ +
+ + +
+ +
+ + +
+ + + +
+
+
+
+ ); +} diff --git a/myfavstuff/app/page.js b/myfavstuff/app/page.js index eb1113f..09275cc 100644 --- a/myfavstuff/app/page.js +++ b/myfavstuff/app/page.js @@ -1,5 +1,7 @@ import { supabase, safeQuery } from '../lib/supabaseClient'; +import Link from 'next/link'; + // Function to generate star ratings const StarRating = ({ rating }) => { const totalStars = 5; @@ -65,7 +67,11 @@ export default async function Home() { {items.length > 0 && (
{items.map((item) => ( -
+ {item.picture_url ? (
) : ( -
+
No Image
)} -

{item.title || 'Untitled'}

+

{item.title || 'Untitled'}

{item.type || 'N/A'}

{typeof item.rating === 'number' && } {item.notes && ( @@ -87,11 +93,13 @@ export default async function Home() { {item.notes}

)} - {/* Placeholder for a details link/modal trigger */} - {/* - View Details - */} -
+
+ Edit Item + + + +
+ ))}
)}