import { Computed, DataAction, StateRepository } from '@angular-ru/ngxs/decorators';
import { NgxsImmutableDataRepository } from '@angular-ru/ngxs/repositories';
import { Injectable, inject } from '@angular/core';
import { State } from '@ngxs/store';
import { iif, patch } from '@ngxs/store/operators';
import { GlobalConfigState, OrganInfo } from 'ccf-shared';
import { pluckUnique } from 'ccf-shared/rxjs-ext/operators';
import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, tap, withLatestFrom } from 'rxjs/operators';
import { environment } from '../../../../environments/environment';
import { normalizeOrcid } from '../../../shared/utils/orcid';
import { GlobalConfig } from '../../services/config/config';
import { RegistrationState } from '../registration/registration.state';
export interface Person {
firstName: string;
middleName?: string;
lastName: string;
email?: string;
orcidId?: string;
}
export interface PageStateModel {
user: Person;
registrationStarted: boolean;
useCancelRegistrationCallback: boolean;
registrationCallbackSet: boolean;
skipConfirmation: boolean;
hasChanges: boolean;
organOptions?: OrganInfo[];
orcidValid: boolean;
}
@StateRepository()
@State<PageStateModel>({
name: 'page',
defaults: {
user: {
firstName: '',
lastName: '',
},
registrationStarted: false,
useCancelRegistrationCallback: false,
registrationCallbackSet: false,
skipConfirmation: true,
hasChanges: false,
organOptions: [],
orcidValid: false,
},
})
@Injectable()
export class PageState extends NgxsImmutableDataRepository<PageStateModel> {
private readonly globalConfig = inject<GlobalConfigState<GlobalConfig>>(GlobalConfigState);
private readonly reg = inject(RegistrationState);
readonly user$ = this.state$.pipe(map((x) => x?.user));
readonly registrationStarted$ = this.state$.pipe(pluckUnique('registrationStarted'));
readonly useCancelRegistrationCallback$ = this.state$.pipe(map((x) => x?.useCancelRegistrationCallback));
readonly registrationCallbackSet$ = this.state$.pipe(map((x) => x?.registrationCallbackSet));
readonly organOptions$ = this.state$.pipe(map((x) => x?.organOptions));
readonly orcidValid$ = this.state$.pipe(map((x) => x?.orcidValid));
@Computed()
get skipConfirmation$(): Observable<boolean> {
return this.state$.pipe(pluckUnique('skipConfirmation'));
}
@Computed()
get globalSkipConfirmation$(): Observable<boolean> {
return this.globalConfig.getOption('skipUnsavedChangesConfirmation').pipe(
map((value) => value ?? environment.skipUnsavedChangesConfirmation),
distinctUntilChanged(),
);
}
@Computed()
get hasChanges$(): Observable<boolean> {
return this.state$.pipe(pluckUnique('hasChanges'));
}
override ngxsOnInit(): void {
super.ngxsOnInit();
combineLatest([this.reg.state$, this.globalConfig.config$])
.pipe(
tap(([reg, config]) => {
this.setState(
patch({
registrationCallbackSet: reg.useRegistrationCallback ? !!config.register : false,
useCancelRegistrationCallback: !!config.cancelRegistration,
user: iif(!!config.user, config.user ?? { firstName: '', lastName: '', email: '' }),
registrationStarted: (config.user ?? reg.initialRegistration) ? true : undefined,
}),
);
}),
)
.subscribe();
this.initSkipConfirmationListeners();
}
cancelRegistration(): void {
const {
globalConfig: {
snapshot: { cancelRegistration: cancelRegistrationCallback },
},
snapshot: { useCancelRegistrationCallback, skipConfirmation },
} = this;
if (useCancelRegistrationCallback) {
if (skipConfirmation || confirm('Changes you made may not be saved.')) {
cancelRegistrationCallback?.();
}
}
}
@DataAction()
setUseCancelRegistrationCallback(use: boolean): void {
this.ctx.patchState({ useCancelRegistrationCallback: use });
}
@DataAction()
setUserName(name: Pick<Person, 'firstName' | 'middleName' | 'lastName'>): void {
this.ctx.setState(
patch({
user: patch({
firstName: name.firstName,
lastName: name.lastName,
middleName: name.middleName !== '' ? name.middleName : undefined,
}),
}),
);
}
@DataAction()
setOrcidId(id?: string): void {
const orcidId = id && normalizeOrcid(id);
const orcidValid = id === undefined || orcidId !== undefined;
this.ctx.setState(
patch({
user: patch({ orcidId }),
orcidValid,
}),
);
}
@DataAction()
setEmail(email?: string): void {
this.ctx.setState(
patch({
user: patch({
email,
}),
}),
);
}
@DataAction()
registrationStarted(): void {
this.ctx.setState(
patch({
registrationStarted: true,
}),
);
}
@DataAction()
setHasChanges(): void {
const {
snapshot: { registrationStarted, hasChanges },
} = this;
if (registrationStarted && !hasChanges) {
this.ctx.patchState({
hasChanges: true,
});
}
}
@DataAction()
clearHasChanges(): void {
this.ctx.patchState({
hasChanges: false,
});
}
private initSkipConfirmationListeners(): void {
const updateSkipConfirmation = (skipConfirmation: boolean) => this.patchState({ skipConfirmation });
this.globalSkipConfirmation$.pipe(filter((s) => s)).subscribe(updateSkipConfirmation);
this.hasChanges$
.pipe(
withLatestFrom(this.globalSkipConfirmation$),
map(([hasChanges, skipConfirmation]) => skipConfirmation || !hasChanges),
distinctUntilChanged(),
)
.subscribe(updateSkipConfirmation);
const beforeUnloadListener = (event: BeforeUnloadEvent) => {
event.preventDefault();
event.returnValue = 'Changes you made may not be saved.';
return event.returnValue;
};
this.skipConfirmation$.subscribe((skipConfirmation) => {
if (skipConfirmation) {
removeEventListener('beforeunload', beforeUnloadListener);
} else {
addEventListener('beforeunload', beforeUnloadListener);
}
});
}
uriToOrcid(uri?: string): string {
return uri ? uri.split('/').slice(-1)[0] : '';
}
}