* feat: add signup watchlist review feature flag and handler logic - Add 'signup-watchlist-review' global feature flag - Add SIGNUP to WatchlistSource enum in Prisma schema - When flag enabled, lock new signups and add email to watchlist - Show 'account under review' message on signup page - Add i18n strings for review UI - Create seed migration for the feature flag Co-Authored-By: alex@cal.com <me@alexvanandel.com> * test: add isAccountUnderReview tests to fetchSignup test suite Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: address Cubic AI review feedback (confidence >= 9/10) - Remove 'import process from node:process' in signup-view.tsx (P0 bug in 'use client' component) - Move watchlist review check before checkoutSessionId early return in calcomSignupHandler (P1 premium bypass) - Revert selfHostedHandler to original state (out of scope per user request) - Add test mocks for FeaturesRepository and GlobalWatchlistRepository Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: remove node:process import from useFlags.ts (client-side file) Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: remove !token condition from watchlist review check Token is present in normal email-verified signups, so the !token condition was incorrectly skipping watchlist review for verified users. Co-Authored-By: alex@cal.com <me@alexvanandel.com> * Apply suggestion from @cubic-dev-ai[bot] Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * refactor: move user lock to UserRepository.lockByEmail Co-Authored-By: alex@cal.com <me@alexvanandel.com> * refactor: use cached getFeatureRepository() instead of deprecated FeaturesRepository Co-Authored-By: alex@cal.com <me@alexvanandel.com> * refactor: remove user locking, keep only watchlist addition on signup review Co-Authored-By: alex@cal.com <me@alexvanandel.com> * feat: lock user on signup review, remove watchlist entry on unlock Co-Authored-By: alex@cal.com <me@alexvanandel.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { SIGNUP_ERROR_CODES } from "../constants";
|
|
|
|
type SignupData = {
|
|
username?: string;
|
|
email: string;
|
|
password: string;
|
|
language: string;
|
|
token?: string;
|
|
};
|
|
|
|
type SignupSuccessResponse = {
|
|
message: string;
|
|
stripeCustomerId?: string;
|
|
accountUnderReview?: boolean;
|
|
};
|
|
|
|
type SignupErrorResponse = {
|
|
message: string;
|
|
checkoutSessionId?: string;
|
|
};
|
|
|
|
type SignupResponse = SignupSuccessResponse | SignupErrorResponse;
|
|
|
|
export type SignupSuccessResult = { ok: true; data: SignupSuccessResponse };
|
|
|
|
export type SignupResult = SignupSuccessResult | { ok: false; status: number; error: SignupErrorResponse };
|
|
|
|
export async function fetchSignup(data: SignupData, cfToken?: string): Promise<SignupResult> {
|
|
const allParams = new URLSearchParams(window.location.search);
|
|
|
|
const utmParams = new URLSearchParams();
|
|
const utmKeys = [
|
|
"utm_source",
|
|
"utm_medium",
|
|
"utm_campaign",
|
|
"utm_term",
|
|
"utm_content",
|
|
"utm_id",
|
|
"utm_referral",
|
|
"landing_page",
|
|
];
|
|
|
|
utmKeys.forEach((key) => {
|
|
const value = allParams.get(key);
|
|
if (value) utmParams.set(key, value);
|
|
});
|
|
|
|
const url = utmParams.toString() ? `/api/auth/signup?${utmParams.toString()}` : "/api/auth/signup";
|
|
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"cf-access-token": cfToken ?? "invalid-token",
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
const contentType = response.headers.get("content-type");
|
|
if (!contentType?.includes("application/json")) {
|
|
return {
|
|
ok: false,
|
|
status: response.status,
|
|
error: { message: SIGNUP_ERROR_CODES.INVALID_SERVER_RESPONSE },
|
|
};
|
|
}
|
|
|
|
const json = (await response.json()) as SignupResponse;
|
|
|
|
if (!response.ok) {
|
|
return {
|
|
ok: false,
|
|
status: response.status,
|
|
error: json as SignupErrorResponse,
|
|
};
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
data: json as SignupSuccessResponse,
|
|
};
|
|
}
|
|
|
|
export function isUserAlreadyExistsError(result: SignupResult): boolean {
|
|
return (
|
|
!result.ok && result.status === 409 && result.error.message === SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS
|
|
);
|
|
}
|
|
|
|
export function hasCheckoutSession(
|
|
result: SignupResult
|
|
): result is { ok: false; status: number; error: SignupErrorResponse & { checkoutSessionId: string } } {
|
|
return !result.ok && !!result.error.checkoutSessionId;
|
|
}
|
|
|
|
export function isAccountUnderReview(result: SignupResult): boolean {
|
|
return result.ok && result.data.accountUnderReview === true;
|
|
}
|