- {authChecked ? (
+ {loggedIn && checkoutIntent ? (
+
diff --git a/frontend/apps/artcraft-webapp/src/components/auth/signup-form.tsx b/frontend/apps/artcraft-webapp/src/components/auth/signup-form.tsx
index 9be41f5820..88f0e5da93 100644
--- a/frontend/apps/artcraft-webapp/src/components/auth/signup-form.tsx
+++ b/frontend/apps/artcraft-webapp/src/components/auth/signup-form.tsx
@@ -9,10 +9,7 @@ import { Input } from "@storyteller/ui-input";
import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { UsersApi } from "@storyteller/api";
-import {
- checkoutIntentFromSearchParams,
- redirectToCheckout,
-} from "@storyteller/ui-pricing-table";
+import { checkoutIntentFromSearchParams } from "@storyteller/ui-pricing-table";
import {
getLandingUrl,
getReferralCode,
@@ -100,10 +97,10 @@ export const SignupForm = ({
hasActiveSubscription(),
]);
- // A pricing-page purchase click brought the user here; resume Stripe
- // checkout for the plan they picked.
- const checkoutIntent = checkoutIntentFromSearchParams(searchParams);
- if (checkoutIntent && (await redirectToCheckout(checkoutIntent))) {
+ // A pricing-page purchase click brought the user here; stay put. The
+ // session refresh above flipped loggedIn, so AuthLayout is already
+ // redirecting to Stripe checkout for the plan they picked.
+ if (checkoutIntentFromSearchParams(searchParams)) {
return;
}
@@ -185,7 +182,10 @@ export const SignupForm = ({
disabled={isLoading}
>
{isLoading ? (
-
+
) : (
"Create account"
)}
diff --git a/frontend/apps/artcraft-webapp/src/pages/login/login.tsx b/frontend/apps/artcraft-webapp/src/pages/login/login.tsx
index fb70fb5c41..5d4a3492d2 100644
--- a/frontend/apps/artcraft-webapp/src/pages/login/login.tsx
+++ b/frontend/apps/artcraft-webapp/src/pages/login/login.tsx
@@ -9,11 +9,12 @@ import { Input } from "@storyteller/ui-input";
import { useState } from "react";
import { Link, useNavigate, useSearchParams } from "react-router-dom";
import { UsersApi } from "@storyteller/api";
+import { checkoutIntentFromSearchParams } from "@storyteller/ui-pricing-table";
import {
- checkoutIntentFromSearchParams,
- redirectToCheckout,
-} from "@storyteller/ui-pricing-table";
-import { AuthHeader, AuthFooter, GoogleLoginButton } from "../../components/auth";
+ AuthHeader,
+ AuthFooter,
+ GoogleLoginButton,
+} from "../../components/auth";
import Seo from "../../components/seo";
import { Reveal, RevealGroup } from "../../components/motion/reveal";
import { refreshSession } from "../../lib/session";
@@ -24,8 +25,9 @@ const Login = () => {
const [searchParams] = useSearchParams();
const fromParam = searchParams.get("from");
const redirectTo = fromParam && fromParam.startsWith("/") ? fromParam : "/";
- // Set when a pricing-page purchase click routed the user through auth;
- // after login we resume Stripe checkout for that plan.
+ // Set when a pricing-page purchase click routed the user through auth.
+ // AuthLayout owns the Stripe redirect once loggedIn flips; this page just
+ // has to not navigate away in the meantime.
const checkoutIntent = checkoutIntentFromSearchParams(searchParams);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -51,7 +53,7 @@ const Login = () => {
// navigating — otherwise RequireAuth on the destination sees loggedIn=false
// and bounces straight back to /login?from=…
await refreshSession(true);
- if (checkoutIntent && (await redirectToCheckout(checkoutIntent))) {
+ if (checkoutIntent) {
return;
}
navigate(redirectTo);
@@ -67,7 +69,7 @@ const Login = () => {
refreshSession(true),
hasActiveSubscription(),
]);
- if (checkoutIntent && (await redirectToCheckout(checkoutIntent))) {
+ if (checkoutIntent) {
return;
}
navigate(subscribed ? redirectTo : "/pricing");
@@ -159,7 +161,10 @@ const Login = () => {
disabled={isLoading}
>
{isLoading ? (
-
+
) : (
"Log in"
)}
diff --git a/frontend/apps/artcraft-webapp/src/pages/signup/signup.tsx b/frontend/apps/artcraft-webapp/src/pages/signup/signup.tsx
index 9ee3689d61..b811619d71 100644
--- a/frontend/apps/artcraft-webapp/src/pages/signup/signup.tsx
+++ b/frontend/apps/artcraft-webapp/src/pages/signup/signup.tsx
@@ -1,8 +1,5 @@
import { Link, useNavigate, useSearchParams } from "react-router-dom";
-import {
- checkoutIntentFromSearchParams,
- redirectToCheckout,
-} from "@storyteller/ui-pricing-table";
+import { checkoutIntentFromSearchParams } from "@storyteller/ui-pricing-table";
import { AuthHeader, AuthFooter, SignupForm } from "../../components/auth";
import Seo from "../../components/seo";
import { Reveal } from "../../components/motion/reveal";
@@ -12,10 +9,11 @@ const Signup = () => {
const [searchParams] = useSearchParams();
const checkoutIntent = checkoutIntentFromSearchParams(searchParams);
- const handleSuccess = async () => {
- // A pricing-page purchase click brought the user here; resume Stripe
- // checkout for the plan they picked instead of the welcome flow.
- if (checkoutIntent && (await redirectToCheckout(checkoutIntent))) {
+ const handleSuccess = () => {
+ // A pricing-page purchase click brought the user here; stay put. Once the
+ // session refresh flips loggedIn, AuthLayout swaps in a spinner and
+ // redirects to Stripe checkout for the plan they picked.
+ if (checkoutIntent) {
return;
}
navigate("/welcome");
@@ -27,15 +25,15 @@ const Signup = () => {
title="Sign Up - ArtCraft"
description="Create your ArtCraft account."
/>
-
+
{/* SignupForm runs its own field cascade internally (see signup-form.tsx).
The footer picks up the tail of that cascade — the form's six staggered
steps land by ~0.36s, so the footer follows at ~0.44s. */}
-
+
diff --git a/frontend/apps/artcraft-website/src/pages/login/login.tsx b/frontend/apps/artcraft-website/src/pages/login/login.tsx
index 767c4896f6..e30e686beb 100644
--- a/frontend/apps/artcraft-website/src/pages/login/login.tsx
+++ b/frontend/apps/artcraft-website/src/pages/login/login.tsx
@@ -21,6 +21,7 @@ const Login = () => {
// Set when a pricing-page purchase click routed the user through auth;
// after login we resume Stripe checkout for that plan.
const checkoutIntent = checkoutIntentFromSearchParams(searchParams);
+ const [redirectingToCheckout, setRedirectingToCheckout] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
@@ -42,8 +43,12 @@ const Login = () => {
if (response.success) {
window.dispatchEvent(new Event("auth-change"));
- if (checkoutIntent && (await redirectToCheckout(checkoutIntent))) {
- return;
+ if (checkoutIntent) {
+ setRedirectingToCheckout(true);
+ if (await redirectToCheckout(checkoutIntent)) {
+ return;
+ }
+ setRedirectingToCheckout(false);
}
navigate("/");
} else {
@@ -81,13 +86,23 @@ const Login = () => {
-
-
Welcome Back
-
Log in to your account
-
+ {redirectingToCheckout ? (
+
+
+ Taking you to secure checkout…
+
+ ) : (
+ <>
+
+
Welcome Back
+
Log in to your account
+
-
- {/*
+ {/* {
*/}
-
-
-
- Don't have an account?{" "}
-
- Sign up
-
-
+
+
+ setEmail(e.target.value)}
+ placeholder="you@example.com or username"
+ inputClassName="w-full bg-black/20 border border-white/10 focus:border-primary/50 rounded-xl px-4 py-3 text-white placeholder-white/20 outline-none transition-colors"
+ />
+
+
+
+
+
+ Forgot password?
+
+
+
+ setPassword(e.target.value)}
+ placeholder="Min. 8 characters"
+ inputClassName="w-full bg-black/20 border border-white/10 focus:border-primary/50 rounded-xl px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12"
+ />
+
+
+
+
+
+
+
+
+