aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+signup
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-01-19 09:29:47 +0100
committerChocobozzz <chocobozzz@cpy.re>2023-01-19 13:53:40 +0100
commit9589907c89d29a6c0acd52c8cb789af9f93ce9af (patch)
treef1d238e6144231bbfbed5614e05a21eca8aa6fc2 /client/src/app/+signup
parentb379759f55a35837b803a3b988674972db2903d1 (diff)
downloadPeerTube-9589907c89d29a6c0acd52c8cb789af9f93ce9af.tar.gz
PeerTube-9589907c89d29a6c0acd52c8cb789af9f93ce9af.tar.zst
PeerTube-9589907c89d29a6c0acd52c8cb789af9f93ce9af.zip
Implement signup approval in client
Diffstat (limited to 'client/src/app/+signup')
-rw-r--r--client/src/app/+signup/+register/register.component.html35
-rw-r--r--client/src/app/+signup/+register/register.component.ts62
-rw-r--r--client/src/app/+signup/+register/shared/index.ts1
-rw-r--r--client/src/app/+signup/+register/shared/register-validators.ts18
-rw-r--r--client/src/app/+signup/+register/steps/register-step-about.component.html4
-rw-r--r--client/src/app/+signup/+register/steps/register-step-about.component.ts1
-rw-r--r--client/src/app/+signup/+register/steps/register-step-channel.component.ts6
-rw-r--r--client/src/app/+signup/+register/steps/register-step-terms.component.html14
-rw-r--r--client/src/app/+signup/+register/steps/register-step-terms.component.ts10
-rw-r--r--client/src/app/+signup/+register/steps/register-step-user.component.ts6
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts6
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html17
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts86
-rw-r--r--client/src/app/+signup/shared/shared-signup.module.ts11
-rw-r--r--client/src/app/+signup/shared/signup-success-after-email.component.html21
-rw-r--r--client/src/app/+signup/shared/signup-success-after-email.component.ts10
-rw-r--r--client/src/app/+signup/shared/signup-success-before-email.component.html35
-rw-r--r--client/src/app/+signup/shared/signup-success-before-email.component.ts12
-rw-r--r--client/src/app/+signup/shared/signup-success.component.html22
-rw-r--r--client/src/app/+signup/shared/signup-success.component.ts19
-rw-r--r--client/src/app/+signup/shared/signup.service.ts85
21 files changed, 379 insertions, 102 deletions
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html
index bafb96a49..86763e801 100644
--- a/client/src/app/+signup/+register/register.component.html
+++ b/client/src/app/+signup/+register/register.component.html
@@ -5,29 +5,34 @@
5 </div> 5 </div>
6 6
7 <ng-container *ngIf="!signupDisabled"> 7 <ng-container *ngIf="!signupDisabled">
8 <h1 i18n class="title-page-v2"> 8 <h1 class="title-page-v2">
9 <strong class="underline-orange">{{ instanceName }}</strong> 9 <strong class="underline-orange">{{ instanceName }}</strong>
10 > 10 >
11 Create an account 11 <my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
12 </h1> 12 </h1>
13 13
14 <div class="register-content"> 14 <div class="register-content">
15 <my-custom-stepper linear> 15 <my-custom-stepper linear>
16 16
17 <cdk-step i18n-label label="About" [editable]="!signupSuccess"> 17 <cdk-step i18n-label label="About" [editable]="!signupSuccess">
18 <my-signup-step-title mascotImageName="about" i18n> 18 <my-signup-step-title mascotImageName="about">
19 <strong>Create an account</strong> 19 <strong>
20 <div>on {{ instanceName }}</div> 20 <my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
21 </strong>
22
23 <div i18n>on {{ instanceName }}</div>
21 </my-signup-step-title> 24 </my-signup-step-title>
22 25
23 <my-register-step-about [videoUploadDisabled]="videoUploadDisabled"></my-register-step-about> 26 <my-register-step-about [requiresApproval]="requiresApproval" [videoUploadDisabled]="videoUploadDisabled"></my-register-step-about>
24 27
25 <div class="step-buttons"> 28 <div class="step-buttons">
26 <a i18n class="skip-step underline-orange" routerLink="/login"> 29 <a i18n class="skip-step underline-orange" routerLink="/login">
27 <strong>I already have an account</strong>, I log in 30 <strong>I already have an account</strong>, I log in
28 </a> 31 </a>
29 32
30 <button i18n cdkStepperNext>Create an account</button> 33 <button cdkStepperNext>
34 <my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
35 </button>
31 </div> 36 </div>
32 </cdk-step> 37 </cdk-step>
33 38
@@ -44,8 +49,8 @@
44 ></my-instance-about-accordion> 49 ></my-instance-about-accordion>
45 50
46 <my-register-step-terms 51 <my-register-step-terms
47 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" 52 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" [minimumAge]="minimumAge" [instanceName]="instanceName"
48 [minimumAge]="minimumAge" 53 [requiresApproval]="requiresApproval"
49 (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" 54 (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
50 ></my-register-step-terms> 55 ></my-register-step-terms>
51 56
@@ -94,14 +99,15 @@
94 <div class="skip-step-description" i18n>You will be able to create a channel later</div> 99 <div class="skip-step-description" i18n>You will be able to create a channel later</div>
95 </div> 100 </div>
96 101
97 <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()" i18n> 102 <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()">
98 Create my account 103 <my-signup-label [requiresApproval]="requiresApproval"></my-signup-label>
99 </button> 104 </button>
100 </div> 105 </div>
101 </cdk-step> 106 </cdk-step>
102 107
103 <cdk-step #lastStep i18n-label label="Done!" [editable]="false"> 108 <cdk-step #lastStep i18n-label label="Done!" [editable]="false">
104 <div *ngIf="!signupSuccess && !signupError" class="done-loader"> 109 <!-- Account creation can be a little bit long so display a loader -->
110 <div *ngIf="!requiresApproval && !signupSuccess && !signupError" class="done-loader">
105 <my-loader [loading]="true"></my-loader> 111 <my-loader [loading]="true"></my-loader>
106 112
107 <div i18n>PeerTube is creating your account...</div> 113 <div i18n>PeerTube is creating your account...</div>
@@ -109,7 +115,10 @@
109 115
110 <div *ngIf="signupError" class="alert alert-danger">{{ signupError }}</div> 116 <div *ngIf="signupError" class="alert alert-danger">{{ signupError }}</div>
111 117
112 <my-signup-success *ngIf="signupSuccess" [requiresEmailVerification]="requiresEmailVerification"></my-signup-success> 118 <my-signup-success-before-email
119 *ngIf="signupSuccess"
120 [requiresEmailVerification]="requiresEmailVerification" [requiresApproval]="requiresApproval" [instanceName]="instanceName"
121 ></my-signup-success-before-email>
113 122
114 <div *ngIf="signupError" class="steps-button"> 123 <div *ngIf="signupError" class="steps-button">
115 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> 124 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button>
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index 958770ebf..9259d902c 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -5,10 +5,10 @@ import { ActivatedRoute } from '@angular/router'
5import { AuthService } from '@app/core' 5import { AuthService } from '@app/core'
6import { HooksService } from '@app/core/plugins/hooks.service' 6import { HooksService } from '@app/core/plugins/hooks.service'
7import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' 7import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
8import { UserSignupService } from '@app/shared/shared-users'
9import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' 8import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
10import { UserRegister } from '@shared/models' 9import { UserRegister } from '@shared/models'
11import { ServerConfig } from '@shared/models/server' 10import { ServerConfig } from '@shared/models/server'
11import { SignupService } from '../shared/signup.service'
12 12
13@Component({ 13@Component({
14 selector: 'my-register', 14 selector: 'my-register',
@@ -53,7 +53,7 @@ export class RegisterComponent implements OnInit {
53 constructor ( 53 constructor (
54 private route: ActivatedRoute, 54 private route: ActivatedRoute,
55 private authService: AuthService, 55 private authService: AuthService,
56 private userSignupService: UserSignupService, 56 private signupService: SignupService,
57 private hooks: HooksService 57 private hooks: HooksService
58 ) { } 58 ) { }
59 59
@@ -61,6 +61,10 @@ export class RegisterComponent implements OnInit {
61 return this.serverConfig.signup.requiresEmailVerification 61 return this.serverConfig.signup.requiresEmailVerification
62 } 62 }
63 63
64 get requiresApproval () {
65 return this.serverConfig.signup.requiresApproval
66 }
67
64 get minimumAge () { 68 get minimumAge () {
65 return this.serverConfig.signup.minimumAge 69 return this.serverConfig.signup.minimumAge
66 } 70 }
@@ -132,42 +136,49 @@ export class RegisterComponent implements OnInit {
132 skipChannelCreation () { 136 skipChannelCreation () {
133 this.formStepChannel.reset() 137 this.formStepChannel.reset()
134 this.lastStep.select() 138 this.lastStep.select()
139
135 this.signup() 140 this.signup()
136 } 141 }
137 142
138 async signup () { 143 async signup () {
139 this.signupError = undefined 144 this.signupError = undefined
140 145
141 const body: UserRegister = await this.hooks.wrapObject( 146 const termsForm = this.formStepTerms.value
147 const userForm = this.formStepUser.value
148 const channelForm = this.formStepChannel?.value
149
150 const channel = this.formStepChannel?.value?.name
151 ? { name: channelForm?.name, displayName: channelForm?.displayName }
152 : undefined
153
154 const body = await this.hooks.wrapObject(
142 { 155 {
143 ...this.formStepUser.value, 156 username: userForm.username,
157 password: userForm.password,
158 email: userForm.email,
159 displayName: userForm.displayName,
160
161 registrationReason: termsForm.registrationReason,
144 162
145 channel: this.formStepChannel?.value?.name 163 channel
146 ? this.formStepChannel.value
147 : undefined
148 }, 164 },
149 'signup', 165 'signup',
150 'filter:api.signup.registration.create.params' 166 'filter:api.signup.registration.create.params'
151 ) 167 )
152 168
153 this.userSignupService.signup(body).subscribe({ 169 const obs = this.requiresApproval
170 ? this.signupService.requestSignup(body)
171 : this.signupService.directSignup(body)
172
173 obs.subscribe({
154 next: () => { 174 next: () => {
155 if (this.requiresEmailVerification) { 175 if (this.requiresEmailVerification || this.requiresApproval) {
156 this.signupSuccess = true 176 this.signupSuccess = true
157 return 177 return
158 } 178 }
159 179
160 // Auto login 180 // Auto login
161 this.authService.login({ username: body.username, password: body.password }) 181 this.autoLogin(body)
162 .subscribe({
163 next: () => {
164 this.signupSuccess = true
165 },
166
167 error: err => {
168 this.signupError = err.message
169 }
170 })
171 }, 182 },
172 183
173 error: err => { 184 error: err => {
@@ -175,4 +186,17 @@ export class RegisterComponent implements OnInit {
175 } 186 }
176 }) 187 })
177 } 188 }
189
190 private autoLogin (body: UserRegister) {
191 this.authService.login({ username: body.username, password: body.password })
192 .subscribe({
193 next: () => {
194 this.signupSuccess = true
195 },
196
197 error: err => {
198 this.signupError = err.message
199 }
200 })
201 }
178} 202}
diff --git a/client/src/app/+signup/+register/shared/index.ts b/client/src/app/+signup/+register/shared/index.ts
new file mode 100644
index 000000000..affb54bf4
--- /dev/null
+++ b/client/src/app/+signup/+register/shared/index.ts
@@ -0,0 +1 @@
export * from './register-validators'
diff --git a/client/src/app/+signup/+register/shared/register-validators.ts b/client/src/app/+signup/+register/shared/register-validators.ts
new file mode 100644
index 000000000..f14803b68
--- /dev/null
+++ b/client/src/app/+signup/+register/shared/register-validators.ts
@@ -0,0 +1,18 @@
1import { Validators } from '@angular/forms'
2import { BuildFormValidator } from '@app/shared/form-validators'
3
4export const REGISTER_TERMS_VALIDATOR: BuildFormValidator = {
5 VALIDATORS: [ Validators.requiredTrue ],
6 MESSAGES: {
7 required: $localize`You must agree with the instance terms in order to register on it.`
8 }
9}
10
11export const REGISTER_REASON_VALIDATOR: BuildFormValidator = {
12 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
13 MESSAGES: {
14 required: $localize`Registration reason is required.`,
15 minlength: $localize`Registration reason must be at least 2 characters long.`,
16 maxlength: $localize`Registration reason cannot be more than 3000 characters long.`
17 }
18}
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.html b/client/src/app/+signup/+register/steps/register-step-about.component.html
index 769fe3127..580e8a92c 100644
--- a/client/src/app/+signup/+register/steps/register-step-about.component.html
+++ b/client/src/app/+signup/+register/steps/register-step-about.component.html
@@ -13,6 +13,10 @@
13 <li i18n>Have access to your <strong>watch history</strong></li> 13 <li i18n>Have access to your <strong>watch history</strong></li>
14 <li *ngIf="!videoUploadDisabled" i18n>Create your channel to <strong>publish videos</strong></li> 14 <li *ngIf="!videoUploadDisabled" i18n>Create your channel to <strong>publish videos</strong></li>
15 </ul> 15 </ul>
16
17 <p *ngIf="requiresApproval" i18n>
18 Moderators of {{ instanceName }} will have to approve your registration request once you have finished to fill the form.
19 </p>
16</div> 20</div>
17 21
18<div> 22<div>
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.ts b/client/src/app/+signup/+register/steps/register-step-about.component.ts
index 9a0941016..b176ffa59 100644
--- a/client/src/app/+signup/+register/steps/register-step-about.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-about.component.ts
@@ -7,6 +7,7 @@ import { ServerService } from '@app/core'
7 styleUrls: [ './register-step-about.component.scss' ] 7 styleUrls: [ './register-step-about.component.scss' ]
8}) 8})
9export class RegisterStepAboutComponent { 9export class RegisterStepAboutComponent {
10 @Input() requiresApproval: boolean
10 @Input() videoUploadDisabled: boolean 11 @Input() videoUploadDisabled: boolean
11 12
12 constructor (private serverService: ServerService) { 13 constructor (private serverService: ServerService) {
diff --git a/client/src/app/+signup/+register/steps/register-step-channel.component.ts b/client/src/app/+signup/+register/steps/register-step-channel.component.ts
index df92c5145..478ca0177 100644
--- a/client/src/app/+signup/+register/steps/register-step-channel.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-channel.component.ts
@@ -2,9 +2,9 @@ import { concat, of } from 'rxjs'
2import { pairwise } from 'rxjs/operators' 2import { pairwise } from 'rxjs/operators'
3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
4import { FormGroup } from '@angular/forms' 4import { FormGroup } from '@angular/forms'
5import { SignupService } from '@app/+signup/shared/signup.service'
5import { VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_NAME_VALIDATOR } from '@app/shared/form-validators/video-channel-validators' 6import { VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_NAME_VALIDATOR } from '@app/shared/form-validators/video-channel-validators'
6import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' 7import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
7import { UserSignupService } from '@app/shared/shared-users'
8 8
9@Component({ 9@Component({
10 selector: 'my-register-step-channel', 10 selector: 'my-register-step-channel',
@@ -20,7 +20,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
20 20
21 constructor ( 21 constructor (
22 protected formReactiveService: FormReactiveService, 22 protected formReactiveService: FormReactiveService,
23 private userSignupService: UserSignupService 23 private signupService: SignupService
24 ) { 24 ) {
25 super() 25 super()
26 } 26 }
@@ -51,7 +51,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
51 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 51 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
52 const name = this.form.value['name'] || '' 52 const name = this.form.value['name'] || ''
53 53
54 const newName = this.userSignupService.getNewUsername(oldDisplayName, newDisplayName, name) 54 const newName = this.signupService.getNewUsername(oldDisplayName, newDisplayName, name)
55 this.form.patchValue({ name: newName }) 55 this.form.patchValue({ name: newName })
56 } 56 }
57} 57}
diff --git a/client/src/app/+signup/+register/steps/register-step-terms.component.html b/client/src/app/+signup/+register/steps/register-step-terms.component.html
index cbfb32518..1d753a3f2 100644
--- a/client/src/app/+signup/+register/steps/register-step-terms.component.html
+++ b/client/src/app/+signup/+register/steps/register-step-terms.component.html
@@ -1,4 +1,16 @@
1<form role="form" [formGroup]="form"> 1<form role="form" [formGroup]="form">
2
3 <div *ngIf="requiresApproval" class="form-group">
4 <label i18n for="registrationReason">Why do you want to join {{ instanceName }}?</label>
5
6 <textarea
7 id="registrationReason" formControlName="registrationReason" class="form-control" rows="4"
8 [ngClass]="{ 'input-error': formErrors['registrationReason'] }"
9 ></textarea>
10
11 <div *ngIf="formErrors.registrationReason" class="form-error">{{ formErrors.registrationReason }}</div>
12 </div>
13
2 <div class="form-group"> 14 <div class="form-group">
3 <my-peertube-checkbox inputName="terms" formControlName="terms"> 15 <my-peertube-checkbox inputName="terms" formControlName="terms">
4 <ng-template ptTemplate="label"> 16 <ng-template ptTemplate="label">
@@ -6,7 +18,7 @@
6 I am at least {{ minimumAge }} years old and agree 18 I am at least {{ minimumAge }} years old and agree
7 to the <a class="link-orange" (click)="onTermsClick($event)" href='#'>Terms</a> 19 to the <a class="link-orange" (click)="onTermsClick($event)" href='#'>Terms</a>
8 <ng-container *ngIf="hasCodeOfConduct"> and to the <a class="link-orange" (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> 20 <ng-container *ngIf="hasCodeOfConduct"> and to the <a class="link-orange" (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
9 of this instance 21 of {{ instanceName }}
10 </ng-container> 22 </ng-container>
11 </ng-template> 23 </ng-template>
12 </my-peertube-checkbox> 24 </my-peertube-checkbox>
diff --git a/client/src/app/+signup/+register/steps/register-step-terms.component.ts b/client/src/app/+signup/+register/steps/register-step-terms.component.ts
index 2df963b30..1b1fb49ee 100644
--- a/client/src/app/+signup/+register/steps/register-step-terms.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-terms.component.ts
@@ -1,7 +1,7 @@
1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
2import { FormGroup } from '@angular/forms' 2import { FormGroup } from '@angular/forms'
3import { USER_TERMS_VALIDATOR } from '@app/shared/form-validators/user-validators'
4import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' 3import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
4import { REGISTER_REASON_VALIDATOR, REGISTER_TERMS_VALIDATOR } from '../shared'
5 5
6@Component({ 6@Component({
7 selector: 'my-register-step-terms', 7 selector: 'my-register-step-terms',
@@ -10,7 +10,9 @@ import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
10}) 10})
11export class RegisterStepTermsComponent extends FormReactive implements OnInit { 11export class RegisterStepTermsComponent extends FormReactive implements OnInit {
12 @Input() hasCodeOfConduct = false 12 @Input() hasCodeOfConduct = false
13 @Input() requiresApproval: boolean
13 @Input() minimumAge = 16 14 @Input() minimumAge = 16
15 @Input() instanceName: string
14 16
15 @Output() formBuilt = new EventEmitter<FormGroup>() 17 @Output() formBuilt = new EventEmitter<FormGroup>()
16 @Output() termsClick = new EventEmitter<void>() 18 @Output() termsClick = new EventEmitter<void>()
@@ -28,7 +30,11 @@ export class RegisterStepTermsComponent extends FormReactive implements OnInit {
28 30
29 ngOnInit () { 31 ngOnInit () {
30 this.buildForm({ 32 this.buildForm({
31 terms: USER_TERMS_VALIDATOR 33 terms: REGISTER_TERMS_VALIDATOR,
34
35 registrationReason: this.requiresApproval
36 ? REGISTER_REASON_VALIDATOR
37 : null
32 }) 38 })
33 39
34 setTimeout(() => this.formBuilt.emit(this.form)) 40 setTimeout(() => this.formBuilt.emit(this.form))
diff --git a/client/src/app/+signup/+register/steps/register-step-user.component.ts b/client/src/app/+signup/+register/steps/register-step-user.component.ts
index 822f8f5c5..0a5d2e437 100644
--- a/client/src/app/+signup/+register/steps/register-step-user.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-user.component.ts
@@ -2,6 +2,7 @@ import { concat, of } from 'rxjs'
2import { pairwise } from 'rxjs/operators' 2import { pairwise } from 'rxjs/operators'
3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
4import { FormGroup } from '@angular/forms' 4import { FormGroup } from '@angular/forms'
5import { SignupService } from '@app/+signup/shared/signup.service'
5import { 6import {
6 USER_DISPLAY_NAME_REQUIRED_VALIDATOR, 7 USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
7 USER_EMAIL_VALIDATOR, 8 USER_EMAIL_VALIDATOR,
@@ -9,7 +10,6 @@ import {
9 USER_USERNAME_VALIDATOR 10 USER_USERNAME_VALIDATOR
10} from '@app/shared/form-validators/user-validators' 11} from '@app/shared/form-validators/user-validators'
11import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' 12import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
12import { UserSignupService } from '@app/shared/shared-users'
13 13
14@Component({ 14@Component({
15 selector: 'my-register-step-user', 15 selector: 'my-register-step-user',
@@ -24,7 +24,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
24 24
25 constructor ( 25 constructor (
26 protected formReactiveService: FormReactiveService, 26 protected formReactiveService: FormReactiveService,
27 private userSignupService: UserSignupService 27 private signupService: SignupService
28 ) { 28 ) {
29 super() 29 super()
30 } 30 }
@@ -57,7 +57,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
57 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 57 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
58 const username = this.form.value['username'] || '' 58 const username = this.form.value['username'] || ''
59 59
60 const newUsername = this.userSignupService.getNewUsername(oldDisplayName, newDisplayName, username) 60 const newUsername = this.signupService.getNewUsername(oldDisplayName, newDisplayName, username)
61 this.form.patchValue({ username: newUsername }) 61 this.form.patchValue({ username: newUsername })
62 } 62 }
63} 63}
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
index 06905f678..75b599e0e 100644
--- 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
@@ -1,8 +1,8 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { SignupService } from '@app/+signup/shared/signup.service'
2import { Notifier, RedirectService, ServerService } from '@app/core' 3import { Notifier, RedirectService, ServerService } from '@app/core'
3import { USER_EMAIL_VALIDATOR } from '@app/shared/form-validators/user-validators' 4import { USER_EMAIL_VALIDATOR } from '@app/shared/form-validators/user-validators'
4import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' 5import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
5import { UserSignupService } from '@app/shared/shared-users'
6 6
7@Component({ 7@Component({
8 selector: 'my-verify-account-ask-send-email', 8 selector: 'my-verify-account-ask-send-email',
@@ -15,7 +15,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
15 15
16 constructor ( 16 constructor (
17 protected formReactiveService: FormReactiveService, 17 protected formReactiveService: FormReactiveService,
18 private userSignupService: UserSignupService, 18 private signupService: SignupService,
19 private serverService: ServerService, 19 private serverService: ServerService,
20 private notifier: Notifier, 20 private notifier: Notifier,
21 private redirectService: RedirectService 21 private redirectService: RedirectService
@@ -34,7 +34,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
34 34
35 askSendVerifyEmail () { 35 askSendVerifyEmail () {
36 const email = this.form.value['verify-email-email'] 36 const email = this.form.value['verify-email-email']
37 this.userSignupService.askSendVerifyEmail(email) 37 this.signupService.askSendVerifyEmail(email)
38 .subscribe({ 38 .subscribe({
39 next: () => { 39 next: () => {
40 this.notifier.success($localize`An email with verification link will be sent to ${email}.`) 40 this.notifier.success($localize`An email with verification link will be sent to ${email}.`)
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
index 122f3c28c..8c8b1098e 100644
--- 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
@@ -1,14 +1,19 @@
1<div class="margin-content"> 1<div *ngIf="loaded" class="margin-content">
2 <h1 i18n class="title-page">Verify account email confirmation</h1> 2 <h1 i18n class="title-page">Verify email</h1>
3 3
4 <my-signup-success i18n *ngIf="!isPendingEmail && success" [requiresEmailVerification]="false"> 4 <my-signup-success-after-email
5 </my-signup-success> 5 *ngIf="displaySignupSuccess()"
6 [requiresApproval]="isRegistrationRequest() && requiresApproval"
7 >
8 </my-signup-success-after-email>
6 9
7 <div i18n class="alert alert-success" *ngIf="isPendingEmail && success">Email updated.</div> 10 <div i18n class="alert alert-success" *ngIf="!isRegistrationRequest() && isPendingEmail && success">Email updated.</div>
8 11
9 <div class="alert alert-danger" *ngIf="failed"> 12 <div class="alert alert-danger" *ngIf="failed">
10 <span i18n>An error occurred.</span> 13 <span i18n>An error occurred.</span>
11 14
12 <a i18n class="ms-1 link-orange" routerLink="/verify-account/ask-send-email" [queryParams]="{ isPendingEmail: isPendingEmail }">Request new verification email</a> 15 <a i18n class="ms-1 link-orange" routerLink="/verify-account/ask-send-email">
16 Request a new verification email
17 </a>
13 </div> 18 </div>
14</div> 19</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
index 88efce4a1..faf663391 100644
--- 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
@@ -1,7 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { ActivatedRoute } from '@angular/router' 2import { ActivatedRoute } from '@angular/router'
3import { AuthService, Notifier } from '@app/core' 3import { SignupService } from '@app/+signup/shared/signup.service'
4import { UserSignupService } from '@app/shared/shared-users' 4import { AuthService, Notifier, ServerService } from '@app/core'
5 5
6@Component({ 6@Component({
7 selector: 'my-verify-account-email', 7 selector: 'my-verify-account-email',
@@ -13,32 +13,82 @@ export class VerifyAccountEmailComponent implements OnInit {
13 failed = false 13 failed = false
14 isPendingEmail = false 14 isPendingEmail = false
15 15
16 requiresApproval: boolean
17 loaded = false
18
16 private userId: number 19 private userId: number
20 private registrationId: number
17 private verificationString: string 21 private verificationString: string
18 22
19 constructor ( 23 constructor (
20 private userSignupService: UserSignupService, 24 private signupService: SignupService,
25 private server: ServerService,
21 private authService: AuthService, 26 private authService: AuthService,
22 private notifier: Notifier, 27 private notifier: Notifier,
23 private route: ActivatedRoute 28 private route: ActivatedRoute
24 ) { 29 ) {
25 } 30 }
26 31
32 get instanceName () {
33 return this.server.getHTMLConfig().instance.name
34 }
35
27 ngOnInit () { 36 ngOnInit () {
28 const queryParams = this.route.snapshot.queryParams 37 const queryParams = this.route.snapshot.queryParams
38
39 this.server.getConfig().subscribe(config => {
40 this.requiresApproval = config.signup.requiresApproval
41
42 this.loaded = true
43 })
44
29 this.userId = queryParams['userId'] 45 this.userId = queryParams['userId']
46 this.registrationId = queryParams['registrationId']
47
30 this.verificationString = queryParams['verificationString'] 48 this.verificationString = queryParams['verificationString']
49
31 this.isPendingEmail = queryParams['isPendingEmail'] === 'true' 50 this.isPendingEmail = queryParams['isPendingEmail'] === 'true'
32 51
33 if (!this.userId || !this.verificationString) { 52 if (!this.verificationString) {
34 this.notifier.error($localize`Unable to find user id or verification string.`) 53 this.notifier.error($localize`Unable to find verification string in URL query.`)
35 } else { 54 return
36 this.verifyEmail() 55 }
56
57 if (!this.userId && !this.registrationId) {
58 this.notifier.error($localize`Unable to find user id or registration id in URL query.`)
59 return
37 } 60 }
61
62 this.verifyEmail()
63 }
64
65 isRegistrationRequest () {
66 return !!this.registrationId
67 }
68
69 displaySignupSuccess () {
70 if (!this.success) return false
71 if (!this.isRegistrationRequest() && this.isPendingEmail) return false
72
73 return true
38 } 74 }
39 75
40 verifyEmail () { 76 verifyEmail () {
41 this.userSignupService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail) 77 if (this.isRegistrationRequest()) {
78 return this.verifyRegistrationEmail()
79 }
80
81 return this.verifyUserEmail()
82 }
83
84 private verifyUserEmail () {
85 const options = {
86 userId: this.userId,
87 verificationString: this.verificationString,
88 isPendingEmail: this.isPendingEmail
89 }
90
91 this.signupService.verifyUserEmail(options)
42 .subscribe({ 92 .subscribe({
43 next: () => { 93 next: () => {
44 if (this.authService.isLoggedIn()) { 94 if (this.authService.isLoggedIn()) {
@@ -55,4 +105,24 @@ export class VerifyAccountEmailComponent implements OnInit {
55 } 105 }
56 }) 106 })
57 } 107 }
108
109 private verifyRegistrationEmail () {
110 const options = {
111 registrationId: this.registrationId,
112 verificationString: this.verificationString
113 }
114
115 this.signupService.verifyRegistrationEmail(options)
116 .subscribe({
117 next: () => {
118 this.success = true
119 },
120
121 error: err => {
122 this.failed = true
123
124 this.notifier.error(err.message)
125 }
126 })
127 }
58} 128}
diff --git a/client/src/app/+signup/shared/shared-signup.module.ts b/client/src/app/+signup/shared/shared-signup.module.ts
index 0aa08f3e2..0600f0af8 100644
--- a/client/src/app/+signup/shared/shared-signup.module.ts
+++ b/client/src/app/+signup/shared/shared-signup.module.ts
@@ -5,7 +5,9 @@ import { SharedMainModule } from '@app/shared/shared-main'
5import { SharedUsersModule } from '@app/shared/shared-users' 5import { SharedUsersModule } from '@app/shared/shared-users'
6import { SignupMascotComponent } from './signup-mascot.component' 6import { SignupMascotComponent } from './signup-mascot.component'
7import { SignupStepTitleComponent } from './signup-step-title.component' 7import { SignupStepTitleComponent } from './signup-step-title.component'
8import { SignupSuccessComponent } from './signup-success.component' 8import { SignupSuccessBeforeEmailComponent } from './signup-success-before-email.component'
9import { SignupSuccessAfterEmailComponent } from './signup-success-after-email.component'
10import { SignupService } from './signup.service'
9 11
10@NgModule({ 12@NgModule({
11 imports: [ 13 imports: [
@@ -16,7 +18,8 @@ import { SignupSuccessComponent } from './signup-success.component'
16 ], 18 ],
17 19
18 declarations: [ 20 declarations: [
19 SignupSuccessComponent, 21 SignupSuccessBeforeEmailComponent,
22 SignupSuccessAfterEmailComponent,
20 SignupStepTitleComponent, 23 SignupStepTitleComponent,
21 SignupMascotComponent 24 SignupMascotComponent
22 ], 25 ],
@@ -26,12 +29,14 @@ import { SignupSuccessComponent } from './signup-success.component'
26 SharedFormModule, 29 SharedFormModule,
27 SharedGlobalIconModule, 30 SharedGlobalIconModule,
28 31
29 SignupSuccessComponent, 32 SignupSuccessBeforeEmailComponent,
33 SignupSuccessAfterEmailComponent,
30 SignupStepTitleComponent, 34 SignupStepTitleComponent,
31 SignupMascotComponent 35 SignupMascotComponent
32 ], 36 ],
33 37
34 providers: [ 38 providers: [
39 SignupService
35 ] 40 ]
36}) 41})
37export class SharedSignupModule { } 42export class SharedSignupModule { }
diff --git a/client/src/app/+signup/shared/signup-success-after-email.component.html b/client/src/app/+signup/shared/signup-success-after-email.component.html
new file mode 100644
index 000000000..1c3536ada
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success-after-email.component.html
@@ -0,0 +1,21 @@
1<my-signup-step-title mascotImageName="success">
2 <strong i18n>Email verified!</strong>
3</my-signup-step-title>
4
5<div class="alert pt-alert-primary">
6 <ng-container *ngIf="requiresApproval">
7 <p i18n>Your email has been verified and your account request has been sent!</p>
8
9 <p i18n>
10 A moderator will check your registration request soon and you'll receive an email when it will be accepted or rejected.
11 </p>
12 </ng-container>
13
14 <ng-container *ngIf="!requiresApproval">
15 <p i18n>Your email has been verified and your account has been created!</p>
16
17 <p i18n>
18 If you need help to use PeerTube, you can have a look at the <a class="link-orange" href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>.
19 </p>
20 </ng-container>
21</div>
diff --git a/client/src/app/+signup/shared/signup-success-after-email.component.ts b/client/src/app/+signup/shared/signup-success-after-email.component.ts
new file mode 100644
index 000000000..3d72fdae9
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success-after-email.component.ts
@@ -0,0 +1,10 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-signup-success-after-email',
5 templateUrl: './signup-success-after-email.component.html',
6 styleUrls: [ './signup-success.component.scss' ]
7})
8export class SignupSuccessAfterEmailComponent {
9 @Input() requiresApproval: boolean
10}
diff --git a/client/src/app/+signup/shared/signup-success-before-email.component.html b/client/src/app/+signup/shared/signup-success-before-email.component.html
new file mode 100644
index 000000000..b9668ee82
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success-before-email.component.html
@@ -0,0 +1,35 @@
1<my-signup-step-title mascotImageName="success">
2 <ng-container *ngIf="requiresApproval">
3 <strong i18n>Account request sent</strong>
4 </ng-container>
5
6 <ng-container *ngIf="!requiresApproval" i18n>
7 <strong>Welcome</strong>
8 <div>on {{ instanceName }}</div>
9 </ng-container>
10</my-signup-step-title>
11
12<div class="alert pt-alert-primary">
13 <p *ngIf="requiresApproval" i18n>Your account request has been sent!</p>
14 <p *ngIf="!requiresApproval" i18n>Your account has been created!</p>
15
16 <ng-container *ngIf="requiresEmailVerification">
17 <p i18n *ngIf="requiresApproval">
18 <strong>Check your emails</strong> to validate your account and complete your registration request.
19 </p>
20
21 <p i18n *ngIf="!requiresApproval">
22 <strong>Check your emails</strong> to validate your account and complete your registration.
23 </p>
24 </ng-container>
25
26 <ng-container *ngIf="!requiresEmailVerification">
27 <p i18n *ngIf="requiresApproval">
28 A moderator will check your registration request soon and you'll receive an email when it will be accepted or rejected.
29 </p>
30
31 <p *ngIf="!requiresApproval" i18n>
32 If you need help to use PeerTube, you can have a look at the <a class="link-orange" href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>.
33 </p>
34 </ng-container>
35</div>
diff --git a/client/src/app/+signup/shared/signup-success-before-email.component.ts b/client/src/app/+signup/shared/signup-success-before-email.component.ts
new file mode 100644
index 000000000..d72462340
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success-before-email.component.ts
@@ -0,0 +1,12 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-signup-success-before-email',
5 templateUrl: './signup-success-before-email.component.html',
6 styleUrls: [ './signup-success.component.scss' ]
7})
8export class SignupSuccessBeforeEmailComponent {
9 @Input() requiresApproval: boolean
10 @Input() requiresEmailVerification: boolean
11 @Input() instanceName: string
12}
diff --git a/client/src/app/+signup/shared/signup-success.component.html b/client/src/app/+signup/shared/signup-success.component.html
deleted file mode 100644
index c14889c72..000000000
--- a/client/src/app/+signup/shared/signup-success.component.html
+++ /dev/null
@@ -1,22 +0,0 @@
1<my-signup-step-title mascotImageName="success" i18n>
2 <strong>Welcome</strong>
3 <div>on {{ instanceName }}</div>
4</my-signup-step-title>
5
6<div class="alert pt-alert-primary">
7 <p i18n>Your account has been created!</p>
8
9 <p i18n *ngIf="requiresEmailVerification">
10 <strong>Check your emails</strong> to validate your account and complete your inscription.
11 </p>
12
13 <ng-container *ngIf="!requiresEmailVerification">
14 <p i18n>
15 If you need help to use PeerTube, you can have a look at the <a class="link-orange" href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>.
16 </p>
17
18 <p i18n>
19 To help moderators and other users to know <strong>who you are</strong>, don't forget to <a class="link-orange" routerLink="/my-account/settings">set up your account profile</a> by adding an <strong>avatar</strong> and a <strong>description</strong>.
20 </p>
21 </ng-container>
22</div>
diff --git a/client/src/app/+signup/shared/signup-success.component.ts b/client/src/app/+signup/shared/signup-success.component.ts
deleted file mode 100644
index a03f3819d..000000000
--- a/client/src/app/+signup/shared/signup-success.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
1import { Component, Input } from '@angular/core'
2import { ServerService } from '@app/core'
3
4@Component({
5 selector: 'my-signup-success',
6 templateUrl: './signup-success.component.html',
7 styleUrls: [ './signup-success.component.scss' ]
8})
9export class SignupSuccessComponent {
10 @Input() requiresEmailVerification: boolean
11
12 constructor (private serverService: ServerService) {
13
14 }
15
16 get instanceName () {
17 return this.serverService.getHTMLConfig().instance.name
18 }
19}
diff --git a/client/src/app/+signup/shared/signup.service.ts b/client/src/app/+signup/shared/signup.service.ts
new file mode 100644
index 000000000..f647298be
--- /dev/null
+++ b/client/src/app/+signup/shared/signup.service.ts
@@ -0,0 +1,85 @@
1import { catchError, tap } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { RestExtractor, UserService } from '@app/core'
5import { UserRegister, UserRegistrationRequest } from '@shared/models'
6
7@Injectable()
8export class SignupService {
9
10 constructor (
11 private authHttp: HttpClient,
12 private restExtractor: RestExtractor,
13 private userService: UserService
14 ) { }
15
16 directSignup (userCreate: UserRegister) {
17 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
18 .pipe(
19 tap(() => this.userService.setSignupInThisSession(true)),
20 catchError(err => this.restExtractor.handleError(err))
21 )
22 }
23
24 requestSignup (userCreate: UserRegistrationRequest) {
25 return this.authHttp.post(UserService.BASE_USERS_URL + 'registrations/request', userCreate)
26 .pipe(catchError(err => this.restExtractor.handleError(err)))
27 }
28
29 // ---------------------------------------------------------------------------
30
31 verifyUserEmail (options: {
32 userId: number
33 verificationString: string
34 isPendingEmail: boolean
35 }) {
36 const { userId, verificationString, isPendingEmail } = options
37
38 const url = `${UserService.BASE_USERS_URL}${userId}/verify-email`
39 const body = {
40 verificationString,
41 isPendingEmail
42 }
43
44 return this.authHttp.post(url, body)
45 .pipe(catchError(res => this.restExtractor.handleError(res)))
46 }
47
48 verifyRegistrationEmail (options: {
49 registrationId: number
50 verificationString: string
51 }) {
52 const { registrationId, verificationString } = options
53
54 const url = `${UserService.BASE_USERS_URL}registrations/${registrationId}/verify-email`
55 const body = { verificationString }
56
57 return this.authHttp.post(url, body)
58 .pipe(catchError(res => this.restExtractor.handleError(res)))
59 }
60
61 askSendVerifyEmail (email: string) {
62 const url = UserService.BASE_USERS_URL + 'ask-send-verify-email'
63
64 return this.authHttp.post(url, { email })
65 .pipe(catchError(err => this.restExtractor.handleError(err)))
66 }
67
68 // ---------------------------------------------------------------------------
69
70 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
71 // Don't update display name, the user seems to have changed it
72 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
73
74 return this.displayNameToUsername(newDisplayName)
75 }
76
77 private displayNameToUsername (displayName: string) {
78 if (!displayName) return ''
79
80 return displayName
81 .toLowerCase()
82 .replace(/\s/g, '_')
83 .replace(/[^a-z0-9_.]/g, '')
84 }
85}