/* ============================================================
INDIC LEAGUE NEWS — App shell
Top-level routing + persistence.
Routes: splash -> login -> onboarding -> quiz -> feed
============================================================ */
const ROUTES = {
SPLASH: 'splash',
LOGIN: 'login',
ONBOARD: 'onboard',
QUIZ: 'quiz',
FEED: 'feed',
};
function App() {
const Storage = window.IL_Util.Storage;
const KEYS = window.IL_KEYS;
// Initial route inference. If user already onboarded, go straight to feed.
const computeInitial = () => {
const topics = Storage.get(KEYS.topics, null);
if (Array.isArray(topics) && topics.length >= 3) return ROUTES.FEED;
return ROUTES.SPLASH;
};
const [route, setRoute] = React.useState(computeInitial);
const [user, setUser] = React.useState(() => Storage.get(KEYS.user, null));
const [theme, setTheme] = React.useState(() => {
const stored = Storage.get(KEYS.theme, null);
if (stored === 'dark' || stored === 'light') return stored;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
return 'light';
});
// Quiz-result-on-return-to-onboarding stash
const [pendingPolitical, setPendingPolitical] = React.useState(null);
// Apply theme
React.useEffect(() => {
window.IL_Util.applyTheme(theme);
Storage.set(KEYS.theme, theme);
}, [theme]);
// Remove pre-react boot splash once mounted
React.useEffect(() => {
const boot = document.querySelector('.boot');
if (boot) {
boot.style.transition = 'opacity 400ms';
boot.style.opacity = '0';
setTimeout(() => boot.remove(), 420);
}
}, []);
// Browser back-button support
React.useEffect(() => {
const onPop = (e) => {
// Always send user to feed if onboarded, else splash
if (Storage.get(KEYS.topics, []).length >= 3) {
setRoute(ROUTES.FEED);
} else {
setRoute(ROUTES.SPLASH);
}
};
window.addEventListener('popstate', onPop);
return () => window.removeEventListener('popstate', onPop);
}, []);
// Track desktop layout class
const [wide, setWide] = React.useState(window.matchMedia('(min-width: 1024px)').matches);
React.useEffect(() => {
const mq = window.matchMedia('(min-width: 1024px)');
const handler = (e) => setWide(e.matches);
if (mq.addEventListener) mq.addEventListener('change', handler);
else mq.addListener(handler);
return () => {
if (mq.removeEventListener) mq.removeEventListener('change', handler);
else mq.removeListener(handler);
};
}, []);
// -------- handlers --------
const onSplashContinue = (where) => {
if (where === 'login') setRoute(ROUTES.LOGIN);
else setRoute(ROUTES.ONBOARD);
};
const onLoggedIn = (u) => {
setUser(u);
const topics = Storage.get(KEYS.topics, []);
if (Array.isArray(topics) && topics.length >= 3) {
setRoute(ROUTES.FEED);
} else {
setRoute(ROUTES.ONBOARD);
}
};
const onOnboardComplete = () => {
setPendingPolitical(null);
setRoute(ROUTES.FEED);
};
const onStartQuiz = () => setRoute(ROUTES.QUIZ);
const onQuizComplete = (result) => {
// result: { position, label, optionId, method }
setPendingPolitical(result);
setRoute(ROUTES.ONBOARD);
};
const onQuizCancel = () => {
setPendingPolitical(null);
// If user came from onboarding, back to onboarding. Otherwise feed.
const topics = Storage.get(KEYS.topics, []);
if (Array.isArray(topics) && topics.length >= 3) setRoute(ROUTES.FEED);
else setRoute(ROUTES.ONBOARD);
};
const onLogout = () => {
Storage.remove(KEYS.user);
Storage.remove(KEYS.authed);
setUser(null);
// Keep topics so they don't have to re-onboard. Just dump to splash.
setRoute(ROUTES.SPLASH);
};
const toggleTheme = () => {
setTheme(t => (t === 'dark' ? 'light' : 'dark'));
window.IL_Sounds && window.IL_Sounds.tap();
window.IL_Util && window.IL_Util.Haptics.light();
};
// -------- render --------
let body;
switch (route) {
case ROUTES.SPLASH:
body = ;
break;
case ROUTES.LOGIN:
body = setRoute(ROUTES.SPLASH)} />;
break;
case ROUTES.ONBOARD:
body = (
);
break;
case ROUTES.QUIZ:
body = ;
break;
case ROUTES.FEED:
default:
body = (
);
break;
}
return (
);
}
Object.assign(window, { App });