Adding UI of FeedBack, Registration, Admin console

This commit is contained in:
2026-05-12 20:54:15 +07:00
parent 6a48d6351c
commit dd95ff5a21
30 changed files with 10038 additions and 36 deletions
+116
View File
@@ -0,0 +1,116 @@
"use client";
import { useEffect, useRef, useState } from "react";
type ToastState = "idle" | "sending" | "ok" | "error";
type Props = {
state: ToastState;
successTitle?: string;
successMessage?: string;
onDismiss?: () => void;
};
/**
* Inline toast that appears below the submit button.
* - Slides up + fades in when visible
* - Auto-dismisses after 6 s on success
* - Shows a close button for manual dismiss
*/
export default function SubmitToast({
state,
successTitle = "Submitted successfully",
successMessage = "Your submission has been received.",
onDismiss,
}: Props) {
const [visible, setVisible] = useState(false);
const [mounted, setMounted] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isActive = state === "ok" || state === "error";
// Mount → trigger CSS enter transition
useEffect(() => {
if (isActive) {
setMounted(true);
// Tiny delay so the browser registers the initial state before animating
requestAnimationFrame(() => {
requestAnimationFrame(() => setVisible(true));
});
// Auto-dismiss success after 6 s
if (state === "ok") {
timerRef.current = setTimeout(() => handleDismiss(), 6000);
}
} else {
// Slide out then unmount
setVisible(false);
timerRef.current = setTimeout(() => setMounted(false), 400);
}
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive, state]);
const handleDismiss = () => {
if (timerRef.current) clearTimeout(timerRef.current);
setVisible(false);
setTimeout(() => {
setMounted(false);
onDismiss?.();
}, 400);
};
if (!mounted) return null;
const isSuccess = state === "ok";
return (
<div
role={isSuccess ? "status" : "alert"}
aria-live="polite"
style={{
opacity: visible ? 1 : 0,
transform: visible ? "translateY(0)" : "translateY(12px)",
transition: "opacity 0.4s cubic-bezier(0.16,1,0.3,1), transform 0.4s cubic-bezier(0.16,1,0.3,1)",
pointerEvents: visible ? "auto" : "none",
}}
className={[
"mt-5 w-full rounded-xl border px-5 py-4 flex items-start gap-4",
isSuccess
? "border-primary/40 bg-primary/10 text-primary"
: "border-red-500/40 bg-red-500/10 text-red-300",
].join(" ")}
>
{/* Icon */}
<span
className="material-symbols-outlined text-[28px] shrink-0 mt-0.5"
style={{ fontVariationSettings: "'FILL' 1" }}
>
{isSuccess ? "check_circle" : "error"}
</span>
{/* Text */}
<div className="flex-1 min-w-0">
<p className="font-[var(--font-display-lg)] font-semibold text-sm leading-snug">
{isSuccess ? successTitle : "Submission failed"}
</p>
<p className="mt-1 text-[13px] leading-relaxed opacity-80">
{isSuccess
? successMessage
: "Please check your connection and try again."}
</p>
</div>
{/* Close button */}
<button
type="button"
aria-label="Dismiss notification"
onClick={handleDismiss}
className="shrink-0 mt-0.5 opacity-60 hover:opacity-100 transition-opacity"
>
<span className="material-symbols-outlined text-[20px]">close</span>
</button>
</div>
);
}