Ajout de la modification des user avec photo de profil etc et des informations de profil dans les messages
This commit is contained in:
parent
2d4cea3c4e
commit
ab3e81d020
7 changed files with 186 additions and 97 deletions
|
@ -6,19 +6,19 @@
|
||||||
|
|
||||||
export let myMessage: boolean; // Si c'est le message de l'utilisateur courant
|
export let myMessage: boolean; // Si c'est le message de l'utilisateur courant
|
||||||
|
|
||||||
export let user = null; // Infos utilisateur
|
export let message = null; // Contenu du message
|
||||||
export let messageContent = ""; // Contenu du message
|
|
||||||
export let createdAt = new Date(); // Date de création du message
|
|
||||||
|
|
||||||
let defaultProfilePicture = "/images/default-profile.png";
|
let defaultProfilePicture = "/images/default-profile.png";
|
||||||
let showProfileInfo = false; // Contrôle la visibilité des informations de profil
|
|
||||||
|
export let setActiveProfile;
|
||||||
|
export let activeProfileId = null;
|
||||||
|
|
||||||
// Temps écoulé (calculé périodiquement)
|
// Temps écoulé (calculé périodiquement)
|
||||||
let timeElapsed: string;
|
let timeElapsed: string;
|
||||||
|
|
||||||
// Fonction pour mettre à jour le temps écoulé
|
// Fonction pour mettre à jour le temps écoulé
|
||||||
const updateElapsed = () => {
|
const updateElapsed = () => {
|
||||||
timeElapsed = formatDistanceToNow(createdAt);
|
timeElapsed = formatDistanceToNow(message.createdAt);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialisation de l'intervalle
|
// Initialisation de l'intervalle
|
||||||
|
@ -30,6 +30,17 @@
|
||||||
clearInterval(interval); // Nettoyage lors du démontage
|
clearInterval(interval); // Nettoyage lors du démontage
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function toggleProfileInfo() {
|
||||||
|
if (activeProfileId === message.id) {
|
||||||
|
// Si le profil cliqué est déjà actif, le fermer
|
||||||
|
setActiveProfile(null);
|
||||||
|
} else {
|
||||||
|
// Sinon, afficher ce profil et masquer les autres
|
||||||
|
setActiveProfile(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card.Root class="relative">
|
<Card.Root class="relative">
|
||||||
|
@ -45,25 +56,24 @@
|
||||||
<div class="flex items-center gap-3 {myMessage ? 'flex-row-reverse' : 'flex-row'}">
|
<div class="flex items-center gap-3 {myMessage ? 'flex-row-reverse' : 'flex-row'}">
|
||||||
<div
|
<div
|
||||||
class="relative"
|
class="relative"
|
||||||
on:mouseenter={() => (showProfileInfo = true)}
|
on:click={toggleProfileInfo}
|
||||||
on:mouseleave={() => (showProfileInfo = false)}
|
|
||||||
>
|
>
|
||||||
<!-- Image de profil -->
|
<!-- Image de profil -->
|
||||||
<img
|
<img
|
||||||
src={user.profilePicture ? `http://localhost:5173/${user.profilePicture}` : defaultProfilePicture}
|
src={message.user.profilePicture ? `http://localhost:5173/${message.user.profilePicture}` : defaultProfilePicture}
|
||||||
alt="Profile Picture"
|
alt="Profile Picture"
|
||||||
class="h-10 w-10 rounded-full border border-gray-300"
|
class="h-10 w-10 rounded-full border border-gray-300 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Infos du profil (affichées au survol) -->
|
<!-- Infos du profil (affichées au survol) -->
|
||||||
<ProfileInfo user={user} show={showProfileInfo} position={myMessage ? "right" : "left"} />
|
<ProfileInfo user={message.user} show={activeProfileId === message.id} position={myMessage} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col text-right {myMessage ? 'text-right' : 'text-left'}">
|
<div class="flex flex-col text-right {myMessage ? 'text-right' : 'text-left'}">
|
||||||
<Card.Title
|
<Card.Title
|
||||||
class="text-gray-800 text-sm sm:text-base md:text-lg truncate {myMessage ? 'font-black' : ''}"
|
class="text-gray-800 text-sm sm:text-base md:text-lg truncate {myMessage ? 'font-bold' : ''}"
|
||||||
>
|
>
|
||||||
{myMessage ? "(Moi)" : ""} {user.username}
|
{myMessage ? "(Moi)" : ""} {message.user.username}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +81,7 @@
|
||||||
|
|
||||||
<!-- Contenu du message -->
|
<!-- Contenu du message -->
|
||||||
<Card.Content class="text-sm sm:text-base md:text-lg text-gray-700">
|
<Card.Content class="text-sm sm:text-base md:text-lg text-gray-700">
|
||||||
<p>{messageContent}</p>
|
<p>{message.text}</p>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,62 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let profilePicture: File | null = null;
|
import { onMount } from 'svelte';
|
||||||
const defaultImage = '/profile-default.svg'; // Remplacez par votre image par défaut
|
|
||||||
|
|
||||||
// Gérer le changement de fichier sélectionné
|
export let profilePicture: File | null = null;
|
||||||
|
export let defaultImage = '/profile-default.svg'; // Image par défaut si aucune image n'est sélectionnée
|
||||||
|
|
||||||
|
let clientPicture: string | null = profilePicture ? `/${profilePicture}` : defaultImage;
|
||||||
|
|
||||||
|
// Fonction exécutée lorsque l'utilisateur sélectionne une image
|
||||||
const handleFileChange = (event: Event) => {
|
const handleFileChange = (event: Event) => {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
if (input.files?.length) {
|
if (input.files?.length) {
|
||||||
profilePicture = input.files[0];
|
profilePicture = input.files[0]; // Affectation du fichier sélectionné
|
||||||
|
clientPicture = URL.createObjectURL(profilePicture); // Prévisualisation de l'image
|
||||||
|
} else {
|
||||||
|
clientPicture = null;
|
||||||
|
profilePicture = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Supprimer l'image
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
profilePicture = null;
|
profilePicture = null;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Conteneur principal -->
|
||||||
|
<div class="container">
|
||||||
|
<!-- Image de profil ou image par défaut -->
|
||||||
|
<img
|
||||||
|
src={clientPicture}
|
||||||
|
alt="Image de profil"
|
||||||
|
class="image-preview mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Sélectionner une image -->
|
||||||
|
<label for="profilePicture" class="file-upload-btn">
|
||||||
|
Sélectionner une image
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="profilePicture"
|
||||||
|
class="file-input"
|
||||||
|
accept="image/*"
|
||||||
|
on:change={handleFileChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<!-- Bouton Supprimer l'image -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600"
|
||||||
|
on:click={handleDelete}
|
||||||
|
disabled={!profilePicture}
|
||||||
|
>
|
||||||
|
Supprimer l'image
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -69,37 +110,3 @@
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Conteneur principal -->
|
|
||||||
<div class="container">
|
|
||||||
<!-- Image de profil ou image par défaut -->
|
|
||||||
<img
|
|
||||||
src={profilePicture ? URL.createObjectURL(profilePicture) : defaultImage}
|
|
||||||
alt="Image de profil"
|
|
||||||
class="image-preview mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Sélectionner une image -->
|
|
||||||
<label for="profilePicture" class="file-upload-btn">
|
|
||||||
Sélectionner une image
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="profilePicture"
|
|
||||||
class="file-input"
|
|
||||||
accept="image/*"
|
|
||||||
on:change={handleFileChange}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<!-- Bouton Supprimer l'image -->
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600"
|
|
||||||
on:click={handleDelete}
|
|
||||||
disabled={!profilePicture}
|
|
||||||
>
|
|
||||||
Supprimer l'image
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -23,18 +23,29 @@
|
||||||
console.error('Erreur lors de la déconnexion:', error);
|
console.error('Erreur lors de la déconnexion:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editProfile = () => {
|
||||||
|
window.location.href = '/user/edit';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<div class="overlay" role="dialog" aria-labelledby="profile-card-title" on:click={onClose}>
|
<div class="overlay" role="dialog" aria-labelledby="profile-card-title" on:click={onClose}>
|
||||||
<div class="profile-card" on:click|stopPropagation>
|
<div class="profile-card flex flex-col gap-5" on:click|stopPropagation>
|
||||||
<div class="profile-header">
|
<div class="flex flex-col gap-2">
|
||||||
<!-- Image de profil -->
|
<div class="profile-header">
|
||||||
<img src={user.profilePicture} alt="Profile" class="profile-image" />
|
<!-- Image de profil -->
|
||||||
<h2 id="profile-card-title" class="profile-name">{user.username}</h2>
|
<img src={user.profilePicture} alt="Profile" class="profile-image" />
|
||||||
|
<h2 id="profile-card-title" class="profile-name">{user.username}</h2>
|
||||||
|
</div>
|
||||||
|
<p>{user.name} {user.surname}</p>
|
||||||
</div>
|
</div>
|
||||||
<p>{user.name} {user.surname}</p>
|
|
||||||
<Button on:click={disconnect}>Deconnecter</Button>
|
<div class="flex flex-col gap-3">
|
||||||
|
<Button on:click={editProfile}>Editer</Button>
|
||||||
|
<Button on:click={disconnect} variant="destructive">Déconnexion</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -65,7 +76,7 @@
|
||||||
|
|
||||||
.profile-header {
|
.profile-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<div class="user-info" style="left: {position === 'left' ? 'auto' : '0'}; right: {position === 'right' ? '0' : 'auto'};">
|
<div class="user-info {position ? 'left' : 'right' }">
|
||||||
<h3>{user.pseudo}</h3>
|
<h2 class="text-lg font-semibold">{user.username}</h2>
|
||||||
<p>{user.name} {user.surname}</p>
|
<p class="text-sm text-gray-500">{user.email}</p>
|
||||||
<p>{user.description}</p>
|
<p class="text-sm text-gray-500">{user.name} {user.surname}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -18,12 +18,13 @@
|
||||||
top: 0; /* Aligner en haut */
|
top: 0; /* Aligner en haut */
|
||||||
left: auto;
|
left: auto;
|
||||||
right: auto;
|
right: auto;
|
||||||
width: 200px; /* Largeur appropriée */
|
width: 200px; /* Largeur par défaut */
|
||||||
background: rgba(255, 255, 255, 0.95); /* Fond semi-transparent */
|
background: rgba(255, 255, 255, 0.95); /* Fond semi-transparent */
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
z-index: 10; /* Gérer les priorités d'affichage */
|
z-index: 10; /* Gérer les priorités d'affichage */
|
||||||
|
transition: all 0.3s ease-in-out; /* Animation fluide pour les changements */
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info.left {
|
.user-info.left {
|
||||||
|
@ -33,5 +34,32 @@
|
||||||
.user-info.right {
|
.user-info.right {
|
||||||
left: 110%; /* Position à droite avec un espace */
|
left: 110%; /* Position à droite avec un espace */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Media Queries pour adapter le style */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.user-info {
|
||||||
|
width: 150px; /* Réduire la largeur sur les écrans plus petits */
|
||||||
|
padding: 10px; /* Réduire le padding */
|
||||||
|
font-size: 0.9rem; /* Réduire la taille de la police */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.user-info {
|
||||||
|
position: fixed; /* Position fixe pour éviter les débordements */
|
||||||
|
left: 10px; /* Centrer avec un padding interne */
|
||||||
|
right: 10px;
|
||||||
|
width: auto; /* Utiliser toute la largeur disponible */
|
||||||
|
max-width: 90%; /* Limiter la largeur pour éviter les débordements */
|
||||||
|
padding: 8px; /* Réduire le padding davantage */
|
||||||
|
font-size: 0.8rem; /* Police encore plus petite */
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info.left,
|
||||||
|
.user-info.right {
|
||||||
|
left: 10px; /* Ignorer les positionnements relatifs */
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,12 @@
|
||||||
let scrollContainer: HTMLElement;
|
let scrollContainer: HTMLElement;
|
||||||
let messageText = '';
|
let messageText = '';
|
||||||
|
|
||||||
|
let activeProfileId = null;
|
||||||
|
|
||||||
|
function setActiveProfile(id) {
|
||||||
|
activeProfileId = id;
|
||||||
|
}
|
||||||
|
|
||||||
let socket = initSocket(); // Initialiser le socket
|
let socket = initSocket(); // Initialiser le socket
|
||||||
|
|
||||||
async function sendMessage() {
|
async function sendMessage() {
|
||||||
|
@ -144,7 +150,12 @@
|
||||||
<!-- Afficher les messages (mock d'un utilisateur sélectionné ou aucun message par défaut) -->
|
<!-- Afficher les messages (mock d'un utilisateur sélectionné ou aucun message par défaut) -->
|
||||||
{#if messages !== undefined && messages.length > 0}
|
{#if messages !== undefined && messages.length > 0}
|
||||||
{#each messages as message}
|
{#each messages as message}
|
||||||
<Message myMessage={data.userId == message.user.id} user={message.user} messageContent={message.text} createdAt={message.createdAt} />
|
<Message
|
||||||
|
myMessage={data.userId == message.user.id}
|
||||||
|
message={message}
|
||||||
|
activeProfileId={activeProfileId}
|
||||||
|
setActiveProfile={setActiveProfile}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div>
|
<div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div>
|
||||||
|
|
23
src/routes/user/edit/+page.server.ts
Normal file
23
src/routes/user/edit/+page.server.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
export async function load({ fetch, locals }) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Appel API ou récupération de données
|
||||||
|
const res = await fetch(`/api/users/${locals.userId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const user = await res.json();
|
||||||
|
|
||||||
|
// Retourner les données à la page sous forme de props
|
||||||
|
return {
|
||||||
|
user
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des canaux:', error);
|
||||||
|
return {
|
||||||
|
user : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,16 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte"; // Import du composant
|
import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte"; // Import du composant
|
||||||
|
|
||||||
let pseudo = '';
|
export let data;
|
||||||
let firstName = '';
|
const user = data.user;
|
||||||
let lastName = '';
|
console.log(user);
|
||||||
let email = '';
|
|
||||||
let profilePicture: File | null = null;
|
let pseudo = user.username;
|
||||||
|
let firstName = user.name;
|
||||||
|
let lastName = user.surname;
|
||||||
|
let email = user.email;
|
||||||
|
|
||||||
|
let profilePicture = user.profilePicture; // Chemin initial ou valeur null
|
||||||
|
|
||||||
let message = '';
|
let message = '';
|
||||||
let showMessage = false;
|
let showMessage = false;
|
||||||
|
@ -26,28 +31,31 @@
|
||||||
message = 'L\'email est invalide.';
|
message = 'L\'email est invalide.';
|
||||||
showMessage = true;
|
showMessage = true;
|
||||||
} else {
|
} else {
|
||||||
// Vous pouvez ici envoyer les données à un serveur via une API
|
updateUser();
|
||||||
message = 'Informations mises à jour avec succès!';
|
message = 'Informations mises à jour avec succès!';
|
||||||
showMessage = true;
|
showMessage = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour gérer le téléchargement de l'image de profil
|
async function updateUser() {
|
||||||
const handleFileChange = (event: Event) => {
|
const formData = new FormData();
|
||||||
const input = event.target as HTMLInputElement;
|
formData.append('username', pseudo);
|
||||||
if (input.files?.length) {
|
formData.append('name', firstName);
|
||||||
profilePicture = input.files[0];
|
formData.append('surname', lastName);
|
||||||
}
|
formData.append('email', email);
|
||||||
};
|
|
||||||
|
if (profilePicture) {
|
||||||
|
formData.append('profilePicture', profilePicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`/api/users/${user.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: formData, // Transmet les données comme multipart/form-data
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
|
||||||
// Simulation de données au chargement
|
|
||||||
onMount(() => {
|
|
||||||
pseudo = '';
|
|
||||||
firstName = '';
|
|
||||||
lastName = '';
|
|
||||||
email = '';
|
|
||||||
profilePicture = null; // ou une valeur par défaut si vous en avez une
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center justify-center min-h-screen bg-gray-100 mg-10">
|
<div class="flex items-center justify-center min-h-screen bg-gray-100 mg-10">
|
||||||
|
@ -109,10 +117,7 @@
|
||||||
|
|
||||||
<!-- Intégration du composant de photo de profil -->
|
<!-- Intégration du composant de photo de profil -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<ChoosePicture profilePicture={profilePicture} onFileChange={handleFileChange} />
|
<ChoosePicture bind:profilePicture={profilePicture} />
|
||||||
{#if profilePicture}
|
|
||||||
<div class="mt-2 text-sm text-gray-600">Image sélectionnée : {profilePicture.name}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 flex justify-center">
|
<div class="mt-6 flex justify-center">
|
||||||
|
@ -128,12 +133,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Supprimez le sélecteur body si non utilisé */
|
|
||||||
/* body {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
} */
|
|
||||||
|
|
||||||
input, button {
|
input, button {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue