Files
void-sentinel/web/components/Toast.tsx

122 lines
4.7 KiB
TypeScript

"use client";
import { useSearchParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { X, AlertCircle, CheckCircle } from "lucide-react";
export default function Toast() {
const searchParams = useSearchParams();
const router = useRouter();
const [isVisible, setIsVisible] = useState(false);
const [message, setMessage] = useState<{ type: "success" | "error"; text: string; title: string } | null>(null);
useEffect(() => {
const error = searchParams.get("error");
const success = searchParams.get("success");
if (error) {
let text = "An unexpected error occurred.";
let title = "Error";
switch (error) {
case "access_denied":
text = "You cancelled the authorization request.";
title = "Access Denied";
break;
case "invalid_request":
text = "The request parameters were invalid.";
title = "Invalid Request";
break;
case "state_mismatch":
text = "Security check failed (State Mismatch). Please try again.";
title = "Security Error";
break;
case "guild_mismatch":
text = "The server you authorized on Discord does not match the one you selected on the dashboard.";
title = "Server Mismatch";
break;
case "not_beta_server":
text = "This server is not part of the Closed Beta program. You cannot add the bot to it yet.";
title = "Beta Access Restricted";
break;
case "token_exchange_failed":
text = "Failed to communicate with Discord. Please try again.";
title = "Connection Failed";
break;
case "internal_server_error":
text = "Something went wrong on our end.";
title = "Server Error";
break;
case "config_error":
text = "System configuration error. Please contact support.";
title = "Configuration Error";
break;
default:
text = error;
}
setMessage({ type: "error", text, title });
setIsVisible(true);
} else if (success) {
let text = "Operation completed successfully.";
let title = "Success";
if (success === "bot_added") {
text = "Void Sentinel has been successfully added to your server!";
title = "Bot Added";
}
setMessage({ type: "success", text, title });
setIsVisible(true);
}
}, [searchParams]);
const closeToast = () => {
setIsVisible(false);
// Optional: clear params from URL without refresh
const params = new URLSearchParams(window.location.search);
params.delete("error");
params.delete("success");
params.delete("guild_id"); // Clean up other params if we want
router.replace(`?${params.toString()}`);
};
if (!isVisible || !message) return null;
return (
<div className="fixed bottom-6 right-6 z-50 animate-in slide-in-from-bottom-5 fade-in duration-300">
<div className={`
flex items-start gap-3 p-4 rounded-xl border shadow-2xl backdrop-blur-md max-w-md
${message.type === 'error'
? "bg-red-500/10 border-red-500/20 text-red-200"
: "bg-green-500/10 border-green-500/20 text-green-200"
}
`}>
<div className="mt-0.5">
{message.type === 'error' ? (
<AlertCircle className="w-5 h-5 text-red-400" />
) : (
<CheckCircle className="w-5 h-5 text-green-400" />
)}
</div>
<div className="flex-1 mr-4">
<h4 className={`font-semibold text-sm mb-1 ${message.type === 'error' ? 'text-red-100' : 'text-green-100'}`}>
{message.title}
</h4>
<p className="text-sm opacity-90 leading-relaxed">
{message.text}
</p>
</div>
<button
onClick={closeToast}
className="p-1 hover:bg-white/10 rounded-lg transition-colors -mr-1 -mt-1"
>
<X className="w-4 h-4 opacity-70" />
</button>
</div>
</div>
);
}