diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts
index d927a789a..59d7fb9b9 100644
--- a/src/app/core/constants/ngxs-states.constant.ts
+++ b/src/app/core/constants/ngxs-states.constant.ts
@@ -6,6 +6,7 @@ import { MeetingsState } from '@osf/features/meetings/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 { SettingsState } from '@osf/features/project/settings/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';
@@ -25,6 +26,7 @@ export const STATES = [
DeveloperAppsState,
AccountSettingsState,
AnalyticsState,
+ SettingsState,
NotificationSubscriptionState,
ProjectOverviewState,
CollectionsState,
diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts
index b07c44ff7..d6dd8d3ed 100644
--- a/src/app/core/interceptors/auth.interceptor.ts
+++ b/src/app/core/interceptors/auth.interceptor.ts
@@ -7,8 +7,8 @@ export const authInterceptor: HttpInterceptorFn = (
next: HttpHandlerFn
): Observable> => {
const authToken = 'UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm';
- // OBJoUomBgbUuDgQo5JoaSKNya6XaYcd0ojAX1XOLmWi6J2arQPzByxyEi81fHE60drQUWv
- // UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm kyrylo
+ // '2rjFZwmdDG4rtKj7hGkEMO6XyHBM2lN7XBbsA1e8OqcFhOWu6Z7fQZiheu9RXtzSeVrgOt';
+ // 'OBJoUomBgbUuDgQo5JoaSKNya6XaYcd0ojAX1XOLmWi6J2arQPzByxyEi81fHE60drQUWv';
if (authToken) {
const authReq = req.clone({
diff --git a/src/app/features/my-projects/services/my-projects.service.ts b/src/app/features/my-projects/services/my-projects.service.ts
index e3b3dd940..8d0bc708c 100644
--- a/src/app/features/my-projects/services/my-projects.service.ts
+++ b/src/app/features/my-projects/services/my-projects.service.ts
@@ -4,7 +4,9 @@ import { map } from 'rxjs/operators';
import { inject, Injectable } from '@angular/core';
import { JsonApiService } from '@osf/core/services';
+import { SparseCollectionsResponse } from '@osf/features/collections/models';
import { SortOrder } from '@osf/shared/enums';
+import { NodeResponseModel, UpdateNodeRequestModel } from '@shared/models';
import { MyProjectsMapper } from '../mappers';
import {
@@ -23,13 +25,15 @@ import { environment } from 'src/environments/environment';
providedIn: 'root',
})
export class MyProjectsService {
- #jsonApiService = inject(JsonApiService);
- #sortFieldMap: Record = {
+ private apiUrl = environment.apiUrl;
+ private sortFieldMap: Record = {
title: 'title',
dateModified: 'date_modified',
};
- #getMyItems(
+ private readonly jsonApiService = inject(JsonApiService);
+
+ private getMyItems(
endpoint: EndpointType,
filters?: MyProjectsSearchFilters,
pageNumber?: number,
@@ -53,8 +57,8 @@ export class MyProjectsService {
params['page[size]'] = pageSize;
}
- if (filters?.sortColumn && this.#sortFieldMap[filters.sortColumn]) {
- const apiField = this.#sortFieldMap[filters.sortColumn];
+ if (filters?.sortColumn && this.sortFieldMap[filters.sortColumn]) {
+ const apiField = this.sortFieldMap[filters.sortColumn];
const sortPrefix = filters.sortOrder === SortOrder.Desc ? '-' : '';
params['sort'] = `${sortPrefix}${apiField}`;
} else {
@@ -65,7 +69,7 @@ export class MyProjectsService {
? environment.apiUrl + '/' + endpoint
: environment.apiUrl + '/users/me/' + endpoint;
- return this.#jsonApiService.get(url, params).pipe(
+ return this.jsonApiService.get(url, params).pipe(
map((response: MyProjectsJsonApiResponse) => ({
data: response.data.map((item: MyProjectsItemGetResponse) => MyProjectsMapper.fromResponse(item)),
links: response.links,
@@ -78,7 +82,22 @@ export class MyProjectsService {
pageNumber?: number,
pageSize?: number
): Observable {
- return this.#getMyItems('nodes', filters, pageNumber, pageSize);
+ return this.getMyItems('nodes', filters, pageNumber, pageSize);
+ }
+
+ getBookmarksCollectionId(): Observable {
+ const params: Record = {
+ 'fields[collections]': 'title,bookmarks',
+ };
+
+ return this.jsonApiService.get(environment.apiUrl + '/collections/', params).pipe(
+ map((response) => {
+ const bookmarksCollection = response.data.find(
+ (collection) => collection.attributes.title === 'Bookmarks' && collection.attributes.bookmarks
+ );
+ return bookmarksCollection?.id ?? '';
+ })
+ );
}
getMyRegistrations(
@@ -86,7 +105,7 @@ export class MyProjectsService {
pageNumber?: number,
pageSize?: number
): Observable {
- return this.#getMyItems('registrations', filters, pageNumber, pageSize);
+ return this.getMyItems('registrations', filters, pageNumber, pageSize);
}
getMyPreprints(
@@ -94,7 +113,7 @@ export class MyProjectsService {
pageNumber?: number,
pageSize?: number
): Observable {
- return this.#getMyItems('preprints', filters, pageNumber, pageSize);
+ return this.getMyItems('preprints', filters, pageNumber, pageSize);
}
getMyBookmarks(
@@ -103,7 +122,7 @@ export class MyProjectsService {
pageNumber?: number,
pageSize?: number
): Observable {
- return this.#getMyItems(`collections/${collectionId}/linked_nodes/`, filters, pageNumber, pageSize);
+ return this.getMyItems(`collections/${collectionId}/linked_nodes/`, filters, pageNumber, pageSize);
}
createProject(
@@ -147,8 +166,16 @@ export class MyProjectsService {
'fields[users]': 'family_name,full_name,given_name,middle_name',
};
- return this.#jsonApiService
+ return this.jsonApiService
.post(`${environment.apiUrl}/nodes/`, payload, params)
.pipe(map((response) => MyProjectsMapper.fromResponse(response)));
}
+
+ getProjectById(projectId: string): Observable {
+ return this.jsonApiService.get(`${this.apiUrl}/nodes/${projectId}`);
+ }
+
+ updateProjectById(model: UpdateNodeRequestModel): Observable {
+ return this.jsonApiService.patch(`${this.apiUrl}/nodes/${model?.data?.id}`, model);
+ }
}
diff --git a/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.html b/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.html
index fcfa1ac96..dca09e02d 100644
--- a/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.html
+++ b/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.html
@@ -7,6 +7,7 @@
type="text"
pInputText
placeholder="Type link name"
+ required
[ngModel]="linkName()"
(ngModelChange)="onLinkNameChange($event)"
/>
@@ -34,18 +35,19 @@
-
-
-
Component Name Example
-
-
-
-
Component Name Example
-
-
-
-
Component Name Example
-
+ @for (item of config.data.sharedComponents; track item.id) {
+
+ }
diff --git a/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts b/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts
index 1f50a0349..4651b3d24 100644
--- a/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts
+++ b/src/app/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component.ts
@@ -2,12 +2,14 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { Checkbox } from 'primeng/checkbox';
-import { DynamicDialogRef } from 'primeng/dynamicdialog';
+import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { InputText } from 'primeng/inputtext';
-import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ViewOnlyLinkNodeModel } from '@osf/features/project/settings/models';
+
@Component({
selector: 'osf-create-view-link-dialog',
imports: [Button, TranslatePipe, InputText, ReactiveFormsModule, FormsModule, Checkbox],
@@ -15,19 +17,65 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
styleUrl: './create-view-link-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class CreateViewLinkDialogComponent {
- dialogRef = inject(DynamicDialogRef);
+export class CreateViewLinkDialogComponent implements OnInit {
+ readonly dialogRef = inject(DynamicDialogRef);
+ protected readonly config = inject(DynamicDialogConfig);
+
+ protected selectedComponents = signal>({});
protected linkName = signal('');
anonymous = signal(true);
- componentExample1 = signal(true);
- componentExample2 = signal(false);
- componentExample3 = signal(false);
+ readonly projectId = signal('');
+
+ ngOnInit(): void {
+ const data = (this.config.data?.['sharedComponents'] as ViewOnlyLinkNodeModel[]) || [];
+ this.projectId.set(this.config.data?.['projectId']);
+ const initialState = data.reduce(
+ (acc, curr) => {
+ if (curr.id) {
+ acc[curr.id] = true;
+ }
+ return acc;
+ },
+ {} as Record
+ );
+ this.selectedComponents.set(initialState);
+ }
+
+ isCurrentProject(item: ViewOnlyLinkNodeModel): boolean {
+ return item.category === 'project' && item.id === this.projectId();
+ }
addContributor(): void {
- this.dialogRef.close();
+ if (!this.linkName()) return;
+
+ const components = (this.config.data?.['sharedComponents'] as ViewOnlyLinkNodeModel[]) || [];
+ const selectedIds = Object.entries(this.selectedComponents()).filter(([component, checked]) => checked);
+
+ const selected = components
+ .filter((comp: ViewOnlyLinkNodeModel) =>
+ selectedIds.find(([id, checked]: [string, boolean]) => id === comp.id && checked)
+ )
+ .map((comp) => ({
+ id: comp.id,
+ type: 'nodes',
+ }));
+
+ const data = {
+ attributes: {
+ name: this.linkName(),
+ anonymous: this.anonymous(),
+ },
+ nodes: selected,
+ };
+
+ this.dialogRef.close(data);
}
onLinkNameChange(value: string): void {
this.linkName.set(value);
}
+
+ onCheckboxToggle(id: string, checked: boolean): void {
+ this.selectedComponents.update((prev) => ({ ...prev, [id]: checked }));
+ }
}
diff --git a/src/app/features/project/contributors/contributors.component.html b/src/app/features/project/contributors/contributors.component.html
index 070f7354e..e33bb19eb 100644
--- a/src/app/features/project/contributors/contributors.component.html
+++ b/src/app/features/project/contributors/contributors.component.html
@@ -204,6 +204,7 @@ {{ 'project.contributors.viewOnly' | translate }}
[label]="'project.contributors.createButton' | translate"
(click)="createViewLink()"
>
-
+
+
diff --git a/src/app/features/project/contributors/contributors.component.ts b/src/app/features/project/contributors/contributors.component.ts
index 1f03080e8..4f27b547b 100644
--- a/src/app/features/project/contributors/contributors.component.ts
+++ b/src/app/features/project/contributors/contributors.component.ts
@@ -1,5 +1,8 @@
+import { createDispatchMap, select, Store } from '@ngxs/store';
+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
+import { ConfirmationService } from 'primeng/api';
import { Button } from 'primeng/button';
import { Checkbox } from 'primeng/checkbox';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
@@ -7,18 +10,29 @@ import { Select } from 'primeng/select';
import { TableModule, TablePageEvent } from 'primeng/table';
import { Tooltip } from 'primeng/tooltip';
-import { ChangeDetectionStrategy, Component, inject, output, signal } from '@angular/core';
-import { toSignal } from '@angular/core/rxjs-interop';
-import { FormsModule } from '@angular/forms';
-
-import { MY_PROJECTS_TABLE_PARAMS } from '@osf/core/constants';
-import { SearchInputComponent, ViewOnlyTableComponent } from '@osf/shared/components';
-import { SelectOption, TableParameters } from '@osf/shared/models';
-import { IS_WEB, IS_XSMALL } from '@osf/shared/utils';
-
-import { LinkTableModel } from '../settings/models';
+import { filter, map, of, switchMap } from 'rxjs';
-import { AddContributorDialogComponent, CreateViewLinkDialogComponent } from './components';
+import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, output, signal } from '@angular/core';
+import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
+import { FormsModule } from '@angular/forms';
+import { ActivatedRoute } from '@angular/router';
+
+import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants';
+import { AddContributorDialogComponent } from '@osf/features/project/contributors/components/add-contributor-dialog/add-contributor-dialog.component';
+import { CreateViewLinkDialogComponent } from '@osf/features/project/contributors/components/create-view-link-dialog/create-view-link-dialog.component';
+import { ViewOnlyLink, ViewOnlyLinkModel } from '@osf/features/project/settings/models';
+import {
+ CreateViewOnlyLink,
+ DeleteViewOnlyLink,
+ GetProjectDetails,
+ GetViewOnlyLinksTable,
+ SettingsSelectors,
+} from '@osf/features/project/settings/store';
+import { ViewOnlyTableComponent } from '@shared/components';
+import { SearchInputComponent } from '@shared/components/search-input/search-input.component';
+import { defaultConfirmationConfig } from '@shared/helpers';
+import { SelectOption, TableParameters } from '@shared/models';
+import { IS_WEB, IS_XSMALL } from '@shared/utils/breakpoints.tokens';
@Component({
selector: 'osf-contributors',
@@ -38,9 +52,16 @@ import { AddContributorDialogComponent, CreateViewLinkDialogComponent } from './
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [DialogService],
})
-export class ContributorsComponent {
- protected searchValue = signal('');
+export class ContributorsComponent implements OnInit {
readonly #translateService = inject(TranslateService);
+ readonly #dialogService = inject(DialogService);
+ readonly #route = inject(ActivatedRoute);
+ readonly #store = inject(Store);
+ readonly #destroyRef = inject(DestroyRef);
+ readonly #confirmationService = inject(ConfirmationService);
+
+ protected searchValue = signal('');
+
readonly items = signal([
{
name: 'John Doe',
@@ -67,6 +88,8 @@ export class ContributorsComponent {
educationHistory: 'https://some_link',
},
]);
+ readonly projectId = toSignal(this.#route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined));
+
protected readonly tableParams = signal({
...MY_PROJECTS_TABLE_PARAMS,
});
@@ -78,7 +101,9 @@ export class ContributorsComponent {
protected readonly isMobile = toSignal(inject(IS_XSMALL));
dialogRef: DynamicDialogRef | null = null;
- readonly #dialogService = inject(DialogService);
+
+ protected viewOnlyLinks = select(SettingsSelectors.getViewOnlyLinks);
+ protected projectDetails = select(SettingsSelectors.getProjectDetails);
protected readonly permissionsOptions: SelectOption[] = [
{
@@ -106,40 +131,18 @@ export class ContributorsComponent {
},
];
- tableData: LinkTableModel[] = [
- {
- linkName: 'name',
- sharedComponents: 'Project name',
- createdDate: new Date(),
- createdBy: 'Igor',
- anonymous: false,
- link: 'www.facebook.com',
- },
- {
- linkName: 'name',
- sharedComponents: 'Project name',
- createdDate: new Date(),
- createdBy: 'Igor',
- anonymous: false,
- link: 'www.facebook.com',
- },
- {
- linkName: 'name',
- sharedComponents: 'Project name',
- createdDate: new Date(),
- createdBy: 'Igor',
- anonymous: false,
- link: 'www.facebook.com',
- },
- {
- linkName: 'name',
- sharedComponents: 'Project name',
- createdDate: new Date(),
- createdBy: 'Igor',
- anonymous: false,
- link: 'www.facebook.com',
- },
- ];
+ protected actions = createDispatchMap({
+ getViewOnlyLinks: GetViewOnlyLinksTable,
+ getProjectDetails: GetProjectDetails,
+ });
+
+ ngOnInit(): void {
+ const id = this.projectId();
+ if (id) {
+ this.actions.getViewOnlyLinks(id);
+ this.actions.getProjectDetails(id);
+ }
+ }
protected onPermissionChange(value: string): void {
this.selectedPermission.set(value);
@@ -169,13 +172,47 @@ export class ContributorsComponent {
}
createViewLink() {
+ const sharedComponents = [
+ {
+ id: this.projectDetails().id,
+ title: this.projectDetails().attributes.title,
+ category: 'project',
+ },
+ ];
this.dialogRef = this.#dialogService.open(CreateViewLinkDialogComponent, {
width: '448px',
focusOnShow: false,
header: this.#translateService.instant('project.contributors.createLinkDialog.dialogTitle'),
+ data: { sharedComponents, projectId: this.projectId() },
closeOnEscape: true,
modal: true,
closable: true,
});
+
+ this.dialogRef.onClose
+ .pipe(
+ filter((res) => !!res),
+ switchMap((result) => this.#store.dispatch(new CreateViewOnlyLink(this.projectId(), result as ViewOnlyLink))),
+ takeUntilDestroyed(this.#destroyRef)
+ )
+ .subscribe();
+ }
+
+ deleteLinkItem(link: ViewOnlyLinkModel): void {
+ this.#confirmationService.confirm({
+ ...defaultConfirmationConfig,
+ message: this.#translateService.instant('myProjects.settings.delete.message'),
+ header: this.#translateService.instant('myProjects.settings.delete.title', {
+ name: link.name,
+ }),
+ acceptButtonProps: {
+ ...defaultConfirmationConfig.acceptButtonProps,
+ severity: 'danger',
+ label: this.#translateService.instant('settings.developerApps.list.deleteButton'),
+ },
+ accept: () => {
+ this.#store.dispatch(new DeleteViewOnlyLink(this.projectId(), link.id)).subscribe();
+ },
+ });
}
}
diff --git a/src/app/features/project/settings/components/accordion-table/accordion-table.component.html b/src/app/features/project/settings/components/accordion-table/accordion-table.component.html
deleted file mode 100644
index 918ce5a7a..000000000
--- a/src/app/features/project/settings/components/accordion-table/accordion-table.component.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
{{ title() }}
-
-
- @if (rightControls().length > 0) {
-
- @for (control of rightControls(); track control) {
-
- @if (control.label) {
-
- {{ control.label }}
-
- }
-
- @switch (control.type) {
- @case ('dropdown') {
-
- }
- @case ('text') {
-
- {{ control.value }}
-
- }
- }
-
- }
-
- }
-
-
-@if (expanded()) {
-
-
-
-}
diff --git a/src/app/features/project/settings/components/accordion-table/accordion-table.component.spec.ts b/src/app/features/project/settings/components/accordion-table/accordion-table.component.spec.ts
deleted file mode 100644
index 26df6a5d4..000000000
--- a/src/app/features/project/settings/components/accordion-table/accordion-table.component.spec.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AccordionTableComponent } from './accordion-table.component';
-
-describe('AccordionTableComponent', () => {
- let component: AccordionTableComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [AccordionTableComponent],
- }).compileComponents();
-
- fixture = TestBed.createComponent(AccordionTableComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/src/app/features/project/settings/components/accordion-table/accordion-table.component.ts b/src/app/features/project/settings/components/accordion-table/accordion-table.component.ts
deleted file mode 100644
index 2a62c7d78..000000000
--- a/src/app/features/project/settings/components/accordion-table/accordion-table.component.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Button } from 'primeng/button';
-import { SelectModule } from 'primeng/select';
-
-import { NgClass } from '@angular/common';
-import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-
-import { RightControl } from '../../models';
-
-@Component({
- selector: 'osf-accordion-table',
- imports: [NgClass, SelectModule, FormsModule, Button],
- templateUrl: './accordion-table.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class AccordionTableComponent {
- title = input.required();
-
- rightControls = input.required();
-
- expanded = signal(false);
-
- toggle() {
- this.expanded.set(!this.expanded());
- }
-}
diff --git a/src/app/features/project/settings/components/index.ts b/src/app/features/project/settings/components/index.ts
index f53a75cb2..d38eed9dd 100644
--- a/src/app/features/project/settings/components/index.ts
+++ b/src/app/features/project/settings/components/index.ts
@@ -1,7 +1,9 @@
export { ProjectDetailSettingAccordionComponent } from './project-detail-setting-accordion/project-detail-setting-accordion.component';
+export { ProjectSettingNotificationsComponent } from './project-setting-notifications/project-setting-notifications.component';
export { SettingsAccessRequestsCardComponent } from './settings-access-requests-card/settings-access-requests-card.component';
export { SettingsCommentingCardComponent } from './settings-commenting-card/settings-commenting-card.component';
export { SettingsProjectFormCardComponent } from './settings-project-form-card/settings-project-form-card.component';
+export { SettingsRedirectLinkComponent } from './settings-redirect-link/settings-redirect-link.component';
export { SettingsStorageLocationCardComponent } from './settings-storage-location-card/settings-storage-location-card.component';
export { SettingsViewOnlyLinksCardComponent } from './settings-view-only-links-card/settings-view-only-links-card.component';
export { SettingsWikiCardComponent } from './settings-wiki-card/settings-wiki-card.component';
diff --git a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html
index c9df99a30..a9a4321cb 100644
--- a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html
+++ b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.html
@@ -14,31 +14,30 @@
{{ title() }}
- @if (rightControls().length > 0) {
+ @if (rightControls()) {
- @for (control of rightControls(); track control.value) {
+ @for (control of rightControls(); let index = $index; track control.value) {
@if (control.label) {
- {{ control.label }}
+ {{ control.label | translate }}
}
- @switch (control.type) {
- @case ('dropdown') {
-
- }
- @case ('text') {
-
- {{ control.value }}
-
- }
- }
+
+
+ {{ item.label | translate }}
+
+
+
+ {{ item.label | translate }}
+
+
}
@@ -48,8 +47,8 @@
@if (expanded()) {
diff --git a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
index 12be4075a..d6e1dea54 100644
--- a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
+++ b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
@@ -1,23 +1,24 @@
+import { TranslatePipe } from '@ngx-translate/core';
+
import { Button } from 'primeng/button';
import { SelectModule } from 'primeng/select';
-import { LowerCasePipe, NgClass } from '@angular/common';
-import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';
+import { NgClass } from '@angular/common';
+import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { RightControl } from '../../models';
+import { RightControl } from '@osf/features/project/settings/models';
@Component({
selector: 'osf-project-detail-setting-accordion',
- imports: [NgClass, SelectModule, FormsModule, Button, LowerCasePipe],
+ imports: [NgClass, SelectModule, FormsModule, Button, TranslatePipe],
templateUrl: './project-detail-setting-accordion.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProjectDetailSettingAccordionComponent {
- title = input.required();
-
- rightControls = input.required();
-
+ emitValueChange = output<{ index: number; value: boolean | string }>();
+ title = input();
+ rightControls = input.required();
expanded = signal(false);
toggle() {
diff --git a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html
new file mode 100644
index 000000000..13d78cfc6
--- /dev/null
+++ b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html
@@ -0,0 +1,25 @@
+
+ {{ 'myProjects.settings.emailNotifications' | translate }}
+
+ {{ 'myProjects.settings.emailNotificationsText' | translate }}
+
+
+
+
+
+ {{ subscriptionEvent.Comments | notificationDescription: notifications()?.[0]?.frequency | translate }}
+
+
+
+
+
+
+ {{ subscriptionEvent.FileUpdated | notificationDescription: notifications()?.[1]?.frequency | translate }}
+
+
+
+
diff --git a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.spec.ts b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.spec.ts
new file mode 100644
index 000000000..b7d1665f5
--- /dev/null
+++ b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProjectSettingNotificationsComponent } from './project-setting-notifications.component';
+
+describe('ProjectSettingNotificationsComponent', () => {
+ let component: ProjectSettingNotificationsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ProjectSettingNotificationsComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProjectSettingNotificationsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts
new file mode 100644
index 000000000..bdb3cdc58
--- /dev/null
+++ b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts
@@ -0,0 +1,66 @@
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { Card } from 'primeng/card';
+
+import { ChangeDetectionStrategy, Component, effect, input, output } from '@angular/core';
+
+import { ProjectDetailSettingAccordionComponent } from '@osf/features/project/settings/components';
+import { NotificationDescriptionPipe } from '@osf/features/project/settings/pipes';
+import { NotificationSubscription } from '@osf/features/settings/notifications/models';
+import { SubscriptionEvent, SubscriptionFrequency } from '@shared/enums';
+
+import { RightControl } from '../../models';
+
+@Component({
+ selector: 'osf-project-setting-notifications',
+ imports: [Card, TranslatePipe, ProjectDetailSettingAccordionComponent, NotificationDescriptionPipe],
+ templateUrl: './project-setting-notifications.component.html',
+ styleUrl: '../../settings.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ProjectSettingNotificationsComponent {
+ notificationEmitValue = output<{ event: SubscriptionEvent; frequency: SubscriptionFrequency }>();
+ title = input();
+ notifications = input();
+
+ allAccordionData: RightControl[] | undefined = [];
+
+ protected readonly subscriptionEvent = SubscriptionEvent;
+ protected subscriptionFrequencyOptions = Object.entries(SubscriptionFrequency).map(([key, value]) => ({
+ label: key,
+ value,
+ }));
+
+ constructor() {
+ effect(() => {
+ this.allAccordionData = this.notifications()?.map((notification) => {
+ if (notification.event === SubscriptionEvent.Comments) {
+ return {
+ label: 'settings.notifications.notificationPreferences.items.comments',
+ value: notification.frequency as string,
+ type: 'dropdown',
+ options: this.subscriptionFrequencyOptions,
+ event: notification.event,
+ } as RightControl;
+ } else {
+ return {
+ label: 'settings.notifications.notificationPreferences.items.files',
+ value: notification.frequency as string,
+ type: 'dropdown',
+ options: this.subscriptionFrequencyOptions,
+ event: notification.event,
+ } as RightControl;
+ }
+ });
+ });
+ }
+
+ changeEmittedValue(emittedValue: { index: number; value: boolean | string }): void {
+ if (this.allAccordionData) {
+ this.notificationEmitValue.emit({
+ event: this.allAccordionData[emittedValue.index].event as SubscriptionEvent,
+ frequency: emittedValue.value as SubscriptionFrequency,
+ });
+ }
+ }
+}
diff --git a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
index 7ac5e9139..b7f7b3070 100644
--- a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
+++ b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
@@ -5,7 +5,8 @@ {{ 'myProjects.settings.accessRequests' | translate }}<
diff --git a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.ts b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.ts
index efc4d9a51..d2a9289fd 100644
--- a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.ts
+++ b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.ts
@@ -3,16 +3,17 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Card } from 'primeng/card';
import { Checkbox } from 'primeng/checkbox';
-import { ChangeDetectionStrategy, Component, input } from '@angular/core';
-import { FormControl } from '@angular/forms';
+import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
+import { FormsModule } from '@angular/forms';
@Component({
selector: 'osf-settings-access-requests-card',
- imports: [Checkbox, TranslatePipe, Card],
+ imports: [Checkbox, TranslatePipe, Card, FormsModule],
templateUrl: './settings-access-requests-card.component.html',
styleUrl: '../../settings.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsAccessRequestsCardComponent {
- formControl = input.required();
+ accessRequestChange = output();
+ accessRequest = input.required();
}
diff --git a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html
index 9e82b5098..1fed0b574 100644
--- a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html
+++ b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html
@@ -6,9 +6,11 @@ {{ 'myProjects.settings.commenting' | translate }}
+
{{ 'myProjects.settings.contributorsCanPost' | translate }}
@@ -16,8 +18,9 @@ {{ 'myProjects.settings.commenting' | translate }}
diff --git a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts
index 2315e3322..4d46cc4a3 100644
--- a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts
+++ b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts
@@ -3,11 +3,9 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Card } from 'primeng/card';
import { RadioButton } from 'primeng/radiobutton';
-import { ChangeDetectionStrategy, Component, model } from '@angular/core';
+import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { ShareIndexingEnum } from '@osf/shared/enums';
-
@Component({
selector: 'osf-settings-commenting-card',
imports: [Card, RadioButton, TranslatePipe, FormsModule],
@@ -16,5 +14,6 @@ import { ShareIndexingEnum } from '@osf/shared/enums';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsCommentingCardComponent {
- commenting = model();
+ anyoneCanComment = input.required();
+ readonly anyoneCanCommentChange = output();
}
diff --git a/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html b/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
index c1b323fcc..697b2daf1 100644
--- a/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
+++ b/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
@@ -1,7 +1,7 @@
{{ 'myProjects.settings.project' | translate }}
-