Skip to content
Commits on Source (28)
......@@ -229,7 +229,7 @@ review:start:
image: minds/helm-eks:latest
script:
- aws eks update-kubeconfig --name=sandbox
- git clone --branch=sandbox-wip https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- git clone --branch=master https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- "helm upgrade \
--install \
--reuse-values \
......
......@@ -116,18 +116,47 @@ context('Discovery', () => {
cy.get("m-topbar--navigation--options ul > m-nsfw-selector ul > li:contains('Other')").click();
});
it('should allow the user to filter by a single hashtag', () => {
it('should allow the user to turn off single hashtag filter and view all posts', () => {
cy.visit('/newsfeed/global/top');
cy.get('m-hashtagssidebarselector__item')
.first()
.click();
});
it('should allow the user to turn off single hashtag filter and view all posts', () => {
it.skip('should allow the user to toggle a single hashtag and then toggle back to the initial feed', () => {
cy.visit('/newsfeed/global/top');
cy.get('m-hashtagssidebarselector__item')
.first()
.find('.m-hashtagsSidebarSelectorList__visibility > i')
.click();
})
// get first label value
cy.get('.m-hashtagsSidebarSelectorList__label').first().invoke('text').then((text) => {
// repeat twice to capture full cycle.
Cypress._.times(2, (i) => {
// split hashtag off of label text
let label = text.split('#')[1];
// click switch
toggleFirstVisibilitySwitch();
// check location name has updated
cy.location('pathname')
.should('eq', `/newsfeed/global/top;period=12h;hashtag=${label}`);
// click switch
toggleFirstVisibilitySwitch();
// check location name has updated
cy.location('pathname')
.should('eq', `/newsfeed/global/top;period=12h`);
});
});
});
// click first visibility switch
const toggleFirstVisibilitySwitch = () => {
cy.get('m-hashtagssidebarselector__item')
.first()
.find('.m-hashtagsSidebarSelectorList__visibility > i')
.click();
}
})
......@@ -661,4 +661,56 @@ context('Newsfeed', () => {
});
});
// enable once failing tests are fixed
it.skip('should post an nsfw activity when value is held by the selector (is blue) but it has not been clicked yet', () => {
// click on nsfw dropdown
cy.get(
'minds-newsfeed-poster m-nsfw-selector .m-dropdown--label-container'
).click();
// select Nudity
cy.get('minds-newsfeed-poster m-nsfw-selector .m-dropdownList__item')
.contains('Nudity')
.click();
// click away
cy.get('minds-newsfeed-poster m-nsfw-selector .minds-bg-overlay').click();
// navigate away from newsfeed and back.
cy.get('[data-cy=data-minds-nav-wallet-button]').first().click(); // bottom bar exists, so take first child
cy.get('[data-cy=data-minds-nav-newsfeed-button]').first().click();
newActivityContent('This is a nsfw post');
postActivityAndAwaitResponse(200);
// should have the mature text toggle
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).should('not.have.class', 'mdl-color-text--red-500');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).should('have.class', 'm-mature-text');
// click the toggle
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).click();
// text should be visible now
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-text-toggle'
).should('have.class', 'mdl-color-text--red-500');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).should('not.have.class', 'm-mature-text');
cy.get(
'.minds-list > minds-activity:first-child .message .m-mature-message-content'
).contains('This is a nsfw post');
deleteActivityFromNewsfeed();
});
});
......@@ -5,64 +5,95 @@
context('Pro Settings', () => {
if (Cypress.env().pro_password) {
// required to run tests against pro user only.
const title = '#title';
const headline = '#headline';
function data(str) {
return `[data-minds=${str}]`;
}
const activityContainer = 'minds-activity';
const footerText = '#footer_text';
const sidebarMenu = data('sidebarMenuLinks');
const general = {
title: data('title'),
headline: data('headline'),
publish: data('publish'),
strings: {
title: 'Minds Pro E2E',
headline: 'This headline is a test',
},
};
const theme = {
textColor: '#textColor',
primaryColor: '#primaryColor',
plainBackgroundColor: '#plainBgColor',
schemeLight: '#scheme_light',
schemeDark: '#scheme_dark',
textColor: data('textColor'),
primaryColor: data('primaryColor'),
plainBgColor: data('plainBgColor'),
schemeLight: data('schemeLight'),
schemeDark: data('schemeDark'),
aspectRatio: {
169: '#tile_ratio_16:9', // 16:9
1610: '#tile_ratio_16:10', // 16:10
43: '#tile_ratio_4:3', // 4:3
11: '#tile_ratio_1:1', // 1:1
169: data('tileRatio_16:9'), // 16:9
1610: data('tileRatio_16:10'), // 16:10
43: data('tileRatio_4:3'), // 4:3
11: data('tileRatio_1:1'), // 1:1
},
strings: {
textColor: '#4690df',
primaryColor: '#cb7848',
plainBgColor: '#b4bbf0',
textColorRgb: 'rgb(70, 144, 223)',
primaryColorRgb: 'rgb(203, 120, 72)',
plainBgColorRgba: 'rgba(180, 187, 240, 0.627)',
resetColor: '#ffffff',
},
};
const hashtags = {
labelInput0: '#tag-label-0',
hashtagInput0: '#tag-tag-0',
labelInput1: '#tag-label-1',
hashtagInput1: '#tag-tag-1',
label1: 'label1',
label2: 'label2',
label3: 'label3',
hashtag1: '#hashtag1',
hashtag2: '#hashtag2',
hashtag3: '#hashtag3',
const assets = {
logo: data('logo'),
background: data('background'),
strings: {
logoFixture: '../../fixtures/avatar.jpeg',
backgroundFixture:
'../../fixtures/international-space-station-1776401_1920.jpg',
},
};
const footer = {
hrefInput: `#footer_link-href-0`,
titleInput: `#footer_link-title-0`,
const hashtags = {
add: data('addHashtag'),
label0: data('tag__label--0'),
tag0: data('tag__tag--0'),
label1: data('tag__label--1'),
tag1: data('tag__tag--1'),
strings: {
label0: 'Label0',
label1: 'Label1',
tag0: '#hashtag0',
tag1: '#hashtag1',
},
};
const strings = {
title: 'Minds Pro E2E',
headline: 'This headline is a test',
footer: 'This is a footer',
footerTitle: 'Minds',
footerHref: 'https://www.minds.com/',
const footer = {
text: data('footerText'),
add: data('addFooterLink'),
linkTitle: data('footerLink__title--0'),
linkHref: data('footerLink__href--0'),
strings: {
text: 'This is a footer',
linkTitle: 'Minds',
linkHref: 'https://www.minds.com/',
},
};
before(() => {
cy.login(true, Cypress.env().pro_username, Cypress.env().pro_password);
});
after(() => {
// cy.visit(`/${Cypress.env().username}`);
cy.visit('/pro/' + Cypress.env().pro_username + '/settings/hashtags')
.location('pathname')
.should(
'eq',
'/pro/' + Cypress.env().pro_username + '/settings/hashtags'
);
clearHashtags();
// Make a post
cy.route('POST', '**/api/v1/newsfeed').as('newsfeed');
cy.visit('/newsfeed/subscriptions');
cy.get('minds-newsfeed-poster textarea')
.click()
.type('Testing 1-2-3');
cy.get('minds-newsfeed-poster .m-posterActionBar__PostButton').click();
cy.wait('@newsfeed').then(xhr => {
expect(xhr.status).to.equal(200);
});
});
beforeEach(() => {
......@@ -76,157 +107,247 @@ context('Pro Settings', () => {
'eq',
'/pro/' + Cypress.env().pro_username + '/settings/general'
);
// ensure window is wide enough to find pro topbar links
cy.viewport(1300, 768);
});
it('should update the title and headline', () => {
it.skip('should update the title and headline', () => {
//enter data
cy.get(title)
cy.get(general.title)
.focus()
.clear()
.type(strings.title);
.type(general.strings.title);
cy.get(headline)
cy.get(general.headline)
.focus()
.clear()
.type(strings.headline);
.type(general.strings.headline);
saveAndPreview();
//check tab title.
//check tab title
cy.title().should(
'eq',
strings.title + ' - ' + strings.headline + ' | Minds'
general.strings.title + ' - ' + general.strings.headline + ' | Minds'
);
});
// Need to find a way around the color input in Cypress.
it('should allow the user to set theme colors', () => {
cy.get(sidebarMenu)
.contains('Theme')
.click();
// reset colors so changes will be submitted
cy.get(theme.textColor)
.click()
.clear()
.type(theme.strings.resetColor);
cy.get(theme.primaryColor)
.click()
.clear()
.type(theme.strings.resetColor);
cy.get(theme.plainBgColor)
.click()
.clear()
.type(theme.strings.resetColor);
save();
// set theme colors to be tested
cy.get(theme.textColor)
.click()
.clear()
.type(theme.strings.textColor);
cy.get(theme.primaryColor)
.click()
.clear()
.type(theme.strings.primaryColor);
cy.get(theme.plainBgColor)
.click()
.clear()
.type(theme.strings.plainBgColor);
saveAndPreview();
cy.get('.m-pro__searchBox input').should(
'have.css',
'background-color',
theme.strings.plainBgColorRgba
);
cy.get('.m-proChannelTopbar__navItem')
.contains('Videos')
.should('have.css', 'color', theme.strings.textColorRgb);
cy.get('.m-proChannelTopbar__navItem')
.contains('Feed')
.click();
it('should allow the user to set a dark theme for posts', () => {
cy.contains('Theme').click();
// make window narrow enough to show hamburger icon/menu
cy.viewport('ipad-mini');
cy.get('.m-proHamburgerMenu__trigger')
.click()
.get('.m-proHamburgerMenu__item--active')
.should('have.css', 'color')
.and('eq', theme.strings.primaryColorRgb);
});
// Skipping until Emi changes feeds from 'top' to 'latest'
it.skip('should allow the user to set a dark theme for posts', () => {
cy.get(sidebarMenu)
.contains('Theme')
.click();
cy.get(theme.schemeDark).click();
// Toggle radio to enable submit button
cy.get(theme.schemeLight).click({ force: true });
cy.get(theme.schemeDark).click({ force: true });
saveAndPreview();
cy.contains('Feed').click();
cy.get('.m-proChannelTopbar__navItem')
.contains('Feed')
.click();
cy.get(activityContainer)
.should('have.css', 'background-color')
.and('eq', 'rgb(35, 35, 35)');
});
it('should allow the user to set a light theme for posts', () => {
cy.contains('Theme').click();
// Skipping until Emi changes feeds from 'top' to 'latest'
it.skip('should allow the user to set a light theme for posts', () => {
cy.get(sidebarMenu)
.contains('Theme')
.click();
cy.get(theme.schemeLight).click();
// Toggle radio to enable submit button
cy.get(theme.schemeDark).click({ force: true });
cy.get(theme.schemeLight).click({ force: true });
saveAndPreview();
cy.contains('Feed').click();
cy.get('.m-proChannelTopbar__navItem')
.contains('Feed')
.click();
cy.get(activityContainer)
.should('have.css', 'background-color')
.and('eq', 'rgb(255, 255, 255)');
});
it.skip('should allow the user to upload logo and background images', () => {
cy.get(sidebarMenu)
.contains('Assets')
.click();
cy.uploadFile(assets.logo, assets.strings.logoFixture, 'image/jpeg');
cy.uploadFile(
assets.background,
assets.strings.backgroundFixture,
'image/jpg'
);
saveAndPreview();
// cy.get('.m-proChannelTopbar__logo').should('have.attr', 'src', Cypress.env().url + '/fs/v1/pro/930229554033729554/logo/1574379135');
// cy.get(m-proChannel).should('have.attr', 'background-image', 'url(' + Cypress.env().url + '/fs/v1/banners/998753812159717376/fat/1563497464)');
});
it('should allow the user to set category hashtags', () => {
cy.contains('Hashtags').click();
cy.get(sidebarMenu)
.contains('Hashtags')
.click();
cy.contains('Add').click();
cy.get(hashtags.add).click();
cy.get('m-draggableList')
.contains('clear')
.click({ multiple: true });
cy.get(hashtags.labelInput0)
cy.get(hashtags.add).click();
cy.get(hashtags.label0)
.clear()
.type(hashtags.label1);
.type(hashtags.strings.label0);
cy.get(hashtags.hashtagInput0)
cy.get(hashtags.tag0)
.clear()
.type(hashtags.hashtag1);
.type(hashtags.strings.tag0);
cy.contains('Add').click();
cy.get(hashtags.add).click();
cy.get(hashtags.labelInput1)
.first()
cy.get(hashtags.label1)
.clear()
.type(hashtags.label2);
.type(hashtags.strings.label1);
cy.get(hashtags.hashtagInput1)
.first()
cy.get(hashtags.tag1)
.clear()
.type(hashtags.hashtag2);
.type(hashtags.strings.tag1);
saveAndPreview();
//check the labels are present and clickable.
cy.contains('label1');
cy.contains('label2');
cy.contains(hashtags.strings.label0);
cy.contains(hashtags.strings.label1);
});
it('should allow the user to set footer', () => {
cy.contains('Footer').click();
cy.get(sidebarMenu)
.contains('Footer')
.click();
cy.get(footerText)
.clear()
.type(strings.footer);
// clear any existing footer links
cy.get(footer.add).click();
cy.get('m-draggableList')
.contains('clear')
.click({ multiple: true });
cy.contains('Add Link').click();
// add a new footer link
cy.get(footer.add).click();
cy.get(footer.hrefInput)
cy.get(footer.linkHref)
.clear()
.type(strings.footerHref);
.type(footer.strings.linkHref);
cy.get(footer.titleInput)
cy.get(footer.linkTitle)
.clear()
.type(strings.footerTitle);
.type(footer.strings.linkTitle);
// add footer text
cy.get(footer.text)
.clear()
.type(footer.strings.text);
saveAndPreview();
cy.contains(strings.footerTitle)
cy.contains(footer.strings.linkTitle)
.should('have.attr', 'href')
.should('contain', strings.footerHref);
.should('contain', footer.strings.linkHref);
cy.get(footer.text).should('contain', footer.strings.text);
});
//save, await response, preview.
function saveAndPreview() {
function save() {
//save and await response
cy.contains('Save')
.click()
cy.get('.m-shadowboxSubmitButton')
.contains('Save')
.click({ force: true })
.wait('@settings')
.then(xhr => {
expect(xhr.status).to.equal(200);
expect(xhr.response.body).to.deep.equal({ status: 'success' });
});
//go to pro page
cy.contains('View Pro Channel').click();
}
function clearHashtags() {
cy.contains('Hashtags').click();
cy.contains('Add').click();
function saveAndPreview() {
save();
cy.contains('clear').click({ multiple: true });
saveAndPreview();
//go to pro page
cy.visit('/pro/' + Cypress.env().pro_username);
}
//
// it.only('should update the theme', () => {
// // nav to theme tab
// cy.contains('Theme')
// .click();
// cy.get(theme.plainBackgroundColor).then(elem => {
// elem.val('#00dd00');
// //save and await response
// cy.contains('Save')
// .click()
// .wait('@settings').then((xhr) => {
// expect(xhr.status).to.equal(200);
// expect(xhr.response.body).to.deep.equal({ status: 'success' });
// });
// //go to pro page
// cy.contains('View Pro Channel').click();
// })
}
});
......@@ -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';
......@@ -126,9 +126,17 @@ import { ShadowboxSubmitButtonComponent } from './components/shadowbox-submit-bu
import { FormDescriptorComponent } from './components/form-descriptor/form-descriptor.component';
import { FormToastComponent } from './components/form-toast/form-toast.component';
import { SsoService } from './services/sso.service';
import { EmailConfirmationComponent } from './components/email-confirmation/email-confirmation.component';
PlotlyModule.plotlyjs = PlotlyJS;
const routes: Routes = [
{
path: 'email-confirmation',
redirectTo: '/',
},
];
@NgModule({
imports: [
NgCommonModule,
......@@ -137,6 +145,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormsModule,
ReactiveFormsModule,
PlotlyModule,
RouterModule.forChild(routes),
],
declarations: [
MINDS_PIPES,
......@@ -241,6 +250,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
EmailConfirmationComponent,
],
exports: [
MINDS_PIPES,
......@@ -340,6 +350,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;
}
}
......@@ -32,25 +32,31 @@
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="rawData?.unit"
*ngFor="let value of hoverInfo.values"
class="m-chartV2__hoverInfo__row--primary"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number: '1.0-0' | abbr }}
{{ rawData.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchCase="eth">
{{ hoverInfo.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ hoverInfo.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.0-3' }} {{ rawData?.unit }}
</ng-template>
<span
class="m-chartV2__hoverInfoRow__hex"
[style.background-color]="value.color"
></span>
<ng-container [ngSwitch]="rawData?.unit">
<ng-template ngSwitchCase="number">
{{ value.value | number: '1.0-0' | abbr }}
{{ value.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ value.value | currency }} USD
</ng-template>
<ng-template ngSwitchCase="eth">
{{ value.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ value.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ value.value | number: '1.0-3' }} {{ rawData?.unit }}
</ng-template>
</ng-container>
</div>
<div class="m-chartV2__hoverInfo__row" *ngIf="isComparison">
vs
......
......@@ -58,6 +58,14 @@ m-chartV2 {
}
}
.m-chartV2__hoverInfoRow__hex {
width: 6px;
height: 6px;
display: inline-block;
margin-right: 2px;
border-radius: 50%;
}
.m-chartV2__hoverInfo__closeBtn {
display: none;
font-size: 15px;
......
......@@ -76,11 +76,12 @@ export class ChartV2Component implements OnInit, OnDestroy {
? this.rawData.visualisation.segments.slice(0, 1)
: this.rawData.visualisation.segments;
if (this.segments.length === 2) {
this.isComparison = true;
// this.isComparison = true;
// Reverse the segments so comparison line is layered behind current line
this.segments.reverse();
// this.segments.reverse();
// Current line should be blue, not grey
this.swapSegmentColors();
// this.swapSegmentColors();
this.detectChanges();
}
this.themeSubscription = this.themeService.isDark$.subscribe(isDark => {
this.isDark = isDark;
......@@ -131,7 +132,9 @@ export class ChartV2Component implements OnInit, OnDestroy {
this.segments.forEach((segment, index) => {
const segmentMarkerFills = [];
for (let i = 0; i < this.pointsPerSegment; i++) {
segmentMarkerFills[i] = this.getColor('m-white');
segmentMarkerFills[i] = this.getColor(
chartPalette.segmentColorIds[index]
);
}
this.markerFills.push(segmentMarkerFills);
});
......@@ -169,12 +172,12 @@ export class ChartV2Component implements OnInit, OnDestroy {
y: this.unpack(this.segments[i].buckets, 'value'),
};
if (this.segments[i].comparison) {
segment.line.dash = 'dot';
}
this.data[i] = segment;
});
if (this.isComparison) {
this.data[0].line.dash = 'dot';
}
}
setLayout() {
......@@ -252,7 +255,7 @@ export class ChartV2Component implements OnInit, OnDestroy {
onHover($event) {
this.hoverPoint = $event.points[0].pointIndex;
this.addMarkerFill();
this.emptyMarkerFill();
if (!this.isMini) {
this.showShape($event);
}
......@@ -265,7 +268,7 @@ export class ChartV2Component implements OnInit, OnDestroy {
}
onUnhover($event) {
this.emptyMarkerFill();
this.addMarkerFill();
this.hideShape();
this.hoverInfoDiv.style.opacity = 0;
this.detectChanges();
......@@ -306,24 +309,37 @@ export class ChartV2Component implements OnInit, OnDestroy {
}
populateHoverInfo() {
const pt = this.isComparison ? 1 : 0;
// TODO: format value strings here and remove ngSwitch from template?
this.hoverInfo['date'] = this.segments[pt].buckets[this.hoverPoint].date;
this.hoverInfo['date'] = this.segments[0].buckets[this.hoverPoint].date;
this.hoverInfo['value'] =
this.rawData.unit !== 'usd'
? this.segments[pt].buckets[this.hoverPoint].value
: this.segments[pt].buckets[this.hoverPoint].value / 100;
if (this.isComparison && this.segments[1]) {
this.hoverInfo['comparisonValue'] =
this.rawData.unit !== 'usd'
? this.segments[0].buckets[this.hoverPoint].value
: this.segments[0].buckets[this.hoverPoint].value / 100;
this.hoverInfo['comparisonDate'] = this.segments[0].buckets[
this.hoverPoint
].date;
? this.segments[0].buckets[this.hoverPoint].value
: this.segments[0].buckets[this.hoverPoint].value / 100;
this.hoverInfo['values'] = [];
for (const pt in this.segments) {
const segment = this.segments[pt];
this.hoverInfo['values'][pt] = {
value:
this.rawData.unit !== 'usd'
? segment.buckets[this.hoverPoint].value
: segment.buckets[this.hoverPoint].value / 100,
label: segment.label || this.rawData.label,
color: this.getColor(chartPalette.segmentColorIds[pt]),
};
}
// if (this.isComparison && this.segments[1]) {
// this.hoverInfo['comparisonValue'] =
// this.rawData.unit !== 'usd'
// ? this.segments[0].buckets[this.hoverPoint].value
// : this.segments[0].buckets[this.hoverPoint].value / 100;
//
// this.hoverInfo['comparisonDate'] = this.segments[0].buckets[
// this.hoverPoint
// ].date;
// }
}
positionHoverInfo($event) {
......
<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);
}
}
......@@ -14,7 +14,7 @@ m-shadowboxHeader.isScrollable {
position: relative;
transition: all 0.3s ease;
@include m-theme() {
border-top: 1px solid rgba(themed($m-grey-50), 0.5);
border-top: 1px solid rgba(themed($m-grey-200), 0.4);
background-color: themed($m-white);
}
}
......
......@@ -37,7 +37,11 @@
<h3>{{ menu.header.label }}</h3>
</div>
<nav class="m-sidebarMenu__linksContainer" *ngIf="menu.links">
<nav
class="m-sidebarMenu__linksContainer"
*ngIf="menu.links"
data-minds="sidebarMenuLinks"
>
<div class="m-sidebarMenu__link" *ngFor="let link of menu.links">
<ng-container *ngIf="link.permissionGranted">
<ng-container *ngIf="!link.newWindow">
......
......@@ -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>
......
......@@ -5,6 +5,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Newsfeed"
i18n-title
data-cy="data-minds-nav-newsfeed-button"
>
<i class="material-icons">home</i>
<span class="m-v2-topbarNavItem__Text" i18n>Newsfeed</span>
......@@ -16,6 +17,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Discovery"
i18n-title
data-cy="data-minds-nav-discovery-button"
>
<i class="material-icons">search</i>
<span class="m-v2-topbarNavItem__Text" i18n>Discovery</span>
......@@ -27,6 +29,7 @@
routerLinkActive="m-v2-topbarNav__Item--active"
title="Wallet"
i18n-title
data-cy="data-minds-nav-wallet-button"
>
<i class="material-icons">account_balance</i>
<span class="m-v2-topbarNavItem__Text" i18n>Wallet</span>
......
......@@ -28,17 +28,13 @@ export class AnalyticsFiltersComponent implements OnInit, OnDestroy {
// TODO: remove all of this once channel search is ready
// Temporarily remove channel search from channel filter options
this.subscription = this.analyticsService.filters$.subscribe(filters => {
this.filters = filters;
const channelFilter = filters.find(filter => filter.id === 'channel');
channelFilter.options = channelFilter.options.filter(option => {
return option.id === 'all' || option.id === 'self';
});
this.filters.find(filter => filter.id === 'channel').options =
channelFilter.options;
if (channelFilter) {
channelFilter.options = channelFilter.options.filter(option => {
return option.id === 'all' || option.id === 'self';
});
}
this.filters = filters;
this.detectChanges();
});
}
......