From 77c6ad411f269fa3be5fda11439b6f1b5dd6d7c2 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Mon, 25 Jan 2021 11:50:01 -0800 Subject: [PATCH 1/3] Implemented unlinking Google account from WISE for teachers and students. #2870 --- .../teacher/TeacherAPIController.java | 8 - .../user/GoogleUserAPIController.java | 84 ++++ .../controllers/user/UserAPIController.java | 31 +- .../exception/InvalidPasswordExcpetion.java | 6 + src/main/resources/i18n/i18n.properties | 6 + .../copy-project-dialog.component.ts | 1 - .../edit-password.component.html | 10 +- .../edit-password.component.spec.ts | 146 ++++--- .../edit-password/edit-password.component.ts | 26 +- .../src/app/modules/shared/shared.module.ts | 10 +- ...link-google-account-confirm.component.html | 17 + ...link-google-account-confirm.component.scss | 3 + ...k-google-account-confirm.component.spec.ts | 37 ++ ...unlink-google-account-confirm.component.ts | 16 + ...ink-google-account-password.component.html | 46 +++ ...ink-google-account-password.component.scss | 3 + ...-google-account-password.component.spec.ts | 51 +++ ...nlink-google-account-password.component.ts | 38 ++ ...link-google-account-success.component.html | 12 + ...link-google-account-success.component.scss | 3 + ...unlink-google-account-success.component.ts | 18 + .../validators/password-match.validator.ts | 13 + .../src/app/services/user.service.spec.ts | 24 +- .../site/src/app/services/user.service.ts | 18 +- .../edit-profile/edit-profile.component.html | 14 +- .../edit-profile.component.spec.ts | 2 + .../edit-profile/edit-profile.component.ts | 30 +- .../edit-profile/edit-profile.component.html | 14 +- .../edit-profile.component.spec.ts | 2 + .../edit-profile/edit-profile.component.ts | 26 +- src/main/webapp/site/src/messages.xlf | 364 ++++++++++++------ .../user/GoogleUserAPIControllerTest.java | 108 ++++++ .../user/UserAPIControllerTest.java | 62 --- 33 files changed, 938 insertions(+), 311 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java create mode 100644 src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss create mode 100644 src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts create mode 100644 src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 79a5d8a107..72da0d69c3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -14,7 +14,6 @@ import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.MessageSource; import org.springframework.security.access.annotation.Secured; import org.springframework.security.acls.model.Permission; import org.springframework.security.core.Authentication; @@ -38,7 +37,6 @@ import org.wise.portal.presentation.web.response.SimpleResponse; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; -import org.wise.portal.service.mail.IMailFacade; /** * Teacher REST API @@ -55,12 +53,6 @@ public class TeacherAPIController extends UserAPIController { @Autowired private UserDetailsService userDetailsService; - @Autowired - protected IMailFacade mailService; - - @Autowired - protected MessageSource messageSource; - @Value("${google.clientId:}") private String googleClientId; diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java new file mode 100644 index 0000000000..d2cba48c11 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIController.java @@ -0,0 +1,84 @@ +package org.wise.portal.presentation.web.controllers.user; + +import java.util.HashMap; +import java.util.Locale; + +import javax.mail.MessagingException; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.PersistentUserDetails; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion; + +@RestController +@RequestMapping("/api/google-user") +public class GoogleUserAPIController extends UserAPIController { + + @GetMapping("/check-user-exists") + boolean isGoogleIdExist(@RequestParam String googleUserId) { + return userService.retrieveUserByGoogleUserId(googleUserId) != null; + } + + @GetMapping("/check-user-matches") + boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) { + User user = userService.retrieveUserByGoogleUserId(googleUserId); + return user != null && user.getId().toString().equals(userId); + } + + @GetMapping("/get-user") + HashMap getUserByGoogleId(@RequestParam String googleUserId) { + User user = userService.retrieveUserByGoogleUserId(googleUserId); + HashMap response = new HashMap(); + if (user == null) { + response.put("status", "error"); + } else { + response.put("status", "success"); + response.put("userId", user.getId()); + response.put("username", user.getUserDetails().getUsername()); + response.put("firstName", user.getUserDetails().getFirstname()); + response.put("lastName", user.getUserDetails().getLastname()); + } + return response; + } + + @Secured("ROLE_USER") + @PostMapping("/unlink-account") + HashMap unlinkGoogleAccount(Authentication auth, @RequestParam String newPassword) + throws InvalidPasswordExcpetion { + if (newPassword.isEmpty()) { + throw new InvalidPasswordExcpetion(); + } + String username = auth.getName(); + User user = userService.retrieveUserByUsername(username); + ((PersistentUserDetails) user.getUserDetails()).setGoogleUserId(null); + userService.updateUserPassword(user, newPassword); + boolean isSendEmail = Boolean.parseBoolean(appProperties.getProperty("send_email_enabled", "false")); + if (isSendEmail && user.isTeacher()) { + this.sendUnlinkGoogleEmail((TeacherUserDetails) user.getUserDetails()); + } + return this.getUserInfo(auth, username); + } + + private void sendUnlinkGoogleEmail(TeacherUserDetails userDetails) { + String[] recipients = { userDetails.getEmailAddress() }; + String subject = messageSource.getMessage("unlink_google_account_success_email_subject", null, + "Successfully Unlinked Google Account", new Locale(userDetails.getLanguage())); + String username = userDetails.getUsername(); + String message = messageSource.getMessage("unlink_google_account_success_email_body", + new Object[]{username}, + "You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created. Your username is: " + username, + new Locale(userDetails.getLanguage())); + try { + mailService.postMail(recipients, subject, message, appProperties.getProperty("portalemailaddress")); + } catch (MessagingException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java index b6ef0251f3..21673c81d5 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.MessageSource; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; @@ -63,6 +64,9 @@ public class UserAPIController { @Autowired protected IMailFacade mailService; + @Autowired + protected MessageSource messageSource; + @Value("${google.clientId:}") protected String googleClientId = ""; @@ -189,33 +193,6 @@ List> getSupportedLanguages() { return langs; } - @GetMapping("/check-google-user-exists") - boolean isGoogleIdExist(@RequestParam String googleUserId) { - return userService.retrieveUserByGoogleUserId(googleUserId) != null; - } - - @GetMapping("/check-google-user-matches") - boolean isGoogleIdMatches(@RequestParam String googleUserId, @RequestParam String userId) { - User user = userService.retrieveUserByGoogleUserId(googleUserId); - return user != null && user.getId().toString().equals(userId); - } - - @GetMapping("/google-user") - HashMap getUserByGoogleId(@RequestParam String googleUserId) { - User user = userService.retrieveUserByGoogleUserId(googleUserId); - HashMap response = new HashMap(); - if (user == null) { - response.put("status", "error"); - } else { - response.put("status", "success"); - response.put("userId", user.getId()); - response.put("username", user.getUserDetails().getUsername()); - response.put("firstName", user.getUserDetails().getFirstname()); - response.put("lastName", user.getUserDetails().getLastname()); - } - return response; - } - private String getLanguageName(String localeString) { if (localeString.toLowerCase().equals("zh_tw")) { return "Chinese (Traditional)"; diff --git a/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java new file mode 100644 index 0000000000..6d6dac6cbc --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/exception/InvalidPasswordExcpetion.java @@ -0,0 +1,6 @@ +package org.wise.portal.presentation.web.exception; + +public class InvalidPasswordExcpetion extends Exception { + + private static final long serialVersionUID = 1L; +} diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index 7c050e8dcb..1ffd28e71c 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -308,6 +308,12 @@ teacher_cap.description=Text for the word "Teacher" team_cap=Team team_cap.description=Text for the word "Team" +unlink_google_account_success_email_subject=Successfully Unlinked Google Account +unlink_google_account_success_email_subject.description=Subject text in email to notify user about successfuly unlinking google account + +unlink_google_account_success_email_body=You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.\n\nYour username is: {0}\n\nThank you for using WISE,\nWISE Team +unlink_google_account_success_email_body.description=Body text in email to notify user about successfully unlinking google account + # Root (/) Pages # accountmenu.forgot=Forgot Username or Password? diff --git a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts index 8a2db347d5..5347591222 100644 --- a/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts +++ b/src/main/webapp/site/src/app/modules/library/copy-project-dialog/copy-project-dialog.component.ts @@ -5,7 +5,6 @@ import { finalize } from 'rxjs/operators'; import { LibraryProject } from '../libraryProject'; import { LibraryService } from '../../../services/library.service'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { Subscription } from 'rxjs'; @Component({ selector: 'app-copy-project-dialog', diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html index 1a47a25674..71af1f2b7d 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html @@ -57,5 +57,13 @@ -

This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please contact us.

+

This account was created using Google and doesn't use a WISE password.

+

+ +

diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts index 6d33a734a5..fd68f182c1 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.spec.ts @@ -1,14 +1,17 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { EditPasswordComponent } from './edit-password.component'; import { UserService } from '../../../services/user.service'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, of } from 'rxjs'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ReactiveFormsModule } from '@angular/forms'; -import { NO_ERRORS_SCHEMA, Provider } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { By } from '@angular/platform-browser'; import { User } from '../../../domain/user'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; +const CORRECT_OLD_PASS = 'a'; +const INCORRECT_OLD_PASS = 'b'; export class MockUserService { getUser(): BehaviorSubject { @@ -22,13 +25,13 @@ export class MockUserService { } changePassword(username, oldPassword, newPassword) { - if (oldPassword === 'a') { - return Observable.create((observer) => { + if (oldPassword === CORRECT_OLD_PASS) { + return new Observable((observer) => { observer.next({ status: 'success', messageCode: 'passwordChanged' }); observer.complete(); }); } else { - return Observable.create((observer) => { + return new Observable((observer) => { observer.next({ status: 'error', messageCode: 'incorrectPassword' }); observer.complete(); }); @@ -36,22 +39,26 @@ export class MockUserService { } } -describe('EditPasswordComponent', () => { - let component: EditPasswordComponent; - let fixture: ComponentFixture; +let component: EditPasswordComponent; +let fixture: ComponentFixture; + +const getSubmitButton = () => { + return fixture.debugElement.nativeElement.querySelector('button[type="submit"]'); +}; - const getSubmitButton = () => { - return fixture.debugElement.nativeElement.querySelector('button[type="submit"]'); - }; +const getUnlinkGoogleAccountButton = () => { + return fixture.debugElement.nativeElement.querySelector('button[id="unlinkGoogleAccount"]'); +}; - const getForm = () => { - return fixture.debugElement.query(By.css('form')); - }; +const getForm = () => { + return fixture.debugElement.query(By.css('form')); +}; +describe('EditPasswordComponent', () => { configureTestSuite(() => { TestBed.configureTestingModule({ declarations: [EditPasswordComponent], - imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule], + imports: [BrowserAnimationsModule, ReactiveFormsModule, MatSnackBarModule, MatDialogModule], providers: [{ provide: UserService, useValue: new MockUserService() }], schemas: [NO_ERRORS_SCHEMA] }); @@ -63,61 +70,60 @@ describe('EditPasswordComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + initialState_disableSubmitButton(); + validForm_enableSubmitButton(); + passwordMismatch_disableSubmitButtonAndInvalidateForm(); + oldPasswordIncorrect_disableSubmitButtonAndShowError(); + formSubmit_disableSubmitButton(); + passwordChanged_handleResponse(); + incorrectPassword_showError(); + notGoogleUser_showUnlinkOption(); + unlinkGoogleButtonClick_showDialog(); +}); +function initialState_disableSubmitButton() { it('should disable submit button and invalidate form on initial state', () => { expect(component.changePasswordFormGroup.valid).toBeFalsy(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + expectSubmitButtonDisabled(); }); +} +function validForm_enableSubmitButton() { it('should enable submit button when form is valid', () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('b'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(false); + setPasswords(CORRECT_OLD_PASS, 'b', 'b'); + expectSubmitButtonEnabled(); expect(component.changePasswordFormGroup.valid).toBeTruthy(); }); +} +function passwordMismatch_disableSubmitButtonAndInvalidateForm() { it('should disable submit button and invalidate form when new password and confirm new password fields do not match', () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('a'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(CORRECT_OLD_PASS, 'a', 'b'); + expectSubmitButtonDisabled(); expect(component.changePasswordFormGroup.valid).toBeFalsy(); }); +} +function oldPasswordIncorrect_disableSubmitButtonAndShowError() { it('should disable submit button and set incorrectPassword error when old password is incorrect', async () => { - component.changePasswordFormGroup.get('oldPassword').setValue('b'); - component.newPasswordFormGroup.get('newPassword').setValue('c'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('c'); - const form = getForm(); - form.triggerEventHandler('submit', null); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(INCORRECT_OLD_PASS, 'c', 'c'); + submitForm(); + expectSubmitButtonDisabled(); expect(component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')).toBe( true ); }); +} +function formSubmit_disableSubmitButton() { it('should disable submit button when form is successfully submitted', async () => { - component.changePasswordFormGroup.get('oldPassword').setValue('a'); - component.newPasswordFormGroup.get('newPassword').setValue('b'); - component.newPasswordFormGroup.get('confirmNewPassword').setValue('b'); - const form = getForm(); - form.triggerEventHandler('submit', null); - fixture.detectChanges(); - const submitButton = getSubmitButton(); - expect(submitButton.disabled).toBe(true); + setPasswords(CORRECT_OLD_PASS, 'b', 'b'); + submitForm(); + expectSubmitButtonDisabled(); }); +} +function passwordChanged_handleResponse() { it('should handle the change password response when the password was successfully changed', () => { const resetFormSpy = spyOn(component, 'resetForm'); const snackBarSpy = spyOn(component.snackBar, 'open'); @@ -129,7 +135,9 @@ describe('EditPasswordComponent', () => { expect(resetFormSpy).toHaveBeenCalled(); expect(snackBarSpy).toHaveBeenCalled(); }); +} +function incorrectPassword_showError() { it('should handle the change password response when the password was incorrect', () => { const response = { status: 'error', @@ -140,4 +148,44 @@ describe('EditPasswordComponent', () => { true ); }); -}); +} + +function notGoogleUser_showUnlinkOption() { + it('should hide show option to unlink google account if the user is not a google user', () => { + expect(getUnlinkGoogleAccountButton()).toBeNull(); + }); +} + +function unlinkGoogleButtonClick_showDialog() { + it('clicking on unlink google account link should open a dialog', () => { + const dialogSpy = spyOn(component.dialog, 'open'); + setGoogleUser(); + getUnlinkGoogleAccountButton().click(); + expect(dialogSpy).toHaveBeenCalled(); + }); +} + +export function expectSubmitButtonDisabled() { + expect(getSubmitButton().disabled).toBe(true); +} + +function expectSubmitButtonEnabled() { + expect(getSubmitButton().disabled).toBe(false); +} + +function submitForm() { + getForm().triggerEventHandler('submit', null); + fixture.detectChanges(); +} + +function setGoogleUser() { + component.isGoogleUser = true; + fixture.detectChanges(); +} + +function setPasswords(oldPass: string, newPass: string, newPassConfirm: string) { + component.changePasswordFormGroup.get('oldPassword').setValue(oldPass); + component.newPasswordFormGroup.get('newPassword').setValue(newPass); + component.newPasswordFormGroup.get('confirmNewPassword').setValue(newPassConfirm); + fixture.detectChanges(); +} diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts index f22788cc5f..8df43f729b 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts @@ -1,15 +1,18 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; +import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { UserService } from '../../../services/user.service'; +import { UnlinkGoogleAccountConfirmComponent } from '../unlink-google-account-confirm/unlink-google-account-confirm.component'; +import { passwordMatchValidator } from '../validators/password-match.validator'; @Component({ selector: 'app-edit-password', templateUrl: './edit-password.component.html', styleUrls: ['./edit-password.component.scss'] }) -export class EditPasswordComponent implements OnInit { +export class EditPasswordComponent { @ViewChild('changePasswordForm', { static: false }) changePasswordForm; isSaving: boolean = false; isGoogleUser: boolean = false; @@ -19,7 +22,7 @@ export class EditPasswordComponent implements OnInit { newPassword: new FormControl('', [Validators.required]), confirmNewPassword: new FormControl('', [Validators.required]) }, - { validator: this.passwordMatchValidator } + { validator: passwordMatchValidator } ); changePasswordFormGroup: FormGroup = this.fb.group({ @@ -30,6 +33,7 @@ export class EditPasswordComponent implements OnInit { constructor( private fb: FormBuilder, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) {} @@ -39,18 +43,6 @@ export class EditPasswordComponent implements OnInit { }); } - passwordMatchValidator(passwordsFormGroup: FormGroup) { - const newPassword = passwordsFormGroup.get('newPassword').value; - const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value; - if (newPassword === confirmNewPassword) { - return null; - } else { - const error = { passwordDoesNotMatch: true }; - passwordsFormGroup.controls['confirmNewPassword'].setErrors(error); - return error; - } - } - saveChanges() { this.isSaving = true; const oldPassword: string = this.getControlFieldValue('oldPassword'); @@ -90,6 +82,10 @@ export class EditPasswordComponent implements OnInit { } } + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } + resetForm() { this.changePasswordForm.resetForm(); } diff --git a/src/main/webapp/site/src/app/modules/shared/shared.module.ts b/src/main/webapp/site/src/app/modules/shared/shared.module.ts index 5ec6a150df..807f04e4e7 100644 --- a/src/main/webapp/site/src/app/modules/shared/shared.module.ts +++ b/src/main/webapp/site/src/app/modules/shared/shared.module.ts @@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -13,6 +14,7 @@ import { MatSelectModule } from '@angular/material/select'; const materialModules = [ MatButtonModule, MatCardModule, + MatDialogModule, MatIconModule, MatInputModule, MatFormFieldModule, @@ -26,6 +28,9 @@ import { HeroSectionComponent } from './hero-section/hero-section.component'; import { SearchBarComponent } from './search-bar/search-bar.component'; import { SelectMenuComponent } from './select-menu/select-menu.component'; import { EditPasswordComponent } from './edit-password/edit-password.component'; +import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm/unlink-google-account-confirm.component'; +import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password/unlink-google-account-password.component'; +import { UnlinkGoogleAccountSuccessComponent } from './unlink-google-account-success/unlink-google-account-success.component'; @NgModule({ imports: [ @@ -52,7 +57,10 @@ import { EditPasswordComponent } from './edit-password/edit-password.component'; HeroSectionComponent, SearchBarComponent, SelectMenuComponent, - EditPasswordComponent + EditPasswordComponent, + UnlinkGoogleAccountConfirmComponent, + UnlinkGoogleAccountPasswordComponent, + UnlinkGoogleAccountSuccessComponent ] }) export class SharedModule {} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html new file mode 100644 index 0000000000..1787a19ea9 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html @@ -0,0 +1,17 @@ +

Unlink Google Account

+ +
+

+ If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. +

+

Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue?

+
+
+ + + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss new file mode 100644 index 0000000000..0af6d8c18e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss @@ -0,0 +1,3 @@ +.info-note { + font-weight: bold; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts new file mode 100644 index 0000000000..400a72a901 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.spec.ts @@ -0,0 +1,37 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogModule } from '@angular/material/dialog'; +import { configureTestSuite } from 'ng-bullet'; +import { UnlinkGoogleAccountConfirmComponent } from './unlink-google-account-confirm.component'; + +let component: UnlinkGoogleAccountConfirmComponent; +let fixture: ComponentFixture; + +describe('UnlinkGoogleAccountConfirmComponent', () => { + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [UnlinkGoogleAccountConfirmComponent], + imports: [MatDialogModule], + providers: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UnlinkGoogleAccountConfirmComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + continue_closeAllDialogsAndOpenChangePasswordDialog(); +}); + +function continue_closeAllDialogsAndOpenChangePasswordDialog() { + it('continue() should closeAllDialogs and open a new dialog to edit password', () => { + const closeAllDialogSpy = spyOn(component.dialog, 'closeAll'); + const openDialogSpy = spyOn(component.dialog, 'open'); + component.continue(); + expect(closeAllDialogSpy).toHaveBeenCalled(); + expect(openDialogSpy).toHaveBeenCalled(); + }); +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts new file mode 100644 index 0000000000..73a3e6d7f5 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountPasswordComponent } from '../unlink-google-account-password/unlink-google-account-password.component'; + +@Component({ + styleUrls: ['./unlink-google-account-confirm.component.scss'], + templateUrl: './unlink-google-account-confirm.component.html' +}) +export class UnlinkGoogleAccountConfirmComponent { + constructor(public dialog: MatDialog) {} + + continue() { + this.dialog.closeAll(); + this.dialog.open(UnlinkGoogleAccountPasswordComponent); + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html new file mode 100644 index 0000000000..88f3989891 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html @@ -0,0 +1,46 @@ +

Unlink Google Account

+ +
+

Create a WISE password:

+
+

+ + New Password + + New Password required + +

+

+ + Confirm New Password + + Confirm Password required + Passwords do not match + +

+
+
+
+ + + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss new file mode 100644 index 0000000000..efd343d97f --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss @@ -0,0 +1,3 @@ +mat-dialog-content { + min-width: 600px; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts new file mode 100644 index 0000000000..d10a68730e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts @@ -0,0 +1,51 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatDialogModule } from '@angular/material/dialog'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { configureTestSuite } from 'ng-bullet'; +import { Subscription } from 'rxjs'; +import { UserService } from '../../../services/user.service'; +import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password.component'; + +class MockUserService { + unlinkGoogleUser(newPassword: string) { + return new Subscription(); + } +} + +let component: UnlinkGoogleAccountPasswordComponent; +let fixture: ComponentFixture; +let userService = new MockUserService(); + +describe('UnlinkGoogleAccountPasswordComponent', () => { + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [UnlinkGoogleAccountPasswordComponent], + imports: [BrowserAnimationsModule, ReactiveFormsModule, MatDialogModule], + providers: [{ provide: UserService, useValue: userService }], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + beforeEach(() => { + fixture = TestBed.createComponent(UnlinkGoogleAccountPasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + formSubmit_callUserServiceUnlinkGoogleUserFunction(); +}); + +function formSubmit_callUserServiceUnlinkGoogleUserFunction() { + it('should call UserService.UnlinkGoogleUserFunction when form is submitted', () => { + const unlinkFunctionSpy = spyOn(userService, 'unlinkGoogleUser').and.returnValue( + new Subscription() + ); + const newPassword = 'aloha'; + component.newPasswordFormGroup.setValue({ + newPassword: newPassword, + confirmNewPassword: newPassword + }); + component.submit(); + expect(unlinkFunctionSpy).toHaveBeenCalledWith(newPassword); + }); +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts new file mode 100644 index 0000000000..152b168f88 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { UserService } from '../../../services/user.service'; +import { UnlinkGoogleAccountSuccessComponent } from '../unlink-google-account-success/unlink-google-account-success.component'; +import { passwordMatchValidator } from '../validators/password-match.validator'; + +@Component({ + styleUrls: ['./unlink-google-account-password.component.scss'], + templateUrl: './unlink-google-account-password.component.html' +}) +export class UnlinkGoogleAccountPasswordComponent { + isSaving: boolean = false; + newPasswordFormGroup: FormGroup = this.fb.group( + { + newPassword: new FormControl('', [Validators.required]), + confirmNewPassword: new FormControl('', [Validators.required]) + }, + { validator: passwordMatchValidator } + ); + + constructor( + private fb: FormBuilder, + public dialog: MatDialog, + private userService: UserService + ) {} + + submit() { + this.isSaving = true; + this.userService + .unlinkGoogleUser(this.newPasswordFormGroup.get('newPassword').value) + .add(() => { + this.isSaving = false; + this.dialog.closeAll(); + this.dialog.open(UnlinkGoogleAccountSuccessComponent); + }); + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html new file mode 100644 index 0000000000..e985c5bb42 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html @@ -0,0 +1,12 @@ +

Unlink Google Account

+ +
+

+ Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. +

+

Your username is: {{username}}

+
+
+ + + diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss new file mode 100644 index 0000000000..0af6d8c18e --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss @@ -0,0 +1,3 @@ +.info-note { + font-weight: bold; +} diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts new file mode 100644 index 0000000000..1701865273 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { Teacher } from '../../../domain/teacher'; +import { UserService } from '../../../services/user.service'; + +@Component({ + styleUrls: ['unlink-google-account-success.component.scss'], + templateUrl: 'unlink-google-account-success.component.html' +}) +export class UnlinkGoogleAccountSuccessComponent { + username: string; + + constructor(private userService: UserService) {} + + ngOnInit() { + const user = this.userService.getUser().getValue(); + this.username = user.username; + } +} diff --git a/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts new file mode 100644 index 0000000000..89ac014720 --- /dev/null +++ b/src/main/webapp/site/src/app/modules/shared/validators/password-match.validator.ts @@ -0,0 +1,13 @@ +import { FormGroup } from '@angular/forms'; + +export function passwordMatchValidator(passwordsFormGroup: FormGroup) { + const newPassword = passwordsFormGroup.get('newPassword').value; + const confirmNewPassword = passwordsFormGroup.get('confirmNewPassword').value; + if (newPassword === confirmNewPassword) { + return null; + } else { + const error = { passwordDoesNotMatch: true }; + passwordsFormGroup.controls['confirmNewPassword'].setErrors(error); + return error; + } +} diff --git a/src/main/webapp/site/src/app/services/user.service.spec.ts b/src/main/webapp/site/src/app/services/user.service.spec.ts index 5cb27e9c91..8671e76ce5 100644 --- a/src/main/webapp/site/src/app/services/user.service.spec.ts +++ b/src/main/webapp/site/src/app/services/user.service.spec.ts @@ -1,8 +1,10 @@ -import { TestBed, inject } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { UserService } from './user.service'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ConfigService } from './config.service'; +let service: UserService; +let http: HttpTestingController; export class MockConfigService {} describe('UserService', () => { @@ -11,9 +13,21 @@ describe('UserService', () => { providers: [UserService, { provide: ConfigService, useClass: MockConfigService }], imports: [HttpClientTestingModule] }); + service = TestBed.inject(UserService); + http = TestBed.inject(HttpTestingController); }); + unlinkGoogleAccount_postToUrl(); +}); - it('should be created', inject([UserService, ConfigService], (service: UserService) => { - expect(service).toBeTruthy(); +function unlinkGoogleAccount_postToUrl() { + it('unlinkGoogleAccount() should make POST request to unlink google account', fakeAsync(() => { + const newPassword = 'my new pass'; + service.unlinkGoogleUser(newPassword); + const unlinkRequest = http.expectOne({ + url: '/api/google-user/unlink-account', + method: 'POST' + }); + unlinkRequest.flush({ response: 'success' }); + tick(); })); -}); +} diff --git a/src/main/webapp/site/src/app/services/user.service.ts b/src/main/webapp/site/src/app/services/user.service.ts index 65d7695e52..b419803a08 100644 --- a/src/main/webapp/site/src/app/services/user.service.ts +++ b/src/main/webapp/site/src/app/services/user.service.ts @@ -12,13 +12,14 @@ import { Student } from '../domain/student'; export class UserService { private userUrl = '/api/user/info'; private user$: BehaviorSubject = new BehaviorSubject(null); - private checkGoogleUserExistsUrl = '/api/user/check-google-user-exists'; - private checkGoogleUserMatchesUrl = '/api/user/check-google-user-matches'; - private googleUserUrl = '/api/user/google-user'; + private checkGoogleUserExistsUrl = '/api/google-user/check-user-exists'; + private checkGoogleUserMatchesUrl = '/api/google-user/check-user-matches'; + private googleUserUrl = '/api/google-user/get-user'; private checkAuthenticationUrl = '/api/user/check-authentication'; private changePasswordUrl = '/api/user/password'; private languagesUrl = '/api/user/languages'; private contactUrl = '/api/contact'; + private unlinkGoogleAccountUrl = '/api/google-user/unlink-account'; isAuthenticated = false; isRecaptchaRequired = false; redirectUrl: string; // redirect here after logging in @@ -126,6 +127,17 @@ export class UserService { return this.http.get(this.checkGoogleUserMatchesUrl, { params: params }); } + unlinkGoogleUser(newPassword: string) { + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'); + let body = new HttpParams(); + body = body.set('newPassword', newPassword); + return this.http + .post(this.unlinkGoogleAccountUrl, body, { headers: headers }) + .subscribe((user) => { + this.user$.next(user); + }); + } + getUserByGoogleId(googleUserId: string) { let params = new HttpParams(); params = params.set('googleUserId', googleUserId); diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html index e69a78b318..14fe2c4609 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html @@ -42,7 +42,9 @@ Language required

-
+
+ +
This profile is linked to a Google account.
+ +
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts index c338a4a343..5c425f8766 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.spec.ts @@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { Student } from '../../../domain/student'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; export class MockUserService { user: User; @@ -76,6 +77,7 @@ describe('EditProfileComponent', () => { imports: [ BrowserAnimationsModule, ReactiveFormsModule, + MatDialogModule, MatInputModule, MatSelectModule, MatSnackBarModule diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index ea256cd802..afe3d42111 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -5,18 +5,22 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { Student } from '../../../domain/student'; import { UserService } from '../../../services/user.service'; import { StudentService } from '../../student.service'; +import { Subscription } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component'; @Component({ selector: 'app-edit-profile', templateUrl: './edit-profile.component.html', styleUrls: ['./edit-profile.component.scss'] }) -export class EditProfileComponent implements OnInit { +export class EditProfileComponent { user: Student; languages: object[]; changed: boolean = false; isSaving: boolean = false; - + isGoogleUser: boolean = false; + userSubscription: Subscription; editProfileFormGroup: FormGroup = this.fb.group({ firstName: new FormControl({ value: '', disabled: true }, [Validators.required]), lastName: new FormControl({ value: '', disabled: true }, [Validators.required]), @@ -28,6 +32,7 @@ export class EditProfileComponent implements OnInit { private fb: FormBuilder, private studentService: StudentService, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) { this.user = this.getUser().getValue(); @@ -38,10 +43,6 @@ export class EditProfileComponent implements OnInit { this.userService.getLanguages().subscribe((response) => { this.languages = response; }); - - this.editProfileFormGroup.valueChanges.subscribe(() => { - this.changed = true; - }); } getUser() { @@ -52,7 +53,18 @@ export class EditProfileComponent implements OnInit { this.editProfileFormGroup.controls[name].setValue(value); } - ngOnInit() {} + ngOnInit() { + this.editProfileFormGroup.valueChanges.subscribe(() => { + this.changed = true; + }); + this.userSubscription = this.userService.getUser().subscribe((user) => { + this.isGoogleUser = user.isGoogleUser; + }); + } + + ngOnDestroy() { + this.userSubscription.unsubscribe(); + } saveChanges() { this.isSaving = true; @@ -84,4 +96,8 @@ export class EditProfileComponent implements OnInit { } this.isSaving = false; } + + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } } diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html index b4f5bd0f54..a551190844 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html @@ -109,7 +109,9 @@

-
+
+ +
This profile is linked to a Google account.
+ +
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts index e8fe132d86..05b8ad23a2 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.spec.ts @@ -13,6 +13,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { User } from '../../../domain/user'; import { configureTestSuite } from 'ng-bullet'; +import { MatDialogModule } from '@angular/material/dialog'; export class MockUserService { user: User; @@ -89,6 +90,7 @@ describe('EditProfileComponent', () => { imports: [ BrowserAnimationsModule, ReactiveFormsModule, + MatDialogModule, MatInputModule, MatSelectModule, MatSnackBarModule diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index 6ed9c50c25..a1c09fd71f 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -5,13 +5,16 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { UserService } from '../../../services/user.service'; import { Teacher } from '../../../domain/teacher'; import { TeacherService } from '../../teacher.service'; +import { MatDialog } from '@angular/material/dialog'; +import { UnlinkGoogleAccountConfirmComponent } from '../../../modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-edit-profile', templateUrl: './edit-profile.component.html', styleUrls: ['./edit-profile.component.scss'] }) -export class EditProfileComponent implements OnInit { +export class EditProfileComponent { user: Teacher; schoolLevels: any[] = [ { id: 'ELEMENTARY_SCHOOL', label: $localize`Elementary School` }, @@ -23,6 +26,8 @@ export class EditProfileComponent implements OnInit { languages: object[]; changed: boolean = false; isSaving: boolean = false; + isGoogleUser: boolean = false; + userSubscription: Subscription; editProfileFormGroup: FormGroup = this.fb.group({ firstName: new FormControl({ value: '', disabled: true }, [Validators.required]), @@ -41,6 +46,7 @@ export class EditProfileComponent implements OnInit { private fb: FormBuilder, private teacherService: TeacherService, private userService: UserService, + public dialog: MatDialog, public snackBar: MatSnackBar ) { this.user = this.getUser().getValue(); @@ -57,10 +63,6 @@ export class EditProfileComponent implements OnInit { this.userService.getLanguages().subscribe((response) => { this.languages = response; }); - - this.editProfileFormGroup.valueChanges.subscribe(() => { - this.changed = true; - }); } getUser() { @@ -71,7 +73,19 @@ export class EditProfileComponent implements OnInit { this.editProfileFormGroup.controls[name].setValue(value); } - ngOnInit() {} + ngOnInit() { + this.editProfileFormGroup.valueChanges.subscribe(() => { + this.changed = true; + }); + + this.userSubscription = this.userService.getUser().subscribe((user) => { + this.isGoogleUser = user.isGoogleUser; + }); + } + + ngOnDestroy() { + this.userSubscription.unsubscribe(); + } saveChanges() { this.isSaving = true; diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index e6b1586225..1feee2c605 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -236,29 +236,87 @@ 10 - - Current Password + + Unlink Google Account + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 1 + + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 1 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 1 + app/modules/shared/edit-password/edit-password.component.html - 8 + 66 + + + app/student/account/edit-profile/edit-profile.component.html + 64 + + + app/teacher/account/edit-profile/edit-profile.component.html + 131 - - Current Password required + + Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. - app/modules/shared/edit-password/edit-password.component.html - 15 + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 4 - - Current Password is incorrect + + Your username is: - app/modules/shared/edit-password/edit-password.component.html - 16 + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 7 + + + + Done + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 11 + + + app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html + 75 + + + app/teacher/create-run-dialog/create-run-dialog.component.html + 85 + + + app/modules/library/share-project-dialog/share-project-dialog.component.html + 70 + + + app/teacher/share-run-dialog/share-run-dialog.component.html + 102 + + + app/teacher/run-settings-dialog/run-settings-dialog.component.html + 76 + + + + Create a WISE password: + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 4 New Password + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 8 + app/modules/shared/edit-password/edit-password.component.html 22 @@ -266,6 +324,10 @@ New Password required + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 15 + app/modules/shared/edit-password/edit-password.component.html 29 @@ -273,6 +335,10 @@ Confirm New Password + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 20 + app/modules/shared/edit-password/edit-password.component.html 34 @@ -280,6 +346,10 @@ Confirm Password required + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 27 + app/modules/shared/edit-password/edit-password.component.html 41 @@ -303,11 +373,155 @@ Passwords do not match + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 28 + app/modules/shared/edit-password/edit-password.component.html 42 + + Cancel + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 35 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 11 + + + app/modules/library/copy-project-dialog/copy-project-dialog.component.html + 11 + + + app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html + 47 + + + app/teacher/create-run-dialog/create-run-dialog.component.html + 63 + + + app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html + 15 + + + app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html + 20 + + + app/student/add-project-dialog/add-project-dialog.component.html + 23 + + + app/student/team-sign-in-dialog/team-sign-in-dialog.component.html + 68 + + + app/teacher/share-run-dialog/share-run-dialog.component.html + 100 + + + app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html + 62 + + + app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html + 40 + + + app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html + 23 + + + app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html + 41 + + + + Submit + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 43 + + + app/contact/contact-form/contact-form.component.html + 112 + + + app/forgot/student/forgot-student-password/forgot-student-password.component.html + 25 + + + app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html + 25 + + + app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html + 25 + + + app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html + 28 + + + app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html + 41 + + + app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html + 39 + + + app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html + 26 + + + + If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 4 + + + + Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue? + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 7 + + + + Continue + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 15 + + + + Current Password + + app/modules/shared/edit-password/edit-password.component.html + 8 + + + + Current Password required + + app/modules/shared/edit-password/edit-password.component.html + 15 + + + + Current Password is incorrect + + app/modules/shared/edit-password/edit-password.component.html + 16 + + Change Password @@ -323,8 +537,8 @@ 7 - - This account was created using Google and doesn't use a WISE password. If you would like to unlink your Google account, please contact us. + + This account was created using Google and doesn't use a WISE password. app/modules/shared/edit-password/edit-password.component.html 60 @@ -1068,57 +1282,6 @@ 7 - - Cancel - - app/modules/library/copy-project-dialog/copy-project-dialog.component.html - 11 - - - app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html - 47 - - - app/teacher/create-run-dialog/create-run-dialog.component.html - 63 - - - app/teacher/use-with-class-warning-dialog/use-with-class-warning-dialog.component.html - 15 - - - app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html - 20 - - - app/student/add-project-dialog/add-project-dialog.component.html - 23 - - - app/student/team-sign-in-dialog/team-sign-in-dialog.component.html - 68 - - - app/teacher/share-run-dialog/share-run-dialog.component.html - 100 - - - app/authoring-tool/import-step/choose-import-step/choose-import-step.component.html - 62 - - - app/authoring-tool/import-step/choose-import-step-location/choose-import-step-location.component.html - 40 - - - app/authoring-tool/add-component/choose-new-component/choose-new-component.component.html - 23 - - - app/authoring-tool/add-component/choose-new-component-location/choose-new-component-location.component.html - 41 - - Copy @@ -1244,29 +1407,6 @@ 69 - - Done - - app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html - 75 - - - app/teacher/create-run-dialog/create-run-dialog.component.html - 85 - - - app/modules/library/share-project-dialog/share-project-dialog.component.html - 70 - - - app/teacher/share-run-dialog/share-run-dialog.component.html - 102 - - - app/teacher/run-settings-dialog/run-settings-dialog.component.html - 76 - - Use with Class @@ -2282,41 +2422,6 @@ 104 - - Submit - - app/contact/contact-form/contact-form.component.html - 112 - - - app/forgot/student/forgot-student-password/forgot-student-password.component.html - 25 - - - app/forgot/teacher/forgot-teacher-username/forgot-teacher-username.component.html - 25 - - - app/forgot/teacher/forgot-teacher-password/forgot-teacher-password.component.html - 25 - - - app/forgot/student/forgot-student-password-security/forgot-student-password-security.component.html - 28 - - - app/forgot/student/forgot-student-password-change/forgot-student-password-change.component.html - 41 - - - app/forgot/teacher/forgot-teacher-password-change/forgot-teacher-password-change.component.html - 39 - - - app/forgot/teacher/forgot-teacher-password-verify/forgot-teacher-password-verify.component.html - 26 - - WISE Features @@ -5287,11 +5392,22 @@ Save Changes app/student/account/edit-profile/edit-profile.component.html - 53 + 55 app/teacher/account/edit-profile/edit-profile.component.html - 120 + 122 + + + + This profile is linked to a Google account. + + app/student/account/edit-profile/edit-profile.component.html + 58 + + + app/teacher/account/edit-profile/edit-profile.component.html + 125 diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java new file mode 100644 index 0000000000..b66900f8ff --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/GoogleUserAPIControllerTest.java @@ -0,0 +1,108 @@ +package org.wise.portal.presentation.web.controllers.user; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.HashMap; + +import org.easymock.TestSubject; +import org.junit.Test; +import org.wise.portal.presentation.web.exception.InvalidPasswordExcpetion; + +public class GoogleUserAPIControllerTest extends UserAPIControllerTest { + + @TestSubject + private GoogleUserAPIController controller = new GoogleUserAPIController(); + + @Test + public void isGoogleIdExist_GoogleUserExists_ReturnTrue() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + assertTrue(controller.isGoogleIdExist(STUDENT1_GOOGLE_ID)); + verify(userService); + } + + @Test + public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + assertFalse(controller.isGoogleIdExist(invalidGoogleId)); + verify(userService); + } + + @Test + public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + assertTrue(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString())); + verify(userService); + } + + @Test + public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + assertFalse(controller.isGoogleIdMatches(invalidGoogleId, student1Id.toString())); + verify(userService); + } + + @Test + public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1); + replay(userService); + assertFalse(controller.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString())); + verify(userService); + } + + @Test + public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() { + expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); + replay(userService); + HashMap response = controller.getUserByGoogleId(STUDENT1_GOOGLE_ID); + assertEquals("success", response.get("status")); + assertEquals(student1.getId(), response.get("userId")); + verify(userService); + } + + @Test + public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() { + String invalidGoogleId = "google-id-not-exists-in-db"; + expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); + replay(userService); + HashMap response = controller.getUserByGoogleId(invalidGoogleId); + assertEquals("error", response.get("status")); + verify(userService); + } + + @Test + public void unlinkGoogleAccount_InvalidNewPassword_ThrowException() { + expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1); + replay(userService); + String newPass = ""; + try { + controller.unlinkGoogleAccount(studentAuth, newPass); + fail("InvalidPasswordException was expected"); + } catch (Exception e) { + } + } + + @Test + public void unlinkGoogleAccount_ValidNewPassword_ReturnUpdatedUserMap() + throws InvalidPasswordExcpetion { + String newPassword = "my new pass"; + assertTrue(student1.getUserDetails().isGoogleUser()); + expect(userService.retrieveUserByUsername(STUDENT_USERNAME)).andReturn(student1).times(2); + expect(userService.updateUserPassword(student1, newPassword)).andReturn(student1); + expect(appProperties.getProperty("send_email_enabled", "false")).andReturn("false"); + replay(userService, appProperties); + controller.unlinkGoogleAccount(studentAuth, newPassword); + verify(userService, appProperties); + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java index 251fcb0b47..c5b901ce8f 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/user/UserAPIControllerTest.java @@ -143,68 +143,6 @@ public void getSupportedLanguages_ThreeSupportedLocales_ReturnLanguageArray() { verify(appProperties); } - @Test - public void isGoogleIdExist_GoogleUserExists_ReturnTrue() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - assertTrue(userAPIController.isGoogleIdExist(STUDENT1_GOOGLE_ID)); - verify(userService); - } - - @Test - public void isGoogleIdExist_InvalidGoogleUserId_ReturnFalse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - assertFalse(userAPIController.isGoogleIdExist(invalidGoogleId)); - verify(userService); - } - - @Test - public void isGoogleIdMatches_GoogleUserIdAndUserIdMatch_ReturnTrue() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - assertTrue(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, student1Id.toString())); - verify(userService); - } - - @Test - public void isGoogleIdMatches_InvalidGoogleUserId_ReturnFalse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - assertFalse(userAPIController.isGoogleIdMatches(invalidGoogleId, student1Id.toString())); - verify(userService); - } - - @Test - public void isGoogleIdMatches_GoogleUserIdAndUserIdDoNotMatch_ReturnFalse() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(teacher1); - replay(userService); - assertFalse(userAPIController.isGoogleIdMatches(STUDENT1_GOOGLE_ID, teacher1.toString())); - verify(userService); - } - - @Test - public void getUserByGoogleId_GoogleUserExists_ReturnSuccessResponse() { - expect(userService.retrieveUserByGoogleUserId(STUDENT1_GOOGLE_ID)).andReturn(student1); - replay(userService); - HashMap response = userAPIController.getUserByGoogleId(STUDENT1_GOOGLE_ID); - assertEquals("success", response.get("status")); - assertEquals(student1.getId(), response.get("userId")); - verify(userService); - } - - @Test - public void getUserByGoogleId_InvalidGoogleUserId_ReturnErrorResponse() { - String invalidGoogleId = "google-id-not-exists-in-db"; - expect(userService.retrieveUserByGoogleUserId(invalidGoogleId)).andReturn(null); - replay(userService); - HashMap response = userAPIController.getUserByGoogleId(invalidGoogleId); - assertEquals("error", response.get("status")); - verify(userService); - } - @Test public void isNameValid_InvalidName_ReturnFalse() { assertFalse(userAPIController.isNameValid("")); From 6f28ba9bf0942ee017e3e39588c3828b12e66714 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 26 Jan 2021 09:24:00 -0800 Subject: [PATCH 2/3] Aded unlinkGoogleAccount() to teacher edit profile. #2870 --- .../student/account/edit-profile/edit-profile.component.ts | 2 +- .../teacher/account/edit-profile/edit-profile.component.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index afe3d42111..f083a25656 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { MatSnackBar } from '@angular/material/snack-bar'; diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index a1c09fd71f..a6e0d95779 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -142,4 +142,8 @@ export class EditProfileComponent { this.snackBar.open($localize`An error occurred. Please try again.`); } } + + unlinkGoogleAccount() { + this.dialog.open(UnlinkGoogleAccountConfirmComponent); + } } From ffd530cb511d3e3609b9464bbf6b0b26b05941d8 Mon Sep 17 00:00:00 2001 From: breity Date: Fri, 29 Jan 2021 13:27:06 -0800 Subject: [PATCH 3/3] Styled and updated text for unlink Google account dialogs, student and teacher profile pages. #2870 --- .../edit-password.component.html | 18 +- .../edit-password.component.scss | 9 +- .../edit-password/edit-password.component.ts | 4 +- ...link-google-account-confirm.component.html | 23 ++- ...link-google-account-confirm.component.scss | 7 +- ...unlink-google-account-confirm.component.ts | 4 +- ...ink-google-account-password.component.html | 87 ++++----- ...ink-google-account-password.component.scss | 5 +- ...nlink-google-account-password.component.ts | 4 +- ...link-google-account-success.component.html | 11 +- ...link-google-account-success.component.scss | 5 +- .../edit-profile/edit-profile.component.html | 43 +++-- .../edit-profile/edit-profile.component.scss | 17 +- .../edit-profile/edit-profile.component.ts | 4 +- .../edit-profile/edit-profile.component.html | 37 ++-- .../edit-profile/edit-profile.component.scss | 13 ++ .../edit-profile/edit-profile.component.ts | 4 +- .../edit-run-warning-dialog.component.html | 4 +- src/main/webapp/site/src/messages.xlf | 177 +++++++++++------- .../site/src/style/layout/_section.scss | 2 +- 20 files changed, 280 insertions(+), 198 deletions(-) diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html index 71af1f2b7d..e2fd57a2df 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.html @@ -57,13 +57,17 @@
-

This account was created using Google and doesn't use a WISE password.

-

-

diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss index 940482c43e..60fab472cc 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.scss @@ -11,6 +11,11 @@ form { } } -.notice { - margin: 0 auto; +.google-icon { + height: 1.8em; + width: auto; +} + +.unlink { + margin: 8px 0; } diff --git a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts index 8df43f729b..7539bc803e 100644 --- a/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/edit-password/edit-password.component.ts @@ -83,7 +83,9 @@ export class EditPasswordComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } resetForm() { diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html index 1787a19ea9..9fd9a47c13 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html @@ -1,17 +1,16 @@ -

Unlink Google Account

+

+ Google logo + Unlink Google Account + + warning +

-

- If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. -

-

Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue?

+

To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password.

+

You will no longer be able to sign in to WISE using Google. Would you like to continue?

- - - + + + Continue diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss index 0af6d8c18e..257ab6ea45 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.scss @@ -1,3 +1,4 @@ -.info-note { - font-weight: bold; -} +.google-icon { + height: 1.4em; + width: auto; +} \ No newline at end of file diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts index 73a3e6d7f5..99a4fe2fd8 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.ts @@ -11,6 +11,8 @@ export class UnlinkGoogleAccountConfirmComponent { continue() { this.dialog.closeAll(); - this.dialog.open(UnlinkGoogleAccountPasswordComponent); + this.dialog.open(UnlinkGoogleAccountPasswordComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html index 88f3989891..e5c496120f 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html @@ -1,46 +1,41 @@ -

Unlink Google Account

- -
-

Create a WISE password:

-
-

- - New Password - - New Password required - -

-

- - Confirm New Password - - Confirm Password required - Passwords do not match - -

-
-
-
- - - - +

+ Google logo + Unlink Google Account +

+
+ +

Create a WISE password:

+ + New Password + + New Password required + + + Confirm New Password + + Confirm Password required + Passwords do not match + +
+ + + + +
diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss index efd343d97f..6629b7fe81 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.scss @@ -1,3 +1,4 @@ -mat-dialog-content { - min-width: 600px; +.google-icon { + height: 1.4em; + width: auto; } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts index 152b168f88..da0a00f3a0 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts @@ -32,7 +32,9 @@ export class UnlinkGoogleAccountPasswordComponent { .add(() => { this.isSaving = false; this.dialog.closeAll(); - this.dialog.open(UnlinkGoogleAccountSuccessComponent); + this.dialog.open(UnlinkGoogleAccountSuccessComponent, { + panelClass: 'mat-dialog--sm' + }); }); } } diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html index e985c5bb42..c9d91706f6 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html @@ -1,10 +1,11 @@ -

Unlink Google Account

+

+ Google logo + Unlink Google Account +

-

- Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. -

-

Your username is: {{username}}

+

Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created.

+

Your username is: {{ username }}.

diff --git a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss index 0af6d8c18e..6629b7fe81 100644 --- a/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss +++ b/src/main/webapp/site/src/app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.scss @@ -1,3 +1,4 @@ -.info-note { - font-weight: bold; +.google-icon { + height: 1.4em; + width: auto; } diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html index 14fe2c4609..10dd31c105 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.html @@ -1,5 +1,5 @@
-
+

First Name @@ -42,28 +42,31 @@ Language required

-
+
+ +
- - -
This profile is linked to a Google account.
- -
diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss index 36ff60ef97..a1e699e818 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.scss @@ -3,10 +3,19 @@ '~style/abstracts/functions', '~style/abstracts/mixins'; -form { +.inputs { max-width: breakpoint('sm.min'); +} + +.actions { + margin-top: 8px; +} + +.google-icon { + height: 1.8em; + width: auto; +} - @media (max-width: breakpoint('sm.max')) { - margin: 0 auto; - } +.unlink { + margin: 8px 0; } diff --git a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts index f083a25656..fe6d7b3452 100644 --- a/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/student/account/edit-profile/edit-profile.component.ts @@ -98,6 +98,8 @@ export class EditProfileComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html index a551190844..159c38c1b1 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.html @@ -105,31 +105,36 @@ Help us translate WISE! Visit https://crowdin.com/project/wise. - Language required + + Language required +

-
+
- -
This profile is linked to a Google account.
- -
+
diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss index dbd834f0b1..1888291374 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.scss @@ -21,3 +21,16 @@ } } } + +.actions { + margin-top: 8px; +} + +.google-icon { + height: 1.8em; + width: auto; +} + +.unlink { + margin: 8px 0; +} diff --git a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts index a6e0d95779..01c612ea48 100644 --- a/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts +++ b/src/main/webapp/site/src/app/teacher/account/edit-profile/edit-profile.component.ts @@ -144,6 +144,8 @@ export class EditProfileComponent { } unlinkGoogleAccount() { - this.dialog.open(UnlinkGoogleAccountConfirmComponent); + this.dialog.open(UnlinkGoogleAccountConfirmComponent, { + panelClass: 'mat-dialog--sm' + }); } } diff --git a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html index 1f9c2f19f8..c196fa4e2f 100644 --- a/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html +++ b/src/main/webapp/site/src/app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html @@ -1,7 +1,7 @@ -

+

Edit Classroom Unit - warning + warning

diff --git a/src/main/webapp/site/src/messages.xlf b/src/main/webapp/site/src/messages.xlf index 1feee2c605..873438e45e 100644 --- a/src/main/webapp/site/src/messages.xlf +++ b/src/main/webapp/site/src/messages.xlf @@ -236,23 +236,47 @@ 10 - - Unlink Google Account + + Google logo app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 1 + 2 app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 1 + 2 app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 1 + 2 app/modules/shared/edit-password/edit-password.component.html - 66 + 61 + + + app/register/register-teacher/register-teacher.component.html + 28 + + + app/register/register-teacher-complete/register-teacher-complete.component.html + 14 + + + app/register/register-student-complete/register-student-complete.component.html + 14 + + + app/register/register-student/register-student.component.html + 39 + + + app/register/register-google-user-already-exists/register-google-user-already-exists.component.html + 10 + + + app/student/team-sign-in-dialog/team-sign-in-dialog.component.html + 59 app/student/account/edit-profile/edit-profile.component.html @@ -260,28 +284,55 @@ app/teacher/account/edit-profile/edit-profile.component.html - 131 + 132 - - Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and password you just created. + + Unlink Google Account app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 4 + 3 + + + app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html + 3 + + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 3 + + + app/modules/shared/edit-password/edit-password.component.html + 70 + + + app/student/account/edit-profile/edit-profile.component.html + 68 + + + app/teacher/account/edit-profile/edit-profile.component.html + 136 - - Your username is: + + Success! You have unlinked your Google account from WISE. To sign in to WISE in the future, please use your username and the password you just created. app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html 7 + + Your username is: . + + app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html + 8 + + Done app/modules/shared/unlink-google-account-success/unlink-google-account-success.component.html - 11 + 12 app/teacher/list-classroom-courses-dialog/list-classroom-courses-dialog.component.html @@ -308,14 +359,14 @@ Create a WISE password: app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 4 + 7 New Password app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 8 + 9 app/modules/shared/edit-password/edit-password.component.html @@ -326,7 +377,7 @@ New Password required app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 15 + 16 app/modules/shared/edit-password/edit-password.component.html @@ -337,7 +388,7 @@ Confirm New Password app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 20 + 19 app/modules/shared/edit-password/edit-password.component.html @@ -348,7 +399,7 @@ Confirm Password required app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 27 + 26 app/modules/shared/edit-password/edit-password.component.html @@ -375,7 +426,7 @@ Passwords do not match app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 28 + 27 app/modules/shared/edit-password/edit-password.component.html @@ -386,11 +437,11 @@ Cancel app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 35 + 31 app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 11 + 14 app/modules/library/copy-project-dialog/copy-project-dialog.component.html @@ -445,7 +496,7 @@ Submit app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.html - 43 + 37 app/contact/contact-form/contact-form.component.html @@ -480,18 +531,33 @@ 26 - - If you remove the link to your Google account, you will be asked to create a WISE password. You will then need to sign in to WISE using your username and password in the future. + + Warning app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 5 + + + app/teacher/edit-run-warning-dialog/edit-run-warning-dialog.component.html 4 + + app/help/teacher-faq/teacher-faq.component.html + 85 + - - Note: You will no longer be able to sign in to this WISE account using Google. Do you want to continue? + + To remove the link to your Google account, you will be asked to create a WISE password. In the future, you'll sign in to WISE using your username and password. app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html - 7 + 9 + + + + You will no longer be able to sign in to WISE using Google. Would you like to continue? + + app/modules/shared/unlink-google-account-confirm/unlink-google-account-confirm.component.html + 10 @@ -537,8 +603,8 @@ 7 - - This account was created using Google and doesn't use a WISE password. + + This account was created using Google and doesn't use a WISE password. app/modules/shared/edit-password/edit-password.component.html 60 @@ -3943,13 +4009,6 @@ 84 - - Warning - - app/help/teacher-faq/teacher-faq.component.html - 85 - - If you move a student to a different period, they will lose all of their work. @@ -4675,33 +4734,6 @@ 22 - - Google logo - - app/register/register-teacher/register-teacher.component.html - 28 - - - app/register/register-teacher-complete/register-teacher-complete.component.html - 14 - - - app/register/register-student-complete/register-student-complete.component.html - 14 - - - app/register/register-student/register-student.component.html - 39 - - - app/register/register-google-user-already-exists/register-google-user-already-exists.component.html - 10 - - - app/student/team-sign-in-dialog/team-sign-in-dialog.component.html - 59 - - Sign up with Google @@ -5383,31 +5415,27 @@ app/student/account/edit-profile/edit-profile.component.html 42 - - app/teacher/account/edit-profile/edit-profile.component.html - 108 - Save Changes app/student/account/edit-profile/edit-profile.component.html - 55 + 57 app/teacher/account/edit-profile/edit-profile.component.html - 122 + 125 This profile is linked to a Google account. app/student/account/edit-profile/edit-profile.component.html - 58 + 65 app/teacher/account/edit-profile/edit-profile.component.html - 125 + 133 @@ -5908,6 +5936,13 @@ 107 + + Language required + + app/teacher/account/edit-profile/edit-profile.component.html + 108 + + Back to Unit Plan diff --git a/src/main/webapp/site/src/style/layout/_section.scss b/src/main/webapp/site/src/style/layout/_section.scss index bc80d059f1..25b5b018a6 100644 --- a/src/main/webapp/site/src/style/layout/_section.scss +++ b/src/main/webapp/site/src/style/layout/_section.scss @@ -20,7 +20,7 @@ } .section__tab { - padding: 24px 0; + padding: 24px 4px; @media (min-width: breakpoint('sm.min')) { padding: 24px 16px;