diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 315a0d94dd..594b9e2ddd 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -31,6 +31,7 @@ import { FirstLoginPageComponent } from '@features/first-login-page/first-login- import { ForgotYourPasswordComponent } from '@features/forgot-your-username-or-password/forgot-your-password/forgot-your-password.component'; import { ForgotYourUsernameOrPasswordComponent } from '@features/forgot-your-username-or-password/forgot-your-username-or-password.component'; import { ForgotYourUsernameComponent } from '@features/forgot-your-username-or-password/forgot-your-username/forgot-your-username.component'; +import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component'; import { LoginComponent } from '@features/login/login.component'; import { LogoutComponent } from '@features/logout/logout.component'; import { MigratedUserTermsConditionsComponent } from '@features/migrated-user-terms-conditions/migrated-user-terms-conditions.component'; @@ -111,6 +112,11 @@ const routes: Routes = [ component: SatisfactionSurveyComponent, data: { title: 'Satisfaction Survey' }, }, + { + path: 'username-found', + component: UsernameFoundComponent, + data: { title: 'Username Found' }, + }, ], }, { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 3e5cf82cda..86c33d5b6b 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -97,6 +97,7 @@ import { SharedModule } from '@shared/shared.module'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { SentryErrorHandler } from './SentryErrorHandler.component'; +import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component'; @NgModule({ declarations: [ @@ -139,6 +140,7 @@ import { SentryErrorHandler } from './SentryErrorHandler.component'; ParentWorkplaceAccounts, DeleteWorkplaceComponent, ForgotYourUsernameOrPasswordComponent, + UsernameFoundComponent, ForgotYourUsernameComponent, FindAccountComponent, FindUsernameComponent, diff --git a/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.html b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.html new file mode 100644 index 0000000000..258f2ae594 --- /dev/null +++ b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.html @@ -0,0 +1,7 @@ +
+
+

Page no longer available

+

The page you are trying to view does not exist anymore.

+

Return to the homepage

+
+
diff --git a/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.spec.ts b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.spec.ts new file mode 100644 index 0000000000..a43de9798d --- /dev/null +++ b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.spec.ts @@ -0,0 +1,41 @@ +import { render } from '@testing-library/angular'; +import { RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { PageNoLongerAvailableComponent } from './page-no-longer-available.component'; + +describe('PageNoLongerAvailableComponent', () => { + const setup = async () => { + const setupTools = await render(PageNoLongerAvailableComponent, { + imports: [RouterModule, RouterTestingModule, HttpClientTestingModule], + declarations: [], + providers: [], + componentProperties: {}, + }); + + const component = setupTools.fixture.componentInstance; + + return { ...setupTools, component }; + }; + + it('should create', async () => { + const { component } = await setup(); + + expect(component).toBeTruthy(); + }); + + it('should show the page heading', async () => { + const { getByRole } = await setup(); + + expect(getByRole('heading', { name: 'Page no longer available' })).toBeTruthy(); + }); + + it('should show a link to the homepage', async () => { + const { getByText } = await setup(); + + const linkText = getByText('Return to the homepage'); + + expect(linkText).toBeTruthy(); + expect(linkText.getAttribute('href')).toEqual('/login'); + }); +}); diff --git a/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.ts b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.ts new file mode 100644 index 0000000000..ad7a1e020c --- /dev/null +++ b/frontend/src/app/core/components/error/page-no-longer-available/page-no-longer-available.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-no-longer-available', + templateUrl: './page-no-longer-available.component.html', +}) +export class PageNoLongerAvailableComponent { + constructor() {} +} diff --git a/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.html b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.html new file mode 100644 index 0000000000..59ecc0d729 --- /dev/null +++ b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.html @@ -0,0 +1,28 @@ + +
+

+ We’ve found your username +

+
+ Your username is +
{{ username }}
+
+
+ +
+ +
+
+ + + + diff --git a/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.scss b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.scss new file mode 100644 index 0000000000..3296d00f22 --- /dev/null +++ b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.scss @@ -0,0 +1,8 @@ +.panel { + width: 64%; +} + +.green-tick-icon { + height: 28px; + vertical-align: baseline; +} diff --git a/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.spec.ts b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.spec.ts new file mode 100644 index 0000000000..ebb4af802e --- /dev/null +++ b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.spec.ts @@ -0,0 +1,94 @@ +import { fireEvent, render, within } from '@testing-library/angular'; +import { UsernameFoundComponent } from './username-found.component'; +import { getTestBed } from '@angular/core/testing'; +import { Router, RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FindUsernameService } from '@core/services/find-username.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { PageNoLongerAvailableComponent } from '@core/components/error/page-no-longer-available/page-no-longer-available.component'; + +describe('UsernameFoundComponent', () => { + const setup = async (overrides: any = {}) => { + const setupTools = await render(UsernameFoundComponent, { + imports: [RouterModule, RouterTestingModule, HttpClientTestingModule], + declarations: [PageNoLongerAvailableComponent], + providers: [ + { + provide: FindUsernameService, + useValue: { usernameFound: overrides.username }, + }, + ], + componentProperties: {}, + }); + + const component = setupTools.fixture.componentInstance; + + const injector = getTestBed(); + const router = injector.inject(Router) as Router; + + const routerSpy = spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + + return { ...setupTools, component, routerSpy }; + }; + + it('should create UsernameFoundComponent', async () => { + const overrides = { + username: 'Bighitterhank1000', + }; + + const { component } = await setup(overrides); + + expect(component).toBeTruthy(); + }); + + it('should show the panel', async () => { + const overrides = { + username: 'Bighitterhank1000', + }; + + const { getByTestId } = await setup(overrides); + + const panel = getByTestId('panel'); + + expect(panel).toBeTruthy(); + expect(within(panel).getByText('We’ve found your username')); + }); + + it('should show the username', async () => { + const overrides = { + username: 'Bighitterhank1000', + }; + + const { getByTestId } = await setup(overrides); + + const panel = getByTestId('panel'); + expect(within(panel).getByText('Your username is')); + expect(within(panel).getByText('Bighitterhank1000')); + }); + + it('should go back to the sign in page when the button is clicked', async () => { + const overrides = { + username: 'Bighitterhank1000', + }; + + const { fixture, getByText, routerSpy } = await setup(overrides); + + const buttonText = getByText('Back to sign in'); + expect(buttonText).toBeTruthy(); + + fireEvent.click(buttonText); + fixture.detectChanges(); + + expect(routerSpy).toHaveBeenCalledWith(['/login']); + }); + + it('should show page no longer available if no username was found', async () => { + const overrides = { + username: null, + }; + + const { getByTestId } = await setup(overrides); + + expect(getByTestId('page-no-longer-available')).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.ts b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.ts new file mode 100644 index 0000000000..7d5ea43b71 --- /dev/null +++ b/frontend/src/app/features/forgot-your-username-or-password/username-found/username-found.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { FindUsernameService } from '@core/services/find-username.service'; + +@Component({ + selector: 'app-username-found', + templateUrl: './username-found.component.html', + styleUrls: ['./username-found.component.scss'], +}) +export class UsernameFoundComponent implements OnInit { + public username: string; + public isUsernameFound: boolean; + + constructor(private router: Router, private findUsernameService: FindUsernameService) {} + + ngOnInit(): void { + this.getUsernameFound(); + this.isUsernameFound = this.username !== null; + } + + public getUsernameFound(): void { + this.username = this.findUsernameService.usernameFound; + } + + public backToSignInPage(event: Event): void { + event.preventDefault(); + this.router.navigate(['/login']); + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index cf32a1c4ab..54c3df794b 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -5,20 +5,15 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { CannotCreateAccountComponent } from '@core/components/error/cannot-create-account/cannot-create-account.component'; import { PageNotFoundComponent } from '@core/components/error/page-not-found/page-not-found.component'; +import { PageNoLongerAvailableComponent } from '@core/components/error/page-no-longer-available/page-no-longer-available.component'; import { ArticleListResolver } from '@core/resolvers/article-list.resolver'; import { PageResolver } from '@core/resolvers/page.resolver'; import { DialogService } from '@core/services/dialog.service'; import { ArticleListComponent } from '@features/articles/article-list/article-list.component'; import { NewArticleListComponent } from '@features/articles/new-article-list/new-article-list.component'; -import { - NewTrainingLinkPanelComponent, -} from '@features/new-dashboard/training-tab/training-link-panel/training-link-panel.component'; -import { - MissingMandatoryTrainingComponent, -} from '@features/training-and-qualifications/new-training-qualifications-record/missing-mandatory-training/missing-mandatory-training.component'; -import { - DeleteWorkplaceDialogComponent, -} from '@features/workplace/delete-workplace-dialog/delete-workplace-dialog.component'; +import { NewTrainingLinkPanelComponent } from '@features/new-dashboard/training-tab/training-link-panel/training-link-panel.component'; +import { MissingMandatoryTrainingComponent } from '@features/training-and-qualifications/new-training-qualifications-record/missing-mandatory-training/missing-mandatory-training.component'; +import { DeleteWorkplaceDialogComponent } from '@features/workplace/delete-workplace-dialog/delete-workplace-dialog.component'; import { AlertComponent } from '@shared/components/alert/alert.component'; import { CheckCQCDetailsComponent } from '@shared/components/check-cqc-details/check-cqc-details.component'; import { NewDashboardHeaderComponent } from '@shared/components/new-dashboard-header/dashboard-header.component'; @@ -27,12 +22,8 @@ import { WorkplaceTabComponent } from '@shared/components/workplace-tab/workplac import { BulkUploadFileTypePipePipe } from '@shared/pipes/bulk-upload-file-type.pipe'; import { SanitizeVideoUrlPipe } from '@shared/pipes/sanitize-video-url.pipe'; -import { - GroupedRadioButtonAccordionComponent, -} from './components/accordions/radio-button-accordion/grouped-radio-button-accordion/grouped-radio-button-accordion.component'; -import { - RadioButtonAccordionComponent, -} from './components/accordions/radio-button-accordion/radio-button-accordion.component'; +import { GroupedRadioButtonAccordionComponent } from './components/accordions/radio-button-accordion/grouped-radio-button-accordion/grouped-radio-button-accordion.component'; +import { RadioButtonAccordionComponent } from './components/accordions/radio-button-accordion/radio-button-accordion.component'; import { AddNoteComponent } from './components/add-note/add-note.component'; import { AutoSuggestComponent } from './components/auto-suggest/auto-suggest.component'; import { BackLinkComponent } from './components/back-link/back-link.component'; @@ -46,66 +37,44 @@ import { CharacterCountComponent } from './components/character-count/character- import { AboutTheDataLinkComponent } from './components/data-area-tab/about-the-data-link/about-the-data-link.component'; import { DatePickerComponent } from './components/date-picker/date-picker.component'; import { DetailsComponent } from './components/details/details.component'; -import { - ValidationErrorMessageComponent, -} from './components/drag-and-drop/validation-error-message/validation-error-message.component'; +import { ValidationErrorMessageComponent } from './components/drag-and-drop/validation-error-message/validation-error-message.component'; import { EligibilityIconComponent } from './components/eligibility-icon/eligibility-icon.component'; import { ErrorSummaryComponent } from './components/error-summary/error-summary.component'; import { InsetTextComponent } from './components/inset-text/inset-text.component'; -import { - LinkToParentCancelDialogComponent, -} from './components/link-to-parent-cancel/link-to-parent-cancel-dialog.component'; -import { - LinkToParentRemoveDialogComponent, -} from './components/link-to-parent-remove/link-to-parent-remove-dialog.component'; +import { LinkToParentCancelDialogComponent } from './components/link-to-parent-cancel/link-to-parent-cancel-dialog.component'; +import { LinkToParentRemoveDialogComponent } from './components/link-to-parent-remove/link-to-parent-remove-dialog.component'; import { LinkToParentDialogComponent } from './components/link-to-parent/link-to-parent-dialog.component'; import { LinkWithArrowComponent } from './components/link-with-arrow/link-with-arrow.component'; import { MessagesComponent } from './components/messages/messages.component'; import { MoveWorkplaceDialogComponent } from './components/move-workplace/move-workplace-dialog.component'; -import { - NavigateToWorkplaceDropdownComponent, -} from './components/navigate-to-workplace-dropdown/navigate-to-workplace-dropdown.component'; +import { NavigateToWorkplaceDropdownComponent } from './components/navigate-to-workplace-dropdown/navigate-to-workplace-dropdown.component'; import { NewBackLinkComponent } from './components/new-back-link/new-back-link.component'; import { NewTabsComponent } from './components/new-tabs/new-tabs.component'; import { WDFTabComponent } from './components/new-wdf-tabs/new-wdf-tab.component'; import { WDFWorkplaceSummaryComponent } from './components/new-wdf-workplace-summary/wdf-workplace-summary.component'; import { NewWorkplaceSummaryComponent } from './components/new-workplace-summary/workplace-summary.component'; import { OtherLinksComponent } from './components/other-links/other-links.component'; -import { - OwnershipChangeMessageDialogComponent, -} from './components/ownership-change-message/ownership-change-message-dialog.component'; +import { OwnershipChangeMessageDialogComponent } from './components/ownership-change-message/ownership-change-message-dialog.component'; import { PageComponent } from './components/page/page.component'; import { PaginationComponent } from './components/pagination/pagination.component'; import { PanelComponent } from './components/panel/panel.component'; import { PhaseBannerComponent } from './components/phase-banner/phase-banner.component'; import { ProgressBarComponent } from './components/progress-bar/progress-bar.component'; import { ProgressComponent } from './components/progress/progress.component'; -import { - RegistrationSubmitButtonsComponent, -} from './components/registration-submit-buttons/registration-submit-buttons.component'; +import { RegistrationSubmitButtonsComponent } from './components/registration-submit-buttons/registration-submit-buttons.component'; import { RejectRequestDialogComponent } from './components/reject-request-dialog/reject-request-dialog.component'; -import { - RemoveParentConfirmationComponent, -} from './components/remove-parent-confirmation/remove-parent-confirmation.component'; +import { RemoveParentConfirmationComponent } from './components/remove-parent-confirmation/remove-parent-confirmation.component'; import { ReviewCheckboxComponent } from './components/review-checkbox/review-checkbox.component'; import { SearchInputComponent } from './components/search-input/search-input.component'; -import { - SelectUploadCertificateComponent, -} from './components/select-upload-certificate/select-upload-certificate.component'; +import { SelectUploadCertificateComponent } from './components/select-upload-certificate/select-upload-certificate.component'; import { SelectUploadFileComponent } from './components/select-upload-file/select-upload-file.component'; -import { - SelectWorkplaceDropdownFormComponent, -} from './components/select-workplace-dropdown-form/select-workplace-dropdown-form.component'; -import { - SelectWorkplaceRadioButtonFormComponent, -} from './components/select-workplace-radio-button-form/select-workplace-radio-button-form.component'; +import { SelectWorkplaceDropdownFormComponent } from './components/select-workplace-dropdown-form/select-workplace-dropdown-form.component'; +import { SelectWorkplaceRadioButtonFormComponent } from './components/select-workplace-radio-button-form/select-workplace-radio-button-form.component'; import { SetDataPermissionDialogComponent } from './components/set-data-permission/set-data-permission-dialog.component'; import { BasicRecordComponent } from './components/staff-record-summary/basic-record/basic-record.component'; import { EmploymentComponent } from './components/staff-record-summary/employment/employment.component'; import { PersonalDetailsComponent } from './components/staff-record-summary/personal-details/personal-details.component'; -import { - QualificationsAndTrainingComponent, -} from './components/staff-record-summary/qualifications-and-training/qualifications-and-training.component'; +import { QualificationsAndTrainingComponent } from './components/staff-record-summary/qualifications-and-training/qualifications-and-training.component'; import { StaffRecordSummaryComponent } from './components/staff-record-summary/staff-record-summary.component'; import { StaffRecordsTabComponent } from './components/staff-records-tab/staff-records-tab.component'; import { StaffSummaryComponent } from './components/staff-summary/staff-summary.component'; @@ -120,35 +89,21 @@ import { TabComponent } from './components/tabs/tab.component'; import { TabsComponent } from './components/tabs/tabs.component'; import { TotalStaffPanelComponent } from './components/total-staff-panel/total-staff-panel.component'; import { TotalStaffComponent } from './components/total-staff/total-staff.component'; -import { - TrainingAndQualificationsCategoriesComponent, -} from './components/training-and-qualifications-categories/training-and-qualifications-categories.component'; -import { - ViewTrainingComponent, -} from './components/training-and-qualifications-categories/view-trainings/view-trainings.component'; -import { - TrainingAndQualificationsSummaryComponent, -} from './components/training-and-qualifications-summary/training-and-qualifications-summary.component'; -import { - TrainingAndQualificationsTabComponent, -} from './components/training-and-qualifications-tab/training-and-qualifications-tab.component'; +import { TrainingAndQualificationsCategoriesComponent } from './components/training-and-qualifications-categories/training-and-qualifications-categories.component'; +import { ViewTrainingComponent } from './components/training-and-qualifications-categories/view-trainings/view-trainings.component'; +import { TrainingAndQualificationsSummaryComponent } from './components/training-and-qualifications-summary/training-and-qualifications-summary.component'; +import { TrainingAndQualificationsTabComponent } from './components/training-and-qualifications-tab/training-and-qualifications-tab.component'; import { TrainingInfoPanelComponent } from './components/training-info-panel/training-info-panel.component'; import { TrainingLinkPanelComponent } from './components/training-link-panel/training-link-panel.component'; -import { - TrainingSelectViewPanelComponent, -} from './components/training-select-view-panel/training-select-view-panel.component'; +import { TrainingSelectViewPanelComponent } from './components/training-select-view-panel/training-select-view-panel.component'; import { UserAccountsSummaryComponent } from './components/user-accounts-summary/user-accounts-summary.component'; import { UserFormComponent } from './components/user-form/user-form.component'; import { UserTableComponent } from './components/users-table/user.table.component'; import { WdfConfirmationPanelComponent } from './components/wdf-confirmation-panel/wdf-confirmation-panel.component'; import { WdfFieldConfirmationComponent } from './components/wdf-field-confirmation/wdf-field-confirmation.component'; -import { - WdfStaffMismatchMessageComponent, -} from './components/wdf-staff-mismatch-message/wdf-staff-mismatch-message.component'; +import { WdfStaffMismatchMessageComponent } from './components/wdf-staff-mismatch-message/wdf-staff-mismatch-message.component'; import { WdfTabComponent } from './components/wdf-tab/wdf-tab.component'; -import { - WorkplaceContinueCancelButtonComponent, -} from './components/workplace-continue-cancel-button.component/workplace-continue-cancel-button.component'; +import { WorkplaceContinueCancelButtonComponent } from './components/workplace-continue-cancel-button.component/workplace-continue-cancel-button.component'; import { WorkplaceSubmitButtonComponent } from './components/workplace-submit-button/workplace-submit-button.component'; import { WorkplaceSummaryComponent } from './components/workplace-summary/workplace-summary.component'; import { FileValueAccessorDirective } from './form-controls/file-control-value-accessor'; @@ -248,6 +203,7 @@ import { WorkplacePermissionsBearerPipe } from './pipes/workplace-permissions-be WdfStaffMismatchMessageComponent, CheckCQCDetailsComponent, PageNotFoundComponent, + PageNoLongerAvailableComponent, ArticleListComponent, PageComponent, FirstErrorPipe, @@ -368,6 +324,7 @@ import { WorkplacePermissionsBearerPipe } from './pipes/workplace-permissions-be MoveWorkplaceDialogComponent, CheckCQCDetailsComponent, PageNotFoundComponent, + PageNoLongerAvailableComponent, ArticleListComponent, PageComponent, FirstErrorPipe,