This commit is contained in:
KS Jannette
2026-03-29 00:02:38 -04:00
parent 33ec729631
commit 3a52e7afb3
5 changed files with 86 additions and 13 deletions

View File

@@ -1,4 +1,5 @@
Backend startup quickstarat: Backend startup quickstart:
-----------------------------> BEST -----------------------------> BEST
## 1. brew services start postgresql@15 ## 1. brew services start postgresql@15

View File

@@ -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 (
<UserPropertiesContext.Provider value={{ state, dispatch, ACTION_TYPES }}>
{children}
</UserPropertiesContext.Provider>
);
}

View File

@@ -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;
}
}

View File

@@ -2,15 +2,18 @@ import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext"; import { AuthProvider } from "./contexts/AuthContext";
import { UserPropertiesProvider } from "./contexts/UserPropertiesContext";
import App from "./App"; import App from "./App";
import "./index.css"; import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<AuthProvider> <UserPropertiesProvider>
<App /> <AuthProvider>
</AuthProvider> <App />
</AuthProvider>
</UserPropertiesProvider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode>, </React.StrictMode>,
); );

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } 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 { useUserProperties } from "../../contexts/UserPropertiesContext";
import { import {
createOnboardingCheckout, createOnboardingCheckout,
verifyCheckoutSession, verifyCheckoutSession,
@@ -15,6 +16,7 @@ const STEPS = ["Create Account", "Choose Plan"];
export default function Subscribe() { export default function Subscribe() {
const { currentUser, signup } = useAuth(); const { currentUser, signup } = useAuth();
const { state: userProps, dispatch, ACTION_TYPES } = useUserProperties();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
@@ -51,9 +53,7 @@ export default function Subscribe() {
// Phase 1: no account yet — create it. Auth state change remounts the // 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. // component (App.jsx swaps route trees) and Phase 2 runs on the next mount.
if (!currentUser) { if (!currentUser) {
const savedEmail = sessionStorage.getItem("kp_onboard_email"); if (!userProps.email || !userProps.password) {
const savedPw = sessionStorage.getItem("kp_onboard_pw");
if (!savedEmail || !savedPw) {
setSearchParams({}, { replace: true }); setSearchParams({}, { replace: true });
setError("Session expired. Please start the signup process again."); setError("Session expired. Please start the signup process again.");
setStep(1); setStep(1);
@@ -61,7 +61,7 @@ export default function Subscribe() {
return; return;
} }
setLoading(true); setLoading(true);
signup(savedEmail, savedPw).catch((err) => { signup(userProps.email, userProps.password).catch((err) => {
setSearchParams({}, { replace: true }); setSearchParams({}, { replace: true });
setError("Account creation failed: " + err.message); setError("Account creation failed: " + err.message);
setStep(1); setStep(1);
@@ -73,8 +73,7 @@ export default function Subscribe() {
// Phase 2: authenticated — verify the checkout and go to the dashboard. // Phase 2: authenticated — verify the checkout and go to the dashboard.
setSearchParams({}, { replace: true }); setSearchParams({}, { replace: true });
setLoading(true); setLoading(true);
sessionStorage.removeItem("kp_onboard_email"); dispatch({ type: ACTION_TYPES.CLEAR_USER_PROPERTIES });
sessionStorage.removeItem("kp_onboard_pw");
verifyCheckoutSession(sessionId) verifyCheckoutSession(sessionId)
.then(() => { .then(() => {
@@ -85,7 +84,7 @@ export default function Subscribe() {
setStep(2); setStep(2);
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [currentUser, searchParams, setSearchParams, signup, navigate]); }, [currentUser, searchParams, setSearchParams, signup, navigate, userProps, dispatch, ACTION_TYPES]);
// ── Step handlers ───────────────────────────────────────────────────────── // ── Step handlers ─────────────────────────────────────────────────────────
@@ -124,8 +123,10 @@ export default function Subscribe() {
return; return;
} }
sessionStorage.setItem("kp_onboard_email", data.email); dispatch({
sessionStorage.setItem("kp_onboard_pw", data.password); type: ACTION_TYPES.SET_USER_PROPERTIES,
payload: { email: data.email, password: data.password },
});
const { url } = await createOnboardingCheckout( const { url } = await createOnboardingCheckout(
data.email, data.email,
data.selectedTier, data.selectedTier,