Add multiple images

This commit is contained in:
MartinBraquet
2025-07-31 14:50:12 +02:00
parent 9c50509643
commit ce5448166f
7 changed files with 496 additions and 402 deletions

View File

@@ -45,7 +45,7 @@ export default function Header() {
<div className="flex items-center space-x-4">
<Link
href="/profile"
className="text-blue-600 hover:text-blue-800 px-3 py-2 text-sm font-medium"
className="text-blue-600 dark:text-blue-100 hover:text-blue-800 dark:hover:text-blue-300 px-3 py-2 text-sm font-medium"
>
My Profile
</Link>

View File

@@ -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(

View File

@@ -28,6 +28,8 @@ function RegisterComponent() {
const [conflictStyle, setConflictStyle] = useState('');
const [image, setImage] = useState<string | null>(null);
const [key, setKey] = useState<string | null>(null);
const [images, setImages] = useState<string[]>([]);
const [keys, setKeys] = useState<string[]>([]);
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<HTMLInputElement>) => {
const handleImagesUpload = async (e: ChangeEvent<HTMLInputElement>) => {
return handleImageUpload(e, false);
}
const handleImageUpload = async (e: ChangeEvent<HTMLInputElement>, 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() {
</div>
)}
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="flex justify-center mb-6">
<div className="relative">
<div className="h-32 w-32 rounded-full overflow-hidden border-4 border-white shadow-md">
{image ? (
<Image
src={image}
alt="Profile"
width={128}
height={128}
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-gray-200 flex items-center justify-center">
<div className="flex justify-center mb-6">
<div className="relative">
<div className="h-32 w-32 rounded-full overflow-hidden border-4 border-white shadow-md">
{image ? (
<Image
src={image}
alt="Profile"
width={128}
height={128}
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-gray-200 flex items-center justify-center">
<span className="text-4xl text-gray-500">
{session?.user?.name?.charAt(0).toUpperCase() || 'U'}
</span>
</div>
)}
</div>
<label
className="absolute -bottom-2 -right-2 bg-blue-600 text-white rounded-full p-2 cursor-pointer hover:bg-blue-700 transition-colors"
title="Upload photo"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd"
d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z"
clipRule="evenodd"/>
</svg>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
accept="image/*"
className="hidden"
disabled={isUploading}
/>
</label>
</div>
)}
</div>
<label
className="absolute -bottom-2 -right-2 bg-blue-600 text-white rounded-full p-2 cursor-pointer hover:bg-blue-700 transition-colors"
title="Upload photo"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd"
d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z"
clipRule="evenodd"/>
</svg>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
accept="image/*"
className="hidden"
disabled={isUploading}
/>
</label>
</div>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="space-y-4">
<div>
<label htmlFor="gender" className={headingStyle}>
Gender <span className="text-red-500">*</span>
Gender
</label>
<select
id="gender"
name="gender"
required
// required
value={gender || ''}
onChange={(e) => setGender(e.target.value as Gender)}
className="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
@@ -591,6 +614,69 @@ function RegisterComponent() {
</div>
</div>
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-900 mb-3">Photos</h3>
<div className="grid grid-cols-3 gap-4">
{Array.from(new Set(images)).map((img, index) => (
<div key={index}
className="relative group aspect-square rounded-lg overflow-hidden border border-gray-200">
<Image
src={img}
alt={`Uploaded image ${index + 1}`}
width={200}
height={200}
className="h-full w-full object-cover"
/>
<button
type="button"
onClick={() => {
setImages(prev => prev.filter((_, i) => i !== index));
setKeys(prev => prev.filter((_, i) => i !== index));
}}
className="absolute top-2 right-2 bg-red-500 text-white rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity"
title="Remove image"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"/>
</svg>
</button>
</div>
))}
{Array.from(new Set(images)).length < 9 && (
<label
className="aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-gray-400 mb-1" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
<span className="text-sm text-gray-500">
{images.length === 0 ? 'Add photos' : 'Add more'}
</span>
<span className="text-xs text-gray-400">
{9 - Array.from(new Set(images)).length} remaining
</span>
<input
type="file"
ref={fileInputRef}
onChange={handleImagesUpload}
accept="image/*"
className="hidden"
disabled={isUploading || Array.from(new Set(images)).length >= 9}
multiple
/>
</label>
)}
</div>
{images.length === 0 && (
<p className="mt-1 text-sm text-gray-500">
Add up to 9 photos to your profile
</p>
)}
</div>
<div>
<button
type="submit"

View File

@@ -1,36 +1,12 @@
'use client';
import Link from 'next/link';
import {useEffect, useState} from "react";
import LoadingSpinner from "@/lib/client/LoadingSpinner";
import {parseImage} from "@/lib/client/media";
import {usePathname} from "next/navigation";
import {getProfile} from "@/lib/client/profile";
export default function ProfilePage() {
const pathname = usePathname(); // Get the current route
const [userData, setUserData] = useState<any>(null);
const [image, setImage] = useState<string | null>(null);
try {
useEffect(() => {
async function fetchImage() {
const res = await fetch('/api/profile');
const data = await res.json();
setUserData(data);
console.log('userData', data);
if (data?.image) {
await parseImage(data.image, setImage);
}
}
fetchImage();
}, []);
if (!userData) {
return <LoadingSpinner/>;
}
console.log('userData', userData);
const header = (
<div className="px-4 py-5 sm:px-6 flex justify-between items-center">
@@ -49,11 +25,12 @@ export default function ProfilePage() {
return (
<div className="min-h-screen py-12 px-4 sm:px-6 lg:px-8">
{getProfile(userData, image, header)}
{getProfile('/api/profile', header)}
</div>
)
;
} catch (error) {
} catch
(error) {
console.error('Error fetching user data:', error);
return <div className="text-center py-10">Error loading profile. Please try again later.</div>;
}

View File

@@ -1,54 +1,17 @@
'use client';
import {useEffect, useState} from "react";
import {useParams} from "next/navigation";
import LoadingSpinner from "@/lib/client/LoadingSpinner";
import {ProfileData} from "@/lib/client/schema";
import {parseImage} from "@/lib/client/media";
import {getProfile} from "@/lib/client/profile";
export const dynamic = "force-dynamic"; // This disables SSG and ISR
export default function Post() {
const {id} = useParams();
const [userData, setUserData] = useState<ProfileData | null>(null);
const [image, setImage] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchImage() {
const res = await fetch(`/api/profiles/${id}`);
console.log('res', res);
if (res.ok) {
const data = await res.json();
setUserData(data);
console.log('userData', data);
if (data?.image) {
await parseImage(data.image, setImage);
}
}
setLoading(false);
}
fetchImage();
}, []);
if (loading) {
return <LoadingSpinner/>;
}
if (!userData) {
return <div>
<h1 className="text-center">Profile not found</h1>
</div>;
}
console.log(`Image: ${image}`)
return (
<div className="min-h-screen py-12 px-4 sm:px-6 lg:px-8">
{getProfile(userData, image)}
{getProfile(`/api/profiles/${id}`)}
</div>
);
}

View File

@@ -16,20 +16,25 @@ export interface ProfileData {
};
}
export async function parseImage(img: string, setImage: any) {
export async function parseImage(img: string, setImage: any, batch = false) {
if (!img) {
return;
}
if (img.startsWith('http')) {
console.log(`img: ${img}`)
setImage(img);
} else {
let url = img;
if (!img.startsWith('http')) {
const imageResponse = await fetch(`/api/download?key=${img}`);
console.log(`imageResponse: ${imageResponse}`)
if (imageResponse.ok) {
const imageBlob = await imageResponse.json();
const imageUrl = imageBlob['url'];
setImage(imageUrl);
url = imageBlob['url'];
}
}
if (url) {
if (batch) {
setImage(prev => [...prev, url]);
} else {
setImage(url);
}
}
}

View File

@@ -1,267 +1,320 @@
import {ProfileData} from "@/lib/client/schema";
import Image from "next/image";
import {pStyle} from "@/lib/client/constants";
import {useEffect, useState} from "react";
import {parseImage} from "@/lib/client/media";
import LoadingSpinner from "@/lib/client/LoadingSpinner";
export function getProfile(url, header = null) {
const [loading, setLoading] = useState(true);
const [userData, setUserData] = useState<any>(null);
const [image, setImage] = useState<string | null>(null);
const [images, setImages] = useState<string[]>([]);
useEffect(() => {
async function fetchImage() {
const res = await fetch(url);
const data = await res.json();
setUserData(data);
console.log('userData', data);
if (data?.image) {
await parseImage(data.image, setImage);
}
setImages([]);
await Promise.all(
(data?.profile?.images || []).map(async (img) => {
await parseImage(img, setImages, true);
})
);
console.log('images', data?.profile?.images);
setLoading(false);
}
fetchImage();
}, []);
if (loading) {
return <LoadingSpinner/>;
}
if (!userData) {
return <div>
<h1 className="text-center">Profile not found</h1>
</div>;
}
console.log('userData', userData);
export function getProfile(userData: ProfileData, image, header = null) {
return (
<article className="max-w-3xl mx-auto shadow-lg rounded-lg overflow-hidden">
{header}
{/* Profile Header with Image */}
< div
className="bg-gradient-to-r h-16 relative">
{
image ? (
<div className="absolute -bottom-16 left-8">
<div className="h-32 w-32 rounded-full border-4 border-white overflow-hidden ">
<Image
src={image}
alt={userData.name || 'Profile picture'}
className="h-full w-full object-cover"
width={200}
height={200}
// onError={(e) => {
// const target = e.target as HTMLImageElement;
// target.onerror = null;
// target.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(profile.name || 'U')}&background=random`;
// }}
/>
</div>
<article className="max-w-3xl mx-auto shadow-lg rounded-lg overflow-hidden">
{header}
{/* Profile Header with Image */}
< div
className="bg-gradient-to-r h-32 relative">
{
image ? (
<div className="absolute -bottom-16 left-8">
<div className="h-32 w-32 rounded-full border-4 border-white overflow-hidden ">
<Image
src={image}
alt={userData.name || 'Profile picture'}
className="h-full w-full object-cover"
width={200}
height={200}
// onError={(e) => {
// const target = e.target as HTMLImageElement;
// target.onerror = null;
// target.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(profile.name || 'U')}&background=random`;
// }}
/>
</div>
) :
(
<div
className="absolute -bottom-16 left-8 h-32 w-32 rounded-full border-4 border-white bg-gray-200 flex items-center justify-center">
</div>
) :
(
<div
className="absolute -bottom-16 left-8 h-32 w-32 rounded-full border-4 border-white bg-gray-200 flex items-center justify-center">
<span className="text-4xl font-bold text-gray-600">
{userData.name ? userData.name.charAt(0).toUpperCase() : 'U'}
</span>
</div>
)
}
</div>
{/* Profile Content */
</div>
)
}
<div className="pt-20 px-8 pb-8">
<h1 className="text-3xl font-bold mb-2">
{userData.name}
</h1>
</div>
< div
className="space-y-6 pt-4 border-t border-gray-200">
{/* Profile Content */
}
<div className="pt-20 px-8 pb-8">
<h1 className="text-3xl font-bold mb-2">
{userData.name}
</h1>
{userData?.profile?.desiredConnections && (
< div
className="space-y-6 pt-4 border-t border-gray-200">
{userData?.profile?.desiredConnections && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Desired
Connections </h2>
< ul
className="flex flex-wrap gap-2 mt-1">
{userData?.profile?.desiredConnections.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.connection?.name
}
</li>
))
}
</ul>
</div>
)
}
{
userData?.profile?.gender && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Gender </h2>
< p
className="mt-1 capitalize"> {userData.profile.gender} </p>
</div>
)
}
{
userData?.profile?.birthYear && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Age </h2>
< p
className="mt-1 capitalize"> {new Date().getFullYear() - userData.profile.birthYear} </p>
</div>
)
}
{
userData?.profile?.location && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Location </h2>
< p
className={pStyle}> {userData.profile.location} </p>
</div>
)
}
{
userData?.profile?.occupation && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Occupation </h2>
< p
className={pStyle}> {userData.profile.occupation} </p>
</div>
)
}
{
userData?.profile?.personalityType && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Personality
Type </h2>
< p
className={pStyle}> {userData.profile.personalityType} </p>
</div>
)
}
{
userData?.profile?.conflictStyle && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Conflict
Style </h2>
< p
className={pStyle}> {userData.profile.conflictStyle} </p>
</div>
)
}
{
userData?.profile?.intellectualInterests && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Desired
Connections </h2>
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Interests </h2>
< ul
className="flex flex-wrap gap-2 mt-1">
{userData?.profile?.desiredConnections.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.connection?.name
}
</li>
))
{
userData.profile.intellectualInterests.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.interest?.name
}
</li>
))
}
</ul>
</div>
)
}
}
{
userData?.profile?.gender && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Gender </h2>
< p
className="mt-1 capitalize"> {userData.profile.gender} </p>
</div>
)
}
{
userData?.profile?.causeAreas && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Cause
Areas </h2>
{
userData?.profile?.birthYear && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Age </h2>
< p
className="mt-1 capitalize"> {new Date().getFullYear() - userData.profile.birthYear} </p>
</div>
)
}
< ul
className="flex flex-wrap gap-2 mt-1">
{
userData.profile.causeAreas.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.causeArea?.name
}
</li>
))
}
</ul>
</div>
)
}
{
userData?.profile?.location && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Location </h2>
< p
className={pStyle}> {userData.profile.location} </p>
</div>
)
}
{
userData?.profile?.description && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> About </h2>
< p
className={pStyle}> {userData.profile.description} </p>
</div>
)
}
{
userData?.profile?.occupation && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Occupation </h2>
< p
className={pStyle}> {userData.profile.occupation} </p>
</div>
)
}
{
userData?.profile?.contactInfo && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Contact </h2>
< p
className={pStyle}> {userData.profile.contactInfo} </p>
</div>
)
}
{
userData?.profile?.personalityType && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Personality
Type </h2>
< p
className={pStyle}> {userData.profile.personalityType} </p>
</div>
)
}
{
userData?.profile?.promptAnswers && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Prompt
Answers </h2>
{
userData?.profile?.conflictStyle && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Conflict
Style </h2>
< p
className={pStyle}> {userData.profile.conflictStyle} </p>
</div>
)
}
< ul
className="flex flex-wrap gap-2 mt-1">
{
userData.profile.promptAnswers.map((value, idx) => (
<li
key={idx}
// className="px-3 py-1 text-sm bg-gray-100 rounded-full hover:bg-gray-200 transition"
>
{value.prompt} {value.answer}
</li>
))
}
</ul>
</div>
)
}
{
userData?.profile?.intellectualInterests && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Interests </h2>
{
images &&
<div className="mb-8">
{/*<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Photos </h2>*/}
<div className="grid grid-cols-3 gap-4 mb-4">
{Array.from(new Set(images)).map((img, index) => ( // Set is a hack to avoid a bug where duplicates fill in images when we navigate different pages
<div key={index}
className="relative group aspect-square rounded-lg overflow-hidden border border-gray-200 ">
<Image
src={img}
alt={`Uploaded image ${index + 1}`}
width={150}
height={150}
className="h-full w-full object-cover"
/>
</div>
))}
</div>
</div>
}
< ul
className="flex flex-wrap gap-2 mt-1">
{
userData.profile.intellectualInterests.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.interest?.name
}
</li>
))
}
</ul>
</div>
)
}
{/*<div>*/
}
{/* <h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider">Creation Date</h2>*/
}
{/* <p className={pStyle}>*/
}
{/* {user.profile.createdAt}*/
}
{/* {new Date(user.profile.createdAt).toLocaleDateString("en-US", {*/
}
{/* year: "numeric",*/
}
{/* month: "long",*/
}
{/* day: "numeric",*/
}
{/* })}*/
}
{/* </p>*/
}
{/*</div>*/
}
{
userData?.profile?.causeAreas && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Cause
Areas </h2>
< ul
className="flex flex-wrap gap-2 mt-1">
{
userData.profile.causeAreas.map((value, idx) => (
<li
key={idx}
className="px-3 py-1 text-sm bg-blue-100 text-blue-800 dark:text-white dark:bg-gray-700 rounded-full hover:bg-gray-200 transition"
>
{value?.causeArea?.name
}
</li>
))
}
</ul>
</div>
)
}
{
userData?.profile?.description && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> About </h2>
< p
className={pStyle}> {userData.profile.description} </p>
</div>
)
}
{
userData?.profile?.contactInfo && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Contact </h2>
< p
className={pStyle}> {userData.profile.contactInfo} </p>
</div>
)
}
{
userData?.profile?.promptAnswers && (
<div className="mt-3"><
h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Prompt
Answers </h2>
< ul
className="flex flex-wrap gap-2 mt-1">
{
userData.profile.promptAnswers.map((value, idx) => (
<li
key={idx}
// className="px-3 py-1 text-sm bg-gray-100 rounded-full hover:bg-gray-200 transition"
>
{value.prompt} {value.answer}
</li>
))
}
</ul>
</div>
)
}
{
userData?.profile?.images && (
<div>
<h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider"> Images </h2>
< p
className={pStyle}> {userData.profile.images} </p> TODO
</div>
)
}
{/*<div>*/
}
{/* <h2 className="text-sm font-medium text-gray-500 uppercase tracking-wider">Creation Date</h2>*/
}
{/* <p className={pStyle}>*/
}
{/* {user.profile.createdAt}*/
}
{/* {new Date(user.profile.createdAt).toLocaleDateString("en-US", {*/
}
{/* year: "numeric",*/
}
{/* month: "long",*/
}
{/* day: "numeric",*/
}
{/* })}*/
}
{/* </p>*/
}
{/*</div>*/
}
</div>
</div>
</article>
</div>
</article>
)
;