10 changed files with 204 additions and 42 deletions
-
2Dockerfile
-
21next.config.ts
-
16src/app/layout.tsx
-
38src/app/questions-list/page.tsx
-
41src/hooks/use-close-service-on-back.ts
-
34src/lib/get-submit-path.ts
-
3src/lib/http.ts
-
75src/lib/match-start-grace.ts
-
3src/translations/locales/en.json
-
3src/translations/locales/fa.json
@ -0,0 +1,41 @@ |
|||
"use client"; |
|||
|
|||
import { useEffect } from "react"; |
|||
|
|||
/** |
|||
* Hook to intercept browser/hardware back button (popstate) and trigger |
|||
* Flutter's `close_service` action when running inside Flutter WebView. |
|||
*/ |
|||
export function useCloseServiceOnBack(enabled: boolean = true) { |
|||
useEffect(() => { |
|||
if (!enabled || typeof window === "undefined") { |
|||
return; |
|||
} |
|||
|
|||
const app = (window as any).HabibApp; |
|||
if (!app?.postMessage) { |
|||
return; |
|||
} |
|||
|
|||
// Push a dummy history state so hardware back triggers popstate instead of navigating away
|
|||
window.history.pushState({ closeOnBack: true }, "", window.location.href); |
|||
|
|||
const handlePopState = () => { |
|||
if ((window as any).HabibApp?.postMessage) { |
|||
// Keep the state pinned in webview and notify Flutter to close the webview
|
|||
window.history.pushState({ closeOnBack: true }, "", window.location.href); |
|||
(window as any).HabibApp.postMessage( |
|||
JSON.stringify({ action: "close_service" }), |
|||
); |
|||
} |
|||
}; |
|||
|
|||
window.addEventListener("popstate", handlePopState); |
|||
|
|||
return () => { |
|||
window.removeEventListener("popstate", handlePopState); |
|||
}; |
|||
}, [enabled]); |
|||
} |
|||
|
|||
export default useCloseServiceOnBack; |
|||
@ -0,0 +1,75 @@ |
|||
// The backend can still report `pending_info` for a short moment after a match
|
|||
// request is accepted. Without a grace window the user is bounced from
|
|||
// /finding-match straight back to /questions-list.
|
|||
//
|
|||
// This lives in sessionStorage on purpose: it must not survive a WebView
|
|||
// restart the way the old `match_submitted` localStorage flag did, because a
|
|||
// sticky flag shadows the real profile status forever.
|
|||
|
|||
const STORAGE_KEY = "marriage:match-start-at"; |
|||
const LEGACY_STORAGE_KEY = "match_submitted"; |
|||
const GRACE_MS = 60_000; |
|||
|
|||
export function markMatchStarted() { |
|||
if (typeof window === "undefined") { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
window.sessionStorage.setItem(STORAGE_KEY, String(Date.now())); |
|||
} catch { |
|||
// Storage can throw in private mode or when the quota is exhausted.
|
|||
} |
|||
} |
|||
|
|||
export function isWithinMatchStartGrace() { |
|||
if (typeof window === "undefined") { |
|||
return false; |
|||
} |
|||
|
|||
try { |
|||
const rawValue = window.sessionStorage.getItem(STORAGE_KEY); |
|||
|
|||
if (!rawValue) { |
|||
return false; |
|||
} |
|||
|
|||
const startedAt = Number(rawValue); |
|||
|
|||
if (!Number.isFinite(startedAt) || Date.now() - startedAt > GRACE_MS) { |
|||
window.sessionStorage.removeItem(STORAGE_KEY); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} catch { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
export function clearMatchStartGrace() { |
|||
if (typeof window === "undefined") { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
window.sessionStorage.removeItem(STORAGE_KEY); |
|||
} catch { |
|||
// Ignore storage failures – the grace window expires on its own anyway.
|
|||
} |
|||
} |
|||
|
|||
// Older builds wrote a `match_submitted` flag to localStorage and never removed
|
|||
// it, which pinned affected users to /finding-match permanently. Drop it on boot
|
|||
// so devices already carrying the flag recover without a manual data clear.
|
|||
export function clearLegacyMatchSubmittedFlag() { |
|||
if (typeof window === "undefined") { |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
window.localStorage.removeItem(LEGACY_STORAGE_KEY); |
|||
} catch { |
|||
// Ignore storage failures.
|
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue