Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6b9f540
feat(replace-icons): replaced icons to the new ones, rebuild fantasti…
rnastyuk May 6, 2025
ce35323
Merge branch 'main' into feat/144-replace-icons
rnastyuk May 6, 2025
1135d50
feat(my-projects-details-submenu): added project details menu to the …
rnastyuk May 6, 2025
f795cb9
Merge branch 'main' into feat/143-my-projects-details-submenu
rnastyuk May 7, 2025
b9d7a29
Merge branch 'refs/heads/main' into feat/148-project-overview-api-int…
rnastyuk May 12, 2025
a0bebf5
Merge branch 'refs/heads/main' into feat/148-project-overview-api-int…
rnastyuk May 14, 2025
ddae89a
Merge branch 'refs/heads/main' into feat/148-project-overview-api-int…
rnastyuk May 20, 2025
5f004ea
Merge branch 'refs/heads/main' into feat/148-project-overview-api-int…
rnastyuk May 20, 2025
cb4c461
feat(project-overview): added base project overview functionality
rnastyuk May 21, 2025
29ad0d5
Merge branch 'main' into feat/148-project-overview-api-integration
rnastyuk May 21, 2025
cd3d713
Merge branch 'main' into feat/148-project-overview-api-integration
rnastyuk May 22, 2025
5d658bd
refactor(project-overview): split project-overview into smaller compo…
rnastyuk May 22, 2025
5602b0b
feat(project-overview): added success toasts to the project-overview
rnastyuk May 22, 2025
e5bb99a
feat(project-overview): added success toasts for the toggle publicity
rnastyuk May 22, 2025
a72fe70
fix(project-overview): added submitting state change in the handle er…
rnastyuk May 22, 2025
7f98ec7
Merge branch 'main' into feat/148-project-overview-api-integration
rnastyuk May 22, 2025
3b59cb1
fix(project-overview): fixed comments
rnastyuk May 22, 2025
960c9f5
fix(project-overview): added destroy refs
rnastyuk May 22, 2025
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
4 changes: 2 additions & 2 deletions src/app/core/components/header/header.component.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<osf-breadcrumb />
<!--<a [routerLink]="authButtonLink()" class="p-button">{{ authButtonText() | translate }}</a>-->

<div class="dropdown-button ml-auto" style="min-width: 7rem">
<div class="dropdown-button ml-auto">
<p-button (click)="menu.toggle($event)">
{{ currentUser()?.fullName }}
<i class="pi pi-chevron-down"></i>
</p-button>

<p-menu #menu [model]="items" popup>
<p-menu appendTo="body" #menu [model]="items" popup>
<ng-template #item let-item>
<a class="p-menu-item-link">{{ item.label | translate }}</a>
</ng-template>
Expand Down
6 changes: 6 additions & 0 deletions src/app/core/constants/ngxs-states.constant.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { AuthState } from '@core/store/auth';
import { UserState } from '@core/store/user';
import { CollectionsState } from '@osf/features/collections/store';
import { InstitutionsState } from '@osf/features/institutions/store';
import { MyProjectsState } from '@osf/features/my-projects/store';
import { AnalyticsState } from '@osf/features/project/analytics/store';
import { ProjectOverviewState } from '@osf/features/project/overview/store';
import { WikiState } from '@osf/features/project/wiki/store/wiki.state';
import { AccountSettingsState } from '@osf/features/settings/account-settings/store/account-settings.state';
import { AddonsState } from '@osf/features/settings/addons/store';
import { DeveloperAppsState } from '@osf/features/settings/developer-apps/store';
Expand All @@ -22,4 +25,7 @@ export const STATES = [
AccountSettingsState,
AnalyticsState,
NotificationSubscriptionState,
ProjectOverviewState,
CollectionsState,
WikiState,
];
2 changes: 1 addition & 1 deletion src/app/core/constants/storage-locations.constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StorageLocation } from '@osf/features/my-projects/entities/storage-location.interface';
import { StorageLocation } from '@osf/features/my-projects/models/storage-location.model';

export const STORAGE_LOCATIONS: StorageLocation[] = [
{ label: 'United States', value: 'us' },
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/services/json-api/json-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class JsonApiService {
return httpParams;
}

post<T>(url: string, body: unknown, params?: Record<string, unknown>): Observable<T> {
post<T>(url: string, body?: unknown, params?: Record<string, unknown>): Observable<T> {
return this.http
.post<JsonApiResponse<T, null>>(url, body, {
params: this.buildHttpParams(params),
Expand Down
13 changes: 13 additions & 0 deletions src/app/features/collections/models/collections.models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface CollectionAttributes {
title: string;
bookmarks: boolean;
}

export interface Collection {
id: string;
attributes: CollectionAttributes;
}

export interface SparseCollectionsResponse {
data: Collection[];
}
1 change: 1 addition & 0 deletions src/app/features/collections/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './collections.models';
58 changes: 58 additions & 0 deletions src/app/features/collections/services/collections.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { map, Observable } from 'rxjs';

import { inject, Injectable } from '@angular/core';

import { JsonApiService } from '@core/services/json-api/json-api.service';
import { SparseCollectionsResponse } from '@osf/features/collections/models/collections.models';

import { environment } from '../../../../environments/environment';

@Injectable({
providedIn: 'root',
})
export class CollectionsService {
#jsonApiService = inject(JsonApiService);

getBookmarksCollectionId(): Observable<string> {
const params: Record<string, unknown> = {
'fields[collections]': 'title,bookmarks',
};

return this.#jsonApiService.get<SparseCollectionsResponse>(environment.apiUrl + '/collections/', params).pipe(
map((response: SparseCollectionsResponse) => {
const bookmarksCollection = response.data.find(
(collection) => collection.attributes.title === 'Bookmarks' && collection.attributes.bookmarks
);
return bookmarksCollection?.id ?? '';
})
);
}

addProjectToBookmarks(bookmarksId: string, projectId: string): Observable<void> {
const url = `${environment.apiUrl}/collections/${bookmarksId}/relationships/linked_nodes/`;
const payload = {
data: [
{
type: 'linked_nodes',
id: projectId,
},
],
};

return this.#jsonApiService.post<void>(url, payload);
}

removeProjectFromBookmarks(bookmarksId: string, projectId: string): Observable<void> {
const url = `${environment.apiUrl}/collections/${bookmarksId}/relationships/linked_nodes/`;
const payload = {
data: [
{
type: 'linked_nodes',
id: projectId,
},
],
};

return this.#jsonApiService.delete(url, payload);
}
}
1 change: 1 addition & 0 deletions src/app/features/collections/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './collections.service';
25 changes: 25 additions & 0 deletions src/app/features/collections/store/collections.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export class GetBookmarksCollectionId {
static readonly type = '[Collections] Get Bookmarks Collection Id';
}

export class AddProjectToBookmarks {
static readonly type = '[Collections] Add Project To Bookmarks';

constructor(
public bookmarksId: string,
public projectId: string
) {}
}

export class RemoveProjectFromBookmarks {
static readonly type = '[Collections] Remove Project From Bookmarks';

constructor(
public bookmarksId: string,
public projectId: string
) {}
}

export class ClearCollections {
static readonly type = '[Collections] Clear Collections';
}
5 changes: 5 additions & 0 deletions src/app/features/collections/store/collections.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AsyncStateModel } from '@osf/shared/models/store';

export interface CollectionsStateModel {
bookmarksId: AsyncStateModel<string>;
}
21 changes: 21 additions & 0 deletions src/app/features/collections/store/collections.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Selector } from '@ngxs/store';

import { CollectionsStateModel } from './collections.model';
import { CollectionsState } from './collections.state';

export class CollectionsSelectors {
@Selector([CollectionsState])
static getBookmarksCollectionId(state: CollectionsStateModel) {
return state.bookmarksId.data;
}

@Selector([CollectionsState])
static getBookmarksCollectionIdLoading(state: CollectionsStateModel) {
return state.bookmarksId.isLoading;
}

@Selector([CollectionsState])
static getBookmarksCollectionIdSubmitting(state: CollectionsStateModel) {
return state.bookmarksId.isSubmitting;
}
}
120 changes: 120 additions & 0 deletions src/app/features/collections/store/collections.state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Action, State, StateContext } from '@ngxs/store';

import { catchError, tap, throwError } from 'rxjs';

import { Injectable } from '@angular/core';

import { CollectionsService } from '../services/collections.service';

import {
AddProjectToBookmarks,
ClearCollections,
GetBookmarksCollectionId,
RemoveProjectFromBookmarks,
} from './collections.actions';
import { CollectionsStateModel } from './collections.model';

const COLLECTIONS_DEFAULTS: CollectionsStateModel = {
bookmarksId: {
data: '',
isLoading: false,
isSubmitting: false,
error: null,
},
};

@State<CollectionsStateModel>({
name: 'collections',
defaults: COLLECTIONS_DEFAULTS,
})
@Injectable()
export class CollectionsState {
constructor(private collectionsService: CollectionsService) {}

@Action(GetBookmarksCollectionId)
getBookmarksCollectionId(ctx: StateContext<CollectionsStateModel>) {
const state = ctx.getState();
ctx.patchState({
bookmarksId: {
...state.bookmarksId,
isLoading: true,
},
});

return this.collectionsService.getBookmarksCollectionId().pipe(
tap((res) => {
ctx.patchState({
bookmarksId: {
data: res,
isLoading: false,
error: null,
},
});
}),
catchError((error) => this.handleError(ctx, 'bookmarksId', error))
);
}

@Action(AddProjectToBookmarks)
addProjectToBookmarks(ctx: StateContext<CollectionsStateModel>, action: AddProjectToBookmarks) {
const state = ctx.getState();
ctx.patchState({
bookmarksId: {
...state.bookmarksId,
isSubmitting: true,
},
});

return this.collectionsService.addProjectToBookmarks(action.bookmarksId, action.projectId).pipe(
tap(() => {
ctx.patchState({
bookmarksId: {
...state.bookmarksId,
isSubmitting: false,
},
});
}),
catchError((error) => this.handleError(ctx, 'bookmarksId', error))
);
}

@Action(RemoveProjectFromBookmarks)
removeProjectFromBookmarks(ctx: StateContext<CollectionsStateModel>, action: RemoveProjectFromBookmarks) {
const state = ctx.getState();
ctx.patchState({
bookmarksId: {
...state.bookmarksId,
isSubmitting: true,
},
});

return this.collectionsService.removeProjectFromBookmarks(action.bookmarksId, action.projectId).pipe(
tap(() => {
ctx.patchState({
bookmarksId: {
...state.bookmarksId,
isSubmitting: false,
},
});
}),
catchError((error) => this.handleError(ctx, 'bookmarksId', error))
);
}

@Action(ClearCollections)
clearCollections(ctx: StateContext<CollectionsStateModel>) {
ctx.patchState(COLLECTIONS_DEFAULTS);
}

private handleError(ctx: StateContext<CollectionsStateModel>, section: keyof CollectionsStateModel, error: Error) {
ctx.patchState({
[section]: {
...ctx.getState()[section],
isLoading: false,
isSubmitting: false,
error: error.message,
},
});
return throwError(() => error);
}
}
4 changes: 4 additions & 0 deletions src/app/features/collections/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './collections.actions';
export * from './collections.model';
export * from './collections.selectors';
export * from './collections.state';
10 changes: 5 additions & 5 deletions src/app/features/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Store } from '@ngxs/store';
import { select, Store } from '@ngxs/store';

import { TranslatePipe, TranslateService } from '@ngx-translate/core';

Expand All @@ -16,8 +16,8 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants';
import { ConfirmEmailComponent } from '@osf/features/home/components/confirm-email/confirm-email.component';
import { GetUserInstitutions } from '@osf/features/institutions/store';
import { MyProjectsItem } from '@osf/features/my-projects/entities/my-projects.entities';
import { MyProjectsSearchFilters } from '@osf/features/my-projects/entities/my-projects-search-filters.models';
import { MyProjectsItem } from '@osf/features/my-projects/models/my-projects.models';
import { MyProjectsSearchFilters } from '@osf/features/my-projects/models/my-projects-search-filters.models';
import { ClearMyProjects, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store';
import { AccountSettingsService } from '@osf/features/settings/account-settings/services/account-settings.service';
import { AddProjectFormComponent } from '@shared/components/add-project-form/add-project-form.component';
Expand Down Expand Up @@ -60,8 +60,8 @@ export class HomeComponent implements OnInit {
...MY_PROJECTS_TABLE_PARAMS,
});

protected readonly projects = this.#store.selectSignal(MyProjectsSelectors.getProjects);
protected readonly totalProjectsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalProjectsCount);
protected readonly projects = select(MyProjectsSelectors.getProjects);
protected readonly totalProjectsCount = select(MyProjectsSelectors.getTotalProjects);

protected readonly filteredProjects = computed(() => {
const search = this.searchValue().toLowerCase();
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/my-projects/mappers/my-projects.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MyProjectsItem, MyProjectsItemGetResponse } from '@osf/features/my-projects/entities/my-projects.entities';
import { MyProjectsItem, MyProjectsItemGetResponse } from '@osf/features/my-projects/models/my-projects.models';

export class MyProjectsMapper {
static fromResponse(response: MyProjectsItemGetResponse): MyProjectsItem {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,3 @@ export interface MyProjectsJsonApiResponse extends JsonApiResponse<MyProjectsIte
};
};
}

export interface CollectionAttributes {
title: string;
bookmarks: boolean;
}

export interface Collection {
id: string;
attributes: CollectionAttributes;
}

export interface SparseCollectionsResponse {
data: Collection[];
}
16 changes: 8 additions & 8 deletions src/app/features/my-projects/my-projects.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ import { ActivatedRoute, Router } from '@angular/router';

import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants';
import { parseQueryFilterParams } from '@core/helpers/http.helper';
import { CollectionsSelectors, GetBookmarksCollectionId } from '@osf/features/collections/store';
import { GetUserInstitutions } from '@osf/features/institutions/store';
import { MyProjectsItem } from '@osf/features/my-projects/entities/my-projects.entities';
import { MyProjectsSearchFilters } from '@osf/features/my-projects/entities/my-projects-search-filters.models';
import { MyProjectsItem } from '@osf/features/my-projects/models/my-projects.models';
import { MyProjectsSearchFilters } from '@osf/features/my-projects/models/my-projects-search-filters.models';
import {
ClearMyProjects,
GetBookmarksCollectionId,
GetMyBookmarks,
GetMyPreprints,
GetMyProjects,
Expand Down Expand Up @@ -116,12 +116,12 @@ export class MyProjectsComponent implements OnInit {
protected readonly registrations = this.#store.selectSignal(MyProjectsSelectors.getRegistrations);
protected readonly preprints = this.#store.selectSignal(MyProjectsSelectors.getPreprints);
protected readonly bookmarks = this.#store.selectSignal(MyProjectsSelectors.getBookmarks);
protected readonly totalProjectsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalProjectsCount);
protected readonly totalRegistrationsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalRegistrationsCount);
protected readonly totalPreprintsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalPreprintsCount);
protected readonly totalBookmarksCount = this.#store.selectSignal(MyProjectsSelectors.getTotalBookmarksCount);
protected readonly totalProjectsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalProjects);
protected readonly totalRegistrationsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalRegistrations);
protected readonly totalPreprintsCount = this.#store.selectSignal(MyProjectsSelectors.getTotalPreprints);
protected readonly totalBookmarksCount = this.#store.selectSignal(MyProjectsSelectors.getTotalBookmarks);

protected readonly bookmarksCollectionId = this.#store.selectSignal(MyProjectsSelectors.getBookmarksCollectionId);
protected readonly bookmarksCollectionId = this.#store.selectSignal(CollectionsSelectors.getBookmarksCollectionId);

constructor() {
this.#setupQueryParamsEffect();
Expand Down
Loading