// Journey screen — stats from /api/journey
const { useState: useState_J, useEffect: useEffect_J } = React;
const PHASE_NAMES = { 1: "FOUNDATION", 2: "EXPLORATION", 3: "BUILDING", 4: "COMPOUND" };
const PHASE_LENGTH = 30;
const prettyTheme = (t) => (t || "unknown").replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
function JourneyScreen({ onSettings }) {
const [journey, setJourney] = useState_J(null);
const [err, setErr] = useState_J(null);
useEffect_J(() => {
api.getJourney().then(setJourney).catch(e => { console.warn("/api/journey failed:", e); setErr(e); });
}, []);
if (err || !journey) {
return (
);
}
const { streak, total_days, avg_rating, moved_pct, phase, theme_ratings, sparkline_7 } = journey;
if (total_days === 0) {
return (
Your journey
Just getting started
No entries yet. The first one is the hardest.
);
}
const phaseName = PHASE_NAMES[phase] || "FOUNDATION";
const phaseProgress = total_days % PHASE_LENGTH || (total_days === 0 ? 0 : PHASE_LENGTH);
const themesSorted = Object.entries(theme_ratings || {})
.sort((a, b) => b[1] - a[1])
.slice(0, 4);
const max = Math.max(5, ...(sparkline_7 || [1]));
const bars = sparkline_7 || [0,0,0,0,0,0,0];
const today = new Date();
const labels = Array.from({ length: 7 }, (_, i) => {
const d = new Date(today); d.setDate(today.getDate() - (6 - i));
return ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
});
return (
Your journey
{streak ? `Streak: ${streak}` : "Building"}
PHASE {phase} · {phaseName}
{phaseProgress} / {PHASE_LENGTH}
{themesSorted.length > 0 && (
What’s landing
{themesSorted.map(([t, v]) => (
{prettyTheme(t)}
{v.toFixed(1)} ★
))}
)}
Last 7 days
{bars.map((v, i) => (
))}
{labels.map((l, i) => {l})}
);
}
Object.assign(window, { JourneyScreen });