From bd9df55e64735ce590078aabeeb9572d9e7ffbc0 Mon Sep 17 00:00:00 2001 From: Nonimart Date: Wed, 17 Sep 2025 14:19:38 +0200 Subject: [PATCH] FEATURE introducing the like button feature --- includes/api.php | 85 ++++++---- resources/js/singles/like-button.ts | 252 ++++++++++++++++++++++++++++ resources/js/singles/singles.ts | 2 + 3 files changed, 309 insertions(+), 30 deletions(-) create mode 100644 resources/js/singles/like-button.ts diff --git a/includes/api.php b/includes/api.php index 68faf6d..69c7273 100644 --- a/includes/api.php +++ b/includes/api.php @@ -6,13 +6,6 @@ add_action('rest_api_init', function () { BUILDING ROUTES -----------------*/ - // ################ Authors ################ - - register_rest_route('dynamiques-datas/v1/build', '/revue/authors', array( - 'methods' => 'GET', - 'callback' => 'build_revue_authors', - 'permission_callback' => '__return_true', - )); // ################ FILTER ARTICLES ################ register_rest_route('dynamiques-datas/v1/build', '/articles', array( @@ -20,32 +13,22 @@ add_action('rest_api_init', function () { 'callback' => 'build_articles', 'permission_callback' => '__return_true', )); + + + /* ---------------- + INTERACTIONS ROUTES + -----------------*/ + + // ################ LIKE POST ################ + + register_rest_route('dynamiques-datas/v1/build', '/articles/like', array( + 'methods' => 'POST', + 'callback' => 'like_post', + 'permission_callback' => '__return_true', + )); }); -// ################ REVUE AUTHORS ################ - -function build_revue_authors($request) -{ - $revueID = esc_html($request->get_param('revue-id')); - - ob_start(); - get_template_part('template-parts/authors/revue-authors-list', null, array( - 'revueID' => $revueID, - )); - - $html_template = ob_get_clean(); - - $response_data = array( - 'html_template' => $html_template, - 'message' => 'Hello World', - ); - $response = new WP_REST_Response($response_data); - - $response->set_status(200); - - return $response; -} // ################ FILTER ARTICLES ################ @@ -139,3 +122,45 @@ function build_articles($request) return $response; } + + +function like_post($request) +{ + + $post_id = $request->get_param('post_id'); + write_log($request); + write_log($post_id); + if (!$post_id) { + return new WP_Error('post_id_required', 'Post ID is required', array('status' => 400)); + } + + + $post_id = intval($post_id); + + // Vérifier que le post existe + if (!get_post($post_id)) { + return new WP_Error('post_not_found', 'Post non trouvé', array('status' => 404)); + } + + + $likes_count = get_post_likes_count($post_id); + + // Incrémenter le compteur + $new_likes = $likes_count + 1; + + // Mettre à jour la meta + update_post_meta($post_id, 'likes_count', $new_likes); + + $response_data = array( + 'success' => true, + 'post_id' => $post_id, + 'likes_count' => $new_likes, + 'message' => 'Like ajouté avec succès' + ); + write_log($response_data); + $response = new WP_REST_Response($response_data); + + $response->set_status(200); + + return $response; +} diff --git a/resources/js/singles/like-button.ts b/resources/js/singles/like-button.ts new file mode 100644 index 0000000..2eaed29 --- /dev/null +++ b/resources/js/singles/like-button.ts @@ -0,0 +1,252 @@ +// ============================================================================= +// TYPE DECLARATIONS +// ============================================================================= + +declare var Notyf: any; + +// ============================================================================= +// UTILITY FUNCTIONS +// ============================================================================= + +function getPostType() { + const isSingleArticle = document.querySelector('.page--single-articles'); + const isSingleRevue = document.querySelector('.page--single-revue'); + if (isSingleArticle) { + return 'article'; + } else if (isSingleRevue) { + return 'revue'; + } + return null; +} + +function getPostId() { + const postType = getPostType(); + if (!postType) return; + const postId = + postType === 'article' + ? document.querySelector('.page--single-articles')?.getAttribute('data-article-id') + : document.querySelector('.page--single-revue')?.getAttribute('data-revue-id'); + return postId; +} + +// ============================================================================= +// LIKE TRACKING FUNCTIONS +// ============================================================================= + +/** + * Clé localStorage pour stocker les posts likés + */ +const LIKED_POSTS_KEY = 'dynamiques_liked_posts'; + +/** + * Récupère la liste des posts likés depuis localStorage + */ +function getLikedPosts(): string[] { + try { + const likedPosts = localStorage.getItem(LIKED_POSTS_KEY); + return likedPosts ? JSON.parse(likedPosts) : []; + } catch (error) { + console.error('Erreur lecture localStorage:', error); + return []; + } +} + +/** + * Ajoute un post à la liste des posts likés + */ +function addLikedPost(postId: string): void { + try { + const likedPosts = getLikedPosts(); + if (!likedPosts.includes(postId)) { + likedPosts.push(postId); + localStorage.setItem(LIKED_POSTS_KEY, JSON.stringify(likedPosts)); + } + } catch (error) { + console.error('Erreur sauvegarde localStorage:', error); + } +} + +/** + * Vérifie si un post a déjà été liké + */ +function isPostLiked(postId: string): boolean { + const likedPosts = getLikedPosts(); + return likedPosts.includes(postId); +} + +// ============================================================================= +// LIKE API FUNCTIONS +// ============================================================================= + +async function likePost(postId: string): Promise { + // Vérifier si le post a déjà été liké + if (isPostLiked(postId)) { + console.log(`Post ${postId} déjà liké !`); + showAlreadyLikedMessage(); + return false; + } + + try { + const response = await fetch(`/wp-json/dynamiques-datas/v1/build/articles/like`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + post_id: postId, + }), + }); + const data = await response.json(); + + if (data.success) { + console.log(`Post ${postId} liké ! Nouveau total: ${data.likes_count} likes`); + + // Sauvegarder le like dans localStorage + addLikedPost(postId); + + // Mettre à jour l'interface utilisateur + updateLikeDisplay(data.likes_count); + updateLikeButtonState(); + + // Afficher message de succès + showLikeSuccessMessage(data.likes_count); + + return true; + } else { + console.error('Erreur API:', data); + return false; + } + } catch (error) { + console.error('Erreur lors du like:', error); + return false; + } +} + +// ============================================================================= +// UI UPDATE FUNCTIONS +// ============================================================================= + +function updateLikeDisplay(likesCount: number) { + // Mettre à jour l'affichage du compteur de likes dans l'interface + const likeCountElement = document.querySelector('.socials-buttons .likes-count'); + const likeButton = document.querySelector('.socials-buttons__button--like'); + + if (likeCountElement && likeButton) { + likeButton.setAttribute('data-likes-count', `${likesCount}`); + likeButton.classList.add('is-disabled'); + likeCountElement.textContent = `${likesCount}`; + } +} + +function updateLikeButtonState() { + const postId = getPostId(); + const likeButton = document.querySelector('.socials-buttons__button--like'); + if (!postId || !likeButton) return; + + const hasAlreadyLiked = isPostLiked(postId); + if (hasAlreadyLiked) { + const actionText = likeButton.querySelector('.button-action-text'); + if (actionText) { + actionText.textContent = 'Déjà liké'; + } + likeButton.setAttribute('title', 'Déjà liké !'); + return; + } + + if (!hasAlreadyLiked) { + likeButton.classList.remove('is-disabled'); + } +} + +function updateLikesCountIndicator() { + const likesCountIndicator = likeButton.querySelector('.likes-count'); + const likesCount = likeButton.getAttribute('data-likes-count'); + + if (likesCount && likesCountIndicator) { + likesCountIndicator.textContent = `${parseInt(likesCount) + 1}`; + } +} + +function showAlreadyLikedMessage() { + const notyf = new Notyf({ + duration: 3000, + ripple: false, + dismissible: true, + types: [ + { + type: 'error', + background: '#ff6b6b', + icon: { + className: 'notyf__icon--error', + tagName: 'i', + // Pas de text personnalisé pour garder l'icône d'erreur par défaut + }, + }, + ], + position: { + x: 'right', + y: 'top', + }, + }); + + notyf.error(' Vous avez déjà liké ce post ! ❤️'); +} + +function showLikeSuccessMessage(likesCount: number) { + const notyf = new Notyf({ + duration: 2000, + ripple: false, + dismissible: true, + types: [ + { + type: 'success', + background: '#10B981', + icon: { + className: 'notyf__icon--success', + tagName: 'i', + text: '❤️', + }, + }, + ], + position: { + x: 'right', + y: 'top', + }, + }); + + notyf.success( + `Merci pour votre like !
Total: ${likesCount} like${likesCount > 1 ? 's' : ''}` + ); +} + +// ============================================================================= +// MAIN INITIALIZATION FUNCTION +// ============================================================================= + +export default function handleLikeButton(): void { + const likeButton: HTMLElement | null = document.querySelector( + '.socials-buttons .socials-buttons__button--like' + ); + const postType = getPostType(); + const postId = getPostId(); + if (!postType || !postId || !likeButton) return; + + updateLikeButtonState(); + + // Ajouter l'événement click + likeButton.addEventListener('click', async (e) => { + e.preventDefault(); // Empêcher comportement par défaut + + const hasAlreadyLiked = isPostLiked(postId); + if (hasAlreadyLiked) return showAlreadyLikedMessage(); + // Désactiver temporairement le bouton pour éviter les clics multiples + likeButton.style.pointerEvents = 'none'; + + const success = await likePost(postId); + + // Réactiver le bouton + setTimeout(() => { + likeButton.style.pointerEvents = 'auto'; + }, 1000); + }); +} diff --git a/resources/js/singles/singles.ts b/resources/js/singles/singles.ts index 24a2056..a33582f 100644 --- a/resources/js/singles/singles.ts +++ b/resources/js/singles/singles.ts @@ -1,6 +1,7 @@ import handleIndexPanel from './index-panel'; import handleFootnoteFormat from './footnote-format'; import handleCiteButton from './cite-button'; +import handleLikeButton from './like-button.ts'; import { handleArticleToolbar } from './article-toolbar.ts'; import { handleRevueToolbar } from './revue-toolbar.ts'; @@ -12,6 +13,7 @@ export default function singles(): void { handleIndexPanel(); handleFootnoteFormat(); handleCiteButton(); + handleLikeButton(); handleArticleToolbar(); handleRevueToolbar(); }