diff --git a/src/app/pages/user-registration/user-registration-form/user-registration-form.component.ts b/src/app/pages/user-registration/user-registration-form/user-registration-form.component.ts
new file mode 100644
index 00000000..850f6fe4
--- /dev/null
+++ b/src/app/pages/user-registration/user-registration-form/user-registration-form.component.ts
@@ -0,0 +1,228 @@
+/*
+Copyright 2022-2023 University of Oxford
+and Health and Social Care Information Centre, also known as NHS Digital
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+SPDX-License-Identifier: Apache-2.0
+*/
+/* eslint-disable @typescript-eslint/unbound-method */
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { MatFormFieldAppearance } from '@angular/material/form-field';
+import { DisplayValuePair } from '@maurodatamapper/sde-resources';
+import { ToastrService } from 'ngx-toastr';
+import { StateRouterService } from 'src/app/core/state-router.service';
+import { DialogService } from 'src/app/data-explorer/dialog.service';
+
+export interface UserRegistrationFormData {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phoneNumber: string;
+ jobTitle: string;
+ orcidId: string;
+ gmcNumber: string;
+ nmcNumber: string;
+ hcpcNumber: string;
+ arNumber: string;
+ organisationId: string;
+ organisationName: string;
+ organisationLegalName: string;
+ organisationWebsite: string;
+ organisationCountryOfOrigin: string;
+ organisationType: OrganisationType;
+ organisationIsSmb: boolean;
+ departmentId: string;
+ departmentName: string;
+}
+
+export type OrganisationType = 'NOT_SELECTED' | 'UNIVERSITY' | 'HEALTHTECH';
+export interface OrganisationTypeOption {
+ value: OrganisationType;
+ displayName: 'University' | 'Healthcare technology company';
+}
+
+export const ORGANISATION_TYPE_OPTIONS: OrganisationTypeOption[] = [
+ { value: 'UNIVERSITY', displayName: 'University' },
+ { value: 'HEALTHTECH', displayName: 'Healthcare technology company' },
+];
+
+@Component({
+ selector: 'mdm-user-registration-form',
+ templateUrl: './user-registration-form.component.html',
+ styleUrls: ['./user-registration-form.component.scss'],
+})
+export class UserRegistrationFormComponent {
+ @Input() organisationOptions: DisplayValuePair[] = [];
+ @Output() formSubmitted = new EventEmitter();
+
+ formFieldAppearance: MatFormFieldAppearance = 'outline';
+ isCreatingNewOrganisation = false;
+
+ organisationTypeOptions = ORGANISATION_TYPE_OPTIONS;
+
+ registrationForm: FormGroup = this.formBuilder.group({
+ personalDetails: this.formBuilder.group({
+ firstName: ['', Validators.required],
+ lastName: ['', Validators.required],
+ email: ['', Validators.compose([Validators.required, Validators.email])],
+ phoneNumber: ['', Validators.pattern('^[- +()0-9]+$')],
+ jobTitle: ['', Validators.required],
+ orcidId: null,
+ gmcNumber: null,
+ nmcNumber: null,
+ hcpcNumber: null,
+ arNumber: null,
+
+ // TODO: Unconfirmed field.
+ // confirmations: false,
+ }),
+ organisationDetails: this.formBuilder.group({
+ organisation: ['', Validators.required],
+ organisationName: null,
+ organisationLegalName: null,
+ organisationWebsite: null,
+ organisationCountryOfOrigin: null,
+ organisationType: null,
+ organisationIsSmb: null,
+ }),
+ departmentDetails: this.formBuilder.group({
+ department: ['', Validators.required],
+ departmentName: null,
+ }),
+ });
+
+ mockedDepartments: DisplayValuePair[] = [
+ { displayValue: 'HR', value: 'hr' },
+ { displayValue: 'Engineering', value: 'engineering' },
+ { displayValue: 'Marketing', value: 'marketing' },
+ ];
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private toastr: ToastrService,
+ private dialogService: DialogService,
+ private stateRouter: StateRouterService
+ ) {}
+
+ get personalDetails() {
+ return (this.registrationForm.controls.personalDetails as FormGroup).controls;
+ }
+
+ get organisationDetails() {
+ return (this.registrationForm.controls.organisationDetails as FormGroup).controls;
+ }
+
+ get departmentDetails() {
+ return (this.registrationForm.controls.departmentDetails as FormGroup).controls;
+ }
+
+ submit(): void {
+ if (this.registrationForm.invalid) {
+ this.toastr.info('Please fill in all required fields');
+ this.logValidationErrors(this.registrationForm);
+ return;
+ }
+
+ // Gather form data and emit.
+ const formData = {
+ ...this.registrationForm.get('personalDetails')?.value,
+ ...this.registrationForm.get('organisationDetails')?.value,
+ ...this.registrationForm.get('departmentDetails')?.value,
+ } as UserRegistrationFormData;
+
+ this.formSubmitted.emit(formData);
+
+ // Show success dialog and navigate to home page.
+ this.dialogService
+ .openSuccess({
+ heading: 'Form submission successful',
+ message: 'User registration complete. Please check your email for further instructions.',
+ })
+ .afterClosed()
+ .subscribe(() => {
+ this.stateRouter.navigateTo(['/']);
+ });
+ }
+
+ logValidationErrors(group: FormGroup): void {
+ Object.keys(group.controls).forEach((key: string) => {
+ const control = group.get(key);
+ if (control instanceof FormGroup) {
+ this.logValidationErrors(control);
+ } else {
+ if (control && control.invalid) {
+ console.log(`Control: ${key}, Errors:`, control.errors);
+ }
+ }
+ });
+ }
+
+ toggleOrganisationCreation(value: boolean) {
+ this.isCreatingNewOrganisation = value;
+ if (this.isCreatingNewOrganisation) {
+ this.registrationForm.get('organisationDetails.organisation')?.clearValidators();
+ this.registrationForm.get('departmentDetails.department')?.clearValidators();
+ this.registrationForm
+ .get('organisationDetails.organisationName')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('organisationDetails.organisationLegalName')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('organisationDetails.organisationWebsite')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('organisationDetails.organisationCountryOfOrigin')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('organisationDetails.organisationType')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('organisationDetails.organisationIsSmb')
+ ?.setValidators(Validators.required);
+ this.registrationForm
+ .get('departmentDetails.departmentName')
+ ?.setValidators(Validators.required);
+ } else {
+ this.registrationForm
+ .get('organisationDetails.organisation')
+ ?.setValidators(Validators.required);
+ this.registrationForm.get('organisationDetails.organisationName')?.clearValidators();
+ this.registrationForm.get('organisationDetails.organisationLegalName')?.clearValidators();
+ this.registrationForm.get('organisationDetails.organisationWebsite')?.clearValidators();
+ this.registrationForm
+ .get('organisationDetails.organisationCountryOfOrigin')
+ ?.clearValidators();
+ this.registrationForm.get('organisationDetails.organisationType')?.clearValidators();
+ this.registrationForm.get('organisationDetails.organisationIsSmb')?.clearValidators();
+ this.registrationForm.get('departmentDetails.department')?.setValidators(Validators.required);
+ this.registrationForm.get('departmentDetails.departmentName')?.clearValidators();
+ }
+
+ this.registrationForm.get('organisationDetails.organisation')?.updateValueAndValidity();
+ this.registrationForm.get('organisationDetails.organisationName')?.updateValueAndValidity();
+ this.registrationForm
+ .get('organisationDetails.organisationLegalName')
+ ?.updateValueAndValidity();
+ this.registrationForm.get('organisationDetails.organisationWebsite')?.updateValueAndValidity();
+ this.registrationForm
+ .get('organisationDetails.organisationCountryOfOrigin')
+ ?.updateValueAndValidity();
+ this.registrationForm.get('organisationDetails.organisationType')?.updateValueAndValidity();
+ this.registrationForm.get('organisationDetails.organisationIsSmb')?.updateValueAndValidity();
+ this.registrationForm.get('departmentDetails.department')?.updateValueAndValidity();
+ this.registrationForm.get('departmentDetails.departmentName')?.updateValueAndValidity();
+ }
+}
diff --git a/src/app/secure-data-environment/pages/departments/departments.component.html b/src/app/secure-data-environment/pages/departments/departments.component.html
index c394f532..c10f42e2 100644
--- a/src/app/secure-data-environment/pages/departments/departments.component.html
+++ b/src/app/secure-data-environment/pages/departments/departments.component.html
@@ -26,6 +26,10 @@
+
+
diff --git a/src/app/secure-data-environment/pages/departments/departments.component.ts b/src/app/secure-data-environment/pages/departments/departments.component.ts
index 3ef19701..951b9d5c 100644
--- a/src/app/secure-data-environment/pages/departments/departments.component.ts
+++ b/src/app/secure-data-environment/pages/departments/departments.component.ts
@@ -53,7 +53,12 @@ export class DepartmentsComponent implements OnInit {
this.sdeDepartmentService
.getUsersDepartments()
.pipe(
+ // NOTE: Use a forkJoin to retrieve the users organisation information too. Alternatively,
+ // create a simple organisation card component.
switchMap((userDepts: UserDepartmentDTO[]) => {
+ // NOTE: It is no longer necessary that a user have a department membership. However,
+ // they will always have an organisation membership.
+ //
// Theoretically, a user should always have an department. If they don't, trigger
// a flag to show a message to the user.
if (userDepts.length === 0) {