Two changes targeting the post-update white screen on the Zapstore APK: 1. vite.config: set base './' when CAPACITOR_BUILD is set. In a native WebView the bundle is served from the local filesystem, so absolute '/' asset paths are the classic cause of a silent white screen. The web deploy keeps '/'. This is the documented Capacitor requirement and is zero-risk to the working web build. 2. index.html: add a dependency-free "boot crash guard" that runs before the app bundle and paints any uncaught error / unhandled rejection — plus a 12s "did not render" timeout — directly to the screen using inline styles only. A boot failure that would otherwise be an info-less white screen now shows the actual error ON DEVICE, with no USB/remote-debugging needed. main.tsx signals a clean boot to cancel the timeout guard. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import { createRoot } from 'react-dom/client';
|
|
|
|
// Import polyfills first
|
|
import './lib/polyfills.ts';
|
|
|
|
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
|
import App from './App.tsx';
|
|
import './index.css';
|
|
|
|
// Hide Capacitor splash screen once JS is ready
|
|
// Uses the global Capacitor object to avoid a hard dependency on the package
|
|
if (typeof window !== 'undefined') {
|
|
const cap = (window as unknown as { Capacitor?: { Plugins?: { SplashScreen?: { hide: () => Promise<void> } } } }).Capacitor;
|
|
if (cap?.Plugins?.SplashScreen) {
|
|
cap.Plugins.SplashScreen.hide().catch(() => {});
|
|
}
|
|
}
|
|
|
|
// Outfit font for body text
|
|
import '@fontsource-variable/outfit';
|
|
// Bebas Neue — bold condensed display font for headlines
|
|
import '@fontsource/bebas-neue';
|
|
// Barlow Condensed — tight condensed font for subheadings and UI labels
|
|
import '@fontsource/barlow-condensed/400.css';
|
|
import '@fontsource/barlow-condensed/600.css';
|
|
import '@fontsource/barlow-condensed/700.css';
|
|
|
|
// Leaflet CSS is loaded via CDN in index.html
|
|
|
|
/**
|
|
* AMBER CALLBACK PRE-CHECK + REDIRECT
|
|
*
|
|
* Amber opens the callbackUrl in a browser tab. Because we use HashRouter,
|
|
* the callbackUrl has no # fragment — it's a plain path:
|
|
* https://skate-spots.org/amber-callback?event=<pubkey>
|
|
*
|
|
* This IIFE runs before React mounts and:
|
|
* 1. Captures ?event= from the URL into sessionStorage
|
|
* 2. If we're on /amber-callback (non-hash path), redirects to /#/amber-callback
|
|
* so HashRouter can match the route and render AmberCallbackPage
|
|
*/
|
|
(function captureAmberCallback() {
|
|
// Try normal query string first (Amber callback: /amber-callback?event=XXX)
|
|
let eventParam = new URLSearchParams(window.location.search).get('event');
|
|
|
|
// Try hash fragment (Capacitor deep link: #/amber-callback?event=XXX)
|
|
if (!eventParam) {
|
|
const hash = window.location.hash;
|
|
const qIdx = hash.indexOf('?');
|
|
if (qIdx !== -1) {
|
|
eventParam = new URLSearchParams(hash.slice(qIdx)).get('event');
|
|
}
|
|
}
|
|
|
|
if (eventParam) {
|
|
console.log('[Amber Pre-Check] Detected Amber callback, event:', eventParam.substring(0, 16) + '...');
|
|
try {
|
|
sessionStorage.setItem('amber_callback_event', eventParam);
|
|
sessionStorage.setItem('amber_callback_url', window.location.href);
|
|
sessionStorage.setItem('amber_callback_timestamp', Date.now().toString());
|
|
} catch (e) {
|
|
console.error('[Amber Pre-Check] Failed to store callback:', e);
|
|
}
|
|
|
|
// If we landed on /amber-callback (non-hash path), redirect into HashRouter
|
|
// so React Router can match the route and render AmberCallbackPage.
|
|
// The event is already saved in sessionStorage above so it survives the redirect.
|
|
const isHashRoute = window.location.hash.startsWith('#/');
|
|
if (!isHashRoute && window.location.pathname.includes('amber-callback')) {
|
|
console.log('[Amber Pre-Check] Redirecting to HashRouter route: /#/amber-callback');
|
|
window.location.replace(window.location.origin + '/#/amber-callback');
|
|
// Stop — the page will reload at the correct hash route
|
|
return;
|
|
}
|
|
}
|
|
})();
|
|
|
|
|
|
createRoot(document.getElementById("root")!).render(
|
|
<ErrorBoundary>
|
|
<App />
|
|
</ErrorBoundary>
|
|
);
|
|
|
|
// Tell the boot crash guard (in index.html) that the app mounted successfully,
|
|
// so its "did not render" timeout doesn't fire.
|
|
try {
|
|
(window as unknown as { __SKATESPOTS_BOOTED__?: () => void }).__SKATESPOTS_BOOTED__?.();
|
|
} catch {
|
|
/* no-op */
|
|
}
|