Repository: bitwarden/clients
Pull request #13361: [PM-12681] Enable new Send Add/Edit Dialog on web
Author: login: djsmith85
Created: 2025-02-11T18:07:17Z | Merged: 2025-03-04T15:09:38Z

## 🎟️ Tracking
<!-- Paste the link to the Jira or GitHub issue or otherwise describe / point to where this change is coming from. -->
https://bitwarden.atlassian.net/browse/PM-12681

## 📔 Objective
<!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. -->
Re-using the new Send Add/Edit form, which was introduced with the browser extension refresh, on the web vault.

### Changes
- **Create web-specific new-send-dropdown component -** https://github.com/bitwarden/clients/commit/21ea12ecf105929a37cdef1bf3ccb5531d56c0ec
- **Create web-specifc Send Add/Edit dialog -** https://github.com/bitwarden/clients/commit/85e67e6d613a3d18ebb7afa44226d7637e475a34
- **Use new-send-dropdown and replace old Send Add/Edit with new Add/Edit dialog -** https://github.com/bitwarden/clients/commit/89b4e2cdff14cc66d0154969fd0e135ffbf0f801
- **Delete old Send Add/Edit component -** https://github.com/bitwarden/clients/commit/b80fc5fb2701311004b610fba479cfaceeb99094
- **Remove unused entries from en/messages.json -** https://github.com/bitwarden/clients/commit/d8de1db836fa40c690cd205ace5993508b7e5d82
- **Add cancel button to close dialog -** https://github.com/bitwarden/clients/pull/13361/commits/f75f7bbd7de2dae8d5ee331d77d95b15bb056990 
  - _Added after the screenshots were taken_

## 📸 Screenshots
<!-- Required for any UI changes; delete if not applicable. Use fixed width images for better display. -->
| Before | After |
|--------|--------|
| ![image](https://github.com/user-attachments/assets/db8d289c-9623-4e70-9246-a53ec1c605d7) | ![image](https://github.com/user-attachments/assets/116f5f43-f753-4ea8-b545-b8b419eb2043) |
| ![image](https://github.com/user-attachments/assets/bec55a80-29d7-42d0-8bac-32e612cc138c) | ![image](https://github.com/user-attachments/assets/2e25054e-a126-48cd-b99f-7931aeb7d51a) |
| | ![image](https://github.com/user-attachments/assets/9da63edb-d865-4738-acec-d5c809d2e762) |
| ![image](https://github.com/user-attachments/assets/f1957ce7-f4d0-4005-9613-1cd2c1cafedc) |![image](https://github.com/user-attachments/assets/86c34dcd-d9fd-40ac-84de-9ee5ceb14b3d)
| ![image](https://github.com/user-attachments/assets/1d7b68af-479c-433c-b9b1-dd2b65eb2e9c) | ![image](https://github.com/user-attachments/assets/701e5c6b-5e8a-4c9e-9e6b-cfabde3bfd24) |

## ⏰ Reminders before review

- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

## 🦮 Reviewer guidelines

<!-- Suggested interactions but feel free to use (or not) as you desire! -->

- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes



Diff:
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index bec54cf2c54d..d57cc6bc1b49 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -67,7 +67,6 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f
 import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../tools/reports/pages/organizations/unsecured-websites-report.component";
 import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../tools/reports/pages/organizations/weak-passwords-report.component";
 /* eslint no-restricted-imports: "error" */
-import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
 import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
 import { AddEditCustomFieldsComponent } from "../vault/individual-vault/add-edit-custom-fields.component";
 import { AddEditComponent } from "../vault/individual-vault/add-edit.component";
@@ -148,7 +147,6 @@ import { SharedModule } from "./shared.module";
     SecurityComponent,
     SecurityKeysComponent,
     SelectableAvatarComponent,
-    SendAddEditComponent,
     SetPasswordComponent,
     SponsoredFamiliesComponent,
     SponsoringOrgRowComponent,
@@ -212,7 +210,6 @@ import { SharedModule } from "./shared.module";
     SecurityComponent,
     SecurityKeysComponent,
     SelectableAvatarComponent,
-    SendAddEditComponent,
     SetPasswordComponent,
     SponsoredFamiliesComponent,
     SponsoringOrgRowComponent,
diff --git a/apps/web/src/app/tools/send/add-edit.component.html b/apps/web/src/app/tools/send/add-edit.component.html
deleted file mode 100644
index 7eade18a7c67..000000000000
--- a/apps/web/src/app/tools/send/add-edit.component.html
+++ /dev/null
@@ -1,286 +0,0 @@
-<form
-  [formGroup]="formGroup"
-  [bitSubmit]="submitAndClose"
-  [appApiAction]="formPromise"
-  autocomplete="off"
->
-  <bit-dialog dialogSize="large">
-    <span bitDialogTitle>
-      {{ title }}
-    </span>
-    <span bitDialogContent *ngIf="send">
-      <bit-callout *ngIf="disableSend">
-        {{ "sendDisabledWarning" | i18n }}
-      </bit-callout>
-      <bit-callout *ngIf="!disableSend && disableHideEmail">
-        {{ "sendOptionsPolicyInEffect" | i18n }}
-        <ul class="tw-mb-0">
-          <li>{{ "sendDisableHideEmailInEffect" | i18n }}</li>
-        </ul>
-      </bit-callout>
-      <bit-form-field class="tw-w-1/2">
-        <bit-label for="name">{{ "name" | i18n }}</bit-label>
-        <input bitInput type="text" formControlName="name" />
-        <bit-hint>{{ "sendNameDesc" | i18n }}</bit-hint>
-      </bit-form-field>
-      <div class="tw-flex" *ngIf="!editMode">
-        <bit-radio-group formControlName="type">
-          <bit-label>{{ "whatTypeOfSend" | i18n }}</bit-label>
-
-          <bit-radio-button
-            *ngFor="let o of typeOptions"
-            id="type_{{ o.value }}"
-            class="tw-block"
-            [value]="o.value"
-            [disabled]="!canAccessPremium && o.premium"
-          >
-            <bit-label>
-              {{ o.name }}
-              <app-premium-badge
-                class="tw-mx-1"
-                *ngIf="!canAccessPremium && o.premium"
-                slot="end"
-              ></app-premium-badge>
-            </bit-label>
-          </bit-radio-button>
-        </bit-radio-group>
-      </div>
-      <!-- Text -->
-      <ng-container *ngIf="type === sendType.Text">
-        <bit-form-field>
-          <bit-label for="text">{{ "sendTypeText" | i18n }}</bit-label>
-          <textarea bitInput id="text" rows="6" formControlName="text"></textarea>
-          <bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
-        </bit-form-field>
-        <bit-form-control>
-          <input bitCheckbox type="checkbox" formControlName="textHidden" />
-          <bit-label>{{ "textHiddenByDefault" | i18n }}</bit-label>
-        </bit-form-control>
-      </ng-container>
-      <!-- File -->
-      <ng-container *ngIf="type === sendType.File">
-        <div class="tw-flex">
-          <div *ngIf="editMode">
-            <bit-label>{{ "file" | i18n }}</bit-label>
-            <p bitTypography="body1" class="tw-mb-0">
-              {{ send.file.fileName }} ({{ send.file.sizeName }})
-            </p>
-          </div>
-          <bit-form-field *ngIf="!editMode">
-            <bit-label>{{ "file" | i18n }}</bit-label>
-            <div>
-              <button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
-                {{ "chooseFile" | i18n }}
-              </button>
-              {{ selectedFile?.name ?? ("noFileChosen" | i18n) }}
-            </div>
-            <input
-              bitInput
-              #fileSelector
-              type="file"
-              id="file"
-              name="file"
-              formControlName="file"
-              (change)="setSelectedFile($event)"
-              hidden
-              class="tw-hidden"
-            />
-            <bit-hint>{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}</bit-hint>
-          </bit-form-field>
-        </div>
-      </ng-container>
-      <h4 bitTypography="h4" class="tw-mt-5">{{ "share" | i18n }}</h4>
-
-      <bit-form-field *ngIf="link">
-        <bit-label for="link">{{ "sendLinkLabel" | i18n }}</bit-label>
-        <input bitInput type="text" readonly formControlName="link" />
-      </bit-form-field>
-
-      <bit-form-control>
-        <input bitCheckbox type="checkbox" formControlName="copyLink" />
-        <bit-label>{{ "copySendLinkOnSave" | i18n }}</bit-label>
-      </bit-form-control>
-      <div class="tw-mt-5 tw-flex" (click)="toggleOptions()">
-        <h4 bitTypography="h4" class="tw-mb-0 tw-mr-2">
-          <button type="button" bitLink appStopClick [attr.aria-expanded]="showOptions">
-            <i
-              class="bwi"
-              aria-hidden="true"
-              [ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
-            ></i>
-            {{ "options" | i18n }}
-          </button>
-        </h4>
-      </div>
-      <div id="options" [hidden]="!showOptions">
-        <div class="tw-flex">
-          <div *ngIf="!editMode" class="tw-w-1/2 tw-pr-3">
-            <bit-form-field>
-              <bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
-              <bit-select
-                id="deletionDate"
-                name="SelectedDeletionDatePreset"
-                formControlName="selectedDeletionDatePreset"
-              >
-                <bit-option
-                  *ngFor="let o of deletionDatePresets"
-                  [value]="o.value"
-                  [label]="o.name"
-                ></bit-option>
-              </bit-select>
-              <ng-container *ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0">
-                <input
-                  bitInput
-                  id="deletionDateCustom"
-                  type="datetime-local"
-                  name="DeletionDate"
-                  formControlName="defaultDeletionDateTime"
-                  placeholder="MM/DD/YYYY HH:MM AM/PM"
-                />
-              </ng-container>
-              <bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
-            </bit-form-field>
-          </div>
-          <div *ngIf="editMode" class="tw-w-1/2 tw-pr-3">
-            <bit-form-field>
-              <bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
-              <input
-                bitInput
-                id="deletionDateCustom"
-                type="datetime-local"
-                name="DeletionDate"
-                formControlName="defaultDeletionDateTime"
-                placeholder="MM/DD/YYYY HH:MM AM/PM"
-              />
-              <bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
-            </bit-form-field>
-          </div>
-          <div *ngIf="!editMode" class="tw-w-1/2 tw-pl-3">
-            <bit-form-field>
-              <bit-label for="expirationDate">
-                {{ "expirationDate" | i18n }}
-              </bit-label>
-              <bit-select
-                bitInput
-                id="expirationDate"
-                name="SelectedExpirationDatePreset"
-                formControlName="selectedExpirationDatePreset"
-              >
-                <bit-option
-                  *ngFor="let e of expirationDatePresets"
-                  [value]="e.value"
-                  [label]="e.name"
-                ></bit-option>
-              </bit-select>
-              <ng-container *ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0">
-                <input
-                  bitInput
-                  id="expirationDateCustom"
-                  type="datetime-local"
-                  name="ExpirationDate"
-                  formControlName="defaultExpirationDateTime"
-                  placeholder="MM/DD/YYYY HH:MM AM/PM"
-                />
-              </ng-container>
-              <bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
-            </bit-form-field>
-          </div>
-          <div *ngIf="editMode" class="tw-w-1/2 tw-pl-3">
-            <bit-form-field>
-              <bit-label class="tw-flex" for="expirationDate">
-                {{ "expirationDate" | i18n }}
-
-                <button
-                  type="button"
-                  bitLink
-                  appStopClick
-                  (click)="clearExpiration()"
-                  *ngIf="!disableSend"
-                  class="tw-ml-auto"
-                  slot="end"
-                >
-                  {{ "clear" | i18n }}
-                </button>
-              </bit-label>
-              <input
-                bitInput
-                id="expirationDateCustom"
-                type="datetime-local"
-                name="ExpirationDate"
-                formControlName="defaultExpirationDateTime"
-                placeholder="MM/DD/YYYY HH:MM AM/PM"
-              />
-              <bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
-            </bit-form-field>
-          </div>
-        </div>
-        <div class="tw-flex">
-          <bit-form-field class="tw-w-1/2 tw-pr-3">
-            <bit-label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</bit-label>
-            <input bitInput type="number" formControlName="maxAccessCount" min="1" />
-            <bit-hint>{{ "maxAccessCountDesc" | i18n }}</bit-hint>
-          </bit-form-field>
-          <bit-form-field class="tw-w-1/2 tw-pl-3" *ngIf="editMode">
-            <bit-label for="accessCount">{{ "currentAccessCount" | i18n }}</bit-label>
-            <input bitInput type="text" formControlName="accessCount" readonly />
-          </bit-form-field>
-        </div>
-        <div class="tw-flex">
-          <bit-form-field class="tw-w-1/2 tw-pr-3">
-            <bit-label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</bit-label>
-            <bit-label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</bit-label>
-
-            <input bitInput type="password" formControlName="password" />
-            <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
-            <bit-hint>{{ "sendPasswordDesc" | i18n }}</bit-hint>
-          </bit-form-field>
-        </div>
-        <bit-form-field>
-          <bit-label>{{ "notes" | i18n }}</bit-label>
-          <textarea bitInput formControlName="notes" rows="6"></textarea>
-          <bit-hint>{{ "sendNotesDesc" | i18n }}</bit-hint>
-        </bit-form-field>
-
-        <bit-form-control>
-          <input bitCheckbox type="checkbox" formControlName="hideEmail" />
-          <bit-label>{{ "hideEmail" | i18n }}</bit-label>
-        </bit-form-control>
-
-        <bit-form-control>
-          <input bitCheckbox type="checkbox" formControlName="disabled" />
-          <bit-label>{{ "disableThisSend" | i18n }}</bit-label>
-        </bit-form-control>
-      </div>
-    </span>
-    <ng-container bitDialogFooter>
-      <button
-        type="submit"
-        bitButton
-        bitFormButton
-        [appA11yTitle]="'save' | i18n"
-        buttonType="primary"
-      >
-        {{ "save" | i18n }}
-      </button>
-      <button
-        type="button"
-        bitButton
-        buttonType="secondary"
-        [appA11yTitle]="'cancel' | i18n"
-        bitDialogClose
-      >
-        {{ "cancel" | i18n }}
-      </button>
-
-      <button
-        *ngIf="editMode"
-        type="button"
-        class="tw-ml-auto"
-        bitIconButton="bwi-trash"
-        buttonType="danger"
-        [appA11yTitle]="'delete' | i18n"
-        [bitAction]="deleteAndClose"
-      ></button>
-    </ng-container>
-  </bit-dialog>
-</form>
diff --git a/apps/web/src/app/tools/send/add-edit.component.ts b/apps/web/src/app/tools/send/add-edit.component.ts
deleted file mode 100644
index 4ce126a33bcb..000000000000
--- a/apps/web/src/app/tools/send/add-edit.component.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
-import { DatePipe } from "@angular/common";
-import { Component, Inject } from "@angular/core";
-import { FormBuilder } from "@angular/forms";
-
-import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
-import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
-import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
-import { DialogService, ToastService } from "@bitwarden/components";
-
-@Component({
-  selector: "app-send-add-edit",
-  templateUrl: "add-edit.component.html",
-})
-export class AddEditComponent extends BaseAddEditComponent {
-  override componentName = "app-send-add-edit";
-  protected selectedFile: File;
-
-  constructor(
-    i18nService: I18nService,
-    platformUtilsService: PlatformUtilsService,
-    environmentService: EnvironmentService,
-    datePipe: DatePipe,
-    sendService: SendService,
-    stateService: StateService,
-    messagingService: MessagingService,
-    policyService: PolicyService,
-    logService: LogService,
-    sendApiService: SendApiService,
-    dialogService: DialogService,
-    formBuilder: FormBuilder,
-    billingAccountProfileStateService: BillingAccountProfileStateService,
-    protected dialogRef: DialogRef,
-    @Inject(DIALOG_DATA) params: { sendId: string },
-    accountService: AccountService,
-    toastService: ToastService,
-  ) {
-    super(
-      i18nService,
-      platformUtilsService,
-      environmentService,
-      datePipe,
-      sendService,
-      messagingService,
-      policyService,
-      logService,
-      stateService,
-      sendApiService,
-      dialogService,
-      formBuilder,
-      billingAccountProfileStateService,
-      accountService,
-      toastService,
-    );
-
-    this.sendId = params.sendId;
-  }
-
-  async copyLinkToClipboard(link: string): Promise<void | boolean> {
-    // Copy function on web depends on the modal being open or not. Since this event occurs during a transition
-    // of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
-    return new Promise((resolve) => {
-      window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500);
-    });
-  }
-
-  protected setSelectedFile(event: Event) {
-    const fileInputEl = <HTMLInputElement>event.target;
-    const file = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null;
-    this.selectedFile = file;
-  }
-
-  submitAndClose = async () => {
-    this.formGroup.markAllAsTouched();
-    if (this.formGroup.invalid) {
-      return;
-    }
-
-    const success = await this.submit();
-    if (success) {
-      this.dialogRef.close();
-    }
-  };
-
-  deleteAndClose = async () => {
-    const success = await this.delete();
-    if (success) {
-      this.dialogRef.close();
-    }
-  };
-}
diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html
new file mode 100644
index 000000000000..34e28be10846
--- /dev/null
+++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html
@@ -0,0 +1,23 @@
+<button bitButton [bitMenuTriggerFor]="itemOptions" buttonType="primary" type="button">
+  <i *ngIf="!hideIcon" class="bwi bwi-plus-f" aria-hidden="true"></i>
+  {{ (hideIcon ? "createSend" : "new") | i18n }}
+</button>
+<bit-menu #itemOptions>
+  <a bitMenuItem (click)="createSend(sendType.Text)">
+    <i class="bwi bwi-file-text" slot="start" aria-hidden="true"></i>
+    {{ "sendTypeText" | i18n }}
+  </a>
+  <a bitMenuItem (click)="createSend(sendType.File)">
+    <i class="bwi bwi-file" slot="start" aria-hidden="true"></i>
+    {{ "sendTypeFile" | i18n }}
+    <button
+      type="button"
+      slot="end"
+      *ngIf="!(canAccessPremium$ | async)"
+      bitBadge
+      variant="success"
+    >
+      {{ "premium" | i18n }}
+    </button>
+  </a>
+</bit-menu>
diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
new file mode 100644
index 000000000000..2fc83a5d03b5
--- /dev/null
+++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
@@ -0,0 +1,65 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom, Observable, of, switchMap } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+/**
+ * A dropdown component that allows the user to create a new Send of a specific type.
+ */
+export class NewSendDropdownComponent {
+  /** If true, the plus icon will be hidden */
+  @Input() hideIcon: boolean = false;
+
+  /** SendType provided for the markup to pass back the selected type of Send */
+  protected sendType = SendType;
+
+  /** Indicates whether the user can access premium features. */
+  protected canAccessPremium$: Observable<boolean>;
+
+  constructor(
+    private router: Router,
+    private billingAccountProfileStateService: BillingAccountProfileStateService,
+    private accountService: AccountService,
+    private dialogService: DialogService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
+  ) {
+    this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
+      switchMap((account) =>
+        account
+          ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id)
+          : of(false),
+      ),
+    );
+  }
+
+  /**
+   * Opens the SendAddEditComponent for a new Send with the provided type.
+   * If has user does not have premium access and the type is File, the user will be redirected to the premium settings page.
+   * @param type The type of Send to create.
+   */
+  async createSend(type: SendType) {
+    if (!(await firstValueFrom(this.canAccessPremium$)) && type === SendType.File) {
+      return await this.router.navigate(["settings/subscription/premium"]);
+    }
+
+    const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type);
+
+    await SendAddEditComponent.open(this.dialogService, { formConfig });
+  }
+}
diff --git a/apps/web/src/app/tools/send/send-add-edit.component.html b/apps/web/src/app/tools/send/send-add-edit.component.html
new file mode 100644
index 000000000000..d3f3ebedf499
--- /dev/null
+++ b/apps/web/src/app/tools/send/send-add-edit.component.html
@@ -0,0 +1,35 @@
+<bit-dialog #dialog dialogSize="large" background="alt">
+  <span bitDialogTitle>
+    {{ headerText }}
+  </span>
+  <span bitDialogContent>
+    <tools-send-form
+      formId="sendForm"
+      [config]="config"
+      (onSendCreated)="onSendCreated($event)"
+      (onSendUpdated)="onSendUpdated($event)"
+      [submitBtn]="submitBtn"
+    >
+    </tools-send-form>
+  </span>
+
+  <ng-container bitDialogFooter>
+    <button bitButton type="submit" form="sendForm" buttonType="primary" #submitBtn>
+      {{ "save" | i18n }}
+    </button>
+    <button bitButton type="button" buttonType="secondary" bitDialogClose>
+      {{ "cancel" | i18n }}
+    </button>
+    <div class="tw-ml-auto">
+      <button
+        *ngIf="config?.mode !== 'add'"
+        type="button"
+        buttonType="danger"
+        slot="end"
+        bitIconButton="bwi-trash"
+        [bitAction]="deleteSend"
+        appA11yTitle="{{ 'delete' | i18n }}"
+      ></button>
+    </div>
+  </ng-container>
+</bit-dialog>
diff --git a/apps/web/src/app/tools/send/send-add-edit.component.ts b/apps/web/src/app/tools/send/send-add-edit.component.ts
new file mode 100644
index 000000000000..490683a4b4d8
--- /dev/null
+++ b/apps/web/src/app/tools/send/send-add-edit.component.ts
@@ -0,0 +1,176 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
+import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { Component, Inject } from "@angular/core";
+import { FormsModule } from "@angular/forms";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
+import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
+import {
+  AsyncActionsModule,
+  ButtonModule,
+  DialogService,
+  IconButtonModule,
+  SearchModule,
+  ToastService,
+  DialogModule,
+} from "@bitwarden/components";
+import { SendFormConfig, SendFormMode, SendFormModule } from "@bitwarden/send-ui";
+
+export interface SendItemDialogParams {
+  /**
+   * The configuration object for the dialog and form.
+   */
+  formConfig: SendFormConfig;
+
+  /**
+   * If true, the "edit" button will be disabled in the dialog.
+   */
+  disableForm?: boolean;
+}
+
+export enum SendItemDialogResult {
+  /**
+   * A Send was saved (created or updated).
+   */
+  Saved = "saved",
+
+  /**
+   * A Send was deleted.
+   */
+  Deleted = "deleted",
+}
+
+/**
+ * Component for adding or editing a send item.
+ */
+@Component({
+  selector: "tools-send-add-edit",
+  templateUrl: "send-add-edit.component.html",
+  standalone: true,
+  imports: [
+    CommonModule,
+    SearchModule,
+    JslibModule,
+    FormsModule,
+    ButtonModule,
+    IconButtonModule,
+    SendFormModule,
+    AsyncActionsModule,
+    DialogModule,
+  ],
+})
+export class SendAddEditComponent {
+  /**
+   * The header text for the component.
+   */
+  headerText: string;
+
+  /**
+   * The configuration for the send form.
+   */
+  config: SendFormConfig;
+
+  constructor(
+    @Inject(DIALOG_DATA) protected params: SendItemDialogParams,
+    private dialogRef: DialogRef<SendItemDialogResult>,
+    private i18nService: I18nService,
+    private sendApiService: SendApiService,
+    private toastService: ToastService,
+    private dialogService: DialogService,
+  ) {
+    this.config = params.formConfig;
+    this.headerText = this.getHeaderText(this.config.mode, this.config.sendType);
+  }
+
+  /**
+   * Handles the event when the send is created.
+   */
+  async onSendCreated(send: SendView) {
+    // FIXME Add dialogService.open send-created dialog
+    this.dialogRef.close(SendItemDialogResult.Saved);
+    return;
+  }
+
+  /**
+   * Handles the event when the send is updated.
+   */
+  async onSendUpdated(send: SendView) {
+    this.dialogRef.close(SendItemDialogResult.Saved);
+  }
+
+  /**
+   * Handles the event when the send is deleted.
+   */
+  async onSendDeleted() {
+    this.dialogRef.close(SendItemDialogResult.Deleted);
+
+    this.toastService.showToast({
+      variant: "success",
+      title: null,
+      message: this.i18nService.t("deletedSend"),
+    });
+  }
+
+  /**
+   * Handles the deletion of the current Send.
+   */
+  deleteSend = async () => {
+    const confirmed = await this.dialogService.openSimpleDialog({
+      title: { key: "deleteSend" },
+      content: { key: "deleteSendPermanentConfirmation" },
+      type: "warning",
+    });
+
+    if (!confirmed) {
+      return;
+    }
+
+    try {
+      await this.sendApiService.delete(this.config.originalSend?.id);
+    } catch (e) {
+      this.toastService.showToast({
+        variant: "error",
+        title: null,
+        message: e.message,
+      });
+      return;
+    }
+
+    await this.onSendDeleted();
+  };
+
+  /**
+   * Gets the header text based on the mode and type.
+   * @param mode The mode of the send form.
+   * @param type The type of the send
+   * @returns The header text.
+   */
+  private getHeaderText(mode: SendFormMode, type: SendType) {
+    const headerKey =
+      mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader";
+
+    switch (type) {
+      case SendType.Text:
+        return this.i18nService.t(headerKey, this.i18nService.t("textSend"));
+      case SendType.File:
+        return this.i18nService.t(headerKey, this.i18nService.t("fileSend"));
+    }
+  }
+
+  /**
+   * Opens the send add/edit dialog.
+   * @param dialogService Instance of the DialogService.
+   * @param params The parameters for the dialog.
+   * @returns The dialog result.
+   */
+  static open(dialogService: DialogService, params: SendItemDialogParams) {
+    return dialogService.open<SendItemDialogResult, SendItemDialogParams>(SendAddEditComponent, {
+      data: params,
+    });
+  }
+}
diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html
index 7aab50b33e59..6f690459bb08 100644
--- a/apps/web/src/app/tools/send/send.component.html
+++ b/apps/web/src/app/tools/send/send.component.html
@@ -11,11 +11,7 @@
       </ng-container>
     </small>
   </ng-container>
-
-  <button type="button" bitButton buttonType="primary" (click)="addSend()" [disabled]="disableSend">
-    <i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
-    {{ "createSend" | i18n }}
-  </button>
+  <tools-new-send-dropdown *ngIf="!disableSend"></tools-new-send-dropdown>
 </app-header>
 
 <bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
@@ -198,10 +194,11 @@ <h3>{{ "types" | i18n }}</h3>
         <bit-no-items [icon]="noItemIcon" class="tw-text-main">
           <ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
           <ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
-          <button slot="button" type="button" bitButton buttonType="secondary" (click)="addSend()">
-            <i class="bwi bwi-plus" aria-hidden="true"></i>
-            {{ "createSend" | i18n }}
-          </button>
+          <tools-new-send-dropdown
+            [hideIcon]="true"
+            *ngIf="!disableSend"
+            slot="button"
+          ></tools-new-send-dropdown>
         </bit-no-items>
       </ng-container>
     </div>
diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts
index 1268e4bfb505..1d4e6305bdc7 100644
--- a/apps/web/src/app/tools/send/send.component.ts
+++ b/apps/web/src/app/tools/send/send.component.ts
@@ -1,6 +1,7 @@
 // FIXME: Update this file to be type safe and remove this and next line
 // @ts-strict-ignore
-import { Component, NgZone, ViewChild, OnInit, OnDestroy, ViewContainerRef } from "@angular/core";
+import { DialogRef } from "@angular/cdk/dialog";
+import { Component, NgZone, OnInit, OnDestroy } from "@angular/core";
 import { lastValueFrom } from "rxjs";
 
 import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
@@ -14,6 +15,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
 import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
 import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
 import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
+import { SendId } from "@bitwarden/common/types/guid";
 import {
   DialogService,
   NoItemsModule,
@@ -21,24 +23,25 @@ import {
   TableDataSource,
   ToastService,
 } from "@bitwarden/components";
-import { NoSendsIcon } from "@bitwarden/send-ui";
+import { DefaultSendFormConfigService, NoSendsIcon, SendFormConfig } from "@bitwarden/send-ui";
 
 import { HeaderModule } from "../../layouts/header/header.module";
 import { SharedModule } from "../../shared";
 
-import { AddEditComponent } from "./add-edit.component";
+import { NewSendDropdownComponent } from "./new-send/new-send-dropdown.component";
+import { SendAddEditComponent, SendItemDialogResult } from "./send-add-edit.component";
 
 const BroadcasterSubscriptionId = "SendComponent";
 
 @Component({
   selector: "app-send",
   standalone: true,
-  imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule],
+  imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent],
   templateUrl: "send.component.html",
+  providers: [DefaultSendFormConfigService],
 })
 export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
-  @ViewChild("sendAddEdit", { read: ViewContainerRef, static: true })
-  sendAddEditModalRef: ViewContainerRef;
+  private sendItemDialogRef?: DialogRef<SendItemDialogResult> | undefined;
   noItemIcon = NoSendsIcon;
 
   override set filteredSends(filteredSends: SendView[]) {
@@ -65,6 +68,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
     sendApiService: SendApiService,
     dialogService: DialogService,
     toastService: ToastService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
   ) {
     super(
       sendService,
@@ -111,17 +115,41 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
       return;
     }
 
-    await this.editSend(null);
+    const config = await this.addEditFormConfigService.buildConfig("add", null, 0);
+
+    await this.openSendItemDialog(config);
   }
 
   async editSend(send: SendView) {
-    const dialog = this.dialogService.open(AddEditComponent, {
-      data: {
-        sendId: send == null ? null : send.id,
-      },
+    const config = await this.addEditFormConfigService.buildConfig(
+      send == null ? "add" : "edit",
+      send == null ? null : (send.id as SendId),
+      send.type,
+    );
+
+    await this.openSendItemDialog(config);
+  }
+
+  /**
+   * Opens the send item dialog.
+   * @param formConfig The form configuration.
+   * */
+  async openSendItemDialog(formConfig: SendFormConfig) {
+    // Prevent multiple dialogs from being opened.
+    if (this.sendItemDialogRef) {
+      return;
+    }
+
+    this.sendItemDialogRef = SendAddEditComponent.open(this.dialogService, {
+      formConfig,
     });
 
-    await lastValueFrom(dialog.closed);
-    await this.load();
+    const result = await lastValueFrom(this.sendItemDialogRef.closed);
+    this.sendItemDialogRef = undefined;
+
+    // If the dialog was closed by deleting the cipher, refresh the vault.
+    if (result === SendItemDialogResult.Deleted || result === SendItemDialogResult.Saved) {
+      await this.load();
+    }
   }
 }
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 22a67cbff281..da237a8a3ab0 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -222,6 +222,9 @@
   "notes": {
     "message": "Notes"
   },
+  "privateNote": {
+    "message": "Private note"
+  },
   "note": {
     "message": "Note"
   },
@@ -5105,12 +5108,40 @@
   "requireSsoExemption": {
     "message": "Organization owners and admins are exempt from this policy's enforcement."
   },
+  "limitSendViews": {
+    "message": "Limit views"
+  },
+  "limitSendViewsHint": {
+    "message": "No one can view this Send after the limit is reached.",
+    "description": "Displayed under the limit views field on Send"
+  },
+  "limitSendViewsCount": {
+    "message": "$ACCESSCOUNT$ views left",
+    "description": "Displayed under the limit views field on Send",
+    "placeholders": {
+      "accessCount": {
+        "content": "$1",
+        "example": "2"
+      }
+    }
+  },
+  "sendDetails": {
+    "message": "Send details",
+    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+  },
+  "sendTypeTextToShare": {
+    "message": "Text to share"
+  },
   "sendTypeFile": {
     "message": "File"
   },
   "sendTypeText": {
     "message": "Text"
   },
+  "sendPasswordDescV3": {
+    "message": "Add an optional password for recipients to access this Send.",
+    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+  },
   "createSend": {
     "message": "New Send",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -5135,19 +5166,15 @@
     "message": "Delete Send",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
   },
-  "deleteSendConfirmation": {
-    "message": "Are you sure you want to delete this Send?",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
-  "whatTypeOfSend": {
-    "message": "What type of Send is this?",
+  "deleteSendPermanentConfirmation": {
+    "message": "Are you sure you want to permanently delete this Send?",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
   },
   "deletionDate": {
     "message": "Deletion date"
   },
-  "deletionDateDesc": {
-    "message": "The Send will be permanently deleted on the specified date and time.",
+  "deletionDateDescV2": {
+    "message": "The Send will be permanently deleted on this date.",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
   },
   "expirationDate": {
@@ -5160,21 +5187,6 @@
   "maxAccessCount": {
     "message": "Maximum access count"
   },
-  "maxAccessCountDesc": {
-    "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
-  "currentAccessCount": {
-    "message": "Current access count"
-  },
-  "sendPasswordDesc": {
-    "message": "Optionally require a password for users to access this Send.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
-  "sendNotesDesc": {
-    "message": "Private notes about this Send.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
   "disabled": {
     "message": "Disabled"
   },
@@ -5201,13 +5213,6 @@
   "removePasswordConfirmation": {
     "message": "Are you sure you want to remove the password?"
   },
-  "hideEmail": {
-    "message": "Hide my email address from recipients."
-  },
-  "disableThisSend": {
-    "message": "Deactivate this Send so that no one can access it.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
   "allSends": {
     "message": "All Sends"
   },
@@ -5218,6 +5223,9 @@
   "pendingDeletion": {
     "message": "Pending deletion"
   },
+  "hideTextByDefault": {
+    "message": "Hide text by default"
+  },
   "expired": {
     "message": "Expired"
   },
@@ -5439,13 +5447,6 @@
     "message": "Always show member’s email address with recipients when creating or editing a Send.",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
   },
-  "sendOptionsPolicyInEffect": {
-    "message": "The following organization policies are currently in effect:"
-  },
-  "sendDisableHideEmailInEffect": {
-    "message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
   "modifiedPolicyId": {
     "message": "Modified policy $ID$.",
     "placeholders": {
@@ -5545,27 +5546,6 @@
   "personalOwnershipCheckboxDesc": {
     "message": "Remove individual ownership for organization users"
   },
-  "textHiddenByDefault": {
-    "message": "When accessing the Send, hide the text by default",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
-  "sendNameDesc": {
-    "message": "A friendly name to describe this Send.",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
-  "sendTextDesc": {
-    "message": "The text you want to Send."
-  },
-  "sendFileDesc": {
-    "message": "The file you want to Send."
-  },
-  "copySendLinkOnSave": {
-    "message": "Copy the link to share this Send to my clipboard upon save."
-  },
-  "sendLinkLabel": {
-    "message": "Send link",
-    "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
-  },
   "send": {
     "message": "Send",
     "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -5714,6 +5694,9 @@
   "dateParsingError": {
     "message": "There was an error saving your deletion and expiration dates."
   },
+  "hideYourEmail": {
+    "message": "Hide your email address from viewers."
+  },
   "webAuthnFallbackMsg": {
     "message": "To verify your 2FA please click the button below."
   },
@@ -9875,9 +9858,15 @@
   "learnMoreAboutApi": {
     "message": "Learn more about Bitwarden's API"
   },
+  "fileSend": {
+    "message": "File Send"
+  },
   "fileSends": {
     "message": "File Sends"
   },
+  "textSend": {
+    "message": "Text Send"
+  },
   "textSends": {
     "message": "Text Sends"
   },


Reviews:
user: login: coroiu
body: Approving platform changes
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T08:19:39Z
user: login: audreyality
body: 👍🏻 Overall this is looking great! I pointed out a few things I was happy to see in the review.

📝 There's a bunch of nits and some questions.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-12T17:43:56Z
user: login: coroiu
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T08:14:25Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T12:30:04Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T12:30:30Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T12:32:45Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T12:34:47Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T12:35:15Z
user: login: audreyality
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-13T15:19:47Z
user: login: audreyality
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-02-13T15:21:35Z
user: login: coroiu
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-14T07:43:13Z
user: login: coroiu
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:21:16Z
user: login: coroiu
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:21:25Z
user: login: audreyality
body: Just some nits
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:47:31Z
user: login: audreyality
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:50:40Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T14:26:57Z
user: login: djsmith85
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T14:27:04Z
user: login: coroiu
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T14:27:38Z
user: login: audreyality
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-04T15:09:36Z

Review comments:
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: audreyality
body: ⛏️ public and protected members should be documented.
created_at: 2025-02-12T17:32:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
+
+  constructor(
+    private router: Router,
+    private billingAccountProfileStateService: BillingAccountProfileStateService,
+    private accountService: AccountService,
+    private dialogService: DialogService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
+  ) {}
+
+  async ngOnInit() {
+    const account = await firstValueFrom(this.accountService.activeAccount$);
+    if (!account) {
+      this.hasNoPremium = true;
+      return;
+    }
+
+    this.hasNoPremium = !(await firstValueFrom(
+      this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
+    ));
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: audreyality
body: 🎨 Consider subscribing to `hasPremiumFromAnySource$` so that when premium status changes, the control updates immediately.
created_at: 2025-02-12T17:34:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,165 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
+import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { Component, Inject } from "@angular/core";
+import { FormsModule } from "@angular/forms";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
+import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
+import {
+  AsyncActionsModule,
+  ButtonModule,
+  DialogService,
+  IconButtonModule,
+  SearchModule,
+  ToastService,
+  DialogModule,
+} from "@bitwarden/components";
+import { SendFormConfig, SendFormMode, SendFormModule } from "@bitwarden/send-ui";
+
+export interface SendItemDialogParams {
+  /**
+   * The configuration object for the dialog and form.
+   */
+  formConfig: SendFormConfig;
+
+  /**
+   * If true, the "edit" button will be disabled in the dialog.
+   */
+  disableForm?: boolean;
path: apps/web/src/app/tools/send/send-add-edit.component.ts
user: login: audreyality
body: 👍🏻 Love the documentation!
created_at: 2025-02-12T17:35:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+<button bitButton [bitMenuTriggerFor]="itemOptions" buttonType="primary" type="button">
+  <i *ngIf="!hideIcon" class="bwi bwi-plus-f" aria-hidden="true"></i>
+  {{ (hideIcon ? "createSend" : "new") | i18n }}
+</button>
+<bit-menu #itemOptions>
+  <a bitMenuItem (click)="createSend(sendType.Text)">
+    <i class="bwi bwi-file-text" slot="start" aria-hidden="true"></i>
+    {{ "sendTypeText" | i18n }}
+  </a>
+  <a bitMenuItem (click)="createSend(sendType.File)">
+    <i class="bwi bwi-file" slot="start" aria-hidden="true"></i>
+    {{ "sendTypeFile" | i18n }}
+    <button type="button" slot="end" *ngIf="hasNoPremium" bitBadge variant="success">
+      {{ "premium" | i18n }}
+    </button>
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html
user: login: audreyality
body: 👍🏻 Yay! Premium badge shows when they need to subscribe!
created_at: 2025-02-12T17:39:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,165 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
+import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { Component, Inject } from "@angular/core";
+import { FormsModule } from "@angular/forms";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
+import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
+import {
+  AsyncActionsModule,
+  ButtonModule,
+  DialogService,
+  IconButtonModule,
+  SearchModule,
+  ToastService,
+  DialogModule,
+} from "@bitwarden/components";
+import { SendFormConfig, SendFormMode, SendFormModule } from "@bitwarden/send-ui";
+
+export interface SendItemDialogParams {
+  /**
+   * The configuration object for the dialog and form.
+   */
+  formConfig: SendFormConfig;
+
+  /**
+   * If true, the "edit" button will be disabled in the dialog.
+   */
+  disableForm?: boolean;
+}
+
+export enum SendItemDialogResult {
+  /**
+   * A Send was saved (created or updated).
+   */
+  Saved = "saved",
+
+  /**
+   * A Send was deleted.
+   */
+  Deleted = "deleted",
+}
+
+/**
+ * Component for adding or editing a send item.
+ */
+@Component({
+  selector: "tools-send-add-edit",
+  templateUrl: "send-add-edit.component.html",
+  standalone: true,
+  imports: [
+    CommonModule,
+    SearchModule,
+    JslibModule,
+    FormsModule,
+    ButtonModule,
+    IconButtonModule,
+    SendFormModule,
+    AsyncActionsModule,
+    DialogModule,
+  ],
+})
+export class SendAddEditComponent {
+  /**
+   * The header text for the component.
+   */
+  headerText: string;
+
+  /**
+   * The configuration for the send form.
+   */
+  config: SendFormConfig;
+
+  constructor(
+    @Inject(DIALOG_DATA) protected params: SendItemDialogParams,
+    private dialogRef: DialogRef<SendItemDialogResult>,
+    private i18nService: I18nService,
+    private sendApiService: SendApiService,
+    private toastService: ToastService,
+    private dialogService: DialogService,
+  ) {
+    this.config = params.formConfig;
+    this.headerText = this.getHeaderText(this.config.mode, this.config.sendType);
+  }
+
+  /**
+   * Handles the event when the send is created.
+   */
+  async onSendCreated(send: SendView) {
+    // FIXME Add dialogService.open send-created dialog
+    this.dialogRef.close(SendItemDialogResult.Saved);
+    return;
+  }
+
+  /**
+   * Handles the event when the send is updated.
+   */
+  async onSendUpdated(_: SendView) {
+    this.dialogRef.close(SendItemDialogResult.Saved);
+  }
+
+  deleteSend = async () => {
+    const confirmed = await this.dialogService.openSimpleDialog({
+      title: { key: "deleteSend" },
+      content: { key: "deleteSendPermanentConfirmation" },
+      type: "warning",
+    });
+
+    if (!confirmed) {
+      return;
+    }
+
+    try {
+      await this.sendApiService.delete(this.config.originalSend?.id);
+    } catch (e) {
+      this.toastService.showToast({
+        variant: "error",
+        title: null,
+        message: e.message,
+      });
+      return;
+    }
+
+    this.dialogRef.close(SendItemDialogResult.Deleted);
+
+    this.toastService.showToast({
+      variant: "success",
+      title: null,
+      message: this.i18nService.t("deletedSend"),
+    });
+  };
+
+  /**
+   * Gets the header text based on the mode and type.
+   * @param mode The mode of the send form.
+   * @param type The type of the send
+   * @returns The header text.
+   */
+  private getHeaderText(mode: SendFormMode, type: SendType) {
+    const headerKey =
+      mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader";
+
+    switch (type) {
+      case SendType.Text:
+        return this.i18nService.t(headerKey, this.i18nService.t("textSend"));
+      case SendType.File:
+        return this.i18nService.t(headerKey, this.i18nService.t("fileSend"));
+    }
+  }
+
+  /**
+   * Opens the VaultItemDialog.
+   * @param dialogService
+   * @param params
path: apps/web/src/app/tools/send/send-add-edit.component.ts
user: login: audreyality
body: ⛏️ These should be filled in
created_at: 2025-02-12T17:41:10Z
author_association: MEMBER
path: apps/web/src/app/tools/send/send.component.ts
user: login: audreyality
body: ⛏️ Several files need their public members documented. This one included. (At least the net-new ones.)
created_at: 2025-02-12T17:42:00Z
author_association: MEMBER
path: apps/web/src/locales/en/messages.json
user: login: audreyality
body: ❓ Do any of these keys need to be carried back to the browser extension?
created_at: 2025-02-12T17:42:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: djsmith85
body: Addressed with 2737b585e9d111cbef29c479e65040a8c7c1f45a
created_at: 2025-02-13T12:30:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,165 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
+import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { Component, Inject } from "@angular/core";
+import { FormsModule } from "@angular/forms";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
+import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
+import {
+  AsyncActionsModule,
+  ButtonModule,
+  DialogService,
+  IconButtonModule,
+  SearchModule,
+  ToastService,
+  DialogModule,
+} from "@bitwarden/components";
+import { SendFormConfig, SendFormMode, SendFormModule } from "@bitwarden/send-ui";
+
+export interface SendItemDialogParams {
+  /**
+   * The configuration object for the dialog and form.
+   */
+  formConfig: SendFormConfig;
+
+  /**
+   * If true, the "edit" button will be disabled in the dialog.
+   */
+  disableForm?: boolean;
+}
+
+export enum SendItemDialogResult {
+  /**
+   * A Send was saved (created or updated).
+   */
+  Saved = "saved",
+
+  /**
+   * A Send was deleted.
+   */
+  Deleted = "deleted",
+}
+
+/**
+ * Component for adding or editing a send item.
+ */
+@Component({
+  selector: "tools-send-add-edit",
+  templateUrl: "send-add-edit.component.html",
+  standalone: true,
+  imports: [
+    CommonModule,
+    SearchModule,
+    JslibModule,
+    FormsModule,
+    ButtonModule,
+    IconButtonModule,
+    SendFormModule,
+    AsyncActionsModule,
+    DialogModule,
+  ],
+})
+export class SendAddEditComponent {
+  /**
+   * The header text for the component.
+   */
+  headerText: string;
+
+  /**
+   * The configuration for the send form.
+   */
+  config: SendFormConfig;
+
+  constructor(
+    @Inject(DIALOG_DATA) protected params: SendItemDialogParams,
+    private dialogRef: DialogRef<SendItemDialogResult>,
+    private i18nService: I18nService,
+    private sendApiService: SendApiService,
+    private toastService: ToastService,
+    private dialogService: DialogService,
+  ) {
+    this.config = params.formConfig;
+    this.headerText = this.getHeaderText(this.config.mode, this.config.sendType);
+  }
+
+  /**
+   * Handles the event when the send is created.
+   */
+  async onSendCreated(send: SendView) {
+    // FIXME Add dialogService.open send-created dialog
+    this.dialogRef.close(SendItemDialogResult.Saved);
+    return;
+  }
+
+  /**
+   * Handles the event when the send is updated.
+   */
+  async onSendUpdated(_: SendView) {
+    this.dialogRef.close(SendItemDialogResult.Saved);
+  }
+
+  deleteSend = async () => {
+    const confirmed = await this.dialogService.openSimpleDialog({
+      title: { key: "deleteSend" },
+      content: { key: "deleteSendPermanentConfirmation" },
+      type: "warning",
+    });
+
+    if (!confirmed) {
+      return;
+    }
+
+    try {
+      await this.sendApiService.delete(this.config.originalSend?.id);
+    } catch (e) {
+      this.toastService.showToast({
+        variant: "error",
+        title: null,
+        message: e.message,
+      });
+      return;
+    }
+
+    this.dialogRef.close(SendItemDialogResult.Deleted);
+
+    this.toastService.showToast({
+      variant: "success",
+      title: null,
+      message: this.i18nService.t("deletedSend"),
+    });
+  };
+
+  /**
+   * Gets the header text based on the mode and type.
+   * @param mode The mode of the send form.
+   * @param type The type of the send
+   * @returns The header text.
+   */
+  private getHeaderText(mode: SendFormMode, type: SendType) {
+    const headerKey =
+      mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader";
+
+    switch (type) {
+      case SendType.Text:
+        return this.i18nService.t(headerKey, this.i18nService.t("textSend"));
+      case SendType.File:
+        return this.i18nService.t(headerKey, this.i18nService.t("fileSend"));
+    }
+  }
+
+  /**
+   * Opens the VaultItemDialog.
+   * @param dialogService
+   * @param params
path: apps/web/src/app/tools/send/send-add-edit.component.ts
user: login: djsmith85
body: Addressed with 2737b585e9d111cbef29c479e65040a8c7c1f45a
created_at: 2025-02-13T12:30:29Z
author_association: CONTRIBUTOR
path: apps/web/src/app/tools/send/send.component.ts
user: login: djsmith85
body: I agree, requested documentation was added with 2737b585e9d111cbef29c479e65040a8c7c1f45a. I haven't extended the existing component, as I'm planning to replace it. Currently in discussion with Product regarding execution and timeline. This was the first step to introduce the newly created Send UIs into the web-vault.
created_at: 2025-02-13T12:32:45Z
author_association: CONTRIBUTOR
path: apps/web/src/locales/en/messages.json
user: login: djsmith85
body: These are all sourced from the browser extension's en/messages.json, so no.
created_at: 2025-02-13T12:34:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
+
+  constructor(
+    private router: Router,
+    private billingAccountProfileStateService: BillingAccountProfileStateService,
+    private accountService: AccountService,
+    private dialogService: DialogService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
+  ) {}
+
+  async ngOnInit() {
+    const account = await firstValueFrom(this.accountService.activeAccount$);
+    if (!account) {
+      this.hasNoPremium = true;
+      return;
+    }
+
+    this.hasNoPremium = !(await firstValueFrom(
+      this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
+    ));
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: djsmith85
body: Added with fc57d15ad9bd0c26df84aa651da4d00882d21145
created_at: 2025-02-13T12:35:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
+
+  constructor(
+    private router: Router,
+    private billingAccountProfileStateService: BillingAccountProfileStateService,
+    private accountService: AccountService,
+    private dialogService: DialogService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
+  ) {}
+
+  async ngOnInit() {
+    const account = await firstValueFrom(this.accountService.activeAccount$);
+    if (!account) {
+      this.hasNoPremium = true;
+      return;
+    }
+
+    this.hasNoPremium = !(await firstValueFrom(
+      this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
+    ));
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: audreyality
body: ⛏️ Do you need to add `takeUntil(this.destroyed$)` to the operators? It doesn't seem to be triggering the lint, but might be a good idea regardless.
created_at: 2025-02-13T15:19:47Z
author_association: MEMBER
diff_hunk: @@ -111,17 +115,36 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
       return;
     }
 
-    await this.editSend(null);
+    const config = await this.addEditFormConfigService.buildConfig("add", null, 0);
+
+    await this.openSendItemDialog(config);
   }
 
   async editSend(send: SendView) {
-    const dialog = this.dialogService.open(AddEditComponent, {
-      data: {
-        sendId: send == null ? null : send.id,
-      },
+    const config = await this.addEditFormConfigService.buildConfig(
+      send == null ? "add" : "edit",
+      send == null ? null : (send.id as SendId),
+      send.type,
+    );
+
+    await this.openSendItemDialog(config);
+  }
+
+  /**
+   * Opens the send item dialog.
+   * @param formConfig The form configuration.
+   * */
+  async openSendItemDialog(formConfig: SendFormConfig) {
+    this.sendItemDialogRef = SendAddEditComponent.open(this.dialogService, {
path: apps/web/src/app/tools/send/send.component.ts
user: login: audreyality
body: ⛏️ This method should protect against opening a send item dialog multiple times.
created_at: 2025-03-04T13:45:53Z
author_association: MEMBER
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: audreyality
body: ⛏️ This file requires documentation
created_at: 2025-03-04T13:47:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,58 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { Router } from "@angular/router";
+import { firstValueFrom } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
+import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
+
+import { SendAddEditComponent } from "../send-add-edit.component";
+
+@Component({
+  selector: "tools-new-send-dropdown",
+  templateUrl: "new-send-dropdown.component.html",
+  standalone: true,
+  imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+  providers: [DefaultSendFormConfigService],
+})
+export class NewSendDropdownComponent implements OnInit {
+  @Input() hideIcon: boolean = false;
+
+  sendType = SendType;
+
+  hasNoPremium = false;
+
+  constructor(
+    private router: Router,
+    private billingAccountProfileStateService: BillingAccountProfileStateService,
+    private accountService: AccountService,
+    private dialogService: DialogService,
+    private addEditFormConfigService: DefaultSendFormConfigService,
+  ) {}
+
+  async ngOnInit() {
+    const account = await firstValueFrom(this.accountService.activeAccount$);
+    if (!account) {
+      this.hasNoPremium = true;
+      return;
+    }
+
+    this.hasNoPremium = !(await firstValueFrom(
+      this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
+    ));
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: audreyality
body: 🤔 The reason the lint wasn't triggered is because that revision doesn't subscribe to the observable; it moves the `firstValueFrom` to a different location.
created_at: 2025-03-04T13:50:40Z
author_association: MEMBER
path: apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts
user: login: djsmith85
body: Addressed with 4d37835eb74ed9ac555f831b3035b394755bbe80
created_at: 2025-03-04T14:26:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -111,17 +115,36 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
       return;
     }
 
-    await this.editSend(null);
+    const config = await this.addEditFormConfigService.buildConfig("add", null, 0);
+
+    await this.openSendItemDialog(config);
   }
 
   async editSend(send: SendView) {
-    const dialog = this.dialogService.open(AddEditComponent, {
-      data: {
-        sendId: send == null ? null : send.id,
-      },
+    const config = await this.addEditFormConfigService.buildConfig(
+      send == null ? "add" : "edit",
+      send == null ? null : (send.id as SendId),
+      send.type,
+    );
+
+    await this.openSendItemDialog(config);
+  }
+
+  /**
+   * Opens the send item dialog.
+   * @param formConfig The form configuration.
+   * */
+  async openSendItemDialog(formConfig: SendFormConfig) {
+    this.sendItemDialogRef = SendAddEditComponent.open(this.dialogService, {
path: apps/web/src/app/tools/send/send.component.ts
user: login: djsmith85
body: Addressed with 47117370c2254cba7dd6e8d7fc5cc726f3cefbba
created_at: 2025-03-04T14:27:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-02-11T18:09:07Z
body: ## [Codecov](https://app.codecov.io/gh/bitwarden/clients/pull/13361?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) Report
Attention: Patch coverage is `0%` with `84 lines` in your changes missing coverage. Please review.
> Project coverage is 35.86%. Comparing base [(`0d68d22`)](https://app.codecov.io/gh/bitwarden/clients/commit/0d68d22b98def5f36b00e7fe57ccd310f44c9e8d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) to head [(`c605304`)](https://app.codecov.io/gh/bitwarden/clients/commit/c60530416556e0c66f5911e16e3e29afb600db80?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).
> Report is 1 commits behind head on main.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/bitwarden/clients/pull/13361?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) | Patch % | Lines |
|---|---|---|
| [.../web/src/app/tools/send/send-add-edit.component.ts](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Ftools%2Fsend%2Fsend-add-edit.component.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-YXBwcy93ZWIvc3JjL2FwcC90b29scy9zZW5kL3NlbmQtYWRkLWVkaXQuY29tcG9uZW50LnRz) | 0.00% | [41 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |
| [...tools/send/new-send/new-send-dropdown.component.ts](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Ftools%2Fsend%2Fnew-send%2Fnew-send-dropdown.component.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-YXBwcy93ZWIvc3JjL2FwcC90b29scy9zZW5kL25ldy1zZW5kL25ldy1zZW5kLWRyb3Bkb3duLmNvbXBvbmVudC50cw==) | 0.00% | [25 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |
| [apps/web/src/app/tools/send/send.component.ts](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Ftools%2Fsend%2Fsend.component.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-YXBwcy93ZWIvc3JjL2FwcC90b29scy9zZW5kL3NlbmQuY29tcG9uZW50LnRz) | 0.00% | [18 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/13361?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main   #13361      +/-   ##
==========================================
- Coverage   35.89%   35.86%   -0.03%     
==========================================
  Files        3175     3176       +1     
  Lines       93638    93662      +24     
  Branches    17033    17032       -1     
==========================================
- Hits        33613    33594      -19     
- Misses      57423    57481      +58     
+ Partials     2602     2587      -15     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bitwarden/clients/pull/13361?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-11T18:16:02Z
body: ![Logo](https://cdn.ast.checkmarx.net/integrations/logo/Checkmarx.png)
**Checkmarx One – Scan Summary & Details** – [7d464b10\-439f\-46df\-9bde\-c42919660e0e](https://ast.checkmarx.net/projects/41bd746e-d417-4eda-afeb-01a59d162661/scans?id=7d464b10-439f-46df-9bde-c42919660e0e&branch=tools%2Fpm-12681%2Fenable-new-send-ui-on-web)
### Great job, no security vulnerabilities found in this Pull Request

author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-04T14:30:34Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=bitwarden_clients&pullRequest=13361) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=bitwarden_clients&pullRequest=13361&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=bitwarden_clients&pullRequest=13361&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=bitwarden_clients&pullRequest=13361&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=bitwarden_clients&pullRequest=13361&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [6.6% Duplication on New Code](https://sonarcloud.io/component_measures?id=bitwarden_clients&pullRequest=13361&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=bitwarden_clients&pullRequest=13361)


author_association: NONE