376 lines
23 KiB
Plaintext
376 lines
23 KiB
Plaintext
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
|
|
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
|
|
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Messenger Facebook Style | RJLResaka</title>
|
|
<meta name="description" content="RJLResaka : discussions privées, groupes, demandes d'amis, réactions et fichiers avec un design clair inspiré de Facebook Messenger.">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="${pageContext.request.contextPath}/assets/css/app.css">
|
|
</head>
|
|
<body class="dashboard-page fb-body">
|
|
<main class="fb-dashboard-layout">
|
|
<aside class="fb-left-column">
|
|
<section class="fb-card profile-card facebook-card">
|
|
<div class="fb-profile-row">
|
|
<div class="avatar xl" style="background:${sessionScope.authUser.avatarColor}">${sessionScope.authUser.initials}</div>
|
|
<div>
|
|
<p class="eyebrow">Compte connecté</p>
|
|
<h1>${sessionScope.authUser.fullName}</h1>
|
|
<p class="muted">@${sessionScope.authUser.username}</p>
|
|
<small>${sessionScope.authUser.email}</small>
|
|
</div>
|
|
</div>
|
|
<div class="stats-row">
|
|
<div>
|
|
<strong>${friendCount}</strong>
|
|
<span>Amis</span>
|
|
</div>
|
|
<div>
|
|
<strong>${requestCount}</strong>
|
|
<span>Demandes</span>
|
|
</div>
|
|
<div>
|
|
<strong>${fn:length(conversations)}</strong>
|
|
<span>Discussions</span>
|
|
</div>
|
|
</div>
|
|
<a class="ghost-link strong" href="${pageContext.request.contextPath}/logout">Déconnexion</a>
|
|
</section>
|
|
|
|
<section class="fb-card facebook-card">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Créer un groupe</p>
|
|
<h2>Style Messenger</h2>
|
|
</div>
|
|
<span class="pill soft">${groupCandidateCount} ami(s)</span>
|
|
</div>
|
|
<form action="${pageContext.request.contextPath}/app/groups/create" method="post" class="stack-form compact-form">
|
|
<label>
|
|
<span>Nom du groupe</span>
|
|
<input type="text" name="groupName" placeholder="Ex: Projet final GL">
|
|
</label>
|
|
<div class="checkbox-grid">
|
|
<c:forEach items="${friends}" var="friend">
|
|
<label class="check-chip">
|
|
<input type="checkbox" name="memberIds" value="${friend.id}">
|
|
<span>${friend.fullName}</span>
|
|
</label>
|
|
</c:forEach>
|
|
<c:if test="${empty friends}">
|
|
<p class="empty-mini">Ajoutez d'abord des amis pour créer un groupe.</p>
|
|
</c:if>
|
|
</div>
|
|
<button class="button primary full" type="submit">Créer le groupe</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="fb-card facebook-card">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Demandes reçues</p>
|
|
<h2>Amis</h2>
|
|
</div>
|
|
</div>
|
|
<div class="request-list">
|
|
<c:forEach items="${pendingRequests}" var="invite">
|
|
<article class="friend-row">
|
|
<div class="avatar large" style="background:${invite.senderAvatarColor}">${invite.senderInitials}</div>
|
|
<div class="friend-copy">
|
|
<strong>${invite.senderName}</strong>
|
|
<small>@${invite.senderUsername}</small>
|
|
</div>
|
|
<div class="friend-actions split">
|
|
<form action="${pageContext.request.contextPath}/app/friends/respond" method="post">
|
|
<input type="hidden" name="requestId" value="${invite.id}">
|
|
<input type="hidden" name="action" value="accept">
|
|
<button class="button tiny primary" type="submit">Accepter</button>
|
|
</form>
|
|
<form action="${pageContext.request.contextPath}/app/friends/respond" method="post">
|
|
<input type="hidden" name="requestId" value="${invite.id}">
|
|
<input type="hidden" name="action" value="decline">
|
|
<button class="button tiny secondary" type="submit">Refuser</button>
|
|
</form>
|
|
</div>
|
|
</article>
|
|
</c:forEach>
|
|
<c:if test="${empty pendingRequests}">
|
|
<p class="empty-mini">Aucune demande pour le moment.</p>
|
|
</c:if>
|
|
</div>
|
|
</section>
|
|
</aside>
|
|
|
|
<section class="fb-center-column">
|
|
<c:if test="${not empty success}">
|
|
<div class="alert success floating">${success}</div>
|
|
</c:if>
|
|
<c:if test="${not empty error}">
|
|
<div class="alert error floating">${error}</div>
|
|
</c:if>
|
|
|
|
<c:choose>
|
|
<c:when test="${not empty activeConversation}">
|
|
<section class="fb-card messenger-shell-card">
|
|
<header class="chat-topbar fb-chat-topbar">
|
|
<div class="chat-partner">
|
|
<div class="avatar xl" style="background:${activeConversation.avatarColor}">${activeConversation.initials}</div>
|
|
<div>
|
|
<div class="chat-title-row">
|
|
<h2>${activeConversation.title}</h2>
|
|
<c:if test="${activeConversation.group}">
|
|
<span class="pill">Groupe</span>
|
|
</c:if>
|
|
<c:if test="${not activeConversation.group}">
|
|
<span class="pill soft">Privé</span>
|
|
</c:if>
|
|
</div>
|
|
<p>${activeConversation.subtitle}</p>
|
|
</div>
|
|
</div>
|
|
<div class="chat-top-actions">
|
|
<span class="pill soft">Thème clair Facebook</span>
|
|
<span class="pill soft">Fichiers + réactions</span>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="messages-stream fb-stream" id="messageStream">
|
|
<c:if test="${empty messages}">
|
|
<article class="empty-chat large-empty">
|
|
<div class="empty-3d-shape"></div>
|
|
<h3>Conversation prête</h3>
|
|
<p>Envoyez votre premier message dans ${activeConversation.title}.</p>
|
|
</article>
|
|
</c:if>
|
|
|
|
<c:forEach items="${messages}" var="message">
|
|
<article class="message-row ${message.mine ? 'mine' : 'other'}">
|
|
<c:if test="${not message.mine}">
|
|
<div class="avatar small" style="background:${message.senderAvatarColor}">${message.senderInitials}</div>
|
|
</c:if>
|
|
|
|
<div class="message-stack">
|
|
<div class="message-meta-top">
|
|
<c:if test="${activeConversation.group && not message.mine}">
|
|
<strong>${message.senderName}</strong>
|
|
</c:if>
|
|
<small><fmt:formatDate value="${message.createdAt}" pattern="dd/MM HH:mm"/></small>
|
|
</div>
|
|
|
|
<div class="message-bubble ${message.mine ? 'mine' : 'other'} ${message.deleted ? 'deleted' : ''}">
|
|
<c:choose>
|
|
<c:when test="${message.deleted}">
|
|
<p class="deleted-copy">Ce message a été supprimé.</p>
|
|
</c:when>
|
|
<c:otherwise>
|
|
<c:if test="${not empty message.body}">
|
|
<p class="message-text">${message.body}</p>
|
|
</c:if>
|
|
<c:if test="${not empty message.attachmentName}">
|
|
<a class="attachment-card" href="${pageContext.request.contextPath}/app/files/download?messageId=${message.id}">
|
|
<span>📎 ${message.attachmentName}</span>
|
|
<small>Télécharger</small>
|
|
</a>
|
|
</c:if>
|
|
<c:if test="${message.edited}">
|
|
<small class="edited-note">Modifié</small>
|
|
</c:if>
|
|
</c:otherwise>
|
|
</c:choose>
|
|
</div>
|
|
|
|
<div class="message-toolbar">
|
|
<div class="reactions-row">
|
|
<c:forEach items="${message.reactions}" var="reaction">
|
|
<form action="${pageContext.request.contextPath}/app/messages/react" method="post" class="inline-form">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<input type="hidden" name="emoji" value="${reaction.emoji}">
|
|
<button class="reaction-pill ${reaction.reactedByCurrentUser ? 'active' : ''}" type="submit">${reaction.emoji} ${reaction.count}</button>
|
|
</form>
|
|
</c:forEach>
|
|
<form action="${pageContext.request.contextPath}/app/messages/react" method="post" class="inline-form">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<input type="hidden" name="emoji" value="👍">
|
|
<button class="emoji-trigger" type="submit">👍</button>
|
|
</form>
|
|
<form action="${pageContext.request.contextPath}/app/messages/react" method="post" class="inline-form">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<input type="hidden" name="emoji" value="❤️">
|
|
<button class="emoji-trigger" type="submit">❤️</button>
|
|
</form>
|
|
<form action="${pageContext.request.contextPath}/app/messages/react" method="post" class="inline-form">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<input type="hidden" name="emoji" value="😂">
|
|
<button class="emoji-trigger" type="submit">😂</button>
|
|
</form>
|
|
</div>
|
|
|
|
<c:if test="${message.mine && not message.deleted}">
|
|
<div class="message-actions">
|
|
<button class="ghost-link mini edit-toggle" type="button" data-target="edit-${message.id}">Modifier</button>
|
|
<form action="${pageContext.request.contextPath}/app/messages/delete" method="post" class="inline-form">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<button class="ghost-link danger mini" type="submit">Supprimer</button>
|
|
</form>
|
|
</div>
|
|
</c:if>
|
|
</div>
|
|
|
|
<c:if test="${message.mine && not message.deleted}">
|
|
<form id="edit-${message.id}" action="${pageContext.request.contextPath}/app/messages/update" method="post" class="edit-form hidden">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<input type="hidden" name="messageId" value="${message.id}">
|
|
<textarea name="body" rows="3" required>${message.body}</textarea>
|
|
<div class="edit-actions">
|
|
<button class="button tiny primary" type="submit">Enregistrer</button>
|
|
<button class="button tiny secondary edit-cancel" type="button" data-target="edit-${message.id}">Annuler</button>
|
|
</div>
|
|
</form>
|
|
</c:if>
|
|
</div>
|
|
</article>
|
|
</c:forEach>
|
|
</section>
|
|
|
|
<form action="${pageContext.request.contextPath}/app/messages/send" method="post" enctype="multipart/form-data" class="composer fb-composer">
|
|
<input type="hidden" name="conversationId" value="${activeConversation.id}">
|
|
<textarea name="body" rows="3" placeholder="Écrivez un message comme sur Messenger..."></textarea>
|
|
<div class="composer-actions">
|
|
<label class="file-label">
|
|
<input type="file" name="attachment" accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip">
|
|
<span>Ajouter image ou fichier</span>
|
|
</label>
|
|
<button class="button primary" type="submit">Envoyer</button>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
</c:when>
|
|
<c:otherwise>
|
|
<section class="fb-card empty-chat large-empty messenger-shell-card">
|
|
<div class="empty-3d-shape"></div>
|
|
<h2>Bienvenue sur votre Messenger</h2>
|
|
<p>Ajoutez des amis, acceptez des demandes ou créez un groupe pour commencer.</p>
|
|
</section>
|
|
</c:otherwise>
|
|
</c:choose>
|
|
|
|
<c:if test="${not empty debugMessage}">
|
|
<p class="debug-note">Détail technique: ${debugMessage}</p>
|
|
</c:if>
|
|
</section>
|
|
|
|
<aside class="fb-right-column">
|
|
<section class="fb-card facebook-card">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Discussions</p>
|
|
<h2>Conversations récentes</h2>
|
|
</div>
|
|
</div>
|
|
<div class="conversation-list">
|
|
<c:forEach items="${conversations}" var="conversation">
|
|
<a class="conversation-item ${conversation.active ? 'active' : ''}" href="${pageContext.request.contextPath}/app/dashboard?conversation=${conversation.id}">
|
|
<div class="avatar large" style="background:${conversation.avatarColor}">${conversation.initials}</div>
|
|
<div class="conversation-copy">
|
|
<div class="conversation-topline">
|
|
<strong>${conversation.title}</strong>
|
|
<c:if test="${conversation.lastMessageAt != null}">
|
|
<small><fmt:formatDate value="${conversation.lastMessageAt}" pattern="HH:mm"/></small>
|
|
</c:if>
|
|
</div>
|
|
<p>${conversation.lastMessagePreview}</p>
|
|
</div>
|
|
<c:if test="${conversation.unreadCount > 0}">
|
|
<span class="unread-badge">${conversation.unreadCount}</span>
|
|
</c:if>
|
|
</a>
|
|
</c:forEach>
|
|
<c:if test="${empty conversations}">
|
|
<p class="empty-mini">Aucune conversation pour le moment.</p>
|
|
</c:if>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="fb-card facebook-card">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Découvrir</p>
|
|
<h2>Envoyer des demandes</h2>
|
|
</div>
|
|
</div>
|
|
<div class="request-list">
|
|
<c:forEach items="${people}" var="person">
|
|
<article class="friend-row stacked-mobile">
|
|
<div class="avatar large" style="background:${person.avatarColor}">${person.initials}</div>
|
|
<div class="friend-copy grow">
|
|
<strong>${person.fullName}</strong>
|
|
<small>@${person.username}</small>
|
|
<c:if test="${not empty person.bio}">
|
|
<p>${person.bio}</p>
|
|
</c:if>
|
|
</div>
|
|
<div class="friend-actions vertical">
|
|
<c:choose>
|
|
<c:when test="${person.requestReceived}">
|
|
<span class="status-chip waiting">Vous a envoyé une demande</span>
|
|
</c:when>
|
|
<c:when test="${person.requestSent}">
|
|
<span class="status-chip sent">Demande envoyée</span>
|
|
</c:when>
|
|
<c:otherwise>
|
|
<form action="${pageContext.request.contextPath}/app/friends/request" method="post">
|
|
<input type="hidden" name="receiverId" value="${person.id}">
|
|
<button class="button tiny secondary" type="submit">Ajouter</button>
|
|
</form>
|
|
</c:otherwise>
|
|
</c:choose>
|
|
</div>
|
|
</article>
|
|
</c:forEach>
|
|
<c:if test="${empty people}">
|
|
<p class="empty-mini">Tous les autres comptes sont déjà vos amis ou en attente.</p>
|
|
</c:if>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="fb-card facebook-card">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Amis</p>
|
|
<h2>Lancer une discussion</h2>
|
|
</div>
|
|
</div>
|
|
<div class="request-list">
|
|
<c:forEach items="${friends}" var="friend">
|
|
<article class="friend-row">
|
|
<div class="avatar large" style="background:${friend.avatarColor}">${friend.initials}</div>
|
|
<div class="friend-copy">
|
|
<strong>${friend.fullName}</strong>
|
|
<small>@${friend.username}</small>
|
|
</div>
|
|
<a class="button tiny primary quick-open-form" href="${pageContext.request.contextPath}/app/dashboard?user=${friend.id}">Ouvrir</a>
|
|
</article>
|
|
</c:forEach>
|
|
<c:if test="${empty friends}">
|
|
<p class="empty-mini">Acceptez une demande ou ajoutez un ami pour ouvrir un chat privé.</p>
|
|
</c:if>
|
|
</div>
|
|
</section>
|
|
</aside>
|
|
</main>
|
|
<script src="${pageContext.request.contextPath}/assets/js/app.js"></script>
|
|
</body>
|
|
</html>
|