Skip to content

Commit

Permalink
feat: firefox extension permission ui (#3864)
Browse files Browse the repository at this point in the history
  • Loading branch information
sshanzel authored Nov 27, 2024
1 parent f08baf3 commit 1693d38
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 112 deletions.
7 changes: 6 additions & 1 deletion packages/extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,12 @@ browser.runtime.onInstalled.addListener(async (details) => {
}

if (details.reason === 'install') {
browser.tabs.create({ url: install, active: true });
const firefoxUrl = browser.runtime.getURL('index.html?source=install');
const isFirefox = process.env.TARGET_BROWSER === 'firefox';
browser.tabs.create({
url: isFirefox ? firefoxUrl : install,
active: true,
});
}
});

Expand Down
62 changes: 32 additions & 30 deletions packages/extension/src/newtab/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { useToastNotification } from '@dailydotdev/shared/src/hooks/useToastNoti
import { useError } from '@dailydotdev/shared/src/hooks/useError';
import { BootApp } from '@dailydotdev/shared/src/lib/boot';
import { useNotificationContext } from '@dailydotdev/shared/src/contexts/NotificationsContext';
import { useLazyModal } from '@dailydotdev/shared/src/hooks/useLazyModal';
import { defaultQueryClientConfig } from '@dailydotdev/shared/src/lib/query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useWebVitals } from '@dailydotdev/shared/src/hooks/useWebVitals';
Expand All @@ -37,20 +36,24 @@ import ExtensionPermissionsPrompt from '@dailydotdev/shared/src/components/Exten
import { useExtensionContext } from '@dailydotdev/shared/src/contexts/ExtensionContext';
import { useConsoleLogo } from '@dailydotdev/shared/src/hooks/useConsoleLogo';
import { DndContextProvider } from '@dailydotdev/shared/src/contexts/DndContext';
import usePersistentState from '@dailydotdev/shared/src/hooks/usePersistentState';
import { LazyModal } from '@dailydotdev/shared/src/components/modals/common/types';
import { structuredCloneJsonPolyfill } from '@dailydotdev/shared/src/lib/structuredClone';
import usePersistentContext from '@dailydotdev/shared/src/hooks/usePersistentContext';
import {
FIREFOX_ACCEPTED_PERMISSION,
FirefoxPermissionType,
} from '@dailydotdev/shared/src/lib/cookie';
import { ExtensionContextProvider } from '../contexts/ExtensionContext';
import CustomRouter from '../lib/CustomRouter';
import { version } from '../../package.json';
import MainFeedPage from './MainFeedPage';
import { BootDataProvider } from '../../../shared/src/contexts/BootProvider';
import { getContentScriptPermissionAndRegister } from '../lib/extensionScripts';
import { useContentScriptStatus } from '../../../shared/src/hooks';
import { FirefoxPermission } from '../permission/FirefoxPermission';
import { FirefoxPermissionDeclined } from '../permission/FirefoxPermissionDeclined';

structuredCloneJsonPolyfill();

const isFirefoxExtension = process.env.TARGET_BROWSER === 'firefox';
const DEFAULT_TAB_TITLE = 'New Tab';
const router = new CustomRouter();
const queryClient = new QueryClient(defaultQueryClientConfig);
Expand All @@ -67,33 +70,13 @@ Modal.defaultStyles = {};
const getRedirectUri = () => browser.runtime.getURL('index.html');
function InternalApp(): ReactElement {
useError();
useLazyModal();
useWebVitals();
const { openModal } = useLazyModal();
const { setCurrentPage, currentPage, promptUninstallExtension } =
useExtensionContext();
const [analyticsConsent, setAnalyticsConsent] = usePersistentState(
'consent',
false,
isFirefoxExtension ? null : true,
);

const analyticsConsentPrompt = useCallback(() => {
openModal({
type: LazyModal.FirefoxPrivacy,
props: {
onAccept: () => setAnalyticsConsent(true),
onDecline: () => promptUninstallExtension(),
},
});
}, [openModal, promptUninstallExtension, setAnalyticsConsent]);

useEffect(() => {
if (analyticsConsent === null) {
analyticsConsentPrompt();
}
}, [analyticsConsent, analyticsConsentPrompt]);

const { setCurrentPage, currentPage } = useExtensionContext();
const [firefoxPermission, setFirefoxPermission, isFetched] =
usePersistentContext<FirefoxPermissionType | null>(
FIREFOX_ACCEPTED_PERMISSION,
null,
);
const { unreadCount } = useNotificationContext();
const { closeLogin, shouldShowLogin, loginState } = useContext(AuthContext);
const { contentScriptGranted } = useContentScriptStatus();
Expand All @@ -106,6 +89,7 @@ function InternalApp(): ReactElement {
const isPageReady =
(growthbook?.ready && router?.isReady && isAuthReady) || isTesting;
const shouldRedirectOnboarding = !user && isPageReady && !isTesting;
const isFirefoxExtension = process.env.TARGET_BROWSER === 'firefox';

useEffect(() => {
if (routeChangedCallbackRef.current && isPageReady) {
Expand Down Expand Up @@ -135,6 +119,24 @@ function InternalApp(): ReactElement {
: DEFAULT_TAB_TITLE;
}, [unreadCount]);

if (isFirefoxExtension) {
if (!isFetched) {
return null;
}

if (firefoxPermission === FirefoxPermissionType.Declined) {
return (
<FirefoxPermissionDeclined
onGoBack={() => setFirefoxPermission(null)}
/>
);
}

if (firefoxPermission !== FirefoxPermissionType.Accepted) {
return <FirefoxPermission onUpdate={setFirefoxPermission} />;
}
}

if (!hostGranted) {
return isCheckingHostPermissions ? null : <ExtensionPermissionsPrompt />;
}
Expand Down
101 changes: 101 additions & 0 deletions packages/extension/src/permission/FirefoxPermission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { ReactElement } from 'react';
import DailyDevLogo from '@dailydotdev/shared/src/components/Logo';
import {
Typography,
TypographyColor,
TypographyTag,
TypographyType,
} from '@dailydotdev/shared/src/components/typography/Typography';
import {
Button,
ButtonVariant,
} from '@dailydotdev/shared/src/components/buttons/Button';
import { VIcon } from '@dailydotdev/shared/src/components/icons';
import { cloudinaryFeedBgLaptop } from '@dailydotdev/shared/src/lib/image';
import {
onboardingUrl,
privacyPolicy,
} from '@dailydotdev/shared/src/lib/constants';
import { useRouter } from 'next/router';
import { FirefoxPermissionType } from '@dailydotdev/shared/src/lib/cookie';
import { anchorDefaultRel } from '@dailydotdev/shared/src/lib/strings';
import { FirefoxPermissionItem } from './FirefoxPermissionItem';
import { FirefoxPermissionContainer } from './common';

interface FirefoxPermissionProps {
onUpdate(permission: FirefoxPermissionType): Promise<void>;
}

export function FirefoxPermission({
onUpdate,
}: FirefoxPermissionProps): ReactElement {
const router = useRouter();

const onAccept = async () => {
await onUpdate(FirefoxPermissionType.Accepted);
await router.push(onboardingUrl);
};

return (
<FirefoxPermissionContainer>
<img
src={cloudinaryFeedBgLaptop}
alt="a glowing background"
className="pointer-events-none absolute -top-10 -z-1 select-none"
/>
<span className="absolute top-0 py-6">
<DailyDevLogo />
</span>
<div className="flex w-full max-w-[33rem] flex-col gap-2 rounded-10 bg-surface-float p-4">
<Typography tag={TypographyTag.H1} type={TypographyType.Title3} bold>
Additional permissions needed
</Typography>
<Typography
type={TypographyType.Callout}
color={TypographyColor.Tertiary}
>
Ready to go? 🟢 Give us the green signal by accepting the permissions
below. Enhance your daily.dev experience with personalized content and
connect with fellow developers.
</Typography>
<FirefoxPermissionItem
className="mt-2"
title="Ads pixel tracking"
description="Enable pixel tracking for fewer, non-intrusive ads, helping us in keeping the experience free and relevant."
icon="📢"
/>
<FirefoxPermissionItem
title="Analytics data"
description="Allow us to capture analytics data to understand your preferences and personalize content for you."
icon="📊"
/>
<Typography
tag={TypographyTag.Link}
className="my-4 text-center"
color={TypographyColor.Link}
type={TypographyType.Subhead}
href={privacyPolicy}
target="_blank"
rel={anchorDefaultRel}
>
Full permission document
</Typography>
<Button
className="w-full"
icon={<VIcon />}
variant={ButtonVariant.Primary}
onClick={onAccept}
>
Accept
</Button>
<Button
className="mt-1 w-full"
variant={ButtonVariant.Float}
onClick={() => onUpdate(FirefoxPermissionType.Declined)}
>
Decline
</Button>
</div>
</FirefoxPermissionContainer>
);
}
70 changes: 70 additions & 0 deletions packages/extension/src/permission/FirefoxPermissionDeclined.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { ReactElement } from 'react';
import {
cloudinaryFeedBgLaptop,
cloudinaryGenericErrorDark,
} from '@dailydotdev/shared/src/lib/image';
import {
Typography,
TypographyColor,
TypographyTag,
TypographyType,
} from '@dailydotdev/shared/src/components/typography/Typography';
import { webappUrl } from '@dailydotdev/shared/src/lib/constants';
import { anchorDefaultRel } from '@dailydotdev/shared/src/lib/strings';
import { FirefoxPermissionContainer } from './common';

interface FirefoxPermissionDeclinedProps {
onGoBack(): void;
}

export function FirefoxPermissionDeclined({
onGoBack,
}: FirefoxPermissionDeclinedProps): ReactElement {
return (
<FirefoxPermissionContainer>
<img
src={cloudinaryFeedBgLaptop}
alt="a glowing background"
className="pointer-events-none absolute -top-10 -z-1 select-none"
/>
<div className="flex w-full max-w-[35rem] flex-col items-center gap-4">
<span className="h-36 max-w-[16.25rem]">
<img src={cloudinaryGenericErrorDark} alt="You declined (FML)" />
</span>
<Typography
tag={TypographyTag.H1}
type={TypographyType.LargeTitle}
bold
>
You declined.
</Typography>
<Typography
color={TypographyColor.Secondary}
tag={TypographyTag.H1}
type={TypographyType.Body}
className="text-center"
>
We understand your choice, but just so you know, we’ll need those
permissions to deliver any content. Without them, it’s like trying to
code without a keyboard!
</Typography>
<Typography
color={TypographyColor.Secondary}
tag={TypographyTag.H1}
type={TypographyType.Body}
className="text-center"
>
You can{' '}
<button type="button" className="text-text-link" onClick={onGoBack}>
go back
</button>{' '}
and accept the required permissions or use our{' '}
<a className="text-text-link" href={webappUrl} rel={anchorDefaultRel}>
web app
</a>{' '}
instead.
</Typography>
</div>
</FirefoxPermissionContainer>
);
}
48 changes: 48 additions & 0 deletions packages/extension/src/permission/FirefoxPermissionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { ReactElement, ReactNode } from 'react';
import classNames from 'classnames';
import {
Typography,
TypographyColor,
TypographyTag,
TypographyType,
} from '@dailydotdev/shared/src/components/typography/Typography';

interface FirefoxPermissionItemProps {
title: string;
description: string;
icon: ReactNode;
className?: string;
}

export function FirefoxPermissionItem({
title,
description,
icon,
className,
}: FirefoxPermissionItemProps): ReactElement {
return (
<div
className={classNames(
'flex flex-row gap-3 border-b border-border-subtlest-tertiary px-4 py-2',
className,
)}
>
<Typography type={TypographyType.LargeTitle}>{icon}</Typography>
<div className="flex flex-1 flex-col gap-2 break-words">
<Typography
tag={TypographyTag.H3}
type={TypographyType.Callout}
color={TypographyColor.Secondary}
>
{title}
</Typography>
<Typography
type={TypographyType.Subhead}
color={TypographyColor.Tertiary}
>
{description}
</Typography>
</div>
</div>
);
}
6 changes: 6 additions & 0 deletions packages/extension/src/permission/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import classed from '@dailydotdev/shared/src/lib/classed';

export const FirefoxPermissionContainer = classed(
'div',
'fixed inset-0 flex h-screen w-screen flex-1 flex-col justify-center items-center',
);
8 changes: 0 additions & 8 deletions packages/shared/src/components/modals/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,6 @@ const PrivilegedMemberModal = dynamic(
),
);

const FirefoxPrivacyModal = dynamic(
() =>
import(
/* webpackChunkName: "firefoxPrivacyModal" */ './firefoxPrivacy/FirefoxPrivacyModal'
),
);

const BookmarkReminderModal = dynamic(
() =>
import(
Expand Down Expand Up @@ -213,7 +206,6 @@ export const modals = {
[LazyModal.UserSettings]: UserSettingsModal,
[LazyModal.Share]: ShareModal,
[LazyModal.PrivilegedMembers]: PrivilegedMemberModal,
[LazyModal.FirefoxPrivacy]: FirefoxPrivacyModal,
[LazyModal.BookmarkReminder]: BookmarkReminderModal,
[LazyModal.RecoverStreak]: StreakRecoverModal,
[LazyModal.SlackIntegration]: SlackIntegrationModal,
Expand Down
1 change: 0 additions & 1 deletion packages/shared/src/components/modals/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export enum LazyModal {
UserSettings = 'userSettings',
Share = 'share',
PrivilegedMembers = 'privilegedMembers',
FirefoxPrivacy = 'firefoxPrivacy',
BookmarkReminder = 'bookmarkReminder',
SlackIntegration = 'slackIntegration',
ReportSource = 'reportSource',
Expand Down
Loading

0 comments on commit 1693d38

Please sign in to comment.