From ce5448166fb50d59a5b061ce4a3b2f92a07914da Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Thu, 31 Jul 2025 14:50:12 +0200 Subject: [PATCH] Add multiple images --- app/Header.tsx | 2 +- app/api/upload/route.ts | 80 +++--- app/complete-profile/page.tsx | 210 ++++++++++---- app/profile/page.tsx | 29 +- app/profiles/[id]/page.tsx | 39 +-- lib/client/media.ts | 19 +- lib/client/profile.tsx | 519 +++++++++++++++++++--------------- 7 files changed, 496 insertions(+), 402 deletions(-) diff --git a/app/Header.tsx b/app/Header.tsx index a334032b..9d0d8836 100644 --- a/app/Header.tsx +++ b/app/Header.tsx @@ -45,7 +45,7 @@ export default function Header() {
My Profile diff --git a/app/api/upload/route.ts b/app/api/upload/route.ts index 4d34b6ed..5fb4ad9c 100644 --- a/app/api/upload/route.ts +++ b/app/api/upload/route.ts @@ -20,51 +20,61 @@ export async function POST(request: Request) { } const formData = await request.formData(); - const file = formData.get('file') as File | null; + console.log('formData', formData); + let files = formData.get('files') as File | null | File[]; - if (!file) { - return NextResponse.json({error: 'No file provided'}, {status: 400}); + if (files?.name) { + files = [files]; } - // Validate file type - if (!file.type.startsWith('image/')) { - return NextResponse.json({error: 'Only image files are allowed'}, {status: 400}); - } + const results = await Promise.all( + files.map(async (file) => { + if (!file) { + return NextResponse.json({error: 'No file provided'}, {status: 400}); + } - // Validate file size (5MB max) - if (file.size > 5 * 1024 * 1024) { - return NextResponse.json({error: 'File size must be less than 5MB'}, {status: 400}); - } + // Validate file type + if (!file.type.startsWith('image/')) { + return NextResponse.json({error: 'Only image files are allowed'}, {status: 400}); + } - const fileExtension = file.name.split('.').pop(); - const fileName = `${uuidv4()}.${fileExtension}`; - const fileBuffer = await file.arrayBuffer(); - const key = `profile-pictures/${fileName}`; + // Validate file size (5MB max) + if (file.size > 5 * 1024 * 1024) { + return NextResponse.json({error: 'File size must be less than 5MB'}, {status: 400}); + } - const uploadParams = { - Bucket: process.env.AWS_S3_BUCKET_NAME!, - Key: key, - Body: Buffer.from(fileBuffer), - ContentType: file.type, - }; + const fileExtension = file.name.split('.').pop(); + const fileName = `${uuidv4()}.${fileExtension}`; + const fileBuffer = await file.arrayBuffer(); + const key = `profile-pictures/${fileName}`; - const response = await s3Client.send(new PutObjectCommand(uploadParams)); - console.log(`Response: ${response}`); + const uploadParams = { + Bucket: process.env.AWS_S3_BUCKET_NAME!, + Key: key, + Body: Buffer.from(fileBuffer), + ContentType: file.type, + }; - // get signed url - const url = await getSignedUrl( - s3Client, - new GetObjectCommand({ - Bucket: process.env.AWS_S3_BUCKET_NAME!, - Key: key, - }), - {expiresIn: 300} // 5 minutes + const response = await s3Client.send(new PutObjectCommand(uploadParams)); + console.log(`Response: ${response}`); + + // get signed url + const url = await getSignedUrl( + s3Client, + new GetObjectCommand({ + Bucket: process.env.AWS_S3_BUCKET_NAME!, + Key: key, + }), + {expiresIn: 300} // 5 minutes + ); + + console.log(`Signed URL: ${url}`); + // const fileUrl = `${process.env.AWS_S3_BUCKET_NAME}/profile-pictures/${fileName}`; + return {url: url, key: key}; + }) ); - console.log(`Signed URL: ${url}`); - // const fileUrl = `${process.env.AWS_S3_BUCKET_NAME}/profile-pictures/${fileName}`; - - return NextResponse.json({url: url, key: key}); + return NextResponse.json(results); } catch (error) { console.error('Upload error:', error); return NextResponse.json( diff --git a/app/complete-profile/page.tsx b/app/complete-profile/page.tsx index 81b8e342..c6850558 100644 --- a/app/complete-profile/page.tsx +++ b/app/complete-profile/page.tsx @@ -28,6 +28,8 @@ function RegisterComponent() { const [conflictStyle, setConflictStyle] = useState(''); const [image, setImage] = useState(null); const [key, setKey] = useState(null); + const [images, setImages] = useState([]); + const [keys, setKeys] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(''); @@ -41,8 +43,6 @@ function RegisterComponent() { const router = useRouter(); const {data: session, update} = useSession(); - console.log('image', image) - // Fetch user profile data useEffect(() => { async function fetchUserProfile() { @@ -52,6 +52,7 @@ function RegisterComponent() { const response = await fetch('/api/profile'); if (response.ok) { const userData = await response.json(); + await parseImage(userData.image, setImage); if (userData?.profile) { const {profile} = userData; setDescription(profile.description || ''); @@ -63,7 +64,6 @@ function RegisterComponent() { if (profile.birthYear) { setAge(new Date().getFullYear() - profile.birthYear); } - await parseImage(profile.image, setImage); // Set selected interests if any if (profile.intellectualInterests?.length > 0) { @@ -71,9 +71,14 @@ function RegisterComponent() { .map((pi: any) => pi.interest.id); setSelectedInterests(new Set(interestIds)); } - } - if (userData?.image) { - await parseImage(userData.image, setImage); + + setImages([]) + setKeys(profile?.images) + await Promise.all( + (profile?.images || []).map(async (img) => { + await parseImage(img, setImages, true); + }) + ); } } } catch (error) { @@ -123,7 +128,11 @@ function RegisterComponent() { ); } - const handleImageUpload = async (e: ChangeEvent) => { + const handleImagesUpload = async (e: ChangeEvent) => { + return handleImageUpload(e, false); + } + + const handleImageUpload = async (e: ChangeEvent, headShot = true) => { const file = e.target.files?.[0]; if (!file) return; @@ -138,7 +147,7 @@ function RegisterComponent() { } const formData = new FormData(); - formData.append('file', file); + formData.append('files', file); try { setIsUploading(true); @@ -155,10 +164,23 @@ function RegisterComponent() { return; } - const {url, key} = await response.json(); - setImage(url); - setKey(key); - } catch (error) { + const results = await response.json(); + + const {url, key} = results[0] + if (headShot) { + setImage(url); + setKey(key); + console.log('headshot', key, url) + } else { + setImages(prev => [...prev, url]); + setKeys(prev => [...prev, key]); + } + // console.log(url, key); + console.log('image', key); + console.log('images', keys); + + } catch + (error) { console.error('Upload error:', error); setError(error instanceof Error ? error.message : 'Failed to upload image'); } finally { @@ -213,39 +235,39 @@ function RegisterComponent() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - - if (!gender) { - setError('Please select your gender'); - return; - } + if (!session?.user?.email) return; try { setIsSubmitting(true); setError(''); - const body = JSON.stringify({ + console.log('submit image', key); + console.log('submit images', keys); + + const data = { profile: { description, contactInfo, location, - gender, - personalityType, - conflictStyle, + gender: gender as Gender, + birthYear: new Date().getFullYear() - age, + personalityType: personalityType as PersonalityType, + conflictStyle: conflictStyle as ConflictStyle, + images: keys, }, interests: Array.from(selectedInterests).map(id => ({ id: id.startsWith('new-') ? undefined : id, name: allInterests.find(i => i.id === id)?.name || id.replace('new-', '') })), ...(key && {image: key}), - }); - console.log(`Body: ${body}`) - + }; + console.log('data', data) const response = await fetch('/api/user/update-profile', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: body, + body: JSON.stringify(data), }); if (!response.ok) { @@ -297,56 +319,57 @@ function RegisterComponent() {
)} -
-
-
-
- {image ? ( - Profile - ) : ( -
+
+
+
+ {image ? ( + Profile + ) : ( +
{session?.user?.name?.charAt(0).toUpperCase() || 'U'} -
- )} -
- +
+ )}
+
+
+ +
= 9} + multiple + /> + + )} +
+ {images.length === 0 && ( +

+ Add up to 9 photos to your profile +

+ )} +
+
+ + ) ;