diff --git a/frontend/__snapshots__/posthog-3000-navigation--dark-mode.png b/frontend/__snapshots__/posthog-3000-navigation--dark-mode.png index e3ca98ef9cd45..427bcb65ce1a7 100644 Binary files a/frontend/__snapshots__/posthog-3000-navigation--dark-mode.png and b/frontend/__snapshots__/posthog-3000-navigation--dark-mode.png differ diff --git a/frontend/__snapshots__/posthog-3000-navigation--light-mode.png b/frontend/__snapshots__/posthog-3000-navigation--light-mode.png index 6c32dda4622f8..c353d85de898f 100644 Binary files a/frontend/__snapshots__/posthog-3000-navigation--light-mode.png and b/frontend/__snapshots__/posthog-3000-navigation--light-mode.png differ diff --git a/frontend/public/icons/android-chrome-192x192.png b/frontend/public/icons/android-chrome-192x192.png index e27d8dbaabb06..9f05e4c652842 100644 Binary files a/frontend/public/icons/android-chrome-192x192.png and b/frontend/public/icons/android-chrome-192x192.png differ diff --git a/frontend/public/icons/android-chrome-512x512.png b/frontend/public/icons/android-chrome-512x512.png index 7d5a25496f787..d38bdf6e3d4b9 100644 Binary files a/frontend/public/icons/android-chrome-512x512.png and b/frontend/public/icons/android-chrome-512x512.png differ diff --git a/frontend/public/icons/apple-touch-icon.png b/frontend/public/icons/apple-touch-icon.png index d90bf6f3cbcc5..ec1d7f3a5a49c 100644 Binary files a/frontend/public/icons/apple-touch-icon.png and b/frontend/public/icons/apple-touch-icon.png differ diff --git a/frontend/public/icons/favicon-16x16.png b/frontend/public/icons/favicon-16x16.png index 0ec1482d77231..68c0f5b7ba759 100644 Binary files a/frontend/public/icons/favicon-16x16.png and b/frontend/public/icons/favicon-16x16.png differ diff --git a/frontend/public/icons/favicon-32x32.png b/frontend/public/icons/favicon-32x32.png index d109bc19a59b6..3c7581a461087 100644 Binary files a/frontend/public/icons/favicon-32x32.png and b/frontend/public/icons/favicon-32x32.png differ diff --git a/frontend/src/layout/navigation-3000/Navigation.scss b/frontend/src/layout/navigation-3000/Navigation.scss index baf6c6bd6bb28..5d59aba88bf10 100644 --- a/frontend/src/layout/navigation-3000/Navigation.scss +++ b/frontend/src/layout/navigation-3000/Navigation.scss @@ -17,10 +17,16 @@ } .Navigation3000__scene { - margin: var(--scene-padding-y) var(--scene-padding-x); - // The below is for positioning of the scene-level spinner + // `relative` is for positioning of the scene-level spinner position: relative; + margin: var(--scene-padding-y) var(--scene-padding-x); min-height: calc(100vh - var(--breadcrumbs-height) - var(--scene-padding-y) * 2); + overflow-x: hidden; + &.Navigation3000__scene--raw { + --scene-padding-y: 0px; + --scene-padding-x: 0px; + display: flex; + } } // Navbar @@ -299,7 +305,7 @@ border-bottom-width: 1px; list-style: none; - &:hover, + &:hover:not([aria-disabled='true']), &[aria-current='page'] { opacity: 1; --sidebar-list-item-background: var(--border-3000); @@ -404,6 +410,9 @@ row-gap: 1px; padding: 0 var(--sidebar-horizontal-padding) 0 var(--sidebar-list-item-inset); color: inherit !important; // Disable link color + .SidebarListItem[aria-disabled='true'] & { + cursor: default; + } } .SidebarListItem__rename { diff --git a/frontend/src/layout/navigation-3000/Navigation.tsx b/frontend/src/layout/navigation-3000/Navigation.tsx index 7f94bdd4971cf..8a975cd20481a 100644 --- a/frontend/src/layout/navigation-3000/Navigation.tsx +++ b/frontend/src/layout/navigation-3000/Navigation.tsx @@ -8,9 +8,12 @@ import { Sidebar } from './components/Sidebar' import './Navigation.scss' import { themeLogic } from './themeLogic' import { navigation3000Logic } from './navigationLogic' +import clsx from 'clsx' +import { sceneLogic } from 'scenes/sceneLogic' export function Navigation({ children }: { children: ReactNode }): JSX.Element { useMountedLogic(themeLogic) + const { sceneConfig } = useValues(sceneLogic) const { activeNavbarItem } = useValues(navigation3000Logic) useEffect(() => { @@ -18,6 +21,10 @@ export function Navigation({ children }: { children: ReactNode }): JSX.Element { document.getElementById('bottom-notice')?.remove() }, []) + if (sceneConfig?.layout === 'plain') { + throw new Error('Navigation should never be rendered for a plain scene') + } + return (
@@ -25,8 +32,13 @@ export function Navigation({ children }: { children: ReactNode }): JSX.Element {
-
-
{children}
+
+ {children}
diff --git a/frontend/src/layout/navigation-3000/components/Navbar.tsx b/frontend/src/layout/navigation-3000/components/Navbar.tsx index 94a9f632c1e8a..c2921515bfbd8 100644 --- a/frontend/src/layout/navigation-3000/components/Navbar.tsx +++ b/frontend/src/layout/navigation-3000/components/Navbar.tsx @@ -12,6 +12,7 @@ import { navigation3000Logic } from '../navigationLogic' import { NAVBAR_ITEMS } from '../navbarItems' import { themeLogic } from '../themeLogic' import { NavbarButton } from './NavbarButton' +import { urls } from 'scenes/urls' export function Navbar(): JSX.Element { const { user } = useValues(userLogic) @@ -91,7 +92,11 @@ export function Navbar(): JSX.Element { } placement="right-end" /> - } identifier={Scene.ProjectSettings} /> + } + identifier={Scene.ProjectSettings} + to={urls.projectSettings()} + /> } visible={isSitePopoverOpen} diff --git a/frontend/src/layout/navigation-3000/components/SidebarList.tsx b/frontend/src/layout/navigation-3000/components/SidebarList.tsx index 83a750ddad8b5..2a5508bc35f6d 100644 --- a/frontend/src/layout/navigation-3000/components/SidebarList.tsx +++ b/frontend/src/layout/navigation-3000/components/SidebarList.tsx @@ -5,7 +5,7 @@ import { IconCheckmark, IconClose, IconEllipsis } from 'lib/lemon-ui/icons' import { BasicListItem, ExtendedListItem, ExtraListItemContext, SidebarCategory } from '../types' import React, { useEffect, useMemo, useRef, useState } from 'react' import { LemonMenu } from 'lib/lemon-ui/LemonMenu' -import { LemonButton, lemonToast } from '@posthog/lemon-ui' +import { LemonButton, LemonTag, lemonToast } from '@posthog/lemon-ui' import { navigation3000Logic } from '../navigationLogic' import { captureException } from '@sentry/react' import { KeyboardShortcut } from './KeyboardShortcut' @@ -104,6 +104,16 @@ function SidebarListItem({ if (!item.url || item.isNamePlaceholder) { formattedName = {formattedName} } + if (item.tag) { + formattedName = ( + <> + {formattedName} + + {item.tag.text} + + + ) + } const { onRename } = item const menuItems = useMemo(() => { @@ -264,6 +274,7 @@ function SidebarListItem({ !!item.marker?.status && `SidebarListItem--marker-status-${item.marker.status}`, 'summary' in item && 'SidebarListItem--extended' )} + aria-disabled={!item.url} aria-current={active ? 'page' : undefined} style={style} // eslint-disable-line react/forbid-dom-props > diff --git a/frontend/src/layout/navigation-3000/navbarItems.tsx b/frontend/src/layout/navigation-3000/navbarItems.tsx index 2abbbfea67c3e..6628c1bfed15e 100644 --- a/frontend/src/layout/navigation-3000/navbarItems.tsx +++ b/frontend/src/layout/navigation-3000/navbarItems.tsx @@ -10,6 +10,7 @@ import { IconLive, IconPerson, IconRecording, + IconTools, IconUnverifiedEvent, } from 'lib/lemon-ui/icons' import { Scene } from 'scenes/sceneTypes' @@ -23,6 +24,7 @@ import { insightsSidebarLogic } from './sidebars/insights' import { dataManagementSidebarLogic } from './sidebars/dataManagement' import { annotationsSidebarLogic } from './sidebars/annotations' import { experimentsSidebarLogic } from './sidebars/experiments' +import { toolbarSidebarLogic } from './sidebars/toolbar' /** A list of navbar sections with items. */ export const NAVBAR_ITEMS: NavbarItem[][] = [ @@ -93,6 +95,12 @@ export const NAVBAR_ITEMS: NavbarItem[][] = [ icon: , logic: experimentsSidebarLogic, }, + { + identifier: Scene.ToolbarLaunch, + label: 'Toolbar', + icon: , + logic: toolbarSidebarLogic, + }, ], [ { diff --git a/frontend/src/layout/navigation-3000/sidebars/dataManagement.ts b/frontend/src/layout/navigation-3000/sidebars/dataManagement.ts index 18bd7e366751c..28fe380ade427 100644 --- a/frontend/src/layout/navigation-3000/sidebars/dataManagement.ts +++ b/frontend/src/layout/navigation-3000/sidebars/dataManagement.ts @@ -36,7 +36,7 @@ export const dataManagementSidebarLogic = kea([ { loadEventDefinitions: async ({ startIndex, stopIndex }) => { if (!startIndex) { - cache.requestedEventDefinitions.length = 0 // Clear cache + cache.requestedEventDefinitions = [] } for (let i = startIndex; i < stopIndex; i++) { cache.requestedEventDefinitions[i] = true @@ -59,7 +59,7 @@ export const dataManagementSidebarLogic = kea([ { loadPropertyDefinitions: async ({ startIndex, stopIndex }) => { if (!startIndex) { - cache.requestedPropertyDefinitions.length = 0 // Clear cache + cache.requestedPropertyDefinitions = [] } for (let i = startIndex; i < stopIndex; i++) { cache.requestedPropertyDefinitions[i] = true diff --git a/frontend/src/layout/navigation-3000/sidebars/insights.ts b/frontend/src/layout/navigation-3000/sidebars/insights.ts index e3bb0be379c28..da3baf796dc1e 100644 --- a/frontend/src/layout/navigation-3000/sidebars/insights.ts +++ b/frontend/src/layout/navigation-3000/sidebars/insights.ts @@ -139,7 +139,7 @@ export const insightsSidebarLogic = kea([ listeners(({ values, cache }) => ({ loadInsights: () => { if (!values.paramsFromFilters.offset) { - cache.requestedInsights.length = 0 + cache.requestedInsights = [] } }, })), diff --git a/frontend/src/layout/navigation-3000/sidebars/toolbar.ts b/frontend/src/layout/navigation-3000/sidebars/toolbar.ts new file mode 100644 index 0000000000000..b439b4dca55f5 --- /dev/null +++ b/frontend/src/layout/navigation-3000/sidebars/toolbar.ts @@ -0,0 +1,118 @@ +import { connect, kea, path, selectors } from 'kea' +import { sceneLogic } from 'scenes/sceneLogic' +import { Scene } from 'scenes/sceneTypes' +import { urls } from 'scenes/urls' +import { SidebarCategory, BasicListItem } from '../types' +import Fuse from 'fuse.js' +import { subscriptions } from 'kea-subscriptions' +import { navigation3000Logic } from '~/layout/navigation-3000/navigationLogic' +import { FuseSearchMatch } from './utils' +import { + AuthorizedUrlListType, + KeyedAppUrl, + authorizedUrlListLogic, +} from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic' + +import type { toolbarSidebarLogicType } from './toolbarType' + +const fuse = new Fuse([], { + keys: ['url'], + threshold: 0.3, + ignoreLocation: true, + includeMatches: true, +}) + +export const toolbarSidebarLogic = kea([ + path(['layout', 'navigation-3000', 'sidebars', 'toolbarSidebarLogic']), + connect(() => ({ + values: [ + authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + ['urlsKeyed', 'suggestionsLoading'], + sceneLogic, + ['activeScene', 'sceneParams'], + ], + actions: [ + authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + ['addUrl', 'removeUrl', 'updateUrl'], + ], + })), + selectors(({ actions }) => ({ + contents: [ + (s) => [s.relevantUrls, s.suggestionsLoading], + (relevantUrls, suggestionsLoading) => [ + { + key: 'sites', + title: 'Sites', + loading: suggestionsLoading, + items: relevantUrls.map( + ([url, matches]) => + ({ + key: url.url, + name: url.url, + url: url.type !== 'suggestion' ? urls.site(url.url) : null, + tag: url.type === 'suggestion' ? { status: 'warning', text: 'SUGGESTION' } : undefined, + searchMatch: matches + ? { + matchingFields: matches.map((match) => match.key), + nameHighlightRanges: matches.find((match) => match.key === 'url')?.indices, + } + : null, + onRename: + url.type !== 'suggestion' + ? (newUrl) => actions.updateUrl(url.originalIndex, newUrl) + : undefined, + menuItems: + url.type !== 'suggestion' + ? (initiateRename) => [ + { + items: [ + { + to: urls.site(url.url), + targetBlank: true, + label: 'Open with Toolbar in new tab', + }, + ], + }, + { + items: [ + { + onClick: initiateRename, + label: 'Edit', + keyboardShortcut: ['enter'], + }, + { + onClick: () => actions.removeUrl(url.originalIndex), + status: 'danger', + label: 'Delete site', + }, + ], + }, + ] + : [{ onClick: () => actions.addUrl(url.url), label: 'Apply suggestion' }], + } as BasicListItem) + ), + } as SidebarCategory, + ], + ], + activeListItemKey: [ + (s) => [s.activeScene, s.sceneParams], + (activeScene, sceneParams) => { + return activeScene === Scene.Site ? decodeURIComponent(sceneParams.params.url) : null + }, + ], + relevantUrls: [ + (s) => [s.urlsKeyed, navigation3000Logic.selectors.searchTerm], + (urlsKeyed, searchTerm): [KeyedAppUrl, FuseSearchMatch[] | null][] => { + if (searchTerm) { + return fuse.search(searchTerm).map((result) => [result.item, result.matches as FuseSearchMatch[]]) + } + return urlsKeyed.map((url) => [url, null]) + }, + ], + })), + subscriptions({ + urlsKeyed: (urlsKeyed) => { + fuse.setCollection(urlsKeyed) + }, + }), +]) diff --git a/frontend/src/layout/navigation-3000/types.ts b/frontend/src/layout/navigation-3000/types.ts index 4c78df9bc73cd..3be340919aa16 100644 --- a/frontend/src/layout/navigation-3000/types.ts +++ b/frontend/src/layout/navigation-3000/types.ts @@ -1,3 +1,4 @@ +import { LemonTagType } from '@posthog/lemon-ui' import { Logic, LogicWrapper } from 'kea' import { Dayjs } from 'lib/dayjs' import { LemonMenuItems } from 'lib/lemon-ui/LemonMenu' @@ -76,8 +77,7 @@ export interface BasicListItem { /** Whether the name is a placeholder (e.g. an insight derived name), in which case it'll be italicized. */ isNamePlaceholder?: boolean /** - * URL within the app. - * In rare cases this can be explicitly null (e.g. the "Load more" item). Such items are italicized. + * URL within the app. In specific cases this can be null - such items are italicized. */ url: string | null /** An optional marker to highlight item state. */ @@ -90,6 +90,11 @@ export interface BasicListItem { */ status?: 'muted' | 'success' | 'warning' | 'danger' | 'completion' } + /** An optional tag shown as a suffix of the name. */ + tag?: { + status: LemonTagType + text: string + } /** If search is on, this should be present to convey why this item is included in results. */ searchMatch?: SearchMatch | null menuItems?: LemonMenuItems | ((initiateRename?: () => void) => LemonMenuItems) diff --git a/frontend/src/layout/navigation/Navigation.tsx b/frontend/src/layout/navigation/Navigation.tsx index 21042046af8ec..a6b7641326bb1 100644 --- a/frontend/src/layout/navigation/Navigation.tsx +++ b/frontend/src/layout/navigation/Navigation.tsx @@ -15,8 +15,8 @@ export function Navigation({ children }: { children: any }): JSX.Element {
{activeScene !== Scene.Ingestion && } -
- {!sceneConfig?.plain && ( +
+ {sceneConfig?.layout !== 'plain' && ( <> {!sceneConfig?.hideProjectNotice && } diff --git a/frontend/src/layout/navigation/navigationLogic.ts b/frontend/src/layout/navigation/navigationLogic.ts index e58f6d306d980..90c723d1324c8 100644 --- a/frontend/src/layout/navigation/navigationLogic.ts +++ b/frontend/src/layout/navigation/navigationLogic.ts @@ -120,7 +120,10 @@ export const navigationLogic = kea({ }), selectors: { /** `bareNav` whether the current scene should display a sidebar at all */ - bareNav: [(s) => [s.fullscreen, s.sceneConfig], (fullscreen, sceneConfig) => fullscreen || sceneConfig?.plain], + bareNav: [ + (s) => [s.fullscreen, s.sceneConfig], + (fullscreen, sceneConfig) => fullscreen || sceneConfig?.layout === 'plain', + ], isSideBarShown: [ (s) => [s.mobileLayout, s.isSideBarShownBase, s.isSideBarShownMobile, s.bareNav], (mobileLayout, isSideBarShownBase, isSideBarShownMobile, bareNav) => diff --git a/frontend/src/lib/lemon-ui/LemonTag/LemonTag.scss b/frontend/src/lib/lemon-ui/LemonTag/LemonTag.scss index 451315918260b..7c188c95ee201 100644 --- a/frontend/src/lib/lemon-ui/LemonTag/LemonTag.scss +++ b/frontend/src/lib/lemon-ui/LemonTag/LemonTag.scss @@ -49,6 +49,11 @@ background: none; } + &.LemonTag--size-small { + font-size: 0.625rem; + padding: 0.0625rem 0.1875rem; + } + .LemonTag__icon { font-size: 0.875rem; margin-right: 0.125rem; @@ -60,8 +65,4 @@ min-height: 1.5rem; padding: 0.125rem 0.125rem; } - - &.clickable { - cursor: pointer; - } } diff --git a/frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx b/frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx index c73af1ea2c014..404eb1835b88c 100644 --- a/frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx +++ b/frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx @@ -18,6 +18,7 @@ export type LemonTagType = interface LemonTagProps extends React.HTMLAttributes { type?: LemonTagType children: React.ReactNode + size?: 'small' | 'medium' icon?: JSX.Element closable?: boolean onClose?: () => void @@ -28,6 +29,7 @@ export function LemonTag({ type = 'default', children, className, + size = 'medium', icon, closable, onClose, @@ -35,7 +37,10 @@ export function LemonTag({ ...props }: LemonTagProps): JSX.Element { return ( -
+
{icon && {icon}} {children} {popover?.overlay && ( diff --git a/frontend/src/scenes/appScenes.ts b/frontend/src/scenes/appScenes.ts index e4ac0ceb77d77..7d5efa5012ccd 100644 --- a/frontend/src/scenes/appScenes.ts +++ b/frontend/src/scenes/appScenes.ts @@ -49,6 +49,7 @@ export const appScenes: Record any> = { [Scene.ProjectCreateFirst]: () => import('./project/Create'), [Scene.SystemStatus]: () => import('./instance/SystemStatus'), [Scene.ToolbarLaunch]: () => import('./toolbar-launch/ToolbarLaunch'), + [Scene.Site]: () => import('./sites/Site'), [Scene.AsyncMigrations]: () => import('./instance/AsyncMigrations/AsyncMigrations'), [Scene.DeadLetterQueue]: () => import('./instance/DeadLetterQueue/DeadLetterQueue'), [Scene.MySettings]: () => import('./me/Settings'), diff --git a/frontend/src/scenes/sceneTypes.ts b/frontend/src/scenes/sceneTypes.ts index 8a971f82a819d..f493f97a8ab64 100644 --- a/frontend/src/scenes/sceneTypes.ts +++ b/frontend/src/scenes/sceneTypes.ts @@ -58,6 +58,7 @@ export enum Scene { AppMetrics = 'AppMetrics', SavedInsights = 'SavedInsights', ToolbarLaunch = 'ToolbarLaunch', + Site = 'Site', WebPerformance = 'WebPerformance', IntegrationsRedirect = 'IntegrationsRedirect', // Authentication, onboarding & initialization routes @@ -114,8 +115,13 @@ export interface SceneConfig { onlyUnauthenticated?: boolean /** Route **can** be accessed when logged out (i.e. can be accessed when logged in too; should be added to posthog/urls.py too) */ allowUnauthenticated?: boolean - /** Hides most navigation UI, like the sidebar and breadcrumbs. */ - plain?: boolean + /** + * If `app`, navigation is shown, and the scene has default padding. + * If `app-raw`, navigation is shown, but the scene has no padding. + * If `plain`, there's no navigation present, and the scene has no padding. + * @default 'app' + */ + layout?: 'app' | 'app-raw' | 'plain' /** Hides project notice (ProjectNotice.tsx). */ hideProjectNotice?: boolean /** Personal account management (used e.g. by breadcrumbs) */ @@ -124,6 +130,6 @@ export interface SceneConfig { instanceLevel?: boolean /** Route requires organization access (used e.g. by breadcrumbs) */ organizationBased?: boolean - /** Route requires project access (used e.g. by breadcrumbs). `true` implies `organizationBased` */ + /** Route requires project access (used e.g. by breadcrumbs). `true` implies also `organizationBased` */ projectBased?: boolean } diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 41525bd566a84..6b9e4d3e32543 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -210,12 +210,17 @@ export const sceneConfigurations: Partial> = { }, [Scene.Ingestion]: { projectBased: true, - plain: true, + layout: 'plain', }, [Scene.ToolbarLaunch]: { projectBased: true, name: 'Launch Toolbar', }, + [Scene.Site]: { + projectBased: true, + hideProjectNotice: true, + layout: 'app-raw', + }, // Organization-based routes [Scene.OrganizationCreateFirst]: { name: 'Organization creation', @@ -252,7 +257,7 @@ export const sceneConfigurations: Partial> = { }, [Scene.InviteSignup]: { allowUnauthenticated: true, - plain: true, + layout: 'plain', }, // Instance management routes [Scene.SystemStatus]: { @@ -282,7 +287,7 @@ export const sceneConfigurations: Partial> = { }, [Scene.VerifyEmail]: { allowUnauthenticated: true, - plain: true, + layout: 'plain', }, [Scene.Feedback]: { projectBased: true, @@ -433,6 +438,7 @@ export const routes: Record = { [urls.deadLetterQueue()]: Scene.DeadLetterQueue, [urls.mySettings()]: Scene.MySettings, [urls.toolbarLaunch()]: Scene.ToolbarLaunch, + [urls.site(':url')]: Scene.Site, // Onboarding / setup routes [urls.login()]: Scene.Login, [urls.login2FA()]: Scene.Login2FA, diff --git a/frontend/src/scenes/sites/Site.scss b/frontend/src/scenes/sites/Site.scss new file mode 100644 index 0000000000000..dfdf92f7044a5 --- /dev/null +++ b/frontend/src/scenes/sites/Site.scss @@ -0,0 +1,4 @@ +.Site { + width: 100%; + border: none; +} diff --git a/frontend/src/scenes/sites/Site.tsx b/frontend/src/scenes/sites/Site.tsx new file mode 100644 index 0000000000000..ad487e77da371 --- /dev/null +++ b/frontend/src/scenes/sites/Site.tsx @@ -0,0 +1,30 @@ +import { SceneExport } from 'scenes/sceneTypes' +import './Site.scss' +import { SiteLogicProps, siteLogic } from './siteLogic' +import { AuthorizedUrlListType, authorizedUrlListLogic } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic' +import { useValues } from 'kea' + +export const scene: SceneExport = { + component: Site, + paramsToProps: ({ params: { url } }): SiteLogicProps => ({ url: decodeURIComponent(url) }), + logic: siteLogic, +} + +export function Site({ url }: { url?: string } = {}): JSX.Element { + const { launchUrl } = useValues( + authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }) + ) + + const decodedUrl = decodeURIComponent(url || '') + + return ( +