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.ca/public_html/src/context/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/context/PublicNotificationContext.tsx
import React, { createContext, useContext, useEffect, useState, ReactNode, useCallback } from 'react';
import { useRouter } from 'next/router';
import toast from 'react-hot-toast';

interface NotificationCampaign {
  id: string;
  type: 'banner' | 'toast' | 'modal' | 'exit-intent';
  title: string;
  message: string;
  actionText?: string;
  actionUrl?: string;
  priority: 'low' | 'medium' | 'high' | 'urgent';
  placement: 'top' | 'bottom' | 'center';
  targetPages?: string[];
  targetRegion?: string;
  language: 'en' | 'fr' | 'both';
  isActive: boolean;
  expiresAt?: string;
  showAfterSeconds?: number;
  maxViews?: number;
  createdAt: string;
}

interface VisitorBehavior {
  sessionId: string;
  timeOnSite: number;
  pagesVisited: string[];
  lastActivity: number;
  hasSignedUp: boolean;
  hasApplied: boolean;
  notificationsShown: string[];
  region?: string;
  language: string;
}

interface PublicNotificationContextValue {
  // Active campaigns
  activeCampaigns: NotificationCampaign[];
  
  // Visitor tracking
  visitorBehavior: VisitorBehavior;
  updateVisitorBehavior: (updates: Partial<VisitorBehavior>) => void;
  
  // Notification controls
  showNotification: (campaign: NotificationCampaign) => void;
  dismissNotification: (campaignId: string) => void;
  subscribeToNewsletter: (email: string) => Promise<boolean>;
  
  // Engagement prompts
  showEducationalPrompt: (topic: string) => void;
  showNewsletterPrompt: () => void;
  showGroupChatPrompt: () => void;
  showExitIntent: () => void;
  showTimeBasedPrompt: () => void;
  
  // Banner state
  activeBanner: NotificationCampaign | null;
  dismissBanner: () => void;
  
  // Analytics
  trackNotificationView: (campaignId: string) => void;
  trackNotificationClick: (campaignId: string) => void;
}

const PublicNotificationContext = createContext<PublicNotificationContextValue>({
  activeCampaigns: [],
  visitorBehavior: {
    sessionId: '',
    timeOnSite: 0,
    pagesVisited: [],
    lastActivity: Date.now(),
    hasSignedUp: false,
    hasApplied: false,
    notificationsShown: [],
    language: 'en'
  },
  updateVisitorBehavior: () => {},
  showNotification: () => {},
  dismissNotification: () => {},
  subscribeToNewsletter: async () => false,
  showEducationalPrompt: () => {},
  showNewsletterPrompt: () => {},
  showGroupChatPrompt: () => {},
  showExitIntent: () => {},
  showTimeBasedPrompt: () => {},
  activeBanner: null,
  dismissBanner: () => {},
  trackNotificationView: () => {},
  trackNotificationClick: () => {},
});

export const PublicNotificationProvider = ({ children }: { children: ReactNode }) => {
  const router = useRouter();
  const [activeCampaigns, setActiveCampaigns] = useState<NotificationCampaign[]>([]);
  const [visitorBehavior, setVisitorBehavior] = useState<VisitorBehavior>({
    sessionId: '',
    timeOnSite: 0,
    pagesVisited: [],
    lastActivity: Date.now(),
    hasSignedUp: false,
    hasApplied: false,
    notificationsShown: [],
    language: (router.locale as 'en' | 'fr') || 'en'
  });
  const [activeBanner, setActiveBanner] = useState<NotificationCampaign | null>(null);
  const [exitIntentShown, setExitIntentShown] = useState(false);
  const [timeBasedShown, setTimeBasedShown] = useState(false);

  // Generate or retrieve session ID
  useEffect(() => {
    if (typeof window !== 'undefined') {
      let sessionId = sessionStorage.getItem('visitorSessionId');
      if (!sessionId) {
        sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        sessionStorage.setItem('visitorSessionId', sessionId);
      }
      
      setVisitorBehavior(prev => ({ ...prev, sessionId }));
    }
  }, []);

  // Track time on site - OPTIMIZED to reduce re-renders
  useEffect(() => {
    const startTime = Date.now();
    let lastUpdateTime = startTime;
    
    // Update every 10 seconds instead of every 1 second to reduce re-renders
    const interval = setInterval(() => {
      const now = Date.now();
      const timeOnSite = Math.floor((now - startTime) / 1000);
      
      // Only update if we've crossed a meaningful threshold (10 seconds)
      if (timeOnSite - Math.floor((lastUpdateTime - startTime) / 1000) >= 10) {
        setVisitorBehavior(prev => ({
          ...prev,
          timeOnSite,
          lastActivity: now
        }));
        lastUpdateTime = now;
      }
    }, 10000); // Update every 10 seconds instead of 1 second

    return () => clearInterval(interval);
  }, []);

  // Track page visits
  useEffect(() => {
    const currentPage = router.asPath;
    setVisitorBehavior(prev => ({
      ...prev,
      pagesVisited: Array.from(new Set([...prev.pagesVisited, currentPage]))
    }));
  }, [router.asPath]);

  // Detect user region (basic implementation)
  useEffect(() => {
    const detectRegion = async () => {
      try {
        const response = await fetch('https://ipapi.co/json/');
        const data = await response.json();
        if (data.country_code === 'CA' && data.region === 'QC') {
          setVisitorBehavior(prev => ({ ...prev, region: 'quebec' }));
        } else if (data.country_code === 'CA') {
          setVisitorBehavior(prev => ({ ...prev, region: 'canada' }));
        } else {
          setVisitorBehavior(prev => ({ ...prev, region: 'international' }));
        }
      } catch (error) {
        console.log('Could not detect region');
      }
    };
    
    detectRegion();
  }, []);

  // Fetch active campaigns
  useEffect(() => {
    const fetchCampaigns = async () => {
      try {
        const response = await fetch('/api/public/notifications/campaigns');
        if (response.ok) {
          const campaigns = await response.json();
          setActiveCampaigns(campaigns);
          
          // Find active banner
          const banner = campaigns.find((c: NotificationCampaign) => 
            c.type === 'banner' && 
            c.isActive && 
            (!c.expiresAt || new Date(c.expiresAt) > new Date()) &&
            (!c.targetPages || c.targetPages.includes(router.asPath)) &&
            (c.language === 'both' || c.language === visitorBehavior.language)
          );
          
          if (banner && typeof window !== 'undefined' && !localStorage.getItem(`banner_dismissed_${banner.id}`)) {
            setActiveBanner(banner);
          }
        }
      } catch (error) {
        console.error('Failed to fetch notification campaigns:', error);
      }
    };

    fetchCampaigns();
  }, [router.asPath]);

  // Exit intent detection
  useEffect(() => {
    const handleMouseLeave = (e: MouseEvent) => {
      if (e.clientY <= 0 && !exitIntentShown && visitorBehavior.timeOnSite > 30) {
        showExitIntent();
      }
    };

    document.addEventListener('mouseleave', handleMouseLeave);
    return () => document.removeEventListener('mouseleave', handleMouseLeave);
  }, [exitIntentShown]);

  // Time-based prompts - only if no other prompts shown recently
  useEffect(() => {
    if (typeof window !== 'undefined' &&
        visitorBehavior.timeOnSite > 120 && 
        !timeBasedShown && 
        visitorBehavior.pagesVisited.length > 2 &&
        !sessionStorage.getItem(`newsletter_prompt_${visitorBehavior.sessionId}`) &&
        !sessionStorage.getItem(`exit_intent_${visitorBehavior.sessionId}`)) {
      showTimeBasedPrompt();
    }
  }, [timeBasedShown]);

  const updateVisitorBehavior = useCallback((updates: Partial<VisitorBehavior>) => {
    setVisitorBehavior(prev => ({ ...prev, ...updates }));
  }, []);

  const showNotification = useCallback((campaign: NotificationCampaign) => {
    // Check if already shown
    if (visitorBehavior.notificationsShown.includes(campaign.id)) return;
    
    // Check max views
    if (typeof window !== 'undefined') {
      const viewCount = parseInt(localStorage.getItem(`campaign_views_${campaign.id}`) || '0');
      if (campaign.maxViews && viewCount >= campaign.maxViews) return;
    }

    switch (campaign.type) {
      case 'toast':
        toast((t) => (
          <div className="flex items-center space-x-3 max-w-md">
            <div className="flex-1">
              <p className="font-medium text-gray-900">{campaign.title}</p>
              <p className="text-sm text-gray-600">{campaign.message}</p>
            </div>
            {campaign.actionText && campaign.actionUrl && (
              <button
                onClick={() => {
                  toast.dismiss(t.id);
                  trackNotificationClick(campaign.id);
                  router.push(campaign.actionUrl!);
                }}
                className="bg-primary text-white px-3 py-1 rounded text-sm hover:bg-primary-dark"
              >
                {campaign.actionText}
              </button>
            )}
          </div>
        ), {
          duration: campaign.priority === 'urgent' ? 8000 : 5000,
          position: campaign.placement === 'top' ? 'top-right' : 'bottom-right',
        });
        break;
        
      case 'modal':
        // Implement modal logic
        break;
    }

    // Track view
    trackNotificationView(campaign.id);
    
    // Mark as shown
    setVisitorBehavior(prev => ({
      ...prev,
      notificationsShown: [...prev.notificationsShown, campaign.id]
    }));
  }, [router]);

  const dismissNotification = useCallback((campaignId: string) => {
    if (typeof window !== 'undefined') {
      localStorage.setItem(`notification_dismissed_${campaignId}`, 'true');
    }
  }, []);

  const subscribeToNewsletter = useCallback(async (email: string): Promise<boolean> => {
    try {
      const response = await fetch('/api/public/newsletter/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          email, 
          language: visitorBehavior.language,
          source: 'website_notification'
        }),
      });
      
      if (response.ok) {
        setVisitorBehavior(prev => ({ ...prev, hasSignedUp: true }));
        toast.success('✅ Successfully subscribed to case updates!', {
          duration: 4000,
          position: 'top-right',
        });
        return true;
      }
      return false;
    } catch (error) {
      toast.error('Failed to subscribe. Please try again.', {
        duration: 4000,
        position: 'top-right',
      });
      return false;
    }
  }, []);

  const showEducationalPrompt = useCallback((topic: string) => {
    const prompts = {
      'class-action': {
        en: {
          title: '📚 New to Class Actions?',
          message: 'Learn about your rights and how class actions work',
          actionText: 'Learn More',
          actionUrl: '/faq'
        },
        fr: {
          title: '📚 Nouveau aux recours collectifs?',
          message: 'Apprenez vos droits et comment fonctionnent les recours collectifs',
          actionText: 'En savoir plus',
          actionUrl: '/faq'
        }
      },
      'rights': {
        en: {
          title: '⚖️ Know Your Rights',
          message: 'Understanding your rights as a former detainee',
          actionText: 'Download Guide',
          actionUrl: '/resources'
        },
        fr: {
          title: '⚖️ Connaissez vos droits',
          message: 'Comprendre vos droits en tant qu\'ancien détenu',
          actionText: 'Télécharger le guide',
          actionUrl: '/resources'
        }
      }
    };

    const prompt = prompts[topic as keyof typeof prompts]?.[visitorBehavior.language as 'en' | 'fr'];
    if (prompt) {
      toast((t) => (
        <div className="flex items-center space-x-3">
          <div className="flex-1">
            <p className="font-medium text-gray-900">{prompt.title}</p>
            <p className="text-sm text-gray-600">{prompt.message}</p>
          </div>
          <button
            onClick={() => {
              toast.dismiss(t.id);
              router.push(prompt.actionUrl);
            }}
            className="bg-primary text-white px-3 py-1 rounded text-sm hover:bg-primary-dark"
          >
            {prompt.actionText}
          </button>
        </div>
      ), {
        duration: 6000,
        position: 'top-right',
      });
    }
  }, [router]);

  const showNewsletterPrompt = useCallback(() => {
    if (visitorBehavior.hasSignedUp) return;
    
    // Check if already shown in this session to prevent duplicates
    if (typeof window !== 'undefined') {
      const newsletterShownKey = `newsletter_prompt_${visitorBehavior.sessionId}`;
      if (sessionStorage.getItem(newsletterShownKey)) return;
      
      // Mark as shown immediately to prevent duplicates
      sessionStorage.setItem(newsletterShownKey, 'shown');
    }

    const message = visitorBehavior.language === 'fr' 
      ? 'Restez informé des mises à jour importantes du dossier'
      : 'Stay informed about important case updates';
    
    const actionText = visitorBehavior.language === 'fr' ? 'S\'abonner' : 'Subscribe';

    toast((t) => (
      <div className="flex flex-col space-y-2 max-w-sm">
        <p className="font-medium text-gray-900">📧 {message}</p>
        <div className="flex space-x-2">
          <input
            type="email"
            placeholder="email@example.com"
            className="flex-1 px-2 py-1 border rounded text-sm"
            id={`newsletter-input-${t.id}`}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                const email = (e.target as HTMLInputElement).value;
                if (email) {
                  subscribeToNewsletter(email);
                  toast.dismiss(t.id);
                }
              }
            }}
          />
          <button
            onClick={() => {
              const input = document.getElementById(`newsletter-input-${t.id}`) as HTMLInputElement;
              if (input?.value) {
                subscribeToNewsletter(input.value);
                toast.dismiss(t.id);
              }
            }}
            className="bg-primary text-white px-3 py-1 rounded text-sm hover:bg-primary-dark"
          >
            {actionText}
          </button>
        </div>
      </div>
    ), {
      duration: 8000,
      position: 'top-right',
    });
  }, [subscribeToNewsletter]);

  const showGroupChatPrompt = useCallback(() => {
    // Check if already shown in this session to prevent duplicates
    if (typeof window !== 'undefined') {
      const groupChatShownKey = `groupchat_prompt_${visitorBehavior.sessionId}`;
      if (sessionStorage.getItem(groupChatShownKey)) return;
      
      // Mark as shown immediately to prevent duplicates
      sessionStorage.setItem(groupChatShownKey, 'shown');
    }

    const content = visitorBehavior.language === 'fr' ? {
      title: '💬 Rejoignez la Communauté',
      message: 'Connectez-vous avec d\'autres membres du recours collectif',
      description: 'Partagez vos expériences, obtenez du soutien et restez informé avec la communauté',
      actionText: 'Rejoindre le Chat',
      benefits: [
        '🤝 Soutien communautaire',
        '📢 Mises à jour en temps réel', 
        '💪 Force collective'
      ]
    } : {
      title: '💬 Join the Community',
      message: 'Connect with other class action members',
      description: 'Share experiences, get support, and stay informed with the community',
      actionText: 'Join Chat',
      benefits: [
        '🤝 Community support',
        '📢 Real-time updates',
        '💪 Collective strength'
      ]
    };

    toast((t) => (
      <div className="flex flex-col space-y-3 max-w-sm">
        <div className="flex items-center space-x-2">
          <div className="text-2xl">💬</div>
          <div>
            <p className="font-bold text-gray-900">{content.title}</p>
            <p className="text-sm text-gray-600">{content.message}</p>
          </div>
        </div>
        
        <div className="text-xs text-gray-600">
          {content.description}
        </div>
        
        <div className="grid grid-cols-1 gap-1 text-xs">
          {content.benefits.map((benefit, index) => (
            <div key={index} className="text-gray-700">{benefit}</div>
          ))}
        </div>
        
        <div className="flex space-x-2">
          <button
            onClick={() => {
              toast.dismiss(t.id);
              router.push('/group-chat');
            }}
            className="flex-1 bg-primary text-white px-3 py-2 rounded text-sm font-medium hover:bg-primary-dark transition-colors"
          >
            {content.actionText}
          </button>
          <button
            onClick={() => toast.dismiss(t.id)}
            className="px-3 py-2 text-xs text-gray-500 hover:text-gray-700"
          >
            {visitorBehavior.language === 'fr' ? 'Plus tard' : 'Later'}
          </button>
        </div>
      </div>
    ), {
      duration: 10000,
      position: 'bottom-right',
    });
  }, [router]);

  const showExitIntent = useCallback(() => {
    setExitIntentShown(true);
    
    // Mark exit intent as shown to prevent conflicts
    if (typeof window !== 'undefined') {
      sessionStorage.setItem(`exit_intent_${visitorBehavior.sessionId}`, 'shown');
    }
    
    const message = visitorBehavior.language === 'fr'
      ? 'Attendez! Vous pourriez être éligible à une compensation'
      : 'Wait! You might be eligible for compensation';
    
    const actionText = visitorBehavior.language === 'fr' ? 'Vérifier l\'éligibilité' : 'Check Eligibility';

    toast((t) => (
      <div className="flex items-center space-x-3">
        <div className="text-2xl">⚖️</div>
        <div className="flex-1">
          <p className="font-bold text-gray-900">{message}</p>
          <p className="text-sm text-gray-600">
            {visitorBehavior.language === 'fr' 
              ? 'Découvrez si vous pouvez vous joindre au recours collectif'
              : 'Find out if you can join the class action'}
          </p>
        </div>
        <button
          onClick={() => {
            toast.dismiss(t.id);
            router.push('/register');
          }}
          className="bg-red-600 text-white px-4 py-2 rounded font-bold hover:bg-red-700"
        >
          {actionText}
        </button>
      </div>
    ), {
      duration: 10000,
      position: 'top-center',
      style: {
        maxWidth: '500px',
      },
    });
  }, [router]);

  const showTimeBasedPrompt = useCallback(() => {
    setTimeBasedShown(true);
    
    const prompts = [
      {
        en: {
          title: '🕐 Still reading?',
          message: 'Get personalized guidance about your case',
          actionText: 'Free Consultation',
          actionUrl: '/contact'
        },
        fr: {
          title: '🕐 Toujours en train de lire?',
          message: 'Obtenez des conseils personnalisés sur votre dossier',
          actionText: 'Consultation gratuite',
          actionUrl: '/contact'
        }
      },
      {
        en: {
          title: '📞 Questions?',
          message: 'Speak with a legal expert about your rights',
          actionText: 'Contact Us',
          actionUrl: '/contact'
        },
        fr: {
          title: '📞 Des questions?',
          message: 'Parlez à un expert juridique de vos droits',
          actionText: 'Nous contacter',
          actionUrl: '/contact'
        }
      }
    ];

    const randomPrompt = prompts[Math.floor(Math.random() * prompts.length)];
    const prompt = randomPrompt[visitorBehavior.language as 'en' | 'fr'];

    toast((t) => (
      <div className="flex items-center space-x-3">
        <div className="flex-1">
          <p className="font-medium text-gray-900">{prompt.title}</p>
          <p className="text-sm text-gray-600">{prompt.message}</p>
        </div>
        <button
          onClick={() => {
            toast.dismiss(t.id);
            router.push(prompt.actionUrl);
          }}
          className="bg-primary text-white px-3 py-1 rounded text-sm hover:bg-primary-dark"
        >
          {prompt.actionText}
        </button>
      </div>
    ), {
      duration: 6000,
      position: 'top-right',
    });
  }, [router]);

  const dismissBanner = useCallback(() => {
    if (activeBanner && typeof window !== 'undefined') {
      localStorage.setItem(`banner_dismissed_${activeBanner.id}`, 'true');
      setActiveBanner(null);
    }
  }, [activeBanner]);

  const trackNotificationView = useCallback((campaignId: string) => {
    if (typeof window !== 'undefined') {
      const currentViews = parseInt(localStorage.getItem(`campaign_views_${campaignId}`) || '0');
      localStorage.setItem(`campaign_views_${campaignId}`, (currentViews + 1).toString());
    }
    
    // Send analytics
    fetch('/api/public/notifications/analytics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        campaignId,
        event: 'view',
        sessionId: visitorBehavior.sessionId,
        page: router.asPath
      }),
    }).catch(() => {}); // Silent fail
  }, [visitorBehavior.sessionId, router.asPath]);

  const trackNotificationClick = useCallback((campaignId: string) => {
    // Send analytics
    fetch('/api/public/notifications/analytics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        campaignId,
        event: 'click',
        sessionId: visitorBehavior.sessionId,
        page: router.asPath
      }),
    }).catch(() => {}); // Silent fail
  }, [visitorBehavior.sessionId, router.asPath]);

  return (
    <PublicNotificationContext.Provider value={{
      activeCampaigns,
      visitorBehavior,
      updateVisitorBehavior,
      showNotification,
      dismissNotification,
      subscribeToNewsletter,
      showEducationalPrompt,
      showNewsletterPrompt,
      showGroupChatPrompt,
      showExitIntent,
      showTimeBasedPrompt,
      activeBanner,
      dismissBanner,
      trackNotificationView,
      trackNotificationClick,
    }}>
      {children}
    </PublicNotificationContext.Provider>
  );
};

export const usePublicNotifications = () => useContext(PublicNotificationContext); 

CasperSecurity Mini