From 13ff8475dbf8810af20be62ff1ed827fb921a3c1 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Tue, 24 Mar 2026 23:20:01 -0400 Subject: [PATCH 1/5] fix: fetch fresh email verification status and update referral reward amount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fetch emailVerified from API in openPanel to avoid stale local state - Close Cello panel when email is not verified to prevent modal overlap - Remove cello-launcher attribute from AccountPopover to avoid SDK auto-open - Fix Cello notification dot causing layout shift via CSS absolute positioning - Update referral reward amount from €1,000 to €100 across all locales --- src/app/i18n/locales/de.json | 4 +- src/app/i18n/locales/en.json | 4 +- src/app/i18n/locales/es.json | 4 +- src/app/i18n/locales/fr.json | 4 +- src/app/i18n/locales/it.json | 4 +- src/app/i18n/locales/ru.json | 4 +- src/app/i18n/locales/tw.json | 4 +- src/app/i18n/locales/zh.json | 4 +- src/index.scss | 6 +++ src/services/referral.service.test.ts | 56 ++++++++++++++++++++ src/services/referral.service.ts | 42 ++++++++++++++- src/views/Home/components/AccountPopover.tsx | 1 - 12 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/app/i18n/locales/de.json b/src/app/i18n/locales/de.json index f42a314c7..9461efb76 100644 --- a/src/app/i18n/locales/de.json +++ b/src/app/i18n/locales/de.json @@ -361,7 +361,7 @@ "trash": "Papierkorb", "desktop": "Desktop", "referralBanner": { - "message": "Denk daran! Verdiene bis zu 1.000 €, indem du einen Freund empfiehlst", + "message": "Denk daran! Verdiene bis zu 100 €, indem du einen Freund empfiehlst", "cta": "Empfehlen und verdienen" } }, @@ -401,7 +401,7 @@ "settings": "Einstellungen", "logout": "Log-out", "referAndEarn": "Empfehlen und verdienen", - "earnReferral": "Verdiene 1.000 €", + "earnReferral": "Verdiene 100 €", "spaceUsed": "{{space}}% Platz verbraucht" }, "tabs": { diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index 1a76b9cbe..0b6a38c54 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -427,7 +427,7 @@ "trash": "Trash", "desktop": "Desktop", "referralBanner": { - "message": "Remember! Earn up to €1,000 by referring a friend", + "message": "Remember! Earn up to €100 by referring a friend", "cta": "Refer and Earn" } }, @@ -461,7 +461,7 @@ "settings": "Settings", "logout": "Log out", "referAndEarn": "Refer and Earn", - "earnReferral": "Earn €1,000", + "earnReferral": "Earn €100", "spaceUsed": "{{space}}% space used" }, "tabs": { diff --git a/src/app/i18n/locales/es.json b/src/app/i18n/locales/es.json index 8303c0652..cb45cedaa 100644 --- a/src/app/i18n/locales/es.json +++ b/src/app/i18n/locales/es.json @@ -410,7 +410,7 @@ "trash": "Papelera", "desktop": "Escritorio", "referralBanner": { - "message": "¡Recuerda! Gana hasta 1.000 € recomendando a un amigo", + "message": "¡Recuerda! Gana hasta 100 € recomendando a un amigo", "cta": "Recomienda y gana" } }, @@ -444,7 +444,7 @@ "settings": "Ajustes", "logout": "Cerrar sesión", "referAndEarn": "Recomienda y gana", - "earnReferral": "Gana 1.000 €", + "earnReferral": "Gana 100 €", "spaceUsed": "{{space}}% espacio usado" }, "tabs": { diff --git a/src/app/i18n/locales/fr.json b/src/app/i18n/locales/fr.json index fc5acdb73..732170893 100644 --- a/src/app/i18n/locales/fr.json +++ b/src/app/i18n/locales/fr.json @@ -379,7 +379,7 @@ "trash": "Corbeille", "desktop": "App de bureau", "referralBanner": { - "message": "N'oubliez pas ! Gagnez jusqu'à 1 000 € en parrainant un ami", + "message": "N'oubliez pas ! Gagnez jusqu'à 100 € en parrainant un ami", "cta": "Parrainez et gagnez" } }, @@ -413,7 +413,7 @@ "settings": "Paramètres", "logout": "Déconnexion", "referAndEarn": "Parrainez et gagnez", - "earnReferral": "Gagnez 1 000 €", + "earnReferral": "Gagnez 100 €", "spaceUsed": "{{space}}% d'espace utilisé" }, "tabs": { diff --git a/src/app/i18n/locales/it.json b/src/app/i18n/locales/it.json index 81b0d1446..d5d0ff4f3 100644 --- a/src/app/i18n/locales/it.json +++ b/src/app/i18n/locales/it.json @@ -470,7 +470,7 @@ "trash": "Cestino", "desktop": "Applicazione desktop", "referralBanner": { - "message": "Ricorda! Guadagna fino a 1.000 € invitando un amico", + "message": "Ricorda! Guadagna fino a 100 € invitando un amico", "cta": "Invita e guadagna" } }, @@ -504,7 +504,7 @@ "settings": "Impostazioni", "logout": "Disconnettersi", "referAndEarn": "Invita e guadagna", - "earnReferral": "Guadagna 1.000 €", + "earnReferral": "Guadagna 100 €", "spaceUsed": "{{space}}% spazio usato" }, "tabs": { diff --git a/src/app/i18n/locales/ru.json b/src/app/i18n/locales/ru.json index 1da83e49d..f8bbb1b94 100644 --- a/src/app/i18n/locales/ru.json +++ b/src/app/i18n/locales/ru.json @@ -379,7 +379,7 @@ "trash": "Корзина", "desktop": "Рабочий стол", "referralBanner": { - "message": "Помните! Заработайте до 1 000 €, пригласив друга", + "message": "Помните! Заработайте до 100 €, пригласив друга", "cta": "Приглашайте и зарабатывайте" } }, @@ -413,7 +413,7 @@ "settings": "Настройки", "logout": "Выйти", "referAndEarn": "Приглашайте и зарабатывайте", - "earnReferral": "Заработайте 1 000 €", + "earnReferral": "Заработайте 100 €", "spaceUsed": "{{space}}% памяти использовано" }, "tabs": { diff --git a/src/app/i18n/locales/tw.json b/src/app/i18n/locales/tw.json index db873e3b2..2ba282f25 100644 --- a/src/app/i18n/locales/tw.json +++ b/src/app/i18n/locales/tw.json @@ -395,7 +395,7 @@ "trash": "垃圾桶", "desktop": "桌面", "referralBanner": { - "message": "別忘了!推薦朋友最高可賺取 €1,000", + "message": "別忘了!推薦朋友最高可賺取 €100", "cta": "推薦賺取" } }, @@ -429,7 +429,7 @@ "settings": "設定", "logout": "登出", "referAndEarn": "推薦賺取", - "earnReferral": "賺取 €1,000", + "earnReferral": "賺取 €100", "spaceUsed": "已使用 {{space}}% 的空間" }, "tabs": { diff --git a/src/app/i18n/locales/zh.json b/src/app/i18n/locales/zh.json index dc04615a2..d5534d345 100644 --- a/src/app/i18n/locales/zh.json +++ b/src/app/i18n/locales/zh.json @@ -393,7 +393,7 @@ "trash": "垃圾箱", "desktop": "桌面应用程序", "referralBanner": { - "message": "别忘了!推荐朋友最高可赚取 €1,000", + "message": "别忘了!推荐朋友最高可赚取 €100", "cta": "推荐赚取" } }, @@ -427,7 +427,7 @@ "settings": "设置", "logout": "登出", "referAndEarn": "推荐赚取", - "earnReferral": "赚取 €1,000", + "earnReferral": "赚取 €100", "spaceUsed": "已使用储存空间的{{space}}%" }, "tabs": { diff --git a/src/index.scss b/src/index.scss index 22e562309..bcad3ca2d 100644 --- a/src/index.scss +++ b/src/index.scss @@ -364,6 +364,12 @@ code { } } +#cello-launcher > div { + position: absolute !important; + top: 2px; + right: 2px; +} + /* RECAPTCHA */ .grecaptcha-badge { visibility: hidden; diff --git a/src/services/referral.service.test.ts b/src/services/referral.service.test.ts index c53cae1f8..7cc043f30 100644 --- a/src/services/referral.service.test.ts +++ b/src/services/referral.service.test.ts @@ -16,6 +16,8 @@ vi.mock('./date.service', () => ({ }, })); +const mockRefreshUser = vi.fn().mockResolvedValue({ user: { emailVerified: true } }); + vi.mock('app/core/factory/sdk', () => ({ SdkFactory: { getNewApiInstance: vi.fn(() => ({ @@ -23,6 +25,9 @@ vi.mock('app/core/factory/sdk', () => ({ createReferralToken: vi.fn().mockResolvedValue({ token: 'mock-token' }), isReferralEnabled: vi.fn().mockResolvedValue({ isEnabled: true }), })), + createUsersClient: vi.fn(() => ({ + refreshUser: mockRefreshUser, + })), })), }, })); @@ -169,6 +174,25 @@ describe('referralService', () => { }); }); + describe('isEligibleForReferral', () => { + it('when no account creation date is provided, then the user is eligible', async () => { + expect(await referralService.isEligibleForReferral()).toBe(true); + }); + + it.each([ + { scenario: 'when the account is older than 30 days, then the user is eligible', days: 31, expected: true }, + { scenario: 'when the account is exactly 30 days old, then the user is eligible', days: 30, expected: true }, + { + scenario: 'when the account is younger than 30 days, then the user is not eligible', + days: 15, + expected: false, + }, + ])('$scenario', async ({ days, expected }) => { + vi.mocked(dateService.getDaysSince).mockReturnValue(days); + + expect(await referralService.isEligibleForReferral(new Date())).toBe(expected); + }); + }); describe('tracking events', () => { it.each([ { @@ -374,5 +398,37 @@ describe('referralService', () => { const saved = localStorage.getItem(BANNER_STATE_KEY); expect(saved).toBeNull(); }); + + it('when the API returns emailVerified true but the passed-in value is false, then the panel opens', async () => { + setupCelloBootFlow(); + const mockCello = vi.fn().mockResolvedValue(undefined); + globalThis.Cello = mockCello; + mockRefreshUser.mockResolvedValueOnce({ user: { emailVerified: true } }); + + const unverifiedUser = { ...mockUser, emailVerified: false }; + await referralService.openPanel(unverifiedUser); + + expect(mockCello).toHaveBeenCalledWith('open'); + }); + + it('when the API call fails, then it falls back to the passed-in emailVerified value', async () => { + mockRefreshUser.mockRejectedValueOnce(new Error('network error')); + + const unverifiedUser = { ...mockUser, emailVerified: false }; + await referralService.openPanel(unverifiedUser); + + expect(globalThis.Cello).toBeUndefined(); + }); + + it('when the email is not verified and the referral widget is loaded, then the panel is closed', async () => { + const mockCello = vi.fn().mockResolvedValue(undefined); + globalThis.Cello = mockCello; + mockRefreshUser.mockResolvedValueOnce({ user: { emailVerified: false } }); + + await referralService.openPanel(mockUser); + + expect(mockCello).toHaveBeenCalledWith('close'); + expect(mockCello).not.toHaveBeenCalledWith('open'); + }); }); }); diff --git a/src/services/referral.service.ts b/src/services/referral.service.ts index 60efc3922..71f7ea9a7 100644 --- a/src/services/referral.service.ts +++ b/src/services/referral.service.ts @@ -1,7 +1,10 @@ import localStorageService from './local-storage.service'; import envService from './env.service'; +import dateService from './date.service'; import { SdkFactory } from 'app/core/factory/sdk'; import { loadExternalScript } from 'utils/loadExternalScript'; +import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; +import { userService } from 'services'; const MAX_BANNER_SHOW_COUNT = 2; const MIN_FILE_UPLOADS_FOR_BANNER = 3; @@ -62,6 +65,16 @@ const trackAndEmit = (update: Partial): void => { const getReferralsClient = () => SdkFactory.getNewApiInstance().createReferralsClient(); +const fetchEmailVerifiedStatus = async (fallback: boolean): Promise => { + try { + const usersClient = SdkFactory.getNewApiInstance().createUsersClient(); + const { user } = await usersClient.refreshUser(); + return user.emailVerified; + } catch { + return fallback; + } +}; + const fetchReferralToken = async (): Promise => { const { token } = await getReferralsClient().createReferralToken(); return token; @@ -119,6 +132,28 @@ const loadAndBoot = async (user: ReferralUser, language?: string): Promise }; const openPanel = async (user: ReferralUser, language?: string): Promise => { + const isEmailVerified = await fetchEmailVerifiedStatus(user.emailVerified); + + if (!isEmailVerified) { + const toastId = notificationsService.show({ + text: 'Verify your email to be elegible to referrals. Check your inbox or', + type: ToastType.Info, + duration: Infinity, + containerClassName: 'w-100 border-primary/30 bg-primary/5 dark:bg-primary/10', + action: { + text: 'resend verification', + onClick: () => { + userService.sendVerificationEmail(); + notificationsService.dismiss(toastId); + }, + }, + }); + if (globalThis.Cello) { + await globalThis.Cello('close'); + } + return; + } + await loadAndBoot(user, language); if (!globalThis.Cello) return; markReferralModalOpened(); @@ -255,9 +290,14 @@ const boot = async (user: ReferralUser, language?: string): Promise => { } }; -//const MIN_ACCOUNT_AGE_DAYS = 30; +const MIN_ACCOUNT_AGE_DAYS = 30; const isEligibleForReferral = async (accountCreatedAt?: Date): Promise => { + const isAccountTooNew = accountCreatedAt && dateService.getDaysSince(accountCreatedAt) < MIN_ACCOUNT_AGE_DAYS; + if (isAccountTooNew) { + return false; + } + try { const { isEnabled } = await getReferralsClient().isReferralEnabled(); return isEnabled; diff --git a/src/views/Home/components/AccountPopover.tsx b/src/views/Home/components/AccountPopover.tsx index 474e18a8d..e8dc9a469 100644 --- a/src/views/Home/components/AccountPopover.tsx +++ b/src/views/Home/components/AccountPopover.tsx @@ -111,7 +111,6 @@ export default function AccountPopover({ className = '', user, plan }: Readonly< {isReferralEligible && ( { referralService.openPanel( { From 6a289d296ed73739ede2a129f722a627148406d6 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Wed, 25 Mar 2026 10:02:06 -0400 Subject: [PATCH 2/5] feat(referrals): localize email verification toast and optimize verification check - replace hardcoded referral verification toast text with i18n keys - add email verification message/action translations to all supported locales - skip API verification lookup when user is already marked as verified - remove explicit infinite toast duration to use default notification behavior --- src/app/i18n/locales/de.json | 4 ++++ src/app/i18n/locales/en.json | 4 ++++ src/app/i18n/locales/es.json | 4 ++++ src/app/i18n/locales/fr.json | 4 ++++ src/app/i18n/locales/it.json | 4 ++++ src/app/i18n/locales/ru.json | 4 ++++ src/app/i18n/locales/tw.json | 4 ++++ src/app/i18n/locales/zh.json | 4 ++++ src/services/referral.service.test.ts | 7 ++++--- src/services/referral.service.ts | 8 ++++---- 10 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/app/i18n/locales/de.json b/src/app/i18n/locales/de.json index 9461efb76..565c6346c 100644 --- a/src/app/i18n/locales/de.json +++ b/src/app/i18n/locales/de.json @@ -1588,6 +1588,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Bestätige deine E-Mail-Adresse, um für Empfehlungen berechtigt zu sein. Überprüfe deinen Posteingang oder", + "resendVerification": "Bestätigung erneut senden" + }, "items": { "create-account": "Erstelle ein Konto", "install-mobile-app": "Installiere die mobile App und lade eine Datei hoch", diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index 0b6a38c54..42f6d5037 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -1671,6 +1671,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Verify your email to be eligible to referrals. Check your inbox or", + "resendVerification": "resend verification" + }, "items": { "create-account": "Create an account", "install-mobile-app": "Install mobile app and upload a file", diff --git a/src/app/i18n/locales/es.json b/src/app/i18n/locales/es.json index cb45cedaa..610c19f7a 100644 --- a/src/app/i18n/locales/es.json +++ b/src/app/i18n/locales/es.json @@ -1648,6 +1648,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Verifica tu correo electrónico para ser elegible para referidos. Revisa tu bandeja de entrada o", + "resendVerification": "reenviar verificación" + }, "items": { "create-account": "Crea una cuenta", "install-mobile-app": "Instala la app móvil y sube un archivo", diff --git a/src/app/i18n/locales/fr.json b/src/app/i18n/locales/fr.json index 732170893..0b5a92e1f 100644 --- a/src/app/i18n/locales/fr.json +++ b/src/app/i18n/locales/fr.json @@ -1594,6 +1594,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Vérifiez votre e-mail pour être éligible aux parrainages. Consultez votre boîte de réception ou", + "resendVerification": "renvoyer la vérification" + }, "items": { "create-account": "Créer un compte", "install-mobile-app": "Installer l'app mobile et télécharger un fichier", diff --git a/src/app/i18n/locales/it.json b/src/app/i18n/locales/it.json index d5d0ff4f3..496eda4e2 100644 --- a/src/app/i18n/locales/it.json +++ b/src/app/i18n/locales/it.json @@ -1701,6 +1701,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Verifica la tua email per essere idoneo ai referral. Controlla la tua casella di posta o", + "resendVerification": "rinvia verifica" + }, "items": { "create-account": "Creazione dell’account", "install-mobile-app": "Installa l’app mobile e carica un file", diff --git a/src/app/i18n/locales/ru.json b/src/app/i18n/locales/ru.json index f8bbb1b94..388d94c04 100644 --- a/src/app/i18n/locales/ru.json +++ b/src/app/i18n/locales/ru.json @@ -1609,6 +1609,10 @@ } }, "referrals": { + "emailVerification": { + "message": "Подтвердите свою электронную почту, чтобы участвовать в реферальной программе. Проверьте свой почтовый ящик или", + "resendVerification": "отправить повторно" + }, "items": { "create-account": "Создать аккаунт", "install-mobile-app": "Установить мобильное приложение и загрузить файл", diff --git a/src/app/i18n/locales/tw.json b/src/app/i18n/locales/tw.json index 2ba282f25..e6d89577c 100644 --- a/src/app/i18n/locales/tw.json +++ b/src/app/i18n/locales/tw.json @@ -1601,6 +1601,10 @@ } }, "referrals": { + "emailVerification": { + "message": "請驗證您的電子郵件以獲得推薦資格。請檢查您的收件匣或", + "resendVerification": "重新發送驗證" + }, "items": { "create-account": "創建帳戶", "install-mobile-app": "安裝移動應用程序並上傳文件", diff --git a/src/app/i18n/locales/zh.json b/src/app/i18n/locales/zh.json index d5534d345..9f9bee35c 100644 --- a/src/app/i18n/locales/zh.json +++ b/src/app/i18n/locales/zh.json @@ -1636,6 +1636,10 @@ } }, "referrals": { + "emailVerification": { + "message": "请验证您的电子邮件以获得推荐资格。请检查您的收件箱或", + "resendVerification": "重新发送验证" + }, "items": { "create-account": "创建一个账户", "install-mobile-app": "安装移动应用程序并上传一个文件", diff --git a/src/services/referral.service.test.ts b/src/services/referral.service.test.ts index 7cc043f30..46794d19f 100644 --- a/src/services/referral.service.test.ts +++ b/src/services/referral.service.test.ts @@ -420,15 +420,16 @@ describe('referralService', () => { expect(globalThis.Cello).toBeUndefined(); }); - it('when the email is not verified and the referral widget is loaded, then the panel is closed', async () => { + it('when email is already verified, then the panel opens', async () => { const mockCello = vi.fn().mockResolvedValue(undefined); globalThis.Cello = mockCello; mockRefreshUser.mockResolvedValueOnce({ user: { emailVerified: false } }); await referralService.openPanel(mockUser); - expect(mockCello).toHaveBeenCalledWith('close'); - expect(mockCello).not.toHaveBeenCalledWith('open'); + expect(mockRefreshUser).not.toHaveBeenCalled(); + expect(mockCello).toHaveBeenCalledWith('open'); + expect(mockCello).not.toHaveBeenCalledWith('close'); }); }); }); diff --git a/src/services/referral.service.ts b/src/services/referral.service.ts index 71f7ea9a7..7d09329d6 100644 --- a/src/services/referral.service.ts +++ b/src/services/referral.service.ts @@ -5,6 +5,7 @@ import { SdkFactory } from 'app/core/factory/sdk'; import { loadExternalScript } from 'utils/loadExternalScript'; import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; import { userService } from 'services'; +import { t } from 'i18next'; const MAX_BANNER_SHOW_COUNT = 2; const MIN_FILE_UPLOADS_FOR_BANNER = 3; @@ -132,16 +133,15 @@ const loadAndBoot = async (user: ReferralUser, language?: string): Promise }; const openPanel = async (user: ReferralUser, language?: string): Promise => { - const isEmailVerified = await fetchEmailVerifiedStatus(user.emailVerified); + const isEmailVerified = user.emailVerified || (await fetchEmailVerifiedStatus(user.emailVerified)); if (!isEmailVerified) { const toastId = notificationsService.show({ - text: 'Verify your email to be elegible to referrals. Check your inbox or', + text: t('referrals.emailVerification.message'), type: ToastType.Info, - duration: Infinity, containerClassName: 'w-100 border-primary/30 bg-primary/5 dark:bg-primary/10', action: { - text: 'resend verification', + text: t('referrals.emailVerification.resendVerification'), onClick: () => { userService.sendVerificationEmail(); notificationsService.dismiss(toastId); From 19916de7d653421ba74127f2eb91dc107005c336 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Wed, 25 Mar 2026 12:29:22 -0400 Subject: [PATCH 3/5] feat(referrals): update referral banner message to 10% commission copy --- src/app/i18n/locales/de.json | 2 +- src/app/i18n/locales/en.json | 2 +- src/app/i18n/locales/es.json | 2 +- src/app/i18n/locales/fr.json | 2 +- src/app/i18n/locales/it.json | 2 +- src/app/i18n/locales/ru.json | 2 +- src/app/i18n/locales/tw.json | 2 +- src/app/i18n/locales/zh.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/i18n/locales/de.json b/src/app/i18n/locales/de.json index 565c6346c..03e781542 100644 --- a/src/app/i18n/locales/de.json +++ b/src/app/i18n/locales/de.json @@ -361,7 +361,7 @@ "trash": "Papierkorb", "desktop": "Desktop", "referralBanner": { - "message": "Denk daran! Verdiene bis zu 100 €, indem du einen Freund empfiehlst", + "message": "Verdiene 10% Provision für jede Empfehlung!", "cta": "Empfehlen und verdienen" } }, diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index 42f6d5037..e12762466 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -427,7 +427,7 @@ "trash": "Trash", "desktop": "Desktop", "referralBanner": { - "message": "Remember! Earn up to €100 by referring a friend", + "message": "Earn 10% commission for every referral!", "cta": "Refer and Earn" } }, diff --git a/src/app/i18n/locales/es.json b/src/app/i18n/locales/es.json index 610c19f7a..d73f650d3 100644 --- a/src/app/i18n/locales/es.json +++ b/src/app/i18n/locales/es.json @@ -410,7 +410,7 @@ "trash": "Papelera", "desktop": "Escritorio", "referralBanner": { - "message": "¡Recuerda! Gana hasta 100 € recomendando a un amigo", + "message": "¡Gana un 10% de comisión por cada referido!", "cta": "Recomienda y gana" } }, diff --git a/src/app/i18n/locales/fr.json b/src/app/i18n/locales/fr.json index 0b5a92e1f..057e2b7a3 100644 --- a/src/app/i18n/locales/fr.json +++ b/src/app/i18n/locales/fr.json @@ -379,7 +379,7 @@ "trash": "Corbeille", "desktop": "App de bureau", "referralBanner": { - "message": "N'oubliez pas ! Gagnez jusqu'à 100 € en parrainant un ami", + "message": "Gagnez 10% de commission pour chaque parrainage !", "cta": "Parrainez et gagnez" } }, diff --git a/src/app/i18n/locales/it.json b/src/app/i18n/locales/it.json index 496eda4e2..3427b0c64 100644 --- a/src/app/i18n/locales/it.json +++ b/src/app/i18n/locales/it.json @@ -470,7 +470,7 @@ "trash": "Cestino", "desktop": "Applicazione desktop", "referralBanner": { - "message": "Ricorda! Guadagna fino a 100 € invitando un amico", + "message": "Guadagna il 10% di commissione per ogni referral!", "cta": "Invita e guadagna" } }, diff --git a/src/app/i18n/locales/ru.json b/src/app/i18n/locales/ru.json index 388d94c04..cbe0f9d2b 100644 --- a/src/app/i18n/locales/ru.json +++ b/src/app/i18n/locales/ru.json @@ -379,7 +379,7 @@ "trash": "Корзина", "desktop": "Рабочий стол", "referralBanner": { - "message": "Помните! Заработайте до 100 €, пригласив друга", + "message": "Зарабатывайте 10% комиссии за каждого приглашённого!", "cta": "Приглашайте и зарабатывайте" } }, diff --git a/src/app/i18n/locales/tw.json b/src/app/i18n/locales/tw.json index e6d89577c..a26fddd7a 100644 --- a/src/app/i18n/locales/tw.json +++ b/src/app/i18n/locales/tw.json @@ -395,7 +395,7 @@ "trash": "垃圾桶", "desktop": "桌面", "referralBanner": { - "message": "別忘了!推薦朋友最高可賺取 €100", + "message": "每次推薦可賺取10%佣金!", "cta": "推薦賺取" } }, diff --git a/src/app/i18n/locales/zh.json b/src/app/i18n/locales/zh.json index 9f9bee35c..938f058b4 100644 --- a/src/app/i18n/locales/zh.json +++ b/src/app/i18n/locales/zh.json @@ -393,7 +393,7 @@ "trash": "垃圾箱", "desktop": "桌面应用程序", "referralBanner": { - "message": "别忘了!推荐朋友最高可赚取 €100", + "message": "每次推荐可赚取10%佣金!", "cta": "推荐赚取" } }, From a569f33b0efdbc44f15df280bb66465a78259e19 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Wed, 25 Mar 2026 14:29:37 -0400 Subject: [PATCH 4/5] fix: add email verified condition --- src/services/referral.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/referral.service.ts b/src/services/referral.service.ts index 5b57c5868..7d09329d6 100644 --- a/src/services/referral.service.ts +++ b/src/services/referral.service.ts @@ -281,6 +281,8 @@ const changeLanguage = async (language: string): Promise => { }; const boot = async (user: ReferralUser, language?: string): Promise => { + if (!user.emailVerified) return; + try { await loadAndBoot(user, language); } catch (error) { From 661f15d18ae349a15dfdb600b707006968173f93 Mon Sep 17 00:00:00 2001 From: Francis Terrero Date: Thu, 26 Mar 2026 08:59:26 -0400 Subject: [PATCH 5/5] feat: use Cello custom launcher label for referral button and allow boot without email verification --- src/services/referral.service.test.ts | 20 ++++++++++++++++++ src/services/referral.service.ts | 9 ++++++-- .../Home/components/NavbarGlobalSearch.tsx | 21 ++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/services/referral.service.test.ts b/src/services/referral.service.test.ts index 46794d19f..4adad77e6 100644 --- a/src/services/referral.service.test.ts +++ b/src/services/referral.service.test.ts @@ -307,6 +307,26 @@ describe('referralService', () => { }); }); + describe('getCustomLauncherLabel', () => { + it('when the Cello widget is loaded, then the custom launcher label is returned', async () => { + const mockCello = vi.fn().mockResolvedValue({ customLauncher: 'Give 85% off' }); + globalThis.Cello = mockCello; + + const label = await referralService.getCustomLauncherLabel(); + + expect(mockCello).toHaveBeenCalledWith('getLabels'); + expect(label).toBe('Give 85% off'); + }); + + it('when the Cello widget is not available, then undefined is returned', async () => { + delete globalThis.Cello; + + const label = await referralService.getCustomLauncherLabel(); + + expect(label).toBeUndefined(); + }); + }); + describe('changeLanguage', () => { it('when the referral widget is loaded, then the language is updated', async () => { const mockCello = vi.fn().mockResolvedValue(undefined); diff --git a/src/services/referral.service.ts b/src/services/referral.service.ts index 7d09329d6..98e9891c9 100644 --- a/src/services/referral.service.ts +++ b/src/services/referral.service.ts @@ -275,14 +275,18 @@ const onTrigger = (listener: () => void): (() => void) => { return () => triggerListeners.delete(listener); }; +const getCustomLauncherLabel = async (): Promise => { + if (!globalThis.Cello) return undefined; + const labels = (await globalThis.Cello('getLabels')) as { customLauncher?: string }; + return labels.customLauncher; +}; + const changeLanguage = async (language: string): Promise => { if (!globalThis.Cello) return; await globalThis.Cello('changeLanguage', language); }; const boot = async (user: ReferralUser, language?: string): Promise => { - if (!user.emailVerified) return; - try { await loadAndBoot(user, language); } catch (error) { @@ -308,6 +312,7 @@ const isEligibleForReferral = async (accountCreatedAt?: Date): Promise const referralService = { boot, + getCustomLauncherLabel, changeLanguage, openPanel, captureUcc, diff --git a/src/views/Home/components/NavbarGlobalSearch.tsx b/src/views/Home/components/NavbarGlobalSearch.tsx index 96ed4f828..d202e3322 100644 --- a/src/views/Home/components/NavbarGlobalSearch.tsx +++ b/src/views/Home/components/NavbarGlobalSearch.tsx @@ -115,6 +115,22 @@ const Navbar = (props: NavbarProps) => { const doneTypingInterval = 200; const isReferralEligible = useAppSelector((state: RootState) => state.referrals.isEligible); + const [customLauncherLabel, setCustomLauncherLabel] = useState(''); + + useEffect(() => { + const fetchLabel = async () => { + const label = await referralService.getCustomLauncherLabel(); + if (label) { + setCustomLauncherLabel(label); + } + }; + + if (isReferralEligible) { + fetchLabel(); + } + }, [isReferralEligible]); + + const referralLauncherLabel = customLauncherLabel || translate('views.account.popover.earnReferral'); const isGlobalSearch = useAppSelector((state: RootState) => state.ui.isGlobalSearch); const selectedWorkspace = useAppSelector(workspacesSelectors.getSelectedWorkspace); @@ -404,6 +420,7 @@ const Navbar = (props: NavbarProps) => {