Files
calendar/packages/features/auth/signup/lib/fetchSignup.ts
T
Alex van AndelGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
d3bbed01f6 feat: add signup watchlist review mode (#27912)
* 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>
2026-02-12 23:14:31 +00:00

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;
}