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 @@
+
+
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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 {