This commit is contained in:
KS Jannette
2026-03-29 08:17:11 -04:00
parent 0412ea3e99
commit 965830fcde
2 changed files with 51 additions and 24 deletions

View File

@@ -1,4 +1,4 @@
import { createContext, useReducer, useContext, useEffect } from "react"; import { createContext, useReducer, useContext, useEffect, useCallback, useRef } from "react";
import authReducer, { initialState, ACTION_TYPES } from "../reducers/authReducer"; import authReducer, { initialState, ACTION_TYPES } from "../reducers/authReducer";
import { loginUser, registerAfterCheckout } from "../api/auth"; import { loginUser, registerAfterCheckout } from "../api/auth";
import { getAccount } from "../api/account"; import { getAccount } from "../api/account";
@@ -7,14 +7,11 @@ const AuthContext = createContext(null);
export function AuthProvider({ children }) { export function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, initialState); const [state, dispatch] = useReducer(authReducer, initialState);
const fetchedRef = useRef(false);
// On mount, if we have a token in localStorage, hydrate the user object const fetchAccount = useCallback(async () => {
// by fetching the account from the backend. try {
useEffect(() => { const account = await getAccount();
if (!state.token || state.user) return;
getAccount()
.then((account) => {
dispatch({ dispatch({
type: ACTION_TYPES.SET_USER, type: ACTION_TYPES.SET_USER,
payload: { payload: {
@@ -22,43 +19,67 @@ export function AuthProvider({ children }) {
email: account.email, email: account.email,
subscriptionStatus: account.subscription_status, subscriptionStatus: account.subscription_status,
subscriptionTier: account.subscription_tier, subscriptionTier: account.subscription_tier,
tierLimits: account.tier_limits,
addressCount: account.address_count,
}, },
}); });
}) } catch {
.catch(() => {
dispatch({ type: ACTION_TYPES.LOGOUT }); dispatch({ type: ACTION_TYPES.LOGOUT });
}); }
}, [state.token, state.user]); }, []);
// On mount, if we have a token in localStorage, hydrate user from backend.
useEffect(() => {
if (!state.token || fetchedRef.current) return;
fetchedRef.current = true;
fetchAccount();
}, [state.token, fetchAccount]);
async function login(email, password) { async function login(email, password) {
const data = await loginUser(email, password); const data = await loginUser(email, password);
dispatch({ type: ACTION_TYPES.LOGIN_SUCCESS, payload: data }); dispatch({ type: ACTION_TYPES.LOGIN_SUCCESS, payload: data });
fetchedRef.current = false;
return data; return data;
} }
async function register(email, password, sessionId) { async function register(email, password, sessionId) {
const data = await registerAfterCheckout(email, password, sessionId); const data = await registerAfterCheckout(email, password, sessionId);
dispatch({ type: ACTION_TYPES.LOGIN_SUCCESS, payload: data }); dispatch({ type: ACTION_TYPES.LOGIN_SUCCESS, payload: data });
fetchedRef.current = false;
return data; return data;
} }
function logout() { function logout() {
fetchedRef.current = false;
dispatch({ type: ACTION_TYPES.LOGOUT }); dispatch({ type: ACTION_TYPES.LOGOUT });
} }
function refreshAccount() {
fetchAccount();
}
const isAuthenticated = state.isAuthenticated && !!state.token; const isAuthenticated = state.isAuthenticated && !!state.token;
const isSubscribed = const isSubscribed =
state.user?.subscriptionStatus === "active" || state.user?.subscriptionStatus === "active" ||
state.user?.subscriptionStatus === "trialing"; state.user?.subscriptionStatus === "trialing";
const tierLimits = state.user?.tierLimits || {
max_addresses: 1,
max_alert_types: 1,
allowed_channels: ["email"],
};
const value = { const value = {
user: state.user, user: state.user,
token: state.token, token: state.token,
isAuthenticated, isAuthenticated,
isSubscribed, isSubscribed,
tierLimits,
userTier: state.user?.subscriptionTier || "free",
login, login,
register, register,
logout, logout,
refreshAccount,
dispatch, dispatch,
ACTION_TYPES, ACTION_TYPES,
}; };

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext"; import { useAuth } from "../../contexts/AuthContext";
import { createOnboardingCheckout, activateFreeTier } from "../../api/stripe"; import { createOnboardingCheckout, activateFreeTier } from "../../api/stripe";
@@ -26,12 +26,17 @@ export default function Subscribe() {
confirmPassword: "", confirmPassword: "",
}); });
const registerCalledRef = useRef(false);
function set(field, value) { function set(field, value) {
setData((prev) => ({ ...prev, [field]: value })); setData((prev) => ({ ...prev, [field]: value }));
} }
// Handle return from Stripe checkout redirect // Handle return from Stripe checkout redirect.
// The ref guard prevents React StrictMode from double-firing this.
useEffect(() => { useEffect(() => {
if (registerCalledRef.current) return;
const payment = searchParams.get("payment"); const payment = searchParams.get("payment");
const sessionId = searchParams.get("session_id"); const sessionId = searchParams.get("session_id");
@@ -44,7 +49,6 @@ export default function Subscribe() {
if (payment !== "success" || !sessionId) return; if (payment !== "success" || !sessionId) return;
// Recover credentials stashed before the Stripe redirect
const savedEmail = sessionStorage.getItem("kp_onboard_email"); const savedEmail = sessionStorage.getItem("kp_onboard_email");
const savedPassword = sessionStorage.getItem("kp_onboard_password"); const savedPassword = sessionStorage.getItem("kp_onboard_password");
@@ -56,6 +60,7 @@ export default function Subscribe() {
return; return;
} }
registerCalledRef.current = true;
setSearchParams({}, { replace: true }); setSearchParams({}, { replace: true });
setLoading(true); setLoading(true);
@@ -66,6 +71,7 @@ export default function Subscribe() {
navigate("/addresses", { replace: true }); navigate("/addresses", { replace: true });
}) })
.catch((err) => { .catch((err) => {
registerCalledRef.current = false;
setError("Registration failed: " + err.message); setError("Registration failed: " + err.message);
setStep(1); setStep(1);
}) })