...
 
Commits (15)
......@@ -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';
......@@ -127,9 +127,17 @@ import { FormDescriptorComponent } from './components/form-descriptor/form-descr
import { FormToastComponent } from './components/form-toast/form-toast.component';
import { SsoService } from './services/sso.service';
import { PagesService } from './services/pages.service';
import { EmailConfirmationComponent } from './components/email-confirmation/email-confirmation.component';
PlotlyModule.plotlyjs = PlotlyJS;
const routes: Routes = [
{
path: 'email-confirmation',
redirectTo: '/',
},
];
@NgModule({
imports: [
NgCommonModule,
......@@ -138,6 +146,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormsModule,
ReactiveFormsModule,
PlotlyModule,
RouterModule.forChild(routes),
],
declarations: [
MINDS_PIPES,
......@@ -242,6 +251,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
EmailConfirmationComponent,
],
exports: [
MINDS_PIPES,
......@@ -341,6 +351,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
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);
}
}
......@@ -43,7 +43,6 @@
<li
class="m-dropdownList__item m-user-menuDropdown__Item"
(click)="closeMenu()"
*ngIf="getCurrentUser()?.pro"
>
<a routerLink="/analytics/dashboard/traffic">
<i class="material-icons">timeline</i>
......
......@@ -32,10 +32,10 @@
class="m-analyticsDashboard__description"
*ngIf="description$ | async as description"
>
{{ description }}
<ng-container *ngIf="(category$ | async) === 'earnings'">
<a *ngIf="!session.getLoggedInUser().pro" routerLink="/pro"
>Upgrade to Pro</a
<span *ngIf="!session.getLoggedInUser().pro">
In order to start earning,
<a routerLink="/pro">upgrade to Pro</a>.</span
>
<a
*ngIf="
......@@ -49,6 +49,7 @@
>Enable payouts</a
>
</ng-container>
{{ description }}
</p>
<m-analytics__layout--chart
m-dashboardLayout__body
......
<div class="m-channel--explicit-overlay--content">
<h3>
This channel contains mature content
</h3>
<div
class="m-btn m-btn--slim m-btn--action m-channel--explicit-overlay--action"
(click)="disableFilter()"
>
View
<div class="m-channel--explicit-overlay--container" *ngIf="!hidden">
<div class="m-channel--explicit-overlay--content">
<h3>
This channel contains content that is NSFW
</h3>
<div
class="m-btn m-btn--slim m-btn--action m-channel--explicit-overlay--action"
(click)="disableFilter()"
>
View
</div>
</div>
</div>
m-channel--explicit-overlay {
.m-channel--explicit-overlay--container {
display: flex;
justify-content: center;
align-items: center;
......
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExplicitOverlayComponent } from './overlay.component';
import { Session } from '../../../services/session';
import { sessionMock } from '../../../../tests/session-mock.spec';
import { Storage } from '../../../services/storage';
import { Router } from '@angular/router';
import { storageMock } from '../../../../tests/storage-mock.spec';
let routerMock = new (function() {
this.navigate = jasmine.createSpy('navigate');
})();
describe('OverlayComponent', () => {
let comp: ExplicitOverlayComponent;
let fixture: ComponentFixture<ExplicitOverlayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ExplicitOverlayComponent],
imports: [],
providers: [
{ provide: Storage, useValue: storageMock },
{ provide: Session, useValue: sessionMock },
{ provide: Router, useValue: routerMock },
],
}).compileComponents();
}));
beforeEach(done => {
jasmine.MAX_PRETTY_PRINT_DEPTH = 10;
jasmine.clock().uninstall();
jasmine.clock().install();
fixture = TestBed.createComponent(ExplicitOverlayComponent);
comp = fixture.componentInstance;
comp.hidden = true;
fixture.detectChanges();
if (fixture.isStable()) {
done();
} else {
fixture.whenStable().then(() => {
done();
});
}
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should not show overlay when mature visibility is set', () => {
comp.channel = {
mature_visibility: true,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
it('should overlay when channel is mature', () => {
comp._channel = {
is_mature: true,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay when channel is nsfw for one reason', () => {
comp._channel = {
nsfw: [1],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay when channel is nsfw for multiple reason', () => {
comp._channel = {
nsfw: [1, 2, 3],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeFalsy();
});
it('should overlay not show overlay if channel is not nsfw, mature and no mature_visibility', () => {
comp._channel = {
mature_visibility: false,
is_mature: false,
nsfw: [],
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
it('should not register undefined values as a false positive, and show the overlay', () => {
comp._channel = {
mature_visibility: undefined,
is_mature: undefined,
nsfw: undefined,
};
comp.showOverlay();
fixture.detectChanges();
expect(comp.hidden).toBeTruthy();
});
});
import { Component, HostBinding, Input } from '@angular/core';
import { Component, Input } from '@angular/core';
import { Session } from '../../../services/session';
import { Router } from '@angular/router';
import { Storage } from '../../../services/storage';
......@@ -8,16 +8,12 @@ import { Storage } from '../../../services/storage';
templateUrl: 'overlay.component.html',
})
export class ExplicitOverlayComponent {
@HostBinding('hidden') hidden: boolean;
_channel: any;
public hidden = true;
public _channel: any;
@Input() set channel(value: any) {
this._channel = value;
this.hidden =
!this._channel ||
!this._channel.is_mature ||
this._channel.mature_visibility;
this.showOverlay();
}
constructor(
......@@ -34,8 +30,31 @@ export class ExplicitOverlayComponent {
this.router.navigate(['/login']);
}
disableFilter() {
/**
* Disables overlay screen, revealing channel.
*/
protected disableFilter(): void {
this._channel.mature_visibility = true;
this.hidden = true;
}
/**
* Determines whether the channel overlay should be shown
* over the a channel.
*/
public showOverlay(): void {
if (!this._channel) {
return;
}
if (this._channel.mature_visibility) {
this.hidden = true;
} else if (this._channel.is_mature) {
this.hidden = false;
} else if (this._channel.nsfw && this._channel.nsfw.length > 0) {
this.hidden = false;
} else {
this.hidden = true;
}
}
}
......@@ -263,16 +263,14 @@
</button>
</div>
<ng-container *mIfFeature="'purchase-pro'">
<a
*ngIf="showBecomeProButton"
class="m-btn m-link-btn m-btn--with-icon m-btn--slim m-btn--action"
routerLink="/pro"
>
<i class="material-icons">business_center</i>
<span i18n>Become Pro</span>
</a>
</ng-container>
<a
*ngIf="showBecomeProButton"
class="m-btn m-link-btn m-btn--with-icon m-btn--slim m-btn--action"
[routerLink]="proSettingsRouterLink"
>
<i class="material-icons">business_center</i>
<span i18n>Try Pro</span>
</a>
<a
*ngIf="showProSettings"
......
......@@ -128,11 +128,13 @@
<ng-container *mIfFeature="'pro'">
<a
class="m-page--sidebar--navigation--item"
routerLink="/pro"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
*ngIf="session.isLoggedIn() && !session.getLoggedInUser().pro"
>
<i class="material-icons">business_center</i>
<span>Become Pro</span>
<span>Try Pro</span>
</a>
</ng-container>
......
......@@ -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%;
......
......@@ -19,6 +19,10 @@ m-pro--channel-tile {
> img {
object-fit: cover;
}
.m-videoPlayer__placeholder {
height: 100%;
}
}
.m-proChannelTile__text {
......
......@@ -3,46 +3,40 @@
<m-shadowboxLayout [scrollableHeader]="false" [isForm]="true">
<div class="m-shadowboxLayout__header hasTitle">
<h3 class="m-shadowboxHeader__title">{{ activeTab | titlecase }}</h3>
<ng-container [ngSwitch]="activeTab">
<ng-template ngSwitchCase="general">
<h4 class="m-shadowboxHeader__subtitle">
<h4 class="m-shadowboxHeader__subtitle">
<ng-container [ngSwitch]="activeTab">
<ng-template ngSwitchCase="general">
Customize your title and headline
</h4>
</ng-template>
<ng-template ngSwitchCase="theme">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="theme">
Set up your site's color theme
</h4>
</ng-template>
<ng-template ngSwitchCase="assets">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="assets">
Upload custom logo and background images
</h4>
</ng-template>
<ng-template ngSwitchCase="hashtags">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="hashtags">
Set up your category filter hashtags
</h4>
</ng-template>
<ng-template ngSwitchCase="footer">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="footer">
Set up your site's footer links
</h4>
</ng-template>
<ng-template ngSwitchCase="domain">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="domain">
Customize your site domain
</h4>
</ng-template>
<ng-template ngSwitchCase="payouts">
<h4 class="m-shadowboxHeader__subtitle">
</ng-template>
<ng-template ngSwitchCase="payouts">
Select the currency type you wish you be paid out in. Please note
payouts only occur after your
<a routerLink="/analytics/dashboard/earnings">earnings</a> are
equivalent to $100 or greater.
</h4>
</ng-template>
</ng-container>
</ng-template>
</ng-container>
<a
class="m-proSettings__upgradeLink"
routerLink="/pro"
*ngIf="!isActive"
>(Upgrade to Pro)</a
>
</h4>
</div>
<div class="m-shadowboxLayout__bottom">
......@@ -110,6 +104,7 @@
for="publish"
class="m-proSettings__customInputContainer--checkbox"
i18n
(click)="onEnableProThemeClick($event)"
>Enable Pro theme
<input
type="checkbox"
......@@ -738,11 +733,18 @@
id="domain"
name="domain"
formControlName="domain"
required
/>
<m-formDescriptor>
If you wish to use a custom domain then please email
info@minds.com.
<ng-container
*ngIf="isActive; else previewModeDomainNote"
>
If you wish to use a custom domain then please email
info@minds.com.
</ng-container>
<ng-template #previewModeDomainNote
>You can control your domain once you purchase
Pro.</ng-template
>
</m-formDescriptor>
</div>
<div class="m-proSettings__row--validation">
......
......@@ -8,6 +8,14 @@ m-proSettings {
padding: 0;
}
.m-proSettings__upgradeLink {
display: inline;
font-weight: bold;
color: #4690df;
text-decoration: none;
margin-bottom: 8px;
}
m-tooltip {
margin-left: 10px;
.m-tooltip {
......
......@@ -45,6 +45,7 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
'payouts',
];
isActive: boolean;
settings: any;
inProgress: boolean;
......@@ -131,6 +132,10 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
this.detectChanges();
this.load();
});
if (!this.session.isLoggedIn()) {
this.router.navigate(['/login'], { replaceUrl: true });
return;
}
}
ngOnDestroy() {
......@@ -143,7 +148,22 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
const { isActive, settings } = await this.service.get(this.user);
if (!isActive && !this.user) {
this.isActive = isActive;
if (!isActive) {
// Non-actives have no domain control
this.form
.get('domain')
.get('domain')
.setValidators([]);
this.form
.get('domain')
.get('domain')
.disable();
this.form.get('published').disable();
}
if (!settings) {
this.router.navigate(['/pro'], { replaceUrl: true });
return;
}
......@@ -389,6 +409,12 @@ export class ProSettingsComponent implements OnInit, OnDestroy {
}
}
onEnableProThemeClick(e: MouseEvent): void {
if (!this.isActive) {
this.router.navigate(['/pro']);
}
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
......
......@@ -2,7 +2,7 @@ const sidebarMenu = {
header: {
id: 'pro_settings',
label: 'Pro Settings',
permissions: ['pro'],
permissions: ['user'],
},
links: [
{
......@@ -40,7 +40,7 @@ const sidebarMenu = {
},
{
id: ':username',
label: 'View Pro Channel',
label: 'Preview Pro Channel',
path: 'pro/:username',
newWindow: true,
},
......
......@@ -50,6 +50,16 @@
>
Upgrade to Pro
</button>
<button
class="mf-button mf-button--alt"
*ngIf="session.getLoggedInUser()"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
>
Try Pro
</button>
</ng-container>
<ng-template #comingSoon>
<button class="mf-button mf-button--alt" i18n>
......@@ -77,6 +87,16 @@
>
Cancel Pro
</button>
<button
class="mf-button mf-button--alt"
*ngIf="session.getLoggedInUser()"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
>
Settings
</button>
</div>
</ng-template>
......
......@@ -50,7 +50,7 @@ export class ProSubscriptionComponent implements OnInit {
constructor(
protected service: ProService,
protected session: Session,
public session: Session,
protected overlayModal: OverlayModalService,
protected wirePaymentHandlers: WirePaymentHandlersService,
protected cd: ChangeDetectorRef,
......
......@@ -83,4 +83,9 @@ m-pro--subscription {
color: themed($m-red);
}
}
.mf-button {
margin-bottom: $minds-margin;
margin-right: $minds-margin;
}
}
......@@ -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 = '';
......
......@@ -111,7 +111,6 @@
<a
class="m-page--sidebar--navigation--item"
*ngIf="session.getLoggedInUser().pro"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
......
......@@ -137,6 +137,17 @@
>
More info
</a>
<a
class="m-upgradesUpgradeOptionsPlan__moreInfo"
[routerLink]="[
'/pro/' + session.getLoggedInUser().username + '/settings'
]"
i18n
*ngIf="session.getLoggedInUser()"
style="padding-left: 16px"
>
Try Pro
</a>
</p>
</div>
......
......@@ -39,6 +39,7 @@ interface Minds {
};
};
contribution_values: { [key: string]: number };
from_email_confirmation?: boolean;
}
interface MindsNavigation {
......