/* P.E.S.T Web App โ Dashboard (orbit: rotating PFP + pinned project nodes) */
function RotatingPFP({ member }) {
const pfps = (member.pfps || []).filter((p) => p.img);
const [i, setI] = useState(0);
useEffect(() => {
if (pfps.length < 2) return;
const t = setInterval(() => setI((x) => (x + 1) % pfps.length), 4200);
return () => clearInterval(t);
}, [pfps.length]);
return (
{pfps.length === 0 &&
{member.nick.slice(0, 2)}
}
{pfps.map((p, k) => (

))}
{member.nick}
);
}
function orbitPositions(n) {
// evenly distribute exactly n nodes on an ellipse, first at top
const rx = 41, ry = 38;
const out = [];
for (let i = 0; i < n; i++) {
const a = (-90 + (360 / n) * i) * (Math.PI / 180);
out.push({ x: 50 + rx * Math.cos(a), y: 50 + ry * Math.sin(a) });
}
return out;
}
function Dashboard({ onNavigate }) {
const store = useStore();
const me = store.member; // full member object of logged-in user
const pinned = store.user.pinned
.map((id) => store.projects.find((p) => p.id === id))
.filter(Boolean);
// Only render real pinned projects โ no empty/placeholder nodes.
const pos = orbitPositions(pinned.length);
const [qa, setQa] = useState(null);
const [pickTokens, setPickTokens] = useState(false);
const [clock, setClock] = useState(new Date());
useEffect(() => {
const t = setInterval(() => setClock(new Date()), 30000);
return () => clearInterval(t);
}, []);
const hh = String(clock.getHours()).padStart(2, "0");
const mm = String(clock.getMinutes()).padStart(2, "0");
const dateStr = clock.toLocaleDateString("en-GB", { weekday: "short", day: "2-digit", month: "short" });
return (
Dashboard
Welcome back, {me.nick}
{hh}:{mm}{dateStr} ยท {pinned.length}/{window.PEST_APP.MAX_PINS} pinned
{pinned.map((p, i) => (
setQa(p)}>
{p.name}
))}
{pinned.length === 0 && (
Your hive is empty. Pin the projects you use most from the Projects page โ they'll orbit your node here for one-tap access.
)}
setPickTokens(true)} />
{qa && setQa(null)} />}
{pickTokens && setPickTokens(false)} />}
);
}
function QuickAccess({ project, onClose }) {
const store = useStore();
useEffect(() => {
const k = (e) => e.key === "Escape" && onClose();
window.addEventListener("keydown", k);
return () => window.removeEventListener("keydown", k);
}, []);
const links = Object.keys(project.links || {}).filter((k) => project.links[k]);
return (
e.stopPropagation()}>
{project.name}
{((project.categories && project.categories.length) ? project.categories : (project.category ? [project.category] : [])).map((c) => {c})}
{links.map((k) => {
const meta = window.A_SOCIAL[k]; const I = meta.icon;
return
{meta.label};
})}
{links.length === 0 &&
No links yet.}
{project.links.website &&
Open}
);
}
window.Dashboard = Dashboard;
window.orbitPositions = orbitPositions;
window.QuickAccess = QuickAccess;