Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export const routes: Routes = [
],
},
{
path: 'confirm/:userId/:emailId',
path: 'confirm/:userId/:token',
loadComponent: () => import('./features/home/home.component').then((mod) => mod.HomeComponent),
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
<div class="flex flex-column gap-6">
<h3 class="font-normal description">
{{ 'home.confirmEmail.description' | translate }}
<p class="inline font-bold">{{ config.data.emailAddress }}</p>
{{ 'home.confirmEmail.description2' | translate }}
</h3>
<div class="flex gap-2">
<p-button class="w-full" styleClass="w-full" (click)="dialogRef.close()" [text]="true" severity="secondary">
{{ 'home.confirmEmail.buttons.doNotAdd' | translate }}
</p-button>

<p-button class="w-full" styleClass="w-full" (click)="verifyEmail()">
{{ 'home.confirmEmail.buttons.addEmail' | translate }}
</p-button>
</div>
@if (!verifyingEmail()) {
<h3 class="font-normal description">
{{ 'home.confirmEmail.description' | translate }}
<p class="inline font-bold">{{ config.data.emailAddress }}</p>
{{ 'home.confirmEmail.description2' | translate }}
</h3>
<div class="flex w-full justify-content-center gap-2">
<p-button
severity="info"
class="w-full"
styleClass="w-full"
(click)="closeDialog()"
[label]="'common.buttons.cancel' | translate"
></p-button>
<p-button
class="w-full"
styleClass="w-full"
(click)="verifyEmail()"
[label]="'home.confirmEmail.goToEmails' | translate"
></p-button>
</div>
} @else {
<osf-loading-spinner></osf-loading-spinner>
}
</div>
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
import { Store } from '@ngxs/store';

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

import { Button } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { finalize } from 'rxjs';

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

import { VerifyEmail } from '@osf/features/settings/account-settings/store';
import { AccountSettingsService } from '@osf/features/settings/account-settings/services';
import { LoadingSpinnerComponent } from '@shared/components';

@Component({
selector: 'osf-confirm-email',
imports: [Button, FormsModule, TranslateModule],
imports: [Button, FormsModule, TranslateModule, LoadingSpinnerComponent],
templateUrl: './confirm-email.component.html',
styleUrl: './confirm-email.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConfirmEmailComponent {
readonly #store = inject(Store);
readonly dialogRef = inject(DynamicDialogRef);
readonly config = inject(DynamicDialogConfig);
readonly #router = inject(Router);
readonly #accountSettingsService = inject(AccountSettingsService);
readonly #destroyRef = inject(DestroyRef);

verifyingEmail = signal(false);

closeDialog() {
this.#router.navigate(['/home']);
this.dialogRef.close();
}

verifyEmail() {
this.#store.dispatch(new VerifyEmail(this.config.data.userId, this.config.data.emailId));
this.#router.navigate(['/settings/account']);
this.verifyingEmail.set(true);
this.#accountSettingsService
.confirmEmail(this.config.data.userId, this.config.data.token)
.pipe(
takeUntilDestroyed(this.#destroyRef),
finalize(() => this.verifyingEmail.set(false))
)
.subscribe({
next: () => {
this.#router.navigate(['/settings/account-settings']);
this.dialogRef.close();
},
error: () => this.closeDialog(),
});
}
}
42 changes: 22 additions & 20 deletions src/app/features/home/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class HomeComponent implements OnInit {
readonly #route = inject(ActivatedRoute);
readonly #translateService = inject(TranslateService);
readonly #dialogService = inject(DialogService);
readonly #accountSettingsServer = inject(AccountSettingsService);
readonly #accountSettingsService = inject(AccountSettingsService);

protected readonly isLoading = signal(false);
protected readonly isSubmitting = signal(false);
Expand Down Expand Up @@ -79,36 +79,38 @@ export class HomeComponent implements OnInit {
this.#setupQueryParamsSubscription();
this.#store.dispatch(new GetUserInstitutions());

// Check for userId and emailId route parameters
this.#route.params.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((params) => {
const userId = params['userId'];
const emailId = params['emailId'];
const token = params['token'];

if (userId && emailId) {
this.#accountSettingsServer
.getEmail(emailId, userId)
if (userId && token) {
this.#accountSettingsService
.getEmail(token, userId)
.pipe(take(1))
.subscribe((email) => {
this.emailAddress = email.emailAddress;
this.addAlternateEmail();
this.addAlternateEmail(token);
});
}
});
}

addAlternateEmail() {
this.dialogRef = this.#dialogService.open(ConfirmEmailComponent, {
width: '448px',
focusOnShow: false,
header: this.#translateService.instant('home.confirmEmail.title'),
closeOnEscape: true,
modal: true,
closable: true,
data: {
emailAddress: this.emailAddress,
userId: this.#route.snapshot.params['userId'],
emailId: this.#route.snapshot.params['emailId'],
},
addAlternateEmail(token: string) {
this.#translateService.get('home.confirmEmail.title').subscribe((title) => {
this.dialogRef = this.#dialogService.open(ConfirmEmailComponent, {
width: '448px',
focusOnShow: false,
header: title,
closeOnEscape: true,
modal: true,
closable: true,
data: {
emailAddress: this.emailAddress,
userId: this.#route.snapshot.params['userId'],
emailId: this.#route.snapshot.params['emailId'],
token: token,
},
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,21 @@ <h2 class="account-setting-header">{{ 'settings.accountSettings.connectedEmails.
</i>
}
@if (deletingEmailIds().has(email.id)) {
<p-progress-spinner
strokeWidth="5"
fill="transparent"
animationDuration=".5s"
[style]="{ width: '0.7rem', height: '0.7rem', marginLeft: '0.7rem' }"
/>
<p-progress-spinner strokeWidth="5" fill="transparent" animationDuration=".5s" class="icon--sm" />
}
</div>

<p-button severity="secondary" (click)="makePrimary(email.id)">
{{ 'settings.accountSettings.connectedEmails.buttons.makePrimary' | translate }}
</p-button>
@if (makingPrimaryIds().has(email.id)) {
<p-progress-spinner strokeWidth="5" fill="transparent" animationDuration=".5s" class="icon--md" />
} @else {
<p-button
[label]="'settings.accountSettings.connectedEmails.buttons.makePrimary' | translate"
severity="secondary"
(click)="makePrimary(email.id)"
[disabled]="makingPrimaryIds().size > 0"
>
</p-button>
}
</div>
}
}
Expand Down Expand Up @@ -82,22 +85,25 @@ <h2 class="account-setting-header">{{ 'settings.accountSettings.connectedEmails.
></i>
}
@if (deletingEmailIds().has(email.id)) {
<p-progress-spinner
strokeWidth="5"
fill="transparent"
animationDuration=".5s"
[style]="{ width: '0.7rem', height: '0.7rem', marginLeft: '0.7rem' }"
/>
<p-progress-spinner strokeWidth="5" fill="transparent" animationDuration=".5s" class="icon--sm" />
}
</div>

<p-button severity="secondary" (click)="resendConfirmation(email.id)">
@if (isMobile()) {
{{ 'settings.accountSettings.connectedEmails.buttons.resend' | translate }}
} @else {
{{ 'settings.accountSettings.connectedEmails.buttons.resendConfirmation' | translate }}
}
</p-button>
@if (resendingEmailIds().has(email.id)) {
<p-progress-spinner strokeWidth="5" fill="transparent" animationDuration=".5s" class="icon--md" />
} @else {
<p-button
[label]="
(isMobile()
? 'settings.accountSettings.connectedEmails.buttons.resend'
: 'settings.accountSettings.connectedEmails.buttons.resendConfirmation'
) | translate
"
severity="secondary"
(click)="resendConfirmation(email.id)"
>
</p-button>
}
</div>
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
:host {
@extend .account-setting;

.icon {
&--sm {
width: 0.7rem;
height: 0.7rem;
margin-left: 0.7rem;
}

&--md {
width: 1rem;
height: 1rem;
}
}

.account-setting {
&-emails {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ProgressSpinner } from 'primeng/progressspinner';
import { Skeleton } from 'primeng/skeleton';

import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { finalize } from 'rxjs';

import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, signal } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';

import { UserSelectors } from '@osf/core/store/user';
import { IS_XSMALL } from '@osf/shared/utils';

import { AccountSettingsService } from '../../services';
import { AccountSettingsSelectors, DeleteEmail } from '../../store';
import { AccountSettingsSelectors, DeleteEmail, MakePrimary } from '../../store';
import { AddEmailComponent } from '../add-email/add-email.component';

@Component({
Expand All @@ -30,10 +32,13 @@ export class ConnectedEmailsComponent {
readonly #dialogService = inject(DialogService);
readonly #translateService = inject(TranslateService);
readonly isMobile = toSignal(inject(IS_XSMALL));
readonly #destroyRef = inject(DestroyRef);

protected readonly currentUser = this.#store.selectSignal(UserSelectors.getCurrentUser);
protected readonly emails = this.#store.selectSignal(AccountSettingsSelectors.getEmails);
protected readonly deletingEmailIds = signal<Set<string>>(new Set());
protected readonly resendingEmailIds = signal<Set<string>>(new Set());
protected readonly makingPrimaryIds = signal<Set<string>>(new Set());
protected readonly unconfirmedEmails = computed(() => {
return this.emails().filter((email) => !email.confirmed && !email.primary);
});
Expand All @@ -60,13 +65,34 @@ export class ConnectedEmailsComponent {

resendConfirmation(emailId: string) {
if (this.currentUser()?.id) {
this.#accountSettingsService.resendConfirmation(emailId, this.currentUser()!.id).subscribe();
this.resendingEmailIds.set(new Set([...this.resendingEmailIds(), emailId]));

this.#accountSettingsService
.resendConfirmation(emailId, this.currentUser()!.id)
.pipe(
finalize(() => {
const currentIds = this.resendingEmailIds();
const updatedIds = new Set([...currentIds].filter((id) => id !== emailId));
this.resendingEmailIds.set(updatedIds);
}),
takeUntilDestroyed(this.#destroyRef)
)
.subscribe();
}
}

makePrimary(emailId: string) {
if (this.currentUser()?.id) {
this.#accountSettingsService.makePrimary(emailId).subscribe();
this.makingPrimaryIds.set(new Set([...this.makingPrimaryIds(), emailId]));

this.#store
.dispatch(new MakePrimary(emailId))
.pipe(takeUntilDestroyed(this.#destroyRef))
.subscribe(() => {
const currentIds = this.makingPrimaryIds();
const updatedIds = new Set([...currentIds].filter((id) => id !== emailId));
this.makingPrimaryIds.set(updatedIds);
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ export class AccountSettingsService {
);
}

confirmEmail(userId: string, token: string): Observable<unknown> {
const body = {
data: {
attributes: {
uid: userId,
token: token,
destination: 'doesnotmatter',
},
},
};
return this.#jsonApiService.post(`${environment.apiUrl}/users/${userId}/confirm/`, body);
}

verifyEmail(userId: string, emailId: string): Observable<AccountEmail> {
const body = {
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class VerifyEmail {
export class MakePrimary {
static readonly type = '[AccountSettings] Make Primary';

constructor(public userId: string) {}
constructor(public emailId: string) {}
}

export class GetRegions {
Expand Down
Loading