more
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
Backend startup quickstarat:
|
||||
Backend startup quickstart:
|
||||
|
||||
-----------------------------> BEST
|
||||
## 1. brew services start postgresql@15
|
||||
|
||||
|
||||
48
frontend/src/contexts/UserPropertiesContext.jsx
Normal file
48
frontend/src/contexts/UserPropertiesContext.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
frontend/src/contexts/userPropertiesReducer.js
Normal file
20
frontend/src/contexts/userPropertiesReducer.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
<UserPropertiesProvider>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</UserPropertiesProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user