diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 7910ca37a7..d9da6f0976 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -89,7 +89,14 @@ import SplashScreen from '@/components/ui/SplashScreen.vue' import WindowControls from '@/components/ui/WindowControls.vue' import { useCheckDisableMouseover } from '@/composables/macCssFix.js' import { config } from '@/config' -import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js' +import { + ads_consent_listener, + get_ads_consent_required, + hide_ads_window, + init_ads_window, + perform_ads_consent_action, + show_ads_window, +} from '@/helpers/ads.js' import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics' import { check_reachable } from '@/helpers/auth.js' import { get_user, get_version } from '@/helpers/cache.js' @@ -184,6 +191,8 @@ const { handleError, addNotification } = notificationManager const popupNotificationManager = new AppPopupNotificationManager() providePopupNotificationManager(popupNotificationManager) const { addPopupNotification } = popupNotificationManager +let adsConsentPopupId = null +let unlistenAdsConsent const appVersion = getVersion() const tauriApiClient = new TauriModrinthClient({ @@ -213,9 +222,18 @@ const { data: authenticatedModrinthUser } = useQuery({ enabled: () => !!credentials.value?.session, retry: false, }) +const hasPlus = computed( + () => + !!credentials.value?.user && + (hasMidasBadge(credentials.value.user) || + hasActivePride26Midas(authenticatedModrinthUser.value?.campaigns?.pride_26)), +) +const showAd = computed( + () => sidebarVisible.value && !hasPlus.value && credentials.value !== undefined, +) providePageContext({ hierarchicalSidebarAvailable: ref(true), - showAds: ref(false), + showAds: showAd, floatingActionBarOffsets: { left: ref(APP_LEFT_NAV_WIDTH), right: computed(() => (sidebarVisible.value ? `${APP_SIDEBAR_WIDTH}px` : '0px')), @@ -295,6 +313,12 @@ const authUnreachable = computed(() => { onMounted(async () => { await useCheckDisableMouseover() + try { + unlistenAdsConsent = await ads_consent_listener(handleAdsConsentRequired) + handleAdsConsentRequired(await get_ads_consent_required()) + } catch (error) { + handleError(error) + } document.querySelector('body').addEventListener('click', handleClick) document.querySelector('body').addEventListener('auxclick', handleAuxClick) @@ -308,6 +332,7 @@ onUnmounted(async () => { unsubscribeSidebarToggle() clearDelayedUpdatePopup() + await unlistenAdsConsent?.() await unlistenUpdateDownload?.() }) @@ -332,8 +357,77 @@ const messages = defineMessages({ defaultMessage: 'Minecraft authentication servers may be down right now. Check your internet connection and try again later.', }, + adsConsentTitle: { + id: 'app.ads-consent.title', + defaultMessage: 'Your privacy and how ads support Modrinth', + }, + adsConsentBody: { + id: 'app.ads-consent.body', + defaultMessage: + 'Ads make Modrinth possible and fund creator payouts. Our partners may store or access cookies in the app to personalize ads and measure performance.', + }, + adsConsentManage: { + id: 'app.ads-consent.manage', + defaultMessage: 'Manage preferences', + }, + adsConsentReject: { + id: 'app.ads-consent.reject', + defaultMessage: 'Reject all', + }, + adsConsentAccept: { + id: 'app.ads-consent.accept', + defaultMessage: 'Accept all', + }, }) +function handleAdsConsentRequired(required) { + if (!required) { + if (adsConsentPopupId !== null) { + popupNotificationManager.removeNotification(adsConsentPopupId) + adsConsentPopupId = null + } + return + } + + if ( + adsConsentPopupId !== null && + popupNotificationManager.getNotifications().some((item) => item.id === adsConsentPopupId) + ) { + return + } + + const notification = addPopupNotification({ + title: formatMessage(messages.adsConsentTitle), + text: formatMessage(messages.adsConsentBody), + type: 'info', + hideIcon: true, + autoCloseMs: null, + dismissible: false, + buttons: [ + { + label: formatMessage(messages.adsConsentManage), + action: () => perform_ads_consent_action('manage').catch(handleError), + color: 'standard', + keepOpen: true, + }, + { + label: formatMessage(messages.adsConsentReject), + action: () => perform_ads_consent_action('reject').catch(handleError), + color: 'brand', + keepOpen: true, + }, + { + label: formatMessage(messages.adsConsentAccept), + action: () => perform_ads_consent_action('accept').catch(handleError), + color: 'brand', + keepOpen: true, + }, + ], + }) + + adsConsentPopupId = notification.id +} + async function setupApp() { const { native_decorations, @@ -403,7 +497,7 @@ async function setupApp() { addNotification({ title: 'Warning', text: e.message, - type: 'warn', + type: 'warning', }), ) @@ -703,17 +797,6 @@ async function logOut() { await fetchCredentials() } -const hasPlus = computed( - () => - !!credentials.value?.user && - (hasMidasBadge(credentials.value.user) || - hasActivePride26Midas(authenticatedModrinthUser.value?.campaigns?.pride_26)), -) - -const showAd = computed( - () => sidebarVisible.value && !hasPlus.value && credentials.value !== undefined, -) - async function fetchIntercomToken() { const creds = await getCreds() if (!creds?.session) { diff --git a/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue b/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue index 3482de94eb..27bc9215ed 100644 --- a/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue +++ b/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue @@ -1,12 +1,44 @@