mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-25 10:39:37 +00:00
webapp: route new Google SSO users to the welcome page (once)
After Google sign-in, brand-new accounts now land on /welcome instead of the set-password page; returning users go to their normal destination. "New user" is detected via the SSO response's username_not_yet_customized (forced to false by the backend for returning Google logins, so repeat logins never re-trigger it). A per-user-token localStorage marker guards against the rare email→Google-link edge case so welcome shows at most once per account. - session.ts: add consumeNewUserWelcome(isNewUser) helper - login / signup-form / signup-cta-modal: route via the helper
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
||||
getReferralUsername,
|
||||
getReferrer,
|
||||
} from "@storyteller/common";
|
||||
import { refreshSession, useSessionStore } from "../../lib/session";
|
||||
import { consumeNewUserWelcome, refreshSession } from "../../lib/session";
|
||||
import { GoogleLoginButton } from "./GoogleLoginButton";
|
||||
|
||||
interface SignupFormProps {
|
||||
@@ -85,13 +85,13 @@ export const SignupForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSuccess = async () => {
|
||||
// Refresh so the session reflects the new cookie (and whether the account
|
||||
// still needs a password), then route accordingly. New SSO users skip
|
||||
// setting a password and go straight to pricing; everyone else goes home.
|
||||
const handleGoogleSuccess = async (isNewUser: boolean) => {
|
||||
// Refresh so the session reflects the new cookie, then route accordingly.
|
||||
// Brand-new accounts see the welcome page once (mirrors the signup flow);
|
||||
// returning users go home.
|
||||
await refreshSession(true);
|
||||
if (useSessionStore.getState().passwordNotSet) {
|
||||
navigate("/pricing");
|
||||
if (consumeNewUserWelcome(isNewUser)) {
|
||||
navigate("/welcome");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { faCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { GoogleLoginButton } from "./auth";
|
||||
import { refreshSession, useSession, useSessionStore } from "../lib/session";
|
||||
import { consumeNewUserWelcome, refreshSession, useSession } from "../lib/session";
|
||||
|
||||
interface SignupCtaState {
|
||||
isOpen: boolean;
|
||||
@@ -51,13 +51,13 @@ export function SignupCtaModal() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Mirror the login/signup pages: refresh the session so it reflects the new
|
||||
// cookie, then send brand-new SSO users straight to pricing. Everyone else
|
||||
// just closes the modal and stays on the page they were on.
|
||||
const handleGoogleSuccess = async () => {
|
||||
// cookie, then send brand-new accounts to the welcome page (once). Everyone
|
||||
// else just closes the modal and stays on the page they were on.
|
||||
const handleGoogleSuccess = async (isNewUser: boolean) => {
|
||||
await refreshSession(true);
|
||||
close();
|
||||
if (useSessionStore.getState().passwordNotSet) {
|
||||
navigate("/pricing");
|
||||
if (consumeNewUserWelcome(isNewUser)) {
|
||||
navigate("/welcome");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -81,6 +81,34 @@ export function updateSessionUser(partial: Partial<UserInfo>): void {
|
||||
invalidateSession();
|
||||
}
|
||||
|
||||
// Decide whether a just-authenticated user should see the one-time post-signup
|
||||
// welcome page, and record that they have. Call once after `refreshSession`.
|
||||
//
|
||||
// `isNewUser` comes from the SSO response (`username_not_yet_customized`), which
|
||||
// the backend forces to false for returning Google logins — so it never re-fires
|
||||
// for normal repeat logins regardless of whether the user customized their
|
||||
// username. As a belt-and-suspenders guard (e.g. the email→Google-link case where
|
||||
// the flag can read true on an existing account), we also persist a per-user-token
|
||||
// marker so the welcome page shows at most once per account on this device.
|
||||
const WELCOME_SHOWN_KEY_PREFIX = "artcraft.welcomeShown.";
|
||||
|
||||
export function consumeNewUserWelcome(isNewUser: boolean): boolean {
|
||||
if (!isNewUser) return false;
|
||||
if (typeof window === "undefined") return true;
|
||||
|
||||
const userToken = useSessionStore.getState().user?.user_token;
|
||||
if (!userToken) return true;
|
||||
|
||||
const key = `${WELCOME_SHOWN_KEY_PREFIX}${userToken}`;
|
||||
try {
|
||||
if (window.localStorage.getItem(key)) return false;
|
||||
window.localStorage.setItem(key, "1");
|
||||
} catch {
|
||||
// localStorage blocked (e.g. privacy mode) — fall through and just show it.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attach the auth-change listener exactly once per page load. Login/logout/
|
||||
// password-reset flows dispatch this event; every consumer shares the resulting
|
||||
// store update instead of each re-running its own effect.
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { UsersApi } from "@storyteller/api";
|
||||
import { AuthHeader, AuthFooter, GoogleLoginButton } from "../../components/auth";
|
||||
import Seo from "../../components/seo";
|
||||
import { refreshSession, useSessionStore } from "../../lib/session";
|
||||
import { consumeNewUserWelcome, refreshSession } from "../../lib/session";
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -48,13 +48,13 @@ const Login = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSuccess = async () => {
|
||||
// Refresh so the session store reflects the new cookie (and whether the
|
||||
// account still needs a password) before deciding where to send them.
|
||||
const handleGoogleSuccess = async (isNewUser: boolean) => {
|
||||
// Refresh so the session store reflects the new cookie before navigating.
|
||||
await refreshSession(true);
|
||||
if (useSessionStore.getState().passwordNotSet) {
|
||||
// New SSO users skip setting a password and go straight to pricing.
|
||||
navigate("/pricing");
|
||||
if (consumeNewUserWelcome(isNewUser)) {
|
||||
// Brand-new accounts see the welcome page once (mirrors the signup flow);
|
||||
// returning users go wherever they were headed.
|
||||
navigate("/welcome");
|
||||
} else {
|
||||
navigate(redirectTo);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user