Place2Play / client /src /components /providers /AlertProvider.tsx
3v324v23's picture
feat: comprehensive game logic fixes, UX upgrades, and mobile optimization
2498190
"use client";
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
import GameAlert from "../ui/GameAlert";
import { Player } from "@/types";
import { useSound } from "@/hooks/useSound";
type AlertType = 'error' | 'info' | 'success' | 'voting';
interface AlertData {
id: number;
message: React.ReactNode;
type: AlertType;
title: string;
image?: string;
votes?: Record<string, number>;
players?: Player[];
sound?: string;
}
interface AlertContextType {
showAlert: (message: React.ReactNode, type?: AlertType, title?: string, image?: string, votes?: Record<string, number>, players?: Player[], sound?: string) => void;
}
const AlertContext = createContext<AlertContextType | undefined>(undefined);
export function AlertProvider({ children }: { children: ReactNode }) {
const [queue, setQueue] = useState<AlertData[]>([]);
const [currentAlert, setCurrentAlert] = useState<AlertData | null>(null);
const { playSound } = useSound();
const showAlert = (msg: React.ReactNode, alertType: AlertType = 'info', alertTitle?: string, alertImage?: string, voteData?: Record<string, number>, playerData?: Player[], sound?: string) => {
// Spam Prevention: Check if the last queued alert or current alert is identical
const lastInQueue = queue[queue.length - 1];
const isDuplicateOfCurrent = currentAlert && currentAlert.message === msg && currentAlert.title === alertTitle;
const isDuplicateOfQueue = lastInQueue && lastInQueue.message === msg && lastInQueue.title === alertTitle;
if (isDuplicateOfCurrent || isDuplicateOfQueue) {
return; // Ignore duplicate
}
const newAlert: AlertData = {
id: Date.now() + Math.random(),
message: msg,
type: alertType,
title: alertTitle || (alertType === 'error' ? 'ERROR' : alertType === 'voting' ? 'PENENTUAN' : 'NOTICE'),
image: alertImage,
votes: voteData,
players: playerData,
sound: sound
};
setQueue((prev) => [...prev, newAlert]);
};
useEffect(() => {
if (!currentAlert && queue.length > 0) {
const nextAlert = queue[0];
setCurrentAlert(nextAlert);
setQueue((prev) => prev.slice(1));
if (nextAlert.sound) {
playSound(nextAlert.sound);
}
}
}, [queue, currentAlert, playSound]);
const closeAlert = () => {
setCurrentAlert(null);
};
return (
<AlertContext.Provider value={{ showAlert }}>
{children}
<GameAlert
isOpen={!!currentAlert}
message={currentAlert?.message || ""}
title={currentAlert?.title}
type={currentAlert?.type || 'info'}
image={currentAlert?.image}
votes={currentAlert?.votes}
players={currentAlert?.players}
onClose={closeAlert}
/>
</AlertContext.Provider>
);
}
export function useAlert() {
const context = useContext(AlertContext);
if (context === undefined) {
throw new Error("useAlert must be used within an AlertProvider");
}
return context;
}