Repository: bcgov/jag-lcrb-carla-public
Pull request #4389: LCSD-7814: Fix SEP Application Withdraw Functionality
Author: login: NickPhura
Created: 2025-04-16T23:15:43Z | Merged: 2025-04-29T19:22:30Z

## Ticket

https://jira.justice.gov.bc.ca/browse/LCSD-7814

@simranjeetgit I included some notes in a comment in this ticket. As well as some issues I noticed while working on this, and reviewing the code in general.

## Changes

### Commit 1
Fix withdraw functionality.

One thing I am not sure about is the business logic around withdrawing completed applications. Right now, you can still withdraw an application that has been approved and paid for. Which might be bad? Or maybe good, if you decide to cancel your event and want to recover one of your 24 allowed annual licenses.

All of the "real" code changes are in this commit.

### Commit 2
Add csharpier.
Add prettier.
Fix formatting of touched code. 
Remove dead code. 
Add JSDoc comments.

This commit just contains cleanup. No real functional changes.

More changes would be needed to fully incorporate proper code formatting, but this is a small step in that direction, without forcing anyone to use it.



Diff:
diff --git a/cllc-public-app/.csharpierrc b/cllc-public-app/.csharpierrc
new file mode 100644
index 0000000000..313b7c2ab4
--- /dev/null
+++ b/cllc-public-app/.csharpierrc
@@ -0,0 +1,6 @@
+{
+    "printWidth": 120,
+    "useTabs": false,
+    "tabWidth": 4,
+    "endOfLine": "lf"
+}
\ No newline at end of file
diff --git a/cllc-public-app/ClientApp/.prettierignore b/cllc-public-app/ClientApp/.prettierignore
new file mode 100644
index 0000000000..b5a6d74bd1
--- /dev/null
+++ b/cllc-public-app/ClientApp/.prettierignore
@@ -0,0 +1,18 @@
+node_modules
+
+package-lock.json
+
+dist
+build
+coverage
+
+openshift
+sonar-runner
+uploads
+
+.s2i
+.angular
+.vscode
+
+*.html
+
diff --git a/cllc-public-app/ClientApp/.prettierrc b/cllc-public-app/ClientApp/.prettierrc
new file mode 100644
index 0000000000..34db11458f
--- /dev/null
+++ b/cllc-public-app/ClientApp/.prettierrc
@@ -0,0 +1,17 @@
+{
+  "printWidth": 120,
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "none",
+  "bracketSpacing": true,
+  "bracketSameLine": true,
+  "requirePragma": false,
+  "insertPragma": false,
+  "proseWrap": "never",
+  "endOfLine": "lf",
+  "arrowParens": "always",
+  "htmlWhitespaceSensitivity": "ignore",
+  "plugins": ["prettier-plugin-organize-imports"]
+}
diff --git a/cllc-public-app/ClientApp/package-lock.json b/cllc-public-app/ClientApp/package-lock.json
index f34984daef..321d968d0e 100644
--- a/cllc-public-app/ClientApp/package-lock.json
+++ b/cllc-public-app/ClientApp/package-lock.json
@@ -5377,9 +5377,9 @@
       "dev": true
     },
     "caniuse-lite": {
-      "version": "1.0.30001712",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
-      "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==",
+      "version": "1.0.30001715",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
+      "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
       "dev": true
     },
     "caseless": {
@@ -11796,6 +11796,18 @@
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
       "dev": true
     },
+    "prettier": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+      "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+      "dev": true
+    },
+    "prettier-plugin-organize-imports": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
+      "integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
+      "dev": true
+    },
     "pretty-bytes": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
diff --git a/cllc-public-app/ClientApp/package.json b/cllc-public-app/ClientApp/package.json
index 519846cdae..97152949a9 100644
--- a/cllc-public-app/ClientApp/package.json
+++ b/cllc-public-app/ClientApp/package.json
@@ -108,6 +108,8 @@
     "karma-jasmine-html-reporter": "^1.7.0",
     "karma-junit-reporter": "^1.2.0",
     "postcss": "^8.4.49",
+    "prettier": "^3.5.3",
+    "prettier-plugin-organize-imports": "^4.1.0",
     "protractor": "~7.0.0",
     "protractor-html-reporter": "^1.3.2",
     "ts-node": "~7.0.0",
diff --git a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.html b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.html
index 30c1ece13b..6e958d883c 100644
--- a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.html
+++ b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.html
@@ -1,131 +1,96 @@
-
-
 <div class="row mb-3">
-    <div style="width:100%;">
-      <table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z0 w-100" matSortActive="eventStartDate" matSortDirection="desc">
-        <!--- Note that these columns can be defined in any order.
-          The actual rendered columns are set as a property on the row definition" -->
-
-
-        <!-- Submitted Date -->
-        <ng-container matColumnDef="dateSubmitted">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Submitted Date</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">
-            {{row.dateSubmitted | date: 'dd-MMM-yyyy HH:mm:ss'}}
-          </td>
-        </ng-container>
-
-        <!-- Event Name -->
-        <ng-container matColumnDef="eventName">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Event Name</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">
-            <!--a [href]="api/special-events/applicant/' + row.specialEventId">Open Application</a-->
-            <button mat-button color="primary" (click)="openApplication(row)">{{row.eventName ? row.eventName : "Unnamed Event"}}</button>
-          </td>
-        </ng-container>
-
-        <!-- Start Date -->
-        <ng-container matColumnDef="eventStartDate">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Start Date</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">
-            {{row.eventStartDate | date: 'MMM dd, yyyy'}}
-          </td>
-        </ng-container>
-
-        <!-- Status -->
-        <ng-container matColumnDef="eventStatusLabel">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header style="min-width:150px;">Status</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">
-            <fa-icon [icon]="getStatusIcon(getStatus(row))" style="margin-right: 10px;"></fa-icon><strong>{{row.eventStatus ? getStatus(row) : "Draft"}}</strong>
-            <!--span *ngIf="row.eventStatus === 'Issued'" style="color: green; font-size:14px; font-weight:600;">
-              <fa-icon [icon]="getStatusIcon(row.eventStatus)" style="margin-right: 10px;"></fa-icon>{{row.eventStatus ? row.eventStatus : "Draft"}}
-            </span>
-            <span *ngIf="row.eventStatus === 'Approved' && !isEventPast(row.eventStartDate)" style='font-size: 14px; color: #fcba19; font-weight:600;'>
-              <fa-icon [icon]="getStatusIcon(row.eventStatus)" style="margin-right: 10px;"></fa-icon>Payment Required
-            </span>
-            <span *ngIf="row.eventStatus === 'Approved' && isEventPast(row.eventStartDate)" style='font-size: 14px; color: red; font-weight:600;'>
-              <fa-icon [icon]="faBan" style="margin-right: 10px;"></fa-icon>Approval Expired
-            </span>
-            <span *ngIf="row.eventStatus !== 'Issued' && row.eventStatus != 'Approved' && row.eventStatus != 'Pending Review'" style='font-size: 14px; font-weight:600;'>
-              <fa-icon [icon]="getStatusIcon(row.eventStatus)" style="margin-right: 10px;"></fa-icon>{{row.eventStatus ? row.eventStatus : "Draft"}}
-            </span>
-            <span *ngIf="row.eventStatus != 'Pending Review' && row.eventStatus != 'Approved' && !isEventPast(row.eventStartDate)" style='font-size: 14px; font-weight:600;'>
-              <fa-icon [icon]="getStatusIcon(row.eventStatus)" style="margin-right: 10px;"></fa-icon>{{row.eventStatus ? row.eventStatus : "Draft"}}
-            </span>
-            <span *ngIf="row.eventStatus == 'Pending Review' && isEventPast(row.eventStartDate)" style='font-size: 14px; color: red; font-weight:600;'>
-              <fa-icon [icon]="faBan" style="margin-right: 10px;"></fa-icon>Review Expired
-            </span-->
-          </td>
-        </ng-container>
-
-
-        <!-- Size -->
-        <ng-container matColumnDef="maximumNumberOfGuests">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Size</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">{{row.maximumNumberOfGuests}}</td>
-        </ng-container>
-
-        <!-- City
-        <ng-container matColumnDef="sepCity">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">{{row.sepCity.name}}</td>
-        </ng-container>
-        -->
-        <!-- Type -->
-        <ng-container matColumnDef="typeOfEventLabel">
-          <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
-          <td mat-cell *matCellDef="let row" class="mat-body-1">{{row.typeOfEventLabel}}</td>
-        </ng-container>
-
-        <!-- Actions -->
-        <ng-container matColumnDef="actions">
-          <th mat-header-cell *matHeaderCellDef>Action</th>
-          <td mat-cell *matCellDef="let row" class="p-0" style="padding: 10px !important; ">
-            <span style="margin-bottom:5px;">
-              <!--button mat-raised-button (click)="payNow(row.specialEventId)" *ngIf="row.eventStatus ==='Approved' && row.invoiceId && !row.isInvoicePaid"><fa-icon [icon]="faShoppingCart" style="margin-right: 10px;"></fa-icon>Pay Now</button-->
-
-            <!-- instead of having them pay from the summary, we should have them open the app to pay it there.-->
-            <a (click)="openApplication(row)" *ngIf="row.eventStatus ==='Approved' && row.invoiceId && !row.isInvoicePaid && !isEventPast(row.eventStartDate)" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faShoppingCart" style="margin-right: 10px;"></fa-icon>Pay Now</a>
-
-            <!--button mat-button color="primary" (click)="openApplication(row)">{{row.eventName ? row.eventName : "Unnamed Event"}}</button-->
+  <div style="width:100%;">
+    <table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z0 w-100" matSortActive="eventStartDate" matSortDirection="desc">
+      <!--- Note that these columns can be defined in any order.
+      The actual rendered columns are set as a property on the row definition" -->
+
+      <!-- Submitted Date -->
+      <ng-container matColumnDef="dateSubmitted">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header>Submitted Date</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">
+          {{row.dateSubmitted | date: 'dd-MMM-yyyy HH:mm:ss'}}
+        </td>
+      </ng-container>
+
+      <!-- Event Name -->
+      <ng-container matColumnDef="eventName">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header>Event Name</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">
+          <button mat-button color="primary" (click)="openApplication(row)">{{row.eventName ? row.eventName : "Unnamed Event"}}</button>
+        </td>
+      </ng-container>
+
+      <!-- Start Date -->
+      <ng-container matColumnDef="eventStartDate">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header>Start Date</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">
+          {{row.eventStartDate | date: 'MMM dd, yyyy'}}
+        </td>
+      </ng-container>
+
+      <!-- Status -->
+      <ng-container matColumnDef="eventStatusLabel">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header style="min-width:150px;">Status</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">
+          <fa-icon [icon]="getStatusIcon(getStatus(row))" style="margin-right: 10px;"></fa-icon><strong>{{row.eventStatus ? getStatus(row) : "Draft"}}</strong>
+        </td>
+      </ng-container>
+
+
+      <!-- Size -->
+      <ng-container matColumnDef="maximumNumberOfGuests">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header>Size</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">{{row.maximumNumberOfGuests}}</td>
+      </ng-container>
+
+      <!-- Type -->
+      <ng-container matColumnDef="typeOfEventLabel">
+        <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
+        <td mat-cell *matCellDef="let row" class="mat-body-1">{{row.typeOfEventLabel}}</td>
+      </ng-container>
+
+      <!-- Actions -->
+      <ng-container matColumnDef="actions">
+        <th mat-header-cell *matHeaderCellDef>Action</th>
+        <td mat-cell *matCellDef="let row" class="p-0" style="padding: 10px !important; ">
+          <span style="margin-bottom:5px;">
+          <!-- instead of having them pay from the summary, we should have them open the app to pay it there.-->
+          <a (click)="openApplication(row)" *ngIf="row.eventStatus ==='Approved' && row.invoiceId && !row.isInvoicePaid && !isEventPast(row.eventStartDate)" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faShoppingCart" style="margin-right: 10px;"></fa-icon>Pay Now</a>
+
+          <!--button mat-button color="primary" (click)="openApplication(row)">{{row.eventName ? row.eventName : "Unnamed Event"}}</button-->
+          <br>
+          </span>
+          <span *ngIf="row.specialEventId && row.eventStatus === 'Issued'" style="margin-bottom:5px;">
+            <a [href]="'api/special-events/applicant/' + row.specialEventId + '/permit/summary.pdf'" target="_blank"><fa-icon [icon]="faCertificate" style="margin-right: 10px;"></fa-icon>Print Permit</a>
+            <br>
+          </span>
+          <span *ngIf="row.specialEventId" style="margin-bottom:5px;">
+            <a [href]="'api/special-events/applicant/' + row.specialEventId + '/summary/summary.pdf'" target="_blank"><fa-icon [icon]="faDownload" style="margin-right: 10px;"></fa-icon>Print Summary</a>
+            <br>
+          </span>
+          <span style="margin-bottom:5px;">
+            <a (click)="cancelApplication(row)" target="_blank" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faCopy" style="margin-right: 10px;"></fa-icon>Withdraw</a>
             <br>
-            </span>
-            <span *ngIf="row.specialEventId && row.eventStatus === 'Issued'" style="margin-bottom:5px;">
-              <a [href]="'api/special-events/applicant/' + row.specialEventId + '/permit/summary.pdf'" target="_blank"><fa-icon [icon]="faCertificate" style="margin-right: 10px;"></fa-icon>Print Permit</a>
-              <br>
-            </span>
-            <span *ngIf="row.specialEventId" style="margin-bottom:5px;">
-              <a [href]="'api/special-events/applicant/' + row.specialEventId + '/summary/summary.pdf'" target="_blank"><fa-icon [icon]="faDownload" style="margin-right: 10px;"></fa-icon>Print Summary</a>
-              <br>
-            </span>
-            <span style="margin-bottom:5px;">
-              <a (click)="cancelApplication(row)" target="_blank" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faCopy" style="margin-right: 10px;"></fa-icon>Withdraw</a>
-              <br>
-            </span>
+          </span>
 
-            <span style="margin-bottom:5px;">
-              <a (click)="cloneApplication(row)" target="_blank" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faCopy" style="margin-right: 10px;"></fa-icon>Create a Copy</a>
-            </span>
-            
-          </td>
-        </ng-container>
+          <span style="margin-bottom:5px;">
+            <a (click)="cloneApplication(row)" target="_blank" style="text-decoration: underline; color:#1a5a96; cursor: pointer;"><fa-icon [icon]="faCopy" style="margin-right: 10px;"></fa-icon>Create a Copy</a>
+          </span>
 
-        <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
-        <tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
-      </table>
-    </div>
-  </div>
+        </td>
+      </ng-container>
 
-  <div class="row">
-    <!--div class="col-3 d-flex align-items-center">
-      <button mat-raised-button color="primary" >Assign</button>
-    </div-->
-    <div class="col-2 d-flex align-items-center">
-      <span>Displaying <strong>{{dataSource?.data?.length}}</strong> application form(s)</span>
-    </div>
-    <div class="col-7">
-      <mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
-    </div>
+      <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
+      <tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
+    </table>
   </div>
+</div>
 
-
+<div class="row">
+  <div class="col-2 d-flex align-items-center">
+    <span>Displaying <strong>{{dataSource?.data?.length}}</strong> application form(s)</span>
+  </div>
+  <div class="col-7">
+    <mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
+  </div>
+</div>
diff --git a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.spec.ts b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.spec.ts
index ffba73ed07..3526ec7772 100644
--- a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.spec.ts
+++ b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.spec.ts
@@ -1,16 +1,15 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, TestBed } from "@angular/core/testing";
 
-import { SubmittedApplicationsComponent } from './submitted-applications.component';
+import { SubmittedApplicationsComponent } from "./submitted-applications.component";
 
-describe('SubmittedApplicationsComponent', () => {
+describe("SubmittedApplicationsComponent", () => {
   let component: SubmittedApplicationsComponent;
   let fixture: ComponentFixture<SubmittedApplicationsComponent>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [ SubmittedApplicationsComponent ]
-    })
-    .compileComponents();
+      declarations: [SubmittedApplicationsComponent],
+    }).compileComponents();
   });
 
   beforeEach(() => {
@@ -19,7 +18,7 @@ describe('SubmittedApplicationsComponent', () => {
     fixture.detectChanges();
   });
 
-  it('should create', () => {
+  it("should create", () => {
     expect(component).toBeTruthy();
   });
 });
diff --git a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.ts b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.ts
index 09bdc74b0c..60e3e2e42e 100644
--- a/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.ts
+++ b/cllc-public-app/ClientApp/src/app/components/sep/my-applications/submitted-applications/submitted-applications.component.ts
@@ -1,25 +1,23 @@
 import { Component, Input, OnInit, ViewChild } from "@angular/core";
+import { MatDialog } from "@angular/material/dialog";
 import { MatPaginator } from "@angular/material/paginator";
 import { MatSnackBar } from "@angular/material/snack-bar";
 import { MatSort } from "@angular/material/sort";
 import { MatTableDataSource } from "@angular/material/table";
+import { Router } from "@angular/router";
 import { PoliceTableElement } from "@components/police-representative/police-table-element";
-import { SepApplicationSummary } from "@models/sep-application-summary.model";
-import { SepApplication } from "@models/sep-application.model";
-import { PaymentDataService } from "@services/payment-data.service";
-import { IndexedDBService } from "@services/indexed-db.service";
-import { SpecialEventsDataService } from "@services/special-events-data.service";
-import { map } from "rxjs/operators";
-import { isBefore } from "date-fns";
+import { CancelSepApplicationDialogComponent } from "@components/sep/sep-application/cancel-sep-application-dialog/cancel-sep-application-dialog.component";
+import { SEP_APPLICATION_STEPS } from "@components/sep/sep-application/sep-application.component";
 import {
   faAward,
-  faCopy,
+  faBan,
   faBirthdayCake,
   faBolt,
   faBusinessTime,
   faCalendarAlt,
   faCertificate,
   faCheck,
+  faCopy,
   faDownload,
   faExchangeAlt,
   faExclamationTriangle,
@@ -29,20 +27,20 @@ import {
   faShoppingCart,
   faStopwatch,
   faTrashAlt,
-  IconDefinition
-} from "@fortawesome/free-solid-svg-icons";
-import {
-  faBan
+  IconDefinition,
 } from "@fortawesome/free-solid-svg-icons";
-import { SEP_APPLICATION_STEPS } from "@components/sep/sep-application/sep-application.component";
-import { Router } from "@angular/router";
-import { MatDialog } from "@angular/material/dialog";
-import { CancelSepApplicationDialogComponent } from "@components/sep/sep-application/cancel-sep-application-dialog/cancel-sep-application-dialog.component";
+import { SepApplicationSummary } from "@models/sep-application-summary.model";
+import { SepApplication } from "@models/sep-application.model";
+import { IndexedDBService } from "@services/indexed-db.service";
+import { PaymentDataService } from "@services/payment-data.service";
+import { SpecialEventsDataService } from "@services/special-events-data.service";
+import { isBefore } from "date-fns";
+import { map } from "rxjs/operators";
 
 @Component({
   selector: "app-submitted-applications",
   templateUrl: "./submitted-applications.component.html",
-  styleUrls: ["./submitted-applications.component.scss"]
+  styleUrls: ["./submitted-applications.component.scss"],
 })
 export class SubmittedApplicationsComponent implements OnInit {
   faDownload = faDownload;
@@ -78,204 +76,272 @@ export class SubmittedApplicationsComponent implements OnInit {
 
   // angular material table columns to display
   columnsToDisplay = [
-    "eventStatusLabel", "eventName", "eventStartDate", "dateSubmitted", "actions"
+    "eventStatusLabel",
+    "eventName",
+    "eventStartDate",
+    "dateSubmitted",
+    "actions",
   ];
 
-  constructor(private sepDataService: SpecialEventsDataService,
+  constructor(
+    private sepDataService: SpecialEventsDataService,
     private snackBar: MatSnackBar,
     public dialog: MatDialog,
     private db: IndexedDBService,
     private router: Router,
-    private paymentDataService: PaymentDataService) { }
+    private paymentDataService: PaymentDataService
+  ) {}
 
   ngOnInit(): void {
-    this.sepDataService.getSubmittedApplications()
-      .subscribe(data => this.dataSource.data = data);
-    if(this.dataSource){
+    this.sepDataService
+      .getSubmittedApplications()
+      .subscribe((data) => (this.dataSource.data = data));
+    if (this.dataSource) {
       this.dataSource.paginator = this.paginator;
       this.dataSource.sort = this.sort;
     }
   }
 
-
+  /**
+   * Navigate to the application page.
+   *
+   * @param {SepApplicationSummary} app
+   * @memberof SubmittedApplicationsComponent
+   */
   openApplication(app: SepApplicationSummary) {
     if (app.eventStatus === "Draft") {
-      this.router.navigateByUrl(`sep/application/${app.localId}/${this.getLastStep(app.lastStepCompleted)}`);
+      this.router.navigateByUrl(
+        `sep/application/${app.localId}/${this.getLastStep(
+          app.lastStepCompleted
+        )}`
+      );
     } else {
-      this.router.navigateByUrl(`sep/application-summary/${app.specialEventId}`);
+      this.router.navigateByUrl(
+        `sep/application-summary/${app.specialEventId}`
+      );
     }
   }
 
-
-
+  /**
+   * Get the last step completed for the application.
+   *
+   * @param {string} stepCompleted
+   * @return {*}  {string}
+   * @memberof SubmittedApplicationsComponent
+   */
   getLastStep(stepCompleted: string): string {
     const lastIndex = SEP_APPLICATION_STEPS.indexOf(stepCompleted);
     // return the next step to be completed
     return SEP_APPLICATION_STEPS[lastIndex + 1];
   }
 
-  payNow(applicationId: string) {
-    // and payment is required due to an invoice being generated
-    if (applicationId) {
-      // proceed to payment
-      this.submitPayment(applicationId)
-        .subscribe(res => {
-        },
-          error => {
-            this.snackBar.open("Error submitting payment", "Fail", { duration: 3500, panelClass: ["red-snackbar"] });
-          }
-        );
-    }
-  }
-
-  isEventPast(eventStartDate: string) {
-    return isBefore(new Date(eventStartDate), new Date() );
+  /**
+   * Returns true if the event start date is before the current date.
+   *
+   * @param {string} eventStartDate
+   * @return {*}  {boolean}
+   * @memberof SubmittedApplicationsComponent
+   */
+  isEventPast(eventStartDate: string): boolean {
+    return isBefore(new Date(eventStartDate), new Date());
   }
 
+  /**
+   * Get the human readable status of the application.
+   *
+   * @param {SepApplication} app
+   * @return {*}  {string}
+   * @memberof SubmittedApplicationsComponent
+   */
   getStatus(app: SepApplication): string {
     // when an application happens in the past, sometimes we need to change the status in the front end.
-      switch(app.eventStatus) {
-        case ("Pending Review"):
-          if(this.isEventPast(String(app.eventStartDate))){
-            return "Review Expired"
-          } else {
-            return app.eventStatus;
-          }
-        case ("Approved"):
-          if(this.isEventPast(String(app.eventStartDate))){
-            return "Approval Expired"
-          } else {
-            return "Payment Required";
-          }
-        default:
-          return app.eventStatus;
-      }
+    const isPast = this.isEventPast(String(app.eventStartDate));
+
+    if (app.eventStatus === "Pending Review") {
+      return isPast ? "Review Expired" : app.eventStatus;
+    }
+
+    if (app.eventStatus === "Approved") {
+      return isPast ? "Approval Expired" : "Payment Required";
+    }
+
+    return app.eventStatus;
   }
 
+  /**
+   * Get the icon for the status of the application.
+   *
+   * @param {string} status
+   * @return {*}  {IconDefinition}
+   * @memberof SubmittedApplicationsComponent
+   */
   getStatusIcon(status: string): IconDefinition {
     switch (status) {
-      case ("Pending Review"):
+      case "Pending Review":
         return faStopwatch;
-      case ("Draft"):
+      case "Draft":
         return faPencilAlt;
-      case ("Approved"):
-      case ("Payment Required"):
-      case ("Reviewed"):
+      case "Approved":
+      case "Payment Required":
+      case "Reviewed":
         return faCheck;
-      case ("Issued"):
+      case "Issued":
         return faAward;
-      case ("Denied"):
-      case ("Cancelled"):
+      case "Denied":
+      case "Cancelled":
       default:
         return faBan;
     }
   }
 
+  /**
+   * Clone an application.
+   *
+   * @param {SepApplicationSummary} appSummary
+   * @memberof SubmittedApplicationsComponent
+   */
   async cloneApplication(appSummary: SepApplicationSummary) {
-
     // first get the full application.
-    this.sepDataService.getSpecialEventForApplicant(appSummary.specialEventId)
-      .subscribe(async app => {
+    this.sepDataService
+      .getSpecialEventForApplicant(appSummary.specialEventId)
+      .subscribe(async (app) => {
         const clone = { ...app };
         // clear dynamics IDs
         clone.id = undefined;
         clone.localId = undefined;
-        clone.eventStatus= "Draft";
+        clone.eventStatus = "Draft";
 
         // ensure the police field are cleared
         clone.policeDecisionBy = undefined;
         clone.policeApproval = undefined;
 
         if (clone?.eventLocations?.length > 0) {
-          clone.eventLocations.forEach(loc => {
+          clone.eventLocations.forEach((loc) => {
             loc.id = undefined;
             if (loc?.serviceAreas?.length > 0) {
-              loc.serviceAreas.forEach(area => {
+              loc.serviceAreas.forEach((area) => {
                 area.id = undefined;
               });
             }
             if (loc?.eventDates?.length > 0) {
-              loc.eventDates.forEach(ed => {
+              loc.eventDates.forEach((ed) => {
                 ed.id = undefined;
               });
             }
           });
         }
-    
+
         const localId = await this.db.saveSepApplication({
           ...clone,
           dateAgreedToTsAndCs: undefined,
           isAgreeTsAndCs: false,
-          dateCreated: new Date()
+          dateCreated: new Date(),
         } as SepApplication);
         this.router.navigateByUrl(`/sep/application/${localId}/applicant`);
-
-
       });
-
-    
   }
 
-
-  async cancelApplication(appSummary: SepApplicationSummary): Promise<void> {
-
-    // open dialog, get reference and process returned data from dialog
+  /**
+   * Cancel (withdraw) an application.
+   *
+   * @param {(SepApplicationSummary | SepApplication)} appSummary
+   * @return {*}  {Promise<void>}
+   * @memberof SubmittedApplicationsComponent
+   */
+  async cancelApplication(
+    appSummary: SepApplicationSummary | SepApplication
+  ): Promise<void> {
     const dialogConfig = {
       disableClose: true,
       autoFocus: true,
       width: "600px",
       height: "500px",
       data: {
-        showStartApp: false
-      }
+        showStartApp: false,
+      },
     };
 
-    const dialogRef = this.dialog.open(CancelSepApplicationDialogComponent, dialogConfig);
-    dialogRef.afterClosed()  
-      .subscribe(async ([cancelApplication, reason]) => {
-      if (cancelApplication) {
-        if (appSummary.specialEventId)
-        {
-          const result = await this.sepDataService.updateSepApplication({ id: appSummary.specialEventId, cancelReason: reason, eventStatus: "Cancelled" } as SepApplication, appSummary.specialEventId)
+    const dialogRef = this.dialog.open(
+      CancelSepApplicationDialogComponent,
+      dialogConfig
+    );
+
+    dialogRef.afterClosed().subscribe(async ([cancelApplication, reason]) => {
+      if (cancelApplication !== true) {
+        return;
+      }
+
+      const applicationId = this.isSepApplicationSummary(appSummary)
+        ? appSummary.specialEventId
+        : appSummary.id;
+
+      if (applicationId) {
+        // If this application was persisted (submitted or persisted draft), update the application with a cancelled
+        // status/reason.
+        await this.sepDataService
+          .updateSepApplication(
+            {
+              id: applicationId,
+              cancelReason: reason,
+              eventStatus: "Cancelled",
+            } as SepApplication,
+            applicationId
+          )
           .toPromise();
+      }
 
-          if (appSummary.localId) {
-            await this.db.applications.update(+appSummary.localId, result);    
-          }
-          this.router.navigateByUrl(`/sep/my-applications`)
-          .then(() => {
-            window.location.reload();
-          });
-        }
-    }
-  });
-}
+      // `localId` can be 0 so explicitly check for undefined or null
+      if (appSummary.localId !== undefined && appSummary.localId !== null) {
+        // If this application was cached locally (submitted or persisted draft or local draft), remove it from local
+        // storage
+        await this.db.applications.delete(Number(appSummary.localId));
+      }
 
-  // async getApplications() {
-  //   let applications = await this.db.applications.toArray();
-  //   applications = applications.filter(app => app.eventStatus === 'Draft');
-  //   applications = applications.sort((a, b) => {
-  //     const dateA = new Date(a.dateCreated).getTime();
-  //     const dateB = new Date(b.dateCreated).getTime();
-  //     return dateB - dateA;
-  //   });
-  //   this.applications = applications;
-  // }
+      this.router.navigateByUrl(`/sep/my-applications`).then(() => {
+        window.location.reload();
+      });
+    });
+  }
 
   /**
- * Redirect to payment processing page (Express Pay / Bambora service)
- * */
-
+   * Redirect to payment processing page (Express Pay / Bambora service)
+   *
+   * @private
+   * @param {string} applicationId
+   * @return {*}
+   * @memberof SubmittedApplicationsComponent
+   */
   private submitPayment(applicationId: string) {
-    return this.paymentDataService.getPaymentURI("specialEventInvoice", applicationId)
-      .pipe(map(jsonUrl => {
-        window.location.href = jsonUrl["url"];
-        return jsonUrl["url"];
-      }, (err: any) => {
-        if (err === "Payment already made") {
-          this.snackBar.open("Application payment has already been made, please refresh the page.", "Fail", { duration: 3500, panelClass: ["red-snackbar"] });
-        }
-      }));
+    return this.paymentDataService
+      .getPaymentURI("specialEventInvoice", applicationId)
+      .pipe(
+        map(
+          (jsonUrl) => {
+            window.location.href = jsonUrl["url"];
+            return jsonUrl["url"];
+          },
+          (err: any) => {
+            if (err === "Payment already made") {
+              this.snackBar.open(
+                "Application payment has already been made, please refresh the page.",
+                "Fail",
+                { duration: 3500, panelClass: ["red-snackbar"] }
+              );
+            }
+          }
+        )
+      );
   }
 
+  /**
+   * Type guard to check if the application is a SepApplicationSummary.
+   *
+   * @param {(SepApplication | SepApplicationSummary)} app
+   * @memberof SubmittedApplicationsComponent
+   */
+  isSepApplicationSummary = (
+    app: SepApplication | SepApplicationSummary
+  ): app is SepApplicationSummary => {
+    return (app as SepApplicationSummary).specialEventId !== undefined;
+  };
 }
diff --git a/cllc-public-app/Controllers/SpecialEventsController.cs b/cllc-public-app/Controllers/SpecialEventsController.cs
index cf8a88129e..920630ca97 100644
--- a/cllc-public-app/Controllers/SpecialEventsController.cs
+++ b/cllc-public-app/Controllers/SpecialEventsController.cs
@@ -1237,12 +1237,20 @@ public IActionResult UpdateSpecialEvent(string eventId, [FromBody] ViewModels.Sp
 
             UserSettings userSettings = UserSettings.CreateFromHttpContext(_httpContextAccessor);
             var existingEvent = GetSpecialEventData(eventId);
-           
-            if((existingEvent.Statuscode != (int?)EventStatus.Draft)){
-                  return BadRequest("Updating a special event is only allowed in Draft status.");
+
+            if (
+                specialEvent.EventStatus != EventStatus.Cancelled
+                && existingEvent.Statuscode != (int?)EventStatus.Draft
+            )
+            {
+                // Only allow drafts to be updated, unless the update action is a cancellation
+                return BadRequest("Updating a special event is only allowed in Draft status.");
             }
-            if (existingEvent._adoxioAccountidValue != userSettings.AccountId &&
-               existingEvent._adoxioContactidValue != userSettings.ContactId)
+
+            if (
+                existingEvent._adoxioAccountidValue != userSettings.AccountId
+                && existingEvent._adoxioContactidValue != userSettings.ContactId
+            )
             {
                 return Unauthorized();
             }
@@ -1264,10 +1272,11 @@ public IActionResult UpdateSpecialEvent(string eventId, [FromBody] ViewModels.Sp
             patchEvent.CopyValues(specialEvent);
 
             // Only allow these status to be set by the portal. Any other status change is ignored
-            if (specialEvent.EventStatus == EventStatus.Cancelled ||
-                specialEvent.EventStatus == EventStatus.Draft ||
-                specialEvent.EventStatus == EventStatus.Submitted
-               )
+            if (
+                specialEvent.EventStatus == EventStatus.Cancelled
+                || specialEvent.EventStatus == EventStatus.Draft
+                || specialEvent.EventStatus == EventStatus.Submitted
+            )
             {
                 patchEvent.Statuscode = (int?)specialEvent.EventStatus;
             }
@@ -1288,106 +1297,137 @@ public IActionResult UpdateSpecialEvent(string eventId, [FromBody] ViewModels.Sp
                 throw httpOperationException;
             }
 
- 
-
-
-
             if (specialEvent.EventLocations?.Count > 0)
             {
                 // patchEvent.AdoxioSpecialeventSpecialeventlocations = new List<MicrosoftDynamicsCRMadoxioSpecialeventlocation>();
                 // add locations to the new special event
-                specialEvent.EventLocations.ForEach((Action<ViewModels.SepEventLocation>)(location =>
-                {
-                    var newLocation = new MicrosoftDynamicsCRMadoxioSpecialeventlocation();
-                    newLocation.CopyValues(location);
-                    newLocation.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI("adoxio_specialevents", specialEvent.Id);
-                    try
-                    {
-                        if (string.IsNullOrEmpty(location.Id))
-                        { // create record
-                            newLocation = _dynamicsClient.Specialeventlocations.Create(newLocation);
-                            location.Id = newLocation.AdoxioSpecialeventlocationid;
-                        }
-                        else
-                        { // update record
-                            _dynamicsClient.Specialeventlocations.Update(location.Id, newLocation);
-                        }
-
-                        // Add service areas to new location
-                        if (location.ServiceAreas?.Count > 0)
+                specialEvent.EventLocations.ForEach(
+                    (Action<ViewModels.SepEventLocation>)(
+                        location =>
                         {
-                            newLocation.AdoxioSpecialeventlocationLicencedareas = new List<MicrosoftDynamicsCRMadoxioSpecialeventlicencedarea>();
-                            location.ServiceAreas.ForEach((Action<ViewModels.SepServiceArea>)(area =>
+                            var newLocation = new MicrosoftDynamicsCRMadoxioSpecialeventlocation();
+                            newLocation.CopyValues(location);
+                            newLocation.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI(
+                                "adoxio_specialevents",
+                                specialEvent.Id
+                            );
+                            try
                             {
-                                var newArea = new MicrosoftDynamicsCRMadoxioSpecialeventlicencedarea();
-                                newArea.CopyValues(area);
-                                newArea.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI("adoxio_specialevents", specialEvent.Id);
-                                newArea.AdoxioSpecialEventLocationODataBind = _dynamicsClient.GetEntityURI("adoxio_specialeventlocations", location.Id);
-                                try
-                                {
-                                    if (string.IsNullOrEmpty((string)area.Id))
-                                    { // create record
-                                        newArea = _dynamicsClient.Specialeventlicencedareas.Create(newArea);
-                                        area.Id = newArea.AdoxioSpecialeventlicencedareaid;
-                                    }
-                                    else
-                                    { // update record
-                                        _dynamicsClient.Specialeventlicencedareas.Update((string)area.Id, newArea);
-                                        newArea.AdoxioSpecialeventlicencedareaid = area.Id;
-                                    }
+                                if (string.IsNullOrEmpty(location.Id))
+                                { // create record
+                                    newLocation = _dynamicsClient.Specialeventlocations.Create(newLocation);
+                                    location.Id = newLocation.AdoxioSpecialeventlocationid;
                                 }
-                                catch (HttpOperationException httpOperationException)
-                                {
-                                    _logger.LogError(httpOperationException, "Error creating/updating special event location");
-                                    throw httpOperationException;
+                                else
+                                { // update record
+                                    _dynamicsClient.Specialeventlocations.Update(location.Id, newLocation);
                                 }
-                            }));
-                        }
 
-                        // Add event dates to the location
-                        if (location.EventDates?.Count > 0)
-                        {
-                            location.EventDates.ForEach(dates =>
-                            {
-                                var newDates = new MicrosoftDynamicsCRMadoxioSpecialeventschedule();
-                                newDates.CopyValues(dates);
-                                try
+                                // Add service areas to new location
+                                if (location.ServiceAreas?.Count > 0)
                                 {
-                                    if (string.IsNullOrEmpty(dates.Id))
-                                    { // create record
-                                        newDates.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI("adoxio_specialevents", specialEvent.Id);
-                                        newDates.AdoxioSpecialEventLocationODataBind = _dynamicsClient.GetEntityURI("adoxio_specialeventlocations", location.Id);
-                                        newDates = _dynamicsClient.Specialeventschedules.Create(newDates);
-                                        dates.Id = newDates.AdoxioSpecialeventscheduleid;
-                                    }
-                                    else
-                                    { // update record
-                                        _dynamicsClient.Specialeventschedules.Update(dates.Id, newDates);
-                                    }
+                                    newLocation.AdoxioSpecialeventlocationLicencedareas =
+                                        new List<MicrosoftDynamicsCRMadoxioSpecialeventlicencedarea>();
+                                    location.ServiceAreas.ForEach(
+                                        (Action<ViewModels.SepServiceArea>)(
+                                            area =>
+                                            {
+                                                var newArea = new MicrosoftDynamicsCRMadoxioSpecialeventlicencedarea();
+                                                newArea.CopyValues(area);
+                                                newArea.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI(
+                                                    "adoxio_specialevents",
+                                                    specialEvent.Id
+                                                );
+                                                newArea.AdoxioSpecialEventLocationODataBind =
+                                                    _dynamicsClient.GetEntityURI(
+                                                        "adoxio_specialeventlocations",
+                                                        location.Id
+                                                    );
+                                                try
+                                                {
+                                                    if (string.IsNullOrEmpty((string)area.Id))
+                                                    { // create record
+                                                        newArea = _dynamicsClient.Specialeventlicencedareas.Create(newArea);
+                                                        area.Id = newArea.AdoxioSpecialeventlicencedareaid;
+                                                    }
+                                                    else
+                                                    { // update record
+                                                        _dynamicsClient.Specialeventlicencedareas.Update(
+                                                            (string)area.Id,
+                                                            newArea
+                                                        );
+                                                        newArea.AdoxioSpecialeventlicencedareaid = area.Id;
+                                                    }
+                                                }
+                                                catch (HttpOperationException httpOperationException)
+                                                {
+                                                    _logger.LogError(
+                                                        httpOperationException,
+                                                        "Error creating/updating special event location"
+                                                    );
+                                                    throw httpOperationException;
+                                                }
+                                            }
+                                        )
+                                    );
                                 }
-                                catch (HttpOperationException httpOperationException)
+
+                                // Add event dates to the location
+                                if (location.EventDates?.Count > 0)
                                 {
-                                    _logger.LogError(httpOperationException, "Error creating/updating special event schedule");
-                                    throw httpOperationException;
+                                    location.EventDates.ForEach(dates =>
+                                    {
+                                        var newDates = new MicrosoftDynamicsCRMadoxioSpecialeventschedule();
+                                        newDates.CopyValues(dates);
+                                        try
+                                        {
+                                            if (string.IsNullOrEmpty(dates.Id))
+                                            { // create record
+                                                newDates.AdoxioSpecialEventODataBind = _dynamicsClient.GetEntityURI(
+                                                    "adoxio_specialevents",
+                                                    specialEvent.Id
+                                                );
+                                                newDates.AdoxioSpecialEventLocationODataBind =
+                                                    _dynamicsClient.GetEntityURI(
+                                                        "adoxio_specialeventlocations",
+                                                        location.Id
+                                                    );
+                                                newDates = _dynamicsClient.Specialeventschedules.Create(newDates);
+                                                dates.Id = newDates.AdoxioSpecialeventscheduleid;
+                                            }
+                                            else
+                                            { // update record
+                                                _dynamicsClient.Specialeventschedules.Update(dates.Id, newDates);
+                                            }
+                                        }
+                                        catch (HttpOperationException httpOperationException)
+                                        {
+                                            _logger.LogError(
+                                                httpOperationException,
+                                                "Error creating/updating special event schedule"
+                                            );
+                                            throw httpOperationException;
+                                        }
+                                    });
                                 }
-                            });
+                            }
+                            catch (HttpOperationException httpOperationException)
+                            {
+                                _logger.LogError(
+                                    httpOperationException,
+                                    "Error creating/updating special event location"
+                                );
+                                throw httpOperationException;
+                            }
                         }
-
-
-                    }
-                    catch (HttpOperationException httpOperationException)
-                    {
-                        _logger.LogError(httpOperationException, "Error creating/updating special event location");
-                        throw httpOperationException;
-                    }
-                }));
+                    )
+                );
             }
             var result = this.GetSpecialEventData(eventId).ToViewModel(_dynamicsClient);
             result.LocalId = specialEvent.LocalId;
             return new JsonResult(result);
         }
 
-
         private ItemsToDelete GetItemsToDelete(ViewModels.SpecialEvent updateEvent, MicrosoftDynamicsCRMadoxioSpecialevent existingEvent)
         {
             var toDelete = new ItemsToDelete();


Reviews:
user: login: simranjeetgit
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T15:40:41Z
user: login: simranjeetgit
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-22T15:41:28Z
user: login: NickPhura
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T17:26:21Z
user: login: simranjeetgit
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T23:50:32Z
user: login: NickPhura
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:06:17Z
user: login: NickPhura
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:17:38Z

Review comments:
path: cllc-public-app/ClientApp/package-lock.json
user: login: simranjeetgit
body: @NickPhura Shall we remove package-lock.json from the commit? 
created_at: 2025-04-22T15:40:41Z
author_association: COLLABORATOR
path: cllc-public-app/ClientApp/package-lock.json
user: login: NickPhura
body: @simranjeetgit I've added 2 new packages, so the lock file would have some updates. 

How do you normally manage package/version changes? Do you try and keep them to a separate ticket? 

The local and openshift build uses `npm install`. So the image versions and lock file are going to be updated anyways in dev/test/prod. We'd need to switch to `npm ci` if we wanted to honour the lock file.
created_at: 2025-04-22T17:26:21Z
author_association: CONTRIBUTOR
path: cllc-public-app/ClientApp/package-lock.json
user: login: simranjeetgit
body: Package updates are fine. Sometime when we commit package.lock, it might create build issues on users devices as package.lock is an autogenerated temporary file that's why I flagged this file. 
created_at: 2025-04-25T23:50:32Z
author_association: COLLABORATOR
path: cllc-public-app/ClientApp/package-lock.json
user: login: NickPhura
body: If we aren't planning to use the lock file, at least currently, then I'll remove it. I will also add it to the `.gitignore` so it can't be merged in future, until we are ready for it.
created_at: 2025-04-28T16:06:16Z
author_association: CONTRIBUTOR
path: cllc-public-app/ClientApp/package-lock.json
user: login: NickPhura
body: Updated.
created_at: 2025-04-28T16:17:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: simranjeetgit
created_at: 2025-04-29T18:02:34Z
body: @NickPhura  please review the conflicts. Thanks 
author_association: COLLABORATOR
user: login: NickPhura
created_at: 2025-04-29T18:21:28Z
body: > @NickPhura please review the conflicts. Thanks

@simranjeetgit 

Fixed the merge conflicts. 

Note that I had to restore package-lock.json and remove it from the `.gitignore`, as it is required by the `preinstall` step. 

(That preinstall step in general is probably something we could remove, but probably not worth it just yet. If we do an angular update, all of those packages will naturally need to be updated, and we will likely be able to remove the resolutions section and the preinstall step).
author_association: CONTRIBUTOR