diff --git a/ui/src/app/home/consortium/add-consortium-member.component.html b/ui/src/app/home/consortium/add-consortium-member.component.html new file mode 100644 index 000000000..b614b60f8 --- /dev/null +++ b/ui/src/app/home/consortium/add-consortium-member.component.html @@ -0,0 +1,507 @@ +
+
+ Back to {{ memberData.publicDisplayName }} +
+
+
Add new consortium member
+

New organization

+
+
+
+ Warning sign +
+
+ Your changes cannot be saved +
+
+ Please fix the issues with the form before trying to save again +
+
+
+
+

Organization details

+ + + +

Name & email domain

+
+ + + + The legal or official name for this organization. Max 41 characters. + +
+ + Organization name cannot be empty + +
+ + Organization name is too long. Please use 41 characters or less. + +
+
+
+
+ + + + The email domain is the remaining address after the @ symbol eg: orcid.org or website.com + +
+ + Email domain is too long. Please use 255 characters or less. + +
+
+ + + +

Billing address

+
+ + +
+ + Street name is too long. Please use 255 characters or less. + +
+
+
+ + +
+ + City name is too long. Please use 40 characters or less. + +
+
+
+ + +
+
+ + +
+ + Please select a country + +
+
+
+ + +
+ + ZIP/Postcode is too long. Please use 20 characters or less. + +
+
+ + + +

+ Trademark license (Required) +

+
+ Can ORCID use this organization's trademarked name and logos? +
+
+
+ + +
+
+ + +
+
+ + Please select a trademark license option + +
+
+ + + +

Membership start date (Required)

+
+ When does this organization's ORCID membership start? All memberships begin on the 1st of the month. +
+
+
+ + +
+ + Please select the membership start date + + + Please select the membership start month + + + Please select the membership start year + +
+ + + +

Main contact

+
+ Please provide details of the main point of contact for this organization. This person will automatically be assigned the + Main relationship and Voting contact roles for this organization. +
+
+ + +
+ + First or given name is too long. Please use 40 characters or less. + + + Please provide a first or given name + +
+
+
+ + +
+ + Last or family name is too long. Please use 80 characters or less. + + + Please provide a last or family name + +
+
+
+ + +
+ + Job title is too long. Please use 128 characters or less. + +
+
+
+ + + + The contact email for this person + +
+ + Please enter a valid email address, for example contactus@website.com + + + Email is too long. Please use 80 characters or less. + + + Please provide an email address + +
+
+
+ Warning sign +
+
+ Please note +
+
+ It can take up to 24 hours before a new organization is available in the Member Portal. Please contact your ORCID engagement + lead for more information. +
+
+
+ + +
+
+
+
+ \ No newline at end of file diff --git a/ui/src/app/home/consortium/add-consortium-member.component.scss b/ui/src/app/home/consortium/add-consortium-member.component.scss new file mode 100644 index 000000000..768ceb164 --- /dev/null +++ b/ui/src/app/home/consortium/add-consortium-member.component.scss @@ -0,0 +1,63 @@ +@use '../../../content/scss/bootstrap-variables' as global; + +input { + font-size: 14px; + letter-spacing: 0.25px; +} + +input, +select, +option { + height: 40px; +} + +.ng-invalid:not(form) { + border: 1px solid #d32f2f; + border-radius: 2px; +} + +label { + margin-bottom: 0.25rem; +} + +.error-message { + img { + padding: 0 16px 0 8px !important; + margin-top: -1.75rem; + } + div { + font-size: 0.875rem; + } + border: 2px solid #b71c1c; + border-radius: 4px; +} + +.warning-message { + img { + padding: 0 16px 0 8px !important; + margin-top: -3rem; + } + div { + font-size: 0.875rem; + } + border: 2px solid #ff9c00; + border-radius: 4px; +} + +.radio { + height: 1.125rem; + width: 1.125rem; + accent-color: global.$info; +} + +.date-dropdown { + width: 7.5rem; +} + +.postcode-input-field { + max-width: 180px; +} + +.org-name-input-field { + max-width: 400px; +} diff --git a/ui/src/app/home/consortium/add-consortium-member.component.spec.ts b/ui/src/app/home/consortium/add-consortium-member.component.spec.ts new file mode 100644 index 000000000..a4f4bdfa0 --- /dev/null +++ b/ui/src/app/home/consortium/add-consortium-member.component.spec.ts @@ -0,0 +1,94 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { AddConsortiumMemberComponent } from './add-consortium-member.component' +import { MemberService } from 'src/app/member/service/member.service' +import { AccountService } from 'src/app/account' +import { AlertService } from 'src/app/shared/service/alert.service' +import { ActivatedRoute, Router } from '@angular/router' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { of } from 'rxjs' +import { AlertType } from 'src/app/app.constants' + +describe('AddConsortiumMemberComponent', () => { + let component: AddConsortiumMemberComponent + let fixture: ComponentFixture + + let memberServiceSpy: jasmine.SpyObj + let accountServiceSpy: jasmine.SpyObj + let alertServiceSpy: jasmine.SpyObj + let activatedRoute: jasmine.SpyObj + let router: jasmine.SpyObj + + beforeEach(() => { + memberServiceSpy = jasmine.createSpyObj('MemberService', ['addConsortiumMember']) + accountServiceSpy = jasmine.createSpyObj('AccountService', ['getAccountData']) + alertServiceSpy = jasmine.createSpyObj('AlertService', ['broadcast']) + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [AddConsortiumMemberComponent], + providers: [ + { provide: MemberService, useValue: memberServiceSpy }, + { provide: AccountService, useValue: accountServiceSpy }, + { provide: AlertService, useValue: alertServiceSpy }, + ], + }) + + accountServiceSpy = TestBed.inject(AccountService) as jasmine.SpyObj + memberServiceSpy = TestBed.inject(MemberService) as jasmine.SpyObj + alertServiceSpy = TestBed.inject(AlertService) as jasmine.SpyObj + router = TestBed.inject(Router) as jasmine.SpyObj + activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj + + fixture = TestBed.createComponent(AddConsortiumMemberComponent) + component = fixture.componentInstance + + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)) + + accountServiceSpy.getAccountData.and.returnValue( + of({ + activated: true, + authorities: ['test', 'test'], + email: 'email@email.com', + firstName: 'name', + langKey: 'en', + lastName: 'surname', + imageUrl: 'url', + salesforceId: 'sfid', + loggedAs: false, + loginAs: 'sfid', + mainContact: false, + mfaEnabled: true, + }) + ) + + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should call memberService.updateContact when saving', () => { + memberServiceSpy.addConsortiumMember.and.returnValue(of(true)) + + // set form to valid + for (const control in component.editForm.controls) { + component.editForm.controls[control].clearAsyncValidators() + component.editForm.controls[control].clearValidators() + component.editForm.controls[control].updateValueAndValidity({ onlySelf: true }) + } + component.editForm.updateValueAndValidity() + + component.save() + + expect(memberServiceSpy.addConsortiumMember).toHaveBeenCalled() + }) + + it('alert service and router should be called on save success', () => { + component.onSaveSuccess() + expect(alertServiceSpy.broadcast).toHaveBeenCalledWith(AlertType.CONSORTIUM_MEMBER_ADDED) + expect(router.navigate).toHaveBeenCalled() + }) +}) diff --git a/ui/src/app/home/consortium/add-consortium-member.component.ts b/ui/src/app/home/consortium/add-consortium-member.component.ts new file mode 100644 index 000000000..d64f19731 --- /dev/null +++ b/ui/src/app/home/consortium/add-consortium-member.component.ts @@ -0,0 +1,156 @@ +import { Component, OnInit } from '@angular/core' +import { FormGroup, FormBuilder, Validators } from '@angular/forms' +import { Router, ActivatedRoute } from '@angular/router' +import { combineLatest, take } from 'rxjs' +import { AccountService } from '../../account' +import { AlertType, EMAIL_REGEXP } from '../../app.constants' +import { AlertService } from '../../shared/service/alert.service' +import { DateUtilService } from '../../shared/service/date-util.service' +import { ISFCountry } from '../../member/model/salesforce-country.model' +import { ISFState } from '../../member/model/salesforce-country.model copy' +import { ISFMemberData } from '../../member/model/salesforce-member-data.model' +import { ISFNewConsortiumMember } from '../../member/model/salesforce-new-consortium-member.model' +import { MemberService } from '../../member/service/member.service' + +@Component({ + selector: 'app-add-consortium-member', + templateUrl: './add-consortium-member.component.html', + styleUrls: ['./add-consortium-member.component.scss'], +}) +export class AddConsortiumMemberComponent implements OnInit { + countries: ISFCountry[] | undefined + states: ISFState[] | undefined + memberData: ISFMemberData | undefined | null + isSaving = false + invalidForm = false + routeData: any + currentMonth: number | undefined + currentYear: number | undefined + monthList: [number, string][] | undefined + yearList: number[] | undefined + editForm: FormGroup = this.fb.group({ + orgName: [null, [Validators.required, Validators.maxLength(41)]], + emailDomain: [null, [Validators.maxLength(255)]], + street: [null, [Validators.maxLength(255)]], + city: [null, [Validators.maxLength(40)]], + state: [null, [Validators.maxLength(80)]], + country: [null, [Validators.required]], + postcode: [null, [Validators.maxLength(20)]], + trademarkLicense: [null, [Validators.required]], + startMonth: [null, [Validators.required]], + startYear: [null, [Validators.required]], + contactGivenName: [null, [Validators.required, Validators.maxLength(40)]], + contactFamilyName: [null, [Validators.required, Validators.maxLength(80)]], + contactJobTitle: [null, [Validators.maxLength(128)]], + contactEmail: [null, [Validators.required, Validators.pattern(EMAIL_REGEXP), Validators.maxLength(80)]], + }) + + rolesData = [ + { id: 1, selected: false, name: 'Main relationship contact' }, + { id: 2, selected: false, name: 'Voting contact' }, + { id: 3, selected: false, name: 'Technical contact' }, + { id: 4, selected: false, name: 'Invoice contact' }, + { id: 5, selected: false, name: 'Comms contact' }, + { id: 6, selected: false, name: 'Product contact' }, + ] + + constructor( + private memberService: MemberService, + private fb: FormBuilder, + private alertService: AlertService, + private router: Router, + private dateUtilService: DateUtilService, + private accountService: AccountService, + protected activatedRoute: ActivatedRoute + ) {} + + ngOnInit() { + this.currentMonth = this.dateUtilService.getCurrentMonthNumber() + this.currentYear = this.dateUtilService.getCurrentYear() + this.monthList = this.dateUtilService.getMonthsList() + this.yearList = this.dateUtilService.getFutureYearsIncludingCurrent(1) + + this.accountService.getAccountData().subscribe((account) => { + if (account) { + combineLatest([this.memberService.getMemberData(account.salesforceId), this.memberService.getCountries()]) + .pipe(take(1)) + .subscribe(([data, countries]) => { + this.memberData = data + this.countries = countries + }) + } + }) + + this.editForm.valueChanges.subscribe(() => { + if (this.editForm!.status === 'VALID') { + this.invalidForm = false + } + }) + } + + createNewConsortiumMemberFromForm(): ISFNewConsortiumMember { + const consortiumMember: ISFNewConsortiumMember = { + orgName: this.editForm!.get('orgName')?.value, + trademarkLicense: this.editForm!.get('trademarkLicense')?.value, + startMonth: this.editForm!.get('startMonth')?.value, + startYear: this.editForm!.get('startYear')?.value, + emailDomain: this.editForm!.get('emailDomain')?.value, + street: this.editForm!.get('street')?.value, + city: this.editForm!.get('city')?.value, + state: + this.editForm!.get(['state'])?.value == '-- No state or province --' + ? null + : this.editForm!.get(['state'])?.value, + country: this.editForm!.get('country')?.value, + postcode: this.editForm!.get('postcode')?.value, + contactGivenName: this.editForm!.get('contactGivenName')?.value, + contactFamilyName: this.editForm!.get('contactFamilyName')?.value, + contactJobTitle: this.editForm!.get('contactJobTitle')?.value, + contactEmail: this.editForm!.get('contactEmail')?.value, + } + return consortiumMember + } + + onCountryChange(countryName: string) { + this.states = this.countries!.find((country) => country.name === countryName)?.states + } + + save() { + if (this.editForm!.status === 'INVALID') { + Object.keys(this.editForm!.controls).forEach((key) => { + this.editForm!.get(key)?.markAsDirty() + }) + this.editForm!.markAllAsTouched() + this.invalidForm = true + } else { + this.invalidForm = false + this.isSaving = true + const newConsortiumMember = this.createNewConsortiumMemberFromForm() + + this.memberService.addConsortiumMember(newConsortiumMember).subscribe( + (res) => { + if (res) { + this.onSaveSuccess() + } else { + console.error(res) + this.onSaveError() + } + }, + (err) => { + console.error(err) + this.onSaveError() + } + ) + } + } + + onSaveSuccess() { + this.isSaving = false + this.alertService.broadcast(AlertType.CONSORTIUM_MEMBER_ADDED) + this.router.navigate(['']) + } + + onSaveError() { + this.isSaving = false + } +} diff --git a/ui/src/app/home/contact/contact-update.component.html b/ui/src/app/home/contact/contact-update.component.html new file mode 100644 index 000000000..05935d26e --- /dev/null +++ b/ui/src/app/home/contact/contact-update.component.html @@ -0,0 +1,163 @@ +
+ +
+
Edit contact
+

{{ contact.name }}

+
Add new contact
+

New contact

+
+
+
+ Warning sign +
+
+ Your changes cannot be saved +
+
+ Please fix the issues with the contact form before trying to save again +
+
+
+

Contact details

+
+
+ + +
+ + Name cannot be empty + +
+ + Name is too long. Please use 80 characters or less. + +
+
+
+
+ + +
+ + Job title is too long. Please use 128 characters or less. + +
+
+
+ + + + The contact email for this person + +
+ + Please enter a valid email address, for example contactus@website.com + + + Email is too long. Please use 80 characters or less. + + + Email cannot be empty + +
+
+
+ + + + A direct contact phone for this person + + + + Phone number is too long. Please use 40 characters or less. + + +
+

Roles (Required)

+ + Please assign at least one role to this contact + +
+
+
+ + +
+
+
+
+ Warning sign +
+
+ Please note +
+
+ Changes made to organization contacts can take up to 48 hours to be reflected in the organization contact list. +
+
+
+ + + +
+
+
+
diff --git a/ui/src/app/home/contact/contact-update.component.scss b/ui/src/app/home/contact/contact-update.component.scss new file mode 100644 index 000000000..1995b8700 --- /dev/null +++ b/ui/src/app/home/contact/contact-update.component.scss @@ -0,0 +1,50 @@ +@use '../../../content/scss/bootstrap-variables' as global; + +.input-prompt { + color: global.$black-transparent; +} + +input { + font-size: 14px; + letter-spacing: 0.25px; +} + +.ng-invalid:not(form) { + border: 1px solid #d32f2f; + border-radius: 2px; +} + +.error-message { + img { + padding: 0 16px 0 8px !important; + margin-top: -1.75rem; + } + div { + font-size: 0.875rem; + } + border: 2px solid #b71c1c; + border-radius: 4px; +} + +.warning-message { + img { + padding: 0 16px 0 8px !important; + margin-top: -3rem; + } + div { + font-size: 0.875rem; + } + border: 2px solid #ff9c00; + border-radius: 4px; +} + +.role-checkbox { + height: 1.125rem; + width: 1.125rem; + margin-right: 0.5rem; + vertical-align: middle; +} + +input { + height: 40px; +} diff --git a/ui/src/app/home/contact/contact-update.component.spec.ts b/ui/src/app/home/contact/contact-update.component.spec.ts new file mode 100644 index 000000000..e51a6f73d --- /dev/null +++ b/ui/src/app/home/contact/contact-update.component.spec.ts @@ -0,0 +1,168 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { ContactUpdateComponent } from './contact-update.component' +import { AppModule } from '../../app.module' +import { MemberService } from 'src/app/member/service/member.service' +import { AccountService } from 'src/app/account' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { ActivatedRoute, Router } from '@angular/router' +import { RouterTestingModule } from '@angular/router/testing' +import { of } from 'rxjs' +import { SFMemberData } from 'src/app/member/model/salesforce-member-data.model' +import { AlertService } from 'src/app/shared/service/alert.service' +import { AlertType } from 'src/app/app.constants' + +describe('ContactUpdateComponent', () => { + let component: ContactUpdateComponent + let fixture: ComponentFixture + let memberServiceSpy: jasmine.SpyObj + let accountServiceSpy: jasmine.SpyObj + let alertServiceSpy: jasmine.SpyObj + let activatedRoute: jasmine.SpyObj + let router: jasmine.SpyObj + + beforeEach(() => { + memberServiceSpy = jasmine.createSpyObj('MemberService', ['find', 'getMemberData', 'updateContact']) + accountServiceSpy = jasmine.createSpyObj('AccountService', ['getAccountData']) + alertServiceSpy = jasmine.createSpyObj('AlertService', ['broadcast']) + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [ContactUpdateComponent], + providers: [ + { provide: MemberService, useValue: memberServiceSpy }, + { provide: AccountService, useValue: accountServiceSpy }, + { provide: AlertService, useValue: alertServiceSpy }, + ], + }) + accountServiceSpy = TestBed.inject(AccountService) as jasmine.SpyObj + memberServiceSpy = TestBed.inject(MemberService) as jasmine.SpyObj + alertServiceSpy = TestBed.inject(AlertService) as jasmine.SpyObj + router = TestBed.inject(Router) as jasmine.SpyObj + activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj + + fixture = TestBed.createComponent(ContactUpdateComponent) + component = fixture.componentInstance + fixture.detectChanges() + + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)) + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should call getAccountData and getMemberData to get contact data on init', () => { + accountServiceSpy.getAccountData.and.returnValue( + of({ + activated: true, + authorities: ['test', 'test'], + email: 'email@email.com', + firstName: 'name', + langKey: 'en', + lastName: 'surname', + imageUrl: 'url', + salesforceId: 'sfid', + loggedAs: false, + loginAs: 'sfid', + mainContact: false, + mfaEnabled: true, + }) + ) + + memberServiceSpy.getMemberData.and.returnValue( + of({ + id: 'some-id', + contacts: [ + { + memberId: 'some-id', + votingContant: false, + memberOrgRole: ['role'], + name: 'contact 1', + contactEmail: 'contact1@orcid.org', + title: 'title', + phone: '0123456789', + }, + ], + }) + ) + + component.contactId = 'contact1@orcid.org' + + component.ngOnInit() + + expect(accountServiceSpy.getAccountData).toHaveBeenCalled() + expect(memberServiceSpy.getMemberData).toHaveBeenCalled() + expect(component.contact).toBeTruthy() + }) + + it('should call memberService.updateContact when saving', () => { + component.memberData = { name: 'member' } + component.contact = { + memberId: 'some-id', + votingContant: false, + memberOrgRole: ['role'], + name: 'contact 1', + contactEmail: 'contact1@orcid.org', + title: 'title', + phone: '0123456789', + } + + memberServiceSpy.updateContact.and.returnValue(of(true)) + + // set form to valid + for (const control in component.editForm.controls) { + component.editForm.controls[control].clearAsyncValidators() + component.editForm.controls[control].clearValidators() + component.editForm.controls[control].updateValueAndValidity({ onlySelf: true }) + } + component.editForm.updateValueAndValidity() + + component.contactId = 'contact1@orcid.org' + + component.save() + + expect(memberServiceSpy.updateContact).toHaveBeenCalled() + }) + + it('should not call memberService.updateContact when saving if form is invalid', () => { + // form is invalid as we haven't set its values + component.save() + expect(memberServiceSpy.updateContact).toHaveBeenCalledTimes(0) + }) + + it('should call memberService.updateContact when deleting', () => { + component.memberData = { name: 'member' } + component.contact = { + memberId: 'some-id', + votingContant: false, + memberOrgRole: ['role'], + name: 'contact 1', + contactEmail: 'contact1@orcid.org', + title: 'title', + phone: '0123456789', + } + + memberServiceSpy.updateContact.and.returnValue(of(true)) + + // set form to valid + for (const control in component.editForm.controls) { + component.editForm.controls[control].clearAsyncValidators() + component.editForm.controls[control].clearValidators() + component.editForm.controls[control].updateValueAndValidity({ onlySelf: true }) + } + component.editForm.updateValueAndValidity() + + component.contactId = 'contact1@orcid.org' + + component.delete() + + expect(memberServiceSpy.updateContact).toHaveBeenCalled() + }) + + it('alert service and router should be called on save success', () => { + component.onSaveSuccess() + expect(alertServiceSpy.broadcast).toHaveBeenCalledWith(AlertType.CONTACT_UPDATED) + expect(router.navigate).toHaveBeenCalled() + }) +}) diff --git a/ui/src/app/member/contact-update.component.ts b/ui/src/app/home/contact/contact-update.component.ts similarity index 85% rename from ui/src/app/member/contact-update.component.ts rename to ui/src/app/home/contact/contact-update.component.ts index 3942c4d17..d1409913d 100644 --- a/ui/src/app/member/contact-update.component.ts +++ b/ui/src/app/home/contact/contact-update.component.ts @@ -1,13 +1,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core' -import { IUser } from '../user/model/user.model' -import { ISFMemberData } from './model/salesforce-member-data.model' +import { IUser } from '../../user/model/user.model' +import { ISFMemberData } from '../../member/model/salesforce-member-data.model' import { ISFMemberContact, ISFMemberContactUpdate, SFMemberContact, SFMemberContactRole, SFMemberContactUpdate, -} from './model/salesforce-member-contact.model' +} from '../../member/model/salesforce-member-contact.model' import { AbstractControl, FormArray, @@ -17,13 +17,13 @@ import { ValidatorFn, Validators, } from '@angular/forms' -import { MemberService } from './service/member.service' -import { AccountService } from '../account/service/account.service' -import { AlertService } from '../shared/service/alert.service' +import { MemberService } from '../../member/service/member.service' +import { AccountService } from '../../account/service/account.service' +import { AlertService } from '../../shared/service/alert.service' import { ActivatedRoute, Router } from '@angular/router' -import { AlertType, EMAIL_REGEXP } from '../app.constants' +import { AlertType, EMAIL_REGEXP } from '../../app.constants' import { EMPTY, Subject, combineLatest, switchMap, takeUntil } from 'rxjs' -import { IAccount } from '../account/model/account.model' +import { IAccount } from '../../account/model/account.model' @Component({ selector: 'app-contact-update', @@ -37,7 +37,6 @@ export class ContactUpdateComponent implements OnInit, OnDestroy { isSaving = false invalidForm = false routeData: any - editForm: FormGroup | undefined contactId: string | undefined managedMember: string | undefined destroy$ = new Subject() @@ -51,6 +50,28 @@ export class ContactUpdateComponent implements OnInit, OnDestroy { new SFMemberContactRole(6, false, 'Product contact'), ] + validateContactRoles: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { + const rolesArray = control as FormArray + const selectedRoles = rolesArray.controls.filter((control) => control.value.selected) + if (selectedRoles.length < 1) { + return { oneRoleSelected: true } + } + return null + } + + editForm: FormGroup = this.fb.group({ + name: [null, [Validators.required, Validators.maxLength(80)]], + phone: [null, [Validators.maxLength(40)]], + email: [null, [Validators.required, Validators.pattern(EMAIL_REGEXP), Validators.maxLength(80)]], + title: [null, [Validators.maxLength(128)]], + roles: this.fb.array( + this.rolesData.map((val: SFMemberContactRole) => + this.fb.group({ id: val.id, selected: val.selected, name: val.name }) + ), + [this.validateContactRoles] + ), + }) + constructor( private memberService: MemberService, private accountService: AccountService, @@ -70,18 +91,7 @@ export class ContactUpdateComponent implements OnInit, OnDestroy { this.memberService.setManagedMember(params['id']) } }) - this.editForm = this.fb.group({ - name: [null, [Validators.required, Validators.maxLength(80)]], - phone: [null, [Validators.maxLength(40)]], - email: [null, [Validators.required, Validators.pattern(EMAIL_REGEXP), Validators.maxLength(80)]], - title: [null, [Validators.maxLength(128)]], - roles: this.fb.array( - this.rolesData.map((val: SFMemberContactRole) => - this.fb.group({ id: val.id, selected: val.selected, name: val.name }) - ), - [this.validateContactRoles] - ), - }) + combineLatest([this.activatedRoute.params, this.accountService.getAccountData()]) .pipe( switchMap(([params, account]) => { @@ -122,15 +132,6 @@ export class ContactUpdateComponent implements OnInit, OnDestroy { }) } - validateContactRoles: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const rolesArray = control as FormArray - const selectedRoles = rolesArray.controls.filter((control) => control.value.selected) - if (selectedRoles.length < 1) { - return { oneRoleSelected: true } - } - return null - } - updateForm(contact: ISFMemberContact) { this.editForm!.patchValue({ name: contact.name, @@ -173,6 +174,8 @@ export class ContactUpdateComponent implements OnInit, OnDestroy { }) this.editForm!.markAllAsTouched() } else { + console.log('form valid') + this.invalidForm = false this.isSaving = true const contact = this.createContactFromForm() diff --git a/ui/src/app/home/home.route.ts b/ui/src/app/home/home.route.ts index 69278315b..7812febc3 100644 --- a/ui/src/app/home/home.route.ts +++ b/ui/src/app/home/home.route.ts @@ -2,10 +2,12 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, Route import { HomeComponent } from '../home/home.component' import { AuthGuard } from '../account/auth.guard' import { MemberInfoComponent } from './member-info/member-info.component' -import { Injectable, inject } from '@angular/core' +import { inject } from '@angular/core' import { MemberService } from '../member/service/member.service' import { Observable, map } from 'rxjs' import { MemberInfoEditComponent } from './member-info/member-info-edit.component' +import { AddConsortiumMemberComponent } from './consortium/add-consortium-member.component' +import { ContactUpdateComponent } from './contact/contact-update.component' export const ManageMemberGuard = (route: ActivatedRouteSnapshot): Observable | boolean => { const router = inject(Router) @@ -82,6 +84,51 @@ export const routes: Routes = [ }, canActivate: [AuthGuard], }, + { + path: 'contact/new', + component: ContactUpdateComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard, ManageMemberGuard], + }, + { + path: 'manage/:id/contact/new', + component: ContactUpdateComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard], + }, + { + path: 'contact/:contactId/edit', + component: ContactUpdateComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard, ManageMemberGuard], + }, + { + path: 'manage/:id/contact/:contactId/edit', + component: ContactUpdateComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard], + }, + { + path: 'consortium-member/new', + component: AddConsortiumMemberComponent, + data: { + authorities: ['ROLE_USER', 'ROLE_CONSORTIUM_LEAD'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard], + }, ], }, ] diff --git a/ui/src/app/member/contact-update.component.html b/ui/src/app/member/contact-update.component.html deleted file mode 100644 index 26e257c06..000000000 --- a/ui/src/app/member/contact-update.component.html +++ /dev/null @@ -1 +0,0 @@ -

contact-update works!

diff --git a/ui/src/app/member/contact-update.component.scss b/ui/src/app/member/contact-update.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/src/app/member/contact-update.component.spec.ts b/ui/src/app/member/contact-update.component.spec.ts deleted file mode 100644 index 66fe8b231..000000000 --- a/ui/src/app/member/contact-update.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' - -import { ContactUpdateComponent } from './contact-update.component' -import { AppModule } from '../app.module' - -describe('ContactUpdateComponent', () => { - let component: ContactUpdateComponent - let fixture: ComponentFixture - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [AppModule], - declarations: [ContactUpdateComponent], - }) - fixture = TestBed.createComponent(ContactUpdateComponent) - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) -}) diff --git a/ui/src/app/member/member.module.ts b/ui/src/app/member/member.module.ts index 5708326a6..62c037829 100644 --- a/ui/src/app/member/member.module.ts +++ b/ui/src/app/member/member.module.ts @@ -8,8 +8,9 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { MembersComponent } from './members.component' import { MemberUpdateComponent } from './member-update.component' import { MemberDetailComponent } from './member-detail.component' -import { MemberImportDialogComponent } from './member-import-dialog.component'; -import { ContactUpdateComponent } from './contact-update.component' +import { MemberImportDialogComponent } from './member-import-dialog.component' +import { ContactUpdateComponent } from '../home/contact/contact-update.component' +import { AddConsortiumMemberComponent } from '../home/consortium/add-consortium-member.component' @NgModule({ imports: [ @@ -20,6 +21,13 @@ import { ContactUpdateComponent } from './contact-update.component' FormsModule, ReactiveFormsModule, ], - declarations: [MembersComponent, MemberUpdateComponent, MemberDetailComponent, MemberImportDialogComponent, ContactUpdateComponent], + declarations: [ + MembersComponent, + MemberUpdateComponent, + MemberDetailComponent, + MemberImportDialogComponent, + ContactUpdateComponent, + AddConsortiumMemberComponent, + ], }) export class MemberModule {} diff --git a/ui/src/app/member/service/member.service.ts b/ui/src/app/member/service/member.service.ts index 4ed41eec6..2a348b346 100644 --- a/ui/src/app/member/service/member.service.ts +++ b/ui/src/app/member/service/member.service.ts @@ -35,6 +35,7 @@ import { } from '../model/salesforce-member-contact.model' import { ISFRawMemberOrgIds, SFMemberOrgIds } from '../model/salesforce-member-org-id.model' import { ISFMemberUpdate } from '../model/salesforce-member-update.model' +import { ISFNewConsortiumMember } from '../model/salesforce-new-consortium-member.model' @Injectable({ providedIn: 'root' }) export class MemberService { @@ -155,6 +156,20 @@ export class MemberService { this.memberData.next(memberData) } + addConsortiumMember(consortiumMember: ISFNewConsortiumMember): Observable { + return this.http + .post(`${this.resourceUrl}/members/add-consortium-member`, consortiumMember, { + observe: 'response', + }) + .pipe( + map((res: HttpResponse) => res.status === 200), + catchError((err) => { + console.log('error adding consortium member', err) + return of(false) + }) + ) + } + private fetchMemberData(salesforceId: string) { this.fetchingMemberDataState = true this.fetchSFMemberData(salesforceId)