...
 
Commits (2)
......@@ -141,14 +141,53 @@ context('Onboarding', () => {
// should be in the newsfeed
cy.location('pathname').should('eq', '/newsfeed/subscriptions');
// should be in the Avatar step
// should have a continue and a skip button
cy.get('button.mf-button--hollow').contains('Skip');
cy.get('button.mf-button--alt').contains('Upload a Photo');
// add avatar
cy.uploadFile(
'#onboarding-avatar-input',
'../fixtures/international-space-station-1776401_1920.jpg',
'image/jpg'
);
// continue should move you to the next step (groups)
cy.get('button.mf-button--alt').contains('Continue').click();
cy.get('.m-onboarding__groupList').should('exist');
// go back to avatar and test Cancel
cy.visit(`/onboarding/avatar`);
cy.location('pathname')
.should('eq', '/onboarding/avatar');
// add avatar
cy.uploadFile(
'#onboarding-avatar-input',
'../fixtures/international-space-station-1776401_1920.jpg',
'image/jpg'
);
cy.wait(1000);
// should cancel cropping
cy.get('button.mf-button--hollow').contains('Cancel').click();
// should redirect to next step
cy.get('button.mf-button--hollow').contains('Skip').click();
// should be in the Groups step
// should have a groups list
// cy.get('.m-groupList__list').should('exist');
cy.get('.m-onboarding__groupList').should('exist');
// clicking on a group join button should join the group
// cy.get('.m-groupList__list .m-groupList__item:first-child .m-join__subscribe').contains('add').click();
// // button should change to a check, and clicking on it should leave the group
// button should change to a check, and clicking on it should leave the group
// cy.get('.m-groupList__list .m-groupList__item:first-child .m-join__subscribed').contains('check').click();
// cy.get('.m-groupList__list .m-groupList__item:first-child .m-join__subscribe i').contains('add');
......@@ -159,6 +198,8 @@ context('Onboarding', () => {
// should be in the Channels step
cy.get('.m-onboarding__channelList').should('exist');
// should have a channels list
// cy.get('.m-channelList__list').should('exist');
// // clicking on a group join button should join the group
......
......@@ -15640,6 +15640,14 @@
"tslib": "^1.9.0"
}
},
"ngx-image-cropper": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ngx-image-cropper/-/ngx-image-cropper-3.0.1.tgz",
"integrity": "sha512-7JecIdG+H8L8WPcFcxHkxjBKTubk6/ImgHE4uutnEmVJ35WwUgCion378ToDwsw9CfRRM/vDSe1OjCTi2pFuLA==",
"requires": {
"tslib": "^1.9.0"
}
},
"ngx-plyr": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ngx-plyr/-/ngx-plyr-3.0.1.tgz",
......
......@@ -63,6 +63,7 @@
"moment": "^2.24.0",
"ng-pick-datetime": "^7.0.0",
"ngx-drag-drop": "^2.0.0",
"ngx-image-cropper": "^3.0.1",
"ngx-plyr": "^3.0.1",
"node-cache": "^5.1.0",
"plotly.js": "^1.47.4",
......
import { Component, EventEmitter } from '@angular/core';
import { Component, ElementRef, EventEmitter, ViewChild } from '@angular/core';
import { UserAvatarService } from '../../services/user-avatar.service';
import { of, Observable } from 'rxjs';
import { ConfigsService } from '../../services/configs.service';
......@@ -57,6 +57,8 @@ export class MindsAvatar {
file: any;
added: EventEmitter<any> = new EventEmitter();
@ViewChild('file', { static: false }) fileInput: ElementRef;
constructor(
public userAvatarService: UserAvatarService,
configs: ConfigsService,
......@@ -121,6 +123,10 @@ export class MindsAvatar {
if (this.waitForDoneSignal !== true) this.done();
}
openFileDialog() {
this.fileInput.nativeElement.click();
}
/**
* Called upon being done.
*/
......
......@@ -23,6 +23,8 @@ import { OnboardingV2Service } from './service/onboarding.service';
import { Client } from '../../services/api/client';
import { Session } from '../../services/session';
import { PhoneVerificationComponent } from './steps/info/phone-input/input.component';
import { ImageCropperModule } from 'ngx-image-cropper';
import { AvatarStepComponent } from './steps/avatar/avatar.component';
const routes: Routes = [
{
......@@ -46,6 +48,10 @@ const routes: Routes = [
path: 'info',
component: InfoStepComponent,
},
{
path: 'avatar',
component: AvatarStepComponent,
},
{
path: 'groups',
component: GroupsStepComponent,
......@@ -72,6 +78,7 @@ const routes: Routes = [
MindsFormsModule,
LegacyModule,
GroupsModule,
ImageCropperModule,
],
exports: [],
declarations: [
......@@ -83,6 +90,7 @@ const routes: Routes = [
GroupsStepComponent,
ChannelsStepComponent,
ChannelListComponent,
AvatarStepComponent,
GroupListComponent,
PhoneVerificationComponent,
],
......
......@@ -70,15 +70,15 @@ m-onboarding {
max-width: 692px;
margin: 0 auto;
@include m-theme() {
background-color: themed($m-white);
}
.m-onboarding__form {
display: block;
padding: 55px 70px 80px;
color: #9b9b9b;
@include m-theme() {
background-color: themed($m-white);
}
&::before {
content: '';
background-color: #fcfcfc;
......@@ -211,6 +211,17 @@ m-onboarding {
}
}
h3 {
font-size: 20px;
line-height: 35px;
font-weight: 500;
margin-top: 0;
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-onboarding__list {
margin: 0 auto;
display: flex;
......
......@@ -21,6 +21,10 @@ export class OnboardingComponent implements OnDestroy {
name: 'Info',
selected: false,
},
{
name: 'Avatar',
selected: false,
},
// {
// name: 'Groups',
// selected: false,
......
......@@ -54,7 +54,7 @@ m-onboarding__progressbar {
flex-grow: 1;
display: flex;
flex-direction: column;
min-width: 72px;
min-width: 50px;
max-width: 122px;
margin: 0 20px;
text-transform: uppercase;
......
<div class="m-onboarding__controls">
<ng-container *ngIf="cropping">
<h3 i18n>Position your photo</h3>
<image-cropper
[imageChangedEvent]="imageChangedEvent"
[aspectRatio]="1 / 1"
[resizeToWidth]="0"
[cropperMinWidth]="128"
[onlyScaleDown]="true"
[roundCropper]="false"
[alignImage]="'left'"
format="png"
[backgroundColor]="'white'"
(imageCropped)="onImageCropped($event)"
(imageLoaded)="onImageLoaded()"
[style.display]="imageLoaded ? null : 'none'"
>
</image-cropper>
</ng-container>
<img [src]="cdnAssetsUrl + 'assets/photos.png'" *ngIf="!cropping" />
<input
id="onboarding-avatar-input"
type="file"
accept="image/*"
#file
(change)="add($event)"
/>
<div class="m-onboarding__actionButtons">
<button
class="mf-button mf-button--hollow"
(click)="cancel()"
*ngIf="cropping"
i18n
>
Cancel
</button>
<button
class="mf-button mf-button--hollow"
(click)="skip()"
*ngIf="!cropping"
i18n
>
Skip
</button>
<button
class="mf-button mf-button--alt"
(click)="uploadPhoto()"
*ngIf="!cropping"
i18n
>
Upload a Photo
</button>
<button
class="mf-button mf-button--alt"
(click)="continue()"
*ngIf="cropping"
i18n
>
Finish
</button>
</div>
</div>
m-onboarding--avatarStep {
.m-onboarding__controls {
display: flex;
flex-direction: column;
image-cropper {
padding: 0 !important;
}
& > img {
width: 125px;
margin: 0 auto 100px;
}
input[type='file'] {
height: 0;
width: 0;
}
}
}
import { Component, ElementRef, ViewChild } from '@angular/core';
import { UserAvatarService } from '../../../../common/services/user-avatar.service';
import { Router } from '@angular/router';
import { ImageCroppedEvent } from 'ngx-image-cropper';
import { Upload } from '../../../../services/api/upload';
import { ConfigsService } from '../../../../common/services/configs.service';
@Component({
selector: 'm-onboarding--avatarStep',
templateUrl: 'avatar.component.html',
})
export class AvatarStepComponent {
readonly cdnAssetsUrl: string;
cropping: boolean = false;
file: any;
imageChangedEvent: any;
imageLoaded: boolean = false;
src: string = '';
waitForDoneSignal: boolean = true;
croppedImage: any = '';
@ViewChild('file', { static: false }) fileInput: ElementRef;
constructor(
protected upload: Upload,
protected userAvatarService: UserAvatarService,
protected router: Router,
private configs: ConfigsService
) {
this.cdnAssetsUrl = this.configs.get('cdn_assets_url');
}
uploadPhoto() {
this.fileInput.nativeElement.click();
}
add(e) {
this.imageChangedEvent = e;
if (e) {
this.cropping = true;
}
}
async save() {
if (!this.croppedImage) {
return;
}
this.userAvatarService.src$.next(this.croppedImage);
fetch(this.croppedImage)
.then(res => res.blob())
.then(async blob => {
const imageFile = new File([blob], Date.now() + '', {
type: 'image/png',
});
try {
const response: any = await this.upload.post(
'api/v1/channel/avatar',
[imageFile],
{ filekey: 'file' }
);
const user = this.configs.get('user');
if (user) {
user.icontime = Date.now();
this.configs.set('user', user);
}
} catch (e) {
console.error(e);
}
});
}
onImageCropped(event: ImageCroppedEvent) {
this.croppedImage = event.base64;
}
onImageLoaded() {
this.imageLoaded = true;
}
cancel() {
this.fileInput.nativeElement.value = null;
this.imageChangedEvent = null;
this.cropping = false;
}
skip() {
this.router.navigate(['/newsfeed']);
}
async continue() {
await this.save();
this.router.navigate(['/newsfeed']);
}
private dataURItoBlob(dataURI) {
const byteString = window.atob(dataURI);
const arrayBuffer = new ArrayBuffer(byteString.length);
const int8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
int8Array[i] = byteString.charCodeAt(i);
}
const blob = new Blob([int8Array], { type: 'image/jpeg' });
return blob;
}
}
......@@ -70,7 +70,7 @@
Skip
</button>
<button class="mf-button mf-button--alt" (click)="continue()" i18n>
Finish
Continue
</button>
</div>
</div>
import { Component, HostListener, ViewChild } from '@angular/core';
import { Session } from '../../../../services/session';
import { MindsUser } from '../../../../interfaces/entities';
import { Client } from '../../../../services/api';
import { Client, Upload } from '../../../../services/api';
import { Router } from '@angular/router';
import { PhoneVerificationComponent } from './phone-input/input.component';
import { ConfigsService } from '../../../../common/services/configs.service';
@Component({
selector: 'm-onboarding__infoStep',
......@@ -34,7 +35,9 @@ export class InfoStepComponent {
constructor(
private session: Session,
private client: Client,
private router: Router
private upload: Upload,
private router: Router,
private configs: ConfigsService
) {
this.user = session.getLoggedInUser();
......@@ -104,6 +107,16 @@ export class InfoStepComponent {
return true;
}
updateUser(prop: string, value: any) {
const user = this.configs.get('user');
user[prop] = value;
const clonedUser = Object.assign({}, user);
this.configs.set('user', clonedUser);
this.session.userEmitter.next(clonedUser);
}
selectedDateChange(date: string) {
this.date = date;
this.dateOfBirthChanged = true;
......@@ -117,12 +130,12 @@ export class InfoStepComponent {
}
skip() {
this.router.navigate(['/newsfeed']);
this.router.navigate(['/onboarding', 'avatar']);
}
continue() {
if (this.saveData()) {
this.router.navigate(['/newsfeed']);
this.router.navigate(['/onboarding', 'avatar']);
}
}
......
This diff was suppressed by a .gitattributes entry.