Add src/components/demo/DemoControlsPanel.jsx
This commit is contained in:
parent
312ac7ab85
commit
db54a34613
|
|
@ -0,0 +1,124 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { FlaskConical, X, RotateCcw, ChevronUp, ChevronDown } from "lucide-react";
|
||||||
|
import { useDemoControls } from "./useDemoControls";
|
||||||
|
|
||||||
|
function Toggle({ label, description, value, onChange }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start justify-between gap-3 py-3 border-b border-gray-100 last:border-0">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium text-gray-800">{label}</p>
|
||||||
|
<p className="text-xs text-gray-400 mt-0.5 leading-relaxed">{description}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onChange(!value)}
|
||||||
|
className={`relative inline-flex h-6 w-11 shrink-0 rounded-full transition-colors duration-200 focus:outline-none mt-0.5 ${
|
||||||
|
value ? "bg-[#1a5276]" : "bg-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow-md transition-transform duration-200 mt-0.5 ${
|
||||||
|
value ? "translate-x-5" : "translate-x-0.5"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DemoControlsPanel() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const { controls, update, reset } = useDemoControls();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Floating trigger button */}
|
||||||
|
<div className="fixed bottom-4 left-4 z-[200]">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen((o) => !o)}
|
||||||
|
className="flex items-center gap-2 bg-[#1a5276] hover:bg-[#154360] text-white text-xs font-semibold px-3.5 py-2.5 rounded-xl shadow-lg transition-all duration-200"
|
||||||
|
>
|
||||||
|
<FlaskConical className="w-3.5 h-3.5" />
|
||||||
|
Demo Controls
|
||||||
|
{open ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Panel */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 12, scale: 0.97 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, y: 12, scale: 0.97 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="fixed bottom-16 left-4 z-[200] w-80 bg-white rounded-2xl shadow-2xl border border-gray-200 overflow-hidden"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between px-4 py-3 bg-gradient-to-r from-[#1a5276] to-[#2980b9]">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FlaskConical className="w-4 h-4 text-amber-300" />
|
||||||
|
<span className="text-white text-sm font-bold">Demo Controls</span>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setOpen(false)} className="text-white/60 hover:text-white transition-colors">
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Subtitle */}
|
||||||
|
<div className="px-4 py-2 bg-amber-50 border-b border-amber-100">
|
||||||
|
<p className="text-[11px] text-amber-700 font-medium">
|
||||||
|
For demo & presentation use only — simulates edge case scenarios
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggles */}
|
||||||
|
<div className="px-4 pt-1 pb-2">
|
||||||
|
<Toggle
|
||||||
|
label="User Activated"
|
||||||
|
description="OFF blocks dashboard access and shows the unverified account screen."
|
||||||
|
value={controls.userActivated}
|
||||||
|
onChange={(v) => update("userActivated", v)}
|
||||||
|
/>
|
||||||
|
<Toggle
|
||||||
|
label="Activation Code Valid"
|
||||||
|
description="OFF simulates an invalid verification code submission."
|
||||||
|
value={controls.activationCodeValid}
|
||||||
|
onChange={(v) => update("activationCodeValid", v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status summary */}
|
||||||
|
<div className="px-4 pb-3">
|
||||||
|
<div className="bg-gray-50 rounded-xl px-3 py-2 border border-gray-100">
|
||||||
|
<p className="text-[10px] text-gray-400 uppercase tracking-wider font-semibold mb-1.5">Active Simulations</p>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{!controls.userActivated && (
|
||||||
|
<span className="text-[10px] bg-red-100 text-red-700 px-2 py-0.5 rounded-full font-medium">Unverified User</span>
|
||||||
|
)}
|
||||||
|
{!controls.activationCodeValid && (
|
||||||
|
<span className="text-[10px] bg-red-100 text-red-700 px-2 py-0.5 rounded-full font-medium">Invalid Code</span>
|
||||||
|
)}
|
||||||
|
{controls.userActivated && controls.activationCodeValid && (
|
||||||
|
<span className="text-[10px] bg-green-100 text-green-700 px-2 py-0.5 rounded-full font-medium">Normal flow — no simulations active</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reset button */}
|
||||||
|
<div className="px-4 pb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => { reset(); setOpen(false); window.location.href = "/"; }}
|
||||||
|
className="w-full flex items-center justify-center gap-2 h-9 bg-gray-100 hover:bg-gray-200 text-gray-700 text-sm font-semibold rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
<RotateCcw className="w-3.5 h-3.5" />
|
||||||
|
Reset Demo State
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue