From 9c451bed6690f7c13b0942feb8d7527ef61a7f74 Mon Sep 17 00:00:00 2001
From: kaw67872 <kawtar.laariche@iais.fraunhofer.de>
Date: Tue, 23 Jul 2024 11:21:53 +0200
Subject: [PATCH] #24: add stepper feature

---
 src/app/core/config/api-config.ts             |   3 +
 .../core/http-shared/http-shared.service.ts   |  17 +-
 .../core/services/private-catalogs.service.ts |  65 ++-
 ...model-details-license-profile.component.ts |  36 +-
 ...publish-to-marketplace-page.component.html | 549 ++++++++++++------
 ...publish-to-marketplace-page.component.scss | 209 ++++++-
 .../publish-to-marketplace-page.component.ts  | 359 +++++++++++-
 .../upload-license-profile.component.html     |   1 +
 .../upload-license-profile.component.ts       |   2 +
 src/app/shared/models/index.ts                |   1 +
 .../shared/models/publish-request.model.ts    |  21 +
 src/assets/images/cloud-active.svg            |   1 +
 src/assets/images/cloud.svg                   |   1 +
 .../images/ico-model-documentation-green.png  | Bin 0 -> 1770 bytes
 .../images/ico_model_documentation_grey.png   | Bin 0 -> 1333 bytes
 src/assets/images/icon-yet-not-publish.png    | Bin 0 -> 1455 bytes
 src/assets/images/request_approval.png        | Bin 0 -> 1007 bytes
 .../images/request_approval_selected.png      | Bin 0 -> 922 bytes
 src/assets/images/stepper_progress.png        | Bin 0 -> 833 bytes
 19 files changed, 1039 insertions(+), 226 deletions(-)
 create mode 100644 src/app/shared/models/publish-request.model.ts
 create mode 100644 src/assets/images/cloud-active.svg
 create mode 100644 src/assets/images/cloud.svg
 create mode 100644 src/assets/images/ico-model-documentation-green.png
 create mode 100644 src/assets/images/ico_model_documentation_grey.png
 create mode 100644 src/assets/images/icon-yet-not-publish.png
 create mode 100644 src/assets/images/request_approval.png
 create mode 100644 src/assets/images/request_approval_selected.png
 create mode 100644 src/assets/images/stepper_progress.png

diff --git a/src/app/core/config/api-config.ts b/src/app/core/config/api-config.ts
index ef0c8f3..025faa8 100644
--- a/src/app/core/config/api-config.ts
+++ b/src/app/core/config/api-config.ts
@@ -65,4 +65,7 @@ export const apiConfig = {
   urlDeleteTag: '/api/dropTag',
   urlAddTag: '/api/addTag',
   urlGetAllTag: '/api/tags',
+  urlPublishSolution: '/api/publish',
+  urlSearchPublishRequest: '/api/publish/request/search/revision',
+  withdrawPublishRequestUrl: '/api/publish/request/withdraw/',
 };
diff --git a/src/app/core/http-shared/http-shared.service.ts b/src/app/core/http-shared/http-shared.service.ts
index cf200e6..fc71fad 100644
--- a/src/app/core/http-shared/http-shared.service.ts
+++ b/src/app/core/http-shared/http-shared.service.ts
@@ -8,11 +8,18 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
 export class HttpSharedService {
   constructor(private http: HttpClient) {}
 
-  private _convertQueryParams(queryParams?: any) {
-    const params = new HttpParams();
-    queryParams.forEach((key: string) => {
-      params.set(key, queryParams[key]);
-    });
+  private _convertQueryParams(queryParams?: any): HttpParams {
+    let params = new HttpParams();
+    if (queryParams) {
+      // Iterate over object keys
+      Object.keys(queryParams).forEach((key) => {
+        // Check if the property is indeed a property of queryParams
+        if (queryParams.hasOwnProperty(key)) {
+          // Add each key to the HttpParams object
+          params = params.set(key, queryParams[key]);
+        }
+      });
+    }
     return params;
   }
 
diff --git a/src/app/core/services/private-catalogs.service.ts b/src/app/core/services/private-catalogs.service.ts
index 3c2d9c2..cc7776a 100644
--- a/src/app/core/services/private-catalogs.service.ts
+++ b/src/app/core/services/private-catalogs.service.ts
@@ -11,7 +11,7 @@ import {
   PublicSolution,
   PublicSolutionDetailsModel,
   PublicSolutionsRequestPayload,
-  Tag,
+  PublishSolutionRequest,
   ThreadModel,
   UserDetails,
 } from 'src/app/shared/models';
@@ -832,4 +832,67 @@ export class PrivateCatalogsService {
       }),
     );
   }
+
+  publishSolution(
+    solutionId: string,
+    visibility: string,
+    userId: string,
+    revisionId: string,
+    ctlg: string,
+  ) {
+    const url =
+      apiConfig.apiBackendURL +
+      apiConfig.urlPublishSolution +
+      '/' +
+      solutionId +
+      '?';
+
+    const data = {
+      visibility,
+      userId,
+      revisionId,
+      ctlg,
+    };
+
+    return this._httpSharedService.put(url, data, undefined).pipe(
+      catchError((error) => {
+        throw error;
+      }),
+    );
+  }
+
+  searchPublishRequestWithCatalogIds(
+    revisionId: string,
+    catalogId: string,
+  ): Observable<PublishSolutionRequest> {
+    const url =
+      apiConfig.apiBackendURL +
+      apiConfig.urlSearchPublishRequest +
+      '/' +
+      revisionId +
+      '/' +
+      catalogId;
+
+    return this._httpSharedService.get(url, undefined).pipe(
+      map((res) => res.response_body),
+      catchError((error) => {
+        throw error;
+      }),
+    );
+  }
+
+  withdrawPublishRequest(
+    publishRequestId: number,
+  ): Observable<PublishSolutionRequest> {
+    const url =
+      apiConfig.apiBackendURL +
+      apiConfig.withdrawPublishRequestUrl +
+      publishRequestId;
+    return this._httpSharedService.put(url).pipe(
+      map((res) => res.response_body),
+      catchError((error) => {
+        throw error;
+      }),
+    );
+  }
 }
diff --git a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts
index 7418035..c142aca 100644
--- a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts
+++ b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts
@@ -9,7 +9,11 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog';
 import { CreateEditLicenseProfileComponent } from '../create-edit-license-profile/create-edit-license-profile.component';
 import { UploadLicenseProfileComponent } from '../upload-license-profile/upload-license-profile.component';
 import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service';
-import { emptyLicenseProfileModel, LicenseProfileModel } from '../../models';
+import {
+  emptyLicenseProfileModel,
+  LicenseProfileModel,
+  Revision,
+} from '../../models';
 import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service';
 
 @Component({
@@ -28,14 +32,13 @@ export class ModelDetailsLicenseProfileComponent implements OnInit {
   modelLicenseError = '';
   solutionId!: string;
   revisionId!: string;
-  versionId: string = '';
-  versionIdSubscription: Subscription | undefined;
+  revisionSubscription: Subscription | undefined;
   isUserIdAvailable$: Observable<boolean | undefined>;
   userId$: Observable<string | undefined>;
   userId!: string;
+  selectedRevision!: Revision;
 
   private subscription: Subscription = new Subscription();
-  private destroy$: Subscription = new Subscription();
 
   constructor(
     private activatedRoute: ActivatedRoute,
@@ -71,15 +74,16 @@ export class ModelDetailsLicenseProfileComponent implements OnInit {
     });
 
     // Get the initial version ID
-    this.versionId = this.sharedDataService.versionId;
+    this.selectedRevision = this.sharedDataService.selectedRevision;
 
     // Subscribe to changes in versionId
-    this.versionIdSubscription = this.sharedDataService.versionId$.subscribe(
-      (versionId: string) => {
-        this.versionId = versionId;
-        this.loadData();
-      },
-    );
+    this.revisionSubscription =
+      this.sharedDataService.selectedRevision$.subscribe(
+        (revision: Revision) => {
+          this.selectedRevision = revision;
+          this.loadData();
+        },
+      );
 
     // Load data initially
     this.loadData();
@@ -87,16 +91,16 @@ export class ModelDetailsLicenseProfileComponent implements OnInit {
 
   ngOnDestroy() {
     // Unsubscribe from versionIdSubscription
-    if (this.versionIdSubscription) {
-      this.versionIdSubscription.unsubscribe();
+    if (this.revisionSubscription) {
+      this.revisionSubscription.unsubscribe();
     }
   }
 
   loadData() {
     // Check if both solutionId and versionId are available
-    if (this.solutionId && this.versionId) {
+    if (this.solutionId && this.selectedRevision.version) {
       this.publicSolutionsService
-        .getLicenseFile(this.solutionId, this.versionId)
+        .getLicenseFile(this.solutionId, this.selectedRevision.version)
         .subscribe(
           (res) => {
             if (res) {
@@ -150,7 +154,7 @@ export class ModelDetailsLicenseProfileComponent implements OnInit {
       this.userId,
       this.solutionId,
       this.revisionId,
-      this.versionId,
+      this.selectedRevision.version,
       file,
     );
   }
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
index 8fd346a..9792b0a 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
@@ -1,12 +1,68 @@
 <div class="workflow-right-header workflow-header">Publish to Marketplace</div>
 <div class="container" *ngIf="data$ | async as data">
   <!-- status stepper start-->
-  <div></div>
+  <div class="stepper">
+    <div class="flex-column">
+      <span
+        [ngClass]="
+          statusStepperProgress.documentation
+            ? 'image-content-active model-documentation-active'
+            : 'image-content-inactive model-documentation'
+        "
+        class="image-content"
+      ></span>
+      <span>Model documentation</span>
+    </div>
+    <span
+      [ngClass]="
+        statusStepperProgress.requestApproval
+          ? 'loading-active'
+          : 'loading-inactive'
+      "
+    ></span>
+    <div class="request-approval-container">
+      <span
+        [ngClass]="
+          statusStepperProgress.requestApproval
+            ? 'request-approval-active request-approval-stepper-active'
+            : 'image-content-inactive request-approval'
+        "
+        class="image-content"
+      ></span>
+      <span>Request Approval</span>
+      <button
+        *ngIf="statusStepperProgress.requestApproval"
+        class="withdraw-request-button"
+        mat-raised-button
+        (click)="onClickWithdrawRequest()"
+      >
+        Withdraw request
+      </button>
+    </div>
+    <span
+      [ngClass]="
+        statusStepperProgress.published ? 'loading-active' : 'loading-inactive'
+      "
+      class="loading-inactive"
+    ></span>
+    <div class="flex-column">
+      <span
+        [ngClass]="
+          statusStepperProgress.published
+            ? 'loading-active'
+            : 'yet-not-publish image-content-inactive'
+        "
+        class="image-content"
+      ></span>
+      <span>Published</span>
+    </div>
+  </div>
   <!-- status stepper end-->
 
   <!-- Steps to submit publication start-->
   <form [formGroup]="publishToMarketPlaceForm" (ngSubmit)="submit()">
     <div class="flex-column">
+      <span class="form-field-label">Model catalog</span>
       <mat-form-field>
         <mat-label>Select a catalog</mat-label>
         <mat-select
@@ -18,203 +74,350 @@
           }
         </mat-select>
       </mat-form-field>
+    </div>
 
+    <div class="form-container">
+      <div class="form-header">
+        <span class="form-title">STEPS TO SUBMIT PUBLICATION</span>
+        <span class="timeline">( COMPLETED)</span>
+      </div>
       @if (publishToMarketPlaceForm.controls["catalog"].value) {
+        <!--Model name starts-->
         <div class="flex-column">
-          <div class="flex-row">
-            <mat-form-field>
-              <mat-label>Model name</mat-label>
-              <input matInput formControlName="name" />
-            </mat-form-field>
-            <button class="custom-icon" (click)="onClickEditModelName()">
-              <mat-icon>edit</mat-icon>
-            </button>
-          </div>
-
-          <div class="flex-column">
-            <div class="flex-row">
-              <span>Model description</span>
-              <button class="custom-icon" (click)="addEditDescription()">
-                @if (publishToMarketPlaceForm.value.description) {
-                  <mat-icon>edit</mat-icon>
-                } @else {
-                  <mat-icon>add</mat-icon>
-                }
-              </button>
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>1</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
             </div>
+            <div class="flex-column flex-grow">
+              <div class="flex-row">
+                <span class="form-field-label">Model name</span>
+                <button class="custom-icon" (click)="onClickEditModelName()">
+                  <mat-icon>edit</mat-icon>
+                </button>
+              </div>
 
-            <div
-              *ngIf="publishToMarketPlaceForm.value.description"
-              [innerHTML]="publishToMarketPlaceForm.value.description"
-            ></div>
+              <div class="flex-row">
+                <mat-form-field>
+                  <mat-label>Model name</mat-label>
+                  <input matInput formControlName="name" />
+                </mat-form-field>
+              </div>
+              <mat-divider
+                class="divider-color horizontal-divider"
+              ></mat-divider>
+            </div>
           </div>
+          <!--Model name ends-->
 
-          <div class="flex-column">
-            <mat-form-field class="full-width">
-              <mat-label>Select category</mat-label>
-              <mat-select
-                formControlName="category"
-                [compareWith]="compareObjects"
-              >
-                @for (category of categories; track category) {
-                  <mat-option [value]="category">{{
-                    category.name
-                  }}</mat-option>
-                }
-              </mat-select>
-            </mat-form-field>
-            <mat-form-field class="full-width">
-              <mat-label>Select toolkitType</mat-label>
-
-              <mat-select
-                formControlName="toolkitType"
-                [compareWith]="compareObjects"
-              >
-                @for (toolkitType of toolkitTypes; track toolkitType) {
-                  <mat-option [value]="toolkitType">{{
-                    toolkitType.name
-                  }}</mat-option>
-                }
-              </mat-select>
-            </mat-form-field>
+          <!--Model description starts-->
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>2</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
+            </div>
+            <div class="flex-column flex-grow">
+              <div class="flex-row">
+                <span class="form-field-label">Model description</span>
+                <button class="custom-icon" (click)="addEditDescription()">
+                  @if (publishToMarketPlaceForm.value.description) {
+                    <mat-icon>edit</mat-icon>
+                  } @else {
+                    <mat-icon>add</mat-icon>
+                  }
+                </button>
+              </div>
+              <div
+                *ngIf="publishToMarketPlaceForm.value.description"
+                [innerHTML]="publishToMarketPlaceForm.value.description"
+              ></div>
+              <div class="flex-column">
+                <span class="description-hint"
+                  >The provided description less than 500 characters
+                </span>
+                <span class="text-medium"
+                  >If you want to improve your model description rating then
+                  please add more details, else click on 'OK' button to go with
+                  the current rating</span
+                >
+              </div>
+              <mat-divider
+                class="divider-color horizontal-divider"
+              ></mat-divider>
+            </div>
           </div>
-          <!--license profile start-->
-          <div class="flex-column">
-            <div class="license-container">
-              <span>Model license profile</span>
+          <!--Model description ends-->
 
-              <button class="custom-icon" (click)="onLicenseProfileClick()">
-                @if (data.licenseProfile) {
-                  <mat-icon>edit</mat-icon>
-                } @else {
-                  <mat-icon>add</mat-icon>
-                }
-              </button>
+          <!--Model category starts-->
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>3</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
+            </div>
+            <div class="flex-column flex-grow">
+              <div class="flex-column">
+                <span class="form-field-label">Model category</span>
+                <mat-form-field class="full-width">
+                  <mat-label>Select category</mat-label>
+                  <mat-select
+                    formControlName="category"
+                    [compareWith]="compareObjects"
+                  >
+                    @for (category of categories; track category) {
+                      <mat-option [value]="category">{{
+                        category.name
+                      }}</mat-option>
+                    }
+                  </mat-select>
+                </mat-form-field>
+              </div>
+              <mat-form-field class="full-width">
+                <mat-label>Select toolkitType</mat-label>
+                <mat-select
+                  formControlName="toolkitType"
+                  [compareWith]="compareObjects"
+                >
+                  @for (toolkitType of toolkitTypes; track toolkitType) {
+                    <mat-option [value]="toolkitType">{{
+                      toolkitType.name
+                    }}</mat-option>
+                  }
+                </mat-select>
+              </mat-form-field>
+              <mat-divider
+                class="divider-color horizontal-divider"
+              ></mat-divider>
             </div>
           </div>
-          @if (addEditLicenseProfile) {
-            <div class="flex-row full-width">
-              <gp-model-details-license-profile
-                [isExistingLicenseProfile]="true"
-              ></gp-model-details-license-profile>
+          <!--Model category ends-->
+
+          <!--license profile start-->
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>4</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
             </div>
-          }
+            <div class="flex-column flex-grow">
+              <div class="license-container">
+                <div class="flex-row">
+                  <span class="form-field-label">Model license profile</span>
+                  <button class="custom-icon" (click)="onLicenseProfileClick()">
+                    @if (data.licenseProfile) {
+                      <mat-icon>edit</mat-icon>
+                    } @else {
+                      <mat-icon>add</mat-icon>
+                    }
+                  </button>
+                </div>
+                <input
+                  style="width: 20%"
+                  *ngIf="licenseName"
+                  [value]="licenseName"
+                  disabled
+                />
+              </div>
+            </div>
+            @if (addEditLicenseProfile) {
+              <div class="flex-row full-width spacebetween buttons-layout">
+                <button
+                  mat-stroked-button
+                  color="primary"
+                  (click)="onClickUploadLicenseProfile()"
+                >
+                  Upload
+                </button>
+                <button
+                  *ngIf="data.licenseProfile"
+                  mat-stroked-button
+                  color="primary"
+                  (click)="onClickUpdateLicenseProfile()"
+                >
+                  Update
+                </button>
+                <button
+                  *ngIf="!data.licenseProfile"
+                  mat-stroked-button
+                  (click)="onClickCreateLicenseProfile()"
+                >
+                  Create
+                </button>
+              </div>
+            }
+            <mat-divider class="divider-color horizontal-divider"></mat-divider>
+          </div>
           <!--license profile end-->
-          <!--upload documents start-->
-          <div class="upload-file-container">
-            <div class="flex-row">
-              <span class="upload-file-text">Model Documents </span>
 
-              <button class="custom-icon" (click)="onClickUploadDocumentFile()">
-                @if (documents().length > 0) {
-                  <mat-icon>edit</mat-icon>
-                } @else {
-                  <mat-icon>add</mat-icon>
-                }
-              </button>
+          <!--upload documents start-->
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>5</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
             </div>
+            <div class="upload-file-container flex-grow">
+              <div class="flex-row">
+                <span class="form-field-label">Model Documents </span>
 
-            <mat-form-field>
-              <mat-chip-grid #documentsGrid formControlName="documents">
-                @for (document of documents(); track document) {
-                  <mat-chip-row (removed)="removeDocument(document)">
-                    {{ document.name }}
-                    <button
-                      matChipRemove
-                      [attr.aria-label]="'remove ' + document"
-                    >
-                      <mat-icon>cancel</mat-icon>
-                    </button>
-                  </mat-chip-row>
-                }
-              </mat-chip-grid>
-              <input
-                matInput
-                [matChipInputFor]="documentsGrid"
-                (matChipInputTokenEnd)="onAddEditDocuments($event)"
-              />
-            </mat-form-field>
+                <button
+                  class="custom-icon"
+                  (click)="onClickUploadDocumentFile()"
+                >
+                  @if (documents().length > 0) {
+                    <mat-icon>edit</mat-icon>
+                  } @else {
+                    <mat-icon>add</mat-icon>
+                  }
+                </button>
+              </div>
+
+              <mat-form-field>
+                <mat-chip-grid #documentsGrid formControlName="documents">
+                  @for (document of documents(); track document) {
+                    <mat-chip-row (removed)="removeDocument(document)">
+                      {{ document.name }}
+                      <button
+                        matChipRemove
+                        [attr.aria-label]="'remove ' + document"
+                      >
+                        <mat-icon>cancel</mat-icon>
+                      </button>
+                    </mat-chip-row>
+                  }
+                </mat-chip-grid>
+                <input
+                  matInput
+                  [matChipInputFor]="documentsGrid"
+                  (matChipInputTokenEnd)="onAddEditDocuments($event)"
+                />
+              </mat-form-field>
+              <div class="flex-column flex-grow">
+                <span class="text-medium"
+                  >Add documents (such as a README file) to give more details
+                  and show how to use your model.</span
+                >
+              </div>
+              <mat-divider
+                class="divider-color horizontal-divider"
+              ></mat-divider>
+            </div>
           </div>
           <!--upload documents end-->
+
           <!--tags starts-->
-          <mat-form-field class="flex-grow">
-            <mat-label>Add tag</mat-label>
-            <mat-chip-grid #chipGrid>
-              @for (tag of tagsItems(); track tag) {
-                <mat-chip-row (removed)="removeTag(tag)">
-                  {{ tag.tag }}
-                  <button matChipRemove [attr.aria-label]="'remove ' + tag">
-                    <mat-icon>cancel</mat-icon>
-                  </button>
-                </mat-chip-row>
-              }
-            </mat-chip-grid>
-            <input
-              #tagInput
-              [formControl]="tagCtrl"
-              [matChipInputFor]="chipGrid"
-              [matAutocomplete]="auto"
-              [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
-              (matChipInputTokenEnd)="addTag($event)"
-              (input)="onChangeInput($event)"
-            />
-            <mat-autocomplete
-              #auto="matAutocomplete"
-              (optionSelected)="selected($event)"
-            >
-              @for (tag of filteredTags | async; track tag) {
-                <mat-option [value]="tag"
-                  ><span class="autocomplete-option">{{
-                    tag
-                  }}</span></mat-option
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>6</span>
+              </div>
+              <mat-divider
+                vertical
+                class="divider-color full-height"
+              ></mat-divider>
+            </div>
+            <div class="flex-column flex-grow">
+              <span class="form-field-label">Model tags</span>
+              <mat-form-field class="flex-grow">
+                <mat-label>Add tag</mat-label>
+                <mat-chip-grid #chipGrid>
+                  @for (tag of tagsItems(); track tag) {
+                    <mat-chip-row (removed)="removeTag(tag)">
+                      {{ tag.tag }}
+                      <button matChipRemove [attr.aria-label]="'remove ' + tag">
+                        <mat-icon>cancel</mat-icon>
+                      </button>
+                    </mat-chip-row>
+                  }
+                </mat-chip-grid>
+                <input
+                  #tagInput
+                  [formControl]="tagCtrl"
+                  [matChipInputFor]="chipGrid"
+                  [matAutocomplete]="auto"
+                  [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
+                  (matChipInputTokenEnd)="addTag($event)"
+                  (input)="onChangeInput($event)"
+                />
+                <mat-autocomplete
+                  #auto="matAutocomplete"
+                  (optionSelected)="selected($event)"
                 >
-              }
-            </mat-autocomplete>
-          </mat-form-field>
+                  @for (tag of filteredTags | async; track tag) {
+                    <mat-option [value]="tag"
+                      ><span class="autocomplete-option">{{
+                        tag
+                      }}</span></mat-option
+                    >
+                  }
+                </mat-autocomplete>
+              </mat-form-field>
+              <mat-divider
+                class="divider-color horizontal-divider"
+              ></mat-divider>
+            </div>
+          </div>
           <!--tags ends-->
-          <!--upload image start-->
-          <div class="flex-column">
-            <span class="upload-file-text">Upload image model </span>
-            <p class="upload-text-hint">
-              Upload an image that will identify your model in Marketplace. You
-              can upload jpg, jpeg, png and gif file with maximum size of 1MB.
-            </p>
 
-            <div class="single-file">
-              <div class="image-container">
-                <img class="file-image" [src]="imageToShow" alt="file" />
-              </div>
-              <div class="flex-row file-name">
-                <span *ngIf="imageToShow.name"> {{ imageToShow.name }}</span>
-                <span>|</span>
-                <a (click)="onClickUploadImageFile()">Change</a>
+          <!--upload image start-->
+          <div class="step-container">
+            <div class="timeline-container">
+              <div class="timeline-entry">
+                <span>7</span>
               </div>
             </div>
+            <div class="flex-column">
+              <div class="flex-row">
+                <span class="form-field-label">Model image </span>
 
-            <!--  <div class="file-name">
-              | <a ng-click="showImageUpload = !showImageUpload">Change</a>
-              <!- - <span>Size 800K</span> - ->
-            </div> -->
-            <!--  <mat-form-field class="mat-form-field-upload-file">
-              <input matInput />
+                @if (!imageToShow) {
+                  <button
+                    class="custom-icon"
+                    (click)="onClickUploadImageFile()"
+                  >
+                    <mat-icon>upload</mat-icon>
+                  </button>
+                }
+              </div>
+              <p class="upload-text-hint">
+                Upload an image that will identify your model in Marketplace.
+                You can upload jpg, jpeg, png and gif file with maximum size of
+                1MB.
+              </p>
 
-              <input
-                #fileDropRef
-                type="file"
-                id="fileInput"
-                name="fileInput"
-                (change)="selectImageFile($event)"
-                hidden
-                formControlName="image"
-              />
-            </mat-form-field> -->
-            <!--  <button class="custom-icon" (click)="onClickUploadImageFile()">
-              @if (imageToShow) {
-                <mat-icon>edit</mat-icon>
-              } @else {
-                <mat-icon>add</mat-icon>
-              }
-            </button> -->
+              <div *ngIf="imageToShow" class="single-file">
+                <div class="image-container">
+                  <img class="file-image" [src]="imageToShow" alt="file" />
+                </div>
+                <div class="flex-row file-name">
+                  <span *ngIf="imageToShow.name"> {{ imageToShow?.name }}</span>
+                  <span>|</span>
+                  <a (click)="onClickUploadImageFile()">Change</a>
+                </div>
+              </div>
+            </div>
           </div>
           <!--upload image end-->
         </div>
@@ -233,7 +436,7 @@
           mat-raised-button
           color="primary"
           type="submit"
-          [disabled]="publishToMarketPlaceForm.invalid"
+          [disabled]="!enableSubmit"
         >
           Publish model
         </button>
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
index c0a41a5..387aef2 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
@@ -6,12 +6,17 @@
   border: none !important;
   color: mat.get-color-from-palette($graphene-ui-primary);
   cursor: pointer;
+
+  mat-icon {
+    font-size: 16px !important;
+  }
 }
 
 .container {
   display: flex;
   flex-direction: column;
   padding: 40px;
+  height: 100%;
 }
 
 .flex-column {
@@ -36,8 +41,8 @@
 
 .license-container {
   display: flex;
-  align-items: center;
-  gap: 40px;
+  flex-direction: column;
+  gap: 10px;
 }
 
 .upload-file-container {
@@ -46,10 +51,6 @@
   gap: 20px;
 }
 
-.upload-file-text {
-  width: 20%;
-}
-
 .mat-form-field-upload-file {
   height: 70px;
   flex-grow: 1;
@@ -76,11 +77,12 @@
   gap: 20px;
 
   button {
-    width: 20%;
+    width: 30%;
   }
 }
 
 .single-file {
+  width: 20%;
   display: flex;
   padding: 0.5rem;
   align-items: center;
@@ -101,8 +103,8 @@
 .file-image {
   align-self: flex-start;
   border: 1px solid #dbcbe8;
-  width: 24px;
-  height: 24px;
+  width: 40px;
+  height: 40px;
   padding: 3px;
 }
 
@@ -144,3 +146,192 @@
   hyphens: auto;
   color: #666;
 }
+
+.form-title {
+  font-size: 16px;
+  text-transform: uppercase;
+  font-weight: bold;
+  color: mat.get-color-from-palette($graphene-ui-primary);
+}
+
+.timeline {
+  font-size: 14px;
+  font-weight: normal;
+  color: mat.get-color-from-palette($graphene-ui-primary);
+}
+
+.form-header {
+  border-bottom: 1px solid #ddd;
+  padding-bottom: 4px;
+  margin-bottom: 24px;
+}
+
+.image-content {
+  width: 74px;
+  height: 74px;
+  border-radius: 74px;
+  float: left;
+  position: relative;
+  text-align: center;
+  line-height: 68px;
+}
+
+.image-content-inactive {
+  border: 3px solid #fff;
+  background-color: #ebebeb;
+}
+
+.image-content-active {
+  border: 3px solid #009901;
+  background-color: #fff;
+}
+
+.request-approval-stepper-active {
+  border: 3px solid #671c9d;
+  background-color: #fff;
+}
+
+.model-documentation {
+  background-image: url(../../../../assets/images/ico_model_documentation_grey.png);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.model-documentation-active {
+  background-image: url(../../../../assets/images/ico-model-documentation-green.png);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.request-approval {
+  background-image: url(../../../../assets/images/request_approval.png);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.request-approval-active {
+  background-image: url(../../../../assets/images/request_approval_selected.png);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.yet-not-publish {
+  background-image: url(../../../../assets/images/cloud.svg);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.stepper {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 20px;
+}
+
+.loading-inactive {
+  background: url("../../../../assets/images/loading_deactive.png") no-repeat
+    left center;
+  width: 45px;
+  height: 12px;
+}
+
+.loading-active {
+  background: url("../../../../assets/images/stepper_progress_completed.png")
+    no-repeat left center;
+  width: 45px;
+  height: 12px;
+}
+
+.withdraw-request-button {
+  background-color: transparent;
+  border: 1px solid #671c9d;
+  height: 28px !important;
+}
+
+.request-approval-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.buttons-layout {
+  margin: 10px;
+  gap: 10px;
+}
+
+.spacebetween {
+}
+
+.description-hint {
+  color: #666;
+  font-size: 12px;
+  font-family: "Open Sans", sans-serif;
+  font-weight: 600;
+}
+.text-medium {
+  font-size: 13px !important;
+  font-weight: 400;
+  line-height: 24px;
+  font-family: "Open Sans", sans-serif;
+  color: #666;
+}
+
+.form-field-label {
+  font-size: 14px;
+  font-weight: bold;
+  color: #671c9d;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.divider-color {
+  color: #dbcbe8;
+}
+
+.timeline-entry {
+  background: #fff;
+  color: #212121;
+  display: block;
+  width: 18px;
+  height: 18px;
+  -webkit-background-clip: padding-box;
+  background-clip: padding-box;
+  -webkit-border-radius: 18px;
+  border-radius: 18px;
+  text-align: center;
+  -webkit-box-shadow: 0 0 0 2px #671c9d;
+  box-shadow: 0 0 0 2px #671c9d;
+  line-height: 18px;
+  font-size: 12px;
+  float: left;
+}
+
+.step-container {
+  display: flex;
+  flex-direction: row;
+  gap: 20px;
+}
+
+.timeline-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.full-height {
+  height: 100%;
+}
+
+.horizontal-divider {
+  margin-left: 6px;
+  margin-top: 30px;
+  margin-bottom: 40px;
+}
+
+.form-fields-layout {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
index c4671ce..7bf2259 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
@@ -15,9 +15,10 @@ import {
   Catalog,
   CatalogFilter,
   DocumentModel,
-  Filter,
   LicenseProfileModel,
   PublicSolutionDetailsModel,
+  PublicSolutionDetailsRevisionModel,
+  PublishSolutionRequest,
   Revision,
   Tag,
   ToolkitTypeCode,
@@ -57,7 +58,10 @@ import {
   Observable,
   of,
   startWith,
+  Subject,
+  Subscription,
   switchMap,
+  takeUntil,
   tap,
 } from 'rxjs';
 import { UpdateModelNameDialogComponent } from '../update-model-name-dialog/update-model-name-dialog.component';
@@ -67,6 +71,10 @@ import {
   MatAutocompleteSelectedEvent,
 } from '@angular/material/autocomplete';
 import { COMMA, ENTER } from '@angular/cdk/keycodes';
+import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service';
+import { CreateEditLicenseProfileComponent } from '../create-edit-license-profile/create-edit-license-profile.component';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatDividerModule } from '@angular/material/divider';
 interface RouteParams {
   solutionId: string;
   revisionId: string;
@@ -104,6 +112,7 @@ interface ModelData {
     MatChipsModule,
     MatIconModule,
     MatAutocompleteModule,
+    MatDividerModule,
   ],
   templateUrl: './publish-to-marketplace-page.component.html',
   styleUrl: './publish-to-marketplace-page.component.scss',
@@ -144,21 +153,20 @@ export class PublishToMarketplacePageComponent implements OnInit {
   defaultSolutionData = {};
   imageToShow: any;
   readonly separatorKeysCodes: number[] = [ENTER, COMMA];
-
-  createImageFromBlob(image: Blob) {
-    const reader = new FileReader();
-    reader.addEventListener(
-      'load',
-      () => {
-        this.imageToShow = reader.result;
-      },
-      false,
-    );
-
-    if (image) {
-      reader.readAsDataURL(image);
-    }
-  }
+  private subscription: Subscription = new Subscription();
+  userId!: string;
+  revisionsList!: Revision[];
+  selectedDefaultRevision!: Revision;
+  licenseName!: string;
+  statusStepperProgress = {
+    documentation: false,
+    requestApproval: false,
+    published: false,
+  };
+  publishRequest!: PublishSolutionRequest;
+  isSubmitted = false;
+  enableSubmit = false;
+  private onDestroy = new Subject<void>();
 
   constructor(
     private activatedRoute: ActivatedRoute,
@@ -168,7 +176,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
     private sharedDataService: SharedDataService,
     public dialog: MatDialog,
     private alertService: AlertService,
-
+    private browserStorageService: BrowserStorageService,
     private publicSolutionsService: PublicSolutionsService,
   ) {
     this.buildForm();
@@ -178,6 +186,15 @@ export class PublishToMarketplacePageComponent implements OnInit {
           return this.publicSolutionsService
             .getSolutionDetails(solutionId, revisionId)
             .pipe(
+              tap((res) => {
+                this.revisionsList = this.getRevisionsList(res);
+
+                this.selectedDefaultRevision = this.revisionsList.filter(
+                  (rv) => rv.revisionId === this.revisionId,
+                )[0];
+
+                this.setRevisionInService(this.selectedDefaultRevision);
+              }),
               switchMap((solution) => {
                 const documentStream =
                   this.selectedCatalog && this.selectedCatalog.catalogId
@@ -206,7 +223,10 @@ export class PublishToMarketplacePageComponent implements OnInit {
                     .getPictureOfSolution(solutionId)
                     .pipe(catchError(() => of(null))),
                   licenseProfile: this.publicSolutionsService
-                    .getLicenseFile(solutionId, revisionId)
+                    .getLicenseFile(
+                      solutionId,
+                      this.selectedDefaultRevision.version,
+                    )
                     .pipe(catchError(() => of(null))),
                   tags: this.privateCatalogsService.getAllTag(),
                   documents: documentStream,
@@ -244,7 +264,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
       category: [null, [Validators.required]],
       toolkitType: [null, Validators.required],
       licenseProfile: [null, [Validators.required]],
-      documents: [[], [Validators.required]],
+      documents: [[]],
       tags: [[], [Validators.required]],
       image: [null, [Validators.required]],
     });
@@ -254,6 +274,25 @@ export class PublishToMarketplacePageComponent implements OnInit {
     this.activatedRoute.parent?.params.subscribe((params) => {
       this.solutionId = params['solutionId'];
       this.revisionId = params['revisionId'];
+      this.privateCatalogsService
+        .getAuthors(this.solutionId, this.revisionId)
+        .subscribe({
+          next: (res) => {
+            console.log({ res });
+            if (res.length === 0) {
+              const alert: Alert = {
+                message:
+                  'You cannot publish the model without entering the author name. Please add author name in the "Manage Publisher/Authors" page to publish it.',
+                type: AlertType.Error,
+              };
+              this.alertService.notify(alert);
+              this.alertService.notify;
+            }
+          },
+          error: (error) => {
+            console.error('Error fetching users:', error);
+          },
+        });
       this.publicSolutionsService
         .getSolutionDetails(this.solutionId, this.revisionId)
         .subscribe({
@@ -302,6 +341,40 @@ export class PublishToMarketplacePageComponent implements OnInit {
       .subscribe((filteredTags) => {
         this.filteredTags.next(filteredTags);
       });
+
+    this.subscription.add(
+      this.browserStorageService.getUserDetails().subscribe((details) => {
+        this.userId = details?.userId ?? '';
+      }),
+    );
+    this.publishToMarketPlaceForm.valueChanges
+      .pipe(takeUntil(this.onDestroy))
+      .subscribe(() => {
+        this.checkFormValidity();
+      });
+  }
+
+  checkFormValidity() {
+    const {
+      name,
+      catalog,
+      description,
+      category,
+      toolkitType,
+      licenseProfile,
+      tags,
+      image,
+    } = this.publishToMarketPlaceForm.value;
+
+    this.enableSubmit =
+      name &&
+      catalog &&
+      description &&
+      category &&
+      toolkitType &&
+      licenseProfile &&
+      tags &&
+      image;
   }
 
   private filterTags(inputValue: string, tags: string[]): string[] {
@@ -338,6 +411,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
             this.publishToMarketPlaceForm.patchValue({
               description: res.response_body.description,
             });
+            console.log('description', res.response_body.description);
           },
           error: (error) => {
             console.error('Failed to fetch description', error);
@@ -366,10 +440,55 @@ export class PublishToMarketplacePageComponent implements OnInit {
             console.error('Failed to fetch documents', error);
           },
         });
+
+      this.privateCatalogsService
+        .searchPublishRequestWithCatalogIds(
+          this.revisionId,
+          this.selectedCatalog.catalogId,
+        )
+        .subscribe({
+          next: (res) => {
+            this.publishRequest = res;
+            if (res) {
+              if (res.requestStatusCode === 'PE') {
+                this.statusStepperProgress.documentation = true;
+                this.statusStepperProgress.requestApproval = true;
+              }
+              if (res.requestStatusCode === 'AP') {
+                this.statusStepperProgress.documentation = true;
+                this.statusStepperProgress.published = true;
+              }
+              if (res.requestStatusCode === 'WD') {
+                this.statusStepperProgress.requestApproval = false;
+                this.statusStepperProgress.published = false;
+              }
+            }
+          },
+          error: (error) => {
+            console.error('Failed to fetch publish request', error);
+            // Handle the error, e.g., set a default value or show an error message
+          },
+        });
     }
   }
 
-  submit() {}
+  submit() {
+    this.privateCatalogsService
+      .publishSolution(
+        this.solutionId,
+        this.selectedCatalog.accessTypeCode,
+        this.userId,
+        this.revisionId,
+        this.selectedCatalog.catalogId,
+      )
+      .subscribe({
+        next: (res) => {
+          console.log({ res });
+          this.loadPublishRequest();
+        },
+        error: () => {},
+      });
+  }
 
   created(event: any) {}
 
@@ -468,6 +587,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
             action: (file: File) => this.onClickUpdateSolutionDocument(file),
             isLicenseProfile: false,
             isProcessEvent: false,
+            accept: 'application/pdf',
           },
         },
       });
@@ -643,7 +763,10 @@ export class PublishToMarketplacePageComponent implements OnInit {
     this.revisionId = data.revisionId;
     this.solutionId = data.solutionId;
 
-    if (data.picture) this.createImageFromBlob(data.picture);
+    if (data.picture) {
+      this.createImageFromBlob(data.picture);
+    }
+
     if (data.solution?.modelTypeName && data.solution.modelType) {
       this.publishToMarketPlaceForm.get('category')?.setValue({
         name: data.solution?.modelTypeName,
@@ -663,7 +786,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
 
     if (
       data.solution &&
-      data.solution?.solutionTagList.length &&
+      data.solution?.solutionTagList &&
       data.solution.solutionTagList.length >= 1
     ) {
       const tagsList = data.solution?.solutionTagList;
@@ -692,6 +815,18 @@ export class PublishToMarketplacePageComponent implements OnInit {
       const updatedTags = data.tags ?? [];
       this.allTags.update((tags: string[]) => [...updatedTags]);
     }
+
+    if (data.licenseProfile) {
+      this.licenseName =
+        'license-' + this.selectedDefaultRevision.version + '.json';
+      this.publishToMarketPlaceForm.patchValue({
+        licenseProfile: data.licenseProfile,
+      });
+    }
+  }
+
+  setRevisionInService(revision: Revision): void {
+    this.sharedDataService.selectedRevision = revision;
   }
 
   editModelName(name: string) {
@@ -751,4 +886,184 @@ export class PublishToMarketplacePageComponent implements OnInit {
         error: () => {},
       });
   }
+
+  uploadNewLicenseFile(file: File) {
+    return this.privateCatalogsService.uploadNewModelLicenseProfile(
+      this.userId,
+      file,
+    );
+  }
+
+  uploadExistingLicenseFile(file: File): Observable<any> {
+    console.log('revisionId', this.revisionId);
+    console.log('selectedDefaultRevision', this.selectedDefaultRevision);
+    return this.privateCatalogsService.uploadExistingModelLicenseProfile(
+      this.userId,
+      this.solutionId,
+      this.selectedDefaultRevision.revisionId,
+      this.selectedDefaultRevision.version,
+      file,
+    );
+  }
+
+  onClickUploadLicenseProfile() {
+    const dialogRef: MatDialogRef<UploadLicenseProfileComponent> =
+      this.dialog.open(UploadLicenseProfileComponent, {
+        data: {
+          dataKey: {
+            isEditMode: false,
+            title: 'Upload License file',
+            errorMessage:
+              'Please update the license profile to correct the following validation errors:',
+            supportedFileText:
+              'Maximum file size: 1mb | Supported file type: .json',
+            action: (file: File) => this.uploadExistingLicenseFile(file),
+            isLicenseProfile: true,
+            isProcessEvent: true,
+          },
+        },
+      });
+
+    dialogRef.afterClosed().subscribe((result) => {
+      // This will be executed when the dialog is closed
+      // Reload data to fetch the updated license profile
+    });
+  }
+
+  getRevisionsList(solution: PublicSolutionDetailsModel): Revision[] {
+    return solution.revisions.map(
+      (revision: PublicSolutionDetailsRevisionModel) => ({
+        version: revision.version,
+        revisionId: revision.revisionId,
+        onBoarded: revision.onboarded,
+      }),
+    );
+  }
+
+  createImageFromBlob(image: Blob) {
+    const reader = new FileReader();
+    reader.addEventListener(
+      'load',
+      () => {
+        this.imageToShow = reader.result;
+        this.publishToMarketPlaceForm.patchValue({
+          image: reader.result,
+        });
+      },
+      false,
+    );
+
+    if (image) {
+      reader.readAsDataURL(image);
+    }
+  }
+
+  onClickUpdateLicenseProfile() {
+    const dialogRef: MatDialogRef<CreateEditLicenseProfileComponent> =
+      this.dialog.open(CreateEditLicenseProfileComponent, {
+        data: {
+          dataKey: {
+            modelLicense: null,
+            solutionId: this.solutionId,
+            revisionId: this.revisionId,
+            isEditMode: true,
+          },
+        },
+      });
+
+    dialogRef.afterClosed().subscribe((result) => {
+      // This will be executed when the dialog is closed
+      // Reload data to fetch the updated license profile
+    });
+  }
+
+  onClickCreateLicenseProfile() {
+    const dialogRef: MatDialogRef<CreateEditLicenseProfileComponent> =
+      this.dialog.open(CreateEditLicenseProfileComponent, {
+        data: {
+          dataKey: {
+            isEditMode: false,
+            solutionId: this.solutionId,
+            revisionId: this.revisionId,
+          },
+        },
+      });
+    dialogRef.afterClosed().subscribe((result) => {
+      // This will be executed when the dialog is closed
+      // Reload data to fetch the updated license profile
+    });
+  }
+
+  onClickWithdrawRequest() {
+    this.privateCatalogsService
+      .withdrawPublishRequest(this.publishRequest.publishRequestId)
+      .subscribe({
+        next: (res) => {
+          this.publishRequest = res;
+          if (res.requestStatusCode == 'PE') {
+            this.statusStepperProgress.documentation = true;
+            this.statusStepperProgress.requestApproval = true;
+          }
+          if (res.requestStatusCode === 'AP') {
+            this.statusStepperProgress.documentation = true;
+            this.statusStepperProgress.published = true;
+          }
+          if (res.requestStatusCode === 'WD') {
+            this.statusStepperProgress.requestApproval = false;
+            this.statusStepperProgress.published = false;
+          }
+        },
+        error: (error) => {},
+      });
+  }
+
+  loadPublishRequest() {
+    this.publicSolutionsService
+      .getSolutionDocuments(
+        this.solutionId,
+        this.revisionId,
+        this.selectedCatalog.catalogId,
+      )
+      .subscribe({
+        next: (res) => {
+          this.publishToMarketPlaceForm.patchValue({
+            documents: res,
+          });
+
+          this.documents.update((documents: DocumentModel[]) => [
+            ...documents,
+            ...res,
+          ]);
+        },
+        error: (error) => {
+          console.error('Failed to fetch documents', error);
+        },
+      });
+    this.privateCatalogsService
+      .searchPublishRequestWithCatalogIds(
+        this.revisionId,
+        this.selectedCatalog.catalogId,
+      )
+      .subscribe({
+        next: (res) => {
+          res = this.publishRequest;
+          if (res.requestStatusCode == 'PE') {
+            this.statusStepperProgress.documentation = true;
+            this.statusStepperProgress.requestApproval = true;
+          }
+          if (res.requestStatusCode === 'AP') {
+            this.statusStepperProgress.documentation = true;
+            this.statusStepperProgress.published = true;
+          }
+          if (res.requestStatusCode === 'WD') {
+            this.statusStepperProgress.requestApproval = false;
+            this.statusStepperProgress.published = false;
+          }
+        },
+        error: (error) => {
+          console.error('Failed to fetch description', error);
+          // Handle the error, e.g., set a default value or show an error message
+        },
+      });
+  }
 }
diff --git a/src/app/shared/components/upload-license-profile/upload-license-profile.component.html b/src/app/shared/components/upload-license-profile/upload-license-profile.component.html
index 694b556..0a8cc52 100644
--- a/src/app/shared/components/upload-license-profile/upload-license-profile.component.html
+++ b/src/app/shared/components/upload-license-profile/upload-license-profile.component.html
@@ -30,6 +30,7 @@
               class="upload-txtbox"
               id="fileDropRef"
               (change)="handleFileInput($event)"
+              [accept]="accept"
             />
             Browse for file
           </button>
diff --git a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
index 8a2c81a..d24978b 100644
--- a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
+++ b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
@@ -83,6 +83,7 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy {
   supportedFileText!: string;
   title!: string;
   isProcessEvent!: boolean;
+  accept!: string;
 
   constructor(
     private alertService: AlertService,
@@ -115,6 +116,7 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy {
     this.supportedFileText = this.data.dataKey.supportedFileText;
     this.title = this.data.dataKey.title;
     this.isProcessEvent = this.data.dataKey.isProcessEvent;
+    this.accept = this.data.dataKey.accept;
 
     // Get the initial version ID
     this.versionId = this.sharedDataService.versionId;
diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts
index 081bcc5..e3fdb9f 100644
--- a/src/app/shared/models/index.ts
+++ b/src/app/shared/models/index.ts
@@ -6,3 +6,4 @@ export * from './comment';
 export * from './navigation-label.model';
 export * from './empty-models.model';
 export * from './document.model';
+export * from './publish-request.model';
diff --git a/src/app/shared/models/publish-request.model.ts b/src/app/shared/models/publish-request.model.ts
new file mode 100644
index 0000000..4123ab9
--- /dev/null
+++ b/src/app/shared/models/publish-request.model.ts
@@ -0,0 +1,21 @@
+export interface PublishSolutionRequest {
+  publishRequestId: number;
+  solutionId: string;
+  revisionId: string;
+  requestUserId: string;
+  requestUserName: string;
+  approverId: string;
+  requestStatusCode: string;
+  comment: string;
+  requestorName: string;
+  solutionName: string;
+  revisionName: string;
+  revisionStatusCode: string;
+  revisionStatusName: string;
+  requestStatusName: string;
+  creationDate: string;
+  lastModifiedDate: string;
+  catalogId: string;
+  catalogName: string;
+  accessType: string;
+}
diff --git a/src/assets/images/cloud-active.svg b/src/assets/images/cloud-active.svg
new file mode 100644
index 0000000..638ee54
--- /dev/null
+++ b/src/assets/images/cloud-active.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#009901"><path d="M240-192q-80 0-136-56T48-384q0-76 52-131.5T227-576q23-85 92.5-138.5T480-768q103 0 179 69.5T744-528q70 0 119 49t49 119q0 70-49 119t-119 49H240Zm0-72h504q40 0 68-28t28-68q0-40-28-68t-68-28h-66l-6-65q-7-74-62-124.5T480-696q-64 0-115 38.5T297-556l-14 49-51 3q-48 3-80 37.5T120-384q0 50 35 85t85 35Zm240-216Z"/></svg>
\ No newline at end of file
diff --git a/src/assets/images/cloud.svg b/src/assets/images/cloud.svg
new file mode 100644
index 0000000..384744e
--- /dev/null
+++ b/src/assets/images/cloud.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#666666"><path d="M240-192q-80 0-136-56T48-384q0-76 52-131.5T227-576q23-85 92.5-138.5T480-768q103 0 179 69.5T744-528q70 0 119 49t49 119q0 70-49 119t-119 49H240Zm0-72h504q40 0 68-28t28-68q0-40-28-68t-68-28h-66l-6-65q-7-74-62-124.5T480-696q-64 0-115 38.5T297-556l-14 49-51 3q-48 3-80 37.5T120-384q0 50 35 85t85 35Zm240-216Z"/></svg>
\ No newline at end of file
diff --git a/src/assets/images/ico-model-documentation-green.png b/src/assets/images/ico-model-documentation-green.png
new file mode 100644
index 0000000000000000000000000000000000000000..fe065c5fb9abb1324cf357e2a686c2f83651c5a0
GIT binary patch
literal 1770
zcmbVNX;2eq7>=bC#F198NSRTWKm(;FyBk7aBgc?Sq9mn=5<JjNc7b4$jmbh1EIOi6
zPL0Jv%ONUPt$=t9^%$sFH5e77PysI#JUXTIq#{Myje_kD#~<C9-TmHg-{*Os?>%O7
zqGbzbID0zNXtWuUNMQ{1o=4puIZdUWZO?{%>;@^4lS_z1(unGDS_DQYa6qC#lkgZE
z#mwv4@DLi!L92?DlX7VkPf2K4sNIKU(&#8OjTRDa(xJ+=I0-25B$YOl{@_Fl9Z+GR
z^teDNB-IJ<WL0Fk9$%6!i&dttRU#NYJPZgi@hAcfPNINGqt+UDrcnAzULLi!Z-aE;
zr3<+>l>SPTTpA4s2t5u2vbano91siyAuKo$31)NV18fN9f)E1293}+wAcP0Oz{EwT
zqUo_jUW`yY5sR`y>B%Ii<AI>jXk-}!ScE<agb@S*AvVZnGbs<I!K@`w6H{yOo@5Z>
z2Blu5BUOYJurs0xB9#oKQ%YYQL8FsOUlVH$6N#cy2AWVE2(utiqp_dsrMH2M!T*)<
zMsGu`S%-r$xPeI3E2(-UdQXC>+`T=J-H}qm`%JH*ih`<zgfdlwYe|VPlumtNVJeI#
zhQtwY03TuVg9AiCFdWQh3&kP?4nkl)M}R<+9B;xRe13qC%@&J9LM{x8f+7S#91#WP
zBEg7&&E-yFC0YZCYL)n8Ton~}0xNnemM75TC`ssJ2|_*D0ny0>Nf?p|9Uu_cD;@;+
z#i_IyVKjVVFVU+L3vs<F4adZKLIb?aF;Ddd4N4TnQ8WNza!?M!gcTf^i9(!2CL7|i
zVJ?O#*@?mQ2|V_HatBg^Kzoz?r)eg)s7|ymUylG~ydEN4OHGcR8j)R<W+!U$TqVMY
zSW{nJ(apt$pL?9ExKg9YyV$s;clm7AwN*#-X@{5bS8)BOel0=CB~Kl@3hG4fZXR>I
zkmtwoJ7KYA4KZvivWI=rda6t=Xj2^7v-yQ}IE1*@bG78j$p*);fsFP`hUN!PGTP1U
zJNl<Y-?z-Ia_h~D-DIlsT5a(focq<@v#m#4YjS+{F1$f+vSj#geeSW-SXfXAj<!rQ
zZ}PDKx0f<HFHXVS4|;cJS7oLYJ<BODtngCv6X;2na_h7~*5m9v<DqTE7Gq=re#cy7
z{Puc9oIEUF?py4;=hixhAKqa_-ut<0!`58JqtLnm$M5Ewns+Q6%bmW!K^BR*evt2Y
z%I)dH@w6F-WxKpT%(XqQnZwA5JGnC<vwO^c@E84W<u%RHN4C>TAI{5EGon4R%B%i5
zp|Dxr<J-JE5agZI5?)95eV}7zziPmh6_@}U`#l7lv(cvfT$hdSgK}g1rWNJq&+m5`
z<A(QmYIb)g@2NjnP||8uNpns`_S7C4@rYlwNbX+o=joxX@sK)5ho(Qut5&yoAHT5N
z*tAGKEVxt0a%b${T${P_n~?A=U(}Qq_|HDNs^S9U`l#oNaeb@ve!{P?NxeYY5Vn5a
zZpg`bz3EJ2Slf8H4tJ=}viM@I2alIMT&B5lE^eS)Tz@$_r!nQ~v2MSWohi$0m2)cj
zt>3G+C*<=FH#~nKURDTYMD>;S{hH>9tdPm;f9KA;W!>&{q^qgyYLvVut@yHNso-wX
zRK-4zy1`}KS!vskw^buM+CIe=Y>2$SrE{SCab`7D%hZ-XvidYf7wz}HTYI<YSXbG@
z^ZQ(TIncI__Mfgyi{Gi}ANg^0Yj)|h>0f4PwvH9X`*}s7MZT;2on1CNB)SIlA0EYQ
z#~Qo9GhNz?KFwB_S&qGrN1n(=KOUzIkh0PTCE%snfsWLn;_5bQ-|ZBajqN?dZixqC
z$7n;PA=&dg+;gwhGH3pMB6m=J16XWk`ODn4$x4ZyTL(HvvV$#E$#=RQ`E}a-m#i=I
jZw$O{J1fa$jJvL(4Vt1DH{rB?``<?*k_j95Ycl@<BlW^)

literal 0
HcmV?d00001

diff --git a/src/assets/images/ico_model_documentation_grey.png b/src/assets/images/ico_model_documentation_grey.png
new file mode 100644
index 0000000000000000000000000000000000000000..85a86247694c57951918ccf42dd82269b96f88c9
GIT binary patch
literal 1333
zcmeAS@N?(olHy`uVBq!ia0vp^;y^6S!3HF&cTd~Kz`*F884^(v;p=0SoS&<gn3A8A
zs#lR)0F-B7u(7WwNKDR7Em25HP0!4;ReHaBzmh^`img((sjq==fpcm`rbks#YH*cb
zNODznvSo^ry&acLg%!|%+|-gpg^Jvqyke^gTP3i$RzNmLSYJs2tfVB{R>=`$p+baj
zfP!;=QL2Keo|$g4p|OR6xuu?=silRHiH?GifuWhcfu+8oiLQa6m4T&|fuRBvDA{o-
z*c7FtSp~VcK`jIFY?U%fN(!v>^~=l4^~#O)@{7{-4J|D#^$m>ljf`}GDs+o0^GXsc
zbn}XpK}JB#a7isrF3Kz@$;{7F0GXMXlwVq6tE8k4vP2(h3($M|aQ^{0@DNJP0|rYG
z(Ekda>#P_Un3sCGIEGmCPQB{uqns*p{NsD$xywsml)Q9m|2~6NXknv3rFQIt1#X)<
z4QEwc%6N48<mosAwNr`t`&6oeAHO;}-SpkT6#d<9doz6Q>~kpROgWnHp(Q}aq+#J(
zpW81VH}*L)b)A1&Kl8cq`TyqUKlkh?{hY?}L!x5V=Kd|Ocj^E9`){gydfJ6j*$09N
z96vZuFuXV*UHT(ep^$9{^A4^%0!}>4st+U|NIhWw#}dbCcd_yG0r3N`|0h3P>b3OK
z%@;RrRK{M9Zqzq7H~*QM+IsNd!Q!>Kx#5j&YyW-NySc-OL+1Fx5UsPj*L5dKY(5a~
zr8#Z-v=dK@?(8gPKVHc4VM*Nl=l0z1Ech;Fh`jusee<mEp@$!|f7Z<VbB=HC-n}~d
z`pJ=EcJuicym>gas#dat`5fo!Z{l37Yf>YN1y66<wR`u*+}URiF|o6=^YHK(SXz1>
z*>s`wuk?ei_SIFqOy3!g72YY0J)4zzv2~d*)B9CXy(vnL3j+?6#+H?rzwCEo+{YTn
zP$Z}K`m5Ck*#J8>?c;@9e=olHd-m+v`M0*+o$jM@<Nkg21$+0-op>>@LG=NrJ}3Y8
zq#4Y5j9V9X{;09Ly=l#}bLY<Wc`ZFuW94f%-<`uzMM$ykqf*c*)dwGy1^@T5zxwL?
zfSEtsYh}^SnCgOx9jliwf3Dnc<n8U8{CwpW1sOhLGrPViMY0uQKmKMMmwR{AAiTah
zlEZOhL`n0qnOk*M^S|&|9+a{<(nK-Cz*p^2Vbj3`u?n6aKYx^8>c9H>K!3xA+~}|0
zze|hn+I^q9)#<s>OrF=TUuVo}OO){O^-Y}hjO~NS@;So%Uya_e9%~UO+Ztv2;?*l7
z=Y<pUKfa5ti;i~o@>CE|5bIB#rRKF#+o|K{<Ohp)Y!{7g6?og~)HrS0v<Z@2v72&d
zJ18^=F!^5Y@b$TH<%)~d)I$b6TR;2yF25;rzw3R7mZ+4pv}FHLxd}mf%Y#=s6ssJ%
za6w>MW>Vxezl{-pYLb(a!!%Q`WNp3trasSgDW7!5(Il4UgYB!XW-%ygO!eyF;cphW
z8nyOYQd;6ecHhfsOQ+n*G-zOxVK6SJFl5xx(|dH|n`!Lz$(Ju*{&wQTiS-<X%F|D~
zKEM1j8VIH@ox)K6M|vIeddBz6_nE&hneyaw<)o8K?q0hVCg$`*bjQ@9%KXyOufWu*
zKV#X?IYF<o|2;OlYH4Fq_dxeS;!=@=2^042wJq8y^Yz@X7J-XxZEUYT)}DCCzp%!r
b+TtJ66OEw2DJ|k0pwiCM)z4*}Q$iB}*F!~w

literal 0
HcmV?d00001

diff --git a/src/assets/images/icon-yet-not-publish.png b/src/assets/images/icon-yet-not-publish.png
new file mode 100644
index 0000000000000000000000000000000000000000..b47a8a98b4678091027b050bbcd55603ff996363
GIT binary patch
literal 1455
zcmbVMZA{c=9Iq1<QKK>@zGc>;3<!69x!!TDo^p4+!y6vTLF6`>u6OMn9ke~$;#FkH
z<{M}*F@(+On3Fk~iW4Sm3C^h-Bm^A`37IneFq~T)$&`sRMwiV!hn(97<A*hApXX`+
zzu*7&f9vkG1$i@)<|jcAG{dus@qu+NxRVnngQxws7Y~7Dp62pvMRG`svI-B`IXS??
z9+3_6KAz>aZ0hBgL(oJ?DE4c9?`k?Ii$*qPW7Ne6Kts^-m3o8?mh&1M;KPDsLq@(l
zkH7+FLrTnE%o}m?Wx}c|g)gcqC=OPY2PqC&xdL9U(||zaH5S&z3Q47P8#2aAgMI8Z
ziojzgTDc8*EXwa)3p-_nhs{QdA&8r-a1Ld}&6JfOvta_mEht8zIBCFe8lz|ohsQ4j
zcvH9#?PJ{IzQD<blxbRoM$u?AYK)qUvJysdilR`AKncPCEDY)vNn>?`q^89g7+wu3
zLPQf}363$c0l8AMAwcQl5X6Yr`-E6h#}frohU#nt#f=y$im|xHtX0j&|10CE)@t#V
z2#@-BRjyQmpdO*LI2h#a*^Xj{Kn=Y{5kOJc3PuiAioB$G7#jk<7&(EX-I&{sn;aD3
zu$o*sIBs<ijN3)wITY?7ofH=5cpBDhBJBijVO%Z-AYC~QlZmj~-K5K8vSK7=iDNyI
zs<Bd#kNXvX-*K$-nONGX@T?{)#j;!x?|`*svL>r#as+ldV-?SVGfD)BlcQ>8tVE9^
zW_U%|%yVu<7U8iR)523U1X-46SrcX;S&}l~0TMT`7#T7Um<5EzaX}(vMaJ>m|H&N+
z1fj7e`A^ftUx7}HZJ&$)I6N65UILS&fDs{mn>`RT;fjZ`7wcC~Hmu2D3R7yrbEh@*
zChbmcYB*h({zG4e*Ea_Vg=_q6cjUL1xb~VC1kx<obroeH{OHMsy@ACSzsYDKt3UZ}
zK>YUod0od_|GfR?{X;)4yLl+ucs(-6`d_Nsz9?|!GSoG+Gpn}Cu<+}HJvTbFkFoH!
z3-dZ0Db>GE-`dZs8@lJPlbiI@GpMQRd0O5zE5$xDJ9kQhJ+q^wt?|Ph_g1IPQd{nZ
z((2#qd$3{9*8~+-H1B~Ir4OYX)cW>xi)n2WiyoexwVP3HC7iha+`ETokJj0SnRP?6
zvkoskkh^%H<I{C{x$0}vf0-H`b?)dmYij(JWKzQYV$yW`2Xty)ea}{X+p@2=mmORs
z)sgux+(wc+5+2?=`t#`G-s}F!NV~gx;MH?owSSz^k6rQhWd5C+zl5u;E=d)tg8IO*
zo13yPsaX2#YMN;G?;1|N*8iC&;gt4MpL<8EH$Cu9d1=BImBh@YkCqU}ZgeIlnD@MF
zBM;xYaA0rZk&V}C22XsS^~O-{CG-0Bs~1}uGE3D5M?z@*A}QfvamL6@1hqceH);Q!
zQP=vBkD7b_n$|O^t#!D5b;m1csb$KF&zlG4Z_Mg?zVrU^yZq&I`H%A7ngH!vxL@yR
S%v%uqg?d~C%qhp~+x`KE@dsD{

literal 0
HcmV?d00001

diff --git a/src/assets/images/request_approval.png b/src/assets/images/request_approval.png
new file mode 100644
index 0000000000000000000000000000000000000000..6b43076a6bab5c1891f5471b83c2f2a12332d505
GIT binary patch
literal 1007
zcmV<L0}%X)P)<h;3K|Lk000e1NJLTq000~S000{Z1^@s6ZwT)!00009a7bBm000XT
z000XT0n*)m`~Uz0p-DtRR7l6ImS1QbRTRd5yV(W1*|3`;rm?o&*4D9wPPd>8(b8S9
zV8}~0Ew~^>qEF(3tS#6SC2gQSScC@AKJ-BntfDRG7PJktw3Aqk^VG@s(lIqIC?*wc
zHg;kRM%RblS+0}Wzj$C^X6~K&?m6c>-#LL&sl@+bimJueKiGLF91i!btgN&J0^A9Q
z!yjF`aPDn+U!Wd9Mb+X!5=eWNbimy4=p#F4=YARM>+Om3JhP9^bsbFKnqlIrNj|@H
z`Pci`w(tM!+y`IQ1XNTl4dj5{N_!%aHEi7Q(BCJH9c}4c*Rg7S`qm7G2L}KA?ausm
z+cFBl3P1@()pX#R@M;w312)^15wI;I0JH;r@mTElfy1x(f=yhX#BmlGdgI7_TOZzh
z79i*YmIO>71q=ZR+cNrX%gEc7QE>O!mQe)EjT_d7wr$<A3hd(86^;&`;I8AaJGqnP
z<>g%fAs;YZJnptF!>p0s#E$Js#1HJvlc#w7wO84@rwf2C?eVChYUz+1MhPrk2iSk?
zhWRab9S6r*EQ9rQ?<=oIqpf&7906i=dgfa3rak=^5dQA_8QyvC48STdfGbxgmH;y(
zu`vY<HR73V8Rlb8KK;k+-0fKQz&#>&c05oHqwIaD^v_M(GKwx~a=-#GT%#T{imK`2
z$^fiwj~_gAWN^uG7ResyT?IBfcbk)^-d+0fr=MRe*P)_nC1BLHjQ%QM3E&6M>!)nX
z(4R<mzZ{9IdF%P-veCVJx`;+wahydau1|9M%=@&pMW4O=#i!>1LL!_1`5F=QJ4_nT
zzt+c2E2?(!?8oN^MnC+d+g)sFY58WpxR9MMF0{JuLPD|-wO7sj*5$e^05ADOCJQ9o
z=O7T5KBz{Jyigq1#%;?OX;kEcjcgV<ZWj`lZl<e*fTBx^YF-KzO1lwJR1F~Cs6d3I
z+$80kcv@3l)T!5wDXKOO<otjoLKftZx*IbU4mDB%%(YaN`prmv^PY#cWfT=vy8vVq
zRf~%*;AP@?6AX3%r{q+*S~@J6Yz{c?Axf^vks}9e^8T;f@kPT;H7MTIRLKMiwq+zM
zl|{eMQ(b(}^e`nz7V}f5phqb(VxuX_pc&6}U>ER$U!;PbeCc9#0l1;4+HrB>dUIw<
zV7@BkJGF_Ey5+DS?(={tvslzwMqHbKDWJa^2WpLp8Nu9C4F{)y;d->SkwG{ubr!k)
d%v!=P+rMJagI+W5D1-n2002ovPDHLkV1f{Y<y8Ox

literal 0
HcmV?d00001

diff --git a/src/assets/images/request_approval_selected.png b/src/assets/images/request_approval_selected.png
new file mode 100644
index 0000000000000000000000000000000000000000..540b6c7f2ba5de9e3139be9e862ca8642d40f0ca
GIT binary patch
literal 922
zcmV;L17-Y)P)<h;3K|Lk000e1NJLTq000{R000{Z1^@s6jnwp200009a7bBm000XT
z000XT0n*)m`~Uz0Oi4sRR7l6ImQ83BXBdE=UB}o~Wg}i}$<!5vZ4Qzs0Sg*3rYEte
z;16Cj%PKga;({PvjB9%6Nl1&7L1f%kic}1&5>HC9AyE{hZWLXEKZ&j;5z%B#NVaa$
z^zu%a%>K<<-^=dz&3xZ8@BizWnVI2ph_0I2H`TtQ|BO$q23W92c`%qvA2w2<0qY%)
z8<?(|wLmT4w`~sq2_qFsoT$Il@zs)w`VD(lDb*X7kr~U<fA<~T(I=VA_|&zQuAMuI
z!gSTF1j0ZNSYGHumVUc%x@J?=yhc5c_jz0MJzhT?ZENY;xg#%3SIsaG0enC&5EU)r
zc5N3{RvfHay=32>U)On@cVx!0-1_r5je3ByP2HK7_eZL8V7h8XfhK{3?Et4lez@GT
zy=Be%uLHgu(7F9L0XX!RMzV95>t~W}N&(RCMk*96<;(2kbX76X{&VXASW&l>>FmFC
zUK^;j9FS8wj6OyNC+2{@SUewK_Sb8LEpP<5QWnU=`=P!x0FOF{IKBBs4hSIGIn07Z
z$^%QY2#}WOb1h!4r@7~kXCEy<<AFd92%zV})2YE^`m`cB)Bv2aCt!h8Xq8+Wkl<-Q
zR(G-g@2jc6+AS*i!oS1lV`AiEw~-3P6=0j}M@uMT`4ss$KcIbhGx`0E-yYw7cIWMi
z4SQEsSOGKCm*)DJ$Gq(y8~*wB?nVFw(16}zT8T6{)(rqgDl`gwccT8%k8REOj%O#Q
zs{rPIIq%iz;Dj$TKDF37phy{txo}Z0P-9!C?GJzI-qjAYJ4_z|PR{yRs>FZ`SkMMY
z%epL4+`ed}LJ6Q!SIt`a&!@oBj%r>g5;ZY7x@v9{YsAE7u3-u5k^-QhM4~TWgzBo<
zYNSGu0>?Grh23Xyphj0se}O)Bz5oc>KPj5#vqpp5KdV_xA|@!6#3@}aEL53i&-#Ga
zoKCH)X58w;<@lh4j}sDDe!Cg5HOeTTM<UYQ=hRxS(Br@_z*gyL|3|B!t%JmJU;+$_
zf0{lODC5L=sWv%DKW8kkRd!cnf$f}MT+krrw0ypIGw^iT2A+fiph?timF-hSa?aH#
w7cP=#R-KFiky4$boFP?n<kP5gyelRD0TuLdbF<&1djJ3c07*qoM6N<$f{atObN~PV

literal 0
HcmV?d00001

diff --git a/src/assets/images/stepper_progress.png b/src/assets/images/stepper_progress.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf0d9b53f884af86ce16cac7a28fe0b45b37daf0
GIT binary patch
literal 833
zcmeAS@N?(olHy`uVBq!ia0vp^-ayR9!3HGLZ$C6;U|{sl42dX-@b$4u&d=3LOvz75
z)vL%Y0Ln8k*w|MTBqnF4mMA2prf25aD!t#mUr8Y|#a1cY)Yrhbz&SM|)1#^=HMq(z
zB)KX(*)m1R-j2(r!U||WZfZ%QLPc&)Ua?h$trFN=D<B&rtgoa1R#K8}tK<l>P$9xM
zK*2e`C{@8!&rCPj(AYx3+)~fb)Y8JpL`T8Mz|c(Jz*67PMAyL3%D~dfz)%4Sl<c?^
zY>HCStb$zJpq2r7wn`Z#B?VUc`sL;2dgaD?`9<mahL)C=`UXb&Mn<|o6}rWhc_oPz
zx_QOQAR{1VxTF>*7iAWdWaj57fXqxx$}cUkRZ`LiS)vcM1?W9}xc`70cnGED0fVIo
z=zj&zbymP&-RSA!7!uKXcbcJhvZKK9dd~IM6SMd97sx*la5QjDSia-2jeToVYsaF{
z78AFFhYsA;l$o<PLAk5-%%TZ5UaAYcSsxZOD`HP?&LW%dGo8ch4L+omeY5Y6ym8ZM
z@kNCxK`Xx~T+1<Q{=UEEoI=D^qYs=njFsO0`}(MU?YFj#tKu7k6U2YyhI0S4tlO*>
z`>A)c@PpsRuZ}GfKE?jNTI|M){D|$5s}C>+u(i#Z+UUGp%qz#L(t_E{ww`mvFHYA>
zmsh29gn0P>)J(d3vQ_Zq1hW_G#n(k$eY$QkLpNg-_mbtaTyv~!Bd+Ejh;Vpi|3FHj
z>|a}GuH?kGEE2o|72THKBd(r55IE1aHYYMv+KN}+r!D={EUUSUueLVcX4#>z*JiT?
zzd(=gb2;Ur%+UQzj~QROCh+}uaWt*xL@3i0=DKxq0V-y5<$6vS7R;IYcpg{%i`*-F
z1aCHO>27z>bzbatBUNg)pI*%3R|mF;ualTKKX!R_`dWtI1DY3#%DDa%WG_6jY;wDh
z!TX=D-BQl_d+d47oZc)n+fU>A66XaBe;!TT9=GtaWQxZg{WY&x44B^RcF*E3FPbxb
U>Z{2QE`n0Nr>mdKI;Vst0ORyU@&Et;

literal 0
HcmV?d00001

-- 
GitLab