Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
80bf9e0
Category browsing (#241)
imitev-bae Apr 22, 2026
302b811
Merge branch 'main' into feat/new-search-page
fdelavega Apr 27, 2026
c59beff
Merge branch 'main' into feat/new-search-page
fdelavega May 6, 2026
00640ef
Merge branch 'main' into feat/new-search-page
fdelavega May 13, 2026
b53d293
Category browsing (#241)
imitev-bae Apr 22, 2026
8445912
Recover providers catalog link
fdelavega May 18, 2026
1865941
Merge
fdelavega May 18, 2026
1ae56cb
Remove hardcoding in main categories root
fdelavega May 21, 2026
31faa00
Images fix (#254)
imitev-bae May 25, 2026
c74b948
Build search filters dynamically
fdelavega May 25, 2026
0e64d73
Load dynamically the search filter config
fdelavega May 25, 2026
0c470de
Add a default catalog admin section
fdelavega May 25, 2026
735650c
Fix issues with multiple requests being sent when filtering
fdelavega May 25, 2026
bce4f10
Fix issues with category parent ID being always sent
fdelavega May 25, 2026
efb2c72
Add missing admin files
fdelavega May 25, 2026
7cd3fd8
Fix issue with clear all button
fdelavega May 26, 2026
6403b9e
Reorder admin section
fdelavega May 26, 2026
631ff75
Add an admin section to configure the search filters
fdelavega May 27, 2026
4de35b2
Merge branch 'feat/new-search-page' of https://github.com/Ficodes/BAE…
fdelavega May 27, 2026
3f313cf
Merge branch 'main' into feat/new-search-page
fdelavega May 27, 2026
5dc974a
Category browsing (#241)
imitev-bae Apr 22, 2026
1a8d87f
Recover providers catalog link
fdelavega May 18, 2026
2a44a08
Category browsing (#241)
imitev-bae Apr 22, 2026
9824466
Remove hardcoding in main categories root
fdelavega May 21, 2026
819f25f
Build search filters dynamically
fdelavega May 25, 2026
8c21734
Load dynamically the search filter config
fdelavega May 25, 2026
be4877d
Add a default catalog admin section
fdelavega May 25, 2026
ec06b56
Fix issues with multiple requests being sent when filtering
fdelavega May 25, 2026
1e0a304
Fix issues with category parent ID being always sent
fdelavega May 25, 2026
5caf9f7
Add missing admin files
fdelavega May 25, 2026
eb559ae
Fix issue with clear all button
fdelavega May 26, 2026
33639c1
Reorder admin section
fdelavega May 26, 2026
244a848
Add an admin section to configure the search filters
fdelavega May 27, 2026
df89fbe
Images fix (#254)
imitev-bae May 25, 2026
e78dbc0
Merge branch 'feat/new-search-page' of https://github.com/Ficodes/BAE…
fdelavega May 28, 2026
4708b65
Avoid local sort and delegate on the backend
fdelavega May 28, 2026
6277f9c
Recover enable and dissable of search feature
fdelavega May 28, 2026
0c6b623
Comment sort options until it is available
fdelavega Jun 3, 2026
5ec02ff
Update unit tests
fdelavega Jun 3, 2026
345448d
Update cypress tests
fdelavega Jun 3, 2026
7a2e9e9
Add missing data-cy attr
fdelavega Jun 3, 2026
93fb7fc
Add some data-cy tags needed for the tests
fdelavega Jun 4, 2026
320a988
Add more data-cy tags
fdelavega Jun 4, 2026
bf40d28
Add cypress attr to default catalog management
fdelavega Jun 4, 2026
8c6395d
Merge branch 'main' into feat/new-search-page
fdelavega Jun 5, 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
11 changes: 5 additions & 6 deletions cypress/e2e/blog.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ describe('/blog',{
})

it('should create a blog entry correctly', () => {
cy.intercept({method: 'POST', url: 'http://proxy.docker:8004/domeblog'}, {statusCode: 201, body: blogEntry}).as('blogPOST')
cy.intercept({method: 'GET', url: '**/domeblog'}, {statusCode: 200, body: []}).as('blogGET')
cy.intercept({method: 'POST', url: '**/domeblog'}, {statusCode: 201, body: blogEntry}).as('blogPOST')
cy.visit('/blog')
cy.wait('@blogGET')
cy.getBySel('newBlogEntry').click()
cy.wait('@blogGET')
cy.getBySel('blogEntryTitle').type('Entry title')
cy.getBySel('textArea').type('Blog entry content')
cy.getBySel('blogEntryCreate').click()
cy.wait('@blogPOST').then((interception) => {
const payload = interception.request.body
expect(payload).to.have.property('title', blogEntry.title).and.be.a('string')
expect(payload).to.have.property('content', blogEntry.content).and.be.a('string')
expect(payload).to.have.property('partyId', blogEntry.partyId).and.be.a('string')
expect(payload).to.have.property('author', blogEntry.author).and.be.a('string')
expect(payload).to.deep.equal(blogEntry)
});
})

})

10 changes: 9 additions & 1 deletion cypress/support/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,15 @@ export const productOffering = {
]
}
export const blogEntry = {
title: 'Entry title', content: 'Blog entry content', partyId: 'urn:ngsi-ld:individual:b73dd8ce-b63f-4c5b-be07-ca7ea10ad78e', author: 'admin'
title: 'Entry title',
slug: 'entry-title',
featuredImage: '',
metaDescription: '',
excerpt: '',
tags: [],
partyId: 'urn:ngsi-ld:individual:b73dd8ce-b63f-4c5b-be07-ca7ea10ad78e',
author: 'admin',
content: 'Blog entry content'
}
export const productSpec = {
id: "urn:ngsi-ld:product-specification:305ac5ed-5845-467b-ab18-13856c2a8bac",
Expand Down
5 changes: 5 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ const routes: Routes = [
canActivate: [AuthGuard], data: { roles: ['admin'] }
},

{
path: 'browse',
loadComponent: () => import('./pages/browse/browse.component').then(c => c.BrowseComponent),
},

{
path: 'landing-page',
children: [{
Expand Down
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { CategoriesComponent } from './pages/admin/categories/categories.compone
import { CreateCategoryComponent } from './pages/admin/categories/create-category/create-category.component';
import { UpdateCategoryComponent } from './pages/admin/categories/update-category/update-category.component';
import { EmailComponent } from './pages/admin/email/email.component';
import { DefaultCatalogComponent } from './pages/admin/default-catalog/default-catalog.component';
import { SearchFiltersConfigComponent } from './pages/admin/search-filters-config/search-filters-config.component';
import { VerificationComponent } from './pages/admin/verification/verification.component';
import { CatalogsComponent } from "./pages/catalogs/catalogs.component";
import { BillingAddressComponent } from "./pages/checkout/billing-address/billing-address.component";
Expand Down Expand Up @@ -150,6 +152,8 @@ import { RequestValidationModalComponent } from './pages/seller-offerings/offeri
ContactUsComponent,
VerificationComponent,
EmailComponent,
SearchFiltersConfigComponent,
DefaultCatalogComponent,
InventoryResourcesComponent,
InventoryServicesComponent,
ProductInvDetailComponent,
Expand Down
134 changes: 121 additions & 13 deletions src/app/data/availableFilters.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,125 @@
export type FilterOption = {
name: string
label?: string
}

export type Filter = {
name: string
children?: Filter[]
}

const availableFilters: Filter[] = [
{
name: 'compliance_profile',
children: [
{ name: 'Baseline' },
{ name: 'Professional' },
{ name: 'Professional+' },
],
},
]
label?: string
source?: 'configured' | 'categoryRoot'
rootName?: string
children?: FilterOption[]
}

export type PrimaryCategoriesMode = 'rooted' | 'catalogFirstLevel'

export const searchCategoriesConfig: {
primaryCategoriesMode: PrimaryCategoriesMode
primaryRootName: string
} = {
primaryCategoriesMode: 'catalogFirstLevel',
primaryRootName: '',
}

const DEFAULT_SEARCH_CATEGORIES_CONFIG: {
primaryCategoriesMode: PrimaryCategoriesMode
primaryRootName: string
} = {
primaryCategoriesMode: 'catalogFirstLevel',
primaryRootName: '',
}

const DEFAULT_AVAILABLE_FILTERS: Filter[] = []

export const availableFilters: Filter[] = cloneFilters(DEFAULT_AVAILABLE_FILTERS)

type RuntimeSearchFiltersConfig = {
primaryCategoriesMode?: unknown
primaryRootName?: unknown
filters?: unknown
}

function cloneFilters(filters: Filter[]): Filter[] {
return (filters || []).map(filter => ({
...filter,
children: filter.children
? filter.children.map(child => ({
name: child.name,
label: child.label,
}))
: undefined,
}))
}

function normalizeFilterOption(raw: any): FilterOption | null {
if (!raw || typeof raw !== 'object' || typeof raw.name !== 'string' || raw.name.trim() === '') {
return null
}

return {
name: raw.name.trim(),
label: typeof raw.label === 'string' ? raw.label : undefined,
}
}

function normalizeFilter(raw: any): Filter | null {
if (!raw || typeof raw !== 'object' || typeof raw.name !== 'string' || raw.name.trim() === '') {
return null
}

const source = raw.source === 'categoryRoot' ? 'categoryRoot' : 'configured'
const filter: Filter = {
name: raw.name.trim(),
label: typeof raw.label === 'string' ? raw.label : undefined,
source,
rootName: source === 'categoryRoot' && typeof raw.rootName === 'string' ? raw.rootName : undefined,
children: source === 'configured' && Array.isArray(raw.children)
? raw.children.map(normalizeFilterOption).filter((child: FilterOption | null): child is FilterOption => !!child)
: undefined,
}

return filter
}

function getRuntimeSearchFiltersConfig(config: any): RuntimeSearchFiltersConfig | undefined {
if (!config || typeof config !== 'object') {
return undefined
}

return config.searchFilters
}

export function applyRuntimeSearchFiltersConfig(config: any): void {
const runtimeConfig = getRuntimeSearchFiltersConfig(config)

const nextMode = runtimeConfig?.primaryCategoriesMode === 'rooted'
? 'rooted'
: 'catalogFirstLevel'

if (nextMode === 'catalogFirstLevel') {
searchCategoriesConfig.primaryCategoriesMode = 'catalogFirstLevel'
searchCategoriesConfig.primaryRootName = ''
availableFilters.splice(0, availableFilters.length, ...cloneFilters(DEFAULT_AVAILABLE_FILTERS))
return
}

const nextRootName = typeof runtimeConfig?.primaryRootName === 'string'
? runtimeConfig.primaryRootName.trim()
: DEFAULT_SEARCH_CATEGORIES_CONFIG.primaryRootName

searchCategoriesConfig.primaryCategoriesMode = 'rooted'
searchCategoriesConfig.primaryRootName = nextRootName

const rawFilters = runtimeConfig?.filters
const normalizedFilters = Array.isArray(rawFilters)
? rawFilters.map(normalizeFilter).filter((filter): filter is Filter => !!filter)
: []

const nextFilters = normalizedFilters.length > 0
? normalizedFilters
: DEFAULT_AVAILABLE_FILTERS

availableFilters.splice(0, availableFilters.length, ...cloneFilters(nextFilters))
}

export default availableFilters
29 changes: 29 additions & 0 deletions src/app/data/categoryIcons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
import {
faBolt,
faBrain,
faCode,
faLayerGroup,
faPlug,
faServer,
faShieldHalved,
faUserTie
} from '@fortawesome/pro-solid-svg-icons';

export const DEFAULT_CATEGORY_ICON: IconDefinition = faLayerGroup;

export const CATEGORY_ICONS: Record<string, IconDefinition> = {
Security: faShieldHalved,
Infrastructure: faServer,
Productivity: faBolt,
DevOps: faCode,
Professional: faUserTie,
Specific: faLayerGroup,
'Data and AI': faBrain,
Integration: faPlug,
};

export function iconForCategory(name: string | null | undefined): IconDefinition {
if (!name) return DEFAULT_CATEGORY_ICON;
return CATEGORY_ICONS[name] ?? DEFAULT_CATEGORY_ICON;
}
1 change: 1 addition & 0 deletions src/app/offerings/featured/featured.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class FeaturedComponent implements OnInit {
} as Category;*/
this.localStorage.addCategoryFilter(cat);
this.eventMessage.emitAddedFilter(cat);
this.eventMessage.emitFiltersCommitted();
this.router.navigate(['/search']);

}
Expand Down
74 changes: 60 additions & 14 deletions src/app/pages/admin/admin.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,76 @@ <h1 class="mb-8 mt-4 text-4xl font-extrabold leading-none tracking-tight text-gr
<!-- Dropdown menu -->
<div id="dropdown-nav-content" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdown-nav">
<li class="px-4 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
Catalog
</li>
<li>
<a (click)="goToDefaultCatalog()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{{ 'ADMIN._defaultCatalog' | translate }}</a>
</li>
<li>
<a (click)="goToCategories()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{{ 'ADMIN._categories' | translate }}</a>
</li>
<li class="my-1 border-t border-gray-100 dark:border-gray-600"></li>
<li class="px-4 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
Business
</li>
<li>
<a (click)="goToVerification()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{{ 'ADMIN._verification' | translate }}</a>
</li>
<li>
<a (click)="goToRevenue()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{{ 'ADMIN._revenue' | translate }}</a>
</li>
<li class="my-1 border-t border-gray-100 dark:border-gray-600"></li>
<li class="px-4 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
Config
</li>
<li>
<a (click)="goToEmail()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{{ 'ADMIN._email' | translate }}</a>
</li>
<li>
<a (click)="goToSearchFilters()" class="cursor-pointer block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Search &amp; filters</a>
</li>
</ul>
</div>

<div class="w-full grid lg:grid-cols-20/80">
<div class="hidden lg:block">
<div class="w-48 h-fit text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white mb-8">
<button (click)="goToCategories()" id="categories-button" aria-current="true" class="block w-full px-4 py-2 text-white bg-primary-100 border-b border-gray-200 rounded-t-lg cursor-pointer dark:border-gray-600 hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._categories' | translate }}
</button>
<button (click)="goToVerification()" id="verify-button" class="block w-full px-4 py-2 border-gray-200 cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._verification' | translate }}
</button>
<button (click)="goToRevenue()" id="revenue-button" class="block w-full px-4 py-2 border-gray-200 cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._revenue' | translate }}
</button>
<button (click)="goToEmail()" id="email-button" class="block w-full px-4 py-2 border-gray-200 cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._email' | translate }}
</button>
<div class="w-48 h-fit text-sm font-medium text-gray-900 dark:text-white mb-8 space-y-4">
<div>
<p class="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Catalog</p>
<div class="bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
<button (click)="goToDefaultCatalog()" id="default-catalog-button" data-cy="adminDefaultCatalogSection" class="block w-full px-4 py-2 border-b border-gray-200 rounded-t-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._defaultCatalog' | translate }}
</button>
<button (click)="goToCategories()" id="categories-button" data-cy="adminCategoriesSection" aria-current="true" class="block w-full px-4 py-2 text-white bg-primary-100 rounded-b-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._categories' | translate }}
</button>
</div>
</div>

<div>
<p class="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Business</p>
<div class="bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
<button (click)="goToVerification()" id="verify-button" class="block w-full px-4 py-2 border-b border-gray-200 rounded-t-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._verification' | translate }}
</button>
<button (click)="goToRevenue()" id="revenue-button" class="block w-full px-4 py-2 rounded-b-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._revenue' | translate }}
</button>
</div>
</div>

<div>
<p class="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Config</p>
<div class="bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
<button (click)="goToEmail()" id="email-button" class="block w-full px-4 py-2 border-b border-gray-200 rounded-t-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
{{ 'ADMIN._email' | translate }}
</button>
<button (click)="goToSearchFilters()" id="search-filters-button" data-cy="adminSearchFiltersSection" class="block w-full px-4 py-2 rounded-b-lg cursor-pointer hover:bg-gray-100 hover:text-secondary-400 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white dark:focus:ring-gray-500 dark:focus:text-white">
Search &amp; filters
</button>
</div>
</div>
</div>
</div>

Expand All @@ -70,5 +110,11 @@ <h1 class="mb-8 mt-4 text-4xl font-extrabold leading-none tracking-tight text-gr
@if(show_email){
<email></email>
}
@if(show_search_filters){
<search-filters-config></search-filters-config>
}
@if(show_default_catalog){
<default-catalog></default-catalog>
}
</div>
</div>
</div>
Loading
Loading