diff --git a/backend/README.md b/backend/README.md
index 940937f..9d3d3b8 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -1,4 +1,5 @@
-Backend startup quickstarat:
+Backend startup quickstart:
+
-----------------------------> BEST
## 1. brew services start postgresql@15
diff --git a/frontend/src/contexts/UserPropertiesContext.jsx b/frontend/src/contexts/UserPropertiesContext.jsx
new file mode 100644
index 0000000..e2bd3e5
--- /dev/null
+++ b/frontend/src/contexts/UserPropertiesContext.jsx
@@ -0,0 +1,48 @@
+import { createContext, useContext, useReducer, useEffect } from "react";
+import {
+ userPropertiesReducer,
+ initialState,
+ ACTION_TYPES,
+} from "./userPropertiesReducer";
+
+const STORAGE_KEY = "kp_onboard_user";
+
+function hydrateState() {
+ try {
+ const raw = sessionStorage.getItem(STORAGE_KEY);
+ if (raw) return { ...initialState, ...JSON.parse(raw) };
+ } catch {
+ /* corrupt data — fall through */
+ }
+ return initialState;
+}
+
+const UserPropertiesContext = createContext(initialState);
+
+export function useUserProperties() {
+ const ctx = useContext(UserPropertiesContext);
+ if (!ctx) {
+ throw new Error(
+ "useUserProperties must be used within UserPropertiesProvider",
+ );
+ }
+ return ctx;
+}
+
+export function UserPropertiesProvider({ children }) {
+ const [state, dispatch] = useReducer(userPropertiesReducer, null, hydrateState);
+
+ useEffect(() => {
+ if (state.email || state.password) {
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ } else {
+ sessionStorage.removeItem(STORAGE_KEY);
+ }
+ }, [state]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/contexts/userPropertiesReducer.js b/frontend/src/contexts/userPropertiesReducer.js
new file mode 100644
index 0000000..c693ccb
--- /dev/null
+++ b/frontend/src/contexts/userPropertiesReducer.js
@@ -0,0 +1,20 @@
+export const initialState = {
+ email: "",
+ password: "",
+};
+
+export const ACTION_TYPES = {
+ SET_USER_PROPERTIES: "SET_USER_PROPERTIES",
+ CLEAR_USER_PROPERTIES: "CLEAR_USER_PROPERTIES",
+};
+
+export function userPropertiesReducer(state, action) {
+ switch (action.type) {
+ case ACTION_TYPES.SET_USER_PROPERTIES:
+ return { ...state, ...action.payload };
+ case ACTION_TYPES.CLEAR_USER_PROPERTIES:
+ return { ...initialState };
+ default:
+ return state;
+ }
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 00913df..6c791fd 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -2,15 +2,18 @@ import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext";
+import { UserPropertiesProvider } from "./contexts/UserPropertiesContext";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
-
-
-
+
+
+
+
+
,
);
diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx
index 4031120..4a342a2 100644
--- a/frontend/src/pages/subscribe/Subscribe.jsx
+++ b/frontend/src/pages/subscribe/Subscribe.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext";
+import { useUserProperties } from "../../contexts/UserPropertiesContext";
import {
createOnboardingCheckout,
verifyCheckoutSession,
@@ -15,6 +16,7 @@ const STEPS = ["Create Account", "Choose Plan"];
export default function Subscribe() {
const { currentUser, signup } = useAuth();
+ const { state: userProps, dispatch, ACTION_TYPES } = useUserProperties();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
@@ -51,9 +53,7 @@ export default function Subscribe() {
// Phase 1: no account yet — create it. Auth state change remounts the
// component (App.jsx swaps route trees) and Phase 2 runs on the next mount.
if (!currentUser) {
- const savedEmail = sessionStorage.getItem("kp_onboard_email");
- const savedPw = sessionStorage.getItem("kp_onboard_pw");
- if (!savedEmail || !savedPw) {
+ if (!userProps.email || !userProps.password) {
setSearchParams({}, { replace: true });
setError("Session expired. Please start the signup process again.");
setStep(1);
@@ -61,7 +61,7 @@ export default function Subscribe() {
return;
}
setLoading(true);
- signup(savedEmail, savedPw).catch((err) => {
+ signup(userProps.email, userProps.password).catch((err) => {
setSearchParams({}, { replace: true });
setError("Account creation failed: " + err.message);
setStep(1);
@@ -73,8 +73,7 @@ export default function Subscribe() {
// Phase 2: authenticated — verify the checkout and go to the dashboard.
setSearchParams({}, { replace: true });
setLoading(true);
- sessionStorage.removeItem("kp_onboard_email");
- sessionStorage.removeItem("kp_onboard_pw");
+ dispatch({ type: ACTION_TYPES.CLEAR_USER_PROPERTIES });
verifyCheckoutSession(sessionId)
.then(() => {
@@ -85,7 +84,7 @@ export default function Subscribe() {
setStep(2);
})
.finally(() => setLoading(false));
- }, [currentUser, searchParams, setSearchParams, signup, navigate]);
+ }, [currentUser, searchParams, setSearchParams, signup, navigate, userProps, dispatch, ACTION_TYPES]);
// ── Step handlers ─────────────────────────────────────────────────────────
@@ -124,8 +123,10 @@ export default function Subscribe() {
return;
}
- sessionStorage.setItem("kp_onboard_email", data.email);
- sessionStorage.setItem("kp_onboard_pw", data.password);
+ dispatch({
+ type: ACTION_TYPES.SET_USER_PROPERTIES,
+ payload: { email: data.email, password: data.password },
+ });
const { url } = await createOnboardingCheckout(
data.email,
data.selectedTier,