mirror of
https://github.com/bitwarden/browser
synced 2026-01-21 11:53:34 +00:00
Pm 28182 add success page (#17814)
* PM-28182-implemented send confirmation drawer * PM-28182 resolved lint issue * PM-28182 resolved pr comment * PM-28182 put behind feature flag * Fix feature flag checks in send component * Fix feature flag checks in send dropdown component * Add SendUIRefresh feature flag * PM-28182 resolved lint issues * PM-28182 resolved N bug in drawer message * PM28182 resolved expirationDate replaced with delettionDate * PM-28182 resolved build issue * PM-28182 resolved failling tests * PM-28182 resolved pr comment to consolidate expression * chore: rerun web build * PM-28182 removed unneeded export
This commit is contained in:
@@ -72,6 +72,7 @@ describe("NewSendDropdownComponent", () => {
|
||||
const openSpy = jest.spyOn(SendAddEditDialogComponent, "open");
|
||||
const openDrawerSpy = jest.spyOn(SendAddEditDialogComponent, "openDrawer");
|
||||
mockConfigService.getFeatureFlag.mockResolvedValue(false);
|
||||
openSpy.mockReturnValue({ closed: of({}) } as any);
|
||||
|
||||
await component.createSend(SendType.Text);
|
||||
|
||||
@@ -85,6 +86,8 @@ describe("NewSendDropdownComponent", () => {
|
||||
mockConfigService.getFeatureFlag.mockImplementation(async (key) =>
|
||||
key === FeatureFlag.SendUIRefresh ? true : false,
|
||||
);
|
||||
const mockRef = { closed: of({}) };
|
||||
openDrawerSpy.mockReturnValue(mockRef as any);
|
||||
|
||||
await component.createSend(SendType.Text);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { firstValueFrom, Observable, of, switchMap } from "rxjs";
|
||||
import { firstValueFrom, Observable, of, switchMap, lastValueFrom } from "rxjs";
|
||||
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -10,7 +10,13 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
|
||||
import { DefaultSendFormConfigService, SendAddEditDialogComponent } from "@bitwarden/send-ui";
|
||||
import {
|
||||
DefaultSendFormConfigService,
|
||||
SendAddEditDialogComponent,
|
||||
SendItemDialogResult,
|
||||
} from "@bitwarden/send-ui";
|
||||
|
||||
import { SendSuccessDrawerDialogComponent } from "../shared";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@@ -60,12 +66,19 @@ export class NewSendDropdownComponent {
|
||||
if (!(await firstValueFrom(this.canAccessPremium$)) && type === SendType.File) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type);
|
||||
|
||||
const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh);
|
||||
|
||||
if (useRefresh) {
|
||||
SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig });
|
||||
const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig });
|
||||
if (dialogRef) {
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result?.result === SendItemDialogResult.Saved && result?.send) {
|
||||
this.dialogService.openDrawer(SendSuccessDrawerDialogComponent, {
|
||||
data: result.send,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SendAddEditDialogComponent.open(this.dialogService, { formConfig });
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { NewSendDropdownComponent } from "./new-send/new-send-dropdown.component";
|
||||
import { SendSuccessDrawerDialogComponent } from "./shared";
|
||||
|
||||
const BroadcasterSubscriptionId = "SendComponent";
|
||||
|
||||
@@ -172,12 +173,25 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
});
|
||||
}
|
||||
|
||||
const result = await lastValueFrom(this.sendItemDialogRef.closed);
|
||||
const result: SendItemDialogResult = 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) {
|
||||
if (
|
||||
result?.result === SendItemDialogResult.Deleted ||
|
||||
result?.result === SendItemDialogResult.Saved
|
||||
) {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
if (
|
||||
result?.result === SendItemDialogResult.Saved &&
|
||||
result?.send &&
|
||||
(await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh))
|
||||
) {
|
||||
this.dialogService.openDrawer(SendSuccessDrawerDialogComponent, {
|
||||
data: result.send,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/web/src/app/tools/send/shared/index.ts
Normal file
1
apps/web/src/app/tools/send/shared/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { SendSuccessDrawerDialogComponent } from "./send-success-drawer-dialog.component";
|
||||
@@ -0,0 +1,45 @@
|
||||
<bit-dialog dialogSize="large" disablePadding="false" background="alt">
|
||||
<ng-container bitDialogTitle>
|
||||
<span>{{ dialogTitle() | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container bitDialogContent>
|
||||
<div
|
||||
class="tw-flex tw-flex-col tw-items-center tw-justify-center tw-text-center tw-h-full tw-px-4 tw-pt-20"
|
||||
>
|
||||
<div class="tw-mb-6 tw-mt-8">
|
||||
<div class="tw-size-[95px] tw-content-center">
|
||||
<bit-icon [icon]="activeSendIcon"></bit-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 bitTypography="h3" class="tw-mb-2 tw-font-bold">
|
||||
{{ "sendCreatedSuccessfully" | i18n }}
|
||||
</h3>
|
||||
|
||||
<p bitTypography="body1" class="tw-mb-6 tw-max-w-sm">
|
||||
{{ "sendCreatedDescription" | i18n: formattedExpirationTime }}
|
||||
</p>
|
||||
|
||||
<bit-form-field class="tw-w-full tw-max-w-sm tw-mb-4">
|
||||
<bit-label>{{ "sendLink" | i18n }}</bit-label>
|
||||
<input bitInput disabled type="text" [value]="sendLink()" />
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
bitSuffix
|
||||
[label]="'copyLink' | i18n"
|
||||
(click)="copyLink()"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton buttonType="primary" (click)="copyLink()">
|
||||
{{ "copyLink" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Component, ChangeDetectionStrategy, Inject, signal, computed } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ActiveSendIcon } from "@bitwarden/assets/svg";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import { DIALOG_DATA, DialogModule, ToastService, TypographyModule } from "@bitwarden/components";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
|
||||
@Component({
|
||||
imports: [SharedModule, DialogModule, TypographyModule],
|
||||
templateUrl: "./send-success-drawer-dialog.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SendSuccessDrawerDialogComponent {
|
||||
readonly sendLink = signal<string>("");
|
||||
activeSendIcon = ActiveSendIcon;
|
||||
|
||||
// Computed property to get the dialog title based on send type
|
||||
readonly dialogTitle = computed(() => {
|
||||
return this.send.type === SendType.Text ? "newTextSend" : "newFileSend";
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) public send: SendView,
|
||||
private environmentService: EnvironmentService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
void this.initLink();
|
||||
}
|
||||
|
||||
async initLink() {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
this.sendLink.set(env.getSendUrl() + this.send.accessId + "/" + this.send.urlB64Key);
|
||||
}
|
||||
|
||||
get formattedExpirationTime(): string {
|
||||
if (!this.send.deletionDate) {
|
||||
return "";
|
||||
}
|
||||
const hoursAvailable = this.getHoursAvailable(this.send);
|
||||
if (hoursAvailable < 24) {
|
||||
return hoursAvailable === 1
|
||||
? this.i18nService.t("oneHour").toLowerCase()
|
||||
: this.i18nService.t("durationTimeHours", String(hoursAvailable)).toLowerCase();
|
||||
}
|
||||
const daysAvailable = Math.ceil(hoursAvailable / 24);
|
||||
return daysAvailable === 1
|
||||
? this.i18nService.t("oneDay").toLowerCase()
|
||||
: this.i18nService.t("days", String(daysAvailable)).toLowerCase();
|
||||
}
|
||||
|
||||
private getHoursAvailable(send: SendView): number {
|
||||
const now = new Date().getTime();
|
||||
const deletionDate = new Date(send.deletionDate).getTime();
|
||||
return Math.max(0, Math.ceil((deletionDate - now) / (1000 * 60 * 60)));
|
||||
}
|
||||
|
||||
copyLink() {
|
||||
const link = this.sendLink();
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.copyToClipboard(link);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("sendLink")),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5616,6 +5616,37 @@
|
||||
"message": "Send saved",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendCreatedSuccessfully": {
|
||||
"message": "Send created successfully!",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendCreatedDescription": {
|
||||
"message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"content": "$1",
|
||||
"example": "7 days"
|
||||
}
|
||||
}
|
||||
},
|
||||
"durationTimeHours": {
|
||||
"message": "$HOURS$ hours",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"newTextSend": {
|
||||
"message": "New Text Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"newFileSend": {
|
||||
"message": "New File Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"editedSend": {
|
||||
"message": "Send saved",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
@@ -12581,4 +12612,4 @@
|
||||
"storageFullDescription": {
|
||||
"message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,10 @@ export const SendItemDialogResult = Object.freeze({
|
||||
} as const);
|
||||
|
||||
/** A result of the Send add/edit dialog. */
|
||||
export type SendItemDialogResult = (typeof SendItemDialogResult)[keyof typeof SendItemDialogResult];
|
||||
|
||||
export type SendItemDialogResult = {
|
||||
result: (typeof SendItemDialogResult)[keyof typeof SendItemDialogResult];
|
||||
send?: SendView;
|
||||
};
|
||||
/**
|
||||
* Component for adding or editing a send item.
|
||||
*/
|
||||
@@ -93,7 +95,7 @@ export class SendAddEditDialogComponent {
|
||||
*/
|
||||
async onSendCreated(send: SendView) {
|
||||
// FIXME Add dialogService.open send-created dialog
|
||||
this.dialogRef.close(SendItemDialogResult.Saved);
|
||||
this.dialogRef.close({ result: SendItemDialogResult.Saved, send });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,14 +103,14 @@ export class SendAddEditDialogComponent {
|
||||
* Handles the event when the send is updated.
|
||||
*/
|
||||
async onSendUpdated(send: SendView) {
|
||||
this.dialogRef.close(SendItemDialogResult.Saved);
|
||||
this.dialogRef.close({ result: SendItemDialogResult.Saved });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the send is deleted.
|
||||
*/
|
||||
async onSendDeleted() {
|
||||
this.dialogRef.close(SendItemDialogResult.Deleted);
|
||||
this.dialogRef.close({ result: SendItemDialogResult.Deleted });
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
|
||||
Reference in New Issue
Block a user