diff --git a/components/game/GameFlowManager.tsx b/components/game/GameFlowManager.tsx index 8028a3d..4466f40 100644 --- a/components/game/GameFlowManager.tsx +++ b/components/game/GameFlowManager.tsx @@ -701,6 +701,38 @@ export default function GameFlowManager() { setPhase('settings') } + // ── THE PRIVY GATE, AHEAD OF EVERYTHING ─────────────────────────────────── + // + // Above `phase === 'menu'` on purpose, and it was BELOW it in the first + // version — which is the whole bug the owner hit: the map is what 'menu' + // renders, so the gate could not appear until something moved the phase off + // it, and the login turned up AFTER the world map instead of before it. + // + // OWNER: *"harusnya sebelum user membuka maps, jd di awal banget persis + // setelah user klik Play game di landing page."* + // + // Nothing else may render first. This is the first screen of the session. + if (showPrivyGate) { + return ( + { + // Through the SAME adopt() the Firebase screen uses: derive an + // address from the id, carry the guest's progress onto it, store the + // identity. Nothing about Privy is special here — adopt() only ever + // wanted a stable id, and this is one. Without this the screen's own + // promise ("it follows you anywhere") would be false. + try { + await acct.adopt(uid, label) + } finally { + // Even a failed migration must not trap the player on this screen. + setGateDone(true) + } + }} + onSkip={() => { acct.rememberSkipped(); setGateDone(true) }} + /> + ) + } + // PHASE: MENU if (phase === 'menu') { // Same props for both — the hub is a drop-in swap for the classic menu. @@ -742,29 +774,8 @@ export default function GameFlowManager() { } // PHASE: SIGN IN — offered once, before the name, and only to the players - // who have nothing keeping their progress. See shouldOfferSignIn(). - // Ahead of every other phase: this is the first thing after the splash. - if (showPrivyGate) { - return ( - { - // Through the SAME adopt() the Firebase screen uses: derive an - // address from the id, carry the guest's progress onto it, store the - // identity. Nothing about Privy is special here — adopt() only ever - // wanted a stable id, and this is one. Without this the screen's own - // promise ("it follows you anywhere") would be false. - try { - await acct.adopt(uid, label) - } finally { - // Even a failed migration must not trap the player on this screen. - setGateDone(true) - } - }} - onSkip={() => { acct.rememberSkipped(); setGateDone(true) }} - /> - ) - } - + // who have nothing keeping their progress. See shouldOfferSignIn(). Off + // entirely while the Privy gate is on; see goPlaying(). if (phase === 'sign-in') { return ( (null) const adopted = useRef(false) // Privy restores an existing session asynchronously. Someone who already @@ -67,48 +79,107 @@ function GateInner({ onAuthenticated, onSkip }: PrivyGateProps) { ) }, [ready, authenticated, user, onAuthenticated]) - const start = useCallback(() => { - setBusy(true) + const start = useCallback((method: Method) => { + setBusy(method) + // Narrowed to the one method the player pressed. Privy's modal would + // otherwise re-ask the question this screen just answered. + login({ loginMethods: [method] }) + // The modal belongs to Privy, so there is no promise to await. Clear the + // pending state shortly after it has been asked to open, or a dismissed + // modal would leave the button stuck reading "Opening…" forever. + setTimeout(() => setBusy(null), 1500) + }, [login]) + + // ── GOOGLE SKIPS PRIVY'S SCREEN ENTIRELY ────────────────────────────────── + // + // OWNER: *"itu kalo aku klik google, ga muncul 2x pop up kan? layar privy + // juga maksudku."* It did. `login({loginMethods:['google']})` opens Privy's + // modal showing a single Google button — the same question this screen just + // asked, asked again — and only then hands off to Google. + // + // initOAuth goes straight to Google with no Privy UI at all, so the tap on + // "Continue with Google" is the last thing before Google's own account + // chooser. It is a full-page redirect rather than a popup, which is also the + // right shape on a phone: popups are what mobile browsers block. + // + // Privy marks the hook @experimental, so it cannot be the only path. If it + // throws, this falls back to the modal — one extra screen is a worse login, + // an unrecoverable one is no login at all. + const startGoogle = useCallback(async () => { + setBusy('google') try { - // Privy owns the modal from here: Google, email and wallet all live - // inside it, so there is no second choice to present. - login() - } finally { - // The modal is Privy's, so there is no promise to await — clear the - // pending state once it has been asked to open, or a dismissed modal - // would leave the button stuck. - setTimeout(() => setBusy(false), 1200) + await initOAuth({ provider: 'google' }) + } catch { + login({ loginMethods: ['google'] }) + setTimeout(() => setBusy(null), 1500) } - }, [login]) + // No success branch clears `busy` on purpose: the page is navigating to + // Google, and a button that flips back to "Continue with Google" while the + // redirect is in flight invites a second tap. + }, [initOAuth, login]) + + const disabled = !ready || busy !== null return (
-
-

// SAVE YOUR PROGRESS

-

KEEP YOUR RUN

-

+

@@ -127,8 +198,8 @@ export default function PrivyGate(props: PrivyGateProps) {