diff --git a/packages/design-system/src/components/landingPage/index.tsx b/packages/design-system/src/components/landingPage/index.tsx
index aac96d9c7..d79a4e4cf 100644
--- a/packages/design-system/src/components/landingPage/index.tsx
+++ b/packages/design-system/src/components/landingPage/index.tsx
@@ -37,6 +37,7 @@ interface LandingPageProps {
contentPanel?: ReactNode;
iframeBorderClass?: string;
extraClasses?: string;
+ showQuickLinks?: boolean;
}
const LandingPage = ({
@@ -47,12 +48,13 @@ const LandingPage = ({
children,
extraClasses,
contentPanel,
+ showQuickLinks = true,
}: LandingPageProps) => {
const [loading, setLoading] = useState(iframeSrc ? true : false);
const [open, setOpen] = useState(true);
return (
-
+
{loading &&
}
)}
{psInfoKey &&
}
- {contentPanel &&
{contentPanel}
}
+ {contentPanel && <>{contentPanel}>}
{children && (
@@ -114,7 +116,7 @@ const LandingPage = ({
)}
-
+ {showQuickLinks && }
);
diff --git a/packages/design-system/src/components/sidebar/collapsedSidebar.tsx b/packages/design-system/src/components/sidebar/collapsedSidebar.tsx
index 18f0bf046..3813151ea 100644
--- a/packages/design-system/src/components/sidebar/collapsedSidebar.tsx
+++ b/packages/design-system/src/components/sidebar/collapsedSidebar.tsx
@@ -23,7 +23,7 @@ import classNames from 'classnames';
* Internal dependencies.
*/
import { MenuOpenIcon } from '../../icons';
-import { useSidebar } from './useSidebar';
+import { useSidebar, type SidebarItemValue } from './useSidebar';
const CollapsedSidebar = () => {
const {
@@ -31,11 +31,15 @@ const CollapsedSidebar = () => {
updateSelectedItemKey,
currentSelectedItemKey,
toggleSidebarCollapse,
+ sidebarItems,
+ selectedItemKey,
} = useSidebar(({ state, actions }) => ({
collapsedSidebarItems: state.collapsedSidebarItems,
updateSelectedItemKey: actions.updateSelectedItemKey,
currentSelectedItemKey: state.currentItemKey,
toggleSidebarCollapse: actions.toggleSidebarCollapse,
+ sidebarItems: state.sidebarItems,
+ selectedItemKey: state.selectedItemKey,
}));
const handleFooterElementClick = useCallback(
@@ -54,13 +58,48 @@ const CollapsedSidebar = () => {
'flex flex-col justify-between items-center p-2 w-full h-full'
)}
>
-
-
-
+
+
+
+
+ {Object.keys(sidebarItems).map((itemKey) => {
+ if (
+ Object.keys(collapsedSidebarItems?.footerElements || {}).includes(
+ itemKey
+ )
+ ) {
+ return null;
+ }
+
+ const sidebarItem = sidebarItems[itemKey] as SidebarItemValue;
+ const Icon = sidebarItem.icon ? sidebarItem.icon.Element : null;
+ const props = sidebarItem?.icon?.props || {};
+ const title =
+ typeof sidebarItem.title === 'function'
+ ? sidebarItem.title()
+ : sidebarItem.title;
+ const isCurrent = itemKey === selectedItemKey;
+ const buttonClassNames = classNames({
+ 'cursor-auto opacity-70 dark:opacity-40': isCurrent,
+ 'hover:opacity-60': !isCurrent,
+ });
+
+ return (
+ updateSelectedItemKey(itemKey)}
+ >
+ {Icon && }
+
+ );
+ })}
+
{Object.keys(collapsedSidebarItems?.footerElements || {}).map((key) => {
const Icon = collapsedSidebarItems?.footerElements[key].icon.Element;
@@ -71,21 +110,24 @@ const CollapsedSidebar = () => {
const props = collapsedSidebarItems?.footerElements[key].icon.props;
const title = collapsedSidebarItems?.footerElements[key].title;
+ const isCurrent = key === currentSelectedItemKey;
return (
handleFooterElementClick(e, key)}
>
-
+
);
})}
diff --git a/packages/design-system/src/components/sidebar/useSidebar/constants.ts b/packages/design-system/src/components/sidebar/useSidebar/constants.ts
index 9097882ec..4b1507c6e 100644
--- a/packages/design-system/src/components/sidebar/useSidebar/constants.ts
+++ b/packages/design-system/src/components/sidebar/useSidebar/constants.ts
@@ -15,6 +15,7 @@
*/
export enum SIDEBAR_ITEMS_KEYS {
+ DASHBOARD = 'dashboard',
COOKIES = 'cookies',
COOKIES_WITH_ISSUES = 'cookie-issues',
PRIVACY_SANDBOX = 'privacy-sandbox',
diff --git a/packages/design-system/src/icons/dashboard-white.svg b/packages/design-system/src/icons/dashboard-white.svg
new file mode 100644
index 000000000..8eda7fc0b
--- /dev/null
+++ b/packages/design-system/src/icons/dashboard-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/design-system/src/icons/dashboard.svg b/packages/design-system/src/icons/dashboard.svg
new file mode 100644
index 000000000..ee7f4eb98
--- /dev/null
+++ b/packages/design-system/src/icons/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/design-system/src/icons/external-link-black.svg b/packages/design-system/src/icons/external-link-black.svg
new file mode 100644
index 000000000..43209f757
--- /dev/null
+++ b/packages/design-system/src/icons/external-link-black.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/design-system/src/icons/index.tsx b/packages/design-system/src/icons/index.tsx
index 2abd7058b..2f4ba9b83 100644
--- a/packages/design-system/src/icons/index.tsx
+++ b/packages/design-system/src/icons/index.tsx
@@ -73,6 +73,9 @@ export { default as GreenTick } from './green-tick-icon.svg';
export { default as ChevronDown } from './chevron-down.svg';
export { default as WarningBare } from './warning.svg';
export { default as GroupsIcon } from './groups.svg';
+export { default as ExternalLinkBlack } from './external-link-black.svg';
+export { default as DashboardIcon } from './dashboard.svg';
+export { default as DashboardIconWhite } from './dashboard-white.svg';
export { default as WikiIcon } from './wiki.svg';
export { default as WikiIconWhite } from './wiki-white.svg';
export { default as ExternalLinkIcon } from './external-link.svg';
diff --git a/packages/design-system/src/icons/privacy-sandbox-colored.svg b/packages/design-system/src/icons/privacy-sandbox-colored.svg
index 52a3c540d..c80948657 100644
--- a/packages/design-system/src/icons/privacy-sandbox-colored.svg
+++ b/packages/design-system/src/icons/privacy-sandbox-colored.svg
@@ -1,6 +1,6 @@
-
+
@@ -13,7 +13,7 @@
-
+
diff --git a/packages/extension/src/view/devtools/app.tsx b/packages/extension/src/view/devtools/app.tsx
index 4c9a6ec2e..bfb7c0c26 100644
--- a/packages/extension/src/view/devtools/app.tsx
+++ b/packages/extension/src/view/devtools/app.tsx
@@ -60,9 +60,12 @@ const App: React.FC = () => {
}
const data = await chrome.storage.session.get();
+ const syncData = await chrome.storage.sync.get();
if (data?.['selectedSidebarItem#' + tabId]) {
setDefaultSelectedItemKey(data['selectedSidebarItem#' + tabId]);
+ } else if (syncData?.psLandingPageViewed) {
+ setDefaultSelectedItemKey(SIDEBAR_ITEMS_KEYS.DASHBOARD);
}
if (data?.['sidebarCollapsedState#' + tabId]) {
diff --git a/packages/extension/src/view/devtools/components/cookies/index.tsx b/packages/extension/src/view/devtools/components/cookies/index.tsx
index f8acb3372..dbcfb002b 100644
--- a/packages/extension/src/view/devtools/components/cookies/index.tsx
+++ b/packages/extension/src/view/devtools/components/cookies/index.tsx
@@ -29,6 +29,7 @@ import { I18n } from '@google-psat/i18n';
* Internal dependencies.
*/
import { useCookie, useSettings } from '../../stateProviders';
+import useCanShowAnalyzeTabButton from '../../hooks/useCanShowAnalyzeTabButton';
import CookiesListing from './cookiesListing';
import AssembledCookiesLanding from './cookieLanding';
@@ -50,6 +51,7 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => {
tabToRead: state.tabToRead,
changeListeningToThisTab: actions.changeListeningToThisTab,
}));
+ const canShowAnalyzeTabButton = useCanShowAnalyzeTabButton();
const { allowedNumberOfTabs } = useSettings(({ state }) => ({
allowedNumberOfTabs: state.allowedNumberOfTabs,
@@ -70,13 +72,7 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => {
);
}
- if (
- (tabToRead &&
- isCurrentTabBeingListenedTo &&
- allowedNumberOfTabs &&
- allowedNumberOfTabs === 'single') ||
- (allowedNumberOfTabs && allowedNumberOfTabs === 'unlimited')
- ) {
+ if (canShowAnalyzeTabButton) {
return (
{
+ const navigateTo = useSidebar(({ actions }) => actions.updateSelectedItemKey);
+ const { tabFrames } = useCookie(({ state }) => ({
+ tabFrames: state.tabFrames,
+ }));
+ const canShowAnalyzeTabButton = useCanShowAnalyzeTabButton();
+
+ const handleButtonClick = useCallback(
+ (event: React.MouseEvent, sidebarKey: string) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const firstFrame =
+ Object.keys(tabFrames || {})?.[0] || SIDEBAR_ITEMS_KEYS.COOKIES;
+
+ navigateTo(sidebarKey === 'FIRST_COOKIE_TABLE' ? firstFrame : sidebarKey);
+ },
+ [navigateTo, tabFrames]
+ );
+
+ useEffect(() => {
+ if (canShowAnalyzeTabButton && FEATURE_LIST[0].buttons.length === 1) {
+ FEATURE_LIST[0].buttons.push({
+ name: 'Cookies Table',
+ sidebarKey: 'FIRST_COOKIE_TABLE',
+ });
+ }
+ }, [canShowAnalyzeTabButton]);
+
+ return (
+
+
+
+ Pinned
+
+ {PINNED_ITEMS.map((item) => {
+ const Icon = item.icon;
+
+ return (
+
navigateTo(item.sidebarKey)}
+ >
+
+ {item.name}
+
+ );
+ })}
+
+
+
+ Features
+
+ {FEATURE_LIST.map((item) => {
+ const Icon = item.icon;
+ const headingClasses = classNames(
+ 'text-sm',
+ item?.colorClasses?.heading ? item?.colorClasses?.heading : ''
+ );
+
+ return (
+
navigateTo(item.sidebarKey)}
+ >
+
+
+
{item.name}
+
+
{item.description}
+
+ {item.buttons &&
+ item.buttons.map((button) => (
+
+ handleButtonClick(event, button.sidebarKey)
+ }
+ >
+ {button.name}
+
+ ))}
+
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default ContentPanel;
diff --git a/packages/extension/src/view/devtools/components/dashboard/index.tsx b/packages/extension/src/view/devtools/components/dashboard/index.tsx
new file mode 100644
index 000000000..17ac6819c
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/dashboard/index.tsx
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+import { LandingPage } from '@google-psat/design-system';
+
+/**
+ * Internal dependencies.
+ */
+import ContentPanel from './contentPanel';
+
+const Dashboard = () => {
+ return (
+
}
+ showQuickLinks={false}
+ />
+ );
+};
+
+export default Dashboard;
diff --git a/packages/extension/src/view/devtools/components/index.ts b/packages/extension/src/view/devtools/components/index.ts
index 6e8d45f6c..38e097d10 100644
--- a/packages/extension/src/view/devtools/components/index.ts
+++ b/packages/extension/src/view/devtools/components/index.ts
@@ -21,3 +21,4 @@ export { default as PrivacySandbox } from './privacySandbox';
export { default as Wiki } from './wiki';
export { default as Settings } from './settings';
export { default as Layout } from './layout';
+export { default as Dashboard } from './dashboard';
diff --git a/packages/extension/src/view/devtools/components/layout.tsx b/packages/extension/src/view/devtools/components/layout.tsx
index 7244f5170..b56d8d089 100644
--- a/packages/extension/src/view/devtools/components/layout.tsx
+++ b/packages/extension/src/view/devtools/components/layout.tsx
@@ -257,7 +257,7 @@ const Layout = ({ setSidebarData }: LayoutProps) => {
{settingsChanged && (
-
+
{
+ const navigateTo = useSidebar(({ actions }) => actions.updateSelectedItemKey);
+
+ return (
+
+
+
+
+
+ Protecting your privacy online
+
+
+ The Privacy Sandbox initiative aims to create technologies that both
+ protect people's privacy online and give companies and
+ developers tools to build thriving digital businesses. The Privacy
+ Sandbox reduces cross-site and cross-app tracking while helping to
+ keep online content and services free for all.
+
+
+
+ Learn About Privacy Sandbox
+
+
+
navigateTo(SIDEBAR_ITEMS_KEYS.DASHBOARD)}
+ className="bg-cultured-grey text-raisin-black py-2 px-9 rounded border border-dark-grey text-base hover:bg-light-gray hover:border-american-silver"
+ >
+ Dashboard
+
+
+
+
+
+ );
+};
+
+export default ContentPanel;
diff --git a/packages/extension/src/view/devtools/components/privacySandbox/index.tsx b/packages/extension/src/view/devtools/components/privacySandbox/index.tsx
index f8188f316..dbbe4f405 100644
--- a/packages/extension/src/view/devtools/components/privacySandbox/index.tsx
+++ b/packages/extension/src/view/devtools/components/privacySandbox/index.tsx
@@ -16,18 +16,26 @@
/**
* External dependencies.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import { LandingPage } from '@google-psat/design-system';
-const PrivacySandbox = () => (
-
-
-
-);
+/**
+ * Internal dependencies.
+ */
+import ContentPanel from './contentPanel';
+
+const PrivacySandbox = () => {
+ useEffect(() => {
+ (async () => {
+ await chrome.storage.sync.set({
+ psLandingPageViewed: true,
+ });
+ })();
+ }, []);
+
+ return (
+ } />
+ );
+};
export default PrivacySandbox;
diff --git a/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts b/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts
new file mode 100644
index 000000000..785c0ca53
--- /dev/null
+++ b/packages/extension/src/view/devtools/hooks/useCanShowAnalyzeTabButton.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Internal dependencies.
+ */
+import { useCookie, useSettings } from '../stateProviders';
+
+const useCanShowAnalyzeTabButton = () => {
+ const { isCurrentTabBeingListenedTo, tabToRead } = useCookie(({ state }) => ({
+ isCurrentTabBeingListenedTo: state.isCurrentTabBeingListenedTo,
+ tabToRead: state.tabToRead,
+ }));
+
+ const { allowedNumberOfTabs } = useSettings(({ state }) => ({
+ allowedNumberOfTabs: state.allowedNumberOfTabs,
+ }));
+
+ return (
+ (tabToRead &&
+ isCurrentTabBeingListenedTo &&
+ allowedNumberOfTabs &&
+ allowedNumberOfTabs === 'single') ||
+ (allowedNumberOfTabs && allowedNumberOfTabs === 'unlimited')
+ );
+};
+
+export default useCanShowAnalyzeTabButton;
diff --git a/packages/extension/src/view/devtools/tabs.ts b/packages/extension/src/view/devtools/tabs.ts
index 93725407b..07c8ef9c2 100644
--- a/packages/extension/src/view/devtools/tabs.ts
+++ b/packages/extension/src/view/devtools/tabs.ts
@@ -42,6 +42,8 @@ import {
type SidebarItems,
SIDEBAR_ITEMS_KEYS,
GroupsIcon,
+ DashboardIcon,
+ DashboardIconWhite,
WikiIcon,
WikiIconWhite,
type CollapsedSidebarItems,
@@ -71,6 +73,7 @@ import {
Settings,
ProtectedAudience,
PrivateAggregation,
+ Dashboard,
} from './components';
const TABS: SidebarItems = {
@@ -258,6 +261,20 @@ const TABS: SidebarItems = {
},
},
},
+ [SIDEBAR_ITEMS_KEYS.DASHBOARD]: {
+ title: () => 'Dashboard',
+ panel: {
+ Element: Dashboard,
+ },
+ icon: {
+ Element: DashboardIcon,
+ },
+ selectedIcon: {
+ Element: DashboardIconWhite,
+ },
+ dropdownOpen: false,
+ children: {},
+ },
[SIDEBAR_ITEMS_KEYS.WIKI]: {
title: () => I18n.getMessage('wiki'),
panel: {
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
index a48825793..24de9173d 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.cjs
@@ -216,6 +216,7 @@ module.exports = {
'selection-green-dark': '#74d47e',
'selection-yellow-light': '#8b8f18',
'selection-yellow-dark': '#dbdb48',
+ 'cultured-grey': '#F7F7F7',
},
borderColor: {
...colors,
@@ -233,6 +234,7 @@ module.exports = {
'baby-blue-eyes': '#A8C7FA',
'leaf-green-dark': '#87DFB2',
sapphire: '#0B57D0',
+ 'dark-grey': '#AAAAAA',
},
colors: {
...colors,