Merge dev → main: surveys, P/OEM page, trial upsell, catalog-issue report, pcat timings #127

Merged
root merged 36 commits from merge-dev-to-main into main 2026-06-11 16:28:16 +03:00
Showing only changes of commit cd54cf9443 - Show all commits

View File

@@ -13,9 +13,17 @@ let initialized = false;
// Set once init succeeds so the catalog-degradation helpers below can use the
// already-loaded SDK without re-awaiting the dynamic import on every call.
let sentryApi: typeof import("@sentry/react") | null = null;
// Cache the init promise so concurrent callers (e.g. a degradation reporter that
// fires on first paint, racing the main.tsx call) await the SAME init instead of
// double-initialising the SDK.
let initPromise: Promise<void> | null = null;
export async function initSentry() {
if (initialized) return;
export function initSentry(): Promise<void> {
if (!initPromise) initPromise = doInitSentry();
return initPromise;
}
async function doInitSentry(): Promise<void> {
const dsn = import.meta.env.VITE_SENTRY_DSN;
if (!dsn) {
if (import.meta.env.DEV) {
@@ -24,20 +32,18 @@ export async function initSentry() {
return;
}
// Both dev.sase.tr (staging) and sase.tr (prod) ship a production Vite build
// (MODE === "production"), so MODE can't separate them. Derive from the host at
// runtime instead, so dev errors stay filterable from prod in Sentry. An
// explicit VITE_SENTRY_ENVIRONMENT still wins.
// Hostname is authoritative for the two real deployments — sase.tr (prod) and
// dev.sase.tr (staging) — which both ship a production Vite build baking the
// SAME VITE_SENTRY_ENVIRONMENT (compose default "production"), so that var can't
// tell them apart. Use it only as a fallback for local/preview hosts.
const host = typeof window !== "undefined" ? window.location.hostname : "";
const environment =
(import.meta.env.VITE_SENTRY_ENVIRONMENT as string | undefined) ??
(host === "sase.tr" || host === "www.sase.tr"
host === "sase.tr" || host === "www.sase.tr"
? "production"
: host.endsWith("dev.sase.tr")
? "staging"
: import.meta.env.DEV
? "development"
: "staging");
: (import.meta.env.VITE_SENTRY_ENVIRONMENT as string | undefined) ||
(import.meta.env.DEV ? "development" : "staging");
const release = import.meta.env.VITE_SENTRY_RELEASE;
try {
@@ -123,11 +129,14 @@ export async function reportCatalogDegradation(
ctx: CatalogIssueContext,
): Promise<void> {
try {
const Sentry = sentryApi;
if (!Sentry) return;
const dedupKey = `${kind}:${ctx.categoryId ?? ctx.vehicleId ?? ""}`;
if (reportedThisSession.has(dedupKey)) return;
reportedThisSession.add(dedupKey);
// The empty/error state can render before the SDK's dynamic import resolves —
// wait for init so the failure is never silently dropped.
await initSentry();
const Sentry = sentryApi;
if (!Sentry) return;
const brand = brandOf(ctx.vehicleLabel);
Sentry.captureMessage(`catalog degraded: ${kind} (browser/${brand})`, {
@@ -153,6 +162,7 @@ export async function reportCatalogDegradation(
*/
export async function openCatalogFeedback(ctx: CatalogIssueContext): Promise<void> {
try {
await initSentry();
const Sentry = sentryApi;
if (!Sentry) return;
Sentry.setTag("catalog_source", ctx.source ?? "unknown");