Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/app/core/components/topnav/topnav.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@use "assets/styles/mixins" as mix;

:host {
z-index: 1300;
z-index: 1103;

.nav-container {
background: var.$dark-blue-1;
Expand Down
19 changes: 15 additions & 4 deletions src/app/core/models/json-api.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ export interface JsonApiResponse<Data, Included> {

export interface JsonApiResponseWithPaging<Data, Included> extends JsonApiResponse<Data, Included> {
links: {
meta: {
total: number;
per_page: number;
};
meta: MetaJsonApi;
};
}

Expand All @@ -20,3 +17,17 @@ export interface ApiData<Attributes, Embeds, Relationships, Links> {
relationships: Relationships;
links: Links;
}

export interface MetaJsonApi {
total: number;
per_page: number;
version?: string;
}

export interface PaginationLinksJsonApi {
self?: string;
first?: string | null;
last?: string | null;
prev?: string | null;
next?: string | null;
}
20 changes: 19 additions & 1 deletion src/app/features/collections/collections.routes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { provideStates } from '@ngxs/store';

import { Routes } from '@angular/router';

import { CollectionsComponent } from '@osf/features/collections/collections.component';

import { ModerationState } from '../moderation/store';

export const collectionsRoutes: Routes = [
{
path: '',
component: CollectionsComponent,
children: [
{
path: '',
pathMatch: 'full',
component: CollectionsComponent,
},
{
path: 'moderation',
loadComponent: () =>
import('@osf/features/moderation/pages/collection-moderation/collection-moderation.component').then(
(m) => m.CollectionModerationComponent
),
providers: [provideStates([ModerationState])],
},
],
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<div class="flex flex-column">
<osf-search-input
[control]="searchControl"
[placeholder]="'project.contributors.addDialog.placeholder' | translate"
/>

<div class="flex flex-column gap-3" [class.users-list]="!isInitialState()" [class.mt-4]="!isInitialState()">
@if (isLoading()) {
<osf-loading-spinner class="mt-3"></osf-loading-spinner>
} @else {
@for (item of users(); track $index) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use id for track if possible

<div class="border-divider flex p-b-12">
<p-checkbox variant="filled" [value]="item" [inputId]="item.id" [(ngModel)]="selectedUsers"></p-checkbox>
<label class="label ml-2" [for]="item.id">{{ item.fullName }}</label>
</div>
}

@if (!totalUsersCount() && !isInitialState()) {
<div class="no-items">{{ 'common.search.noResultsFound' | translate }}</div>
}
}
</div>

@if (totalUsersCount() > rows()) {
<osf-custom-paginator
class="mt-2"
[first]="first()"
[rows]="rows()"
[totalCount]="totalUsersCount()"
(pageChanged)="pageChanged($event)"
></osf-custom-paginator>
}

<div class="flex justify-content-end mt-4">
<p-button
class="secondary-add-btn"
severity="secondary"
[label]="'moderation.inviteModerator' | translate"
(click)="inviteModerator()"
/>
</div>

<div class="flex gap-2 mt-6">
<p-button
class="w-full"
styleClass="w-full"
(click)="dialogRef.close()"
severity="info"
[label]="'common.buttons.cancel' | translate"
>
</p-button>

<p-button
class="w-full"
styleClass="w-full"
(click)="addModerator()"
[label]="'common.buttons.add' | translate"
[disabled]="!selectedUsers().length"
>
</p-button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@use "assets/styles/variables" as var;
@use "assets/styles/mixins" as mix;

.label {
color: var.$dark-blue-1;
margin: 0;
cursor: pointer;
}

.users-list {
height: 30vh;
overflow: auto;
}

.border-divider {
border-bottom: 1px solid var.$grey-2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AddModeratorDialogComponent } from './add-moderator-dialog.component';

describe('AddModeratorDialogComponent', () => {
let component: AddModeratorDialogComponent;
let fixture: ComponentFixture<AddModeratorDialogComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AddModeratorDialogComponent],
}).compileComponents();

fixture = TestBed.createComponent(AddModeratorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createDispatchMap, select } from '@ngxs/store';

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

import { Button } from 'primeng/button';
import { Checkbox } from 'primeng/checkbox';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { PaginatorState } from 'primeng/paginator';

import { debounceTime, distinctUntilChanged, filter, switchMap } from 'rxjs';

import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormsModule } from '@angular/forms';

import { CustomPaginatorComponent, LoadingSpinnerComponent, SearchInputComponent } from '@osf/shared/components';

import { AddModeratorType } from '../../enums';
import { ModeratorAddModel, ModeratorDialogAddModel } from '../../models';
import { ClearUsers, ModerationSelectors, SearchUsers } from '../../store';

@Component({
selector: 'osf-add-moderator-dialog',
imports: [
Button,
Checkbox,
FormsModule,
TranslatePipe,
SearchInputComponent,
LoadingSpinnerComponent,
CustomPaginatorComponent,
],
templateUrl: './add-moderator-dialog.component.html',
styleUrl: './add-moderator-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddModeratorDialogComponent implements OnInit, OnDestroy {
protected dialogRef = inject(DynamicDialogRef);
private readonly destroyRef = inject(DestroyRef);
readonly config = inject(DynamicDialogConfig);

protected users = select(ModerationSelectors.getUsers);
protected isLoading = select(ModerationSelectors.isUsersLoading);
protected totalUsersCount = select(ModerationSelectors.getUsersTotalCount);
protected isInitialState = signal(true);

protected currentPage = signal(1);
protected first = signal(0);
protected rows = signal(10);

protected selectedUsers = signal<ModeratorAddModel[]>([]);
protected searchControl = new FormControl<string>('');

protected actions = createDispatchMap({ searchUsers: SearchUsers, clearUsers: ClearUsers });

ngOnInit(): void {
this.setSearchSubscription();
this.selectedUsers.set([]);
}

ngOnDestroy(): void {
this.actions.clearUsers();
}

addModerator(): void {
const dialogData: ModeratorDialogAddModel = { data: this.selectedUsers(), type: AddModeratorType.Search };
this.dialogRef.close(dialogData);
}

inviteModerator() {
const dialogData: ModeratorDialogAddModel = { data: [], type: AddModeratorType.Invite };
this.dialogRef.close(dialogData);
}

pageChanged(event: PaginatorState) {
this.currentPage.set(event.page ? this.currentPage() + 1 : 1);
this.first.set(event.first ?? 0);
this.actions.searchUsers(this.searchControl.value, this.currentPage());
}

private setSearchSubscription() {
this.searchControl.valueChanges
.pipe(
filter((searchTerm) => !!searchTerm && searchTerm.trim().length > 0),
debounceTime(500),
distinctUntilChanged(),
switchMap((searchTerm) => this.actions.searchUsers(searchTerm, this.currentPage())),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.isInitialState.set(false);
this.selectedUsers.set([]);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>
<span>{{ 'moderation.settingsMessage' | translate }}</span>
<a class="ml-1 font-bold cursor-pointer" routerLink="/settings/notifications">
{{ 'moderation.userSettings' | translate }}
</a>
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CollectionModerationSettingsComponent } from './collection-moderation-settings.component';

describe('CollectionModerationSettingsComponent', () => {
let component: CollectionModerationSettingsComponent;
let fixture: ComponentFixture<CollectionModerationSettingsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CollectionModerationSettingsComponent],
}).compileComponents();

fixture = TestBed.createComponent(CollectionModerationSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { TranslatePipe } from '@ngx-translate/core';

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
selector: 'osf-collection-moderation-settings',
imports: [TranslatePipe, RouterLink],
templateUrl: './collection-moderation-settings.component.html',
styleUrl: './collection-moderation-settings.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CollectionModerationSettingsComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div class="flex justify-content-between align-items-center">
<div class="flex gap-3">
<p-selectbutton
class="review-state-select"
[options]="submissionReviewOptions"
[ngModel]="selectedReviewOption"
(ngModelChange)="changeReviewStatus($event)"
optionLabel="label"
optionValue="value"
>
<ng-template #item let-item>
<osf-icon [iconClass]="item.icon" [class]="item.value"></osf-icon>
<p>{{ totalCount }}</p>
<p>{{ item.label | translate }}</p>
</ng-template>
</p-selectbutton>
</div>

<div class="col-4">
<osf-select
[options]="sortOptions"
[placeholder]="'project.files.sort.placeholder' | translate"
[fullWidth]="true"
[(selectedValue)]="selectedSortOption"
(changeValue)="changeSort($event)"
></osf-select>
</div>
</div>

<osf-submissions-list></osf-submissions-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.pending {
color: var(--yellow-1);
}

.accepted {
color: var(--green-1);
}

.rejected {
color: var(--red-1);
}

.withdrawn {
color: var(--dark-blue-1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CollectionModerationSubmissionsComponent } from './collection-moderation-submissions.component';

describe('CollectionModerationSubmissionsComponent', () => {
let component: CollectionModerationSubmissionsComponent;
let fixture: ComponentFixture<CollectionModerationSubmissionsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CollectionModerationSubmissionsComponent],
}).compileComponents();

fixture = TestBed.createComponent(CollectionModerationSubmissionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

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