From bc9445935b32d808cb7eafa4f760ca7bf9b188c5 Mon Sep 17 00:00:00 2001 From: crisnicandrei <62384997+crisnicandrei@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:29:28 +0300 Subject: [PATCH] WIP PER-9237-gift-storage-interface WIP PER-9237-gift-storage-interface This pr's purpose is the implementation of the gift storage interface. I have added a new tab to the dropdown for the gift component. I have created a new component which in which the new form lies where the user must input the email, amount and the message. Upon confirmation a new modal pops up for confirmation. The api is not yet implemented, so this is only the interface --- .../confirm-gift-dialog.component.html | 24 +++++ .../confirm-gift-dialog.component.scss | 46 ++++++++ .../confirm-gift-dialog.component.spec.ts | 84 +++++++++++++++ .../confirm-gift-dialog.component.ts | 45 ++++++++ .../gift-storage/gift-storage.component.html | 102 ++++++++++++++++++ .../gift-storage/gift-storage.component.scss | 87 +++++++++++++++ .../gift-storage.component.spec.ts | 24 +++++ .../gift-storage/gift-storage.component.ts | 79 ++++++++++++++ .../storage-dialog.component.html | 8 ++ .../storage-dialog.component.scss | 5 +- src/app/core/core.module.ts | 8 ++ src/app/dialog/dialog.service.ts | 3 +- 12 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.html create mode 100644 src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.scss create mode 100644 src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.spec.ts create mode 100644 src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.ts create mode 100644 src/app/core/components/gift-storage/gift-storage.component.html create mode 100644 src/app/core/components/gift-storage/gift-storage.component.scss create mode 100644 src/app/core/components/gift-storage/gift-storage.component.spec.ts create mode 100644 src/app/core/components/gift-storage/gift-storage.component.ts diff --git a/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.html b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.html new file mode 100644 index 000000000..d6125e4d4 --- /dev/null +++ b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.html @@ -0,0 +1,24 @@ + +
+
+ Gift storage + +
+
+ + +
+
diff --git a/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.scss b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.scss new file mode 100644 index 000000000..1a1806fa4 --- /dev/null +++ b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.scss @@ -0,0 +1,46 @@ +@import 'variables'; + +:host { + display: block; + width: 100%; +} + +.header { + @include tabbedDialogHeader; +} + +.content { + @include tabbedDialogContent; + flex-direction: column +} + +.tabs { + @include tabbedDialogNav; +} + +.panel { + @include tabbedDialogPanel; +} + +.panel-title { + @include tabbedDialogPanelTitle; +} + +form { + @include tabbedDialogPanelForm; + max-width: 600px; + padding-top: 0; +} + +pr-archive-small { + margin-bottom: $grid-unit; + + &.waiting { + opacity: 0.7; + } +} + +.popup-text { + text-align: center; + padding: 20px; +} diff --git a/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.spec.ts b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.spec.ts new file mode 100644 index 000000000..897d06ddf --- /dev/null +++ b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.spec.ts @@ -0,0 +1,84 @@ +/* @format */ +import { + ComponentFixture, + TestBed, + TestModuleMetadata, +} from '@angular/core/testing'; +import { DialogRef, DIALOG_DATA } from '@root/app/dialog/dialog.module'; +import { SharedModule } from '@shared/shared.module'; +import * as Testing from '@root/test/testbedConfig'; +import { cloneDeep } from 'lodash'; +import { ConfirmGiftDialogComponent } from './confirm-gift-dialog.component'; +import { Observable, Observer, Subject } from 'rxjs'; + +describe('ConfirmGiftDialogComponent', () => { + let component: ConfirmGiftDialogComponent; + let fixture: ComponentFixture; + let dialogRef: DialogRef; + let dialogData: { + email: string; + amount: string; + message: string; + giftResult: Observable; + }; + + beforeEach(async () => { + const config: TestModuleMetadata = cloneDeep(Testing.BASE_TEST_CONFIG); + + dialogData = { + email: 'test@email.com', + amount: '10', + message: 'test message', + giftResult: new Observable(() => {}), + }; + + dialogRef = new DialogRef(1, null); + + config.imports.push(SharedModule); + config.declarations.push(ConfirmGiftDialogComponent); + config.providers.push({ + provide: DIALOG_DATA, + useValue: { + email: 'test@email.com', + amount: 10, + message: 'test message', + giftResult: new Observable(() => {}), + }, + }); + config.providers.push({ + provide: DialogRef, + useValue: dialogRef, + }); + await TestBed.configureTestingModule(config).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmGiftDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should take the email from the dialog data', () => { + expect(component.email).toEqual('test@email.com'); + }); + + it('should take the amount from the dialog data', () => { + expect(component.amount).toEqual(10); + }); + + it('should take the message from the dialog data', () => { + expect(component.message).toEqual('test message'); + }); + + it('should close when close method is called', () => { + const dialogRefSpy = spyOn(dialogRef, 'close'); + + component.onDoneClick(); + + expect(dialogRefSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.ts b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.ts new file mode 100644 index 000000000..83a721d31 --- /dev/null +++ b/src/app/core/components/confirm-gift-dialog/confirm-gift-dialog.component.ts @@ -0,0 +1,45 @@ +/* @format */ +import { Observable, Subscription } from 'rxjs'; +import { Component, Inject, OnDestroy } from '@angular/core'; +import { DialogRef, DIALOG_DATA } from '@root/app/dialog/dialog.service'; +import { ApiService } from '@shared/services/api/api.service'; +import { MessageService } from '@shared/services/message/message.service'; +@Component({ + selector: 'pr-confirm-gift-dialog', + templateUrl: './confirm-gift-dialog.component.html', + styleUrls: ['./confirm-gift-dialog.component.scss'], +}) +export class ConfirmGiftDialogComponent { + email: string; + amount: number; + message: string; + giftResult: Observable; + + constructor( + private dialogRef: DialogRef, + private api: ApiService, + @Inject(DIALOG_DATA) public data: any, + private msg: MessageService + ) { + this.email = this.data.email; + this.amount = this.data.amount; + this.message = this.data.message; + this.giftResult = this.data.giftResult; + } + + public onDoneClick(): void { + this.dialogRef.close(); + } + + public onConfirmClick() { + let sub: Subscription; + try { + sub = this.giftResult.subscribe(); + } catch (e) { + this.msg.showError('Something went wrong! Please try again.'); + } finally { + sub.unsubscribe(); + this.dialogRef?.close(); + } + } +} diff --git a/src/app/core/components/gift-storage/gift-storage.component.html b/src/app/core/components/gift-storage/gift-storage.component.html new file mode 100644 index 000000000..cb448d00e --- /dev/null +++ b/src/app/core/components/gift-storage/gift-storage.component.html @@ -0,0 +1,102 @@ + + +
+

Gift Storage

+

+ Gift any amount of your unused storage to someone else. You can send + storage to both current Permanent users and those who do not yet have an + account; they must log in to/create an account in order to claim their + storage. +

+
+
+
+ +
+ +
+
+
+ + {{ this.availableSpace }} GB available +
+ + gigabytes +
+
+
+ + Optional +
+ +
+
+ +
+
+
+
+ + + + + + + + + + + +

Storage successfully gifted

+

+ Success! You sent {{ giftForm.value.amount }} GB of Permanent storage to + {{ giftForm.value.email }}. +

+

+ You have + {{ (+(+availableSpace) - +(+giftForm.value.amount)).toFixed(2) }} GB of + storage available. +

+
+
diff --git a/src/app/core/components/gift-storage/gift-storage.component.scss b/src/app/core/components/gift-storage/gift-storage.component.scss new file mode 100644 index 000000000..e24a689fd --- /dev/null +++ b/src/app/core/components/gift-storage/gift-storage.component.scss @@ -0,0 +1,87 @@ +/* @format */ +@import 'variables'; + +.gift-storage { + & > form { + margin: 0; + max-width: 800px; + } +} + +.dialog-form-field { + display: flex; + margin-bottom: 23px; + + @include beforeDesktop { + flex-direction: column; + + & > div { + margin-bottom: 10px; + } + } + + & .text { + width: 200px; + display: flex; + justify-content: center; + flex-direction: column; + } + & .label { + font-weight: 700; + font-family: 'Open-Sans', sans-serif; + margin: 0; + } + + & .label-info { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + font-size: 14px; + line-height: 17px; + } + + & .gigabytes { + display: inline-flex; + align-items: center; + + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-size: 16px; + line-height: 17px; + color: $gray-dark; + margin-left: 10px; + + @include beforeDesktop { + margin-left: 0; + margin-top: 10px; + } + } + + & > input, + textarea { + width: 400px; + border: 1px solid $gray-light; + border-radius: 6px; + height: 42px; + } + + & > textarea { + height: 150px; + resize: none; + } + + & .btn { + width: 200px; + + &-disabled { + background-color: $gray-light; + color: $gray-dark; + border-color: $gray-light; + } + } +} + +.panel-title { + @include tabbedDialogPanelTitle; +} diff --git a/src/app/core/components/gift-storage/gift-storage.component.spec.ts b/src/app/core/components/gift-storage/gift-storage.component.spec.ts new file mode 100644 index 000000000..8badb6260 --- /dev/null +++ b/src/app/core/components/gift-storage/gift-storage.component.spec.ts @@ -0,0 +1,24 @@ +/* @format */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClient, HttpHandler } from '@angular/common/http'; +import { GiftStorageComponent } from './gift-storage.component'; + +describe('GiftStorageComponent', () => { + let component: GiftStorageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [GiftStorageComponent], + providers: [HttpClient, HttpHandler], + }).compileComponents(); + + fixture = TestBed.createComponent(GiftStorageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/components/gift-storage/gift-storage.component.ts b/src/app/core/components/gift-storage/gift-storage.component.ts new file mode 100644 index 000000000..98ec3bb36 --- /dev/null +++ b/src/app/core/components/gift-storage/gift-storage.component.ts @@ -0,0 +1,79 @@ +/* @format */ +import { Dialog } from './../../../dialog/dialog.service'; +import { AccountService } from './../../../shared/services/account/account.service'; +import { Component } from '@angular/core'; +import { + UntypedFormGroup, + UntypedFormBuilder, + Validators, +} from '@angular/forms'; +import { AccountVO } from '@models/index'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'pr-gift-storage', + templateUrl: './gift-storage.component.html', + styleUrls: ['./gift-storage.component.scss'], +}) +export class GiftStorageComponent { + giftForm: UntypedFormGroup; + availableSpace: string; + account: AccountVO; + bytesPerGigabyte = 1073741824; + + public isSuccessful: boolean = false; + public giftResult: Observable = new Observable(() => { + this.isSuccessful = true; + }); + + constructor( + private fb: UntypedFormBuilder, + private accountService: AccountService, + private dialog: Dialog + ) { + this.account = this.accountService.getAccount(); + this.availableSpace = this.bytesToGigabytes(this.account?.spaceLeft); + this.giftForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]], + amount: [ + '', + [ + Validators.required, + Validators.min(0.1), + Validators.max(Number(this.availableSpace)), + ], + ], + message: ['', []], + }); + } + + submitStorageGiftForm(value: { + email: string; + amount: number; + message: string; + }) { + this.dialog.open( + 'ConfirmGiftDialogComponent', + { + ...value, + giftResult: this.giftResult, + }, + { + width: '700px', + } + ); + } + + closeSuccessMessage() { + const remainingSpaceAfterGift = + Number(this.availableSpace) - Number(this.giftForm.value.amount); + this.availableSpace = this.bytesToGigabytes( + remainingSpaceAfterGift * this.bytesPerGigabyte + ); + this.isSuccessful = false; + } + + bytesToGigabytes(bytes: number): string { + return (bytes / this.bytesPerGigabyte).toFixed(2); + } +} diff --git a/src/app/core/components/storage-dialog/storage-dialog.component.html b/src/app/core/components/storage-dialog/storage-dialog.component.html index f965dead0..0d1775be9 100644 --- a/src/app/core/components/storage-dialog/storage-dialog.component.html +++ b/src/app/core/components/storage-dialog/storage-dialog.component.html @@ -11,6 +11,10 @@ (click)="setTab('add')" [class.active]="activeTab === 'add'"> Add Storage +
+ Gift Storage +
Redeem Gift
@@ -34,6 +38,10 @@ + + + +
Redeem Gift

If you've been given a gift code, you can redeem it for free storage below.

diff --git a/src/app/core/components/storage-dialog/storage-dialog.component.scss b/src/app/core/components/storage-dialog/storage-dialog.component.scss index 76078c65b..c23ffceec 100644 --- a/src/app/core/components/storage-dialog/storage-dialog.component.scss +++ b/src/app/core/components/storage-dialog/storage-dialog.component.scss @@ -1,3 +1,4 @@ +/* @format */ @import 'variables'; :host { @@ -6,7 +7,7 @@ } .header { - @include tabbedDialogHeader($PR-purple) + @include tabbedDialogHeader($PR-purple); } .content { @@ -36,4 +37,4 @@ form { pr-storage-meter { margin: $grid-unit 0; -} \ No newline at end of file +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 44de1b56a..8e6dc55f0 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -60,6 +60,8 @@ import { PublicSettingsComponent } from './components/public-settings/public-set import { ManageMetadataModule } from '../archive-settings/manage-metadata/manage-metadata.module'; import { ArchiveTypeChangeDialogComponent } from './components/archive-type-change-dialog/archive-type-change-dialog.component'; import { DirectiveModule } from '../directive/directive.module'; +import { GiftStorageComponent } from './components/gift-storage/gift-storage.component'; +import { ConfirmGiftDialogComponent } from './components/confirm-gift-dialog/confirm-gift-dialog.component'; @NgModule({ imports: [ @@ -108,6 +110,8 @@ import { DirectiveModule } from '../directive/directive.module'; WelcomeInvitationDialogComponent, PublicSettingsComponent, ArchiveTypeChangeDialogComponent, + GiftStorageComponent, + ConfirmGiftDialogComponent, ], providers: [ DataService, @@ -172,6 +176,10 @@ export class CoreModule { token: 'ArchiveTypeChangeDialogComponent', component: ArchiveTypeChangeDialogComponent, }, + { + token: 'ConfirmGiftDialogComponent', + component: ConfirmGiftDialogComponent, + }, ]; constructor( diff --git a/src/app/dialog/dialog.service.ts b/src/app/dialog/dialog.service.ts index 795d94960..0826299cb 100644 --- a/src/app/dialog/dialog.service.ts +++ b/src/app/dialog/dialog.service.ts @@ -32,7 +32,8 @@ export type DialogComponentToken = 'WelcomeDialogComponent' | 'WelcomeInvitationDialogComponent' | 'CreateAccountDialogComponent' | - 'ArchiveTypeChangeDialogComponent' + 'ArchiveTypeChangeDialogComponent' | + 'ConfirmGiftDialogComponent' ; export interface DialogChildComponentData {