Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
48f1e34
feat: add featured snippets API consumer and REST endpoint
code-snippets-bot Mar 30, 2026
1510b6f
feat: integrate featured snippets into community cloud page
code-snippets-bot Mar 30, 2026
5e31860
fix: remove duplicate get_featured_snippets method and constants
code-snippets-bot Mar 31, 2026
9021e75
fix: update featured endpoint URL to /api/v1/public/featured
code-snippets-bot Mar 31, 2026
c1b8be1
test: add PHPUnit and Playwright tests for featured snippets
code-snippets-bot Mar 31, 2026
f50b84f
fix: debounce cloud search, prevent stale fetch overwriting results
code-snippets-bot Mar 31, 2026
fbdd691
fix: correct test mock URL match and PHP 7.4 compatibility in feature…
code-snippets-bot Mar 31, 2026
9d89072
Merge branch 'core/core-beta' into core/feat/community-page
imantsk May 11, 2026
5c2917b
feat: add pagination params to featured snippets API calls
imantsk May 11, 2026
162613f
feat: pass pagination params in featured snippets fetch request
imantsk May 11, 2026
dcdcaa4
fix: align tablenav nav to the right on community page
imantsk May 11, 2026
f68bbc9
feat: add types endpoint proxy and server-side filter params to cloud…
imantsk May 11, 2026
b0c5298
feat: move filters to server-side and add type dropdown to cloud search
imantsk May 11, 2026
f8e3939
feat: add categories endpoint proxy for server-side category filter o…
imantsk May 11, 2026
e00f0bf
feat: fetch categories from API for filter dropdown instead of curren…
imantsk May 11, 2026
1b107d9
fix: add wp-header-end marker to prevent admin notices from jumping i…
imantsk May 11, 2026
c5f4761
feat: move search spinner inside submit button to prevent layout shift
imantsk May 11, 2026
188344c
fix: build filter options from search results instead of full API tax…
imantsk May 11, 2026
c4f96cf
feat: add consistent cloud snippets heading with filters visible on e…
imantsk May 11, 2026
9e84cda
feat: pass available_filters from cloud API response via X-WP-Filters…
imantsk May 11, 2026
11c4e26
feat: build filter dropdowns from available_filters header instead of…
imantsk May 11, 2026
3015f50
feat: use integer IDs for filter params instead of name strings
imantsk May 11, 2026
ff4f47c
fix: replace button label with spinner during cloud search loading
imantsk May 11, 2026
f1396be
fix: resolve short ternary lint error and line length warnings
imantsk May 11, 2026
97b366a
fix: update featured snippets PHPUnit tests for new API response format
imantsk May 11, 2026
e8e85b7
fix: correct community cloud URL in Playwright test constants
imantsk May 11, 2026
7b067ed
fix: clear cloud API transients when resetting caches from settings
imantsk May 11, 2026
bac2d4b
feat: show orange pro-only button that opens upsell modal for pro sni…
imantsk May 11, 2026
acabd43
fix: use cloud API base URL for codevault author links instead of har…
imantsk May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/css/manage/_cloud-community.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use 'sass:color';
@use '../common/theme';
@use '../common/banners';

Expand All @@ -15,6 +16,16 @@
flex: 0 0 165px;
}

.cloud-search-submit {
display: inline-flex;
align-items: center;
justify-content: center;

.components-spinner {
margin: 0;
}
}

.cloud-search-query {
flex: 1;
position: relative;
Expand Down Expand Up @@ -51,6 +62,10 @@
block-size: 100%;
}

nav {
margin-inline-start: auto;
}

.tablenav-pages {
margin: 0;
margin-inline-start: auto;
Expand Down Expand Up @@ -169,3 +184,16 @@
.cloud-search-results .cloud-search-result footer > .components-spinner {
margin: 0;
}

.cloud-pro-button.button {
background-color: theme.$secondary;
border-color: theme.$secondary;
color: #fff;

&:hover,
&:focus {
background-color: color.adjust(theme.$secondary, $lightness: -10%);
border-color: color.adjust(theme.$secondary, $lightness: -10%);
color: #fff;
}
}
65 changes: 41 additions & 24 deletions src/js/components/ManageMenu/CommunityCloud/CloudSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { __ } from '@wordpress/i18n'
import React, { useEffect } from 'react'
import { Spinner } from '@wordpress/components'
import { TablePagination } from '../../common/ListTable/TablePagination'
import { SubmitButton } from '../../common/SubmitButton'
import { useCloudSearch } from './WithCloudSearchContext'
import { WithCloudSearchFiltersContext, useCloudSearchFilters } from './WithCloudSearchFiltersContext'
import { SearchFilters } from './SearchFilters'
Expand Down Expand Up @@ -43,14 +42,18 @@ const SearchBox = () => {
placeholder={__('e.g. Remove unused JavaScript…', 'code-snippets')}
/>
<span role="status" aria-live="polite">
{isSearching && <>
<Spinner />
<span className="screen-reader-text">{__('Searching…', 'code-snippets')}</span>
</>}
{isSearching &&
<span className="screen-reader-text">{__('Searching…', 'code-snippets')}</span>}
</span>
</div>

<SubmitButton primary disabled={isSearching} text={__('Search Cloud Library', 'code-snippets')} />
<button
type="submit"
className="button button-primary cloud-search-submit"
disabled={isSearching}
>
{isSearching ? <Spinner /> : __('Search Cloud Library', 'code-snippets')}
</button>
</form>
)
}
Expand Down Expand Up @@ -78,18 +81,22 @@ const SearchResultsTable = () => {
/>
</div>

<SearchResults results={filteredSearchResults} />

<div className="tablenav bottom">
<TablePagination
which="bottom"
totalItems={totalItems}
totalPages={totalPages}
disabled={isSearching}
currentPage={page}
setCurrentPage={setPage}
/>
</div>
{0 < filteredSearchResults.length
? <>
<SearchResults results={filteredSearchResults} />

<div className="tablenav bottom">
<TablePagination
which="bottom"
totalItems={totalItems}
totalPages={totalPages}
disabled={isSearching}
currentPage={page}
setCurrentPage={setPage}
/>
</div>
</>
: <NoSearchResultsBanner />}
</>
: null
}
Expand All @@ -104,20 +111,30 @@ const NoSearchResultsBanner = () =>
<p>{__('No snippets or codevault could be found with that search term. Please try again.', 'code-snippets')}</p>
</div>

const CloudSnippetsHeading: React.FC<{ isFeatured: boolean }> = ({ isFeatured }) =>
<h3 className="cloud-snippets-heading">
{isFeatured
? __('Featured Snippets', 'code-snippets')
: __('Search Results', 'code-snippets')}
</h3>

export const CloudSearch = () => {
const { searchResults, error, page } = useCloudSearch()
const { searchResults, error, isFeatured } = useCloudSearch()

return (
<div className="cloud-search">
<SearchBox />

{error && <ErrorBanner />}

{0 < page && 0 === searchResults?.length
? <NoSearchResultsBanner />
: <WithCloudSearchFiltersContext>
<SearchResultsTable />
</WithCloudSearchFiltersContext>}
{searchResults !== undefined
? <>
<CloudSnippetsHeading isFeatured={isFeatured} />
<WithCloudSearchFiltersContext>
<SearchResultsTable />
</WithCloudSearchFiltersContext>
</>
: null}
</div>
)
}
50 changes: 28 additions & 22 deletions src/js/components/ManageMenu/CommunityCloud/SearchFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { __ } from '@wordpress/i18n'
import { CloudStatus } from '../../../types/schema/CloudSnippetSchema'
import { updateQueryParam } from '../../../utils/urls'
import { useCloudSearch } from './WithCloudSearchContext'
import { useCloudSearchFilters } from './WithCloudSearchFiltersContext'
import type { Dispatch, SetStateAction } from 'react'

export const STATUS_LABELS: Record<CloudStatus, string> = {
Expand All @@ -15,7 +14,8 @@ export const STATUS_LABELS: Record<CloudStatus, string> = {
}

export interface CloudSearchFilters {
tags: string
category: number
type: number
status: number
}

Expand All @@ -35,7 +35,7 @@ const SearchFilter: React.FC<SearchFilterProps> = ({ options, filter, filters, s
</label>

<select
id="cloud-search-category"
id={`cloud-search-${filter}`}
className="cloud-search-category-filter"
value={filters[filter]}
onChange={event => {
Expand All @@ -55,42 +55,48 @@ const SearchFilter: React.FC<SearchFilterProps> = ({ options, filter, filters, s
</>

export const SearchFilters = () => {
const { searchResults: snippets } = useCloudSearch()
const { filters, setFilters } = useCloudSearchFilters()
const { availableFilters, filters, setFilters } = useCloudSearch()

const options: { [K in keyof CloudSearchFilters]: [CloudSearchFilters[K], string][] } = useMemo(
() => {
const tags = new Set<string>()
const statuses = new Set<CloudStatus>()
const categoryOptions: [number, string][] = useMemo(
() => (availableFilters.categories ?? []).map(c => [c.id, c.name]),
[availableFilters.categories]
)

snippets?.forEach(snippet => {
snippet.tags.forEach(tag => tags.add(tag))
statuses.add(snippet.status)
})
const typeOptions: [number, string][] = useMemo(
() => (availableFilters.types ?? []).map(t => [t.id, t.name]),
[availableFilters.types]
)

return {
tags: Array.from(tags).sort().map(tag => [tag, tag]),
status: Array.from(statuses).sort().map(status => [status, STATUS_LABELS[status]])
}
},
[snippets])
const statusOptions: [number, string][] = useMemo(
() => (availableFilters.statuses ?? []).map(s => [s.id, s.name]),
[availableFilters.statuses]
)

return (
<>
<SearchFilter
filter="tags"
filter="category"
filters={filters}
setFilters={setFilters}
options={options.tags}
options={categoryOptions}
label={__('Snippet Category', 'code-snippets')}
allOptionLabel={__('All Categories', 'code-snippets')}
/>

<SearchFilter
filter="type"
filters={filters}
setFilters={setFilters}
options={typeOptions}
label={__('Snippet Type', 'code-snippets')}
allOptionLabel={__('All Types', 'code-snippets')}
/>

<SearchFilter
filter="status"
filters={filters}
setFilters={setFilters}
options={options.status}
options={statusOptions}
label={__('Snippet Status', 'code-snippets')}
allOptionLabel={__('All Snippet Statuses', 'code-snippets')}
/>
Expand Down
11 changes: 9 additions & 2 deletions src/js/components/ManageMenu/CommunityCloud/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getSnippetEditUrl, getSnippetType, isProSnippet } from '../../../utils/
import { Badge } from '../../common/Badge'
import { Button } from '../../common/Button'
import { ErrorTooltip } from '../../common/Tooltip'
import { UpsellDialog } from '../../common/UpsellDialog'
import { STATUS_LABELS } from './SearchFilters'
import type { CloudSnippetSchema } from '../../../types/schema/CloudSnippetSchema'

Expand Down Expand Up @@ -69,7 +70,7 @@ const CloudSnippetDetails: React.FC<CloudSnippetDetailsProps> = ({ snippet, setI

<p className="cloud-snippet-author">
{_x('by ', 'snippet author', 'code-snippets')}
<a href={`https://codesnippets.cloud/codevault/${snippet.codevault}`} target="_blank" rel="noopener noreferrer">
<a href={`${window.CODE_SNIPPETS?.urls.cloud}codevault/${snippet.codevault}`} target="_blank" rel="noopener noreferrer">
{snippet.codevault}
</a>
</p>
Expand Down Expand Up @@ -105,6 +106,7 @@ const DownloadButton: React.FC<DownloadButtonProps> = ({ snippet }) => {
const [isWorking, setIsWorking] = useState(false)
const [errorMessage, setErrorMessage] = useState<string>()
const [localSnippetId, setLocalSnippetId] = useState<number>()
const [isUpsellOpen, setIsUpsellOpen] = useState(false)

const handleDownload = () => {
setIsWorking(true)
Expand Down Expand Up @@ -133,7 +135,12 @@ const DownloadButton: React.FC<DownloadButtonProps> = ({ snippet }) => {

if (isProSnippet(snippet) && !isLicensed()) {
return (
<Button primary disabled>{__('Pro only', 'code-snippets')}</Button>
<>
<Button className="cloud-pro-button" onClick={() => setIsUpsellOpen(true)}>
{__('Pro Only', 'code-snippets')}
</Button>
<UpsellDialog isOpen={isUpsellOpen} setIsOpen={setIsUpsellOpen} />
</>
)
}

Expand Down
Loading
Loading