grants/src/components/DeploymentPlaybook.tsx
gdegelas a05331128b Atlas Green Morocco — grant strategy platform
- Full grant strategy framework for renewable energy & green hydrogen
- AI-powered grant studio, partner outreach, financial modeling
- Umami analytics with data-performance tracking
- Live Degelas metrics connected to solar.degelas.be
- Trilingual (EN/FR/AR) with i18n support
- Dockerized with Nginx frontend + Express API proxy
2026-06-01 09:44:03 +00:00

205 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
import { deploymentStages } from "../data/deployment";
import { deploymentStagesDarija } from "../data/deployment.darija";
import { deploymentStagesFr } from "../data/deployment.fr";
import { Section } from "./Section";
import { cn } from "../utils/cn";
function CriticalityBadge({ level, suffix }: { level: "CRITICAL" | "HIGH" | "MEDIUM"; suffix: string }) {
const styles = {
CRITICAL: "bg-rose-100 text-rose-700 border-rose-300",
HIGH: "bg-amber-100 text-amber-700 border-amber-300",
MEDIUM: "bg-teal-100 text-teal-700 border-teal-300",
};
return (
<span className={cn("rounded-full border px-3 py-1 text-xs font-semibold", styles[level])}>
{level} {suffix}
</span>
);
}
import { useI18n } from "../i18n";
export function DeploymentPlaybook() {
const { t, locale } = useI18n();
const [expanded, setExpanded] = useState<string>("0");
const currentStages = locale === "darija" ? deploymentStagesDarija : locale === "fr" ? deploymentStagesFr : deploymentStages;
return (
<Section
id="deployment"
eyebrow={t.deployment.eyebrow}
title={t.deployment.title}
intro={t.deployment.intro}
>
<div className="space-y-4">
{currentStages.map((stage) => {
const isOpen = expanded === stage.phase;
return (
<div
key={stage.phase}
className={cn(
"overflow-hidden rounded-3xl border transition-all",
isOpen ? "border-emerald-300 bg-white shadow-xl shadow-emerald-500/10" : "border-slate-200 bg-white"
)}
>
<button
onClick={() => setExpanded(isOpen ? "" : stage.phase)}
className="w-full px-6 py-6 text-left sm:px-8"
>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-3">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-500 to-teal-600 text-sm font-bold text-white">
{stage.phase}
</span>
<div>
<h3 className="text-lg font-bold text-emerald-950 sm:text-xl">{stage.title}</h3>
<p className="text-xs text-slate-500">{stage.subtitle}</p>
</div>
</div>
<p className="mt-3 text-sm leading-relaxed text-slate-600">{stage.objective}</p>
</div>
<span
className={cn(
"flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-slate-200 text-slate-400 transition-transform",
isOpen && "rotate-45 border-emerald-400 text-emerald-500"
)}
>
<svg className="h-5 w-5" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
<path strokeLinecap="round" d="M12 5v14M5 12h14" />
</svg>
</span>
</div>
</button>
<div
className={cn(
"grid transition-all duration-300",
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
)}
>
<div className="overflow-hidden">
<div className="border-t border-slate-100 px-6 pb-8 pt-6 sm:px-8 space-y-8">
{/* Key Metrics */}
<div className="grid gap-4 sm:grid-cols-2">
<div className="rounded-2xl border border-slate-100 bg-slate-50 p-5">
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest">{t.deployment.duration}</div>
<div className="mt-2 text-xl font-bold text-emerald-950">{stage.duration}</div>
</div>
<div className="rounded-2xl border border-slate-100 bg-slate-50 p-5">
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest">{t.deployment.priority}</div>
<div className="mt-2">
<CriticalityBadge level={stage.criticality} suffix={t.deployment.pathSuffix} />
</div>
</div>
</div>
{/* Actions */}
<div>
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">{t.deployment.actions}</h4>
<div className="space-y-4">
{stage.actions.map((action, idx) => (
<div key={idx} className="rounded-2xl border border-slate-100 bg-slate-50/50 p-5">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<h5 className="font-semibold text-emerald-950">{action.title}</h5>
<p className="mt-2 text-sm text-slate-600">{action.description}</p>
<div className="mt-4 flex flex-wrap gap-3 text-xs">
<div className="flex items-center gap-1">
<span className="font-semibold text-slate-500">{t.deployment.owner}:</span>
<span className="text-slate-600">{action.owner}</span>
</div>
<div className="flex items-center gap-1">
<span className="font-semibold text-slate-500">{t.deployment.timeline}:</span>
<span className="text-slate-600">{action.timeline}</span>
</div>
</div>
</div>
</div>
{action.resources.length > 0 && (
<div className="mt-4 pt-4 border-t border-slate-100">
<div className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-2">
{t.deployment.resources}
</div>
<div className="flex flex-wrap gap-2">
{action.resources.map((r) => (
<span key={r} className="rounded-full bg-white px-2.5 py-1 text-xs text-slate-600">
{r}
</span>
))}
</div>
</div>
)}
</div>
))}
</div>
</div>
{/* Checkpoints */}
<div>
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">{t.deployment.checkpoints}</h4>
<div className="space-y-3">
{stage.checkpoints.map((cp, idx) => (
<div key={idx} className="rounded-2xl border border-emerald-100 bg-emerald-50/40 p-5">
<h5 className="font-semibold text-emerald-950">{cp.name}</h5>
<p className="mt-1 text-sm text-emerald-900/70">{cp.description}</p>
<ul className="mt-3 space-y-1.5">
{cp.success.map((s) => (
<li key={s} className="flex items-center gap-2 text-xs text-emerald-800">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
{s}
</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Success Metrics */}
<div>
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">
{t.deployment.metrics}
</h4>
<div className="grid gap-3 sm:grid-cols-2">
{stage.successMetrics.map((metric, idx) => (
<div
key={idx}
className="flex items-start gap-3 rounded-2xl border border-teal-100 bg-teal-50/40 p-4"
>
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-teal-500" />
<span className="text-xs leading-relaxed text-teal-900">{metric}</span>
</div>
))}
</div>
</div>
{/* Pitfalls */}
{stage.pitfalls.length > 0 && (
<div>
<h4 className="mb-4 text-sm font-bold uppercase tracking-widest text-slate-500">
{t.deployment.pitfalls}
</h4>
<div className="space-y-2.5">
{stage.pitfalls.map((pitfall, idx) => (
<div key={idx} className="flex items-start gap-3 rounded-2xl border border-rose-100 bg-rose-50/40 p-4">
<span className="mt-0.5 shrink-0 text-lg">🚫</span>
<span className="text-sm text-rose-900">{pitfall}</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
})}
</div>
</Section>
);
}