| @@ -1,9 +1,12 @@ | ||
| 1 | 1 | import { __, _x } from '@wordpress/i18n' |
| 2 | 2 | import classnames from 'classnames' |
| 3 | 3 | import React, { useMemo, useState } from 'react' |
| 4 | 4 | import { isLicensed, shouldShowUpsell } from '../../utils/screen' |
| 5 | -import { buildUrl } from '../../utils/urls' | |
| 5 | +import { buildUrl, fetchConstQueryParam } from '../../utils/urls' | |
| 6 | +import { SUBPAGES, SUBPAGE_ENTRIES, type SubpageName } from '../ManageMenu/subpages' | |
| 7 | +import { hasSeenDemo } from './demo/useDemoSeen' | |
| 8 | +import { AiAgentIcon } from './icons/AiAgentIcon' | |
| 6 | 9 | import { BlueprintIcon } from './icons/BlueprintIcon' |
| 7 | 10 | import { CommunityIcon } from './icons/CommunityIcon' |
| 8 | 11 | import { LibraryIcon } from './icons/LibraryIcon' |
| 9 | 12 | import { SettingsIcon } from './icons/SettingsIcon' |
| @@ -10,12 +13,21 @@ | ||
| 10 | 13 | import { SnippetsIcon } from './icons/SnippetsIcon' |
| 11 | 14 | import { UpsellDialog } from './UpsellDialog' |
| 12 | 15 | import type { SVGProps } from 'react' |
| 13 | 16 | |
| 14 | -export const SUBPAGES = ['snippets', 'blueprints', 'cloud-community', 'cloud-library'] as const | |
| 17 | +const searchParams = new URLSearchParams(window.location.search) | |
| 15 | 18 | |
| 16 | -const searchParams = new URLSearchParams(window.location.search) | |
| 19 | +const managePageSlug = window.CODE_SNIPPETS?.urls.manage | |
| 20 | + ? new URL(window.CODE_SNIPPETS.urls.manage).searchParams.get('page') | |
| 21 | + : null | |
| 17 | 22 | |
| 23 | +// The manage screen resolves an absent or unrecognised `subpage` to the first subpage, so | |
| 24 | +// mirror that fallback here — comparing against the raw param would leave every lower-nav | |
| 25 | +// tab inactive on the default Snippets view, which is where the page opens. | |
| 26 | +const activeSubpage = searchParams.get('page') === managePageSlug | |
| 27 | + ? fetchConstQueryParam('subpage', SUBPAGES) ?? SUBPAGES[0] | |
| 28 | + : null | |
| 29 | + | |
| 18 | 30 | interface UpperNavItemProps { |
| 19 | 31 | name: string |
| 20 | 32 | url: string |
| 21 | 33 | label: string |
| @@ -39,8 +51,29 @@ | ||
| 39 | 51 | ) |
| 40 | 52 | } |
| 41 | 53 | const UpperNavItems = () => |
| 42 | 54 | <> |
| 55 | + {window.CODE_SNIPPETS?.urls.insights && ( | |
| 56 | + <UpperNavItem | |
| 57 | + name="insights" | |
| 58 | + url={window.CODE_SNIPPETS.urls.insights} | |
| 59 | + label={__('Insights', 'code-snippets')} | |
| 60 | + />)} | |
| 61 | + | |
| 62 | + {window.CODE_SNIPPETS?.urls.import && ( | |
| 63 | + <UpperNavItem | |
| 64 | + name="import-snippets" | |
| 65 | + url={window.CODE_SNIPPETS.urls.import} | |
| 66 | + label={_x('Import', 'snippets', 'code-snippets')} | |
| 67 | + />)} | |
| 68 | + | |
| 69 | + {window.CODE_SNIPPETS?.urls.welcome && ( | |
| 70 | + <UpperNavItem | |
| 71 | + name="welcome" | |
| 72 | + url={window.CODE_SNIPPETS.urls.welcome} | |
| 73 | + label={__("What's New", 'code-snippets')} | |
| 74 | + />)} | |
| 75 | + | |
| 43 | 76 | <UpperNavItem |
| 44 | 77 | name="docs" |
| 45 | 78 | url="https://codesnippets.pro/docs" |
| 46 | 79 | label={__('Docs', 'code-snippets')} |
| @@ -47,26 +80,12 @@ | ||
| 47 | 80 | /> |
| 48 | 81 | |
| 49 | 82 | <UpperNavItem |
| 50 | 83 | name="cloud" |
| 51 | - url="https://codesnippets.cloud/" | |
| 84 | + url={window.CODE_SNIPPETS?.urls.cloud ?? 'https://codesnippets.cloud'} | |
| 52 | 85 | label={__('Cloud Dashboard', 'code-snippets')} |
| 53 | 86 | /> |
| 54 | 87 | |
| 55 | - {window.CODE_SNIPPETS?.urls.welcome && ( | |
| 56 | - <UpperNavItem | |
| 57 | - name="welcome" | |
| 58 | - url={window.CODE_SNIPPETS.urls.welcome} | |
| 59 | - label={__("What's New", 'code-snippets')} | |
| 60 | - />)} | |
| 61 | - | |
| 62 | - {window.CODE_SNIPPETS?.urls.import && ( | |
| 63 | - <UpperNavItem | |
| 64 | - name="import-snippets" | |
| 65 | - url={window.CODE_SNIPPETS.urls.import} | |
| 66 | - label={_x('Import', 'snippets', 'code-snippets')} | |
| 67 | - />)} | |
| 68 | - | |
| 69 | 88 | </> |
| 70 | 89 | |
| 71 | 90 | const MoreNav = () => |
| 72 | 91 | <li className="toolbar-more-item"> |
| @@ -123,26 +142,49 @@ | ||
| 123 | 142 | ) |
| 124 | 143 | } |
| 125 | 144 | |
| 126 | 145 | interface SubpageItemProps { |
| 127 | - subpage: typeof SUBPAGES[number] | |
| 146 | + subpage: SubpageName | |
| 128 | 147 | label: string |
| 129 | 148 | Icon: React.FC<SVGProps<SVGSVGElement>> |
| 130 | - isPro?: boolean | |
| 131 | 149 | } |
| 132 | 150 | |
| 133 | -const SubpageItem: React.FC<SubpageItemProps> = ({ subpage, label, Icon, isPro }) => | |
| 134 | - <li> | |
| 135 | - <a | |
| 136 | - href={buildUrl(window.CODE_SNIPPETS?.urls.manage, { subpage: subpage })} | |
| 137 | - className={classnames(`${subpage}-link`, { 'active-link': subpage === searchParams.get('subpage') })} | |
| 138 | - > | |
| 139 | - <Icon aria-hidden="true" /> | |
| 140 | - <span className="toolbar-nav-label">{label}</span> | |
| 141 | - {isPro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>} | |
| 142 | - </a> | |
| 143 | - </li> | |
| 151 | +const SubpageItem: React.FC<SubpageItemProps> = ({ subpage, label, Icon }) => { | |
| 152 | + const { demo, isPro } = SUBPAGE_ENTRIES[subpage] | |
| 153 | + const isNew = 'announce' === demo && !hasSeenDemo(subpage) | |
| 144 | 154 | |
| 155 | + return ( | |
| 156 | + <li> | |
| 157 | + <a | |
| 158 | + href={buildUrl(window.CODE_SNIPPETS?.urls.manage, { subpage: subpage })} | |
| 159 | + className={classnames(`${subpage}-link`, { 'active-link': subpage === activeSubpage })} | |
| 160 | + > | |
| 161 | + <Icon aria-hidden="true" /> | |
| 162 | + <span className="toolbar-nav-label">{label}</span> | |
| 163 | + {demo && (isNew | |
| 164 | + ? <span className="new-chip">{__('New', 'code-snippets')}</span> | |
| 165 | + : <span className="demo-chip">{__('Demo', 'code-snippets')}</span>)} | |
| 166 | + {isPro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>} | |
| 167 | + </a> | |
| 168 | + </li> | |
| 169 | + ) | |
| 170 | +} | |
| 171 | + | |
| 172 | +const SettingsItem = () => | |
| 173 | + window.CODE_SNIPPETS?.urls.settings | |
| 174 | + ? <li> | |
| 175 | + <a | |
| 176 | + href={window.CODE_SNIPPETS.urls.settings} | |
| 177 | + className={classnames('settings-link', { | |
| 178 | + 'active-link': 'snippets-settings' === searchParams.get('page') | |
| 179 | + })} | |
| 180 | + > | |
| 181 | + <SettingsIcon aria-hidden="true" /> | |
| 182 | + <span className="toolbar-nav-label">{__('Settings', 'code-snippets')}</span> | |
| 183 | + </a> | |
| 184 | + </li> | |
| 185 | + : null | |
| 186 | + | |
| 145 | 187 | const LowerNav = () => |
| 146 | 188 | <div className="code-snippets-toolbar-lower"> |
| 147 | 189 | <nav aria-label={__('Main features', 'code-snippets')}> |
| 148 | 190 | <ul> |
| @@ -168,9 +210,8 @@ | ||
| 168 | 210 | <SubpageItem |
| 169 | 211 | subpage="cloud-library" |
| 170 | 212 | label={__('Cloud Library', 'code-snippets')} |
| 171 | 213 | Icon={LibraryIcon} |
| 172 | - isPro | |
| 173 | 214 | /> |
| 174 | 215 | |
| 175 | 216 | {!isLicensed() && ( |
| 176 | 217 | <SubpageItem |
| @@ -176,18 +217,17 @@ | ||
| 176 | 217 | <SubpageItem |
| 177 | 218 | subpage="blueprints" |
| 178 | 219 | label={__('Blueprints', 'code-snippets')} |
| 179 | 220 | Icon={BlueprintIcon} |
| 180 | - isPro | |
| 181 | 221 | />)} |
| 182 | 222 | |
| 183 | - {window.CODE_SNIPPETS?.urls.settings && ( | |
| 184 | - <li className="toolbar-end-item"> | |
| 185 | - <a href={window.CODE_SNIPPETS.urls.settings} className="settings-link"> | |
| 186 | - <SettingsIcon aria-hidden="true" /> | |
| 187 | - <span className="toolbar-nav-label">{__('Settings', 'code-snippets')}</span> | |
| 188 | - </a> | |
| 189 | - </li>)} | |
| 223 | + <SubpageItem | |
| 224 | + subpage="ai-agent" | |
| 225 | + label={__('AI Agent', 'code-snippets')} | |
| 226 | + Icon={AiAgentIcon} | |
| 227 | + /> | |
| 228 | + | |
| 229 | + <SettingsItem /> | |
| 190 | 230 | </ul> |
| 191 | 231 | </nav> |
| 192 | 232 | </div> |
| 193 | 233 | |