From b30a680d045976b12b6db00b6f7eaa7f86c0c0c3 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 11 Dec 2025 11:05:34 +0200 Subject: [PATCH 1/7] PM-3202 - check and store utm codes --- src/lib/functions/utm-cookies.handler.ts | 201 +++++++++++++++++++++++ src/main.ts | 4 + 2 files changed, 205 insertions(+) create mode 100644 src/lib/functions/utm-cookies.handler.ts diff --git a/src/lib/functions/utm-cookies.handler.ts b/src/lib/functions/utm-cookies.handler.ts new file mode 100644 index 0000000..d7f3b0b --- /dev/null +++ b/src/lib/functions/utm-cookies.handler.ts @@ -0,0 +1,201 @@ +import { TC_DOMAIN } from '../config/hosts' +import { getEnvValue } from '../config/env-vars' + +// UTM cookie configuration types +interface UtmParams { + utm_source?: string + utm_medium?: string + utm_campaign?: string +} + +// Cookie configuration constants +const TC_UTM_COOKIE_NAME = 'tc_utm' +const DEFAULT_COOKIE_LIFETIME_DAYS = 3 +const COOKIE_PATH = '/' +const COOKIE_SAMESITE = 'Lax' + +/** + * Sanitizes a string to remove all characters except A-Z, a-z, 0-9, hyphen (-), underscore (_) + * @param input - The string to sanitize + * @returns Sanitized string + */ +export function sanitize(input: string): string { + if (!input || typeof input !== 'string') { + return '' + } + // Remove all characters except A-Z, a-z, 0-9, hyphen (-), underscore (_) + return input.replace(/[^A-Za-z0-9\-_]/g, '') +} + +/** + * Extracts and sanitizes UTM parameters from the URL + * @returns Object containing sanitized utm_source, utm_medium, utm_campaign + */ +function extractUtmParams(): UtmParams { + const params: UtmParams = {} + + try { + const searchParams = new URLSearchParams(window.location.search) + + const utm_source = searchParams.get('utm_source') + const utm_medium = searchParams.get('utm_medium') + const utm_campaign = searchParams.get('utm_campaign') + + if (utm_source) { + params.utm_source = sanitize(utm_source) + } + if (utm_medium) { + params.utm_medium = sanitize(utm_medium) + } + if (utm_campaign) { + params.utm_campaign = sanitize(utm_campaign) + } + } catch (error) { + console.warn('Error extracting UTM parameters:', error) + } + + return params +} + +/** + * Gets the cookie lifetime from environment variable or uses default + * @returns Lifetime in days + */ +function getCookieLifetimeDays(): number { + try { + const envValue = getEnvValue('VITE_UTM_COOKIE_LIFETIME_DAYS', String(DEFAULT_COOKIE_LIFETIME_DAYS)) + const days = parseInt(envValue, 10) + return isNaN(days) ? DEFAULT_COOKIE_LIFETIME_DAYS : days + } catch { + return DEFAULT_COOKIE_LIFETIME_DAYS + } +} + +/** + * Gets the cookie domain with leading dot for broader subdomain coverage + * @returns Cookie domain (e.g., .topcoder.com) + */ +function getCookieDomain(): string { + return `.${TC_DOMAIN}` +} + +/** + * Checks if a cookie with the given name exists + * @param name - Cookie name + * @returns true if cookie exists, false otherwise + */ +function cookieExists(name: string): boolean { + const cookies = document.cookie.split(';') + return cookies.some(cookie => cookie.trim().startsWith(`${name}=`)) +} + +/** + * Sets a cookie with the specified attributes + * @param name - Cookie name + * @param value - Cookie value + * @param options - Cookie options (domain, path, sameSite, secure, maxAge) + */ +function setCookie( + name: string, + value: string, + options: { + domain?: string + path?: string + sameSite?: string + secure?: boolean + maxAge?: number + } = {} +): void { + const { + domain = getCookieDomain(), + path = COOKIE_PATH, + sameSite = COOKIE_SAMESITE, + secure = true, + maxAge = DEFAULT_COOKIE_LIFETIME_DAYS * 24 * 60 * 60, // Convert days to seconds + } = options + + let cookieString = `${name}=${encodeURIComponent(value)}` + + if (domain) { + cookieString += `; domain=${domain}` + } + if (path) { + cookieString += `; path=${path}` + } + if (maxAge) { + cookieString += `; max-age=${maxAge}` + } + if (sameSite) { + cookieString += `; SameSite=${sameSite}` + } + if (secure) { + cookieString += '; Secure' + } + + document.cookie = cookieString +} + +/** + * Initializes UTM cookie handling on page load + * Extracts UTM parameters from URL, sanitizes them, and persists to cookie + * Only sets the cookie if it doesn't already exist + */ +export function initializeUtmCookieHandler(): void { + try { + // Check if cookie already exists + if (cookieExists(TC_UTM_COOKIE_NAME)) { + console.debug('UTM cookie already exists, skipping initialization') + return + } + + // Extract and sanitize UTM parameters + const utmParams = extractUtmParams() + + // Only set cookie if we have at least one UTM parameter + if (Object.keys(utmParams).length === 0) { + console.debug('No UTM parameters found in URL') + return + } + + // Create JSON value with all UTM parameters + const cookieValue = JSON.stringify(utmParams) + + // Get cookie lifetime in seconds + const lifetimeDays = getCookieLifetimeDays() + const maxAgeSecs = lifetimeDays * 24 * 60 * 60 + + // Set the cookie with proper attributes + setCookie(TC_UTM_COOKIE_NAME, cookieValue, { + domain: getCookieDomain(), + path: COOKIE_PATH, + sameSite: COOKIE_SAMESITE, + secure: true, + maxAge: maxAgeSecs, + }) + + console.debug(`UTM cookie set successfully:`, utmParams) + } catch (error) { + console.error('Error initializing UTM cookie handler:', error) + } +} + +/** + * Retrieves and parses the tc_utm cookie + * @returns Parsed UTM parameters or null if cookie doesn't exist + */ +export function getUtmCookie(): UtmParams | null { + try { + const cookies = document.cookie.split(';') + const cookieStr = cookies.find(cookie => cookie.trim().startsWith(`${TC_UTM_COOKIE_NAME}=`)) + + if (!cookieStr) { + return null + } + + const cookieValue = decodeURIComponent(cookieStr.split('=')[1]) + return JSON.parse(cookieValue) as UtmParams + } catch (error) { + console.warn('Error retrieving UTM cookie:', error) + return null + } +} diff --git a/src/main.ts b/src/main.ts index cd3e7ba..603af73 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import type { Writable } from 'svelte/store' import { buildContext, type AuthUser, type NavigationHandler, type SupportMeta } from './lib/app-context' import { PubSub } from './lib/utils/pubsub'; +import { initializeUtmCookieHandler } from './lib/functions/utm-cookies.handler'; import 'lib/styles/main.scss'; @@ -207,4 +208,7 @@ function execQueueCall(method: TcUniNavMethods, ...args: unknown[]) { // replace the method that adds the calls to the queue // with a direct exec call Object.assign(window as any, {[globalName]: execQueueCall.bind(null)}); + + // Initialize UTM cookie handler on module load + initializeUtmCookieHandler(); })() From b22aa1868959c544b376d7ba7ed1bd5275ac2814 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 11 Dec 2025 11:06:15 +0200 Subject: [PATCH 2/7] lint --- src/lib/functions/utm-cookies.handler.ts | 118 +++++++++++------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/src/lib/functions/utm-cookies.handler.ts b/src/lib/functions/utm-cookies.handler.ts index d7f3b0b..6f4701a 100644 --- a/src/lib/functions/utm-cookies.handler.ts +++ b/src/lib/functions/utm-cookies.handler.ts @@ -1,18 +1,18 @@ -import { TC_DOMAIN } from '../config/hosts' -import { getEnvValue } from '../config/env-vars' +import { TC_DOMAIN } from '../config/hosts'; +import { getEnvValue } from '../config/env-vars'; // UTM cookie configuration types interface UtmParams { - utm_source?: string - utm_medium?: string - utm_campaign?: string + utm_source?: string; + utm_medium?: string; + utm_campaign?: string; } // Cookie configuration constants -const TC_UTM_COOKIE_NAME = 'tc_utm' -const DEFAULT_COOKIE_LIFETIME_DAYS = 3 -const COOKIE_PATH = '/' -const COOKIE_SAMESITE = 'Lax' +const TC_UTM_COOKIE_NAME = 'tc_utm'; +const DEFAULT_COOKIE_LIFETIME_DAYS = 3; +const COOKIE_PATH = '/'; +const COOKIE_SAMESITE = 'Lax'; /** * Sanitizes a string to remove all characters except A-Z, a-z, 0-9, hyphen (-), underscore (_) @@ -21,10 +21,10 @@ const COOKIE_SAMESITE = 'Lax' */ export function sanitize(input: string): string { if (!input || typeof input !== 'string') { - return '' + return ''; } // Remove all characters except A-Z, a-z, 0-9, hyphen (-), underscore (_) - return input.replace(/[^A-Za-z0-9\-_]/g, '') + return input.replace(/[^A-Za-z0-9\-_]/g, ''); } /** @@ -32,29 +32,29 @@ export function sanitize(input: string): string { * @returns Object containing sanitized utm_source, utm_medium, utm_campaign */ function extractUtmParams(): UtmParams { - const params: UtmParams = {} + const params: UtmParams = {}; try { - const searchParams = new URLSearchParams(window.location.search) + const searchParams = new URLSearchParams(window.location.search); - const utm_source = searchParams.get('utm_source') - const utm_medium = searchParams.get('utm_medium') - const utm_campaign = searchParams.get('utm_campaign') + const utm_source = searchParams.get('utm_source'); + const utm_medium = searchParams.get('utm_medium'); + const utm_campaign = searchParams.get('utm_campaign'); if (utm_source) { - params.utm_source = sanitize(utm_source) + params.utm_source = sanitize(utm_source); } if (utm_medium) { - params.utm_medium = sanitize(utm_medium) + params.utm_medium = sanitize(utm_medium); } if (utm_campaign) { - params.utm_campaign = sanitize(utm_campaign) + params.utm_campaign = sanitize(utm_campaign); } } catch (error) { - console.warn('Error extracting UTM parameters:', error) + console.warn('Error extracting UTM parameters:', error); } - return params + return params; } /** @@ -63,11 +63,11 @@ function extractUtmParams(): UtmParams { */ function getCookieLifetimeDays(): number { try { - const envValue = getEnvValue('VITE_UTM_COOKIE_LIFETIME_DAYS', String(DEFAULT_COOKIE_LIFETIME_DAYS)) - const days = parseInt(envValue, 10) - return isNaN(days) ? DEFAULT_COOKIE_LIFETIME_DAYS : days + const envValue = getEnvValue('VITE_UTM_COOKIE_LIFETIME_DAYS', String(DEFAULT_COOKIE_LIFETIME_DAYS)); + const days = parseInt(envValue, 10); + return isNaN(days) ? DEFAULT_COOKIE_LIFETIME_DAYS : days; } catch { - return DEFAULT_COOKIE_LIFETIME_DAYS + return DEFAULT_COOKIE_LIFETIME_DAYS; } } @@ -76,7 +76,7 @@ function getCookieLifetimeDays(): number { * @returns Cookie domain (e.g., .topcoder.com) */ function getCookieDomain(): string { - return `.${TC_DOMAIN}` + return `.${TC_DOMAIN}`; } /** @@ -85,8 +85,8 @@ function getCookieDomain(): string { * @returns true if cookie exists, false otherwise */ function cookieExists(name: string): boolean { - const cookies = document.cookie.split(';') - return cookies.some(cookie => cookie.trim().startsWith(`${name}=`)) + const cookies = document.cookie.split(';'); + return cookies.some(cookie => cookie.trim().startsWith(`${name}=`)); } /** @@ -99,11 +99,11 @@ function setCookie( name: string, value: string, options: { - domain?: string - path?: string - sameSite?: string - secure?: boolean - maxAge?: number + domain?: string; + path?: string; + sameSite?: string; + secure?: boolean; + maxAge?: number; } = {} ): void { const { @@ -112,27 +112,27 @@ function setCookie( sameSite = COOKIE_SAMESITE, secure = true, maxAge = DEFAULT_COOKIE_LIFETIME_DAYS * 24 * 60 * 60, // Convert days to seconds - } = options + } = options; - let cookieString = `${name}=${encodeURIComponent(value)}` + let cookieString = `${name}=${encodeURIComponent(value)}`; if (domain) { - cookieString += `; domain=${domain}` + cookieString += `; domain=${domain}`; } if (path) { - cookieString += `; path=${path}` + cookieString += `; path=${path}`; } if (maxAge) { - cookieString += `; max-age=${maxAge}` + cookieString += `; max-age=${maxAge}`; } if (sameSite) { - cookieString += `; SameSite=${sameSite}` + cookieString += `; SameSite=${sameSite}`; } if (secure) { - cookieString += '; Secure' + cookieString += '; Secure'; } - document.cookie = cookieString + document.cookie = cookieString; } /** @@ -144,25 +144,25 @@ export function initializeUtmCookieHandler(): void { try { // Check if cookie already exists if (cookieExists(TC_UTM_COOKIE_NAME)) { - console.debug('UTM cookie already exists, skipping initialization') - return + console.debug('UTM cookie already exists, skipping initialization'); + return; } // Extract and sanitize UTM parameters - const utmParams = extractUtmParams() + const utmParams = extractUtmParams(); // Only set cookie if we have at least one UTM parameter if (Object.keys(utmParams).length === 0) { - console.debug('No UTM parameters found in URL') - return + console.debug('No UTM parameters found in URL'); + return; } // Create JSON value with all UTM parameters - const cookieValue = JSON.stringify(utmParams) + const cookieValue = JSON.stringify(utmParams); // Get cookie lifetime in seconds - const lifetimeDays = getCookieLifetimeDays() - const maxAgeSecs = lifetimeDays * 24 * 60 * 60 + const lifetimeDays = getCookieLifetimeDays(); + const maxAgeSecs = lifetimeDays * 24 * 60 * 60; // Set the cookie with proper attributes setCookie(TC_UTM_COOKIE_NAME, cookieValue, { @@ -171,11 +171,11 @@ export function initializeUtmCookieHandler(): void { sameSite: COOKIE_SAMESITE, secure: true, maxAge: maxAgeSecs, - }) + }); - console.debug(`UTM cookie set successfully:`, utmParams) + console.debug(`UTM cookie set successfully:`, utmParams); } catch (error) { - console.error('Error initializing UTM cookie handler:', error) + console.error('Error initializing UTM cookie handler:', error); } } @@ -185,17 +185,17 @@ export function initializeUtmCookieHandler(): void { */ export function getUtmCookie(): UtmParams | null { try { - const cookies = document.cookie.split(';') - const cookieStr = cookies.find(cookie => cookie.trim().startsWith(`${TC_UTM_COOKIE_NAME}=`)) + const cookies = document.cookie.split(';'); + const cookieStr = cookies.find(cookie => cookie.trim().startsWith(`${TC_UTM_COOKIE_NAME}=`)); if (!cookieStr) { - return null + return null; } - const cookieValue = decodeURIComponent(cookieStr.split('=')[1]) - return JSON.parse(cookieValue) as UtmParams + const cookieValue = decodeURIComponent(cookieStr.split('=')[1]); + return JSON.parse(cookieValue) as UtmParams; } catch (error) { - console.warn('Error retrieving UTM cookie:', error) - return null + console.warn('Error retrieving UTM cookie:', error); + return null; } } From fe3169b276d86e59d30bb3906d1b2269926fab81 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 11 Dec 2025 11:26:06 +0200 Subject: [PATCH 3/7] PM-3203 - use utm cookies on the login & signup links --- src/lib/components/user-area/UserArea.svelte | 6 +++- src/lib/functions/utm-cookies.handler.ts | 37 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/lib/components/user-area/UserArea.svelte b/src/lib/components/user-area/UserArea.svelte index cdd86cd..e9be684 100644 --- a/src/lib/components/user-area/UserArea.svelte +++ b/src/lib/components/user-area/UserArea.svelte @@ -5,6 +5,7 @@ import { AUTH0_AUTHENTICATOR_URL } from 'lib/config'; import { AUTH_USER_ROLE } from 'lib/config/auth'; import { DISABLE_NUDGES } from "lib/config/profile-toasts.config"; + import { appendUtmParamsToUrl } from 'lib/functions/utm-cookies.handler'; import ToolSelector from '../tool-selector/ToolSelector.svelte'; @@ -65,12 +66,15 @@ function onSignIn(signup?: any) { const locationHref = `${window.location.origin}${window.location.pathname}` - const signupUrl = [ + let signupUrl = [ `${AUTH0_AUTHENTICATOR_URL}?retUrl=${encodeURIComponent(locationHref)}`, signup === true ? '&mode=signUp' : '', $ctx.signupUtmCodes, ].filter(Boolean).join('&') + // Append UTM parameters from cookie if they exist + signupUrl = appendUtmParamsToUrl(signupUrl); + window.location.href = signupUrl; } diff --git a/src/lib/functions/utm-cookies.handler.ts b/src/lib/functions/utm-cookies.handler.ts index 6f4701a..24b2d72 100644 --- a/src/lib/functions/utm-cookies.handler.ts +++ b/src/lib/functions/utm-cookies.handler.ts @@ -199,3 +199,40 @@ export function getUtmCookie(): UtmParams | null { return null; } } + +/** + * Appends UTM parameters from the tc_utm cookie to a given URL + * Only appends parameters that exist in the cookie + * @param url - The base URL to append parameters to + * @returns URL with UTM parameters appended, or original URL if no cookie exists + */ +export function appendUtmParamsToUrl(url: string): string { + if (!url) { + return url; + } + + const utmParams = getUtmCookie(); + if (!utmParams || Object.keys(utmParams).length === 0) { + return url; + } + + try { + const urlObj = new URL(url, window.location.origin); + + // Append only the UTM parameters that exist in the cookie + if (utmParams.utm_source) { + urlObj.searchParams.set('utm_source', utmParams.utm_source); + } + if (utmParams.utm_medium) { + urlObj.searchParams.set('utm_medium', utmParams.utm_medium); + } + if (utmParams.utm_campaign) { + urlObj.searchParams.set('utm_campaign', utmParams.utm_campaign); + } + + return urlObj.toString(); + } catch (error) { + console.warn('Error appending UTM parameters to URL:', error); + return url; + } +} From 5616f7274a94a0107b040eab1922f383c4de0d68 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 11 Dec 2025 11:55:29 +0200 Subject: [PATCH 4/7] types --- .../lib/functions/utm-cookies.handler.d.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 types/src/lib/functions/utm-cookies.handler.d.ts diff --git a/types/src/lib/functions/utm-cookies.handler.d.ts b/types/src/lib/functions/utm-cookies.handler.d.ts new file mode 100644 index 0000000..c3d9844 --- /dev/null +++ b/types/src/lib/functions/utm-cookies.handler.d.ts @@ -0,0 +1,30 @@ +interface UtmParams { + utm_source?: string; + utm_medium?: string; + utm_campaign?: string; +} +/** + * Sanitizes a string to remove all characters except A-Z, a-z, 0-9, hyphen (-), underscore (_) + * @param input - The string to sanitize + * @returns Sanitized string + */ +export declare function sanitize(input: string): string; +/** + * Initializes UTM cookie handling on page load + * Extracts UTM parameters from URL, sanitizes them, and persists to cookie + * Only sets the cookie if it doesn't already exist + */ +export declare function initializeUtmCookieHandler(): void; +/** + * Retrieves and parses the tc_utm cookie + * @returns Parsed UTM parameters or null if cookie doesn't exist + */ +export declare function getUtmCookie(): UtmParams | null; +/** + * Appends UTM parameters from the tc_utm cookie to a given URL + * Only appends parameters that exist in the cookie + * @param url - The base URL to append parameters to + * @returns URL with UTM parameters appended, or original URL if no cookie exists + */ +export declare function appendUtmParamsToUrl(url: string): string; +export {}; From 8e7acb4ff7b2001eeeb7f8c2e644ea826c279859 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 17 Dec 2025 13:44:25 +0200 Subject: [PATCH 5/7] PM-3267 - nasa referral --- src/lib/config/nav-menu/all-nav-items.config.ts | 5 +++++ src/lib/config/nav-menu/main-navigation.config.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/lib/config/nav-menu/all-nav-items.config.ts b/src/lib/config/nav-menu/all-nav-items.config.ts index 823d3fc..d02a2d9 100644 --- a/src/lib/config/nav-menu/all-nav-items.config.ts +++ b/src/lib/config/nav-menu/all-nav-items.config.ts @@ -217,4 +217,9 @@ export const allNavItems: {[key: string]: NavMenuItem} = { marketingPathname: '/talent', url: getMarketingUrl('/talent'), }, + referralProgram: { + label: 'Referral Programme', + marketingPathname: '/nasa-referral', + url: getMarketingUrl('/nasa-referral'), + }, } diff --git a/src/lib/config/nav-menu/main-navigation.config.ts b/src/lib/config/nav-menu/main-navigation.config.ts index da53db8..032d5b2 100644 --- a/src/lib/config/nav-menu/main-navigation.config.ts +++ b/src/lib/config/nav-menu/main-navigation.config.ts @@ -22,6 +22,7 @@ export const mainNavigationItems: NavMenuItem[] = [ allNavItems.aiHub, allNavItems.statistics, allNavItems.discordApp, + allNavItems.referralProgram, ] }, { From 98e6587bd6321626e761a91e88ab4a5f585de6fd Mon Sep 17 00:00:00 2001 From: vas3a Date: Wed, 17 Dec 2025 13:50:00 +0200 Subject: [PATCH 6/7] Revert "PM-3267 - nasa referral" --- src/lib/config/nav-menu/all-nav-items.config.ts | 5 ----- src/lib/config/nav-menu/main-navigation.config.ts | 1 - 2 files changed, 6 deletions(-) diff --git a/src/lib/config/nav-menu/all-nav-items.config.ts b/src/lib/config/nav-menu/all-nav-items.config.ts index d02a2d9..823d3fc 100644 --- a/src/lib/config/nav-menu/all-nav-items.config.ts +++ b/src/lib/config/nav-menu/all-nav-items.config.ts @@ -217,9 +217,4 @@ export const allNavItems: {[key: string]: NavMenuItem} = { marketingPathname: '/talent', url: getMarketingUrl('/talent'), }, - referralProgram: { - label: 'Referral Programme', - marketingPathname: '/nasa-referral', - url: getMarketingUrl('/nasa-referral'), - }, } diff --git a/src/lib/config/nav-menu/main-navigation.config.ts b/src/lib/config/nav-menu/main-navigation.config.ts index 032d5b2..da53db8 100644 --- a/src/lib/config/nav-menu/main-navigation.config.ts +++ b/src/lib/config/nav-menu/main-navigation.config.ts @@ -22,7 +22,6 @@ export const mainNavigationItems: NavMenuItem[] = [ allNavItems.aiHub, allNavItems.statistics, allNavItems.discordApp, - allNavItems.referralProgram, ] }, { From ad1ec27c76a848c3c3700296867bd1bc62968845 Mon Sep 17 00:00:00 2001 From: vas3a Date: Wed, 17 Dec 2025 15:17:20 +0200 Subject: [PATCH 7/7] Revert "Revert "PM-3267 - nasa referral"" --- src/lib/config/nav-menu/all-nav-items.config.ts | 5 +++++ src/lib/config/nav-menu/main-navigation.config.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/lib/config/nav-menu/all-nav-items.config.ts b/src/lib/config/nav-menu/all-nav-items.config.ts index 823d3fc..d02a2d9 100644 --- a/src/lib/config/nav-menu/all-nav-items.config.ts +++ b/src/lib/config/nav-menu/all-nav-items.config.ts @@ -217,4 +217,9 @@ export const allNavItems: {[key: string]: NavMenuItem} = { marketingPathname: '/talent', url: getMarketingUrl('/talent'), }, + referralProgram: { + label: 'Referral Programme', + marketingPathname: '/nasa-referral', + url: getMarketingUrl('/nasa-referral'), + }, } diff --git a/src/lib/config/nav-menu/main-navigation.config.ts b/src/lib/config/nav-menu/main-navigation.config.ts index da53db8..032d5b2 100644 --- a/src/lib/config/nav-menu/main-navigation.config.ts +++ b/src/lib/config/nav-menu/main-navigation.config.ts @@ -22,6 +22,7 @@ export const mainNavigationItems: NavMenuItem[] = [ allNavItems.aiHub, allNavItems.statistics, allNavItems.discordApp, + allNavItems.referralProgram, ] }, {