Backend exposes account and plan as proper Inertia resources so any
page can read them off shared props:
- AuthAccountResource — id, name, created_at
- AuthPlanResource — id, slug, name, interval (derived from the active
Cashier subscription's stripe_price)
Both wired into HandleInertiaRequests::share so `auth.account` and
`auth.plan` are available everywhere.
Frontend pushes app + identity context to GTM's dataLayer on every
page load via initializeDataLayer (resources/js/datalayer.ts), and
useTracking gets begin_checkout/purchase wired in the billing flow.
The pushes are no-ops without GTM configured (the array still exists
in memory, nothing reads it). Self-hosted instances without GTM_ID
incur zero error noise. The GTM partials in app.blade.php were
already conditional on `config('services.gtm.id')`.
Processing.vue polls `auth` alongside `subscriptionActive` so
auth.plan.interval is fresh once the Stripe webhook creates the local
Subscription row (it doesn't exist yet at the initial render). The
watch fires only on the false → true transition; an onMounted
fallback redirects users that land on the page with an already-active
subscription without re-firing trackPurchase.
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import type { Auth } from './types';
|
|
|
|
interface FlashData {
|
|
conversion_event?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/**
|
|
* Push app + identity context to GTM's dataLayer on every page load. The
|
|
* pushes are no-ops when GTM isn't configured (the array still exists in
|
|
* memory; nothing reads it). Self-hosted instances without GTM_ID set
|
|
* incur zero error noise.
|
|
*
|
|
* Billing is account-scoped (not workspace-scoped) in trypost, so the
|
|
* plan/subscription fields are emitted under `account_*` keys.
|
|
*/
|
|
export const initializeDataLayer = (
|
|
auth: Auth | undefined,
|
|
flash: FlashData | undefined,
|
|
applicationUrl: string,
|
|
env: string,
|
|
): void => {
|
|
window.dataLayer = window.dataLayer || [];
|
|
|
|
window.dataLayer.push({
|
|
app_url: applicationUrl,
|
|
app_env: env,
|
|
app_context: 'app',
|
|
});
|
|
|
|
if (flash?.conversion_event) {
|
|
window.dataLayer.push({ event: flash.conversion_event });
|
|
}
|
|
|
|
if (!auth?.user) {
|
|
return;
|
|
}
|
|
|
|
window.dataLayer.push({
|
|
user_id: auth.user.id,
|
|
user_email: auth.user.email,
|
|
user_name: auth.user.name,
|
|
user_created_at: auth.user.created_at,
|
|
});
|
|
|
|
if (auth.account) {
|
|
window.dataLayer.push({
|
|
account_id: auth.account.id,
|
|
account_name: auth.account.name,
|
|
account_created_at: auth.account.created_at,
|
|
account_plan: auth.plan?.name ?? null,
|
|
account_plan_slug: auth.plan?.slug ?? null,
|
|
account_subscribed: Boolean(auth.hasActiveSubscription),
|
|
});
|
|
}
|
|
|
|
if (auth.currentWorkspace) {
|
|
window.dataLayer.push({
|
|
workspace_id: auth.currentWorkspace.id,
|
|
workspace_name: auth.currentWorkspace.name,
|
|
workspace_count: auth.workspaces?.length ?? 0,
|
|
});
|
|
}
|
|
};
|