Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
86e0a13
feat(dashboard): Prevent project creation
futa-ikeda May 15, 2026
8b197a3
feat(my-projects): Prevent project creation
futa-ikeda May 15, 2026
d6b3fd6
feat(dashboard): Update translation key
futa-ikeda May 20, 2026
df7ea86
Merge pull request #1 from futa-ikeda/feature/eng-11086
futa-ikeda May 20, 2026
62ea709
feat(duplicates): Prevent duplicate creation
futa-ikeda May 20, 2026
80caa07
Merge pull request #2 from futa-ikeda/feature/eng-11084
futa-ikeda May 20, 2026
59b79f4
feat(project-components): Prevent component cretion
futa-ikeda May 20, 2026
dd305b8
Merge pull request #3 from futa-ikeda/feature/eng-11085
futa-ikeda May 22, 2026
bc2cf03
feat(preprint-component): Prevent project creation
futa-ikeda May 21, 2026
6c93302
Merge pull request #4 from futa-ikeda/feature/eng-11086-preprint
futa-ikeda May 26, 2026
3a817d3
feat(registration-overview): Hide associated project when hasProject …
futa-ikeda May 28, 2026
7941d0c
feat(registries): Allow undefined associatedProjectId
futa-ikeda May 28, 2026
05e7897
Merge pull request #5 from futa-ikeda/feature/eng-11266
futa-ikeda May 28, 2026
47b8b5d
feat(preprints): Allow Supplements step to be toggled on or off
futa-ikeda Jun 1, 2026
926fedb
Merge pull request #6 from futa-ikeda/feature/eng-11294
futa-ikeda Jun 2, 2026
c4eda55
feat(registrations): Update file upload description
futa-ikeda Jun 2, 2026
5d42bdb
feat(i18n): Update wording
futa-ikeda Jun 2, 2026
d6881e2
fix(registrations): Fix test name typo
futa-ikeda Jun 2, 2026
9b29bd8
Merge pull request #7 from futa-ikeda/feature/eng-11293
futa-ikeda Jun 2, 2026
655ed23
refactor(UserSelectors): add new selector to indicate if project crea…
futa-ikeda Jun 17, 2026
b445787
Merge pull request #11 from futa-ikeda/refactor/project-creation-sele…
futa-ikeda Jun 17, 2026
76d54e9
feat(wiki): Prevent wiki edits based on feature flag value
futa-ikeda Jun 17, 2026
0c35a4c
Merge pull request #8 from futa-ikeda/feature/wiki-editing
futa-ikeda Jun 17, 2026
007079f
feat(collections): Prevent submission to collections based on waffle …
futa-ikeda Jun 18, 2026
ae56d1d
Merge pull request #9 from futa-ikeda/feature/collection-submission
futa-ikeda Jun 18, 2026
3f34f99
feat(metadata): Prevent updates to node metadata based on waffle flag…
futa-ikeda Jun 17, 2026
02521e3
feat(metadata): Prevent edit of subjects and tags based on waffle flag
futa-ikeda Jun 17, 2026
6c7cec8
Merge pull request #10 from futa-ikeda/feature/node-metadata-page
futa-ikeda Jun 18, 2026
a0f08a0
feat(project-overview): Prevent users from editing project on project…
futa-ikeda Jun 18, 2026
2ba849c
Merge pull request #12 from futa-ikeda/feature/project-overview-controls
futa-ikeda Jun 22, 2026
f90862e
feat(contributors): Prevent contributor editing based on waffle flag
futa-ikeda Jun 25, 2026
d8b54ea
feat(settings): Prevent project title and descrtion updates in projec…
futa-ikeda Jun 25, 2026
32454f2
Merge pull request #13 from futa-ikeda/feature/prevent-contributor-ed…
futa-ikeda Jun 29, 2026
f6d2ff3
Merge pull request #14 from futa-ikeda/feature/prevent-title-descript…
futa-ikeda Jun 29, 2026
ca3cba7
feat(metadata): Prevent users from adding/editing community metadata …
futa-ikeda Jun 29, 2026
7bef767
feat(registrations): Disable registration creation in project registr…
futa-ikeda Jun 29, 2026
ec62aeb
Merge pull request #15 from futa-ikeda/feature/community-metadata
futa-ikeda Jun 30, 2026
4a4def9
fix(translations): Update broken translation keys
futa-ikeda Jun 30, 2026
ceb34c6
Merge pull request #16 from futa-ikeda/misc-translations
futa-ikeda Jun 30, 2026
2f86f4b
Merge branch 'develop' into feature/prevent-project-creation
futa-ikeda Jul 8, 2026
c4f3d77
feat(files): Prevent file actions based on waffle flag
futa-ikeda Jul 10, 2026
f54e991
Merge pull request #17 from futa-ikeda/feature/file-actions
futa-ikeda Jul 13, 2026
75d0655
feat(settings): Prevent users from updating project settings based on…
futa-ikeda Jul 14, 2026
e6a6188
Merge pull request #19 from futa-ikeda/feature/settings-edit
futa-ikeda Jul 16, 2026
3a0a4c4
feat(contributors): Update wording for contributors permissions
futa-ikeda Jul 16, 2026
0807616
Merge pull request #20 from futa-ikeda/feature/wording-updates
futa-ikeda Jul 17, 2026
d16eac7
feat(request-access): Update request access page based on waffle flag
futa-ikeda Jul 23, 2026
bb0ee1d
Merge pull request #21 from futa-ikeda/feature/request-access
futa-ikeda Jul 23, 2026
5627640
feat(project-settings): Allow projects to become public, but not priv…
futa-ikeda Jul 24, 2026
daf3188
Merge pull request #22 from futa-ikeda/feature/privacy-toggle
futa-ikeda Jul 27, 2026
43a7d30
feat(project overview): Update view forks icon; Prevent inappropriate…
futa-ikeda Jul 29, 2026
71a1226
feat(registrations): Prevent project-based registrations based on waf…
futa-ikeda Jul 30, 2026
1bdace1
Merge pull request #24 from futa-ikeda/feature/project-based-registra…
futa-ikeda Aug 3, 2026
643531e
Merge pull request #23 from futa-ikeda/fork-page-updates
futa-ikeda Aug 3, 2026
2ee0fe9
feat(addons): Prevent new storage addons from being added when waffle…
futa-ikeda Aug 5, 2026
7c55f6b
Merge pull request #25 from futa-ikeda/feature/addon-cards
futa-ikeda Aug 7, 2026
7606186
fix(duplicates): Fix issue with duplicates button based on project cr…
futa-ikeda Aug 11, 2026
37c7ac2
Merge pull request #26 from futa-ikeda/bug/project-duplicates
futa-ikeda Aug 12, 2026
3f84536
Merge branch 'develop' into feature/prevent-project-creation
futa-ikeda Aug 12, 2026
2c33ab6
fix(preprints): Hide supplements section if that section is not shown
futa-ikeda Aug 17, 2026
62093c3
Merge pull request #27 from futa-ikeda/bug/preprint-review
futa-ikeda Aug 18, 2026
6ba652a
fix(contributors): Update hover text position
futa-ikeda Aug 24, 2026
353466e
fix(wiki): disable collapsed wiki add button
futa-ikeda Aug 24, 2026
8260024
Merge pull request #28 from futa-ikeda/bug/wiki-and-contrib-delete-bu…
futa-ikeda Aug 26, 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
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
<section class="container text-center flex flex-column flex-1 my-7 mx-3 p-3 md:p-4">
<h2>{{ 'requestAccess.title' | translate }}</h2>
<h2>{{ titleTranslation() | translate }}</h2>

<p class="mt-4">{{ 'requestAccess.message' | translate }}</p>
<p class="mt-4">
<span [innerHTML]="messageTranslation() | translate"></span>
@if (isProjectReadOnly()) {
<a class="ml-1 font-bold" [href]="'mailto:' + supportEmail">{{ supportEmail }}</a>
}
</p>

<div class="flex flex-column text-left mt-4">
<label class="font-bold" for="comment">
{{ 'requestAccess.commentLabel' | translate }}
</label>
@if (!isProjectReadOnly()) {
<div class="flex flex-column text-left mt-4">
<label class="font-bold" for="comment">
{{ 'requestAccess.commentLabel' | translate }}
</label>

<textarea
class="comment-input"
pTextarea
id="comment"
[rows]="3"
[maxLength]="commentLimit"
[(ngModel)]="comment"
></textarea>
</div>
<textarea
class="comment-input"
pTextarea
id="comment"
[rows]="3"
[maxLength]="commentLimit"
[(ngModel)]="comment"
></textarea>
</div>
}

<div class="flex align-items-center gap-3 mt-6">
<p-button
class="w-full"
styleClass="w-full"
[label]="'requestAccess.requestAccess' | translate"
(onClick)="requestAccess()"
></p-button>
@if (!isProjectReadOnly()) {
<p-button
class="w-full"
styleClass="w-full"
[label]="'requestAccess.requestAccess' | translate"
(onClick)="requestAccess()"
></p-button>
}

<p-button
class="w-full"
styleClass="w-full"
severity="secondary"
[severity]="isProjectReadOnly() ? 'primary' : 'secondary'"
[label]="'requestAccess.switchAccount' | translate"
(onClick)="switchAccount()"
></p-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';

import { AuthService } from '@core/services/auth.service';
import { UserSelectors } from '@core/store/user';
import { InputLimits } from '@osf/shared/constants/input-limits.const';
import { RequestAccessService } from '@osf/shared/services/request-access.service';
import { ToastService } from '@osf/shared/services/toast.service';
Expand All @@ -18,10 +19,17 @@ import { AuthServiceMock, AuthServiceMockType } from '@testing/providers/auth-se
import { LoaderServiceMock, provideLoaderServiceMock } from '@testing/providers/loader-service.mock';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
import { BaseSetupOverrides, mergeSignalOverrides, provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';

import { RequestAccessComponent } from './request-access.component';

interface SetupOverrides extends BaseSetupOverrides {
routeId?: string;
requestAccessResult?: Observable<void>;
requestAccessError?: HttpErrorResponse;
}

describe('RequestAccessComponent', () => {
let fixture: ComponentFixture<RequestAccessComponent>;
let component: RequestAccessComponent;
Expand All @@ -31,12 +39,10 @@ describe('RequestAccessComponent', () => {
let toastServiceMock: ToastServiceMockType;
let authServiceMock: AuthServiceMockType;

function setup(overrides?: {
routeId?: string;
requestAccessResult?: Observable<void>;
requestAccessError?: HttpErrorResponse;
}) {
function setup(overrides?: SetupOverrides) {
const routeId = overrides?.routeId ?? 'project-1';
const defaultSignals = [{ selector: UserSelectors.isProjectReadOnly, value: false }];
const signals = mergeSignalOverrides(defaultSignals, overrides?.selectorOverrides ?? []);
routerMock = RouterMockBuilder.create().withNavigate(vi.fn().mockResolvedValue(true)).build();
loaderServiceMock = new LoaderServiceMock();
toastServiceMock = ToastServiceMock.simple();
Expand All @@ -60,6 +66,7 @@ describe('RequestAccessComponent', () => {
MockProvider(RequestAccessService, requestAccessServiceMock),
MockProvider(ToastService, toastServiceMock),
MockProvider(AuthService, authServiceMock),
provideMockStore({ signals }),
],
});

Expand All @@ -86,6 +93,23 @@ describe('RequestAccessComponent', () => {
expect(supportLink.textContent).toContain(component.supportEmail);
});

it('should expose title and message translations based on read-only state', () => {
setup({ selectorOverrides: [{ selector: UserSelectors.isProjectReadOnly, value: true }] });
expect(component.titleTranslation()).toBe('requestAccess.readOnlyTitle');
expect(component.messageTranslation()).toBe('requestAccess.messageReadOnly');

const buttons = fixture.nativeElement.querySelectorAll('p-button');
expect(buttons).toHaveLength(1);
});

it('should expose title and message translations based on non-read-only state', () => {
setup({ selectorOverrides: [{ selector: UserSelectors.isProjectReadOnly, value: false }] });
expect(component.titleTranslation()).toBe('requestAccess.title');
expect(component.messageTranslation()).toBe('requestAccess.message');
const buttons = fixture.nativeElement.querySelectorAll('p-button');
expect(buttons.length).toBe(2);
});

it('should request access and handle success flow', () => {
setup({ routeId: 'project-123' });
component.comment.set('please grant access');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { select } from '@ngxs/store';

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

import { Button } from 'primeng/button';
Expand All @@ -6,13 +8,14 @@ import { Textarea } from 'primeng/textarea';
import { map, of } from 'rxjs';

import { HttpErrorResponse } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, inject, model } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject, model } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';

import { ENVIRONMENT } from '@core/provider/environment.provider';
import { AuthService } from '@core/services/auth.service';
import { UserSelectors } from '@osf/core/store/user/user.selectors';
import { InputLimits } from '@osf/shared/constants/input-limits.const';
import { LoaderService } from '@osf/shared/services/loader.service';
import { RequestAccessService } from '@osf/shared/services/request-access.service';
Expand Down Expand Up @@ -41,6 +44,16 @@ export class RequestAccessComponent {
private readonly toastService = inject(ToastService);
private readonly authService = inject(AuthService);

readonly isProjectReadOnly = select(UserSelectors.isProjectReadOnly);

readonly titleTranslation = computed(() =>
this.isProjectReadOnly() ? 'requestAccess.readOnlyTitle' : 'requestAccess.title'
);

readonly messageTranslation = computed(() =>
this.isProjectReadOnly() ? 'requestAccess.messageReadOnly' : 'requestAccess.message'
);

requestAccess() {
this.loaderService.show();
this.requestAccessService.requestAccessToProject(this.id(), this.comment()).subscribe({
Expand Down
10 changes: 10 additions & 0 deletions src/app/core/store/user/user.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,14 @@ export class UserSelectors {
static getActiveFlags(state: UserStateModel): string[] {
return state.activeFlags || [];
}

@Selector([UserState])
static isProjectCreationDisabled(state: UserStateModel): boolean {
return state.activeFlags?.includes('prevent_project_creation') || false;
}

@Selector([UserState])
static isProjectReadOnly(state: UserStateModel): boolean {
return state.activeFlags?.includes('project_read_only') || false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
[title]="'project.analytics.kpi.forks' | translate"
[showButton]="isAuthenticated()"
[buttonLabel]="'project.overview.actions.forkProjectLabel' | translate"
[isButtonDisabled]="preventDuplicateCreation()"
[buttonTooltip]="duplicateButtonTooltip() | translate"
(buttonClick)="handleForkResource()"
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { of } from 'rxjs';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';

import { UserSelectors } from '@osf/core/store/user/user.selectors';
import { ProjectOverviewSelectors } from '@osf/features/project/overview/store';
import { RegistrySelectors } from '@osf/features/registry/store/registry';
import { ContributorsListComponent } from '@osf/shared/components/contributors-list/contributors-list.component';
Expand All @@ -24,25 +25,41 @@ import { provideOSFCore } from '@testing/osf.testing.provider';
import { CustomDialogServiceMockBuilder } from '@testing/providers/custom-dialog-provider.mock';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { BaseSetupOverrides, mergeSignalOverrides, provideMockStore } from '@testing/providers/store-provider.mock';

import { ViewDuplicatesComponent } from './view-duplicates.component';

interface SetupOverrides extends BaseSetupOverrides {
selectors?: any[];
}

describe('Component: View Duplicates', () => {
let component: ViewDuplicatesComponent;
let fixture: ComponentFixture<ViewDuplicatesComponent>;
let routerMock: ReturnType<RouterMockBuilder['build']>;
let activatedRouteMock: ReturnType<ActivatedRouteMockBuilder['build']>;
let mockCustomDialogService: ReturnType<CustomDialogServiceMockBuilder['build']>;

beforeEach(() => {
function setup(overrides: SetupOverrides = {}) {
mockCustomDialogService = CustomDialogServiceMockBuilder.create().build();
routerMock = RouterMockBuilder.create().build();
activatedRouteMock = ActivatedRouteMockBuilder.create()
.withParams({ id: 'rid' })
.withData({ resourceType: ResourceType.Project })
.build();

const defaultSelectors = [
{ selector: DuplicatesSelectors.getDuplicates, value: [] },
{ selector: DuplicatesSelectors.getDuplicatesLoading, value: false },
{ selector: DuplicatesSelectors.getDuplicatesTotalCount, value: 0 },
{ selector: ProjectOverviewSelectors.getProject, value: MOCK_PROJECT_OVERVIEW },
{ selector: ProjectOverviewSelectors.isProjectAnonymous, value: false },
{ selector: RegistrySelectors.getRegistry, value: undefined },
{ selector: RegistrySelectors.isRegistryAnonymous, value: false },
{ selector: UserSelectors.isProjectCreationDisabled, value: false },
];
const signals = mergeSignalOverrides(defaultSelectors, overrides.selectors || []);

TestBed.configureTestingModule({
imports: [
ViewDuplicatesComponent,
Expand All @@ -58,15 +75,7 @@ describe('Component: View Duplicates', () => {
providers: [
provideOSFCore(),
provideMockStore({
signals: [
{ selector: DuplicatesSelectors.getDuplicates, value: [] },
{ selector: DuplicatesSelectors.getDuplicatesLoading, value: false },
{ selector: DuplicatesSelectors.getDuplicatesTotalCount, value: 0 },
{ selector: ProjectOverviewSelectors.getProject, value: MOCK_PROJECT_OVERVIEW },
{ selector: ProjectOverviewSelectors.isProjectAnonymous, value: false },
{ selector: RegistrySelectors.getRegistry, value: undefined },
{ selector: RegistrySelectors.isRegistryAnonymous, value: false },
],
signals,
}),
MockProvider(CustomDialogService, mockCustomDialogService),
MockProvider(Router, routerMock),
Expand All @@ -78,13 +87,23 @@ describe('Component: View Duplicates', () => {
component = fixture.componentInstance;

fixture.detectChanges();
});
}

it('should create', () => {
setup();
expect(component).toBeTruthy();
});

it('should disable fork button and show tooltip when isProjectCreationDisabled is true', () => {
setup({
selectors: [{ selector: UserSelectors.isProjectCreationDisabled, value: true }],
});
expect(component.preventDuplicateCreation()).toBe(true);
expect(component.duplicateButtonTooltip()).toBe('project.overview.actions.duplicatingProjectsNotAllowed');
});

it('should open ForkDialog with width 450px when small and not refresh on failure', () => {
setup();
(component as any).actions = { ...component.actions, getDuplicates: vi.fn() };

const openSpy = vi
Expand All @@ -98,12 +117,14 @@ describe('Component: View Duplicates', () => {
});

it('should update currentPage when page is defined', () => {
setup();
const event: PaginatorState = { page: 1 } as PaginatorState;
component.onPageChange(event);
expect(component.currentPage()).toBe(2);
});

it('should not update currentPage when page is undefined', () => {
setup();
component.currentPage.set(5);
const event: PaginatorState = { page: undefined } as PaginatorState;
component.onPageChange(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ export class ViewDuplicatesComponent {
isDuplicatesLoading = select(DuplicatesSelectors.getDuplicatesLoading);
totalDuplicates = select(DuplicatesSelectors.getDuplicatesTotalCount);
isAuthenticated = select(UserSelectors.isAuthenticated);
preventDuplicateCreation = select(UserSelectors.isProjectCreationDisabled);

readonly pageSize = 10;

currentPage = signal<number>(1);
firstIndex = computed(() => (this.currentPage() - 1) * this.pageSize);
duplicateButtonTooltip = computed(() =>
this.preventDuplicateCreation() ? 'project.overview.actions.duplicatingProjectsNotAllowed' : ''
);

readonly forkActionItems = (resourceId: string) => [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ <h1 class="collections-heading flex align-items-center">{{ collectionProvider()?
[stepperActiveValue]="stepperActiveValue()"
[collectionId]="primaryCollectionId() ?? ''"
[targetStepValue]="AddToCollectionSteps.SelectProject"
[isProjectReadOnly]="isProjectReadOnly()"
(projectSelected)="handleProjectSelected()"
(stepChange)="handleChangeStep($event)"
/>
Expand Down Expand Up @@ -68,7 +69,8 @@ <h1 class="collections-heading flex align-items-center">{{ collectionProvider()?

<p-button
[label]="(isEditMode() ? 'common.buttons.update' : 'common.buttons.addToCollection') | translate"
[disabled]="stepperActiveValue() !== AddToCollectionSteps.Complete"
[disabled]="stepperActiveValue() !== AddToCollectionSteps.Complete || isProjectReadOnly()"
[pTooltip]="disabledAddButtonTooltip() | translate"
(click)="handleAddToCollection()"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const DEFAULT_SIGNALS: SignalOverride[] = [
{ selector: CollectionsSelectors.getRequiredMetadataTemplate, value: null },
{ selector: ProjectsSelectors.getSelectedProject, value: MOCK_PROJECT },
{ selector: UserSelectors.getCurrentUser, value: MOCK_USER },
{ selector: UserSelectors.getActiveFlags, value: [] },
{ selector: UserSelectors.isProjectReadOnly, value: false },
{ selector: MetadataSelectors.getCedarRecords, value: [] },
{ selector: AddToCollectionSelectors.getCurrentCollectionSubmission, value: null },
];
Expand Down Expand Up @@ -331,4 +333,9 @@ describe('AddToCollectionComponent', () => {
expect(component.allowNavigation()).toBe(true);
expect(mockRouter.navigate).toHaveBeenCalledWith(['project-1', 'overview']);
});

it('should disable the add to collection button if isProjectReadOnly', () => {
const { component } = setup({ selectorOverrides: [{ selector: UserSelectors.isProjectReadOnly, value: true }] });
expect(component.disabledAddButtonTooltip()).toBe('common.errorMessages.actionUnavailable');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TranslatePipe } from '@ngx-translate/core';

import { Button } from 'primeng/button';
import { Stepper } from 'primeng/stepper';
import { Tooltip } from 'primeng/tooltip';

import { filter, finalize, map, Observable, of, switchMap } from 'rxjs';

Expand Down Expand Up @@ -65,6 +66,7 @@ import { SelectProjectStepComponent } from './select-project-step/select-project
Button,
Stepper,
RouterLink,
Tooltip,
TranslatePipe,
LoadingSpinnerComponent,
SelectProjectStepComponent,
Expand Down Expand Up @@ -100,6 +102,7 @@ export class AddToCollectionComponent implements CanDeactivateComponent {
selectedProject = select(ProjectsSelectors.getSelectedProject);
currentUser = select(UserSelectors.getCurrentUser);
currentCollectionSubmission = select(AddToCollectionSelectors.getCurrentCollectionSubmission);
isProjectReadOnly = select(UserSelectors.isProjectReadOnly);
cedarRecords = select(MetadataSelectors.getCedarRecords);

providerId = signal<string>('');
Expand All @@ -117,6 +120,7 @@ export class AddToCollectionComponent implements CanDeactivateComponent {
isCollectionMetadataDisabled = computed(
() => !this.selectedProject() || !this.projectMetadataSaved() || !this.projectContributorsSaved()
);
disabledAddButtonTooltip = computed(() => (this.isProjectReadOnly() ? 'common.errorMessages.actionUnavailable' : ''));
existingCedarRecord = computed<CedarMetadataRecordData | null>(() => {
const records = this.cedarRecords();
const templateId = this.requiredMetadataTemplate()?.id;
Expand Down
Loading
Loading