202 lines
7.9 KiB
JavaScript
202 lines
7.9 KiB
JavaScript
import { NavLink, useLocation, useNavigate } from 'react-router-dom';
|
|
import { useBusiness } from '../context/BusinessContext';
|
|
|
|
const SVG_ICONS = {
|
|
globalSms: (
|
|
<svg className="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
),
|
|
events: (
|
|
<svg className="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
),
|
|
templates: (
|
|
<svg className="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
),
|
|
};
|
|
|
|
function TopLevelStatus({ done, active }) {
|
|
if (done) {
|
|
return (
|
|
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white shadow-sm">
|
|
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (active) {
|
|
return <span className="inline-block h-2.5 w-2.5 rounded-full bg-indigo-600 shadow-sm" />;
|
|
}
|
|
|
|
return <span className="inline-block h-2.5 w-2.5 rounded-full bg-gray-200" />;
|
|
}
|
|
|
|
export default function Sidebar() {
|
|
const {
|
|
activeBusiness,
|
|
activeBusinessId,
|
|
clearBusiness,
|
|
hasGlobalSms,
|
|
isSetupComplete,
|
|
hasSelectedTemplates,
|
|
} = useBusiness();
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
const globalSmsPath = `/${activeBusinessId}/global-sms`;
|
|
const eventsPath = `/${activeBusinessId}/events`;
|
|
const templatesPath = `/${activeBusinessId}/templates`;
|
|
|
|
const isGlobalSmsRoute = location.pathname === globalSmsPath;
|
|
const isEventsRoute = location.pathname === eventsPath;
|
|
const isTemplatesRoute = location.pathname === templatesPath;
|
|
|
|
const omniSubsteps = [
|
|
{ id: 'profile', label: 'Add / Select Profile', done: hasGlobalSms, active: isGlobalSmsRoute && !hasGlobalSms },
|
|
{ id: 'validate', label: 'Validate cURL', done: hasGlobalSms, active: false },
|
|
{ id: 'fields', label: 'Complete Fields', done: isSetupComplete, active: isGlobalSmsRoute && hasGlobalSms && !isSetupComplete },
|
|
{ id: 'ready', label: 'Ready', done: isSetupComplete, active: isGlobalSmsRoute && isSetupComplete },
|
|
];
|
|
|
|
const stepItems = [
|
|
{
|
|
id: 'globalSms',
|
|
to: globalSmsPath,
|
|
label: 'Omni-channel SMS',
|
|
enabled: true,
|
|
done: isSetupComplete && !isGlobalSmsRoute,
|
|
active: isGlobalSmsRoute,
|
|
expanded: isGlobalSmsRoute,
|
|
substeps: omniSubsteps,
|
|
},
|
|
{
|
|
id: 'events',
|
|
to: eventsPath,
|
|
label: 'Events',
|
|
enabled: isSetupComplete,
|
|
done: hasSelectedTemplates && !isEventsRoute,
|
|
active: isEventsRoute,
|
|
},
|
|
{
|
|
id: 'templates',
|
|
to: templatesPath,
|
|
label: 'Templates',
|
|
enabled: hasSelectedTemplates,
|
|
done: false,
|
|
active: isTemplatesRoute,
|
|
},
|
|
];
|
|
|
|
function handleSwitch() {
|
|
clearBusiness();
|
|
navigate('/');
|
|
}
|
|
|
|
return (
|
|
<aside className="fixed top-0 left-0 h-screen w-60 bg-white border-r border-gray-200 flex flex-col z-10">
|
|
{/* Business info + switch */}
|
|
<div className="px-5 py-5 border-b border-gray-100">
|
|
<button
|
|
onClick={handleSwitch}
|
|
className="flex items-center gap-2 text-gray-500 hover:text-gray-900 transition text-sm group font-medium"
|
|
>
|
|
<span className="group-hover:-translate-x-0.5 transition-transform text-lg leading-none">←</span>
|
|
<span>Switch Business</span>
|
|
</button>
|
|
{activeBusiness && (
|
|
<div className="mt-4 flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded-md bg-indigo-600 flex items-center justify-center text-sm font-bold text-white shrink-0 shadow-sm">
|
|
{activeBusiness.brandName?.[0]?.toUpperCase() || 'B'}
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-semibold text-gray-900 truncate">{activeBusiness.brandName}</p>
|
|
<p className="text-xs text-gray-400 truncate font-medium">{activeBusiness.domain}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Nav */}
|
|
<nav className="flex-1 px-3 pt-5">
|
|
<div className="space-y-2">
|
|
{stepItems.map((item) => (
|
|
<div key={item.id}>
|
|
{item.enabled ? (
|
|
<NavLink
|
|
to={item.to}
|
|
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold transition-colors duration-150 ${
|
|
item.active
|
|
? 'bg-gray-100/70 text-gray-900'
|
|
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
{SVG_ICONS[item.id]}
|
|
<span className="flex-1 truncate">{item.label}</span>
|
|
<div className="flex items-center gap-3">
|
|
{!item.substeps && <TopLevelStatus done={item.done} active={item.active} />}
|
|
{item.substeps && (
|
|
<svg
|
|
className={`h-4 w-4 text-gray-400 transition-transform ${item.expanded ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
</NavLink>
|
|
) : (
|
|
<div
|
|
aria-disabled="true"
|
|
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold text-gray-300 cursor-not-allowed select-none"
|
|
>
|
|
{SVG_ICONS[item.id]}
|
|
<span className="flex-1 truncate">{item.label}</span>
|
|
<div className="flex items-center gap-3">
|
|
{!item.substeps && <TopLevelStatus done={item.done} active={item.active} />}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{item.expanded && item.substeps && (
|
|
<div className="relative mt-2 mb-2 pb-1">
|
|
<div className="absolute left-[21.5px] top-1 bottom-3 w-px bg-gray-200" />
|
|
<div className="space-y-0.5">
|
|
{item.substeps.map((substep) => (
|
|
<div key={substep.id} className="relative flex items-center pr-3 group cursor-default">
|
|
<div className="w-[44px] flex justify-center items-center shrink-0">
|
|
{substep.active && <div className="w-1.5 h-1.5 rounded-full bg-[#8b2bfa] z-10 shadow-[0_0_0_2px_white]" />}
|
|
</div>
|
|
<div
|
|
className={`flex-1 px-3 py-2.5 rounded-[12px] text-[14px] transition-colors ${
|
|
substep.active
|
|
? 'bg-[#f5eeff] text-[#8b2bfa] font-semibold'
|
|
: 'text-gray-500 font-medium hover:text-gray-900 hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
{substep.label}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="px-5 py-4 border-t border-gray-100">
|
|
<p className="text-xs text-gray-500 font-medium tracking-wide">TRAI-compliant SMS</p>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|