feat(auth): add Messenger topup handoff entry
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
||||
} from "@/data/constants/character";
|
||||
import { Logger } from "@/utils/logger";
|
||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { consumeTopUpHandoff } from "@/lib/auth/top_up_handoff";
|
||||
import { Result } from "@/utils/result";
|
||||
import {
|
||||
isFavoriteEntryRequest,
|
||||
persistFavoriteEntryIntent,
|
||||
@@ -38,6 +40,7 @@ interface ExternalEntryPersistProps {
|
||||
mode: string | null;
|
||||
promotionType: string | null;
|
||||
favorite: string | null;
|
||||
handoffToken: string | null;
|
||||
}
|
||||
|
||||
export default function ExternalEntryPersist({
|
||||
@@ -50,13 +53,16 @@ export default function ExternalEntryPersist({
|
||||
mode,
|
||||
promotionType,
|
||||
favorite,
|
||||
handoffToken,
|
||||
}: ExternalEntryPersistProps) {
|
||||
const router = useRouter();
|
||||
const authState = useAuthState();
|
||||
const authDispatch = useAuthDispatch();
|
||||
const [hasPersisted, setHasPersisted] = useState(false);
|
||||
const [handoffError, setHandoffError] = useState<string | null>(null);
|
||||
const [handoffCompleted, setHandoffCompleted] = useState(false);
|
||||
const hasNavigatedRef = useRef(false);
|
||||
const hasReinitializedForPsidRef = useRef(false);
|
||||
const handoffStartedRef = useRef(false);
|
||||
const targetRoute = resolveExternalEntryTarget({ target });
|
||||
const destination = resolveExternalEntryDestination({ target, character });
|
||||
const characterId =
|
||||
@@ -65,6 +71,12 @@ export default function ExternalEntryPersist({
|
||||
mode,
|
||||
promotionType,
|
||||
});
|
||||
const isTopUpHandoff = targetRoute === ROUTES.subscription;
|
||||
const displayedHandoffError =
|
||||
handoffError ??
|
||||
(isTopUpHandoff && !hasValue(handoffToken)
|
||||
? "This top-up link is invalid. Please request a new link in Messenger."
|
||||
: null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -116,12 +128,39 @@ export default function ExternalEntryPersist({
|
||||
useEffect(() => {
|
||||
if (hasNavigatedRef.current || !hasPersisted) return;
|
||||
|
||||
// AuthStatusChecker can initialize before this entry has persisted PSID.
|
||||
// Re-run the check so PSID direct login is not skipped by that race.
|
||||
if (hasValue(psid) && !hasReinitializedForPsidRef.current) {
|
||||
if (isTopUpHandoff) {
|
||||
if (!authState.hasInitialized || authState.isLoading) return;
|
||||
hasReinitializedForPsidRef.current = true;
|
||||
authDispatch({ type: "AuthInit" });
|
||||
if (handoffCompleted) {
|
||||
if (
|
||||
authState.loginStatus === "notLoggedIn" ||
|
||||
authState.loginStatus === "guest"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
hasNavigatedRef.current = true;
|
||||
router.replace(destination);
|
||||
return;
|
||||
}
|
||||
if (handoffStartedRef.current) return;
|
||||
if (!hasValue(handoffToken)) return;
|
||||
handoffStartedRef.current = true;
|
||||
void (async () => {
|
||||
const result = await consumeTopUpHandoff(handoffToken);
|
||||
if (Result.isErr(result)) {
|
||||
log.warn("[ExternalEntryPersist] top-up handoff failed", result.error);
|
||||
window.history.replaceState(
|
||||
window.history.state,
|
||||
"",
|
||||
"/external-entry?target=topup",
|
||||
);
|
||||
setHandoffError(
|
||||
"This top-up link is invalid or has expired. Please request a new link in Messenger.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setHandoffCompleted(true);
|
||||
authDispatch({ type: "AuthInit" });
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,6 +191,9 @@ export default function ExternalEntryPersist({
|
||||
authState.loginStatus,
|
||||
destination,
|
||||
hasPersisted,
|
||||
handoffCompleted,
|
||||
handoffToken,
|
||||
isTopUpHandoff,
|
||||
psid,
|
||||
router,
|
||||
]);
|
||||
@@ -161,11 +203,26 @@ export default function ExternalEntryPersist({
|
||||
className="flex flex-1 items-center justify-center"
|
||||
style={{ minHeight: "100dvh" }}
|
||||
>
|
||||
<div
|
||||
aria-label="Redirecting"
|
||||
className="size-10 animate-spin rounded-full border-4 border-t-transparent"
|
||||
style={{ borderColor: "var(--color-accent)" }}
|
||||
/>
|
||||
{displayedHandoffError ? (
|
||||
<div className="mx-6 max-w-sm text-center">
|
||||
<p className="text-sm text-[var(--color-text-secondary)]">
|
||||
{displayedHandoffError}
|
||||
</p>
|
||||
<button
|
||||
className="mt-5 rounded-full bg-[var(--color-accent)] px-5 py-2.5 text-sm font-semibold text-white"
|
||||
type="button"
|
||||
onClick={() => router.replace(ROUTES.root)}
|
||||
>
|
||||
Back to Cozsweet
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
aria-label="Redirecting"
|
||||
className="size-10 animate-spin rounded-full border-4 border-t-transparent"
|
||||
style={{ borderColor: "var(--color-accent)" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user