Don't have an account?{" "}
-
+
Sign up here
diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx
index 4a342a2..590e526 100644
--- a/frontend/src/pages/subscribe/Subscribe.jsx
+++ b/frontend/src/pages/subscribe/Subscribe.jsx
@@ -1,12 +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,
- activateFreeTier,
-} from "../../api/stripe";
+import { createOnboardingCheckout, activateFreeTier } from "../../api/stripe";
import Input from "../../components/Input";
import Button from "../../components/Button";
import TierPicker from "../../components/TierPicker";
@@ -15,13 +10,12 @@ import "./Subscribe.css";
const STEPS = ["Create Account", "Choose Plan"];
export default function Subscribe() {
- const { currentUser, signup } = useAuth();
- const { state: userProps, dispatch, ACTION_TYPES } = useUserProperties();
+ const { isAuthenticated, register } = useAuth();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const hasPaymentReturn = searchParams.get("payment") === "success";
- const [step, setStep] = useState(hasPaymentReturn || currentUser ? 2 : 1);
+ const [step, setStep] = useState(hasPaymentReturn || isAuthenticated ? 2 : 1);
const [loading, setLoading] = useState(hasPaymentReturn);
const [error, setError] = useState("");
@@ -36,7 +30,7 @@ export default function Subscribe() {
setData((prev) => ({ ...prev, [field]: value }));
}
- // Handle Stripe payment returns
+ // Handle return from Stripe checkout redirect
useEffect(() => {
const payment = searchParams.get("payment");
const sessionId = searchParams.get("session_id");
@@ -50,41 +44,33 @@ export default function Subscribe() {
if (payment !== "success" || !sessionId) return;
- // 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) {
- if (!userProps.email || !userProps.password) {
- setSearchParams({}, { replace: true });
- setError("Session expired. Please start the signup process again.");
- setStep(1);
- setLoading(false);
- return;
- }
- setLoading(true);
- signup(userProps.email, userProps.password).catch((err) => {
- setSearchParams({}, { replace: true });
- setError("Account creation failed: " + err.message);
- setStep(1);
- setLoading(false);
- });
+ // Recover credentials stashed before the Stripe redirect
+ const savedEmail = sessionStorage.getItem("kp_onboard_email");
+ const savedPassword = sessionStorage.getItem("kp_onboard_password");
+
+ if (!savedEmail || !savedPassword) {
+ setSearchParams({}, { replace: true });
+ setError("Session expired. Please start the signup process again.");
+ setStep(1);
+ setLoading(false);
return;
}
- // Phase 2: authenticated — verify the checkout and go to the dashboard.
setSearchParams({}, { replace: true });
setLoading(true);
- dispatch({ type: ACTION_TYPES.CLEAR_USER_PROPERTIES });
- verifyCheckoutSession(sessionId)
+ register(savedEmail, savedPassword, sessionId)
.then(() => {
+ sessionStorage.removeItem("kp_onboard_email");
+ sessionStorage.removeItem("kp_onboard_password");
navigate("/addresses", { replace: true });
})
.catch((err) => {
- setError("Payment verification failed: " + err.message);
- setStep(2);
+ setError("Registration failed: " + err.message);
+ setStep(1);
})
.finally(() => setLoading(false));
- }, [currentUser, searchParams, setSearchParams, signup, navigate, userProps, dispatch, ACTION_TYPES]);
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
// ── Step handlers ─────────────────────────────────────────────────────────
@@ -114,31 +100,28 @@ export default function Subscribe() {
try {
setLoading(true);
+ // Free tier: register immediately, then activate free tier
if (data.selectedTier === "free") {
- if (!currentUser) {
- await signup(data.email, data.password);
+ if (!isAuthenticated) {
+ await register(data.email, data.password, "");
}
await activateFreeTier();
navigate("/addresses", { replace: true });
return;
}
- dispatch({
- type: ACTION_TYPES.SET_USER_PROPERTIES,
- payload: { email: data.email, password: data.password },
- });
+ // Paid tier: stash credentials in sessionStorage, then redirect to Stripe
+ sessionStorage.setItem("kp_onboard_email", data.email);
+ sessionStorage.setItem("kp_onboard_password", data.password);
+
const { url } = await createOnboardingCheckout(
data.email,
data.selectedTier,
);
window.location.href = url;
} catch (err) {
- if (err.code === "auth/email-already-in-use") {
- setError("Email already in use. Try logging in instead.");
- } else if (err.code === "auth/invalid-email") {
- setError("Invalid email address");
- } else if (err.code === "auth/weak-password") {
- setError("Password is too weak");
+ if (err.message.includes("already exists")) {
+ setError("An account with this email already exists. Try logging in instead.");
} else {
setError("Failed to process plan selection: " + err.message);
}
@@ -269,7 +252,7 @@ export default function Subscribe() {
return (
- {step === 2 && (
+ {step === 2 && !isAuthenticated && (
diff --git a/frontend/src/pages/user_account/Account.jsx b/frontend/src/pages/user_account/Account.jsx
index 52493be..4df0bc8 100644
--- a/frontend/src/pages/user_account/Account.jsx
+++ b/frontend/src/pages/user_account/Account.jsx
@@ -1,7 +1,5 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
-import { updatePassword } from "firebase/auth";
-import { auth } from "../../firebase/config";
import { getAccount, createPortalSession } from "../../api/account";
import "./Account.css";
@@ -55,7 +53,8 @@ export default function Account() {
try {
setChangingPassword(true);
- await updatePassword(auth.currentUser, newPassword);
+ // TODO: implement password change endpoint on backend
+ throw new Error("Password change not yet implemented");
setPasswordMsg("Password updated successfully");
setNewPassword("");
setConfirmPassword("");
diff --git a/frontend/src/reducers/authReducer.jsx b/frontend/src/reducers/authReducer.jsx
index dd1c505..4017f8d 100644
--- a/frontend/src/reducers/authReducer.jsx
+++ b/frontend/src/reducers/authReducer.jsx
@@ -1,32 +1,56 @@
-import { useReducer } from "react"
-
-const UserAuthContext = createContext();
-
-export const initialState = {
- authUser: null,
- AuthTier: null
+export const ACTION_TYPES = {
+ LOGIN_SUCCESS: "LOGIN_SUCCESS",
+ SET_USER: "SET_USER",
+ LOGOUT: "LOGOUT",
+ AUTH_ERROR: "AUTH_ERROR",
};
-const authReducer = (state, action) => {
- const { type, payload = "FREE_TIER" } = action
+export const initialState = {
+ user: null,
+ token: localStorage.getItem("kp_token") || null,
+ isAuthenticated: !!localStorage.getItem("kp_token"),
+ error: null,
+};
- switch (type) {
- case "ADD_AUTH_USER":
- console.log("ADD_AUTH_USER", payload);
+export default function authReducer(state, action) {
+ switch (action.type) {
+ case ACTION_TYPES.LOGIN_SUCCESS:
+ localStorage.setItem("kp_token", action.payload.token);
return {
...state,
- auth: payload
+ user: {
+ id: action.payload.user_id,
+ email: action.payload.email,
+ subscriptionStatus: action.payload.subscription_status,
+ subscriptionTier: action.payload.subscription_tier,
+ },
+ token: action.payload.token,
+ isAuthenticated: true,
+ error: null,
};
+
+ case ACTION_TYPES.SET_USER:
+ return {
+ ...state,
+ user: action.payload,
+ error: null,
+ };
+
+ case ACTION_TYPES.LOGOUT:
+ localStorage.removeItem("kp_token");
+ return {
+ ...initialState,
+ token: null,
+ isAuthenticated: false,
+ };
+
+ case ACTION_TYPES.AUTH_ERROR:
+ return {
+ ...state,
+ error: action.payload,
+ };
+
default:
- throw new Error(`No case for type ${type} found.`);
- }
- switch (type) {
- case "DELETE_AUTH_USER":
- return {
- ...state,
- users: state.users.filter(user => user.id !== action.userIdToDelete)
- };
+ return state;
}
}
-
-export default shopReducer
\ No newline at end of file