T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.quebec/private_html/src/components/ui/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.quebec/private_html/src/components/ui/MessageReactions.tsx
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useSession } from 'next-auth/react';
import EmojiPicker, { saveRecentEmoji } from './EmojiPicker';

interface Reaction {
  id: string;
  emoji: string;
  userId: string;
  user: {
    id: string;
    name: string;
  };
  createdAt: string;
}

interface MessageReactionsProps {
  messageId: string;
  reactions: Reaction[];
  onAddReaction: (messageId: string, emoji: string) => void;
  onRemoveReaction: (messageId: string, emoji: string) => void;
  className?: string;
}

const MessageReactions: React.FC<MessageReactionsProps> = ({
  messageId,
  reactions,
  onAddReaction,
  onRemoveReaction,
  className = ''
}) => {
  const { data: session } = useSession();
  const [hoveredReaction, setHoveredReaction] = useState<string | null>(null);
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);

  // Group reactions by emoji
  const groupedReactions = reactions.reduce((acc, reaction) => {
    if (!acc[reaction.emoji]) {
      acc[reaction.emoji] = [];
    }
    acc[reaction.emoji].push(reaction);
    return acc;
  }, {} as Record<string, Reaction[]>);

  const handleReactionClick = (emoji: string) => {
    if (!session?.user?.id) return;

    const userReaction = groupedReactions[emoji]?.find(
      r => r.userId === session.user.id
    );

    if (userReaction) {
      onRemoveReaction(messageId, emoji);
    } else {
      onAddReaction(messageId, emoji);
    }
  };

  const getReactionTooltip = (emoji: string, reactions: Reaction[]) => {
    const names = reactions.map(r => r.user.name);
    if (names.length === 1) {
      return `${names[0]} reacted with ${emoji}
    } else if (names.length === 2) {
      return `${names[0]} and ${names[1]} reacted with ${emoji}
    } else if (names.length <= 5) {
      return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]} reacted with ${emoji}
    } else {
      return `${names.slice(0, 3).join(', ')} and ${names.length - 3} others reacted with ${emoji}
    }
  };

  const handleEmojiSelect = (emoji: string) => {
    onAddReaction(messageId, emoji);
    saveRecentEmoji(emoji);
    setShowEmojiPicker(false);
  };

  return (
    <div className={`flex flex-wrap items-center gap-1 mt-1 ${className}
      <AnimatePresence>
        {Object.entries(groupedReactions).map(([emoji, reactionList]) => {
          const hasUserReaction = session?.user?.id && 
            reactionList.some(r => r.userId === session.user.id);
          
          return (
            <motion.button
              key={emoji}
              initial={{ scale: 0 }}
              animate={{ scale: 1 }}
              exit={{ scale: 0 }}
              whileHover={{ scale: 1.1 }}
              whileTap={{ scale: 0.95 }}
              onClick={() => handleReactionClick(emoji)}
              onMouseEnter={() => setHoveredReaction(emoji)}
              onMouseLeave={() => setHoveredReaction(null)}
              className={
                relative inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium
                transition-all duration-200 border
                ${hasUserReaction 
                  ? 'bg-blue-100 dark:bg-blue-900/30 border-blue-300 dark:border-blue-600 text-blue-700 dark:text-blue-300' 
                  : 'bg-gray-100 dark:bg-gray-700 border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
                }
              
              title={getReactionTooltip(emoji, reactionList)}
            >
              <span className="text-sm">{emoji}</span>
              <span className="text-xs font-medium">{reactionList.length}</span>
              
              {/* Tooltip */}
              <AnimatePresence>
                {hoveredReaction === emoji && (
                  <motion.div
                    initial={{ opacity: 0, y: 5 }}
                    animate={{ opacity: 1, y: 0 }}
                    exit={{ opacity: 0, y: 5 }}
                    transition={{ duration: 0.15 }}
                    className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 z-10"
                  >
                    <div className="bg-gray-900 dark:bg-gray-700 text-white text-xs rounded-lg px-3 py-2 whitespace-nowrap max-w-xs">
                      {getReactionTooltip(emoji, reactionList)}
                      <div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
                    </div>
                  </motion.div>
                )}
              </AnimatePresence>
            </motion.button>
          );
        })}
      </AnimatePresence>

      {/* Add reaction button */}
      <div className="relative">
        <motion.button
          onClick={() => setShowEmojiPicker(!showEmojiPicker)}
          className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors opacity-60 hover:opacity-100"
          whileHover={{ scale: 1.1 }}
          whileTap={{ scale: 0.95 }}
          title="Add reaction"
        >
          <span className="text-xs">+</span>
        </motion.button>

        <EmojiPicker
          isOpen={showEmojiPicker}
          onClose={() => setShowEmojiPicker(false)}
          onEmojiSelect={handleEmojiSelect}
          position="top"
        />
      </div>
    </div>
  );
};

export default MessageReactions; 

CasperSecurity Mini