...
 
Commits (2)
......@@ -1980,11 +1980,6 @@
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/video.js": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.3.tgz",
"integrity": "sha512-yAb46+4A0dKFxOQRVLoLyfC/S/BmHLE10MxPXt/t88+7R4GWLHosHelVtYpKBRykjptdkqfQXNRXoQzDeKm6MA=="
},
"@types/webpack-sources": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz",
......
......@@ -33,6 +33,7 @@
[class.has-v2-navbar]="featuresService.has('top-feeds')"
[class.is-pro-domain]="isProDomain"
>
<m-emailConfirmation></m-emailConfirmation>
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
......
import { NgModule } from '@angular/core';
import { CommonModule as NgCommonModule } from '@angular/common';
import { RouterModule, Router } from '@angular/router';
import { RouterModule, Router, Routes } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MINDS_PIPES } from './pipes/pipes';
......@@ -128,9 +128,17 @@ import { FormToastComponent } from './components/form-toast/form-toast.component
import { SsoService } from './services/sso.service';
import { ShadowboxHeaderTabsComponent } from './components/shadowbox-header-tabs/shadowbox-header-tabs.component';
import { TimespanFilterComponent } from './components/timespan-filter/timespan-filter.component';
import { EmailConfirmationComponent } from './components/email-confirmation/email-confirmation.component';
PlotlyModule.plotlyjs = PlotlyJS;
const routes: Routes = [
{
path: 'email-confirmation',
redirectTo: '/',
},
];
@NgModule({
imports: [
NgCommonModule,
......@@ -139,6 +147,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormsModule,
ReactiveFormsModule,
PlotlyModule,
RouterModule.forChild(routes),
],
declarations: [
MINDS_PIPES,
......@@ -245,6 +254,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
ShadowboxSubmitButtonComponent,
ShadowboxHeaderTabsComponent,
TimespanFilterComponent,
EmailConfirmationComponent,
],
exports: [
MINDS_PIPES,
......@@ -346,6 +356,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
ShadowboxSubmitButtonComponent,
ShadowboxHeaderTabsComponent,
TimespanFilterComponent,
EmailConfirmationComponent,
],
providers: [
SiteService,
......
......@@ -67,4 +67,9 @@ m-announcement {
}
}
}
.m-announcement__clickable {
cursor: pointer;
font-weight: bold;
}
}
......@@ -14,7 +14,7 @@ import { Client } from '../../../services/api';
<ng-content></ng-content>
</div>
<div class="m-announcement--close" (click)="close()">
<div class="m-announcement--close" *ngIf="canClose" (click)="close()">
<i class="material-icons">close</i>
</div>
</div>
......@@ -24,6 +24,8 @@ export class AnnouncementComponent {
minds: Minds = window.Minds;
hidden: boolean = false;
@Input() id: string = 'default';
@Input() canClose: boolean = true;
@Input() remember: boolean = true;
constructor(private storage: Storage) {}
......@@ -32,7 +34,10 @@ export class AnnouncementComponent {
}
close() {
this.storage.set('hide-announcement:' + this.id, true);
if (this.remember) {
this.storage.set('hide-announcement:' + this.id, true);
}
this.hidden = true;
}
}
<ng-container *ngIf="shouldShow">
<m-announcement
id="email-confirmation"
[canClose]="canClose"
[remember]="false"
>
Please confirm your email address.
<ng-container *ngIf="!sent"
>Didn't get it?
<span class="m-announcement__clickable" (click)="send()"
>Click here to send again.</span
></ng-container
>
</m-announcement>
</ng-container>
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit,
} from '@angular/core';
import { EmailConfirmationService } from './email-confirmation.service';
import { Session } from '../../../services/session';
import { Subscription } from 'rxjs';
/**
* Component that displays an announcement-like banner
* asking the user to confirm their email address and a link
* to re-send the confirmation email.
* @see AnnouncementComponent
*/
@Component({
providers: [EmailConfirmationService],
selector: 'm-emailConfirmation',
templateUrl: 'email-confirmation.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EmailConfirmationComponent implements OnInit, OnDestroy {
sent: boolean = false;
shouldShow: boolean = false;
canClose: boolean = false;
protected userEmitter$: Subscription;
protected canCloseTimer: number;
protected minds = window.Minds;
constructor(
protected service: EmailConfirmationService,
protected session: Session,
protected cd: ChangeDetectorRef
) {}
ngOnInit(): void {
this.setShouldShow(this.session.getLoggedInUser());
this.userEmitter$ = this.session.userEmitter.subscribe(user => {
this.sent = false;
this.setShouldShow(user);
this.detectChanges();
});
this.canCloseTimer = window.setTimeout(() => {
this.canClose = true;
this.detectChanges();
}, 3000);
}
ngOnDestroy(): void {
window.clearTimeout(this.canCloseTimer);
if (this.userEmitter$) {
this.userEmitter$.unsubscribe();
}
}
/**
* Re-calculates the visibility of the banner
* @param {Object} user
*/
setShouldShow(user): void {
this.shouldShow =
!this.minds.from_email_confirmation &&
user &&
user.email_confirmed === false;
}
/**
* Uses the service to re-send the confirmation email
*/
async send(): Promise<void> {
this.sent = true;
this.detectChanges();
try {
const sent = await this.service.send();
if (!sent) {
this.sent = false;
}
} catch (e) {}
this.detectChanges();
}
detectChanges(): void {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
import { Injectable } from '@angular/core';
import { Client } from '../../../services/api/client';
/**
* API implementation service for Email Confirmation component
* @see EmailConfirmationComponent
*/
@Injectable()
export class EmailConfirmationService {
constructor(protected client: Client) {}
/**
* Attempts to re-send the confirmation email to the current logged in user
*/
async send(): Promise<boolean> {
const response = (await this.client.post(
'api/v2/email/confirmation/resend',
{}
)) as any;
return Boolean(response && response.sent);
}
}
......@@ -12,7 +12,9 @@
[(ngModel)]="phoneNumber"
(ngModelChange)="onPhoneNumberChange()"
[placeholder]="selectedCountry.placeHolder"
class="form-control m-phoneInput__input"
#input
required
autofocus
/>
</div>
......@@ -12,6 +12,10 @@ m-phone-input {
}
}
span {
font-weight: 300;
}
.m-phone-input--wrapper {
display: flex;
@media (min-width: $max-mobile) {
......@@ -70,6 +74,7 @@ m-phone-input {
cursor: pointer;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
outline: 0;
@include m-theme() {
background-color: themed($m-white);
}
......@@ -85,13 +90,13 @@ m-phone-input {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
@include m-theme() {
border-top: 6px solid themed($m-grey-700);
border-top: 6px solid themed($m-grey-300);
}
}
.m-phone-input--arrow.up {
border-top: none;
@include m-theme() {
border-bottom: 6px solid themed($m-grey-700);
border-bottom: 6px solid themed($m-grey-300);
}
}
}
......
......@@ -2,7 +2,14 @@
class="m-shadowboxSubmitButton"
type="submit"
[disabled]="disabled"
[ngClass]="{ saving: saveStatus === 'saving' }"
[ngClass]="{ saving: saving }"
>
<ng-content></ng-content>
<div *ngIf="!saving" class="m-shadowboxSubmitButton__status--unsaved">
<ng-content></ng-content>
</div>
<div *ngIf="saving" class="m-shadowboxSubmitButton__status--saving">
<span></span>
<span></span>
<span></span>
</div>
</button>
......@@ -5,7 +5,7 @@ import { Component, Input } from '@angular/core';
templateUrl: './shadowbox-submit-button.component.html',
})
export class ShadowboxSubmitButtonComponent {
@Input() saveStatus: string = 'unsaved';
@Input() saving: boolean = false;
@Input() disabled: boolean = false;
constructor() {}
......
<div class="m-channel--explicit-overlay--container" *ngIf="!hidden">
<div class="m-channel--explicit-overlay--content">
<h3>
This channel contains mature content
This channel contains content that is NSFW
</h3>
<div
class="m-btn m-btn--slim m-btn--action m-channel--explicit-overlay--action"
......
......@@ -51,6 +51,11 @@ m-proChannel__home {
@media screen and (max-width: $max-mobile) {
width: 100%;
}
> * {
min-width: 0;
min-height: 0;
}
}
.m-proChannelHome__featuredContent {
......
......@@ -88,6 +88,11 @@ m-pro--channel-list {
grid-template-columns: 100%;
}
> * {
min-width: 0;
min-height: 0;
}
&.m-proChannelListContent__normalList {
grid-template-columns: 100%;
......
......@@ -899,27 +899,14 @@
[disabled]="
!form.valid || form.pristine || saveStatus === 'saving'
"
[saveStatus]="saveStatus"
[saving]="saveStatus === 'saving' ? true : false"
>
<ng-container [ngSwitch]="saveStatus">
<ng-template ngSwitchCase="saving">
<div class="m-shadowboxSubmitButton__status--saving">
<span></span>
<span></span>
<span></span>
</div>
</ng-template>
<ng-template ngSwitchDefault>
<div class="m-shadowboxSubmitButton__status--unsaved">
Save
{{
activeTab === 'general'
? 'Personal Details'
: (activeTab | titlecase)
}}
</div>
</ng-template>
</ng-container>
Save
{{
activeTab === 'general'
? 'Personal Details'
: (activeTab | titlecase)
}}
</m-shadowboxSubmitButton>
</div>
</form>
......
......@@ -37,9 +37,14 @@
id="email"
name="email"
[(ngModel)]="email"
(keyup)="change()"
(keyup)="change(); changeEmail()"
/>
</li>
<li class="m-settings--emails-campaigns__note" *ngIf="emailChanged" i18n>
Note: If you change your email address, it will need to be confirmed
again.
</li>
</ul>
</div>
......
......@@ -26,5 +26,15 @@ m-settings--emails {
margin-left: 8px;
}
}
.m-settings--emails-campaigns__note {
font-size: 0.8em;
line-height: 1;
padding: 2px 8px 0;
@include m-theme() {
color: themed($m-grey-300);
}
}
}
}
......@@ -4,6 +4,7 @@ import { Subscription } from 'rxjs';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { ConfirmPasswordModalComponent } from '../../modals/confirm-password/modal.component';
import { Session } from '../../../services/session';
@Component({
selector: 'm-settings--emails',
......@@ -33,6 +34,7 @@ export class SettingsEmailsComponent implements OnInit {
error: string = '';
changed: boolean = false;
emailChanged: boolean = false;
saved: boolean = false;
inProgress: boolean = false;
loading: boolean = false;
......@@ -41,7 +43,8 @@ export class SettingsEmailsComponent implements OnInit {
constructor(
public client: Client,
public overlayModal: OverlayModalService
public overlayModal: OverlayModalService,
protected session: Session
) {}
ngOnInit() {
......@@ -77,6 +80,10 @@ export class SettingsEmailsComponent implements OnInit {
this.saved = false;
}
changeEmail() {
this.emailChanged = true;
}
canSubmit() {
return this.changed;
}
......@@ -89,7 +96,13 @@ export class SettingsEmailsComponent implements OnInit {
notifications: this.notifications,
})
.then((response: any) => {
if (this.emailChanged && window.Minds.user) {
window.Minds.user.email_confirmed = false;
this.session.inject(window.Minds.user);
}
this.changed = false;
this.emailChanged = false;
this.saved = true;
this.error = '';
......
import { Component, Input } from '@angular/core';
@Component({
selector: 'm-walletActionButton',
templateUrl: './action-button.component.html',
})
export class WalletActionButtonComponent {
@Input() disabled: boolean = false;
constructor() {}
}
......@@ -14,7 +14,6 @@
>
</div>
</div>
<!-- <ng-container *ngTemplateOutlet="buyTokens"></ng-container> -->
<a class="m-walletBalance--tokens__buyButtonWrapper" routerLink="/tokens"
><m-shadowboxSubmitButton
[disabled]="!session.getLoggedInUser().rewards"
......@@ -61,7 +60,9 @@
>
tokens
</div>
<a *ngIf="session.getLoggedInUser().rewards">Transfer to On-Chain</a>
<a *ngIf="session.getLoggedInUser().rewards" (click)="showModal = true"
>Transfer to On-Chain</a
>
</div>
</div>
<div class="m-walletBalance--tokens__learnMore">
......@@ -75,16 +76,13 @@
tokens. Next payout in
<span>{{ nextPayout | timediff }}</span> (Daily at 2:00am UTC)
</div>
<m-walletModal [showModal]="showModal" (closeModal)="showModal = false">
<m-walletOnchainTransfer
[offchainBalance]="offchainBalance.total"
[onchainAddress]="wallet.onchain.address"
></m-walletOnchainTransfer>
</m-walletModal>
</ng-container>
<!-- <ng-template #buyTokens>
<a class="m-walletBalance--tokens__buyButtonWrapper" routerLink="/tokens"
><m-shadowboxSubmitButton [disabled]="!session.getLoggedInUser().rewards"
>Buy tokens</m-shadowboxSubmitButton
></a
>
</ng-template> -->
<ng-template #loading>
<h2>...</h2>
</ng-template>
m-walletBalance--tokens {
display: block;
max-width: 700px;
> * {
font-weight: 300;
font-size: 15px;
......@@ -17,9 +18,11 @@ m-walletBalance--tokens {
flex-flow: row wrap;
}
.m-walletBalance--tokens__buyButtonWrapper {
margin-left: 36px;
cursor: default;
min-width: 105px;
m-shadowboxSubmitButton {
margin-left: 36px;
}
}
.m-walletBalance--tokens__balanceTitle {
position: relative;
......@@ -122,13 +125,28 @@ m-walletBalance--tokens {
}
}
m-shadowboxSubmitButton {
min-width: 105px;
button.m-shadowboxSubmitButton {
height: 35px;
max-height: 35px;
min-width: 0;
min-height: 0;
}
.m-shadowboxSubmitButton__status--unsaved {
line-height: 15px;
}
}
@media screen and (max-width: 800px) {
.m-walletBalance--tokens__equationLeft {
flex-direction: column;
}
.m-walletBalance--tokens__buyButtonWrapper {
margin-left: 0;
margin-top: 22px;
m-shadowboxSubmitButton {
margin-left: 0;
margin-top: 22px;
}
}
}
@media screen and (max-width: $min-tablet) {
......
......@@ -29,6 +29,7 @@ export class WalletBalanceTokensV2Component implements OnInit, OnDestroy {
offchainBalance;
onchainBalance;
inProgress = true;
showModal = false;
protected updateTimer$;
nextPayout;
......@@ -43,7 +44,7 @@ export class WalletBalanceTokensV2Component implements OnInit, OnDestroy {
this.getPayout();
this.inProgress = false;
this.updateTimer$ = setInterval(this.updateNextPayout.bind(this), 60000);
this.updateTimer$ = setInterval(this.updateNextPayout.bind(this), 1000);
this.detectChanges();
}
ngOnDestroy() {
......
......@@ -40,6 +40,7 @@ export class WalletChartComponent implements OnInit {
constructor(protected walletService: WalletDashboardService) {}
// TODOOJM: use analytics dashboard response to control timespans and populate chart
ngOnInit() {
this.activeTimespan = this.timespans[0];
this.data['visualisation'] = this.walletService.getTokenChart(
......
......@@ -20,6 +20,12 @@
<div class="m-shadowboxLayout__body">
<div class="m-walletDashboardBalances__wrapper">
<!-- TODOOJM: make m-walletPhoneVerification have another output for success -- then trigger formToastService -->
<m-walletTokenOnboarding
*ngIf="activeCurrencyId === 'tokens' && !onboardingComplete"
(onboardingCompleted)="onboardingCompleted()"
(showTokenSettings)="updateView('settings')"
></m-walletTokenOnboarding>
<m-walletBalance--tokens
[wallet]="wallet"
class="m-shadowboxLayout__body"
......@@ -27,7 +33,11 @@
></m-walletBalance--tokens>
</div>
<div class="m-walletDashboardViews__wrapper">
<div class="m-walletDashboardViews__tabsContainer">
<div
class="m-walletDashboardViews__tabsContainer"
*ngIf="views[activeCurrencyId].length > 1"
id="dashboardViewsTabs"
>
<div
class="m-walletDashboardViews__tab"
[ngClass]="{ active: view.id === activeViewId }"
......@@ -38,9 +48,6 @@
</div>
</div>
<div class="m-walletDashboardViews__viewsContainer">
<!-- <div class="m-walletDashboard__spinnerContainer" *ngIf="loading">
<div class="mdl-spinner mdl-js-spinner is-active" [mdl]></div>
</div> -->
<m-walletChart
class="m-walletDashboardViews__view"
*ngIf="
......@@ -52,22 +59,26 @@
*ngIf="activeViewId === 'transactions'"
></m-walletTransactionsTable>
<ng-container *ngIf="activeViewId === 'settings'">
<m-walletSettings--tokens
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'tokens'"
></m-walletSettings--tokens>
<m-walletSettings--usd
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'usd'"
></m-walletSettings--usd>
<m-walletSettings--eth
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'eth'"
></m-walletSettings--eth>
<m-walletSettings--btc
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'btc'"
></m-walletSettings--btc>
<div class="m-walletDashboardSettings__wrapper">
<m-walletSettings--tokens
[wallet]="wallet"
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'tokens'"
></m-walletSettings--tokens>
<m-walletSettings--usd
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'usd'"
></m-walletSettings--usd>
<m-walletSettings--eth
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'eth'"
(showTokenSettings)="updateView('settings')"
></m-walletSettings--eth>
<m-walletSettings--btc
class="m-walletDashboardViews__view"
*ngIf="activeCurrencyId === 'btc'"
></m-walletSettings--btc>
</div>
</ng-container>
</div>
</div>
......
......@@ -32,6 +32,8 @@ m-walletDashboard {
font-size: 15px;
font-weight: 300;
line-height: 20px;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
color: themed($m-grey-300);
border-bottom: 3px solid themed($m-white);
......@@ -52,36 +54,417 @@ m-walletDashboard {
display: block;
padding-bottom: 50px;
}
m-shadowboxSubmitButton {
.m-shadowboxSubmitButton {
max-height: 35px;
min-width: 0;
min-height: 0;
font-weight: 300;
padding: 7px 15px;
font-size: 15px;
line-height: 20px;
}
}
.m-walletDashboardBalances__wrapper {
margin: 50px 40px 34px 40px;
margin: 50px 53px 34px 53px;
}
.m-walletDashboardViews__tabsContainer {
margin: 0 40px;
}
.m-walletDashboardViews__view {
margin: 50px 57px 0 57px;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
m-shadowboxSubmitButton {
display: block;
flex: 0 1 auto;
margin: 0 0 0 21px;
min-width: 105px;
box-sizing: border-box;
.m-shadowboxSubmitButton {
box-sizing: border-box;
max-height: none;
min-height: 42px;
height: 42px;
min-width: 0;
font-weight: 300;
padding: 7px 15px;
font-size: 15px;
line-height: 20px;
}
.m-shadowboxSubmitButton__status--unsaved {
font-size: 15px;
line-height: 20px;
}
}
// *******************************************************
// ** DASHBOARD - MEDIA QUERIES **************************
// *******************************************************
@media screen and (max-width: $min-tablet) {
.m-walletDashboardBalances__wrapper {
margin: 50px 24px 34px 24px;
}
.m-walletDashboardViews__tabsContainer,
.m-walletDashboardViews__view {
.m-walletDashboardViews__tabsContainer {
margin-left: 16px;
margin-right: 16px;
}
.m-walletDashboardViews__view {
margin: 30px 24px 0 24px;
}
}
@media screen and (max-width: $max-mobile) {
.m-walletDashboardViews__tab {
padding: 10px 9px;
margin: 0px 3px;
}
}
// *******************************************************
// ** FORMS - COMMON STYLES ******************************
// *******************************************************
form {
margin: 0;
padding: 0;
}
.m-walletForm__wrapper {
display: flex;
flex-flow: row wrap;
justify-content: flex-end;
align-items: center;
}
[class*='m-walletForm__field'] {
font-size: 15px;
line-height: 20px;
display: flex;
flex-flow: column nowrap;
m-tooltip {
margin-left: 10px;
.m-tooltip {
margin: 0;
}
.m-tooltip--bubble {
bottom: 18px;
margin-left: 12px;
font-weight: 400;
}
}
}
[class*='m-walletForm__row'] {
display: flex;
flex-flow: row wrap;
box-sizing: border-box;
&[class*='--label'] {
font-size: 15px;
line-height: 20px;
margin: 0 0 12px 0;
align-items: center;
flex-flow: row nowrap;
position: relative;
@include m-theme() {
color: themed($m-grey-300);
}
}
&[class*='--validation'] {
margin-top: 3px;
min-height: 22px;
p {
font-size: 14px;
line-height: 19px;
margin: 0;
font-weight: 300;
@include m-theme() {
color: themed($m-red-dark);
}
}
}
}
.stretchedField {
.m-walletForm__row--label m-tooltip {
margin-left: -12px;
}
.m-walletForm__row--input {
flex-flow: row nowrap;
&.invalid {
input,
.m-phone-input--wrapper {
@include m-theme() {
border-color: themed($m-red-dark) !important;
}
}
}
}
.m-walletForm__row--validation {
align-self: flex-end;
}
label,
input,
.m-walletForm__row--validation {
flex: 1 1 auto;
max-width: auto;
}
}
input {
outline: 0;
font-size: 15px;
line-height: 20px;
font-weight: 300;
width: 0;
min-width: 0;
padding: 10px 20px;
border-radius: 2px;
cursor: text;
@include m-theme() {
color: themed($m-grey-800);
}
}
input:not(.m-phoneInput__input),
.m-phone-input--wrapper {
border-radius: 2px;
@include m-theme() {
box-shadow: 0 1px 4px 0 rgba(themed($m-black), 0.1);
border: 1px solid themed($m-grey-50);
}
&:focus {
&:not(:read-only) {
@include m-theme() {
border-color: themed($m-blue);
}
}
}
}
.m-walletForm__fieldsContainer {
flex: 1 1 auto;
}
m-phone-input {
margin-bottom: 0;
.m-phone-input--wrapper {
width: 100%;
min-width: 240px;
justify-content: flex-start;
align-items: center;
}
m-phone-input--country {
flex: 0 1 auto;
}
input {
flex: 1 1 auto;
padding: 10px;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
@include m-theme() {
border-left: 1px solid themed($m-grey-50);
}
}
}
@media screen and (max-width: 500px) {
m-shadowboxSubmitButton {
margin: 21px 0 0 0;
}
.stretchedField {
.m-walletForm__row--label m-tooltip {
.m-tooltip--bubble {
right: 12px;
}
}
[class*='m-walletForm__row'] {
flex-flow: row wrap;
label,
input {
flex: 1 1 100%;
min-width: 100%;
max-width: 100%;
}
m-phone-input input {
min-width: none;
}
}
}
}
}
// *******************************************************
// ** SETTINGS - COMMON STYLES ***************************
// *******************************************************
.m-walletSettings {
// font-weight: 300;
@include m-theme() {
color: themed($m-grey-800);
}
m-shadowboxSubmitButton {
margin: 27px 0 0 0;
}
h2 {
margin: 0;
font-size: 26px;
line-height: 34px;
}
h4 {
margin: 0;
font-size: 21px;
line-height: 28px;
}
ul {
list-style: none;
margin: 0;
padding: 0;
// font-weight: 300;
}
a {
// font-weight: 300;
text-decoration: underline;
cursor: pointer;
font-size: 14px;
line-height: 19px;
}
p {
font-size: 14px;
line-height: 19px;
margin: 23px 0 0 0;
// font-weight: 300;
@include m-theme() {
color: themed($m-grey-300);
}
}
input {
outline: 0;
font-size: 15px;
line-height: 20px;
font-weight: 300;
width: 0;
min-width: 0;
padding: 10px 20px;
border-radius: 2px;
cursor: text;
@include m-theme() {
color: themed($m-grey-800);
box-shadow: 0 1px 4px 0 rgba(themed($m-black), 0.1);
border: 1px solid themed($m-grey-50);
}
&:focus {
&:not(:read-only) {
@include m-theme() {
border-color: themed($m-blue);
}
}
}
}
.m-walletSettingsView--addressCurrent {
h2 {
margin-bottom: 23px;
}
}
.m-walletSettings__setupOptions__recommendation {
display: flex;
flex-flow: row nowrap;
align-items: flex-end;
font-size: 12px;
line-height: 16px;
margin: 31px 0 0 0;
@include m-theme() {
color: themed($m-grey-300);
}
i {
font-size: 18px;
margin-right: 8px;
}
}
.m-walletSettingsView__setupOptions__container {
display: flex;
flex-flow: row wrap;
li {
box-sizing: border-box;
flex: 1 1 50%;
padding: 16px 0 16px 54px;
}
p {
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-walletSettingsView__setupOption--custom {
div {
margin-top: 16px;
display: inline-block;
a,
span {
display: inline-block;
margin-right: 5px;
}
span {
@include m-theme() {
color: themed($m-grey-300);
}
}
}
}
.m-walletSettingsView__setupOption--metamask {
padding-right: 32px;
padding-left: 0;
border-width: 0;
border-right-width: 1px;
border-style: solid;
@include m-theme() {
border-image: linear-gradient(
transparent,
rgba(themed($m-grey-200), 0.6),
transparent
)
10 100%;
}
}
div {
display: flex;
flex-flow: row nowrap;
align-items: center;
img {
height: 34px;
margin-right: 12px;
}
}
}
span.m-walletSettings__address--emphasis {
@include m-theme() {
color: themed($m-grey-800);
}
}
a.m-walletSettings__backButton {
margin-top: 33px;
display: flex;
flex-flow: row nowrap;
align-items: flex-end;
text-decoration: none;
i {
font-size: 18px;
margin-right: 5px;
}
&:hover {
span {
text-decoration: underline;
}
}
}
@media screen and (max-width: $min-tablet) {
.m-walletSettingsView__setupOptions__container {
li.m-walletSettingsView__setupOption--custom {
padding-left: 32px;
}
}
}
@media screen and (max-width: 520px) {
.m-walletSettingsView__setupOptions__container {
li {
flex-basis: 100%;
&.m-walletSettingsView__setupOption--metamask {
padding: 16px 0 32px 0;
border-right-width: 0;
}
&.m-walletSettingsView__setupOption--custom {
padding: 32px 0 0 0;
}
}
}
}
}
......@@ -4,8 +4,9 @@ import {
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
ComponentFactoryResolver,
} from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { Subscription } from 'rxjs';
import { WalletDashboardService } from './dashboard.service';
import { Session } from '../../../services/session';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
......@@ -13,6 +14,7 @@ import { MindsTitle } from '../../../services/ux/title';
import sidebarMenu from './sidebar-menu.default';
import { Menu } from '../../../common/components/sidebar-menu/sidebar-menu.component';
import { ShadowboxHeaderTab } from '../../../interfaces/dashboard';
import { Storage } from '../../../services/storage';
@Component({
selector: 'm-walletDashboard',
......@@ -22,10 +24,11 @@ import { ShadowboxHeaderTab } from '../../../interfaces/dashboard';
export class WalletDashboardComponent implements OnInit, OnDestroy {
menu: Menu = sidebarMenu;
paramsSubscription: Subscription;
wallet;
wallet = {};
activeCurrencyId: string;
activeViewId: string;
onboardingComplete = false;
views: any = {
tokens: [
......@@ -49,7 +52,8 @@ export class WalletDashboardComponent implements OnInit, OnDestroy {
protected router: Router,
protected route: ActivatedRoute,
protected cd: ChangeDetectorRef,
protected title: MindsTitle
protected title: MindsTitle,
protected storage: Storage
) {}
ngOnInit() {
......@@ -58,6 +62,14 @@ export class WalletDashboardComponent implements OnInit, OnDestroy {
return;
}
if (
this.storage.get('walletOnboardingComplete') ||
(this.session.getLoggedInUser().rewards &&
this.session.getLoggedInUser().eth_wallet)
) {
this.onboardingComplete = true;
}
this.title.setTitle('Wallet');
this.wallet = this.walletService.getWallet();
......@@ -121,6 +133,12 @@ export class WalletDashboardComponent implements OnInit, OnDestroy {
this.detectChanges();
}
onboardingCompleted() {
this.storage.set('walletOnboardingComplete', true);
this.onboardingComplete = true;
this.detectChanges();
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
......
......@@ -65,6 +65,8 @@ export class WalletDashboardService {
protected session: Session
) {}
// TODOOJM: make wallet an observable and have the dashboard component subscribe to it
// TODOOJM: make functions to
getWallet() {
this.getTokenAccounts();
this.getEthAccount();
......@@ -80,6 +82,8 @@ export class WalletDashboardService {
async getTokenAccounts() {
await this.loadOffchainAndReceiver();
await this.loadOnchain();
const tokenTypes = ['tokens', 'onchain', 'offchain']; // receiver?
// TODOOJM iterate through this.wallet and return walletObj where key matches one of the tokenTypes
}
async loadOffchainAndReceiver() {
......@@ -137,6 +141,7 @@ export class WalletDashboardService {
if (ethBalance) {
this.wallet.eth.balance = ethBalance;
}
return this.wallet.eth;
}
async getStripeAccount() {
......@@ -181,8 +186,8 @@ export class WalletDashboardService {
return fakeData.visualisation;
}
// TODOOJM tx/contribution endpoint needed
getTokenTransactionTable() {
// TODOOJM get this from contributions component
return fakeData.token_transactions;
}
}
<div *ngIf="_showModal" class="m-walletModal__container">
<div class="m-walletModal__backdrop"></div>
<div class="m-walletModal__close" (click)="close()">
<i class="material-icons">close</i>
</div>
<div class="m-walletModal" (click)="clickedModal($event)">
<div class="m-walletModal__content">
<ng-content></ng-content>
</div>
</div>
</div>
m-walletModal {
.m-walletModal__container {
display: block;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
overflow: scroll;
z-index: 9999999;
}
.m-walletModal__backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999995;
@include m-theme() {
background-color: rgba(themed($m-white), 0.6);
}
}
.m-walletModal {
padding: 56px 65px;
z-index: 9999996;
position: fixed;
transform: translateY(-50%);
width: 70vw;
max-width: 700px;
max-height: 90vh;
right: 2vw;
left: 2vw;
margin: auto;
box-sizing: border-box;
top: 50%;
overflow-y: auto;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
box-shadow: 0 12px 24px rgba(themed($m-black), 0.3);
background-color: themed($m-white);
color: themed($m-grey-300);
}
a {
text-decoration: underline;
cursor: pointer;
display: inline-block;
font-weight: 300;
}
}
.m-walletModal__title {
font-weight: 500;
font-size: 24px;
line-height: 32px;
margin-bottom: 18px;
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-walletModal__desc {
margin-bottom: 30px;
p {
font-size: 16px;
line-height: 21px;
}
}
[class*='m-walletModal__footnote--'] {
margin: 10px 0 11px 0;
font-size: 15px;
line-height: 20px;
}
.m-walletModal__close {
z-index: 9999998;
line-height: normal;
cursor: pointer;
box-sizing: border-box;
position: fixed;
height: 53px;
width: 53px;
right: 25px;
top: 25px;
padding: 4px;
display: inline-block;
border-radius: 50%;
cursor: pointer;
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
background-color: themed($m-white);
box-shadow: 0 0 15px 0 rgba(themed($m-black), 0.25);
}
i {
position: absolute;
top: 50%;
left: 50%;
margin-top: -25%;
transform: translate(-50%);
font-size: 28px;
opacity: 0.3;
transition: opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1);
}
&:hover {
transform: scale(1.05);
@include m-theme() {
box-shadow: 0 0 8px 0 rgba(themed($m-black), 0.22);
}
i {
opacity: 0.6;
}
}
}
@media screen and (max-width: $min-tablet) {
.m-walletModal {
padding: 46px 50px;
max-width: 80vw;
max-height: 90vh;
}
}
@media screen and (max-width: $max-mobile) {
.m-walletModal {
padding: 26px 35px;
max-width: 90vw;
max-height: 95vh;
}
}
}
import {
Component,
Output,
Input,
EventEmitter,
OnDestroy,
HostListener,
} from '@angular/core';
@Component({
selector: 'm-walletModal',
templateUrl: './modal.component.html',
})
export class WalletModalComponent implements OnDestroy {
showModalTimeout: any = null;
justOpened = true;
public _showModal = false;
@Input()
public set showModal(val: boolean) {
this._showModal = val;
val ? this.show() : this.close();
}
@Output() closeModal: EventEmitter<any> = new EventEmitter();
constructor() {}
show() {
if (document && document.body) {
this.justOpened = true;
document.body.classList.add('m-overlay-modal--shown--no-scroll');
// Prevent dismissal of modal when it's just been opened
this.showModalTimeout = setTimeout(() => {
this.justOpened = false;
}, 20);
}
}
// * MODAL DISMISSAL * --------------------------------------------------------------------------
// Dismiss modal when backdrop is clicked and modal is open
@HostListener('document:click', ['$event'])
clickedBackdrop($event) {
if (this._showModal && !this.justOpened) {
$event.preventDefault();
$event.stopPropagation();
this.close();
}
}
// Don't dismiss modal if click somewhere other than backdrop
clickedModal($event) {
$event.stopPropagation();
}
close() {
document.body.classList.remove('m-overlay-modal--shown--no-scroll');
this.closeModal.emit();
}
ngOnDestroy() {
if (this.showModalTimeout) {
clearTimeout(this.showModalTimeout);
}
this.close();
}
}
<div class="m-walletModal__title">
On-Chain Transfer
</div>
<div class="m-walletModal__desc">
<!-- TODOOJM convert balance into number in .ts and use template format like this: {available, plural, =1
{{{available | number}} token} other {{{available | number}} tokens}}' (full example commented below)-->
<p>
You can request to withdraw up to
<span class="m-walletOnchainTransfer__offchainBalance"
>{{ offchainBalance | token: 18 | number: '1.0-3' }} tokens</span
>
from your rewards to your on-chain wallet below.
</p>
<p class="m-walletOnchainTransfer__gasNotice">
Note: a small amount of ETH will be charged to cover the transaction fee.
Withdrawals go through an approval process and may take up to 72 hours to
complete.
</p>
</div>
<!-- TODOOJM does keyup duplicate the transfer() function trigger?-->
<!-- <form [formGroup]="form" (ngSubmit)="transfer()" (keyup.enter)="transfer()"> -->
<form [formGroup]="form" (keyup.enter)="transfer()">
<div class="m-walletForm__wrapper">
<div class="m-walletForm__fieldsContainer">
<div class="m-walletForm__field--text stretchedField">
<div class="m-walletForm__row--label">
<label for="amount" i18n>Amount</label>
<m-tooltip icon="help">
<ng-container i18n>Placeholder</ng-container>
</m-tooltip>
</div>
<div class="m-walletForm__row--input" [ngClass]="{ invalid: invalid }">
<input
type="number"
id="amount"
name="amount"
formControlName="amount"
class="form-control"
autofocus
/>
</div>
<div class="m-walletForm__row--validation">
<p *ngIf="invalid">
🚧 This doesn't do anything yet 🚧
</p>
</div>
</div>
</div>
<m-shadowboxSubmitButton
[disabled]="inProgress || invalid"
[saving]="inProgress"
(click)="transfer()"
>
Transfer
</m-shadowboxSubmitButton>
</div>
</form>
<!-- TODOOJM -->
<!-- <div
class="m-border mdl-color--white m-token-withdraw"
*ngIf="session.getLoggedInUser().rewards"
>
<p i18n>
You can request to withdraw up to {available, plural, =1
{{{available | number}} token} other {{{available | number}} tokens}} from
your rewards to your <b>OnChain</b> wallet below.
</p>
<p *ngIf="withholding" i18n>
{withholding, plural, =1
{{{withholding | number}} token} other {{{withholding | number}} tokens}}
are unavailable due to credit card payment. They will be released after 30
days the payment occurred.
</p>
<p class="m-token-withdraw__note" i18n>
Note: a small amount of ETH will be charged to cover the transaction fee.
Withdrawals <b>go through an approval process</b>
and may take up to 72 hours to complete.
</p>
<div class="m-token-withdraw--form">
<input
type="text"
class="m-token-withdraw--input"
[disabled]="inProgress"
[ngModel]="amount | number"
(ngModelChange)="setAmount($event)"
/>
<button
class="m-token-withdraw--submit-button"
[disabled]="!canWithdraw()"
(click)="withdraw()"
i18n="@@WALLET__TOKENS__WITHDRAW__WITHDRAW_ACTION"
>
Withdraw
</button>
<div
*ngIf="inProgress"
class="mdl-spinner mdl-js-spinner is-active"
[mdl]
></div>
</div>
<p class="m-token-withdraw--error" *ngIf="!!error">
{{ error }}
</p>
</div>
<m-wallet-token--withdraw-ledger
[preview]="true"
></m-wallet-token--withdraw-ledger> -->
m-walletOnchainTransfer {
.m-walletOnchainTransfer__offchainBalance {
@include m-theme() {
color: themed($m-grey-800);
}
}
p.m-walletOnchainTransfer__gasNotice {
font-size: 15px;
line-height: 20px;
}
}
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Client } from '../../../../services/api';
import { Session } from '../../../../services/session';
import { FormToastService } from '../../../../common/services/form-toast.service';
@Component({
selector: 'm-walletOnchainTransfer',
templateUrl: './onchain-transfer.component.html',
})
export class WalletOnchainTransferComponent implements OnInit {
@Input() offchainBalance;
@Input() onchainAddress;
inProgress;
invalid;
transferComplete = false;
form = this.fb.group({
amount: [this.offchainBalance],
address: [this.onchainAddress],
});
constructor(
protected session: Session,
private formToastService: FormToastService,
private fb: FormBuilder,
protected client: Client
) {}
ngOnInit() {}
transfer() {
// TODOOJM
this.invalid = true;
}
}
// TODOOJM move this over
// import {
// ChangeDetectionStrategy,
// ChangeDetectorRef,
// Component,
// ViewChild,
// } from '@angular/core';
// import { Client } from '../../../../services/api/client';
// import { WithdrawContractService } from '../../../blockchain/contracts/withdraw-contract.service';
// import { Session } from '../../../../services/session';
// import { WalletTokenWithdrawLedgerComponent } from './ledger/ledger.component';
// import { Web3WalletService } from '../../../blockchain/web3-wallet.service';
// @Component({
// moduleId: module.id,
// selector: 'm-wallet-token--withdraw',
// templateUrl: 'withdraw.component.html',
// changeDetection: ChangeDetectionStrategy.OnPush,
// })
// export class WalletTokenWithdrawComponent {
// inProgress: boolean = false;
// balance: number = 0;
// available: number = 0;
// amount: number = 0;
// error: string = '';
// hasWithdrawnToday: boolean = false;
// withholding: number = 0;
// @ViewChild(WalletTokenWithdrawLedgerComponent, { static: true })
// protected ledgerComponent: WalletTokenWithdrawLedgerComponent;
// constructor(
// protected client: Client,
// protected cd: ChangeDetectorRef,
// public session: Session,
// protected contract: WithdrawContractService,
// protected web3Wallet: Web3WalletService
// ) {}
// async ngOnInit() {
// this.load();
// try {
// await this.checkPreviousWithdrawals();
// } catch (e) {
// this.error = 'You can only withdraw once a day';
// }
// }
// async load() {
// this.inProgress = true;
// this.error = '';
// this.detectChanges();
// try {
// let response: any = await this.client.get(
// `api/v2/blockchain/wallet/balance`
// );
// if (response && typeof response.addresses !== 'undefined') {
// this.balance = response.addresses[1].balance / Math.pow(10, 18);
// this.available = response.addresses[1].available / Math.pow(10, 18);
// if (this.balance > this.available) {
// this.withholding = this.balance - this.available;
// }
// this.setAmount(this.available);
// } else {
// this.error = 'Server error';
// }
// } catch (e) {
// console.error(e);
// this.error = (e && e.message) || 'Server error';
// } finally {
// this.inProgress = false;
// this.detectChanges();
// }
// }
// async checkPreviousWithdrawals() {
// let response: any = await this.client.post(
// 'api/v2/blockchain/transactions/can-withdraw'
// );
// if (!response.canWithdraw) {
// this.hasWithdrawnToday = true;
// throw new Error('You can only withdraw once a day');
// }
// }
// setAmount(amount: number | string) {
// if (!amount) {
// this.amount = 0;
// return;
// }
// if (typeof amount === 'number') {
// this.amount = amount;
// this.detectChanges();
// return;
// }
// amount = amount.replace(/,/g, '');
// this.amount = parseFloat(amount);
// this.detectChanges();
// }
// canWithdraw() {
// return (
// !this.hasWithdrawnToday &&
// !this.inProgress &&
// this.amount > 0 &&
// this.amount <= this.available
// );
// }
// async withdraw() {
// this.inProgress = true;
// this.error = '';
// this.detectChanges();
// try {
// await this.checkPreviousWithdrawals();
// await this.web3Wallet.ready();
// if (this.web3Wallet.isUnavailable()) {
// throw new Error('No Ethereum wallets available on your browser.');
// } else if (!(await this.web3Wallet.unlock())) {
// throw new Error(
// 'Your Ethereum wallet is locked or connected to another network.'
// );
// }
// let result: {
// address;
// guid;
// amount;
// gas;
// tx;
// } = await this.contract.request(
// this.session.getLoggedInUser().guid,
// this.amount * Math.pow(10, 18)
// );
// let response: any = await this.client.post(
// `api/v2/blockchain/transactions/withdraw`,
// result
// );
// if (response.done) {
// this.refresh();
// this.ledgerComponent.prepend(response.entity);
// } else {
// this.error = 'Server error';
// }
// } catch (e) {
// console.error(e);
// this.error = (e && e.message) || 'Server error';
// } finally {
// this.inProgress = false;
// this.detectChanges();
// }
// }
// refresh() {
// this.load();
// }
// detectChanges() {
// this.cd.markForCheck();
// this.cd.detectChanges();
// }
// }
<div class="m-walletModal__title">
Phone Verification
</div>
<div class="m-walletModal__desc">
<p>
You can earn tokens for your contributions to the Minds network. The more
interactions on your content, the greater your share of the daily token
reward pool to your off-chain address.
</p>
<p>
To start earning rewards, you will need to enter a unique phone number.
</p>
</div>
<!-- TODOOJM: ensure the three options to trigger onSubmit() don't double-trigger the fx -->
<form [formGroup]="form" (ngSubmit)="onSubmit()" (keyup.enter)="onSubmit()">
<div class="m-walletForm__wrapper">
<div class="m-walletForm__fieldsContainer">
<div class="m-walletForm__field--text stretchedField" *ngIf="!confirming">
<div class="m-walletForm__row--label">
<label for="number" i18n>Mobile Phone Number</label>
<m-tooltip icon="help">
<ng-container i18n
>Placeholder...probs something about no voip numbers
allowed?</ng-container
>
</m-tooltip>
</div>
<m-phone-input
formControlName="number"
class="m-walletForm__row--input"
[ngClass]="{ invalid: invalidNumber }"
name="number"
id="number"
ngDefaultControl
></m-phone-input>
<div class="m-walletForm__row--validation">
<p *ngIf="invalidNumber">
Invalid phone number
</p>
</div>
</div>
<div class="m-walletForm__field--text stretchedField" *ngIf="confirming">
<div class="m-walletForm__row--label">
<label for="code" i18n>Verification Code</label>
<m-tooltip icon="help">
<ng-container i18n>Placeholder</ng-container>
</m-tooltip>
</div>
<div
class="m-walletForm__row--input"
[ngClass]="{ invalid: invalidCode }"
>
<input
type="text"
id="code"
name="code"
formControlName="code"
class="form-control"
autofocus
/>
</div>
<div class="m-walletForm__row--validation">
<p *ngIf="invalidCode">
Invalid verification code
</p>
</div>
</div>
</div>
<!-- TODOOJM button disabling -->
<!-- [disabled]="inProgress || invalidNumber || invalidCode" -->
<m-shadowboxSubmitButton
[disabled]="inProgress"
[saving]="inProgress"
(click)="onSubmit()"
>
{{ !confirming ? 'Send Verification' : 'Verify Code' }}
</m-shadowboxSubmitButton>
</div>
</form>
<div class="m-walletModal__footnote--privacy" *ngIf="!confirming">
Note: Minds does not store the phone numbers you provide. The numbers are
hashed using SHA-256 and combined with a salt key for privacy and security
purposes.
</div>
<div class="m-walletModal__footnote--formStatus" *ngIf="confirming">
<div class="m-walletPhoneVerification__formStatusCodeSent">
Verification code sent to +{{ form.value.number }}
</div>
<div class="m-walletPhoneVerification__formStatusActions">
Didn't receive it? <a (click)="validateNumber()">Send it again</a> or
<a (click)="changePhone()">change phone number</a>
</div>
</div>
<!-- <m-formToast></m-formToast> -->
m-walletPhoneVerification {
.m-walletModal__footnote--privacy {
margin-top: 10px;
font-size: 13px;
line-height: 18px;
}
.m-walletModal__footnote--formStatus {
.m-walletPhoneVerification__formStatusCodeSent {
margin-bottom: 6px;
@include m-theme() {
color: themed($m-grey-800);
}
}
}
m-shadowboxSubmitButton {
width: 147px;
button {
width: 100%;
}
}
@media screen and (max-width: 771px) {
m-shadowboxSubmitButton {
margin: 0 0 21px 0;
}
.stretchedField {
.m-walletForm__row--label m-tooltip {
.m-tooltip--bubble {
right: 12px;
}
}
[class*='m-walletForm__row'] {
flex-flow: row wrap;
label,
input {
flex: 1 1 100%;
min-width: 100%;
max-width: 100%;
}
}
}
}
}
import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import {
FormBuilder,
AbstractControl,
FormGroup,
FormControl,
} from '@angular/forms';
import { FormToastService } from '../../../../common/services/form-toast.service';
import { Client } from '../../../../services/api';
import { Session } from '../../../../services/session';
@Component({
selector: 'm-walletPhoneVerification',
templateUrl: './phone-verification.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WalletPhoneVerificationComponent implements OnInit {
inProgress = false;
confirming = false;
invalidNumber = false;
invalidCode = false;
form = this.fb.group({
number: [''],
code: [''],
secret: [''],
});
@Output() phoneVerificationComplete: EventEmitter<any> = new EventEmitter();
constructor(
protected session: Session,
private formToastService: FormToastService,
private fb: FormBuilder,
protected client: Client,
protected cd: ChangeDetectorRef
) {}
ngOnInit() {}
async validateNumber() {
this.invalidNumber = false;
this.invalidCode = false;
this.inProgress = true;
this.detectChanges();
try {
const response: any = await this.client.post(
'api/v2/blockchain/rewards/verify',
{
number: this.form.value.number,
}
);
this.form.controls['secret'].setValue(response.secret);
this.confirming = true;
} catch (e) {
this.invalidNumber = true;
// this.formToastService.error(e.message);
console.error(e.message);
}
this.inProgress = false;
this.detectChanges();
}
async confirmCode() {
this.invalidNumber = false;
this.invalidCode = false;
this.inProgress = true;
this.detectChanges();
try {
const response: any = await this.client.post(
'api/v2/blockchain/rewards/confirm',
{
number: this.form.value.number,
code: this.form.value.code,
secret: this.form.value.secret,
}
);
window.Minds.user.rewards = true;
this.phoneVerificationComplete.emit();
} catch (e) {
this.invalidCode = true;
// this.formToastService.error(e.message);
}
this.inProgress = false;
this.detectChanges();
}
changePhone() {
this.form.reset();
console.log(this.form.value);
this.invalidNumber = false;
this.invalidCode = false;
this.confirming = false;
this.detectChanges();
}
onSubmit() {
this.confirming ? this.confirmCode() : this.validateNumber();
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
<p>
TODO: m-walletSettings--btc
</p>
<div class="m-walletSettings">
<p>🚧 m-walletSettings--btc 🚧</p>
</div>
<p>
TODO: m-walletSettings--eth
</p>
<div class="m-walletSettings">
<h2>Ether Address</h2>
<p>
🚧 WIP! See
<a href="https://gitlab.com/minds/ux-design/ux/issues/26" target="_blank"
>UX issue #26</a
>
🚧
</p>
<p>
Your Ether address is the same as your on-chain address.<m-tooltip
icon="help"
>
<ng-container i18n
>🚧 Placeholder: Why? On-chain tokens run on the Ethereum network and
require Ether to send 🚧</ng-container
>
</m-tooltip>
</p>
<p></p>
<p>
To view or change your address, go to
<a click="scrollToTokenSettings())">token settings</a>.
</p>
</div>
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'm-walletSettings--eth',
templateUrl: './settings-eth.component.html',
})
export class WalletSettingsETHComponent implements OnInit {
@Output() showTokenSettings: EventEmitter<any> = new EventEmitter();
constructor() {}
ngOnInit() {}
scrollToSettings() {
const settingsEl = document.getElementById('tokenSettings');
if (!settingsEl) {
this.showTokenSettings.emit();
}
setTimeout(
() =>
document.getElementById('dashboardViewsTabs').scrollIntoView({
behavior: 'smooth',
}),
0
);
}
}
<p>
TODO: m-walletSettings--tokens
</p>
<div class="m-walletSettings" id="tokenSettings">
<div class="m-walletSettingsView--setup" *ngIf="!display">
<h4>
On-Chain address
</h4>
<div class="m-walletSettings__setupOptions__recommendation">
<i class="material-icons">thumb_up</i><span>RECOMMENDED</span>
</div>
<ul class="m-walletSettingsView__setupOptions__container">
<li class="m-walletSettingsView__setupOption--metamask">
<div>
<img
class="metamask"
[src]="minds.cdn_assets_url + 'assets/ext/metamask.png'"
/>
<h2>
MetaMask
</h2>
</div>
<p>
<a href="https://metamask.io/" target="_blank">Install MetaMask</a>
for the most seamless user experience.
</p>
<m-shadowboxSubmitButton
[disabled]="inProgress"
[saving]="inProgress"
(click)="display = Views.UseExternal; useExternal()"
>
Link MetaMask
</m-shadowboxSubmitButton>
</li>
<li class="m-walletSettingsView__setupOption--custom">
<h2>
Custom
</h2>
<p>
You don’t need to use MetaMask. You can alternatively manage your own
address.
</p>
<div>
<a (click)="display = Views.CreateAddress; createAddress()"
>Create a new address</a
>
<span>or</span>
<a (click)="display = Views.ProvideAddress">Use your private key</a>
</div>
</li>
<p *ngIf="">
<a (click)="display = Views.currentAddress"
>🚧 Current On-Chain Address 🚧</a
>
</p>
</ul>
</div>
<ul *ngIf="display">
<!---CUSTOM - CREATE ADDRESS----------------------------------------->
<li
class="m-walletSettingsView--addressCreate"
*ngIf="display === Views.CreateAddress"
>
<h2>
Create Address
</h2>
<p *ngIf="!generatedAccount">
Generating address ...<span
*ngIf="inProgress && !generatedAccount"
class="mdl-spinner mdl-js-spinner is-active"
[mdl]
></span>
</p>
<p *ngIf="generatedAccount">
The address
<span class="m-walletSettings__address--emphasis">{{
generatedAccount.address
}}</span>
was successully saved as your on-chain address. Clicking the button
below will download its private key. Ensure you safely store the private
key file.
</p>
<m-shadowboxSubmitButton
[disabled]="!generatedAccount"
[saving]="inProgress && generatedAccount"
(click)="downloadPrivateKey()"
>
Download Private Key
</m-shadowboxSubmitButton>
<a class="m-walletSettings__backButton" (click)="display = null"
><i class="material-icons">arrow_back</i><span>Back</span></a
>
</li>
<!---CUSTOM - PROVIDE ADDRESS---------------------------->
<li
class="m-walletSettingsView--addressProvide"
*ngIf="display === Views.ProvideAddress"
>
<h2>
Provide Address
</h2>
<p>
Enter the address that will be stored as your wallet for Minds on-chain
tokens and Ether payments. Remember that you will need your wallet's
private key to conduct transactions.
</p>
<div class="m-walletForm__field--text stretchedField">
<div class="m-walletForm__row--label">
<label for="providedAddress" i18n>On-chain Address</label>
<m-tooltip icon="help">
<ng-container i18n
>This address is where you will receive on-chain Minds tokens and
Ether payments</ng-container
>
</m-tooltip>
</div>
<div class="m-walletForm__row--input">
<input
type="text"
id="providedAddress"
name="providedAddress"
class="form-control"
[readonly]="inProgress"
[(ngModel)]="providedAddress"
/>
</div>
</div>
<m-shadowboxSubmitButton
[disabled]="inProgress || !providedAddress"
[saving]="inProgress"
(click)="provideAddress()"
>
Save Address
</m-shadowboxSubmitButton>
<a class="m-walletSettings__backButton" (click)="display = null"
><i class="material-icons">arrow_back</i><span>Back</span></a
>
</li>
<!---USE EXTERNAL ADDRESS----------------------------------------------->
<li
class="m-walletSettingsView--addressExternal"
*ngIf="display === Views.UseExternal"
>
<p>
🚧 lol 🦊 see
<a
href="https://gitlab.com/minds/ux-design/ux/issues/27"
target="_blank"
>UX issue #27</a
>
🚧
</p>
<img
class="metamask"
[src]="minds.cdn_assets_url + 'assets/ext/metamask.png'"
/>
<h2>
MetaMask
</h2>
<p>
<ng-container>
Please ensure you select the <b>Main Ethereum Network</b>.
</ng-container>
<ng-container *ngIf="downloadingMetamask">
<b>
Note: After installing and setting up MetaMask you might need to
reload Minds.
</b>
</ng-container>
</p>
<div class="m-walletSettings--tokens--buttons" *ngIf="!hasExternal">
<button (click)="downloadMetamask()">
Download MetaMask
</button>
</div>
<div class="m-walletSettings--tokens--buttons" *ngIf="hasExternal">
<span class="m-walletSettings--tokens--buttons-input">
<ng-container *ngIf="providedAddress; else noProvidedAddress">
{{ providedAddress }}
</ng-container>
<ng-template #noProvidedAddress>
MetaMask is either locked or connected to another network.
</ng-template>
</span>
</div>
<a class="m-walletSettings__backButton" (click)="display = null"
><i class="material-icons">arrow_back</i><span>Back</span></a
>
</li>
<!---CURRENT ADDRESS ----------------------------------------------->
<li
class="m-walletSettingsView--addressCurrent"
*ngIf="display === Views.currentAddress"
>
<h2>
On-Chain Address
</h2>
<p style="margin-bottom: 24px">
🚧 WIP! See
<a
href="https://gitlab.com/minds/ux-design/ux/issues/18"
target="_blank"
>UX issue #18</a
>
🚧
</p>
<div class="m-walletForm__field--text stretchedField">
<div class="m-walletForm__row--label">
<label for="currentAddress" i18n>Address</label>
<m-tooltip icon="help">
<ng-container i18n
>placeholder: 🚧 This will eventually not be an input field
🚧</ng-container
>
</m-tooltip>
</div>
<div class="m-walletForm__row--input">
<input
type="text"
id="currentAddress"
name="currentAddress"
class="form-control"
readonly
[(ngModel)]="currentAddress"
/>
</div>
</div>
<p><a (click)="display = null">Change Address</a></p>
</li>
</ul>
</div>
<m-formToast></m-formToast>
import { Component, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
OnDestroy,
Input,
Output,
EventEmitter,
} from '@angular/core';
import { Router } from '@angular/router';
import { Client } from '../../../../services/api/client';
import { Session } from '../../../../services/session';
import { LocalWalletService } from '../../../blockchain/local-wallet.service';
import { BlockchainService } from '../../../blockchain/blockchain.service';
import { Web3WalletService } from '../../../blockchain/web3-wallet.service';
import { getBrowser } from '../../../../utils/browser';
import { FormToastService } from '../../../../common/services/form-toast.service';
enum Views {
CreateAddress = 1,
ProvideAddress,
UseExternal,
currentAddress,
}
@Component({
selector: 'm-walletSettings--tokens',
templateUrl: './settings-tokens.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WalletSettingsTokensComponent implements OnInit {
constructor() {}
export class WalletSettingsTokensComponent implements OnInit, OnDestroy {
@Input() wallet;
@Input() skippable: boolean = true;
@Output() addressSetupComplete: EventEmitter<any> = new EventEmitter();
inProgress: boolean = false;
error: string;
display: Views;
generatedAccount: any;
providedAddress: string = '';
hasExternal: boolean = false;
currentAddress: string = '';
downloadingMetamask: boolean = false;
minds = window.Minds;
readonly Views = Views;
private _externalTimer;
constructor(
protected client: Client,
protected cd: ChangeDetectorRef,
protected session: Session,
protected router: Router,
protected localWallet: LocalWalletService,
protected blockchain: BlockchainService,
protected web3Wallet: Web3WalletService,
private formToastService: FormToastService
) {}
ngOnInit() {
//already has an address
const currentAddress = this.session.getLoggedInUser().eth_wallet;
this.currentAddress = currentAddress || this.wallet.receiver.address;
if (this.currentAddress) {
this.display = Views.currentAddress;
this.addressSetupComplete.emit();
}
this.checkExternal();
}
ngOnDestroy() {
if (this._externalTimer) {
clearInterval(this._externalTimer);
}
}
async checkExternal() {
this.hasExternal = !(await this.web3Wallet.isLocal());
this.detectChanges();
}
async createAddress() {
try {
this.inProgress = true;
this.detectChanges();
this.generatedAccount = await this.localWallet.create(false);
await this.blockchain.setWallet({
address: this.generatedAccount.address,
});
} catch (e) {
console.error(e);
this.formToastService.error(e);
} finally {
this.inProgress = false;
this.detectChanges();
}
}
async downloadPrivateKey() {
try {
this.inProgress = true;
this.detectChanges();
const { address, privateKey } = this.generatedAccount,
filename = `pk_${address}.csv`,
blob = new Blob([privateKey], { type: 'text/csv' });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
this.inProgress = false; // TODOOJM
this.addressSetupComplete.emit();
} else {
const link = window.document.createElement('a'),
objectUrl = window.URL.createObjectURL(blob);
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(objectUrl);
this.generatedAccount = null;
// this.addressSetupComplete.emit(); //TODOOJM
this.inProgress = false; // TODOOJM
}, 1000);
}
} catch (e) {
console.error(e);
this.formToastService.error(e);
this.inProgress = false;
}
}
canProvideAddress() {
return (
this.providedAddress && /^0x[a-fA-F0-9]{40}$/.test(this.providedAddress)
);
}
async provideAddress() {
if (!this.canProvideAddress() || this.inProgress) {
return;
}
try {
this.inProgress = true;
this.detectChanges();
await this.blockchain.setWallet({ address: this.providedAddress });
this.addressSetupComplete.emit();
} catch (e) {
this.formToastService.error(e);
console.error(e);
} finally {
this.inProgress = false;
this.detectChanges();
}
}
downloadMetamask() {
let browser: string = getBrowser();
let url = '';
switch (browser) {
case 'chrome':
url =
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn';
case 'firefox':
url = 'https://addons.mozilla.org/firefox/addon/ether-metamask/';
case 'opera':
url = 'https://addons.opera.com/extensions/details/metamask/';
default:
url = 'https://metamask.io';
}
window.open(url);
this.downloadingMetamask = true;
}
async useExternal() {
await this.web3Wallet.ready();
this.detectExternal();
this._externalTimer = setInterval(() => {
this.detectExternal();
}, 1000);
}
async detectExternal() {
const address: string =
(await this.web3Wallet.getCurrentWallet(true)) || '';
if (this.providedAddress !== address) {
this.providedAddress = address;
this.detectChanges();
if (this.providedAddress) {
clearInterval(this._externalTimer);
this.provideAddress();
}
}
}
ngOnInit() {}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
<p>
TODO: m-walletSettings--usd
</p>
<div class="m-walletSettings">
🚧 m-walletSettings--usd 🚧
</div>
<div class="m-walletTokenOnboarding__stepsContainer">
<div class="m-walletTokenOnboardingStep--verifyPhone">
<div class="m-walletTokenOnboardingStep__title">
<span>1.</span>
<a
(click)="clickedPhoneStep()"
[ngClass]="{ phoneVerified: phoneVerified }"
>Verify your phone number</a
>
<i class="material-icons" *ngIf="phoneVerified">check</i>
</div>
<div class="m-walletTokenOnboardingStep__desc">
Used simply to verify your uniqueness.
</div>
</div>
<div class="m-walletTokenOnboardingStep--addAddress">
<div class="m-walletTokenOnboardingStep__title">
<span>2.</span>
<a
(click)="clickedAddressStep()"
[ngClass]="{ disabled: !phoneVerified }"
>
Add your on-chain address</a
>
</div>
<div class="m-walletTokenOnboardingStep__desc">
Your Ethereum address allows you to start receiving payments on the
blockchain.
</div>
</div>
</div>
<m-walletModal [showModal]="showModal" (closeModal)="showModal = false">
<m-walletPhoneVerification
class="m-walletTokenOnboardingModal__view--verifyPhone"
*ngIf="activeStep === 'phone'"
(phoneVerificationComplete)="phoneVerificationComplete()"
>
</m-walletPhoneVerification>
</m-walletModal>
m-walletTokenOnboarding {
display: block;
font-size: 14px;
line-height: 19px;
margin-bottom: 35px;
font-weight: 300;
.m-walletTokenOnboarding__stepsContainer {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
border-radius: 2px;
@include m-theme() {
border: 1px solid themed($m-grey-50);
}
a {
text-decoration: underline;
cursor: pointer;
display: inline-block;
font-weight: 400;
&.disabled {
text-decoration: none;
cursor: text;
@include m-theme() {
color: themed($m-grey-800);
}
}
}
}
[class*='m-walletTokenOnboardingStep--'] {
display: flex;
flex-direction: column;
padding: 19px 27px;
box-sizing: border-box;
flex-basis: 50%;
min-width: 200px;
}
.m-walletTokenOnboardingStep--verifyPhone {
@include m-theme() {
border-right: 1px solid themed($m-grey-50);
}
}
.m-walletTokenOnboardingStep__title {
font-weight: 500;
display: flex;
flex-flow: row nowrap;
align-items: center;
@include m-theme() {
color: themed($m-grey-800);
}
a {
font-size: 14px;
line-height: 19px;
margin: 0 8px 0 4px;
&.phoneVerified {
cursor: default;
text-decoration: line-through;
@include m-theme() {
color: themed($m-grey-200);
}
}
}
i {
line-height: 17px;
@include m-theme() {
color: themed($m-green);
}
}
}
.m-walletTokenOnboardingStep__desc {
font-size: 13px;
line-height: 18px;
margin-top: 6px;
@include m-theme() {
color: themed($m-grey-600);
}
}
// ***************************************************
@media screen and (max-width: $max-mobile) {
[class*='m-walletTokenOnboardingStep--'] {
padding: 19px;
}
}
@media screen and (max-width: 830px) {
[class*='m-walletTokenOnboardingStep--'] {
flex-basis: 100%;
padding: 14px 20px;
}
.m-walletTokenOnboardingStep--verifyPhone {
@include m-theme() {
border-right-width: 0px;
border-bottom: 1px solid themed($m-grey-50);
}
}
.m-walletTokenOnboardingStep__desc {
padding-left: 16px;
}
}
}
import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Session } from '../../../../services/session';
import { FormToastService } from '../../../../common/services/form-toast.service';
@Component({
selector: 'm-walletTokenOnboarding',
templateUrl: './token-onboarding.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WalletTokenOnboardingComponent implements OnInit {
phoneVerified = false;
addressAdded = false;
showModal = false;
activeStep = 'phone'; // || address
@Output() onboardingComplete: EventEmitter<any> = new EventEmitter();
@Output() showTokenSettings: EventEmitter<any> = new EventEmitter();
constructor(
protected session: Session,
protected cd: ChangeDetectorRef,
private formToastService: FormToastService,
protected router: Router,
private route: ActivatedRoute
) {}
ngOnInit() {
this.phoneVerified = this.session.getLoggedInUser().rewards;
this.addressAdded = this.session.getLoggedInUser().eth_wallet;
if (this.phoneVerified && this.addressAdded) {
this.onboardingComplete.emit();
}
this.detectChanges();
}
clickedPhoneStep() {
if (!this.phoneVerified) {
this.activeStep = 'phone';
this.showModal = true;
}
this.detectChanges();
}
clickedAddressStep() {
if (!this.addressAdded) {
this.activeStep = 'address';
this.scrollToSettings();
}
this.detectChanges();
}
scrollToSettings() {
const settingsEl = document.getElementById('tokenSettings');
if (!settingsEl) {
this.showTokenSettings.emit();
}
setTimeout(
() =>
document.getElementById('dashboardViewsTabs').scrollIntoView({
behavior: 'smooth',
}),
0
);
}
phoneVerificationComplete() {
this.phoneVerified = true;
this.showModal = false;
this.formToastService.success('Your phone number has been verified');
if (!this.addressAdded) {
this.activeStep = 'address';
} else {
this.onboardingComplete.emit();
}
}
addressSetupComplete() {
this.addressAdded = true;
this.showModal = false;
if (!this.phoneVerified) {
this.activeStep = 'phone';
} else {
this.onboardingComplete.emit();
}
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
<p>
TODO: m-walletTransactionsTable
🚧 m-walletTransactionsTable 🚧
</p>
......@@ -53,13 +53,16 @@ import { WalletDashboardComponent } from './v2/dashboard.component';
import { WalletBalanceTokensV2Component } from './v2/balance-tokens/balance-tokens.component';
import { WalletChartComponent } from './v2/chart/chart.component';
import { WalletTransactionsTableComponent } from './v2/transactions-table/transactions-table.component';
import { WalletActionButtonComponent } from './v2/action-button/action-button.component';
import { WalletRewardsPopupComponent } from './v2/rewards-popup/rewards-popup.component';
import { WalletDashboardService } from './v2/dashboard.service';
import { WalletSettingsTokensComponent } from './v2/settings-tokens/settings-tokens.component';
import { WalletSettingsUSDComponent } from './v2/settings-usd/settings-usd.component';
import { WalletSettingsETHComponent } from './v2/settings-eth/settings-eth.component';
import { WalletSettingsBTCComponent } from './v2/settings-btc/settings-btc.component';
import { WalletTokenOnboardingComponent } from './v2/token-onboarding/token-onboarding.component';
import { WalletModalComponent } from './v2/modal/modal.component';
import { WalletPhoneVerificationComponent } from './v2/phone-verification/phone-verification.component';
import { WalletOnchainTransferComponent } from './v2/onchain-transfer/onchain-transfer.component';
const walletRoutes: Routes = [
{
......@@ -174,13 +177,16 @@ const walletRoutes: Routes = [
WalletDashboardComponent,
WalletBalanceTokensV2Component,
WalletChartComponent,
WalletActionButtonComponent,
WalletRewardsPopupComponent,
WalletTransactionsTableComponent,
WalletSettingsTokensComponent,
WalletSettingsUSDComponent,
WalletSettingsETHComponent,
WalletSettingsBTCComponent,
WalletTokenOnboardingComponent,
WalletModalComponent,
WalletPhoneVerificationComponent,
WalletOnchainTransferComponent,
],
exports: [
WalletComponent,
......
......@@ -39,6 +39,7 @@ interface Minds {
};
};
contribution_values: { [key: string]: number };
from_email_confirmation?: boolean;
}
interface MindsNavigation {
......