Skip to content

Commit f1b744e

Browse files
authored
fix(breadcrumbs): Setting proper provider and institution name to the breadcrumbs (#584)
1 parent e67522e commit f1b744e

File tree

7 files changed

+61
-31
lines changed

7 files changed

+61
-31
lines changed

src/app/core/components/breadcrumb/breadcrumb.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<div class="breadcrumbs flex align-items-center gap-2 md:p-5 md:pb-0 xl:p-0">
33
<osf-icon iconClass="fas fa-home"></osf-icon>
44
<p>/</p>
5-
@for (url of parsedUrl(); track $index; let l = $last) {
6-
{{ url }}
5+
@for (breadcrumb of breadcrumbs(); track $index; let l = $last) {
6+
{{ breadcrumb }}
77
@if (!l) {
88
/
99
}

src/app/core/components/breadcrumb/breadcrumb.component.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import { select } from '@ngxs/store';
2+
13
import { filter, map, startWith } from 'rxjs';
24

35
import { Component, computed, inject } from '@angular/core';
46
import { toSignal } from '@angular/core/rxjs-interop';
5-
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
7+
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
68

9+
import { ProviderSelectors } from '@core/store/provider';
710
import { RouteData } from '@osf/core/models';
11+
import { InstitutionsAdminSelectors } from '@osf/features/admin-institutions/store';
812
import { IconComponent } from '@osf/shared/components';
13+
import { InstitutionsSearchSelectors } from '@shared/stores/institutions-search';
914

1015
@Component({
1116
selector: 'osf-breadcrumb',
@@ -17,14 +22,9 @@ export class BreadcrumbComponent {
1722
private readonly router = inject(Router);
1823
private readonly route = inject(ActivatedRoute);
1924

20-
readonly url = toSignal(
21-
this.router.events.pipe(
22-
filter((event) => event instanceof NavigationEnd),
23-
map(() => this.router.url),
24-
startWith(this.router.url)
25-
),
26-
{ initialValue: this.router.url }
27-
);
25+
currentProvider = select(ProviderSelectors.getCurrentProvider);
26+
institution = select(InstitutionsSearchSelectors.getInstitution);
27+
institutionDashboard = select(InstitutionsAdminSelectors.getInstitution);
2828

2929
readonly routeData = toSignal(
3030
this.router.events.pipe(
@@ -37,13 +37,48 @@ export class BreadcrumbComponent {
3737

3838
readonly showBreadcrumb = computed(() => this.routeData()?.skipBreadcrumbs !== true);
3939

40-
readonly parsedUrl = computed(() =>
41-
this.url()
42-
.split('?')[0]
43-
.split('/')
44-
.filter(Boolean)
45-
.map((segment) => segment.replace(/-/g, ' '))
46-
);
40+
readonly breadcrumbs = computed<string[]>(() => {
41+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
42+
const currentRoute = this.routeData(); // Trigger recomputation when route data changes
43+
const providerName = this.currentProvider()?.name;
44+
const institution = this.institution()?.name;
45+
const institutionDashboard = this.institutionDashboard()?.name;
46+
47+
return this.buildBreadcrumbs(this.route.root.snapshot, providerName, institution ?? institutionDashboard);
48+
});
49+
50+
private buildBreadcrumbs(
51+
route: ActivatedRouteSnapshot,
52+
providerName: string | undefined,
53+
institutionName: string | undefined,
54+
segments: string[] = []
55+
): string[] {
56+
for (const segment of route.url) {
57+
const segmentPath = segment.path;
58+
let label = segmentPath.replace(/-/g, ' ');
59+
60+
const isProviderIdSegment = route.params['providerId'] === segmentPath;
61+
const isInstitutionIdSegment = route.params['institutionId'] === segmentPath;
62+
63+
if (isProviderIdSegment && providerName) {
64+
label = providerName;
65+
}
66+
67+
if (isInstitutionIdSegment && institutionName) {
68+
label = institutionName;
69+
}
70+
71+
if (label && label.trim().length > 0) {
72+
segments.push(label);
73+
}
74+
}
75+
76+
if (route.firstChild) {
77+
return this.buildBreadcrumbs(route.firstChild, providerName, institutionName, segments);
78+
}
79+
80+
return segments;
81+
}
4782

4883
private getCurrentRouteData(): RouteData {
4984
let currentRoute = this.route;

src/app/core/constants/ngxs-states.constant.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ProviderState } from '@core/store/provider';
22
import { UserState } from '@core/store/user';
33
import { UserEmailsState } from '@core/store/user-emails';
4+
import { InstitutionsAdminState } from '@osf/features/admin-institutions/store';
45
import { FilesState } from '@osf/features/files/store';
56
import { MetadataState } from '@osf/features/metadata/store';
67
import { ProjectOverviewState } from '@osf/features/project/overview/store';
@@ -9,6 +10,7 @@ import { AddonsState, CurrentResourceState, WikiState } from '@osf/shared/stores
910
import { BannersState } from '@osf/shared/stores/banners';
1011
import { GlobalSearchState } from '@shared/stores/global-search';
1112
import { InstitutionsState } from '@shared/stores/institutions';
13+
import { InstitutionsSearchState } from '@shared/stores/institutions-search';
1214
import { LicensesState } from '@shared/stores/licenses';
1315
import { MyResourcesState } from '@shared/stores/my-resources';
1416
import { RegionsState } from '@shared/stores/regions';
@@ -20,6 +22,8 @@ export const STATES = [
2022
ProviderState,
2123
MyResourcesState,
2224
InstitutionsState,
25+
InstitutionsAdminState,
26+
InstitutionsSearchState,
2327
ProjectOverviewState,
2428
WikiState,
2529
RegistrationsState,

src/app/features/admin-institutions/admin-institutions.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class AdminInstitutionsComponent implements OnInit {
3737
selectedTab = AdminInstitutionResourceTab.Summary;
3838

3939
ngOnInit() {
40-
const institutionId = this.route.snapshot.params['institution-id'];
40+
const institutionId = this.route.snapshot.params['institutionId'];
4141

4242
if (institutionId) {
4343
this.actions.fetchInstitution(institutionId);

src/app/features/admin-institutions/routes.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { provideStates } from '@ngxs/store';
2-
31
import { Routes } from '@angular/router';
42

53
import {
@@ -12,13 +10,10 @@ import {
1210

1311
import { AdminInstitutionsComponent } from './admin-institutions.component';
1412

15-
import { InstitutionsAdminState } from 'src/app/features/admin-institutions/store';
16-
1713
export const routes: Routes = [
1814
{
1915
path: '',
2016
component: AdminInstitutionsComponent,
21-
providers: [provideStates([InstitutionsAdminState])],
2217
children: [
2318
{
2419
path: '',

src/app/features/institutions/institutions.routes.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { provideStates } from '@ngxs/store';
2-
31
import { Routes } from '@angular/router';
42

53
import { authGuard } from '@core/guards';
6-
import { InstitutionsSearchState } from '@shared/stores/institutions-search';
74

85
import { InstitutionsComponent } from './institutions.component';
96
import { InstitutionsListComponent, InstitutionsSearchComponent } from './pages';
@@ -19,12 +16,11 @@ export const routes: Routes = [
1916
component: InstitutionsListComponent,
2017
},
2118
{
22-
path: ':institution-id',
19+
path: ':institutionId',
2320
component: InstitutionsSearchComponent,
24-
providers: [provideStates([InstitutionsSearchState])],
2521
},
2622
{
27-
path: ':institution-id/dashboard',
23+
path: ':institutionId/dashboard',
2824
canActivate: [authGuard],
2925
loadChildren: () => import('../admin-institutions/routes').then((inst) => inst.routes),
3026
},

src/app/features/institutions/pages/institutions-search/institutions-search.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class InstitutionsSearchComponent implements OnInit {
3333
readonly resourceTabOptions = SEARCH_TAB_OPTIONS;
3434

3535
ngOnInit(): void {
36-
const institutionId = this.route.snapshot.params['institution-id'];
36+
const institutionId = this.route.snapshot.params['institutionId'];
3737
if (institutionId) {
3838
this.actions.fetchInstitution(institutionId).subscribe({
3939
next: () => {

0 commit comments

Comments
 (0)