diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index c645acad..c143d959 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -12,6 +12,7 @@ import clsx from 'clsx'; import { VariableContextComponent } from '@gitroom/react/helpers/variable.context'; import { Fragment } from 'react'; import { PHProvider } from '@gitroom/react/helpers/posthog'; +import UtmSaver from '@gitroom/helpers/utils/utm.saver'; const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] }); @@ -49,6 +50,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) { phkey={process.env.NEXT_PUBLIC_POSTHOG_KEY} host={process.env.NEXT_PUBLIC_POSTHOG_HOST} > + {children} diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx index 69c49ab0..d528ceaa 100644 --- a/apps/frontend/src/components/billing/main.billing.component.tsx +++ b/apps/frontend/src/components/billing/main.billing.component.tsx @@ -22,6 +22,7 @@ import { useModals } from '@mantine/modals'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { Textarea } from '@gitroom/react/form/textarea'; import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events'; +import { useUtmUrl } from '@gitroom/helpers/utils/utm.saver'; export interface Tiers { month: Array<{ @@ -219,6 +220,7 @@ export const MainBillingComponent: FC<{ const user = useUser(); const modal = useModals(); const router = useRouter(); + const utm = useUtmUrl(); const [subscription, setSubscription] = useState( sub @@ -344,6 +346,7 @@ export const MainBillingComponent: FC<{ method: 'POST', body: JSON.stringify({ period: monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY', + utm, billing, }), }) diff --git a/libraries/helpers/src/utils/utm.saver.tsx b/libraries/helpers/src/utils/utm.saver.tsx index 656ca9c6..df280f71 100644 --- a/libraries/helpers/src/utils/utm.saver.tsx +++ b/libraries/helpers/src/utils/utm.saver.tsx @@ -1,47 +1,38 @@ -import {FC, useCallback, useEffect} from "react"; -import {useSearchParams} from "next/navigation"; +'use client'; + +import { FC, useCallback, useEffect } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { useLocalStorage } from '@mantine/hooks'; const UtmSaver: FC = () => { - const query = useSearchParams(); - useEffect(() => { - const landingUrl = localStorage.getItem('landingUrl'); - if (landingUrl) { - return ; - } + const query = useSearchParams(); + const [value, setValue] = useLocalStorage({ key: 'utm', defaultValue: '' }); - localStorage.setItem('landingUrl', window.location.href); - localStorage.setItem('referrer', document.referrer); - }, []); + useEffect(() => { + const landingUrl = localStorage.getItem('landingUrl'); + if (landingUrl) { + return; + } - useEffect(() => { - const utm = query.get('utm_source') || query.get('utm'); - const utmMedium = query.get('utm_medium'); - const utmCampaign = query.get('utm_campaign'); + localStorage.setItem('landingUrl', window.location.href); + localStorage.setItem('referrer', document.referrer); + }, []); - if (utm) { - localStorage.setItem('utm', utm); - } - if (utmMedium) { - localStorage.setItem('utm_medium', utmMedium); - } - if (utmCampaign) { - localStorage.setItem('utm_campaign', utmCampaign); - } - }, [query]); + useEffect(() => { + const utm = query.get('utm_source') || query.get('utm') || query.get('ref'); + if (utm && !value) { + setValue(utm); + } + }, [query, value]); - return <>; -} + return <>; +}; -export const useUtmSaver = () => { - return useCallback(() => { - return { - utm: localStorage.getItem('utm'), - utmMedium: localStorage.getItem('utm_medium'), - utmCampaign: localStorage.getItem('utm_campaign'), - landingUrl: localStorage.getItem('landingUrl'), - referrer: localStorage.getItem('referrer'), - } - }, []); -} - -export default UtmSaver; \ No newline at end of file +export const useUtmUrl = () => { + const [value] = useLocalStorage({ key: 'utm', defaultValue: '' }); + if (value) { + return `utm_source=${value}`; + } + return ''; +}; +export default UtmSaver; diff --git a/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts b/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts index 7544d722..1cbde147 100644 --- a/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts @@ -6,4 +6,6 @@ export class BillingSubscribeDto { @IsIn(['STANDARD', 'PRO', 'TEAM', 'ULTIMATE']) billing: 'STANDARD' | 'PRO' | 'TEAM' | 'ULTIMATE'; + + utm: string; } diff --git a/libraries/nestjs-libraries/src/services/stripe.service.ts b/libraries/nestjs-libraries/src/services/stripe.service.ts index 5464a7a8..64837eea 100644 --- a/libraries/nestjs-libraries/src/services/stripe.service.ts +++ b/libraries/nestjs-libraries/src/services/stripe.service.ts @@ -270,12 +270,13 @@ export class StripeService { body: BillingSubscribeDto, price: string ) { + const isUtm = body.utm ? `&utm_source=${body.utm}` : ''; const { url } = await stripe.checkout.sessions.create({ customer, - cancel_url: process.env['FRONTEND_URL'] + `/billing`, + cancel_url: process.env['FRONTEND_URL'] + `/billing?cancel=true${isUtm}`, success_url: process.env['FRONTEND_URL'] + - `/launches?onboarding=true&check=${uniqueId}`, + `/launches?onboarding=true&check=${uniqueId}${isUtm}`, mode: 'subscription', subscription_data: { trial_period_days: 7,