From 52bc2b13112b694e074110c1543a09d0b2a8fd6d Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Fri, 11 Jul 2025 17:20:18 +0300 Subject: [PATCH 1/4] feat(regiestries): add my registration page --- src/app/core/constants/nav-items.constant.ts | 2 +- .../registration-card.component.html | 108 -------------- .../registration-card.component.ts | 21 --- .../mappers/registrations.mapper.ts | 21 +-- .../registrations/registrations.component.ts | 2 +- .../services/registrations.service.ts | 8 +- .../store/registrations.model.ts | 3 +- .../components/drafts/drafts.component.ts | 4 +- .../components/metadata/metadata.component.ts | 3 +- .../{defaultSteps.ts => default-steps.ts} | 2 +- .../features/registries/constants/index.ts | 3 +- .../constants/registrations-tabs.ts | 12 ++ .../registries/mappers/registration.mapper.ts | 49 ++++++- src/app/features/registries/models/index.ts | 1 - .../models/registration-json-api.model.ts | 59 -------- src/app/features/registries/pages/index.ts | 1 + .../my-registrations.component.html | 46 ++++++ .../my-registrations.component.scss | 0 .../my-registrations.component.spec.ts | 22 +++ .../my-registrations.component.ts | 99 +++++++++++++ .../features/registries/registries.routes.ts | 4 + .../registries/services/licenses.service.ts | 13 +- .../registries/services/registries.service.ts | 69 +++++++-- .../registries/store/default.state.ts | 12 ++ .../registries/store/registries.actions.ts | 20 ++- .../registries/store/registries.model.ts | 16 ++- .../registries/store/registries.selectors.ts | 23 ++- .../registries/store/registries.state.ts | 50 +++++++ .../registration-card.component.html | 125 ++++++++++++++++ .../registration-card.component.scss | 0 .../registration-card.component.spec.ts | 0 .../registration-card.component.ts | 25 ++++ src/app/shared/models/index.ts | 1 + src/app/shared/models/registration/index.ts | 2 + .../registration/registration-card.model.ts | 16 +++ .../registration-json-api.model.ts | 133 ++++++++++++++++++ src/assets/i18n/en.json | 10 +- 37 files changed, 741 insertions(+), 244 deletions(-) delete mode 100644 src/app/features/project/registrations/components/registration-card/registration-card.component.html delete mode 100644 src/app/features/project/registrations/components/registration-card/registration-card.component.ts rename src/app/features/registries/constants/{defaultSteps.ts => default-steps.ts} (85%) create mode 100644 src/app/features/registries/constants/registrations-tabs.ts delete mode 100644 src/app/features/registries/models/registration-json-api.model.ts create mode 100644 src/app/features/registries/pages/my-registrations/my-registrations.component.html create mode 100644 src/app/features/registries/pages/my-registrations/my-registrations.component.scss create mode 100644 src/app/features/registries/pages/my-registrations/my-registrations.component.spec.ts create mode 100644 src/app/features/registries/pages/my-registrations/my-registrations.component.ts create mode 100644 src/app/shared/components/registration-card/registration-card.component.html rename src/app/{features/project/registrations => shared}/components/registration-card/registration-card.component.scss (100%) rename src/app/{features/project/registrations => shared}/components/registration-card/registration-card.component.spec.ts (100%) create mode 100644 src/app/shared/components/registration-card/registration-card.component.ts create mode 100644 src/app/shared/models/registration/registration-card.model.ts create mode 100644 src/app/shared/models/registration/registration-json-api.model.ts diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index 5c247a216..e04dcc589 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -32,7 +32,7 @@ export const MENU_ITEMS: MenuItem[] = [ routerLinkActiveOptions: { exact: true }, }, { - routerLink: '/my-registrations', + routerLink: '/registries/my-registrations', label: 'navigation.registriesSubRoutes.myRegistrations', routerLinkActiveOptions: { exact: true }, }, diff --git a/src/app/features/project/registrations/components/registration-card/registration-card.component.html b/src/app/features/project/registrations/components/registration-card/registration-card.component.html deleted file mode 100644 index 6ac0530eb..000000000 --- a/src/app/features/project/registrations/components/registration-card/registration-card.component.html +++ /dev/null @@ -1,108 +0,0 @@ -
- -
-
- @if (registrationData().status === RegistrationStatus.WITHDRAWN) { - - } - - @if (registrationData().status === RegistrationStatus.IN_PROGRESS) { - - } - -

{{ registrationData().title }}

- - @if (registrationData().status === RegistrationStatus.IN_PROGRESS) { - - } - - @if (registrationData().status === RegistrationStatus.WITHDRAWN) { - - } -
- -
-
-
- {{ 'project.registrations.card.registrationTemplate' | translate }} - {{ registrationData().registrationSupplement }} -
- -
- {{ 'project.registrations.card.registry' | translate }} - {{ registrationData().registry }} -
- -
- {{ 'project.registrations.card.registered' | translate }} - {{ registrationData().dateRegistered }} -
- -
- {{ 'project.registrations.card.lastUpdated' | translate }} - {{ registrationData().dateModified }} -
- -
- {{ 'project.overview.metadata.contributors' | translate }}: - - @for (contributor of registrationData().contributors; track contributor.name) { - {{ contributor.name }} - @if (!$last) { - , - } - } - -
- -
- {{ 'project.registrations.card.description' | translate }} -

{{ registrationData().description }}

-
- -
- @if (registrationData().status === RegistrationStatus.DRAFT) { - - - - } @else { - - @if (registrationData().status === RegistrationStatus.IN_PROGRESS) { - - } - } -
-
- - @if (registrationData().status !== RegistrationStatus.DRAFT) { - - } -
-
-
-
diff --git a/src/app/features/project/registrations/components/registration-card/registration-card.component.ts b/src/app/features/project/registrations/components/registration-card/registration-card.component.ts deleted file mode 100644 index 86cc3d598..000000000 --- a/src/app/features/project/registrations/components/registration-card/registration-card.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TranslatePipe } from '@ngx-translate/core'; - -import { Button } from 'primeng/button'; -import { Card } from 'primeng/card'; -import { Tag } from 'primeng/tag'; - -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; - -import { RegistrationModel, RegistrationStatus } from '../../models'; - -@Component({ - selector: 'osf-registration-card', - imports: [Card, Button, Tag, TranslatePipe], - templateUrl: './registration-card.component.html', - styleUrl: './registration-card.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RegistrationCardComponent { - readonly RegistrationStatus = RegistrationStatus; - readonly registrationData = input.required(); -} diff --git a/src/app/features/project/registrations/mappers/registrations.mapper.ts b/src/app/features/project/registrations/mappers/registrations.mapper.ts index 932ca5774..71c66236d 100644 --- a/src/app/features/project/registrations/mappers/registrations.mapper.ts +++ b/src/app/features/project/registrations/mappers/registrations.mapper.ts @@ -1,4 +1,7 @@ -import { RegistrationModel, RegistrationsGetResponse, RegistrationStatus } from '../models'; +import { RegistryStatus } from '@osf/shared/enums'; +import { RegistrationModel } from '@osf/shared/models'; + +import { RegistrationsGetResponse } from '../models'; export class RegistrationsMapper { static fromResponse(response: RegistrationsGetResponse): RegistrationModel { @@ -8,16 +11,16 @@ export class RegistrationsMapper { title: response.attributes?.title, dateRegistered: response.attributes?.date_registered, dateModified: response.attributes?.date_modified, - registrationSupplement: response.attributes?.registration_supplement, - registry: '', + // registrationSupplement: response.attributes?.registration_supplement, + // registry: '', description: response.attributes?.description, - withdrawn: response.attributes?.withdrawn, - lastFetched: Date.now(), + // withdrawn: response.attributes?.withdrawn, + // lastFetched: Date.now(), status: response.attributes?.withdrawn - ? RegistrationStatus.WITHDRAWN + ? RegistryStatus.PendingWithdraw : response.attributes?.date_modified - ? RegistrationStatus.IN_PROGRESS - : RegistrationStatus.DRAFT, - }; + ? RegistryStatus.InProgress + : RegistryStatus.Pending, + } as RegistrationModel; } } diff --git a/src/app/features/project/registrations/registrations.component.ts b/src/app/features/project/registrations/registrations.component.ts index 07ddaffd4..208142a8d 100644 --- a/src/app/features/project/registrations/registrations.component.ts +++ b/src/app/features/project/registrations/registrations.component.ts @@ -12,8 +12,8 @@ import { FormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components'; +import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component'; -import { RegistrationCardComponent } from './components'; import { GetRegistrations, RegistrationsSelectors } from './store'; @Component({ diff --git a/src/app/features/project/registrations/services/registrations.service.ts b/src/app/features/project/registrations/services/registrations.service.ts index 8ce21ecfa..17c678a15 100644 --- a/src/app/features/project/registrations/services/registrations.service.ts +++ b/src/app/features/project/registrations/services/registrations.service.ts @@ -4,9 +4,9 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiResponse } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; +import { RegistrationModel } from '@osf/shared/models'; -import { submittedRegistrations } from '../mock-data'; -import { RegistrationModel, RegistrationsGetResponse } from '../models'; +import { RegistrationsGetResponse } from '../models'; import { RegistrationsMapper } from './../mappers'; @@ -26,9 +26,7 @@ export class RegistrationsService { return this.#jsonApiService.get>(url, params).pipe( map((response) => { - return response.data.length - ? response.data.map((registration) => RegistrationsMapper.fromResponse(registration)) - : submittedRegistrations; + return response.data.map((registration) => RegistrationsMapper.fromResponse(registration)); }) ); } diff --git a/src/app/features/project/registrations/store/registrations.model.ts b/src/app/features/project/registrations/store/registrations.model.ts index 545e87bb1..0919559de 100644 --- a/src/app/features/project/registrations/store/registrations.model.ts +++ b/src/app/features/project/registrations/store/registrations.model.ts @@ -1,7 +1,6 @@ +import { RegistrationModel } from '@osf/shared/models'; import { AsyncStateModel } from '@osf/shared/models/store'; -import { RegistrationModel } from '../models'; - export interface RegistrationsStateModel { registrations: AsyncStateModel; } diff --git a/src/app/features/registries/components/drafts/drafts.component.ts b/src/app/features/registries/components/drafts/drafts.component.ts index 8898fcbbe..0f9e87e58 100644 --- a/src/app/features/registries/components/drafts/drafts.component.ts +++ b/src/app/features/registries/components/drafts/drafts.component.ts @@ -12,7 +12,7 @@ import { StepperComponent, SubHeaderComponent } from '@osf/shared/components'; import { StepOption } from '@osf/shared/models'; import { LoaderService } from '@osf/shared/services'; -import { defaultSteps } from '../../constants'; +import { DEFAULT_STEPS } from '../../constants'; import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors } from '../../store'; @Component({ @@ -46,7 +46,7 @@ export class DraftsComponent { defaultSteps: StepOption[] = []; steps: Signal = computed(() => { - this.defaultSteps = defaultSteps.map((step) => ({ + this.defaultSteps = DEFAULT_STEPS.map((step) => ({ ...step, label: this.translateService.instant(step.label), invalid: this.stepsValidation()?.[step.index]?.invalid || false, diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index 4e5656009..45bc600d7 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -15,8 +15,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { TextInputComponent } from '@osf/shared/components'; import { INPUT_VALIDATION_MESSAGES, InputLimits } from '@osf/shared/constants'; -import { SubjectModel } from '@osf/shared/models'; -import { DraftRegistrationModel } from '@osf/shared/models/registration'; +import { DraftRegistrationModel, SubjectModel } from '@osf/shared/models'; import { CustomConfirmationService } from '@osf/shared/services'; import { SubjectsSelectors } from '@osf/shared/stores'; import { CustomValidators, findChangedFields } from '@osf/shared/utils'; diff --git a/src/app/features/registries/constants/defaultSteps.ts b/src/app/features/registries/constants/default-steps.ts similarity index 85% rename from src/app/features/registries/constants/defaultSteps.ts rename to src/app/features/registries/constants/default-steps.ts index 417ed494b..bafca0734 100644 --- a/src/app/features/registries/constants/defaultSteps.ts +++ b/src/app/features/registries/constants/default-steps.ts @@ -1,6 +1,6 @@ import { StepOption } from '@osf/shared/models'; -export const defaultSteps: StepOption[] = [ +export const DEFAULT_STEPS: StepOption[] = [ { index: 0, label: 'navigation.project.metadata', diff --git a/src/app/features/registries/constants/index.ts b/src/app/features/registries/constants/index.ts index 92e572c61..778cd4ea5 100644 --- a/src/app/features/registries/constants/index.ts +++ b/src/app/features/registries/constants/index.ts @@ -1 +1,2 @@ -export * from './defaultSteps'; +export * from './default-steps'; +export * from './registrations-tabs'; diff --git a/src/app/features/registries/constants/registrations-tabs.ts b/src/app/features/registries/constants/registrations-tabs.ts new file mode 100644 index 000000000..5dfedb63b --- /dev/null +++ b/src/app/features/registries/constants/registrations-tabs.ts @@ -0,0 +1,12 @@ +import { TabOption } from '@osf/shared/models'; + +export const REGISTRATIONS_TABS: TabOption[] = [ + { + label: 'common.labels.drafts', + value: 0, + }, + { + label: 'common.labels.submitted', + value: 1, + }, +]; diff --git a/src/app/features/registries/mappers/registration.mapper.ts b/src/app/features/registries/mappers/registration.mapper.ts index 8e548e518..86424f15a 100644 --- a/src/app/features/registries/mappers/registration.mapper.ts +++ b/src/app/features/registries/mappers/registration.mapper.ts @@ -1,9 +1,14 @@ -import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; - -import { RegistrationDataJsonApi } from '../models'; +import { RegistryStatus } from '@osf/shared/enums'; +import { + DraftRegistrationDataJsonApi, + DraftRegistrationModel, + RegistrationCard, + RegistrationDataJsonApi, + RegistrationModel, +} from '@osf/shared/models'; export class RegistrationMapper { - static fromDraftRegistrationResponse(response: RegistrationDataJsonApi): DraftRegistrationModel { + static fromDraftRegistrationResponse(response: DraftRegistrationDataJsonApi): DraftRegistrationModel { return { id: response.id, title: response.attributes.title, @@ -30,4 +35,40 @@ export class RegistrationMapper { type: 'registration', } as RegistrationModel; } + + static fromDraftToRegistrationCard(registration: DraftRegistrationDataJsonApi): RegistrationCard { + return { + id: registration.id, + title: registration.attributes.title, + description: registration.attributes.description || '', + status: RegistryStatus.None, + dateCreated: registration.attributes.datetime_initiated, + dateModified: registration.attributes.datetime_updated, + registrationTemplate: registration.embeds?.registration_schema?.data?.attributes?.name || '', + registry: registration.embeds?.provider?.data?.attributes?.name || '', + contributors: + registration.embeds?.bibliographic_contributors?.data.map((contributor) => ({ + id: contributor.id, + fullName: contributor.embeds?.users?.data.attributes.full_name, + })) || [], + }; + } + + static fromRegistrationToRegistrationCard(registration: RegistrationDataJsonApi): RegistrationCard { + return { + id: registration.id, + title: registration.attributes.title, + description: registration.attributes.description || '', + status: RegistryStatus.InProgress, // [NM] TODO: map status accordingly + dateCreated: registration.attributes.datetime_initiated, + dateModified: registration.attributes.date_modified, + registrationTemplate: registration.embeds?.registration_schema?.data?.attributes?.name || '', + registry: registration.embeds?.provider?.data?.attributes?.name || '', + contributors: + registration.embeds?.bibliographic_contributors?.data.map((contributor) => ({ + id: contributor.id, + fullName: contributor.embeds?.users?.data.attributes.full_name, + })) || [], + }; + } } diff --git a/src/app/features/registries/models/index.ts b/src/app/features/registries/models/index.ts index b912472db..b22752736 100644 --- a/src/app/features/registries/models/index.ts +++ b/src/app/features/registries/models/index.ts @@ -3,5 +3,4 @@ export * from './project'; export * from './projects-json-api.model'; export * from './provider.model'; export * from './providers-json-api.model'; -export * from './registration-json-api.model'; export * from './schema-blocks-json-api.model'; diff --git a/src/app/features/registries/models/registration-json-api.model.ts b/src/app/features/registries/models/registration-json-api.model.ts deleted file mode 100644 index 21c013157..000000000 --- a/src/app/features/registries/models/registration-json-api.model.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ApiData, MetaJsonApi, PaginationLinksJsonApi } from '@osf/core/models'; -import { LicenseRecordJsonApi } from '@osf/shared/models'; - -export interface RegistrationResponseJsonApi { - data: RegistrationDataJsonApi; - meta: MetaJsonApi; - links: PaginationLinksJsonApi; -} - -export type RegistrationDataJsonApi = ApiData< - RegistrationAttributesJsonApi, - null, - RegistrationRelationshipsJsonApi, - null ->; - -export interface RegistrationAttributesJsonApi { - category: string; - current_user_permissions: string[]; - date_created: string; - datetime_updated: string; - description: string; - has_project: boolean; - node_license: LicenseRecordJsonApi; - registration_metadata: Record; - registration_responses: Record; - tags: string[]; - title: string; -} - -export interface RegistrationRelationshipsJsonApi { - registration_schema?: { - data: { - id: string; - type: 'registration-schemas'; - }; - }; - license?: { - data: { - id: string; - type: 'licenses'; - }; - }; - branched_from?: { - data: { - id: string; - type: 'nodes'; - }; - }; -} - -export interface RegistrationPayloadJsonApi { - data: { - type: 'draft_registrations'; - id: string; - relationships?: RegistrationRelationshipsJsonApi; - attributes?: Partial; - }; -} diff --git a/src/app/features/registries/pages/index.ts b/src/app/features/registries/pages/index.ts index d5bc067cd..a5e6a80e0 100644 --- a/src/app/features/registries/pages/index.ts +++ b/src/app/features/registries/pages/index.ts @@ -1 +1,2 @@ +export * from './my-registrations/my-registrations.component'; export * from './registries-landing/registries-landing.component'; diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.html b/src/app/features/registries/pages/my-registrations/my-registrations.component.html new file mode 100644 index 000000000..c812e4ec6 --- /dev/null +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.html @@ -0,0 +1,46 @@ +
+
+ +
+
+ + @if (!isMobile()) { + + @for (tab of tabOptions; track tab.value) { + {{ tab.label | translate }} + } + + } + + + @if (isMobile()) { + + } + + + @for (registration of draftRegistrations(); track registration.id) { + + } + + + + + @for (registration of submittedRegistrations(); track registration.registry) { + + } + + + +
+
diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.scss b/src/app/features/registries/pages/my-registrations/my-registrations.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.spec.ts b/src/app/features/registries/pages/my-registrations/my-registrations.component.spec.ts new file mode 100644 index 000000000..cdf878f0e --- /dev/null +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyRegistrationsComponent } from './my-registrations.component'; + +describe('MyRegistrationsComponent', () => { + let component: MyRegistrationsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MyRegistrationsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(MyRegistrationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.ts b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts new file mode 100644 index 000000000..e61fc9fce --- /dev/null +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts @@ -0,0 +1,99 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; + +import { TabsModule } from 'primeng/tabs'; + +import { tap } from 'rxjs'; + +import { ChangeDetectionStrategy, Component, effect, inject, signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; +import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component'; +import { CustomConfirmationService, LoaderService } from '@osf/shared/services'; +import { IS_XSMALL } from '@osf/shared/utils'; + +import { REGISTRATIONS_TABS } from '../../constants/registrations-tabs'; +import { DeleteDraft, FetchDraftRegistrations, FetchSubmittedRegistrations, RegistriesSelectors } from '../../store'; + +@Component({ + selector: 'osf-my-registrations', + imports: [SubHeaderComponent, TranslatePipe, TabsModule, FormsModule, SelectComponent, RegistrationCardComponent], + templateUrl: './my-registrations.component.html', + styleUrl: './my-registrations.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MyRegistrationsComponent { + private router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly loaderService = inject(LoaderService); + + private readonly translateService = inject(TranslateService); + private readonly customConfirmationService = inject(CustomConfirmationService); + + protected readonly isMobile = toSignal(inject(IS_XSMALL)); + protected readonly tabOptions = REGISTRATIONS_TABS; + + protected draftRegistrations = select(RegistriesSelectors.getDraftRegistrations); + protected submittedRegistrations = select(RegistriesSelectors.getSubmittedRegistrations); + + protected actions = createDispatchMap({ + getDraftRegistrations: FetchDraftRegistrations, + getSubmittedRegistrations: FetchSubmittedRegistrations, + deleteDraft: DeleteDraft, + }); + + selectedTab = signal(1); + + constructor() { + const initialTab = this.route.snapshot.queryParams['tab']; + if (initialTab == 'drafts') { + this.selectedTab.set(0); + } else { + this.selectedTab.set(1); + } + + effect(() => { + const tab = this.selectedTab(); + this.loaderService.show(); + + if (tab === 0) { + this.actions + .getDraftRegistrations() + .pipe(tap(() => this.loaderService.hide())) + .subscribe(); + } else { + this.actions + .getSubmittedRegistrations() + .pipe(tap(() => this.loaderService.hide())) + .subscribe(); + } + this.router.navigate([], { + relativeTo: this.route, + queryParams: { tab: tab === 0 ? 'drafts' : 'submitted' }, + queryParamsHandling: 'merge', + }); + }); + } + + goToCreateRegistration(): void { + this.router.navigate(['/registries/new']); + } + + onDeleteDraft(id: string): void { + this.customConfirmationService.confirmDelete({ + headerKey: 'registries.deleteDraft', + messageKey: 'registries.confirmDeleteDraft', + onConfirm: () => { + this.actions.deleteDraft(id).subscribe({ + next: () => { + this.actions.getDraftRegistrations(); + }, + }); + }, + }); + } +} diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index dc995a80c..74a7ed027 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -32,6 +32,10 @@ export const registriesRoutes: Routes = [ path: 'overview', loadComponent: () => import('@osf/features/registries/pages').then((c) => c.RegistriesLandingComponent), }, + { + path: 'my-registrations', + loadComponent: () => import('@osf/features/registries/pages').then((c) => c.MyRegistrationsComponent), + }, { path: 'moderation', loadComponent: () => diff --git a/src/app/features/registries/services/licenses.service.ts b/src/app/features/registries/services/licenses.service.ts index 03c0db321..8363db72a 100644 --- a/src/app/features/registries/services/licenses.service.ts +++ b/src/app/features/registries/services/licenses.service.ts @@ -3,10 +3,15 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/core/services'; -import { License, LicenseOptions, LicensesResponseJsonApi } from '@osf/shared/models'; +import { + CreateRegistrationPayloadJsonApi, + DraftRegistrationDataJsonApi, + License, + LicenseOptions, + LicensesResponseJsonApi, +} from '@osf/shared/models'; import { LicensesMapper } from '../mappers'; -import { RegistrationDataJsonApi, RegistrationPayloadJsonApi } from '../models'; import { environment } from 'src/environments/environment'; @@ -50,7 +55,7 @@ export class LicensesService { } updateLicense(registrationId: string, licenseId: string, licenseOptions?: LicenseOptions) { - const payload: RegistrationPayloadJsonApi = { + const payload: CreateRegistrationPayloadJsonApi = { data: { type: 'draft_registrations', id: registrationId, @@ -73,7 +78,7 @@ export class LicensesService { }, }; - return this.jsonApiService.patch( + return this.jsonApiService.patch( `${this.apiUrl}/draft_registrations/${registrationId}/`, payload ); diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 6000c35d7..e8a880f23 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -2,19 +2,23 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { JsonApiResponseWithPaging } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; -import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; - -import { PageSchemaMapper } from '../mappers'; -import { RegistrationMapper } from '../mappers/registration.mapper'; import { - PageSchema, + DraftRegistrationDataJsonApi, + DraftRegistrationModel, + DraftRegistrationRelationshipsJsonApi, + DraftRegistrationResponseJsonApi, RegistrationAttributesJsonApi, + RegistrationCard, RegistrationDataJsonApi, - RegistrationRelationshipsJsonApi, + RegistrationModel, RegistrationResponseJsonApi, - SchemaBlocksResponseJsonApi, -} from '../models'; +} from '@osf/shared/models'; + +import { PageSchemaMapper } from '../mappers'; +import { RegistrationMapper } from '../mappers/registration.mapper'; +import { PageSchema, SchemaBlocksResponseJsonApi } from '../models'; import { environment } from 'src/environments/environment'; @@ -48,20 +52,20 @@ export class RegistriesService { }, }; return this.jsonApiService - .post(`${this.apiUrl}/draft_registrations/`, payload) + .post(`${this.apiUrl}/draft_registrations/`, payload) .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response.data))); } getDraft(draftId: string): Observable { return this.jsonApiService - .get(`${this.apiUrl}/draft_registrations/${draftId}/`) + .get(`${this.apiUrl}/draft_registrations/${draftId}/`) .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response.data))); } updateDraft( id: string, attributes: Partial, - relationships?: Partial + relationships?: Partial ): Observable { const payload = { data: { @@ -73,7 +77,7 @@ export class RegistriesService { }; return this.jsonApiService - .patch(`${this.apiUrl}/draft_registrations/${id}/`, payload) + .patch(`${this.apiUrl}/draft_registrations/${id}/`, payload) .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response))); } @@ -117,4 +121,45 @@ export class RegistriesService { .get(`${this.apiUrl}/schemas/registrations/${registrationSchemaId}/schema_blocks/`) .pipe(map((response) => PageSchemaMapper.fromSchemaBlocksResponse(response))); } + + getDraftRegistrations(): Observable<{ data: RegistrationCard[]; totalCount: number }> { + const params = { + 'page[size]': 10, + embed: ['bibliographic_contributors', 'registration_schema', 'provider'], + }; + return this.jsonApiService + .get< + JsonApiResponseWithPaging + >(`${this.apiUrl}/draft_registrations/`, params) + .pipe( + map((response) => { + const data = response.data.map((registration: DraftRegistrationDataJsonApi) => + RegistrationMapper.fromDraftToRegistrationCard(registration) + ); + return { + data, + totalCount: response.links.meta?.total, + }; + }) + ); + } + + getSubmittedRegistrations(): Observable<{ data: RegistrationCard[]; totalCount: number }> { + return this.jsonApiService + .get>(`${this.apiUrl}/registrations/`, { + 'page[size]': 10, + embed: ['bibliographic_contributors', 'registration_schema', 'provider'], + }) + .pipe( + map((response) => { + const data = response.data.map((registration: RegistrationDataJsonApi) => + RegistrationMapper.fromRegistrationToRegistrationCard(registration) + ); + return { + data, + totalCount: response.links.meta?.total, + }; + }) + ); + } } diff --git a/src/app/features/registries/store/default.state.ts b/src/app/features/registries/store/default.state.ts index 348eb649e..19d8c515a 100644 --- a/src/app/features/registries/store/default.state.ts +++ b/src/app/features/registries/store/default.state.ts @@ -39,4 +39,16 @@ export const DefaultState: RegistriesStateModel = { isSubmitting: false, error: null, }, + draftRegistrations: { + data: [], + isLoading: false, + error: null, + totalCount: 0, + }, + submittedRegistrations: { + data: [], + isLoading: false, + error: null, + totalCount: 0, + }, }; diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index 1b7742f91..34e99854a 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -1,6 +1,8 @@ -import { LicenseOptions } from '@osf/shared/models'; - -import { RegistrationAttributesJsonApi, RegistrationRelationshipsJsonApi } from '../models'; +import { + DraftRegistrationAttributesJsonApi, + DraftRegistrationRelationshipsJsonApi, + LicenseOptions, +} from '@osf/shared/models'; export class GetRegistries { static readonly type = '[Registries] Get Registries'; @@ -28,8 +30,8 @@ export class UpdateDraft { static readonly type = '[Registries] Update Registration Tags'; constructor( public draftId: string, - public attributes: Partial, - public relationships?: Partial + public attributes: Partial, + public relationships?: Partial ) {} } @@ -72,3 +74,11 @@ export class UpdateStepValidation { public invalid: boolean ) {} } + +export class FetchDraftRegistrations { + static readonly type = '[Registries] Fetch Draft Registrations'; +} + +export class FetchSubmittedRegistrations { + static readonly type = '[Registries] Fetch Submitted Registrations'; +} diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index 96bce982f..17498743c 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -1,5 +1,11 @@ -import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; -import { AsyncStateModel, License, Resource } from '@shared/models'; +import { + AsyncStateModel, + DraftRegistrationModel, + License, + RegistrationCard, + RegistrationModel, + Resource, +} from '@shared/models'; import { PageSchema, Project, Provider } from '../models'; @@ -12,4 +18,10 @@ export interface RegistriesStateModel { licenses: AsyncStateModel; pagesSchema: AsyncStateModel; stepsValidation: Record; + draftRegistrations: AsyncStateModel & { + totalCount: number; + }; + submittedRegistrations: AsyncStateModel & { + totalCount: number; + }; } diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index c93296c4b..a23a6351b 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -1,7 +1,6 @@ import { Selector } from '@ngxs/store'; -import { DraftRegistrationModel } from '@osf/shared/models/registration'; -import { License, Resource } from '@shared/models'; +import { DraftRegistrationModel, License, RegistrationCard, Resource } from '@shared/models'; import { PageSchema, Project, Provider } from '../models'; @@ -83,4 +82,24 @@ export class RegistriesSelectors { static isRegistrationSubmitting(state: RegistriesStateModel): boolean { return state.registration.isSubmitting || false; } + + @Selector([RegistriesState]) + static getDraftRegistrations(state: RegistriesStateModel): RegistrationCard[] { + return state.draftRegistrations.data; + } + + @Selector([RegistriesState]) + static isDraftRegistrationsLoading(state: RegistriesStateModel): boolean { + return state.draftRegistrations.isLoading; + } + + @Selector([RegistriesState]) + static getSubmittedRegistrations(state: RegistriesStateModel): RegistrationCard[] { + return state.submittedRegistrations.data; + } + + @Selector([RegistriesState]) + static isSubmittedRegistrationsLoading(state: RegistriesStateModel): boolean { + return state.submittedRegistrations.isLoading; + } } diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index a600666f2..f2bda6e47 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -19,8 +19,10 @@ import { CreateDraft, DeleteDraft, FetchDraft, + FetchDraftRegistrations, FetchLicenses, FetchSchemaBlocks, + FetchSubmittedRegistrations, GetProjects, GetProviders, GetRegistries, @@ -250,4 +252,52 @@ export class RegistriesState { saveLicense(ctx: StateContext, { registrationId, licenseId, licenseOptions }: SaveLicense) { return this.licensesHandler.saveLicense(ctx, { registrationId, licenseId, licenseOptions }); } + + @Action(FetchDraftRegistrations) + fetchDraftRegistrations(ctx: StateContext) { + const state = ctx.getState(); + ctx.patchState({ + draftRegistrations: { + ...state.draftRegistrations, + isLoading: true, + error: null, + }, + }); + + return this.registriesService.getDraftRegistrations().pipe( + tap((draftRegistrations) => { + ctx.patchState({ + draftRegistrations: { + data: draftRegistrations.data, + totalCount: draftRegistrations.totalCount, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'draftRegistrations', error)) + ); + } + + @Action(FetchSubmittedRegistrations) + fetchSubmittedRegistrations(ctx: StateContext) { + const state = ctx.getState(); + ctx.patchState({ + submittedRegistrations: { ...state.submittedRegistrations, isLoading: true, error: null }, + }); + + return this.registriesService.getSubmittedRegistrations().pipe( + tap((submittedRegistrations) => { + ctx.patchState({ + submittedRegistrations: { + data: submittedRegistrations.data, + totalCount: submittedRegistrations.totalCount, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'submittedRegistrations', error)) + ); + } } diff --git a/src/app/shared/components/registration-card/registration-card.component.html b/src/app/shared/components/registration-card/registration-card.component.html new file mode 100644 index 000000000..098685f27 --- /dev/null +++ b/src/app/shared/components/registration-card/registration-card.component.html @@ -0,0 +1,125 @@ +
+ + @if (registrationData()) { +
+
+ +

+ {{ registrationData().title || 'project.registrations.card.noTitle' | translate }} +

+ +
+
+
+
+ {{ 'project.registrations.card.registrationTemplate' | translate }} + {{ registrationData().registrationTemplate }} +
+ +
+ {{ 'project.registrations.card.registry' | translate }} + {{ registrationData().registry }} +
+ +
+ {{ 'project.registrations.card.registered' | translate }} + {{ registrationData().dateCreated | date: 'medium' }} +
+ +
+ {{ 'project.registrations.card.lastUpdated' | translate }} + {{ registrationData().dateModified | date: 'medium' }} +
+ +
+ {{ 'project.overview.metadata.contributors' | translate }}: + + @for (contributor of registrationData().contributors; track contributor) { + {{ contributor.fullName }} + @if (!$last) { + , + } + } + +
+ +
+ {{ 'project.registrations.card.description' | translate }} +

{{ registrationData().description || 'project.registrations.card.noDescription' | translate }}

+
+
+ @if (registrationData().status === RegistrationStatus.None) { + + + + } @else { + + @if (registrationData().status === RegistrationStatus.InProgress) { + + } + } +
+
+ + +
+
+ } +
+
diff --git a/src/app/features/project/registrations/components/registration-card/registration-card.component.scss b/src/app/shared/components/registration-card/registration-card.component.scss similarity index 100% rename from src/app/features/project/registrations/components/registration-card/registration-card.component.scss rename to src/app/shared/components/registration-card/registration-card.component.scss diff --git a/src/app/features/project/registrations/components/registration-card/registration-card.component.spec.ts b/src/app/shared/components/registration-card/registration-card.component.spec.ts similarity index 100% rename from src/app/features/project/registrations/components/registration-card/registration-card.component.spec.ts rename to src/app/shared/components/registration-card/registration-card.component.spec.ts diff --git a/src/app/shared/components/registration-card/registration-card.component.ts b/src/app/shared/components/registration-card/registration-card.component.ts new file mode 100644 index 000000000..cef9a114c --- /dev/null +++ b/src/app/shared/components/registration-card/registration-card.component.ts @@ -0,0 +1,25 @@ +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Tag } from 'primeng/tag'; + +import { DatePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +import { RegistryStatus } from '@osf/shared/enums'; +import { RegistrationCard } from '@osf/shared/models'; + +@Component({ + selector: 'osf-registration-card', + imports: [Card, Button, Tag, TranslatePipe, DatePipe, RouterLink], + templateUrl: './registration-card.component.html', + styleUrl: './registration-card.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RegistrationCardComponent { + RegistrationStatus = RegistryStatus; + readonly registrationData = input.required(); + readonly deleteDraft = output(); +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 809165bd6..a2b9274ee 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -22,6 +22,7 @@ export * from './nodes/create-project-form.model'; export * from './nodes/nodes-json-api.model'; export * from './paginated-data.model'; export * from './query-params.model'; +export * from './registration'; export * from './resource-card'; export * from './resource-overview.model'; export * from './search'; diff --git a/src/app/shared/models/registration/index.ts b/src/app/shared/models/registration/index.ts index 58f03a095..510ca14e1 100644 --- a/src/app/shared/models/registration/index.ts +++ b/src/app/shared/models/registration/index.ts @@ -1,2 +1,4 @@ export * from './draft-registration.model'; export * from './registration.model'; +export * from './registration-card.model'; +export * from './registration-json-api.model'; diff --git a/src/app/shared/models/registration/registration-card.model.ts b/src/app/shared/models/registration/registration-card.model.ts new file mode 100644 index 000000000..2e3c76f72 --- /dev/null +++ b/src/app/shared/models/registration/registration-card.model.ts @@ -0,0 +1,16 @@ +import { RegistryStatus } from '@osf/shared/enums'; + +import { ContributorModel } from '../contributors'; + +export interface RegistrationCard { + id: string; + title: string; + description: string; + status: RegistryStatus; + dateCreated: string; + dateModified: string; + contributors: Partial[]; + registrationTemplate: string; + registry: string; + resources?: Record; +} diff --git a/src/app/shared/models/registration/registration-json-api.model.ts b/src/app/shared/models/registration/registration-json-api.model.ts new file mode 100644 index 000000000..e79d94840 --- /dev/null +++ b/src/app/shared/models/registration/registration-json-api.model.ts @@ -0,0 +1,133 @@ +import { ApiData, MetaJsonApi, PaginationLinksJsonApi } from '@osf/core/models'; +import { LicenseRecordJsonApi } from '@osf/shared/models'; + +export interface DraftRegistrationResponseJsonApi { + data: DraftRegistrationDataJsonApi; + meta: MetaJsonApi; + links: PaginationLinksJsonApi; +} + +export interface RegistrationResponseJsonApi { + data: RegistrationDataJsonApi; + meta: MetaJsonApi; + links: PaginationLinksJsonApi; +} + +export type DraftRegistrationDataJsonApi = ApiData< + DraftRegistrationAttributesJsonApi, + DraftRegistrationEmbedsJsonApi, + DraftRegistrationRelationshipsJsonApi, + null +>; + +export type RegistrationDataJsonApi = ApiData< + RegistrationAttributesJsonApi, + RegistrationEmbedsJsonApi, + RegistrationRelationshipsJsonApi, + null +>; + +export interface DraftRegistrationAttributesJsonApi { + category: string; + date_created: string; + datetime_initiated: string; + datetime_updated: string; + description: string; + has_project: boolean; + node_license: LicenseRecordJsonApi; + registration_metadata: Record; + registration_responses: Record; + tags: string[]; + title: string; +} + +export interface RegistrationAttributesJsonApi { + access_requests_enabled: boolean; + datetime_initiated: string; + date_modified: string; + description: string; + embargoed: boolean; + archiving: boolean; + title: string; +} + +export interface DraftRegistrationRelationshipsJsonApi { + registration_schema?: { + data: { + id: string; + type: 'registration-schemas'; + }; + }; + license?: { + data: { + id: string; + type: 'licenses'; + }; + }; + branched_from?: { + data: { + id: string; + type: 'nodes'; + }; + }; +} + +export interface RegistrationRelationshipsJsonApi { + registration_schema?: { + data: { + id: string; + type: 'registration-schemas'; + }; + }; + license?: { + data: { + id: string; + type: 'licenses'; + }; + }; +} + +export interface RegistrationEmbedsJsonApi { + registration_schema?: { + data: { + attributes: { + name: string; + }; + }; + }; + bibliographic_contributors?: { + data: { + id: string; + type: 'users'; + embeds: { + users: { + data: { + attributes: { + full_name: string; + }; + id: string; + }; + }; + }; + }[]; + }; + provider?: { + data: { + attributes: { + name: string; + }; + }; + }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface DraftRegistrationEmbedsJsonApi extends RegistrationEmbedsJsonApi {} + +export interface CreateRegistrationPayloadJsonApi { + data: { + type: 'draft_registrations'; + id: string; + relationships?: DraftRegistrationRelationshipsJsonApi; + attributes?: Partial; + }; +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 376cb6ce2..59b88e9af 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -31,7 +31,9 @@ "update": "Update", "continue update": "Continue update", "withdraw": "Withdraw", - "submit": "Submit" + "submit": "Submit", + "view": "View", + "review": "Review" }, "search": { "title": "Search", @@ -72,7 +74,9 @@ "optional": "Optional", "makePublic": "Make Public", "noData": "No data", - "noFiles": "No files selected" + "noFiles": "No files selected", + "drafts": "Drafts", + "submitted": "Submitted" }, "deleteConfirmation": { "header": "Delete", @@ -706,6 +710,8 @@ "addRegistration": "Add a Registration", "emptyState": "There have been no completed registrations of this project.", "card": { + "noTitle": "(Untitled)", + "noDescription": "No description", "registrationTemplate": "Registration template:", "registry": "Registry:", "registered": "Registered:", From daf725c19fdefe808a331c354649f436eeea5d73 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Sat, 12 Jul 2025 19:30:20 +0300 Subject: [PATCH 2/4] feat(registrations): refactor project registrations --- .../project/registrations/mappers/index.ts | 1 - .../mappers/registrations.mapper.ts | 26 ------------ .../project/registrations/models/index.ts | 1 - .../models/registrations.model.ts | 40 ------------------- .../services/registrations.service.ts | 23 ++++++----- .../store/registrations.model.ts | 6 ++- .../store/registrations.state.ts | 12 +++--- .../registries/services/registries.service.ts | 2 +- src/app/shared/mappers/registration/index.ts | 1 + .../registration}/registration.mapper.ts | 0 10 files changed, 26 insertions(+), 86 deletions(-) delete mode 100644 src/app/features/project/registrations/mappers/index.ts delete mode 100644 src/app/features/project/registrations/mappers/registrations.mapper.ts delete mode 100644 src/app/features/project/registrations/models/index.ts delete mode 100644 src/app/features/project/registrations/models/registrations.model.ts create mode 100644 src/app/shared/mappers/registration/index.ts rename src/app/{features/registries/mappers => shared/mappers/registration}/registration.mapper.ts (100%) diff --git a/src/app/features/project/registrations/mappers/index.ts b/src/app/features/project/registrations/mappers/index.ts deleted file mode 100644 index 5afc367f2..000000000 --- a/src/app/features/project/registrations/mappers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RegistrationsMapper } from './registrations.mapper'; diff --git a/src/app/features/project/registrations/mappers/registrations.mapper.ts b/src/app/features/project/registrations/mappers/registrations.mapper.ts deleted file mode 100644 index 71c66236d..000000000 --- a/src/app/features/project/registrations/mappers/registrations.mapper.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RegistryStatus } from '@osf/shared/enums'; -import { RegistrationModel } from '@osf/shared/models'; - -import { RegistrationsGetResponse } from '../models'; - -export class RegistrationsMapper { - static fromResponse(response: RegistrationsGetResponse): RegistrationModel { - return { - id: response.id, - type: response.type, - title: response.attributes?.title, - dateRegistered: response.attributes?.date_registered, - dateModified: response.attributes?.date_modified, - // registrationSupplement: response.attributes?.registration_supplement, - // registry: '', - description: response.attributes?.description, - // withdrawn: response.attributes?.withdrawn, - // lastFetched: Date.now(), - status: response.attributes?.withdrawn - ? RegistryStatus.PendingWithdraw - : response.attributes?.date_modified - ? RegistryStatus.InProgress - : RegistryStatus.Pending, - } as RegistrationModel; - } -} diff --git a/src/app/features/project/registrations/models/index.ts b/src/app/features/project/registrations/models/index.ts deleted file mode 100644 index af11e9261..000000000 --- a/src/app/features/project/registrations/models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './registrations.model'; diff --git a/src/app/features/project/registrations/models/registrations.model.ts b/src/app/features/project/registrations/models/registrations.model.ts deleted file mode 100644 index a05067cb2..000000000 --- a/src/app/features/project/registrations/models/registrations.model.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface RegistrationsGetResponse { - id: string; - type: string; - attributes: RegistrationsAttributes; -} - -interface RegistrationsAttributes { - title: string; - date_registered: string; - date_modified: string; - registration_supplement: string; - description: string; - withdrawn: boolean; -} - -export interface RegistrationModel { - id: string; - type: string; - lastFetched?: number; - title: string; - dateRegistered: string; - dateModified: string; - registrationSupplement: string; - description: string; - withdrawn: boolean; - registry: string; - status: RegistrationStatus; - contributors?: Contributor[]; -} - -export interface Contributor { - name: string; - link?: string; -} -export enum RegistrationStatus { - DRAFT = 'draft', - IN_PROGRESS = 'in_progress', - WITHDRAWN = 'withdrawn', - SUBMITTED = 'submitted', -} diff --git a/src/app/features/project/registrations/services/registrations.service.ts b/src/app/features/project/registrations/services/registrations.service.ts index 17c678a15..706a54f01 100644 --- a/src/app/features/project/registrations/services/registrations.service.ts +++ b/src/app/features/project/registrations/services/registrations.service.ts @@ -2,13 +2,10 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { JsonApiResponse } from '@osf/core/models'; +import { JsonApiResponseWithPaging } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; -import { RegistrationModel } from '@osf/shared/models'; - -import { RegistrationsGetResponse } from '../models'; - -import { RegistrationsMapper } from './../mappers'; +import { RegistrationMapper } from '@osf/shared/mappers/registration'; +import { RegistrationCard, RegistrationDataJsonApi } from '@osf/shared/models'; import { environment } from 'src/environments/environment'; @@ -16,17 +13,23 @@ import { environment } from 'src/environments/environment'; providedIn: 'root', }) export class RegistrationsService { - #jsonApiService = inject(JsonApiService); + private readonly jsonApiService = inject(JsonApiService); - getRegistrations(projectId: string): Observable { + getRegistrations(projectId: string): Observable<{ data: RegistrationCard[]; totalCount: number }> { const params: Record = { embed: 'contributors', }; const url = `${environment.apiUrl}/nodes/${projectId}/linked_by_registrations/`; - return this.#jsonApiService.get>(url, params).pipe( + return this.jsonApiService.get>(url, params).pipe( map((response) => { - return response.data.map((registration) => RegistrationsMapper.fromResponse(registration)); + const data = response.data.map((registration: RegistrationDataJsonApi) => + RegistrationMapper.fromRegistrationToRegistrationCard(registration) + ); + return { + data, + totalCount: response.links.meta?.total, + }; }) ); } diff --git a/src/app/features/project/registrations/store/registrations.model.ts b/src/app/features/project/registrations/store/registrations.model.ts index 0919559de..a5a72de86 100644 --- a/src/app/features/project/registrations/store/registrations.model.ts +++ b/src/app/features/project/registrations/store/registrations.model.ts @@ -1,6 +1,8 @@ -import { RegistrationModel } from '@osf/shared/models'; +import { RegistrationCard } from '@osf/shared/models'; import { AsyncStateModel } from '@osf/shared/models/store'; export interface RegistrationsStateModel { - registrations: AsyncStateModel; + registrations: AsyncStateModel & { + totalCount: number; + }; } diff --git a/src/app/features/project/registrations/store/registrations.state.ts b/src/app/features/project/registrations/store/registrations.state.ts index c9ff3db45..b51e1e032 100644 --- a/src/app/features/project/registrations/store/registrations.state.ts +++ b/src/app/features/project/registrations/store/registrations.state.ts @@ -15,13 +15,14 @@ import { RegistrationsStateModel } from './registrations.model'; registrations: { data: [], isLoading: false, - error: '', + error: null, + totalCount: 0, }, }, }) @Injectable() export class RegistrationsState { - #registrationsService = inject(RegistrationsService); + private readonly registrationsService = inject(RegistrationsService); @Action(GetRegistrations) getRegistrations(ctx: StateContext, action: GetRegistrations) { @@ -31,15 +32,16 @@ export class RegistrationsState { registrations: { ...state.registrations, isLoading: true, error: null }, }); - return this.#registrationsService.getRegistrations(action.projectId).pipe( + return this.registrationsService.getRegistrations(action.projectId).pipe( tap((registrations) => { const state = ctx.getState(); ctx.setState({ ...state, registrations: { - data: registrations, + data: registrations.data, isLoading: false, - error: '', + error: null, + totalCount: registrations.totalCount, }, }); }), diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index e8a880f23..1e9598f3b 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -4,6 +4,7 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiResponseWithPaging } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; +import { RegistrationMapper } from '@osf/shared/mappers/registration'; import { DraftRegistrationDataJsonApi, DraftRegistrationModel, @@ -17,7 +18,6 @@ import { } from '@osf/shared/models'; import { PageSchemaMapper } from '../mappers'; -import { RegistrationMapper } from '../mappers/registration.mapper'; import { PageSchema, SchemaBlocksResponseJsonApi } from '../models'; import { environment } from 'src/environments/environment'; diff --git a/src/app/shared/mappers/registration/index.ts b/src/app/shared/mappers/registration/index.ts new file mode 100644 index 000000000..8f0e07dd0 --- /dev/null +++ b/src/app/shared/mappers/registration/index.ts @@ -0,0 +1 @@ +export * from './registration.mapper'; diff --git a/src/app/features/registries/mappers/registration.mapper.ts b/src/app/shared/mappers/registration/registration.mapper.ts similarity index 100% rename from src/app/features/registries/mappers/registration.mapper.ts rename to src/app/shared/mappers/registration/registration.mapper.ts From d98930989ebb17feea7519b6e200194695c09dd0 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Mon, 14 Jul 2025 10:25:42 +0300 Subject: [PATCH 3/4] feat(registration): add loading state and pagination --- .../my-registrations.component.html | 51 ++++++++++++++++-- .../my-registrations.component.ts | 54 +++++++++++++------ .../registries/services/registries.service.ts | 20 ++++--- .../registries/store/registries.actions.ts | 8 +++ .../registries/store/registries.selectors.ts | 10 ++++ .../registries/store/registries.state.ts | 11 ++-- src/assets/i18n/en.json | 1 + 7 files changed, 124 insertions(+), 31 deletions(-) diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.html b/src/app/features/registries/pages/my-registrations/my-registrations.component.html index c812e4ec6..742a1ac72 100644 --- a/src/app/features/registries/pages/my-registrations/my-registrations.component.html +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.html @@ -29,18 +29,61 @@ } - @for (registration of draftRegistrations(); track registration.id) { - + @if (isDraftRegistrationsLoading()) { + @for (item of skeletons; track $index) { + + } + } @else { + @if (draftRegistrationsTotalCount() === 0) { + + } + @for (registration of draftRegistrations(); track registration.id) { + + } + @if (draftRegistrationsTotalCount() > itemsPerPage) { + + } } - @for (registration of submittedRegistrations(); track registration.registry) { - + @if (isSubmittedRegistrationsLoading()) { + @for (item of skeletons; track $index) { + + } + } @else { + @if (submittedRegistrationsTotalCount() === 0) { + + } + @for (registration of submittedRegistrations(); track registration.registry) { + + } + @if (submittedRegistrationsTotalCount() > itemsPerPage) { + + } } + + +
+ {{ 'registries.noRegistrations' | translate }} + + +
+
diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.ts b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts index e61fc9fce..8525a2a39 100644 --- a/src/app/features/registries/pages/my-registrations/my-registrations.component.ts +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts @@ -2,18 +2,20 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { Button } from 'primeng/button'; +import { PaginatorState } from 'primeng/paginator'; +import { Skeleton } from 'primeng/skeleton'; import { TabsModule } from 'primeng/tabs'; -import { tap } from 'rxjs'; - +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, effect, inject, signal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; +import { CustomPaginatorComponent, SelectComponent, SubHeaderComponent } from '@osf/shared/components'; import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component'; -import { CustomConfirmationService, LoaderService } from '@osf/shared/services'; +import { CustomConfirmationService } from '@osf/shared/services'; import { IS_XSMALL } from '@osf/shared/utils'; import { REGISTRATIONS_TABS } from '../../constants/registrations-tabs'; @@ -21,7 +23,19 @@ import { DeleteDraft, FetchDraftRegistrations, FetchSubmittedRegistrations, Regi @Component({ selector: 'osf-my-registrations', - imports: [SubHeaderComponent, TranslatePipe, TabsModule, FormsModule, SelectComponent, RegistrationCardComponent], + imports: [ + SubHeaderComponent, + TranslatePipe, + TabsModule, + FormsModule, + SelectComponent, + RegistrationCardComponent, + CustomPaginatorComponent, + Skeleton, + Button, + RouterLink, + NgTemplateOutlet, + ], templateUrl: './my-registrations.component.html', styleUrl: './my-registrations.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -29,7 +43,6 @@ import { DeleteDraft, FetchDraftRegistrations, FetchSubmittedRegistrations, Regi export class MyRegistrationsComponent { private router = inject(Router); private readonly route = inject(ActivatedRoute); - private readonly loaderService = inject(LoaderService); private readonly translateService = inject(TranslateService); private readonly customConfirmationService = inject(CustomConfirmationService); @@ -38,7 +51,11 @@ export class MyRegistrationsComponent { protected readonly tabOptions = REGISTRATIONS_TABS; protected draftRegistrations = select(RegistriesSelectors.getDraftRegistrations); + protected draftRegistrationsTotalCount = select(RegistriesSelectors.getDraftRegistrationsTotalCount); + protected isDraftRegistrationsLoading = select(RegistriesSelectors.isDraftRegistrationsLoading); protected submittedRegistrations = select(RegistriesSelectors.getSubmittedRegistrations); + protected submittedRegistrationsTotalCount = select(RegistriesSelectors.getSubmittedRegistrationsTotalCount); + protected isSubmittedRegistrationsLoading = select(RegistriesSelectors.isSubmittedRegistrationsLoading); protected actions = createDispatchMap({ getDraftRegistrations: FetchDraftRegistrations, @@ -46,7 +63,11 @@ export class MyRegistrationsComponent { deleteDraft: DeleteDraft, }); + readonly provider = 'osf'; + selectedTab = signal(1); + itemsPerPage = 10; + skeletons = [...Array(8)]; constructor() { const initialTab = this.route.snapshot.queryParams['tab']; @@ -58,18 +79,11 @@ export class MyRegistrationsComponent { effect(() => { const tab = this.selectedTab(); - this.loaderService.show(); if (tab === 0) { - this.actions - .getDraftRegistrations() - .pipe(tap(() => this.loaderService.hide())) - .subscribe(); + this.actions.getDraftRegistrations(); } else { - this.actions - .getSubmittedRegistrations() - .pipe(tap(() => this.loaderService.hide())) - .subscribe(); + this.actions.getSubmittedRegistrations(); } this.router.navigate([], { relativeTo: this.route, @@ -96,4 +110,12 @@ export class MyRegistrationsComponent { }, }); } + + onDraftsPageChange(event: PaginatorState): void { + this.actions.getDraftRegistrations(event.page! + 1); + } + + onSubmittedPageChange(event: PaginatorState): void { + this.actions.getSubmittedRegistrations(event.page! + 1); + } } diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 1e9598f3b..7eb30aad7 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -122,9 +122,10 @@ export class RegistriesService { .pipe(map((response) => PageSchemaMapper.fromSchemaBlocksResponse(response))); } - getDraftRegistrations(): Observable<{ data: RegistrationCard[]; totalCount: number }> { + getDraftRegistrations(page: number, pageSize: number): Observable<{ data: RegistrationCard[]; totalCount: number }> { const params = { - 'page[size]': 10, + page, + 'page[size]': pageSize, embed: ['bibliographic_contributors', 'registration_schema', 'provider'], }; return this.jsonApiService @@ -144,12 +145,17 @@ export class RegistriesService { ); } - getSubmittedRegistrations(): Observable<{ data: RegistrationCard[]; totalCount: number }> { + getSubmittedRegistrations( + page: number, + pageSize: number + ): Observable<{ data: RegistrationCard[]; totalCount: number }> { + const params = { + page, + 'page[size]': pageSize, + embed: ['bibliographic_contributors', 'registration_schema', 'provider'], + }; return this.jsonApiService - .get>(`${this.apiUrl}/registrations/`, { - 'page[size]': 10, - embed: ['bibliographic_contributors', 'registration_schema', 'provider'], - }) + .get>(`${this.apiUrl}/registrations/`, params) .pipe( map((response) => { const data = response.data.map((registration: RegistrationDataJsonApi) => diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index 34e99854a..ba30f2fd2 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -77,8 +77,16 @@ export class UpdateStepValidation { export class FetchDraftRegistrations { static readonly type = '[Registries] Fetch Draft Registrations'; + constructor( + public page = 1, + public pageSize = 10 + ) {} } export class FetchSubmittedRegistrations { static readonly type = '[Registries] Fetch Submitted Registrations'; + constructor( + public page = 1, + public pageSize = 10 + ) {} } diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index a23a6351b..f40112415 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -93,6 +93,11 @@ export class RegistriesSelectors { return state.draftRegistrations.isLoading; } + @Selector([RegistriesState]) + static getDraftRegistrationsTotalCount(state: RegistriesStateModel): number { + return state.draftRegistrations.totalCount; + } + @Selector([RegistriesState]) static getSubmittedRegistrations(state: RegistriesStateModel): RegistrationCard[] { return state.submittedRegistrations.data; @@ -102,4 +107,9 @@ export class RegistriesSelectors { static isSubmittedRegistrationsLoading(state: RegistriesStateModel): boolean { return state.submittedRegistrations.isLoading; } + + @Selector([RegistriesState]) + static getSubmittedRegistrationsTotalCount(state: RegistriesStateModel): number { + return state.submittedRegistrations.totalCount; + } } diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index f2bda6e47..d9ffa102f 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -254,7 +254,7 @@ export class RegistriesState { } @Action(FetchDraftRegistrations) - fetchDraftRegistrations(ctx: StateContext) { + fetchDraftRegistrations(ctx: StateContext, { page, pageSize }: FetchDraftRegistrations) { const state = ctx.getState(); ctx.patchState({ draftRegistrations: { @@ -264,7 +264,7 @@ export class RegistriesState { }, }); - return this.registriesService.getDraftRegistrations().pipe( + return this.registriesService.getDraftRegistrations(page, pageSize).pipe( tap((draftRegistrations) => { ctx.patchState({ draftRegistrations: { @@ -280,13 +280,16 @@ export class RegistriesState { } @Action(FetchSubmittedRegistrations) - fetchSubmittedRegistrations(ctx: StateContext) { + fetchSubmittedRegistrations( + ctx: StateContext, + { page, pageSize }: FetchSubmittedRegistrations + ) { const state = ctx.getState(); ctx.patchState({ submittedRegistrations: { ...state.submittedRegistrations, isLoading: true, error: null }, }); - return this.registriesService.getSubmittedRegistrations().pipe( + return this.registriesService.getSubmittedRegistrations(page, pageSize).pipe( tap((submittedRegistrations) => { ctx.patchState({ submittedRegistrations: { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 3df038b05..415506911 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1809,6 +1809,7 @@ "registries": { "title": "Registries", "addRegistration": "Add a Registration", + "noRegistrations": "You don't have any registrations. Create one with the button below.", "description": "The open registries network", "searchPlaceholder": "Search Registrations", "services": { From 24b8225fa17ee63ae9656567fde3cc638c00d2c3 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Mon, 14 Jul 2025 13:40:12 +0300 Subject: [PATCH 4/4] feat(registration): PR comments --- .../registrations/store/registrations.model.ts | 6 ++---- .../registries/constants/registrations-tabs.ts | 6 ++++-- src/app/features/registries/enums/index.ts | 1 + .../registries/enums/registration-tab.enum.ts | 4 ++++ .../my-registrations.component.html | 4 ++-- .../my-registrations.component.ts | 18 +++++++++++------- .../registries/store/registries.model.ts | 9 +++------ src/assets/i18n/en.json | 1 + 8 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/app/features/registries/enums/registration-tab.enum.ts diff --git a/src/app/features/project/registrations/store/registrations.model.ts b/src/app/features/project/registrations/store/registrations.model.ts index a5a72de86..63fb02293 100644 --- a/src/app/features/project/registrations/store/registrations.model.ts +++ b/src/app/features/project/registrations/store/registrations.model.ts @@ -1,8 +1,6 @@ import { RegistrationCard } from '@osf/shared/models'; -import { AsyncStateModel } from '@osf/shared/models/store'; +import { AsyncStateWithTotalCount } from '@osf/shared/models/store'; export interface RegistrationsStateModel { - registrations: AsyncStateModel & { - totalCount: number; - }; + registrations: AsyncStateWithTotalCount; } diff --git a/src/app/features/registries/constants/registrations-tabs.ts b/src/app/features/registries/constants/registrations-tabs.ts index 5dfedb63b..f1866fb61 100644 --- a/src/app/features/registries/constants/registrations-tabs.ts +++ b/src/app/features/registries/constants/registrations-tabs.ts @@ -1,12 +1,14 @@ import { TabOption } from '@osf/shared/models'; +import { RegistrationTab } from '../enums'; + export const REGISTRATIONS_TABS: TabOption[] = [ { label: 'common.labels.drafts', - value: 0, + value: RegistrationTab.Drafts, }, { label: 'common.labels.submitted', - value: 1, + value: RegistrationTab.Submitted, }, ]; diff --git a/src/app/features/registries/enums/index.ts b/src/app/features/registries/enums/index.ts index f7bf73467..70c02a5da 100644 --- a/src/app/features/registries/enums/index.ts +++ b/src/app/features/registries/enums/index.ts @@ -1,3 +1,4 @@ export * from './block-type.enum'; export * from './field-type.enum'; +export * from './registration-tab.enum'; export * from './submit-type.enum'; diff --git a/src/app/features/registries/enums/registration-tab.enum.ts b/src/app/features/registries/enums/registration-tab.enum.ts new file mode 100644 index 000000000..67eeac498 --- /dev/null +++ b/src/app/features/registries/enums/registration-tab.enum.ts @@ -0,0 +1,4 @@ +export enum RegistrationTab { + Drafts, + Submitted, +} diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.html b/src/app/features/registries/pages/my-registrations/my-registrations.component.html index ac943b01f..3ca18a7ef 100644 --- a/src/app/features/registries/pages/my-registrations/my-registrations.component.html +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.html @@ -28,7 +28,7 @@ > } - + @if (isDraftRegistrationsLoading()) { @for (item of skeletons; track $index) { @@ -51,7 +51,7 @@ - + @if (isSubmittedRegistrationsLoading()) { @for (item of skeletons; track $index) { diff --git a/src/app/features/registries/pages/my-registrations/my-registrations.component.ts b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts index 90e82e6ed..ebf9690fb 100644 --- a/src/app/features/registries/pages/my-registrations/my-registrations.component.ts +++ b/src/app/features/registries/pages/my-registrations/my-registrations.component.ts @@ -1,6 +1,6 @@ import { createDispatchMap, select } from '@ngxs/store'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { PaginatorState } from 'primeng/paginator'; @@ -15,10 +15,11 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { CustomPaginatorComponent, SelectComponent, SubHeaderComponent } from '@osf/shared/components'; import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component'; -import { CustomConfirmationService } from '@osf/shared/services'; +import { CustomConfirmationService, ToastService } from '@osf/shared/services'; import { IS_XSMALL } from '@osf/shared/utils'; import { REGISTRATIONS_TABS } from '../../constants/registrations-tabs'; +import { RegistrationTab } from '../../enums'; import { DeleteDraft, FetchDraftRegistrations, FetchSubmittedRegistrations, RegistriesSelectors } from '../../store'; @Component({ @@ -44,8 +45,8 @@ export class MyRegistrationsComponent { private router = inject(Router); private readonly route = inject(ActivatedRoute); - private readonly translateService = inject(TranslateService); private readonly customConfirmationService = inject(CustomConfirmationService); + private readonly toastService = inject(ToastService); protected readonly isMobile = toSignal(inject(IS_XSMALL)); protected readonly tabOptions = REGISTRATIONS_TABS; @@ -63,9 +64,11 @@ export class MyRegistrationsComponent { deleteDraft: DeleteDraft, }); + protected readonly RegistrationTab = RegistrationTab; + readonly provider = 'osf'; - selectedTab = signal(1); + selectedTab = signal(RegistrationTab.Submitted); itemsPerPage = 10; skeletons = [...Array(8)]; draftFirst = 0; @@ -74,9 +77,9 @@ export class MyRegistrationsComponent { constructor() { const initialTab = this.route.snapshot.queryParams['tab']; if (initialTab == 'drafts') { - this.selectedTab.set(0); + this.selectedTab.set(RegistrationTab.Drafts); } else { - this.selectedTab.set(1); + this.selectedTab.set(RegistrationTab.Submitted); } effect(() => { @@ -89,7 +92,7 @@ export class MyRegistrationsComponent { } this.router.navigate([], { relativeTo: this.route, - queryParams: { tab: tab === 0 ? 'drafts' : 'submitted' }, + queryParams: { tab: tab === RegistrationTab.Drafts ? 'drafts' : 'submitted' }, queryParamsHandling: 'merge', }); }); @@ -106,6 +109,7 @@ export class MyRegistrationsComponent { onConfirm: () => { this.actions.deleteDraft(id).subscribe({ next: () => { + this.toastService.showSuccess('registries.successDeleteDraft'); this.actions.getDraftRegistrations(); }, }); diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index b739951b4..ec41ab1b2 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -1,5 +1,6 @@ import { AsyncStateModel, + AsyncStateWithTotalCount, DraftRegistrationModel, License, RegistrationCard, @@ -18,10 +19,6 @@ export interface RegistriesStateModel { licenses: AsyncStateModel; pagesSchema: AsyncStateModel; stepsValidation: Record; - draftRegistrations: AsyncStateModel & { - totalCount: number; - }; - submittedRegistrations: AsyncStateModel & { - totalCount: number; - }; + draftRegistrations: AsyncStateWithTotalCount; + submittedRegistrations: AsyncStateWithTotalCount; } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 415506911..cc7311812 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1836,6 +1836,7 @@ }, "deleteDraft": "Delete Draft", "confirmDeleteDraft": "Are you sure you want to delete this draft registration?", + "successDeleteDraft": "Draft registration successfully deleted.", "metadata": { "title": "Registration Metadata", "description": "This metadata applies only to the registration you are creating, and will not be applied to your project.",