Ajouts du front de la page contenant les chats, de la creation de chat, des composants d'alerte, de profil et la page de modification de profil.

This commit is contained in:
Bilal Dieumegard 2024-12-01 15:04:54 +01:00
parent 267dcdf5ea
commit 1c967dd1db
16 changed files with 1070 additions and 2 deletions

View file

@ -36,6 +36,8 @@
"vite": "^5.0.3" "vite": "^5.0.3"
}, },
"dependencies": { "dependencies": {
"lucide-svelte": "^0.462.0",
"multer": "^1.4.5-lts.1",
"svelte-radix": "^2.0.1" "svelte-radix": "^2.0.1"
} }
} }

View file

@ -0,0 +1,49 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition'; // Importer fade et fly
export let message: string = ""; // Le message d'alerte
export let onClose: () => void = () => {}; // Fonction de fermeture de l'alerte
export let duration: number = 5000;
// Fonction pour fermer l'alerte
const closeAlert = () => {
onClose();
};
setTimeout(() => {
closeAlert();
}, duration);
</script>
<!-- Alerte avec animation d'apparition (glissement et fade) -->
<div class="fixed top-4 right-4 bg-blue-500 text-white px-6 py-3 rounded-lg shadow-lg"
in:fly={{ y: -20, opacity: 0, duration: 300 }}
out:fly={{ y: -20, opacity: 0, duration: 300 }}>
<span>{message}</span>
<!-- Bouton pour fermer l'alerte -->
<button on:click={closeAlert} class="ml-4 text-xl">&times;</button>
</div>
<style>
/* Styles de l'alerte */
.alert {
position: absolute;
top: 20px;
right: 20px;
background-color: #3182ce;
color: white;
padding: 10px 20px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
button {
background: transparent;
border: none;
color: white;
font-size: 1.2em;
cursor: pointer;
}
</style>

View file

@ -0,0 +1,19 @@
<script lang="ts">
export let title: string; // Nom ou titre du chat
export let lastMessage: string; // Dernier message affiché
export let time: string; // Heure du dernier message
</script>
<div class="chat-item p-4 border rounded-md hover:bg-gray-100 cursor-pointer flex justify-between items-center">
<div>
<p class="font-semibold text-lg">{title}</p>
<p class="text-sm text-gray-500 truncate">{lastMessage}</p>
</div>
<p class="text-xs text-gray-400">{time}</p>
</div>
<style>
.chat-item {
transition: background-color 0.2s ease-in-out;
}
</style>

View file

@ -0,0 +1,105 @@
<script lang="ts">
export let profilePicture: File | null = null;
const defaultImage = '/profile-default.svg'; // Remplacez par votre image par défaut
// Gérer le changement de fichier sélectionné
const handleFileChange = (event: Event) => {
const input = event.target as HTMLInputElement;
if (input.files?.length) {
profilePicture = input.files[0];
}
};
// Supprimer l'image
const handleDelete = () => {
profilePicture = null;
};
</script>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
}
.file-upload-btn {
position: relative;
display: inline-block;
background-color: #3182ce;
color: white;
font-size: 16px;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 10px;
}
.file-upload-btn:hover {
background-color: #2563eb;
}
.file-upload-btn:active {
background-color: #1e40af;
}
.file-input {
display: none;
}
.image-preview {
margin-top: 20px;
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 50%; /* Arrondir l'image en cercle */
border: 4px solid #3182ce; /* Bordure autour de l'image */
}
.action-buttons {
margin-top: 20px;
}
.action-buttons button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</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>

View file

@ -0,0 +1,28 @@
<script lang="ts">
export let name: string = "Nom de l'utilisateur";
export let imageUrl: string = "https://via.placeholder.com/150"; // URL de l'image du profil
export let bio: string = "Une courte bio sur l'utilisateur.";
export let onClick: () => void = () => {}; // Fonction pour ouvrir un modal ou autre action
</script>
<div class="bg-white shadow-lg rounded-lg overflow-hidden w-64 cursor-pointer" on:click={onClick}>
<!-- Image de profil -->
<img src={imageUrl} alt="Photo de profil" class="w-full h-32 object-cover" />
<!-- Détails du profil -->
<div class="p-4">
<h3 class="text-lg font-bold">{name}</h3>
<p class="text-gray-600 text-sm">{bio}</p>
</div>
</div>
<style>
div {
transition: transform 0.3s ease;
}
div:hover {
transform: scale(1.05);
}
</style>

View file

@ -0,0 +1,36 @@
<script lang="ts">
export let name: string = "Nom de l'utilisateur";
export let imageUrl: string = "https://via.placeholder.com/150";
export let bio: string = "Bio de l'utilisateur détaillée.";
export let onClose: () => void = () => {}; // Fonction pour fermer le modal
</script>
<div class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center" on:click={onClose}>
<div class="bg-white rounded-lg w-96 p-8" on:click|stopPropagation>
<div class="flex justify-between items-center">
<h2 class="text-2xl font-bold">{name}</h2>
<button on:click={onClose} class="text-gray-600 hover:text-gray-900">×</button>
</div>
<div class="mt-4 text-center">
<img src={imageUrl} alt="Photo de profil" class="w-24 h-24 rounded-full mx-auto mb-4" />
<p class="text-gray-800">{bio}</p>
</div>
<div class="mt-6 text-center">
<button on:click={onClose} class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
Fermer
</button>
</div>
</div>
</div>
<style>
div {
transition: opacity 0.3s ease, transform 0.3s ease;
}
div:hover {
opacity: 1;
}
</style>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { Search } from "lucide-svelte"; // Icône de recherche depuis Lucide Svelte
export let placeholder: string = "Rechercher..."; // Texte par défaut pour l'input
</script>
<div class="w-full">
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
<input
type="text"
placeholder={placeholder}
class="w-full pl-10 pr-4 py-2 border rounded-md focus:outline-none focus:ring focus:border-blue-300"
/>
</div>

View file

@ -0,0 +1,123 @@
import { json } from '@sveltejs/kit';
import prisma from '$lib/prismaClient';
import redisClient from '$lib/redisClient'; // Assurez-vous d'importer le client Redis
// Récupérer les informations du canal et le dernier message (avec cache Redis)
export async function GET({ params }) {
const canalId = parseInt(params.id);
// Clé cache pour les informations du canal et le dernier message
const canalCacheKey = `canal:${canalId}:info`;
try {
// Vérifier si les informations du canal et le dernier message sont dans le cache Redis
const cachedCanalData = await redisClient.get(canalCacheKey);
if (cachedCanalData) {
console.log('✅ Cache hit pour les informations du canal et le dernier message');
return json(JSON.parse(cachedCanalData));
}
console.log('❌ Cache miss');
// Si non, récupérer les informations du canal et le dernier message depuis Prisma
const canal = await prisma.canal.findUnique({
where: { id: canalId },
include: {
users: true, // Inclut les utilisateurs associés au canal
},
});
if (!canal) {
return json({ error: 'Canal non trouvé' }, { status: 404 });
}
// Récupérer le dernier message
const lastMessage = await prisma.message.findFirst({
where: { canalId },
include: {
user: { select: { id: true, pseudo: true } },
},
orderBy: { createdAt: 'desc' }, // Trie par date décroissante, donc le dernier message est récupéré en premier
});
// Créer un objet combiné pour le canal et le dernier message
const canalData = {
canal,
lastMessage, // Inclure uniquement le dernier message
};
// Mettre en cache les informations du canal et le dernier message pendant 5 minutes
await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 300); // Cache pendant 5 minutes
console.log('❌ Cache miss - Mise en cache des résultats');
return json(canalData);
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la récupération du canal ou du dernier message' }, { status: 500 });
}
}
// Supprimer un canal et invalider le cache associé
export async function DELETE({ params }) {
const canalId = parseInt(params.id);
try {
// Supprimer le canal de la base de données
await prisma.canal.delete({
where: { id: canalId },
});
// Invalider le cache
await redisClient.del(`canal:${canalId}:info`);
return json({ message: 'Canal supprimé avec succès' });
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la suppression du canal' }, { status: 500 });
}
}
// Modifier un canal
export async function PUT({ params, request }) {
const canalId = parseInt(params.id);
const { nom, domaine } = await request.json(); // On suppose que ce sont les champs à mettre à jour
// Clé cache pour les informations du canal et le dernier message
const canalCacheKey = `canal:${canalId}:info`;
try {
// Mettre à jour les informations du canal dans la base de données
const updatedCanal = await prisma.canal.update({
where: { id: canalId },
data: {
nom, // Nom du canal
domaine, // Domaine du canal
},
include: {
users: true, // Inclut les utilisateurs associés au canal
},
});
// Récupérer le dernier message associé au canal après mise à jour
const lastMessage = await prisma.message.findFirst({
where: { canalId },
include: {
user: { select: { id: true, pseudo: true } },
},
orderBy: { createdAt: 'desc' },
});
// Créer un objet combiné pour les nouvelles informations du canal et le dernier message
const canalData = {
canal: updatedCanal,
lastMessage, // Inclure uniquement le dernier message
};
// Mettre en cache les nouvelles informations pendant 5 minutes
await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 60 * 5); // Cache pendant 5 minutes
return json(canalData);
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la mise à jour du canal' }, { status: 500 });
}
}

View file

@ -0,0 +1,149 @@
import { json } from '@sveltejs/kit';
import prisma from '$lib/prismaClient';
import redisClient from '$lib/redisClient'; // Assure-toi d'importer ton client Redis
export async function GET({ params, url }) {
const canalId = parseInt(params.id);
// Gestion de la pagination avec des paramètres optionnels `page` et `limit`
const page = parseInt(url.searchParams.get('page')) || 1;
const limit = parseInt(url.searchParams.get('limit')) || 10;
const offset = (page - 1) * limit;
// Générer une clé cache Redis unique en fonction du canal et des paramètres de pagination
const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
try {
// 1. Vérifier si les messages sont déjà dans le cache Redis
const cachedMessages = await redisClient.get(cacheKey);
if (cachedMessages) {
console.log('✅ Cache hit');
return json(JSON.parse(cachedMessages)); // Si les données sont en cache, les retourner
}
// 2. Si les messages ne sont pas en cache, récupérer depuis la base de données
const messages = await prisma.message.findMany({
where: { canalId },
include: {
user: {
select: { id: true, pseudo: true }, // Inclut uniquement lID et le pseudo de lutilisateur
},
},
orderBy: {
createdAt: 'asc', // Trie par date croissante
},
skip: offset,
take: limit,
});
// 3. Compter le nombre total de messages pour la pagination
const totalMessages = await prisma.message.count({
where: { canalId },
});
const response = {
messages,
pagination: {
page,
limit,
totalMessages,
totalPages: Math.ceil(totalMessages / limit),
},
};
// 4. Mettre en cache les messages avec une expiration (par exemple 5 minutes)
await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5); // Cache pendant 5 minutes
console.log('❌ Cache miss - Mise en cache des résultats');
return json(response); // Retourner les données récupérées
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la récupération des messages' }, { status: 500 });
}
}
export async function POST({ params, request }) {
const canalId = parseInt(params.id);
const { userId, text } = await request.json();
try {
// Créer un nouveau message dans la base de données
const newMessage = await prisma.message.create({
data: {
userId,
canalId,
text,
},
include: { user: { select: { id: true, pseudo: true } } },
});
updateCaches(); // Mettre à jour les caches après la création dun nouveau message
return json(newMessage, { status: 201 });
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la création du message' }, { status: 500 });
}
}
export async function DELETE({ params }) {
const messageId = parseInt(params.id);
try {
// Supprimer le message de la base de données
await prisma.message.delete({
where: { id: messageId },
});
updateCaches(); // Mettre à jour les caches après la suppression dun message
return json({ message: 'Message supprimé avec succès' });
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la suppression du message' }, { status: 500 });
}
}
// Fonction pour mettre à jour tous les caches des messages
function updateCaches(canalId) {
// Mettre à jour tous les caches
// Mettre à jour toutes les pages dans le cache
let page : number = 1;
let limit : number = 10;
let offset : number = (page - 1) * limit;
while (true) {
const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
const cachedMessages = await redisClient.get(cacheKey);
if (!cachedMessages) {
break;
}
const totalMessages = await prisma.message.count({
where: { canalId },
});
const messages = await prisma.message.findMany({
where: { canalId },
include: {
user: {
select: { id: true, pseudo: true },
},
},
orderBy: {
createdAt: 'asc',
},
skip: offset,
take: limit,
});
const response = {
messages,
pagination: {
page,
limit,
totalMessages,
totalPages: Math.ceil(totalMessages / limit),
},
};
await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5);
page++;
offset = (page - 1) * limit;
}
}

View file

@ -0,0 +1,47 @@
import { json } from '@sveltejs/kit';
import prisma from '$lib/prismaClient';
import redisClient from '$lib/redisClient';
// GET: Liste tous les canaux
export async function GET() {
try {
const cachedCanaux = await redisClient.get('canaux');
if (cachedCanaux) {
console.log('✅ Cache hit');
return json(JSON.parse(cachedCanaux));
}
console.log('❌ Cache miss');
const canaux = await prisma.canal.findMany({
include: { users: true, messages: true }, // Inclut les relations
});
await redisClient.set('canaux', JSON.stringify(canaux), { EX: 600 }); // Met en cache
return json(canaux);
} catch (err) {
console.error(err);
return json({ error: 'Erreur serveur' }, { status: 500 });
}
}
export async function POST({ request }) {
const { nom, domaine, userIds } = await request.json();
try {
const canal = await prisma.canal.create({
data: {
nom,
domaine,
users: {
connect: userIds.map((id) => ({ id })), // Associe des utilisateurs au canal
},
},
});
return json(canal, { status: 201 });
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la création du canal' }, { status: 500 });
}
}

View file

@ -0,0 +1,241 @@
import { json } from '@sveltejs/kit';
import redisClient from '$lib/redisClient';
import prisma from '$lib/prismaClient';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
const destinationDir = '/uploads';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, `.${destinationDir}'); // Dossier où les images sont stockées`
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
fileFilter(req, file, cb) {
const fileExtension = path.extname(file.originalname).toLowerCase();
if (fileExtension !== '.jpg' && fileExtension !== '.jpeg' && fileExtension !== '.png') {
return cb(new Error('Seules les images JPG, JPEG et PNG sont autorisées.'));
}
cb(null, true);
}
});
const upload = multer({ storage });
export async function GET({ params }) {
const userId = params.id;
try {
// Vérifier si l'utilisateur est dans le cache Redis
const cachedUser = await redisClient.get(`user:${userId}`);
if (cachedUser) {
console.log('✅ Cache hit');
return json(JSON.parse(cachedUser));
}
console.log('❌ Cache miss');
// Si non, récupérer depuis MongoDB via Prisma
const user = await prisma.user.findUnique({
where: { id: parseInt(userId) },
});
if (!user) {
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
}
// Mettre l'utilisateur en cache
await redisClient.set(`user:${userId}`, JSON.stringify(user), { EX: 3600 });
return json(user);
} catch (err) {
console.error(err);
return json({ error: 'Erreur serveur' }, { status: 500 });
}
}
export async function POST({ request }) {
return new Promise((resolve, reject) => {
// Utilisation de multer pour récupérer le fichier
upload.single('profilePicture')(request.raw, request.raw, async (err) => {
if (err) {
console.error('Erreur de téléchargement:', err);
return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
}
// Récupérer les données du formulaire (sans le fichier)
const { pseudo, nom, prenom, email, password } = await request.json();
// L'URL de l'image sera le chemin relatif à partir du dossier uploads
const imageUrl = request.file ? `${destinationDir}/${request.file.filename}` : null;
try {
// Créer un nouvel utilisateur avec l'URL de l'image
const user = await prisma.user.create({
data: {
pseudo,
nom,
prenom,
email,
password,
profilePictureUrl: imageUrl, // Stocker l'URL de l'image
},
});
// Mettre l'utilisateur dans le cache Redis
await redisClient.set(`user:${user.id}`, JSON.stringify(user), { EX: 3600 });
// Réponse avec les données de l'utilisateur
return resolve(json(user, { status: 201 }));
} catch (error) {
console.error('Erreur lors de la création de l\'utilisateur:', error);
return reject(json({ error: 'Erreur lors de la création de l\'utilisateur' }, { status: 500 }));
}
});
});
}
// Mettre à jour un utilisateur avec PUT
export async function PUT({ params, request }) {
const userId = parseInt(params.id);
const cachedUser = await redisClient.get(`user:${userId}`);
// Récupérer l'utilisateur à partir de la base de données
let existingUser;
if (cachedUser) {
console.log('✅ Cache hit');
// Si l'utilisateur est dans le cache, on le parse
existingUser = JSON.parse(cachedUser);
} else {
// Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
console.log('❌ Cache miss');
existingUser = await prisma.user.findUnique({
where: { id: userId },
});
if (!existingUser) {
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
}
// Utilisation de multer pour récupérer l'image (si présente)
return new Promise((resolve, reject) => {
upload.single('profilePicture')(request.raw, request.raw, async (err) => {
if (err) {
console.error('Erreur de téléchargement:', err);
return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
}
// Extraire les autres données (pseudo, nom, etc.) du body de la requête
const { pseudo, nom, prenom, email, password } = await request.json();
let updatedUserData = {
pseudo,
nom,
prenom,
email,
password, // Assurez-vous de bien sécuriser les mots de passe
};
// Si une nouvelle image est envoyée
if (request.file) {
// Vérifiez si l'utilisateur a déjà une image
if (existingUser.profilePictureUrl) {
// Supprimer l'ancienne image
const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
if (fs.existsSync(oldImagePath)) {
fs.unlinkSync(oldImagePath); // Suppression du fichier
}
}
// Ajouter la nouvelle image à la base de données
updatedUserData.profilePictureUrl = `${destinationDir}/${request.file.filename}`;
} else if (!request.file && existingUser.profilePictureUrl) {
// Si aucune image n'est envoyée, supprimer l'image actuelle
const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
if (fs.existsSync(oldImagePath)) {
fs.unlinkSync(oldImagePath); // Supprimer l'ancienne image
}
// Mettre à jour l'URL de l'image en null
updatedUserData.profilePictureUrl = null;
}
try {
// Mettre à jour l'utilisateur dans la base de données
const updatedUser = await prisma.user.update({
where: { id: userId },
data: updatedUserData,
});
// Mettre à jour l'utilisateur dans le cache Redis
await redisClient.set(`user:${userId}`, JSON.stringify(updatedUser), 'EX', 3600); // Cache pendant 1 heure (3600 secondes)
// Réponse avec l'utilisateur mis à jour
return resolve(json(updatedUser));
} catch (error) {
console.error('Erreur lors de la mise à jour de l\'utilisateur:', error);
return reject(json({ error: 'Erreur lors de la mise à jour de l\'utilisateur' }, { status: 500 }));
}
});
});
}
export async function DELETE({ params }) {
const userId = parseInt(params.id);
try {
// Vérifier si l'utilisateur est dans le cache Redis
const cachedUser = await redisClient.get(`user:${userId}`);
let userToDelete;
if (cachedUser) {
console.log('✅ Cache hit');
// Si l'utilisateur est dans le cache, on le parse
userToDelete = JSON.parse(cachedUser);
} else {
// Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
console.log('❌ Cache miss');
userToDelete = await prisma.user.findUnique({
where: { id: userId },
});
if (!userToDelete) {
return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
}
// Mettre l'utilisateur dans le cache Redis
await redisClient.set(`user:${userId}`, JSON.stringify(userToDelete), { EX: 3600 }); // Cache pendant 1 heure
}
// Si l'utilisateur a une image de profil, la supprimer
if (userToDelete.profilePictureUrl) {
// Calculer le chemin du fichier à supprimer
const imagePath = `.${destinationDir}/${path.basename(userToDelete.profilePictureUrl)}`;
if (fs.existsSync(imagePath)) {
fs.unlinkSync(imagePath); // Supprimer le fichier image
}
}
// Supprimer l'utilisateur de la base de données
await prisma.user.delete({
where: { id: userId },
});
// Supprimer l'utilisateur du cache Redis
await redisClient.del(`user:${userId}`);
// Réponse après suppression réussie
return json({ message: 'Utilisateur et image supprimés avec succès' });
} catch (err) {
console.error(err);
return json({ error: 'Erreur lors de la suppression de lutilisateur' }, { status: 500 });
}
}

View file

@ -0,0 +1,27 @@
// src/routes/api/users/+server.js
import { json } from '@sveltejs/kit';
import redisClient from '$lib/redisClient';
import prisma from '$lib/prismaClient';
export async function GET() {
try {
// Vérifier si les utilisateurs sont dans le cache Redis
const cachedUsers = await redisClient.get('users');
if (cachedUsers) {
console.log('✅ Cache hit');
return json(JSON.parse(cachedUsers));
}
console.log('❌ Cache miss');
// Sinon, récupérer les utilisateurs depuis MongoDB
const users = await prisma.user.findMany();
// Mettre les utilisateurs en cache
await redisClient.set('users', JSON.stringify(users), { EX: 600 });
return json(users);
} catch (err) {
console.error(err);
return json({ error: 'Erreur serveur' }, { status: 500 });
}
}

View file

@ -1,3 +1,53 @@
<script></script> <script lang="ts">
import Alert from "$lib/components/ui/Alert.svelte"; // Importer le composant Alert
Salut let chatName = "";
let showAlert = false;
let alertMessage = "";
// Fonction pour valider la création du chat
const createChat = () => {
if (chatName.trim()) {
alertMessage = `Le chat "${chatName}" a été créé avec succès.`;
showAlert = true;
} else {
alertMessage = "Veuillez entrer un nom pour le chat.";
showAlert = true;
}
};
</script>
<!-- Container principal centré -->
<div class="flex items-center justify-center h-screen bg-gray-100">
<div class="bg-white border border-gray-300 rounded-lg p-8 w-96">
<h1 class="text-2xl font-bold mb-6 text-center">Créer un nouveau chat</h1>
<!-- Champ pour entrer le nom du chat -->
<input
type="text"
bind:value={chatName}
placeholder="Nom du chat..."
class="w-full border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring focus:border-blue-500 mb-4"
/>
<!-- Bouton pour valider -->
<button
on:click={createChat}
class="bg-blue-500 text-white w-full px-4 py-2 rounded-lg hover:bg-blue-600 focus:outline-none"
>
Créer
</button>
</div>
</div>
<!-- Affichage de l'alerte si elle doit être visible -->
{#if showAlert}
<Alert message={alertMessage} onClose={() => showAlert = false} />
{/if}
<style>
body {
background-color: #f9fafb; /* Couleur de fond claire */
font-family: sans-serif;
}
</style>

View file

@ -0,0 +1,32 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import Plus from "svelte-radix/Plus.svelte"; // Icône pour "Ajouter"
import Search from "$lib/components/ui/Search.svelte";
import ChatItem from "$lib/components/ui/ChatItem.svelte";
</script>
<div class="h-full flex flex-col gap-5 p-5">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2 w-full mr-10">
<div class="relative w-full">
<Search class="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400" />
</div>
</div>
<Button size="default" class="flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white">
<Plus class="h-5 w-5" />
Nouveau Chat
</Button>
</div>
<div class="flex flex-col gap-4 overflow-y-auto">
<ChatItem title="Discussion avec Yanax" lastMessage="Salut les amis !" time="12:34" />
<ChatItem title="Discussion avec Luxray" lastMessage="Salut Yanax" time="12:30" />
</div>
</div>
<style>
.h-full {
height: 100%;
background-color: #f9f9f9;
}
</style>

View file

@ -0,0 +1,140 @@
<script lang="ts">
import { onMount } from 'svelte';
import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte"; // Import du composant
let pseudo = '';
let firstName = '';
let lastName = '';
let email = '';
let profilePicture: File | null = null;
let message = '';
let showMessage = false;
// Fonction pour valider l'email
const validateEmail = (email: string) => {
const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return re.test(email);
};
// Fonction de soumission du formulaire
const handleSubmit = () => {
if (!pseudo || !firstName || !lastName || !email) {
message = 'Veuillez remplir tous les champs.';
showMessage = true;
} else if (!validateEmail(email)) {
message = 'L\'email est invalide.';
showMessage = true;
} else {
// Vous pouvez ici envoyer les données à un serveur via une API
message = 'Informations mises à jour avec succès!';
showMessage = true;
}
};
// Fonction pour gérer le téléchargement de l'image de profil
const handleFileChange = (event: Event) => {
const input = event.target as HTMLInputElement;
if (input.files?.length) {
profilePicture = input.files[0];
}
};
// 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>
<div class="flex items-center justify-center min-h-screen bg-gray-100 mg-10">
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-lg">
<h2 class="text-2xl font-semibold text-center mb-6">Modifier les informations du compte</h2>
<!-- Message d'alerte -->
{#if showMessage}
<div class="bg-blue-500 text-white p-3 rounded-lg text-center mb-4">
{message}
</div>
{/if}
<!-- Formulaire de modification du profil -->
<form on:submit|preventDefault={handleSubmit}>
<div class="mb-4">
<label for="pseudo" class="block text-sm font-semibold text-gray-700">Pseudo</label>
<input
type="text"
id="pseudo"
bind:value={pseudo}
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Votre pseudo"
/>
</div>
<div class="mb-4">
<label for="firstName" class="block text-sm font-semibold text-gray-700">Prénom</label>
<input
type="text"
id="firstName"
bind:value={firstName}
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Votre prénom"
/>
</div>
<div class="mb-4">
<label for="lastName" class="block text-sm font-semibold text-gray-700">Nom</label>
<input
type="text"
id="lastName"
bind:value={lastName}
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Votre nom"
/>
</div>
<div class="mb-4">
<label for="email" class="block text-sm font-semibold text-gray-700">Email</label>
<input
type="email"
id="email"
bind:value={email}
class="w-full p-2 mt-1 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Votre email"
/>
</div>
<!-- Intégration du composant de photo de profil -->
<div class="mb-4">
<ChoosePicture profilePicture={profilePicture} onFileChange={handleFileChange} />
{#if profilePicture}
<div class="mt-2 text-sm text-gray-600">Image sélectionnée : {profilePicture.name}</div>
{/if}
</div>
<div class="mt-6 flex justify-center">
<button
type="submit"
class="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 focus:outline-none"
>
Mettre à jour
</button>
</div>
</form>
</div>
</div>
<style>
/* Supprimez le sélecteur body si non utilisé */
/* body {
background-color: #f8fafc;
font-family: sans-serif;
} */
input, button {
font-family: inherit;
}
</style>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<path fill="#494c4e" d="M9 0a9 9 0 0 0-9 9 8.654 8.654 0 0 0 .05.92 9 9 0 0 0 17.9 0A8.654 8.654 0 0 0 18 9a9 9 0 0 0-9-9zm5.42 13.42c-.01 0-.06.08-.07.08a6.975 6.975 0 0 1-10.7 0c-.01 0-.06-.08-.07-.08a.512.512 0 0 1-.09-.27.522.522 0 0 1 .34-.48c.74-.25 1.45-.49 1.65-.54a.16.16 0 0 1 .03-.13.49.49 0 0 1 .43-.36l1.27-.1a2.077 2.077 0 0 0-.19-.79v-.01a2.814 2.814 0 0 0-.45-.78 3.83 3.83 0 0 1-.79-2.38A3.38 3.38 0 0 1 8.88 4h.24a3.38 3.38 0 0 1 3.1 3.58 3.83 3.83 0 0 1-.79 2.38 2.814 2.814 0 0 0-.45.78v.01a2.077 2.077 0 0 0-.19.79l1.27.1a.49.49 0 0 1 .43.36.16.16 0 0 1 .03.13c.2.05.91.29 1.65.54a.49.49 0 0 1 .25.75z"/>
</svg>

After

Width:  |  Height:  |  Size: 843 B