diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index 174408c69..92dbd5630 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -9,11 +9,12 @@ export const authInterceptor: HttpInterceptorFn = ( const authToken = 'UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm'; // UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm kyrylo // 2rjFZwmdDG4rtKj7hGkEMO6XyHBM2lN7XBbsA1e8OqcFhOWu6Z7fQZiheu9RXtzSeVrgOt roman nastyuk - - if (authToken) { + const localStorageToken = localStorage.getItem('authToken'); + const token = localStorageToken || authToken; + if (token) { const authReq = req.clone({ setHeaders: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${token}`, Accept: req.responseType === 'text' ? '*/*' : 'application/vnd.api+json', 'Content-Type': 'application/vnd.api+json', }, diff --git a/src/app/features/project/registrations/registrations.component.ts b/src/app/features/project/registrations/registrations.component.ts index 208142a8d..3cb69a5da 100644 --- a/src/app/features/project/registrations/registrations.component.ts +++ b/src/app/features/project/registrations/registrations.component.ts @@ -9,7 +9,7 @@ import { map, of } from 'rxjs'; import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components'; import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component'; @@ -26,20 +26,20 @@ import { GetRegistrations, RegistrationsSelectors } from './store'; }) export class RegistrationsComponent implements OnInit { private readonly route = inject(ActivatedRoute); - + private readonly router = inject(Router); readonly projectId = toSignal(this.route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined)); - protected registrations = select(RegistrationsSelectors.getRegistrations); protected isRegistrationsLoading = select(RegistrationsSelectors.isRegistrationsLoading); - protected actions = createDispatchMap({ getRegistrations: GetRegistrations }); + private readonly OSF_PROVIDER_ID = 'osf'; ngOnInit(): void { this.actions.getRegistrations(this.projectId()); } addRegistration(): void { - //TODO: Implement the logic to add a new registration. - console.log('Add Registration clicked'); + this.router.navigate([`registries/${this.OSF_PROVIDER_ID}/new`], { + queryParams: { projectId: this.projectId() }, + }); } } diff --git a/src/app/features/registries/components/new-registration/new-registration.component.html b/src/app/features/registries/components/new-registration/new-registration.component.html index 4fd357a08..b33354feb 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.html +++ b/src/app/features/registries/components/new-registration/new-registration.component.html @@ -41,6 +41,7 @@

{{ ('registries.new.steps.title' | translate) + '2' }}

[placeholder]="'registries.new.selectProject' | translate" optionLabel="title" optionValue="id" + [loading]="isProjectsLoading()" (onChange)="onSelectProject($event.value)" class="w-6" /> diff --git a/src/app/features/registries/components/new-registration/new-registration.component.ts b/src/app/features/registries/components/new-registration/new-registration.component.ts index 49599a698..85c031005 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.ts +++ b/src/app/features/registries/components/new-registration/new-registration.component.ts @@ -32,6 +32,7 @@ export class NewRegistrationComponent { protected readonly isDraftSubmitting = select(RegistriesSelectors.isDraftSubmitting); protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected readonly isProvidersLoading = select(RegistriesSelectors.isProvidersLoading); + protected readonly isProjectsLoading = select(RegistriesSelectors.isProjectsLoading); protected actions = createDispatchMap({ getProjects: GetProjects, getProviderSchemas: GetProviderSchemas, @@ -39,12 +40,13 @@ export class NewRegistrationComponent { }); protected readonly providerId = this.route.snapshot.params['providerId']; + protected readonly projectId = this.route.snapshot.queryParams['projectId']; - fromProject = false; + fromProject = this.projectId !== undefined; draftForm = this.fb.group({ providerSchema: ['', Validators.required], - project: [''], + project: [this.projectId || ''], }); constructor() { diff --git a/src/app/features/registries/components/review/review.component.html b/src/app/features/registries/components/review/review.component.html index f54c10636..e2bda9dca 100644 --- a/src/app/features/registries/components/review/review.component.html +++ b/src/app/features/registries/components/review/review.component.html @@ -37,10 +37,21 @@

{{ 'navigation.registration.contributors' | translate }}

} -
+ +

{{ 'shared.license.title' | translate }}

-

{{ draftRegistration()?.license?.id }}

- @if (!draftRegistration()?.license) { + @if (draftRegistration()?.license && license()) { + + + +
{{ license()?.name }}
+
+ +
{{ license()!.text | interpolate: licenseOptionsRecord() }}
+
+
+
+ } @else {

{{ 'common.labels.noData' | translate }}

{{ INPUT_VALIDATION_MESSAGES.required | translate }} diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index 4c542628f..0b24465d8 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -2,6 +2,7 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { DialogService } from 'primeng/dynamicdialog'; @@ -16,6 +17,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { ResourceType } from '@osf/shared/enums'; +import { InterpolatePipe } from '@osf/shared/pipes'; import { CustomConfirmationService, ToastService } from '@osf/shared/services'; import { ContributorsSelectors, @@ -25,13 +27,25 @@ import { } from '@osf/shared/stores'; import { FieldType } from '../../enums'; -import { DeleteDraft, FetchProjectChildren, RegistriesSelectors } from '../../store'; +import { DeleteDraft, FetchLicenses, FetchProjectChildren, RegistriesSelectors } from '../../store'; import { ConfirmRegistrationDialogComponent } from '../confirm-registration-dialog/confirm-registration-dialog.component'; import { SelectComponentsDialogComponent } from '../select-components-dialog/select-components-dialog.component'; @Component({ selector: 'osf-review', - imports: [TranslatePipe, Card, Message, RouterLink, Tag, Button], + imports: [ + TranslatePipe, + Card, + Message, + RouterLink, + Tag, + Button, + Accordion, + AccordionContent, + AccordionHeader, + AccordionPanel, + InterpolatePipe, + ], templateUrl: './review.component.html', styleUrl: './review.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -54,6 +68,9 @@ export class ReviewComponent { protected readonly contributors = select(ContributorsSelectors.getContributors); protected readonly subjects = select(SubjectsSelectors.getSelectedSubjects); protected readonly components = select(RegistriesSelectors.getRegistrationComponents); + protected readonly license = select(RegistriesSelectors.getRegistrationLicense); + private readonly OSF_PROVIDER_ID = 'osf'; + protected readonly FieldType = FieldType; protected actions = createDispatchMap({ @@ -61,6 +78,7 @@ export class ReviewComponent { getSubjects: FetchSelectedSubjects, deleteDraft: DeleteDraft, getProjectsComponents: FetchProjectChildren, + fetchLicenses: FetchLicenses, }); private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); @@ -71,6 +89,10 @@ export class ReviewComponent { return Object.values(this.stepsValidation()).some((step) => step.invalid); }); + licenseOptionsRecord = computed(() => { + return (this.draftRegistration()?.license.options ?? {}) as Record; + }); + constructor() { if (!this.contributors()?.length) { this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration); @@ -79,6 +101,12 @@ export class ReviewComponent { this.actions.getSubjects(this.draftId(), ResourceType.DraftRegistration); } + effect(() => { + if (this.draftRegistration()) { + this.actions.fetchLicenses(this.draftRegistration()?.providerId ?? this.OSF_PROVIDER_ID); + } + }); + let componentsLoaded = false; effect(() => { if (!this.isDraftSubmitting()) { diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index 8a4acbffe..52bf4eacf 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -23,6 +23,11 @@ export class RegistriesSelectors { return state.projects.data; } + @Selector([RegistriesState]) + static isProjectsLoading(state: RegistriesStateModel): boolean { + return state.projects.isLoading; + } + @Selector([RegistriesState]) static isDraftSubmitting(state: RegistriesStateModel): boolean { return state.draftRegistration.isSubmitting ?? false; @@ -63,6 +68,11 @@ export class RegistriesSelectors { return state.draftRegistration.data?.license || null; } + @Selector([RegistriesState]) + static getRegistrationLicense(state: RegistriesStateModel): License | null { + return state.licenses.data.find((l) => l.id === state.draftRegistration.data?.license.id) || null; + } + @Selector([RegistriesState]) static getPagesSchema(state: RegistriesStateModel): PageSchema[] { return state.pagesSchema.data; diff --git a/src/app/shared/components/files-tree/files-tree.component.html b/src/app/shared/components/files-tree/files-tree.component.html index f02951db3..0e1119685 100644 --- a/src/app/shared/components/files-tree/files-tree.component.html +++ b/src/app/shared/components/files-tree/files-tree.component.html @@ -1,94 +1,102 @@ -@if (!viewOnly()) { -
- @if (isDragOver()) { -
- -

{{ 'project.files.dropText' | translate }}

-
- } -
-} - -@if (isLoading()) { -
- -
-} @else { -
- + @if (!viewOnly()) { +
- - @if (file.previousFolder) { -
-
-
- - - {{ file.name ?? '' }} -
-
-
- } @else { -
-
- @if (file.kind !== 'folder') { -
- - {{ file?.name ?? '' }} -
- } @else { - -
- {{ file?.name ?? '' }} -
- } -
- -
- @if (file.extra.downloads) { - {{ file.kind === 'file' ? file.extra.downloads + ' ' + ('common.labels.downloads' | translate) : '' }} - } -
+ @if (isDragOver()) { +
+ +

{{ 'project.files.dropText' | translate }}

+
+ } +
+ } -
- {{ file.size | fileSize }} + @if (isLoading()) { +
+ +
+ } @else { +
+ + + @if (file.previousFolder) { +
+
+
+ + + {{ file.name ?? '' }} +
+
+ } @else { +
+
+ @if (file.kind !== 'folder') { +
+ + {{ file?.name ?? '' }} +
+ } @else { + +
+ {{ file?.name ?? '' }} +
+ } +
-
- {{ file.dateModified | date: 'MMM d, y hh:mm a' }} -
+
+ @if (file.extra.downloads) { + {{ file.kind === 'file' ? file.extra.downloads + ' ' + ('common.labels.downloads' | translate) : '' }} + } +
- @if (!viewOnly() && !viewOnlyDownloadable()) { -
- +
+ {{ file.size | fileSize }}
- } @else if (viewOnly() && viewOnlyDownloadable()) { +
- + {{ file.dateModified | date: 'MMM d, y hh:mm a' }}
- } -
- } - - - @if (!files().length) { -
-

{{ 'project.files.emptyState' | translate }}

-
- } -
-} + @if (!viewOnly() && !viewOnlyDownloadable()) { +
+ +
+ } @else if (viewOnly() && viewOnlyDownloadable()) { +
+ +
+ } +
+ } + + + + @if (!files().length) { +
+

{{ 'project.files.emptyState' | translate }}

+
+ } +
+ } +
diff --git a/src/app/shared/components/files-tree/files-tree.component.scss b/src/app/shared/components/files-tree/files-tree.component.scss index 864001576..922feb710 100644 --- a/src/app/shared/components/files-tree/files-tree.component.scss +++ b/src/app/shared/components/files-tree/files-tree.component.scss @@ -1,6 +1,10 @@ @use "assets/styles/variables" as var; @use "assets/styles/mixins" as mix; +:host { + min-height: 200px; +} + .files-table { display: flex; flex-direction: column; @@ -68,7 +72,7 @@ } .drop-zone { - position: fixed; + position: absolute; top: 0; left: 0; width: 100%; diff --git a/src/app/shared/components/files-tree/files-tree.component.ts b/src/app/shared/components/files-tree/files-tree.component.ts index 5ce87dfb2..4f4debb9d 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -9,17 +9,19 @@ import { catchError } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; import { + AfterViewInit, ChangeDetectionStrategy, Component, computed, effect, + ElementRef, HostBinding, inject, input, OnDestroy, - OnInit, output, signal, + viewChild, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; @@ -38,8 +40,9 @@ import { CustomConfirmationService, FilesService, ToastService } from '@shared/s styleUrl: './files-tree.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class FilesTreeComponent implements OnInit, OnDestroy { +export class FilesTreeComponent implements OnDestroy, AfterViewInit { @HostBinding('class') classes = 'relative'; + private dropZoneContainerRef = viewChild('dropZoneContainer'); readonly filesService = inject(FilesService); readonly router = inject(Router); readonly toastService = inject(ToastService); @@ -78,15 +81,15 @@ export class FilesTreeComponent implements OnInit, OnDestroy { } }); - ngOnInit(): void { - window.addEventListener('dragenter', this.onGlobalDragEnter); + ngAfterViewInit(): void { + this.dropZoneContainerRef()!.nativeElement.addEventListener('dragenter', this.dragEnterHandler); } ngOnDestroy(): void { - window.removeEventListener('dragenter', this.onGlobalDragEnter); + this.dropZoneContainerRef()!.nativeElement.removeEventListener('dragenter', this.dragEnterHandler); } - onGlobalDragEnter = (event: DragEvent) => { + private dragEnterHandler = (event: DragEvent) => { if (event.dataTransfer?.types?.includes('Files')) { this.isDragOver.set(true); } @@ -94,12 +97,20 @@ export class FilesTreeComponent implements OnInit, OnDestroy { onDragOver(event: DragEvent) { event.preventDefault(); + event.stopPropagation(); event.dataTransfer!.dropEffect = 'copy'; this.isDragOver.set(true); } + onDragLeave(event: Event) { + event.preventDefault(); + event.stopPropagation(); + this.isDragOver.set(false); + } + onDrop(event: DragEvent) { event.preventDefault(); + event.stopPropagation(); this.isDragOver.set(false); const files = event.dataTransfer?.files;