diff options
Diffstat (limited to 'client/src/app/+signup')
23 files changed, 882 insertions, 0 deletions
diff --git a/client/src/app/+signup/+register/custom-stepper.component.html b/client/src/app/+signup/+register/custom-stepper.component.html new file mode 100644 index 000000000..bf507fc4f --- /dev/null +++ b/client/src/app/+signup/+register/custom-stepper.component.html | |||
@@ -0,0 +1,25 @@ | |||
1 | <section class="container"> | ||
2 | <header> | ||
3 | <ng-container *ngFor="let step of steps; let i = index; let isLast = last;"> | ||
4 | <div | ||
5 | class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }" | ||
6 | (click)="onClick(i)" | ||
7 | > | ||
8 | <div class="step-index"> | ||
9 | <ng-container *ngIf="!isCompleted(step)">{{ i + 1 }}</ng-container> | ||
10 | <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon> | ||
11 | </div> | ||
12 | |||
13 | <div class="step-label">{{ step.label }}</div> | ||
14 | </div> | ||
15 | |||
16 | <!-- Do no display if this is the last child --> | ||
17 | <div *ngIf="!isLast" class="connector"></div> | ||
18 | </ng-container> | ||
19 | </header> | ||
20 | |||
21 | <div [style.display]="selected ? 'block' : 'none'"> | ||
22 | <ng-container [ngTemplateOutlet]="selected.content"></ng-container> | ||
23 | </div> | ||
24 | |||
25 | </section> | ||
diff --git a/client/src/app/+signup/+register/custom-stepper.component.scss b/client/src/app/+signup/+register/custom-stepper.component.scss new file mode 100644 index 000000000..2371c8ae5 --- /dev/null +++ b/client/src/app/+signup/+register/custom-stepper.component.scss | |||
@@ -0,0 +1,66 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | $grey-color: #9CA3AB; | ||
5 | $index-block-height: 32px; | ||
6 | |||
7 | header { | ||
8 | display: flex; | ||
9 | justify-content: space-between; | ||
10 | font-size: 15px; | ||
11 | margin-bottom: 30px; | ||
12 | |||
13 | .step-info { | ||
14 | color: $grey-color; | ||
15 | display: flex; | ||
16 | flex-direction: column; | ||
17 | align-items: center; | ||
18 | width: $index-block-height; | ||
19 | |||
20 | .step-index { | ||
21 | display: flex; | ||
22 | justify-content: center; | ||
23 | align-items: center; | ||
24 | width: $index-block-height; | ||
25 | height: $index-block-height; | ||
26 | border-radius: 100px; | ||
27 | border: 2px solid $grey-color; | ||
28 | margin-bottom: 10px; | ||
29 | |||
30 | my-global-icon { | ||
31 | @include apply-svg-color(var(--mainBackgroundColor)); | ||
32 | |||
33 | width: 22px; | ||
34 | height: 22px; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | .step-label { | ||
39 | width: max-content; | ||
40 | } | ||
41 | |||
42 | &.active, | ||
43 | &.completed { | ||
44 | .step-index { | ||
45 | border-color: var(--mainColor); | ||
46 | background-color: var(--mainColor); | ||
47 | color: var(--mainBackgroundColor); | ||
48 | } | ||
49 | |||
50 | .step-label { | ||
51 | color: var(--mainColor); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | &.completed { | ||
56 | cursor: pointer; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | .connector { | ||
61 | flex: auto; | ||
62 | margin: $index-block-height/2 10px 0 10px; | ||
63 | height: 2px; | ||
64 | background-color: $grey-color; | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts new file mode 100644 index 000000000..2ae40f3a9 --- /dev/null +++ b/client/src/app/+signup/+register/custom-stepper.component.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { Component } from '@angular/core' | ||
2 | import { CdkStep, CdkStepper } from '@angular/cdk/stepper' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-custom-stepper', | ||
6 | templateUrl: './custom-stepper.component.html', | ||
7 | styleUrls: [ './custom-stepper.component.scss' ], | ||
8 | providers: [ { provide: CdkStepper, useExisting: CustomStepperComponent } ] | ||
9 | }) | ||
10 | export class CustomStepperComponent extends CdkStepper { | ||
11 | |||
12 | onClick (index: number): void { | ||
13 | this.selectedIndex = index | ||
14 | } | ||
15 | |||
16 | isCompleted (step: CdkStep) { | ||
17 | return step.stepControl && step.stepControl.dirty && step.stepControl.valid | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/+signup/+register/register-routing.module.ts b/client/src/app/+signup/+register/register-routing.module.ts new file mode 100644 index 000000000..e3a5001dc --- /dev/null +++ b/client/src/app/+signup/+register/register-routing.module.ts | |||
@@ -0,0 +1,28 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { MetaGuard } from '@ngx-meta/core' | ||
4 | import { RegisterComponent } from './register.component' | ||
5 | import { ServerConfigResolver } from '@app/core/routing/server-config-resolver.service' | ||
6 | import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service' | ||
7 | |||
8 | const registerRoutes: Routes = [ | ||
9 | { | ||
10 | path: '', | ||
11 | component: RegisterComponent, | ||
12 | canActivate: [ MetaGuard, UnloggedGuard ], | ||
13 | data: { | ||
14 | meta: { | ||
15 | title: 'Register' | ||
16 | } | ||
17 | }, | ||
18 | resolve: { | ||
19 | serverConfigLoaded: ServerConfigResolver | ||
20 | } | ||
21 | } | ||
22 | ] | ||
23 | |||
24 | @NgModule({ | ||
25 | imports: [ RouterModule.forChild(registerRoutes) ], | ||
26 | exports: [ RouterModule ] | ||
27 | }) | ||
28 | export class RegisterRoutingModule {} | ||
diff --git a/client/src/app/+signup/+register/register-step-channel.component.html b/client/src/app/+signup/+register/register-step-channel.component.html new file mode 100644 index 000000000..68ea4473a --- /dev/null +++ b/client/src/app/+signup/+register/register-step-channel.component.html | |||
@@ -0,0 +1,50 @@ | |||
1 | <form role="form" [formGroup]="form"> | ||
2 | |||
3 | <div class="channel-explanations"> | ||
4 | <p i18n> | ||
5 | A channel is an entity in which you upload your videos. Creating several of them helps you to organize and separate your content.<br /> | ||
6 | For example, you could decide to have a channel to publish your piano concerts, and another channel in which you publish your videos talking about ecology. | ||
7 | </p> | ||
8 | |||
9 | <p> | ||
10 | Other users can decide to subscribe any channel they want, to be notified when you publish a new video. | ||
11 | </p> | ||
12 | </div> | ||
13 | |||
14 | <div class="form-group"> | ||
15 | <label for="name" i18n>Channel name</label> | ||
16 | |||
17 | <div class="input-group"> | ||
18 | <input | ||
19 | type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel" | ||
20 | formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" | ||
21 | > | ||
22 | <div class="input-group-append"> | ||
23 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
24 | </div> | ||
25 | </div> | ||
26 | |||
27 | <div *ngIf="formErrors.name" class="form-error"> | ||
28 | {{ formErrors.name }} | ||
29 | </div> | ||
30 | |||
31 | <div *ngIf="isSameThanUsername()" class="form-error" i18n> | ||
32 | Channel name cannot be the same than your account name. You can click on the first step to update your account name. | ||
33 | </div> | ||
34 | </div> | ||
35 | |||
36 | <div class="form-group"> | ||
37 | <label for="displayName" i18n>Channel display name</label> | ||
38 | |||
39 | <div class="input-group"> | ||
40 | <input | ||
41 | type="text" id="displayName" | ||
42 | formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }" | ||
43 | > | ||
44 | </div> | ||
45 | |||
46 | <div *ngIf="formErrors.displayName" class="form-error"> | ||
47 | {{ formErrors.displayName }} | ||
48 | </div> | ||
49 | </div> | ||
50 | </form> | ||
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/register-step-channel.component.ts new file mode 100644 index 000000000..9e13f75b3 --- /dev/null +++ b/client/src/app/+signup/+register/register-step-channel.component.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||
2 | import { AuthService } from '@app/core' | ||
3 | import { FormReactive, VideoChannelValidatorsService } from '@app/shared' | ||
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
5 | import { FormGroup } from '@angular/forms' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-register-step-channel', | ||
9 | templateUrl: './register-step-channel.component.html', | ||
10 | styleUrls: [ './register.component.scss' ] | ||
11 | }) | ||
12 | export class RegisterStepChannelComponent extends FormReactive implements OnInit { | ||
13 | @Input() username: string | ||
14 | @Output() formBuilt = new EventEmitter<FormGroup>() | ||
15 | |||
16 | constructor ( | ||
17 | protected formValidatorService: FormValidatorService, | ||
18 | private authService: AuthService, | ||
19 | private videoChannelValidatorsService: VideoChannelValidatorsService | ||
20 | ) { | ||
21 | super() | ||
22 | } | ||
23 | |||
24 | get instanceHost () { | ||
25 | return window.location.host | ||
26 | } | ||
27 | |||
28 | isSameThanUsername () { | ||
29 | return this.username && this.username === this.form.value['name'] | ||
30 | } | ||
31 | |||
32 | ngOnInit () { | ||
33 | this.buildForm({ | ||
34 | name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME, | ||
35 | displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME | ||
36 | }) | ||
37 | |||
38 | setTimeout(() => this.formBuilt.emit(this.form)) | ||
39 | } | ||
40 | } | ||
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html new file mode 100644 index 000000000..cd0c78bfa --- /dev/null +++ b/client/src/app/+signup/+register/register-step-user.component.html | |||
@@ -0,0 +1,54 @@ | |||
1 | <form role="form" [formGroup]="form"> | ||
2 | |||
3 | <div class="form-group"> | ||
4 | <label for="username" i18n>Username</label> | ||
5 | |||
6 | <div class="input-group"> | ||
7 | <input | ||
8 | type="text" id="username" i18n-placeholder placeholder="Example: jane_doe" | ||
9 | formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }" | ||
10 | > | ||
11 | <div class="input-group-append"> | ||
12 | <span class="input-group-text">@{{ instanceHost }}</span> | ||
13 | </div> | ||
14 | </div> | ||
15 | |||
16 | <div *ngIf="formErrors.username" class="form-error"> | ||
17 | {{ formErrors.username }} | ||
18 | </div> | ||
19 | </div> | ||
20 | |||
21 | <div class="form-group"> | ||
22 | <label for="email" i18n>Email</label> | ||
23 | <input | ||
24 | type="text" id="email" i18n-placeholder placeholder="Email" | ||
25 | formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }" | ||
26 | > | ||
27 | <div *ngIf="formErrors.email" class="form-error"> | ||
28 | {{ formErrors.email }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
33 | <label for="password" i18n>Password</label> | ||
34 | <input | ||
35 | type="password" id="password" i18n-placeholder placeholder="Password" | ||
36 | formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" | ||
37 | > | ||
38 | <div *ngIf="formErrors.password" class="form-error"> | ||
39 | {{ formErrors.password }} | ||
40 | </div> | ||
41 | </div> | ||
42 | |||
43 | <div class="form-group form-group-terms"> | ||
44 | <my-peertube-checkbox | ||
45 | inputName="terms" formControlName="terms" | ||
46 | i18n-labelHtml | ||
47 | labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance" | ||
48 | ></my-peertube-checkbox> | ||
49 | |||
50 | <div *ngIf="formErrors.terms" class="form-error"> | ||
51 | {{ formErrors.terms }} | ||
52 | </div> | ||
53 | </div> | ||
54 | </form> | ||
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts new file mode 100644 index 000000000..3825ae371 --- /dev/null +++ b/client/src/app/+signup/+register/register-step-user.component.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | ||
2 | import { AuthService } from '@app/core' | ||
3 | import { FormReactive, UserValidatorsService } from '@app/shared' | ||
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
5 | import { FormGroup } from '@angular/forms' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-register-step-user', | ||
9 | templateUrl: './register-step-user.component.html', | ||
10 | styleUrls: [ './register.component.scss' ] | ||
11 | }) | ||
12 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | ||
13 | @Output() formBuilt = new EventEmitter<FormGroup>() | ||
14 | |||
15 | constructor ( | ||
16 | protected formValidatorService: FormValidatorService, | ||
17 | private authService: AuthService, | ||
18 | private userValidatorsService: UserValidatorsService | ||
19 | ) { | ||
20 | super() | ||
21 | } | ||
22 | |||
23 | get instanceHost () { | ||
24 | return window.location.host | ||
25 | } | ||
26 | |||
27 | ngOnInit () { | ||
28 | this.buildForm({ | ||
29 | username: this.userValidatorsService.USER_USERNAME, | ||
30 | password: this.userValidatorsService.USER_PASSWORD, | ||
31 | email: this.userValidatorsService.USER_EMAIL, | ||
32 | terms: this.userValidatorsService.USER_TERMS | ||
33 | }) | ||
34 | |||
35 | setTimeout(() => this.formBuilt.emit(this.form)) | ||
36 | } | ||
37 | } | ||
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html new file mode 100644 index 000000000..24def68c1 --- /dev/null +++ b/client/src/app/+signup/+register/register.component.html | |||
@@ -0,0 +1,41 @@ | |||
1 | <div class="margin-content"> | ||
2 | |||
3 | <div i18n class="title-page title-page-single"> | ||
4 | Create an account | ||
5 | </div> | ||
6 | |||
7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> | ||
8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> | ||
9 | |||
10 | <div class="wrapper" *ngIf="!signupDone"> | ||
11 | <div> | ||
12 | <my-custom-stepper linear *ngIf="!signupDone"> | ||
13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User information"> | ||
14 | <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user> | ||
15 | |||
16 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> | ||
17 | </cdk-step> | ||
18 | |||
19 | <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel information"> | ||
20 | <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel> | ||
21 | |||
22 | <button i18n cdkStepperNext (click)="signup()" | ||
23 | [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" | ||
24 | > | ||
25 | Create my account | ||
26 | </button> | ||
27 | </cdk-step> | ||
28 | |||
29 | <cdk-step i18n-label label="Done" editable="false"> | ||
30 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
31 | </cdk-step> | ||
32 | </my-custom-stepper> | ||
33 | </div> | ||
34 | |||
35 | <div> | ||
36 | <label i18n>Features found on this instance</label> | ||
37 | <my-instance-features-table></my-instance-features-table> | ||
38 | </div> | ||
39 | </div> | ||
40 | |||
41 | </div> | ||
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss new file mode 100644 index 000000000..6f61b78f7 --- /dev/null +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -0,0 +1,58 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .alert { | ||
5 | font-size: 15px; | ||
6 | text-align: center; | ||
7 | } | ||
8 | |||
9 | .wrapper { | ||
10 | display: flex; | ||
11 | justify-content: space-between; | ||
12 | flex-wrap: wrap; | ||
13 | |||
14 | & > div { | ||
15 | margin-bottom: 40px; | ||
16 | width: 450px; | ||
17 | |||
18 | @media screen and (max-width: 500px) { | ||
19 | width: auto; | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | |||
24 | my-instance-features-table { | ||
25 | display: block; | ||
26 | |||
27 | margin-bottom: 40px; | ||
28 | } | ||
29 | |||
30 | .form-group-terms { | ||
31 | margin: 30px 0; | ||
32 | } | ||
33 | |||
34 | .input-group { | ||
35 | @include peertube-input-group(400px); | ||
36 | } | ||
37 | |||
38 | .input-group-append { | ||
39 | height: 30px; | ||
40 | } | ||
41 | |||
42 | input:not([type=submit]) { | ||
43 | @include peertube-input-text(400px); | ||
44 | |||
45 | display: block; | ||
46 | |||
47 | &#username, | ||
48 | &#name { | ||
49 | width: auto !important; | ||
50 | flex-grow: 1; | ||
51 | } | ||
52 | } | ||
53 | |||
54 | input[type=submit], | ||
55 | button { | ||
56 | @include peertube-button; | ||
57 | @include orange-button; | ||
58 | } | ||
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts new file mode 100644 index 000000000..cd6059728 --- /dev/null +++ b/client/src/app/+signup/+register/register.component.ts | |||
@@ -0,0 +1,89 @@ | |||
1 | import { Component } from '@angular/core' | ||
2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' | ||
3 | import { UserService, UserValidatorsService } from '@app/shared' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { UserRegister } from '@shared/models/users/user-register.model' | ||
6 | import { FormGroup } from '@angular/forms' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-register', | ||
10 | templateUrl: './register.component.html', | ||
11 | styleUrls: [ './register.component.scss' ] | ||
12 | }) | ||
13 | export class RegisterComponent { | ||
14 | info: string = null | ||
15 | error: string = null | ||
16 | success: string = null | ||
17 | signupDone = false | ||
18 | |||
19 | formStepUser: FormGroup | ||
20 | formStepChannel: FormGroup | ||
21 | |||
22 | constructor ( | ||
23 | private authService: AuthService, | ||
24 | private userValidatorsService: UserValidatorsService, | ||
25 | private notifier: Notifier, | ||
26 | private userService: UserService, | ||
27 | private serverService: ServerService, | ||
28 | private redirectService: RedirectService, | ||
29 | private i18n: I18n | ||
30 | ) { | ||
31 | } | ||
32 | |||
33 | get requiresEmailVerification () { | ||
34 | return this.serverService.getConfig().signup.requiresEmailVerification | ||
35 | } | ||
36 | |||
37 | hasSameChannelAndAccountNames () { | ||
38 | return this.getUsername() === this.getChannelName() | ||
39 | } | ||
40 | |||
41 | getUsername () { | ||
42 | if (!this.formStepUser) return undefined | ||
43 | |||
44 | return this.formStepUser.value['username'] | ||
45 | } | ||
46 | |||
47 | getChannelName () { | ||
48 | if (!this.formStepChannel) return undefined | ||
49 | |||
50 | return this.formStepChannel.value['name'] | ||
51 | } | ||
52 | |||
53 | onUserFormBuilt (form: FormGroup) { | ||
54 | this.formStepUser = form | ||
55 | } | ||
56 | |||
57 | onChannelFormBuilt (form: FormGroup) { | ||
58 | this.formStepChannel = form | ||
59 | } | ||
60 | |||
61 | signup () { | ||
62 | this.error = null | ||
63 | |||
64 | const body: UserRegister = Object.assign(this.formStepUser.value, { channel: this.formStepChannel.value }) | ||
65 | |||
66 | this.userService.signup(body).subscribe( | ||
67 | () => { | ||
68 | this.signupDone = true | ||
69 | |||
70 | if (this.requiresEmailVerification) { | ||
71 | this.info = this.i18n('Now please check your emails to verify your account and complete signup.') | ||
72 | return | ||
73 | } | ||
74 | |||
75 | // Auto login | ||
76 | this.authService.login(body.username, body.password) | ||
77 | .subscribe( | ||
78 | () => { | ||
79 | this.success = this.i18n('You are now logged in as {{username}}!', { username: body.username }) | ||
80 | }, | ||
81 | |||
82 | err => this.error = err.message | ||
83 | ) | ||
84 | }, | ||
85 | |||
86 | err => this.error = err.message | ||
87 | ) | ||
88 | } | ||
89 | } | ||
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts new file mode 100644 index 000000000..46336cbd0 --- /dev/null +++ b/client/src/app/+signup/+register/register.module.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RegisterRoutingModule } from './register-routing.module' | ||
3 | import { RegisterComponent } from './register.component' | ||
4 | import { SharedModule } from '@app/shared' | ||
5 | import { CdkStepperModule } from '@angular/cdk/stepper' | ||
6 | import { RegisterStepChannelComponent } from './register-step-channel.component' | ||
7 | import { RegisterStepUserComponent } from './register-step-user.component' | ||
8 | import { CustomStepperComponent } from './custom-stepper.component' | ||
9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | ||
10 | |||
11 | @NgModule({ | ||
12 | imports: [ | ||
13 | RegisterRoutingModule, | ||
14 | SharedModule, | ||
15 | CdkStepperModule, | ||
16 | SignupSharedModule | ||
17 | ], | ||
18 | |||
19 | declarations: [ | ||
20 | RegisterComponent, | ||
21 | CustomStepperComponent, | ||
22 | RegisterStepChannelComponent, | ||
23 | RegisterStepUserComponent | ||
24 | ], | ||
25 | |||
26 | exports: [ | ||
27 | RegisterComponent | ||
28 | ], | ||
29 | |||
30 | providers: [ | ||
31 | ] | ||
32 | }) | ||
33 | export class RegisterModule { } | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html new file mode 100644 index 000000000..2e4180632 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html | |||
@@ -0,0 +1,22 @@ | |||
1 | <div class="margin-content"> | ||
2 | <div i18n class="title-page title-page-single"> | ||
3 | Request email for account verification | ||
4 | </div> | ||
5 | |||
6 | <form *ngIf="requiresEmailVerification; else emailVerificationNotRequired" role="form" (ngSubmit)="askSendVerifyEmail()" [formGroup]="form"> | ||
7 | <div class="form-group"> | ||
8 | <label i18n for="verify-email-email">Email</label> | ||
9 | <input | ||
10 | type="email" id="verify-email-email" i18n-placeholder placeholder="Email address" required | ||
11 | formControlName="verify-email-email" [ngClass]="{ 'input-error': formErrors['verify-email-email'] }" | ||
12 | > | ||
13 | <div *ngIf="formErrors['verify-email-email']" class="form-error"> | ||
14 | {{ formErrors['verify-email-email'] }} | ||
15 | </div> | ||
16 | </div> | ||
17 | <input type="submit" i18n-value value="Send verification email" [disabled]="!form.valid"> | ||
18 | </form> | ||
19 | <ng-template #emailVerificationNotRequired> | ||
20 | <div i18n>This instance does not require email verification.</div> | ||
21 | </ng-template> | ||
22 | </div> | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss new file mode 100644 index 000000000..efec6b706 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss | |||
@@ -0,0 +1,12 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input:not([type=submit]) { | ||
5 | @include peertube-input-text(340px); | ||
6 | display: block; | ||
7 | } | ||
8 | |||
9 | input[type=submit] { | ||
10 | @include peertube-button; | ||
11 | @include orange-button; | ||
12 | } | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts new file mode 100644 index 000000000..cfd471fa4 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
3 | import { Notifier, RedirectService } from '@app/core' | ||
4 | import { ServerService } from '@app/core/server' | ||
5 | import { FormReactive, UserService } from '@app/shared' | ||
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
7 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-verify-account-ask-send-email', | ||
11 | templateUrl: './verify-account-ask-send-email.component.html', | ||
12 | styleUrls: [ './verify-account-ask-send-email.component.scss' ] | ||
13 | }) | ||
14 | |||
15 | export class VerifyAccountAskSendEmailComponent extends FormReactive implements OnInit { | ||
16 | |||
17 | constructor ( | ||
18 | protected formValidatorService: FormValidatorService, | ||
19 | private userValidatorsService: UserValidatorsService, | ||
20 | private userService: UserService, | ||
21 | private serverService: ServerService, | ||
22 | private notifier: Notifier, | ||
23 | private redirectService: RedirectService, | ||
24 | private i18n: I18n | ||
25 | ) { | ||
26 | super() | ||
27 | } | ||
28 | |||
29 | get requiresEmailVerification () { | ||
30 | return this.serverService.getConfig().signup.requiresEmailVerification | ||
31 | } | ||
32 | |||
33 | ngOnInit () { | ||
34 | this.buildForm({ | ||
35 | 'verify-email-email': this.userValidatorsService.USER_EMAIL | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | askSendVerifyEmail () { | ||
40 | const email = this.form.value['verify-email-email'] | ||
41 | this.userService.askSendVerifyEmail(email) | ||
42 | .subscribe( | ||
43 | () => { | ||
44 | const message = this.i18n( | ||
45 | 'An email with verification link will be sent to {{email}}.', | ||
46 | { email } | ||
47 | ) | ||
48 | this.notifier.success(message) | ||
49 | this.redirectService.redirectToHomepage() | ||
50 | }, | ||
51 | |||
52 | err => { | ||
53 | this.notifier.error(err.message) | ||
54 | } | ||
55 | ) | ||
56 | } | ||
57 | } | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html new file mode 100644 index 000000000..728709ca6 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html | |||
@@ -0,0 +1,15 @@ | |||
1 | <div class="margin-content"> | ||
2 | <div i18n class="title-page title-page-single"> | ||
3 | Verify account email confirmation | ||
4 | </div> | ||
5 | |||
6 | <my-signup-success i18n *ngIf="success; else verificationError" message="Your email has been verified and you may now login."> | ||
7 | </my-signup-success> | ||
8 | |||
9 | <ng-template #verificationError> | ||
10 | <div> | ||
11 | <span i18n>An error occurred. </span> | ||
12 | <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a> | ||
13 | </div> | ||
14 | </ng-template> | ||
15 | </div> | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts new file mode 100644 index 000000000..3fb2d1cd8 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts | |||
@@ -0,0 +1,50 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { Notifier } from '@app/core' | ||
5 | import { UserService } from '@app/shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-verify-account-email', | ||
9 | templateUrl: './verify-account-email.component.html' | ||
10 | }) | ||
11 | |||
12 | export class VerifyAccountEmailComponent implements OnInit { | ||
13 | success = false | ||
14 | |||
15 | private userId: number | ||
16 | private verificationString: string | ||
17 | |||
18 | constructor ( | ||
19 | private userService: UserService, | ||
20 | private notifier: Notifier, | ||
21 | private router: Router, | ||
22 | private route: ActivatedRoute, | ||
23 | private i18n: I18n | ||
24 | ) { | ||
25 | } | ||
26 | |||
27 | ngOnInit () { | ||
28 | this.userId = this.route.snapshot.queryParams['userId'] | ||
29 | this.verificationString = this.route.snapshot.queryParams['verificationString'] | ||
30 | |||
31 | if (!this.userId || !this.verificationString) { | ||
32 | this.notifier.error(this.i18n('Unable to find user id or verification string.')) | ||
33 | } else { | ||
34 | this.verifyEmail() | ||
35 | } | ||
36 | } | ||
37 | |||
38 | verifyEmail () { | ||
39 | this.userService.verifyEmail(this.userId, this.verificationString) | ||
40 | .subscribe( | ||
41 | () => { | ||
42 | this.success = true | ||
43 | }, | ||
44 | |||
45 | err => { | ||
46 | this.notifier.error(err.message) | ||
47 | } | ||
48 | ) | ||
49 | } | ||
50 | } | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account-routing.module.ts b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts new file mode 100644 index 000000000..16d5fe0d0 --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { MetaGuard } from '@ngx-meta/core' | ||
4 | import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component' | ||
5 | import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component' | ||
6 | |||
7 | const verifyAccountRoutes: Routes = [ | ||
8 | { | ||
9 | path: '', | ||
10 | canActivateChild: [ MetaGuard ], | ||
11 | children: [ | ||
12 | { | ||
13 | path: 'email', | ||
14 | component: VerifyAccountEmailComponent, | ||
15 | data: { | ||
16 | meta: { | ||
17 | title: 'Verify account email' | ||
18 | } | ||
19 | } | ||
20 | }, | ||
21 | { | ||
22 | path: 'ask-send-email', | ||
23 | component: VerifyAccountAskSendEmailComponent, | ||
24 | data: { | ||
25 | meta: { | ||
26 | title: 'Verify account ask send email' | ||
27 | } | ||
28 | } | ||
29 | } | ||
30 | ] | ||
31 | } | ||
32 | ] | ||
33 | |||
34 | @NgModule({ | ||
35 | imports: [ RouterModule.forChild(verifyAccountRoutes) ], | ||
36 | exports: [ RouterModule ] | ||
37 | }) | ||
38 | export class VerifyAccountRoutingModule {} | ||
diff --git a/client/src/app/+signup/+verify-account/verify-account.module.ts b/client/src/app/+signup/+verify-account/verify-account.module.ts new file mode 100644 index 000000000..9fe14e81e --- /dev/null +++ b/client/src/app/+signup/+verify-account/verify-account.module.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { VerifyAccountRoutingModule } from './verify-account-routing.module' | ||
3 | import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component' | ||
4 | import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component' | ||
5 | import { SharedModule } from '@app/shared' | ||
6 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | ||
7 | |||
8 | @NgModule({ | ||
9 | imports: [ | ||
10 | VerifyAccountRoutingModule, | ||
11 | SharedModule, | ||
12 | SignupSharedModule | ||
13 | ], | ||
14 | |||
15 | declarations: [ | ||
16 | VerifyAccountEmailComponent, | ||
17 | VerifyAccountAskSendEmailComponent | ||
18 | ], | ||
19 | |||
20 | exports: [], | ||
21 | |||
22 | providers: [] | ||
23 | }) | ||
24 | export class VerifyAccountModule { | ||
25 | } | ||
diff --git a/client/src/app/+signup/shared/signup-shared.module.ts b/client/src/app/+signup/shared/signup-shared.module.ts new file mode 100644 index 000000000..cd21fdef3 --- /dev/null +++ b/client/src/app/+signup/shared/signup-shared.module.ts | |||
@@ -0,0 +1,21 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { SignupSuccessComponent } from '../shared/signup-success.component' | ||
3 | import { SharedModule } from '@app/shared' | ||
4 | |||
5 | @NgModule({ | ||
6 | imports: [ | ||
7 | SharedModule | ||
8 | ], | ||
9 | |||
10 | declarations: [ | ||
11 | SignupSuccessComponent | ||
12 | ], | ||
13 | |||
14 | exports: [ | ||
15 | SignupSuccessComponent | ||
16 | ], | ||
17 | |||
18 | providers: [ | ||
19 | ] | ||
20 | }) | ||
21 | export class SignupSharedModule { } | ||
diff --git a/client/src/app/+signup/shared/signup-success.component.html b/client/src/app/+signup/shared/signup-success.component.html new file mode 100644 index 000000000..e35f858c6 --- /dev/null +++ b/client/src/app/+signup/shared/signup-success.component.html | |||
@@ -0,0 +1,16 @@ | |||
1 | <!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO --> | ||
2 | |||
3 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2"> | ||
4 | <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/> | ||
5 | <polyline class="path check" fill="none" stroke="#73AF55" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" points="100.2,40.2 51.5,88.8 29.8,67.5 "/> | ||
6 | </svg> | ||
7 | |||
8 | <p class="bottom-message">Welcome on PeerTube!</p> | ||
9 | |||
10 | <div *ngIf="message" class="alert alert-success"> | ||
11 | <p>{{ message }}</p> | ||
12 | |||
13 | <p i18n> | ||
14 | If you need help to use PeerTube, you can take a look to the <a href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>. | ||
15 | </p> | ||
16 | </div> | ||
diff --git a/client/src/app/+signup/shared/signup-success.component.scss b/client/src/app/+signup/shared/signup-success.component.scss new file mode 100644 index 000000000..fbc27c8bc --- /dev/null +++ b/client/src/app/+signup/shared/signup-success.component.scss | |||
@@ -0,0 +1,76 @@ | |||
1 | svg { | ||
2 | width: 100px; | ||
3 | display: block; | ||
4 | margin: 40px auto 0; | ||
5 | } | ||
6 | |||
7 | .path { | ||
8 | stroke-dasharray: 1000; | ||
9 | stroke-dashoffset: 0; | ||
10 | |||
11 | &.circle { | ||
12 | -webkit-animation: dash .9s ease-in-out; | ||
13 | animation: dash .9s ease-in-out; | ||
14 | } | ||
15 | |||
16 | &.line { | ||
17 | stroke-dashoffset: 1000; | ||
18 | -webkit-animation: dash .9s .35s ease-in-out forwards; | ||
19 | animation: dash .9s .35s ease-in-out forwards; | ||
20 | } | ||
21 | |||
22 | &.check { | ||
23 | stroke-dashoffset: -100; | ||
24 | -webkit-animation: dash-check .9s .35s ease-in-out forwards; | ||
25 | animation: dash-check .9s .35s ease-in-out forwards; | ||
26 | } | ||
27 | } | ||
28 | |||
29 | .bottom-message { | ||
30 | text-align: center; | ||
31 | margin: 20px 0 60px; | ||
32 | font-size: 1.25em; | ||
33 | color: #73AF55; | ||
34 | } | ||
35 | |||
36 | .alert { | ||
37 | font-size: 15px; | ||
38 | text-align: center; | ||
39 | } | ||
40 | |||
41 | |||
42 | @-webkit-keyframes dash { | ||
43 | 0% { | ||
44 | stroke-dashoffset: 1000; | ||
45 | } | ||
46 | 100% { | ||
47 | stroke-dashoffset: 0; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | @keyframes dash { | ||
52 | 0% { | ||
53 | stroke-dashoffset: 1000; | ||
54 | } | ||
55 | 100% { | ||
56 | stroke-dashoffset: 0; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | @-webkit-keyframes dash-check { | ||
61 | 0% { | ||
62 | stroke-dashoffset: -100; | ||
63 | } | ||
64 | 100% { | ||
65 | stroke-dashoffset: 900; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | @keyframes dash-check { | ||
70 | 0% { | ||
71 | stroke-dashoffset: -100; | ||
72 | } | ||
73 | 100% { | ||
74 | stroke-dashoffset: 900; | ||
75 | } | ||
76 | } | ||
diff --git a/client/src/app/+signup/shared/signup-success.component.ts b/client/src/app/+signup/shared/signup-success.component.ts new file mode 100644 index 000000000..19fb5922a --- /dev/null +++ b/client/src/app/+signup/shared/signup-success.component.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-signup-success', | ||
5 | templateUrl: './signup-success.component.html', | ||
6 | styleUrls: [ './signup-success.component.scss' ] | ||
7 | }) | ||
8 | export class SignupSuccessComponent { | ||
9 | @Input() message: string | ||
10 | } | ||