aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html2
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.html37
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.scss139
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.ts9
-rw-r--r--client/src/app/+signup/+register/register-step-channel.component.html52
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.html64
-rw-r--r--client/src/app/+signup/+register/register.component.html131
-rw-r--r--client/src/app/+signup/+register/register.component.scss93
-rw-r--r--client/src/app/+signup/+register/register.component.ts53
-rw-r--r--client/src/app/+signup/+register/register.module.ts9
-rw-r--r--client/src/app/+signup/+register/steps/index.ts4
-rw-r--r--client/src/app/+signup/+register/steps/register-step-about.component.html39
-rw-r--r--client/src/app/+signup/+register/steps/register-step-about.component.scss53
-rw-r--r--client/src/app/+signup/+register/steps/register-step-about.component.ts19
-rw-r--r--client/src/app/+signup/+register/steps/register-step-channel.component.html55
-rw-r--r--client/src/app/+signup/+register/steps/register-step-channel.component.ts (renamed from client/src/app/+signup/+register/register-step-channel.component.ts)5
-rw-r--r--client/src/app/+signup/+register/steps/register-step-terms.component.html (renamed from client/src/app/+signup/+register/register-step-terms.component.html)6
-rw-r--r--client/src/app/+signup/+register/steps/register-step-terms.component.ts (renamed from client/src/app/+signup/+register/register-step-terms.component.ts)2
-rw-r--r--client/src/app/+signup/+register/steps/register-step-user.component.html73
-rw-r--r--client/src/app/+signup/+register/steps/register-step-user.component.ts (renamed from client/src/app/+signup/+register/register-step-user.component.ts)7
-rw-r--r--client/src/app/+signup/+register/steps/step.component.scss27
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html2
-rw-r--r--client/src/app/+signup/shared/shared-signup.module.ts10
-rw-r--r--client/src/app/+signup/shared/signup-mascot.component.scss11
-rw-r--r--client/src/app/+signup/shared/signup-mascot.component.ts29
-rw-r--r--client/src/app/+signup/shared/signup-step-title.component.html9
-rw-r--r--client/src/app/+signup/shared/signup-step-title.component.scss23
-rw-r--r--client/src/app/+signup/shared/signup-step-title.component.ts12
-rw-r--r--client/src/app/+signup/shared/signup-success.component.html32
-rw-r--r--client/src/app/+signup/shared/signup-success.component.scss54
-rw-r--r--client/src/app/+signup/shared/signup-success.component.ts11
-rw-r--r--client/src/app/shared/form-validators/user-validators.ts2
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.html6
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.scss3
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.ts7
35 files changed, 721 insertions, 369 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
index 3d8ab094f..2a965ac97 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html
@@ -4,7 +4,7 @@
4 <div class="col-12 col-lg-4 col-xl-3"></div> 4 <div class="col-12 col-lg-4 col-xl-3"></div>
5 <div class="col-12 col-lg-8"> 5 <div class="col-12 col-lg-8">
6 6
7 <div class="callout callout-info"> 7 <div class="callout callout-orange">
8 <span i18n> 8 <span i18n>
9 Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically. 9 Estimating a server's capacity to transcode and stream videos isn't easy and we can't tune PeerTube automatically.
10 </span> 10 </span>
diff --git a/client/src/app/+signup/+register/custom-stepper.component.html b/client/src/app/+signup/+register/custom-stepper.component.html
index a07e2fca3..f43a46842 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.html
+++ b/client/src/app/+signup/+register/custom-stepper.component.html
@@ -1,24 +1,29 @@
1<section class="container"> 1<section>
2 <header *ngIf="steps.length > 2"> 2 <header *ngIf="steps.length > 2">
3 <ng-container *ngFor="let step of steps; let i = index; let isLast = last;"> 3 <div class="header-steps">
4 <div 4 <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
5 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(i) }" [attr.aria-current]="selectedIndex === i" 5 <div
6 (click)="onClick(i)" 6 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(step) }" [attr.aria-current]="selectedIndex === i"
7 > 7 (click)="onClick(i)"
8 <div class="step-index"> 8 >
9 <ng-container *ngIf="!isCompleted(step)"><span class="visually-hidden" i18n>Step</span> {{ i + 1 }}</ng-container> 9 <div class="step-index">
10 <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon> 10 <span class="visually-hidden" i18n>Step</span> {{ i + 1 }}
11 </div> 11
12 <div class="completed-icon" *ngIf="isCompleted(step)">
13 <my-global-icon iconName="tick"></my-global-icon>
14 </div>
15 </div>
12 16
13 <div class="step-label">{{ step.label }}</div> 17 <div class="step-label">{{ step.label }}</div>
14 </div> 18 </div>
15 19
16 <!-- Do no display if this is the last child --> 20 <!-- Do no display if this is the last child -->
17 <div *ngIf="!isLast" class="connector"></div> 21 <div *ngIf="!isLast" class="connector"></div>
18 </ng-container> 22 </ng-container>
23 </div>
19 </header> 24 </header>
20 25
21 <div [style.display]="selected ? 'block' : 'none'"> 26 <div class="margin-content" [style.display]="selected ? 'block' : 'none'">
22 <ng-container [ngTemplateOutlet]="selected.content"></ng-container> 27 <ng-container [ngTemplateOutlet]="selected.content"></ng-container>
23 </div> 28 </div>
24 29
diff --git a/client/src/app/+signup/+register/custom-stepper.component.scss b/client/src/app/+signup/+register/custom-stepper.component.scss
index 6a8815c77..4dda93489 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.scss
+++ b/client/src/app/+signup/+register/custom-stepper.component.scss
@@ -2,76 +2,113 @@
2@use '_variables' as *; 2@use '_variables' as *;
3@use '_mixins' as *; 3@use '_mixins' as *;
4 4
5$grey-color: #9CA3AB; 5$index-block-height: 40px;
6$index-block-height: 32px;
7 6
8.container { 7header {
9 @include padding-left(0); 8 margin-bottom: 40px;
10 @include padding-right(0); 9 padding-bottom: 60px;
11 max-width: unset !important; 10 width: 100%;
11 background-color: pvar(--mainColorVeryLight);
12} 12}
13 13
14header { 14.header-steps {
15 max-width: 800px;
15 display: flex; 16 display: flex;
16 justify-content: space-between; 17 justify-content: space-between;
17 font-size: 15px; 18 margin: auto;
18 margin-bottom: 30px; 19
20 // Useful on small screens
21 padding: 0 20px;
22}
19 23
20 .step-info { 24.step-index {
21 color: $grey-color; 25 display: flex;
26 justify-content: center;
27 align-items: center;
28 width: $index-block-height;
29 height: $index-block-height;
30 border-radius: $index-block-height;
31 border: 1px solid pvar(--mainColor);
32 margin-bottom: 10px;
33 font-size: 24px;
34 position: relative;
35
36 .completed-icon {
37 width: 16px;
38 height: 16px;
39 border-radius: 16px;
40 background-color: pvar(--mainBackgroundColor);
41 position: absolute;
42 bottom: 0;
43 right: 0;
22 display: flex; 44 display: flex;
23 flex-direction: column; 45 justify-content: center;
24 align-items: center; 46 align-items: center;
25 width: $index-block-height; 47 border: 1px solid pvar(--mainColor);
48
49 my-global-icon {
50 @include apply-svg-color(pvar(--mainColor));
26 51
27 &:not(.c-hand) { 52 display: flex;
28 cursor: default; 53 width: 12px;
54 height: 12px;
29 } 55 }
56 }
57}
30 58
59.step-label {
60 width: max-content;
61 font-size: 18px;
62}
63
64.step-info {
65 color: pvar(--mainColor);
66 display: flex;
67 flex-direction: column;
68 align-items: center;
69 width: $index-block-height;
70 opacity: 0.5;
71 cursor: default;
72
73 &.c-hand {
74 cursor: pointer;
75 }
76
77 &.active,
78 &.completed {
31 .step-index { 79 .step-index {
32 display: flex; 80 background-color: pvar(--mainColor);
33 justify-content: center; 81 color: pvar(--mainBackgroundColor);
34 align-items: center;
35 width: $index-block-height;
36 height: $index-block-height;
37 border-radius: 100px;
38 border: 2px solid $grey-color;
39 margin-bottom: 10px;
40
41 my-global-icon {
42 @include apply-svg-color(pvar(--mainBackgroundColor));
43
44 width: 22px;
45 height: 22px;
46 }
47 } 82 }
48 83
49 .step-label { 84 .step-label {
50 width: max-content; 85 color: pvar(--mainColor);
51 } 86 }
87 }
52 88
53 &.active, 89 &.active {
54 &.completed { 90 opacity: 1;
55 .step-index { 91 }
56 border-color: pvar(--mainColor); 92}
57 background-color: pvar(--mainColor);
58 color: pvar(--mainBackgroundColor);
59 }
60
61 .step-label {
62 color: pvar(--mainColor);
63 }
64 }
65 93
66 &.completed { 94.connector {
67 cursor: pointer; 95 flex: auto;
68 } 96 margin: math.div($index-block-height, 2) 10px 0 10px;
97 height: 2px;
98 background-color: pvar(--mainColor);
99 opacity: 0.3;
100}
101
102@media screen and (min-width: $small-view) {
103 .margin-content {
104 max-width: 1000px;
105 margin:auto;
69 } 106 }
107}
70 108
71 .connector { 109@media screen and (max-width: $small-view) {
72 flex: auto; 110 .step-label {
73 margin: math.div($index-block-height, 2) 10px 0 10px; 111 width: auto;
74 height: 2px; 112 text-align: center;
75 background-color: $grey-color;
76 } 113 }
77} 114}
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts
index 3b7ba40e8..4c308f7b6 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.ts
+++ b/client/src/app/+signup/+register/custom-stepper.component.ts
@@ -14,13 +14,10 @@ export class CustomStepperComponent extends CdkStepper {
14 } 14 }
15 15
16 isCompleted (step: CdkStep) { 16 isCompleted (step: CdkStep) {
17 return step.stepControl?.dirty && step.stepControl.valid 17 return step.completed
18 } 18 }
19 19
20 isAccessible (index: number) { 20 isAccessible (step: CdkStep) {
21 const stepsCompletedMap = this.steps.map(step => this.isCompleted(step)) 21 return step.editable && step.completed
22 return index === 0
23 ? true
24 : stepsCompletedMap[index - 1]
25 } 22 }
26} 23}
diff --git a/client/src/app/+signup/+register/register-step-channel.component.html b/client/src/app/+signup/+register/register-step-channel.component.html
deleted file mode 100644
index 888e3245d..000000000
--- a/client/src/app/+signup/+register/register-step-channel.component.html
+++ /dev/null
@@ -1,52 +0,0 @@
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 i18n>
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="displayName" i18n>Channel display name</label>
16
17 <div class="input-group">
18 <input
19 type="text" id="displayName"
20 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
21 >
22 </div>
23
24 <div *ngIf="formErrors.displayName" class="form-error">
25 {{ formErrors.displayName }}
26 </div>
27 </div>
28
29 <div class="form-group">
30 <label for="name" i18n>Channel name</label>
31
32 <div class="input-group">
33 <input
34 type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
35 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
36 >
37 <div class="input-group-text">@{{ instanceHost }}</div>
38 </div>
39
40 <div class="name-information" i18n>
41 The channel name is a unique identifier of your channel on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it.
42 </div>
43
44 <div *ngIf="formErrors.name" class="form-error">
45 {{ formErrors.name }}
46 </div>
47
48 <div *ngIf="isSameThanUsername()" class="form-error" i18n>
49 Channel name cannot be the same as your account name. You can click on the first step to update your account name.
50 </div>
51 </div>
52</form>
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html
deleted file mode 100644
index 6e367b4c7..000000000
--- a/client/src/app/+signup/+register/register-step-user.component.html
+++ /dev/null
@@ -1,64 +0,0 @@
1<form role="form" [formGroup]="form">
2
3 <div class="capability-information alert alert-info" i18n *ngIf="videoUploadDisabled">
4 Video uploads are disabled on this instance, hence your account won't be able to upload videos.
5 </div>
6
7 <div class="form-group">
8 <label for="displayName" i18n>Display name</label>
9
10 <div class="input-group">
11 <input
12 type="text" id="displayName" placeholder="John Doe"
13 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
14 >
15 </div>
16
17 <div *ngIf="formErrors.displayName" class="form-error">
18 {{ formErrors.displayName }}
19 </div>
20 </div>
21
22 <div class="form-group">
23 <label for="username" i18n>Username</label>
24
25 <div class="input-group">
26 <input
27 type="text" id="username" i18n-placeholder="Username choice placeholder in the registration form" placeholder="e.g. jane_doe"
28 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }"
29 >
30 <span class="input-group-text">@{{ instanceHost }}</span>
31 </div>
32
33 <div class="name-information" i18n>
34 The username is a unique identifier of your account on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it.
35 </div>
36
37 <div *ngIf="formErrors.username" class="form-error">
38 {{ formErrors.username }}
39 </div>
40 </div>
41
42 <div class="form-group">
43 <label for="email" i18n>Email</label>
44 <input
45 type="text" id="email" i18n-placeholder placeholder="Email"
46 formControlName="email" class="form-control" [ngClass]="{ 'input-error': formErrors['email'] }"
47 >
48 <div *ngIf="formErrors.email" class="form-error">
49 {{ formErrors.email }}
50 </div>
51 </div>
52
53 <div class="form-group">
54 <label for="password" i18n>Password</label>
55 <my-input-text formControlName="password" inputId="password"
56 i18n-placeholder placeholder="Password"
57 [ngClass]="{ 'input-error': formErrors['password'] }"
58 autocomplete="new-password"></my-input-text>
59 <div *ngIf="formErrors.password" class="form-error">
60 {{ formErrors.password }}
61 </div>
62 </div>
63
64</form>
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html
index 5c4fe5f0b..76b145604 100644
--- a/client/src/app/+signup/+register/register.component.html
+++ b/client/src/app/+signup/+register/register.component.html
@@ -1,64 +1,121 @@
1<div class="margin-content"> 1<div>
2 2
3 <div class="signup-disabled" *ngIf="signupDisabled"> 3 <div class="signup-disabled" *ngIf="signupDisabled">
4 <div class="alert alert-warning" i18n>Signup is not enabled on this instance.</div> 4 <div class="alert alert-warning" i18n>Signup is not enabled on this instance.</div>
5 </div> 5 </div>
6 6
7 <ng-container *ngIf="!signupDisabled"> 7 <ng-container *ngIf="!signupDisabled">
8 <div i18n class="title-page title-page-single"> 8 <h1 i18n class="header-title">
9 <strong class="underline-orange">{{ instanceName }}</strong>
10 >
9 Create an account 11 Create an account
10 </div> 12 </h1>
11 13
12 <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> 14 <div class="register-content">
13 <div *ngIf="info" class="alert alert-info">{{ info }}</div> 15 <my-custom-stepper linear>
14 16
15 <div class="wrapper" [hidden]="signupDone"> 17 <cdk-step i18n-label label="About" [editable]="!signupSuccess">
16 <div class="register-form"> 18 <my-signup-step-title mascotImageName="about" i18n>
17 <my-custom-stepper linear *ngIf="!signupDone"> 19 <strong>Create an account</strong>
18 <cdk-step [stepControl]="formStepTerms" i18n-label="Stepper label for the registration page describing terms of service" label="Terms"> 20 <div>on {{ instanceName }}</div>
19 <div class="instance-information"> 21 </my-signup-step-title>
20 <my-instance-about-accordion 22
21 (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels" 23 <my-register-step-about [videoUploadDisabled]="videoUploadDisabled"></my-register-step-about>
22 pluginScope="signup" pluginHook="filter:signup.instance-about-plugin-panels.create.result" 24
23 ></my-instance-about-accordion> 25 <div class="step-buttons">
24 </div> 26 <a i18n class="skip-step underline-orange" routerLink="/login">
27 <strong>I already have an account</strong>, I log in
28 </a>
25 29
26 <my-register-step-terms 30 <button i18n cdkStepperNext>I create an account</button>
27 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" 31 </div>
28 [minimumAge]="minimumAge" 32 </cdk-step>
29 (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
30 ></my-register-step-terms>
31 33
34 <cdk-step [stepControl]="formStepTerms" i18n-label label="Terms" [editable]="!signupSuccess">
35 <my-signup-step-title mascotImageName="terms" i18n>
36 <strong>Terms</strong>
37 <div>of {{ instanceName }}</div>
38 </my-signup-step-title>
39
40 <my-instance-about-accordion
41 [displayInstanceName]="false"
42 (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"
43 pluginScope="signup" pluginHook="filter:signup.instance-about-plugin-panels.create.result"
44 ></my-instance-about-accordion>
45
46 <my-register-step-terms
47 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct"
48 [minimumAge]="minimumAge"
49 (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
50 ></my-register-step-terms>
51
52 <div class="step-buttons">
53 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button>
32 <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button> 54 <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button>
33 </cdk-step> 55 </div>
56 </cdk-step>
57
58 <cdk-step [stepControl]="formStepUser" label="My account" [editable]="!signupSuccess">
59 <my-signup-step-title mascotImageName="account" i18n>
60 <strong>Setup</strong>
61 <div>your account</div>
62 </my-signup-step-title>
34 63
35 <cdk-step [stepControl]="formStepUser" i18n-label="Stepper label for the registration page asking user information" label="User"> 64 <my-register-step-user
36 <my-register-step-user (formBuilt)="onUserFormBuilt($event)" [videoUploadDisabled]="videoUploadDisabled"></my-register-step-user> 65 (formBuilt)="onUserFormBuilt($event)"
66 [videoUploadDisabled]="videoUploadDisabled" [requiresEmailVerification]="requiresEmailVerification"
67 ></my-register-step-user>
37 68
69 <div class="step-buttons">
38 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> 70 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button>
39 <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button> 71 <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button>
40 </cdk-step> 72 </div>
73 </cdk-step>
41 74
42 <cdk-step [stepControl]="formStepChannel" i18n-label="Stepper label for the registration page asking information about the default channel" label="Channel" *ngIf="!videoUploadDisabled"> 75 <cdk-step *ngIf="!videoUploadDisabled" [optional]="true" [stepControl]="formStepChannel" i18n-label label="My channel" [editable]="!signupSuccess">
43 <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel> 76 <my-signup-step-title mascotImageName="channel" i18n>
77 <div>Create</div>
78 <strong>your first channel</strong>
79 </my-signup-step-title>
44 80
81 <my-register-step-channel
82 (formBuilt)="onChannelFormBuilt($event)"
83 [videoQuota]="videoQuota" [instanceName]="instanceName" [username]="getUsername()"
84 ></my-register-step-channel>
85
86 <div class="step-buttons">
45 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button> 87 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button>
88
89 <div class="skip-step">
90 <span class="underline-orange" role="button" (click)="skipChannelCreation()">
91 <strong i18n>I don't want to create a channel</strong>
92 </span>
93
94 <div class="skip-step-description" i18n>You will be able to create a channel later</div>
95 </div>
96
46 <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()" i18n> 97 <button cdkStepperNext [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()" (click)="signup()" i18n>
47 Create my account 98 Create my account
48 </button> 99 </button>
49 </cdk-step> 100 </div>
101 </cdk-step>
50 102
51 <cdk-step i18n-label label="Done" editable="false"> 103 <cdk-step #lastStep i18n-label label="Done!" [editable]="false">
52 <div *ngIf="!signupDone && !error" class="done-loader"> 104 <div *ngIf="!signupSuccess && !signupError" class="done-loader">
53 <my-loader [loading]="true"></my-loader> 105 <my-loader [loading]="true"></my-loader>
54 106
55 <div i18n>PeerTube is creating your account...</div> 107 <div i18n>PeerTube is creating your account...</div>
56 </div> 108 </div>
109
110 <div *ngIf="signupError" class="alert alert-danger">{{ signupError }}</div>
57 111
58 <div *ngIf="error" class="alert alert-danger">{{ error }}</div> 112 <my-signup-success *ngIf="signupSuccess" [requiresEmailVerification]="requiresEmailVerification"></my-signup-success>
59 </cdk-step> 113
60 </my-custom-stepper> 114 <div *ngIf="signupError" class="steps-button">
61 </div> 115 <button cdkStepperPrevious>{{ defaultPreviousStepButtonLabel }}</button>
116 </div>
117 </cdk-step>
118 </my-custom-stepper>
62 </div> 119 </div>
63 </ng-container> 120 </ng-container>
64 121
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss
index 53093a81a..5d0df81bd 100644
--- a/client/src/app/+signup/+register/register.component.scss
+++ b/client/src/app/+signup/+register/register.component.scss
@@ -2,7 +2,7 @@
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.alert { 4.alert {
5 font-size: 15px; 5 font-size: 16px;
6 text-align: center; 6 text-align: center;
7} 7}
8 8
@@ -10,61 +10,75 @@
10 padding-top: 30vh; 10 padding-top: 30vh;
11} 11}
12 12
13.wrapper { 13.header-title {
14 font-weight: normal;
15 font-size: 15px;
16 background-color: pvar(--mainColorVeryLight);
17 padding: 35px 25px 15px 25px;
18 margin: 0;
19}
20
21.register-content {
22 font-size: 16px;
23}
24
25my-instance-about-accordion {
26 display: block;
27 margin-bottom: 25px;
28}
29
30.step-buttons {
14 display: flex; 31 display: flex;
15 flex-direction: column; 32 flex-wrap: wrap;
33 align-items: center;
16 34
17 .register-form { 35 .skip-step {
18 max-width: 600px; 36 @include margin-right(30px);
19 align-self: center; 37
38 display: inline-block;
20 } 39 }
21 40
22 .register-form, 41 .skip-step-description {
23 .instance-information { 42 margin-top: 5px;
24 width: 100%; 43 font-size: 14px;
25 } 44 }
26 45
27 .instance-information { 46 .underline-orange {
28 margin-bottom: 15px; 47 color: pvar(--mainForegroundColor);
48
49 &:hover {
50 opacity: 0.8;
51 }
29 } 52 }
30}
31 53
32input:not([type=submit]) { 54 button,
33 @include peertube-input-text(100%); 55 .skip-step {
34 display: block; 56 margin-top: 20px;
57 margin-bottom: 20px;
58 }
35 59
36 &#username, 60 .skip-step,
37 &#name { 61 button[cdkStepperNext] {
38 width: auto !important; 62 @include margin-left(auto);
39 flex-grow: 1; 63 }
64
65 .skip-step + button[cdkStepperNext] {
66 @include margin-left(0);
40 } 67 }
41} 68}
42 69
43input[type=submit],
44button { 70button {
45 @include peertube-button; 71 @include peertube-button-big;
46 72
47 &[cdkStepperNext] { 73 &[cdkStepperNext] {
48 @include orange-button; 74 @include orange-button;
49
50 // Chrome does not support inline-end
51 float: right;
52 float: inline-end;
53 } 75 }
54 76
55 &[cdkStepperPrevious] { 77 &[cdkStepperPrevious] {
56 @include grey-button; 78 @include grey-button;
57
58 // Chrome does not support inline-start
59 float: left;
60 float: inline-start;
61 } 79 }
62} 80}
63 81
64.name-information {
65 margin-top: 10px;
66}
67
68.done-loader { 82.done-loader {
69 display: flex; 83 display: flex;
70 justify-content: center; 84 justify-content: center;
@@ -73,13 +87,16 @@ button {
73 87
74 my-loader { 88 my-loader {
75 margin-bottom: 20px; 89 margin-bottom: 20px;
90 }
91}
76 92
77 ::ng-deep .loader div { 93@media screen and (max-width: $small-view) {
78 border-color: pvar(--mainColor) transparent transparent transparent; 94 .step-buttons {
79 } 95 justify-content: space-between;
80 96
81 + div { 97 .skip-step,
82 font-size: 15px; 98 button[cdkStepperNext] {
99 @include margin-left(0);
83 } 100 }
84 } 101 }
85} 102}
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index b4a7c0d0e..396b27e5a 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -1,4 +1,5 @@
1import { Component, OnInit } from '@angular/core' 1import { CdkStep } from '@angular/cdk/stepper'
2import { Component, OnInit, ViewChild } from '@angular/core'
2import { FormGroup } from '@angular/forms' 3import { FormGroup } from '@angular/forms'
3import { ActivatedRoute } from '@angular/router' 4import { ActivatedRoute } from '@angular/router'
4import { AuthService } from '@app/core' 5import { AuthService } from '@app/core'
@@ -15,13 +16,15 @@ import { ServerConfig } from '@shared/models/server'
15 styleUrls: [ './register.component.scss' ] 16 styleUrls: [ './register.component.scss' ]
16}) 17})
17export class RegisterComponent implements OnInit { 18export class RegisterComponent implements OnInit {
19 @ViewChild('lastStep') lastStep: CdkStep
20
18 accordion: NgbAccordion 21 accordion: NgbAccordion
19 info: string = null 22
20 error: string = null 23 signupError: string
21 success: string = null 24 signupSuccess = false
22 signupDone = false
23 25
24 videoUploadDisabled: boolean 26 videoUploadDisabled: boolean
27 videoQuota: number
25 28
26 formStepTerms: FormGroup 29 formStepTerms: FormGroup
27 formStepUser: FormGroup 30 formStepUser: FormGroup
@@ -39,8 +42,8 @@ export class RegisterComponent implements OnInit {
39 moderation: false 42 moderation: false
40 } 43 }
41 44
42 defaultPreviousStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Back` 45 defaultPreviousStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Go to the previous step`
43 defaultNextStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Next` 46 defaultNextStepButtonLabel = $localize`:Button on the registration form to go to the previous step:Go to the next step`
44 stepUserButtonLabel = this.defaultNextStepButtonLabel 47 stepUserButtonLabel = this.defaultNextStepButtonLabel
45 48
46 signupDisabled = false 49 signupDisabled = false
@@ -62,7 +65,11 @@ export class RegisterComponent implements OnInit {
62 return this.serverConfig.signup.minimumAge 65 return this.serverConfig.signup.minimumAge
63 } 66 }
64 67
65 ngOnInit (): void { 68 get instanceName () {
69 return this.serverConfig.instance.name
70 }
71
72 ngOnInit () {
66 this.serverConfig = this.route.snapshot.data.serverConfig 73 this.serverConfig = this.route.snapshot.data.serverConfig
67 74
68 if (this.serverConfig.signup.allowed === false || this.serverConfig.signup.allowedForCurrentIP === false) { 75 if (this.serverConfig.signup.allowed === false || this.serverConfig.signup.allowedForCurrentIP === false) {
@@ -70,7 +77,9 @@ export class RegisterComponent implements OnInit {
70 return 77 return
71 } 78 }
72 79
73 this.videoUploadDisabled = this.serverConfig.user.videoQuota === 0 80 this.videoQuota = this.serverConfig.user.videoQuota
81 this.videoUploadDisabled = this.videoQuota === 0
82
74 this.stepUserButtonLabel = this.videoUploadDisabled 83 this.stepUserButtonLabel = this.videoUploadDisabled
75 ? $localize`:Button on the registration form to finalize the account and channel creation:Signup` 84 ? $localize`:Button on the registration form to finalize the account and channel creation:Signup`
76 : this.defaultNextStepButtonLabel 85 : this.defaultNextStepButtonLabel
@@ -120,21 +129,31 @@ export class RegisterComponent implements OnInit {
120 this.aboutHtml = instanceAboutAccordion.aboutHtml 129 this.aboutHtml = instanceAboutAccordion.aboutHtml
121 } 130 }
122 131
132 skipChannelCreation () {
133 this.formStepChannel.reset()
134 this.lastStep.select()
135 this.signup()
136 }
137
123 async signup () { 138 async signup () {
124 this.error = null 139 this.signupError = undefined
125 140
126 const body: UserRegister = await this.hooks.wrapObject( 141 const body: UserRegister = await this.hooks.wrapObject(
127 Object.assign(this.formStepUser.value, { channel: this.videoUploadDisabled ? undefined : this.formStepChannel.value }), 142 {
143 ...this.formStepUser.value,
144
145 channel: this.formStepChannel?.value?.name
146 ? this.formStepChannel.value
147 : undefined
148 },
128 'signup', 149 'signup',
129 'filter:api.signup.registration.create.params' 150 'filter:api.signup.registration.create.params'
130 ) 151 )
131 152
132 this.userSignupService.signup(body).subscribe({ 153 this.userSignupService.signup(body).subscribe({
133 next: () => { 154 next: () => {
134 this.signupDone = true
135
136 if (this.requiresEmailVerification) { 155 if (this.requiresEmailVerification) {
137 this.info = $localize`Now please check your emails to verify your account and complete signup.` 156 this.signupSuccess = true
138 return 157 return
139 } 158 }
140 159
@@ -142,17 +161,17 @@ export class RegisterComponent implements OnInit {
142 this.authService.login(body.username, body.password) 161 this.authService.login(body.username, body.password)
143 .subscribe({ 162 .subscribe({
144 next: () => { 163 next: () => {
145 this.success = $localize`You are now logged in as ${body.username}!` 164 this.signupSuccess = true
146 }, 165 },
147 166
148 error: err => { 167 error: err => {
149 this.error = err.message 168 this.signupError = err.message
150 } 169 }
151 }) 170 })
152 }, 171 },
153 172
154 error: err => { 173 error: err => {
155 this.error = err.message 174 this.signupError = err.message
156 } 175 }
157 }) 176 })
158 } 177 }
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts
index 52cdb33bc..684aae2e9 100644
--- a/client/src/app/+signup/+register/register.module.ts
+++ b/client/src/app/+signup/+register/register.module.ts
@@ -2,15 +2,15 @@ import { CdkStepperModule } from '@angular/cdk/stepper'
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SharedSignupModule } from '@app/+signup/shared/shared-signup.module' 3import { SharedSignupModule } from '@app/+signup/shared/shared-signup.module'
4import { SharedInstanceModule } from '@app/shared/shared-instance' 4import { SharedInstanceModule } from '@app/shared/shared-instance'
5import { SharedMainModule } from '@app/shared/shared-main'
5import { CustomStepperComponent } from './custom-stepper.component' 6import { CustomStepperComponent } from './custom-stepper.component'
6import { RegisterRoutingModule } from './register-routing.module' 7import { RegisterRoutingModule } from './register-routing.module'
7import { RegisterStepChannelComponent } from './register-step-channel.component'
8import { RegisterStepTermsComponent } from './register-step-terms.component'
9import { RegisterStepUserComponent } from './register-step-user.component'
10import { RegisterComponent } from './register.component' 8import { RegisterComponent } from './register.component'
9import { RegisterStepAboutComponent, RegisterStepChannelComponent, RegisterStepTermsComponent, RegisterStepUserComponent } from './steps'
11 10
12@NgModule({ 11@NgModule({
13 imports: [ 12 imports: [
13 SharedMainModule,
14 RegisterRoutingModule, 14 RegisterRoutingModule,
15 15
16 CdkStepperModule, 16 CdkStepperModule,
@@ -25,7 +25,8 @@ import { RegisterComponent } from './register.component'
25 CustomStepperComponent, 25 CustomStepperComponent,
26 RegisterStepChannelComponent, 26 RegisterStepChannelComponent,
27 RegisterStepTermsComponent, 27 RegisterStepTermsComponent,
28 RegisterStepUserComponent 28 RegisterStepUserComponent,
29 RegisterStepAboutComponent
29 ], 30 ],
30 31
31 exports: [ 32 exports: [
diff --git a/client/src/app/+signup/+register/steps/index.ts b/client/src/app/+signup/+register/steps/index.ts
new file mode 100644
index 000000000..b5eae7468
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/index.ts
@@ -0,0 +1,4 @@
1export * from './register-step-about.component'
2export * from './register-step-channel.component'
3export * from './register-step-terms.component'
4export * from './register-step-user.component'
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
new file mode 100644
index 000000000..f93de8ce9
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/register-step-about.component.html
@@ -0,0 +1,39 @@
1<div class="why">
2 <h3 i18n>Why creating an account?</h3>
3
4 <p i18n>
5 As you probably noticed: creating an account is not necessary to watch video son {{ instanceName }}.
6 <br />
7 However, creating an account on {{ instanceName }} will allow you to:
8 </p>
9
10 <ul>
11 <li i18n><strong>Comment</strong> videos</li>
12 <li i18n><strong>Subscribe</strong> to channels to be notified of new videos</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>
15 </ul>
16</div>
17
18<div>
19 <h4 i18n>You're using Mastodon, ActivityPub or a RSS feed aggregator?</h4>
20
21 <p i18n>
22 You can already follow {{ instanceName }} using your favorite tool.
23 </p>
24</div>
25
26<div class="callout callout-orange callout-light">
27 <div class="mascot-container" style="min-width: 140px">
28 <img class="mascot" width="140px" height="160px" src="/client/assets/images/mascot/happy.svg" alt="mascot"/>
29 </div>
30
31 <div class="callout-content">
32 <h4 i18>This website is a GAFAM alternative</h4>
33
34 <p i18n>
35 {{ instanceName }} has been created using <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">PeerTube</a>, a video creation platform developed by Framasoft.
36 <a class="link-orange" target="_blank" rel="noopener noreferrer" href="https://framasoft.org">Framasoft</a> is a french non-profit organization that offers alternatives to Big Tech's digital tools
37 </p>
38 </div>
39</div>
diff --git a/client/src/app/+signup/+register/steps/register-step-about.component.scss b/client/src/app/+signup/+register/steps/register-step-about.component.scss
new file mode 100644
index 000000000..ab6d6dd4d
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/register-step-about.component.scss
@@ -0,0 +1,53 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4h3 {
5 font-weight: $font-bold;
6 font-size: 24px;
7}
8
9h4 {
10 font-size: 18px;
11 font-weight: $font-bold;
12}
13
14.why {
15 margin-bottom: 30px;
16}
17
18.callout {
19 margin: 75px auto 25px;
20 border-width: 2px;
21 display: flex;
22
23 .mascot-container {
24 position: relative;
25
26 .mascot {
27 position: absolute;
28 top: -65px;
29 }
30 }
31
32 .callout-content {
33 margin-left: 30px;
34
35 p {
36 margin: 0;
37 }
38 }
39}
40
41@media screen and (max-width: $small-view) {
42 .callout {
43 margin-top: 20px;
44
45 .mascot-container {
46 display: none;
47 }
48
49 .callout-content {
50 margin-left: 0;
51 }
52 }
53}
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
new file mode 100644
index 000000000..9a0941016
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/register-step-about.component.ts
@@ -0,0 +1,19 @@
1import { Component, Input } from '@angular/core'
2import { ServerService } from '@app/core'
3
4@Component({
5 selector: 'my-register-step-about',
6 templateUrl: './register-step-about.component.html',
7 styleUrls: [ './register-step-about.component.scss' ]
8})
9export class RegisterStepAboutComponent {
10 @Input() videoUploadDisabled: 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/+register/steps/register-step-channel.component.html b/client/src/app/+signup/+register/steps/register-step-channel.component.html
new file mode 100644
index 000000000..c79256c68
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/register-step-channel.component.html
@@ -0,0 +1,55 @@
1<div class="mb-5">
2 <p i18n>
3 You want to <strong>publish videos</strong> on {{ instanceName }}? Then you need to create your first <strong>channel</strong>.
4 </p>
5
6 <p i18n>
7 You might want to <strong>create a channel by theme:</strong> for example, you can create a channel named "SweetMelodies"
8 to publish your piano concerts and another one "Ecology" in which you publish your videos talking about ecology.
9 </p>
10
11 <p i18n *ngIf="videoQuota !== -1">
12 {{ instanceName }} administrators allow you to publish up to <strong>{{ videoQuota | bytes: 0 }} of videos</strong> on their website.
13 </p>
14</div>
15
16<form role="form" [formGroup]="form">
17
18 <div class="row">
19
20 <div class="col-md-12 col-xl-6 form-group">
21 <label for="displayName" i18n>Channel display name</label>
22
23 <div i18n class="form-group-description">This is the name that will be publicly visible by other users.</div>
24
25 <div class="input-group">
26 <input
27 type="text" id="displayName" i18n-placeholder placeholder="Example: Sweet Melodies"
28 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
29 >
30 </div>
31
32 <div *ngIf="formErrors.displayName" class="form-error">{{ formErrors.displayName }}</div>
33 </div>
34
35 <div class="col-md-12 col-xl-6 form-group">
36 <label for="name" i18n>Channel identifier</label>
37
38 <div i18n class="form-group-description">This is the name that will be displayed in your profile URL.</div>
39
40 <div class="input-group">
41 <input
42 type="text" id="name" i18n-placeholder placeholder="Example: sweetmelodies24"
43 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
44 >
45 <div class="input-group-text">@{{ instanceHost }}</div>
46 </div>
47
48 <div *ngIf="formErrors.name" class="form-error">{{ formErrors.name }}</div>
49
50 <div *ngIf="isSameThanUsername()" class="form-error" i18n>
51 Channel identifier cannot be the same as your account name. You can click on the first step to update your account name.
52 </div>
53 </div>
54 </div>
55</form>
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/steps/register-step-channel.component.ts
index 1bc0ccfd3..c10b568ba 100644
--- a/client/src/app/+signup/+register/register-step-channel.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-channel.component.ts
@@ -9,10 +9,13 @@ import { UserSignupService } from '@app/shared/shared-users'
9@Component({ 9@Component({
10 selector: 'my-register-step-channel', 10 selector: 'my-register-step-channel',
11 templateUrl: './register-step-channel.component.html', 11 templateUrl: './register-step-channel.component.html',
12 styleUrls: [ './register.component.scss' ] 12 styleUrls: [ './step.component.scss' ]
13}) 13})
14export class RegisterStepChannelComponent extends FormReactive implements OnInit { 14export class RegisterStepChannelComponent extends FormReactive implements OnInit {
15 @Input() username: string 15 @Input() username: string
16 @Input() instanceName: string
17 @Input() videoQuota: number
18
16 @Output() formBuilt = new EventEmitter<FormGroup>() 19 @Output() formBuilt = new EventEmitter<FormGroup>()
17 20
18 constructor ( 21 constructor (
diff --git a/client/src/app/+signup/+register/register-step-terms.component.html b/client/src/app/+signup/+register/steps/register-step-terms.component.html
index 717a289e6..f54ca77e2 100644
--- a/client/src/app/+signup/+register/register-step-terms.component.html
+++ b/client/src/app/+signup/+register/steps/register-step-terms.component.html
@@ -4,15 +4,13 @@
4 <ng-template ptTemplate="label"> 4 <ng-template ptTemplate="label">
5 <ng-container i18n> 5 <ng-container i18n>
6 I am at least {{ minimumAge }} years old and agree 6 I am at least {{ minimumAge }} years old and agree
7 to the <a class="terms-anchor" (click)="onTermsClick($event)" href='#'>Terms</a> 7 to the <a class="link-orange" (click)="onTermsClick($event)" href='#'>Terms</a>
8 <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> 8 <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
9 of this instance 9 of this instance
10 </ng-container> 10 </ng-container>
11 </ng-template> 11 </ng-template>
12 </my-peertube-checkbox> 12 </my-peertube-checkbox>
13 13
14 <div *ngIf="formErrors.terms" class="form-error"> 14 <div *ngIf="formErrors.terms" class="form-error">{{ formErrors.terms }}</div>
15 {{ formErrors.terms }}
16 </div>
17 </div> 15 </div>
18</form> 16</form>
diff --git a/client/src/app/+signup/+register/register-step-terms.component.ts b/client/src/app/+signup/+register/steps/register-step-terms.component.ts
index 20c1ae1c4..87d16696e 100644
--- a/client/src/app/+signup/+register/register-step-terms.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-terms.component.ts
@@ -8,7 +8,7 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8@Component({ 8@Component({
9 selector: 'my-register-step-terms', 9 selector: 'my-register-step-terms',
10 templateUrl: './register-step-terms.component.html', 10 templateUrl: './register-step-terms.component.html',
11 styleUrls: [ './register.component.scss' ] 11 styleUrls: [ './step.component.scss' ]
12}) 12})
13export class RegisterStepTermsComponent extends FormReactive implements OnInit { 13export class RegisterStepTermsComponent extends FormReactive implements OnInit {
14 @Input() hasCodeOfConduct = false 14 @Input() hasCodeOfConduct = false
diff --git a/client/src/app/+signup/+register/steps/register-step-user.component.html b/client/src/app/+signup/+register/steps/register-step-user.component.html
new file mode 100644
index 000000000..bffcf0346
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/register-step-user.component.html
@@ -0,0 +1,73 @@
1<div class="alert pt-alert-primary" i18n *ngIf="videoUploadDisabled">
2 Video uploads are disabled on this instance, hence your account won't be able to upload videos.
3</div>
4
5<form role="form" [formGroup]="form">
6 <div class="row">
7
8 <div class="col-md-12 col-xl-6 form-group">
9 <label for="displayName" i18n>Public name</label>
10
11 <div class="form-group-description" i18n>
12 This is the name that will be publicly visible by other users.
13 </div>
14
15 <div class="input-group">
16 <input
17 type="text" id="displayName" i18n-placeholder placeholder="Example: John Doe"
18 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
19 >
20 </div>
21
22 <div *ngIf="formErrors.displayName" class="form-error">{{ formErrors.displayName }}</div>
23 </div>
24
25 <div class="col-md-12 col-xl-6 form-group">
26 <label for="username" i18n>Username</label>
27
28 <div class="form-group-description" i18n>
29 This is the name that will be displayed in your profile URL.
30 </div>
31
32 <div class="input-group">
33 <input
34 type="text" id="username" i18n-placeholder placeholder="Example: john_doe58"
35 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }"
36 >
37 <span class="input-group-text">@{{ instanceHost }}</span>
38 </div>
39
40 <div *ngIf="formErrors.username" class="form-error">{{ formErrors.username }}</div>
41 </div>
42 </div>
43
44 <div class="row">
45 <div class="col-md-12 col-xl-6 form-group">
46 <label for="email" i18n>Email</label>
47
48 <div *ngIf="requiresEmailVerification" class="form-group-description" i18n>
49 This email address will be used to validate your account.
50 </div>
51
52 <input
53 type="text" id="email" i18n-placeholder placeholder="Example: john@example.com"
54 formControlName="email" class="form-control" [ngClass]="{ 'input-error': formErrors['email'] }"
55 >
56
57 <div *ngIf="formErrors.email" class="form-error">{{ formErrors.email }}</div>
58 </div>
59
60 <div class="col-md-12 col-xl-6 form-group">
61 <label for="password" i18n>Password</label>
62
63 <div class="form-group-description">{{ getMinPasswordLengthMessage() }}</div>
64
65 <my-input-text
66 formControlName="password" inputId="password"
67 [ngClass]="{ 'input-error': formErrors['password'] }" autocomplete="new-password"
68 ></my-input-text>
69
70 <div *ngIf="formErrors.password" class="form-error">{{ formErrors.password }}</div>
71 </div>
72 </div>
73</form>
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/steps/register-step-user.component.ts
index 92ddfca2e..b89e38a28 100644
--- a/client/src/app/+signup/+register/register-step-user.component.ts
+++ b/client/src/app/+signup/+register/steps/register-step-user.component.ts
@@ -14,10 +14,11 @@ import { UserSignupService } from '@app/shared/shared-users'
14@Component({ 14@Component({
15 selector: 'my-register-step-user', 15 selector: 'my-register-step-user',
16 templateUrl: './register-step-user.component.html', 16 templateUrl: './register-step-user.component.html',
17 styleUrls: [ './register.component.scss' ] 17 styleUrls: [ './step.component.scss' ]
18}) 18})
19export class RegisterStepUserComponent extends FormReactive implements OnInit { 19export class RegisterStepUserComponent extends FormReactive implements OnInit {
20 @Input() videoUploadDisabled = false 20 @Input() videoUploadDisabled = false
21 @Input() requiresEmailVerification = false
21 22
22 @Output() formBuilt = new EventEmitter<FormGroup>() 23 @Output() formBuilt = new EventEmitter<FormGroup>()
23 24
@@ -49,6 +50,10 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
49 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) 50 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
50 } 51 }
51 52
53 getMinPasswordLengthMessage () {
54 return USER_PASSWORD_VALIDATOR.MESSAGES.minlength
55 }
56
52 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 57 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
53 const username = this.form.value['username'] || '' 58 const username = this.form.value['username'] || ''
54 59
diff --git a/client/src/app/+signup/+register/steps/step.component.scss b/client/src/app/+signup/+register/steps/step.component.scss
new file mode 100644
index 000000000..35cfdae91
--- /dev/null
+++ b/client/src/app/+signup/+register/steps/step.component.scss
@@ -0,0 +1,27 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4input:not([type=submit]) {
5 @include peertube-input-text(100%);
6 display: block;
7
8 &#username,
9 &#name {
10 width: auto !important;
11 flex-grow: 1;
12 }
13}
14
15input[type=submit],
16button {
17 @include peertube-button;
18}
19
20label {
21 font-size: 18px;
22 margin-bottom: 5px;
23}
24
25.row {
26 margin-bottom: 30px;
27}
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 47519c943..327e23f3f 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
@@ -3,7 +3,7 @@
3 Verify account email confirmation 3 Verify account email confirmation
4 </div> 4 </div>
5 5
6 <my-signup-success i18n *ngIf="!isPendingEmail && success" message="Your email has been verified and you may now login."> 6 <my-signup-success i18n *ngIf="!isPendingEmail && success" [requiresEmailVerification]="false">
7 </my-signup-success> 7 </my-signup-success>
8 8
9 <div i18n class="alert alert-success" *ngIf="isPendingEmail && success"> 9 <div i18n class="alert alert-success" *ngIf="isPendingEmail && success">
diff --git a/client/src/app/+signup/shared/shared-signup.module.ts b/client/src/app/+signup/shared/shared-signup.module.ts
index f8b224c71..0aa08f3e2 100644
--- a/client/src/app/+signup/shared/shared-signup.module.ts
+++ b/client/src/app/+signup/shared/shared-signup.module.ts
@@ -3,6 +3,8 @@ import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedGlobalIconModule } from '@app/shared/shared-icons' 3import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedMainModule } from '@app/shared/shared-main' 4import { 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'
7import { SignupStepTitleComponent } from './signup-step-title.component'
6import { SignupSuccessComponent } from './signup-success.component' 8import { SignupSuccessComponent } from './signup-success.component'
7 9
8@NgModule({ 10@NgModule({
@@ -14,7 +16,9 @@ import { SignupSuccessComponent } from './signup-success.component'
14 ], 16 ],
15 17
16 declarations: [ 18 declarations: [
17 SignupSuccessComponent 19 SignupSuccessComponent,
20 SignupStepTitleComponent,
21 SignupMascotComponent
18 ], 22 ],
19 23
20 exports: [ 24 exports: [
@@ -22,7 +26,9 @@ import { SignupSuccessComponent } from './signup-success.component'
22 SharedFormModule, 26 SharedFormModule,
23 SharedGlobalIconModule, 27 SharedGlobalIconModule,
24 28
25 SignupSuccessComponent 29 SignupSuccessComponent,
30 SignupStepTitleComponent,
31 SignupMascotComponent
26 ], 32 ],
27 33
28 providers: [ 34 providers: [
diff --git a/client/src/app/+signup/shared/signup-mascot.component.scss b/client/src/app/+signup/shared/signup-mascot.component.scss
new file mode 100644
index 000000000..5eebfb014
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-mascot.component.scss
@@ -0,0 +1,11 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.root {
5 display: inline-block;
6 width: 270px;
7}
8
9div ::ng-deep svg {
10 color: pvar(--mainColor);
11}
diff --git a/client/src/app/+signup/shared/signup-mascot.component.ts b/client/src/app/+signup/shared/signup-mascot.component.ts
new file mode 100644
index 000000000..a96ccffee
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-mascot.component.ts
@@ -0,0 +1,29 @@
1import { Component, Input } from '@angular/core'
2import { DomSanitizer } from '@angular/platform-browser'
3
4const images = {
5 about: require('!!raw-loader?!../../../assets/images/mascot/register/about.svg').default,
6 terms: require('!!raw-loader?!../../../assets/images/mascot/register/terms.svg').default,
7 success: require('!!raw-loader?!../../../assets/images/mascot/register/success.svg').default,
8 channel: require('!!raw-loader?!../../../assets/images/mascot/register/channel.svg').default,
9 account: require('!!raw-loader?!../../../assets/images/mascot/register/account.svg').default
10}
11
12export type MascotImageName = keyof typeof images
13
14@Component({
15 selector: 'my-signup-mascot',
16 styleUrls: [ './signup-mascot.component.scss' ],
17 template: `<div class="root" [innerHTML]="html"></div>`
18})
19export class SignupMascotComponent {
20 @Input() imageName: MascotImageName
21
22 constructor (private sanitize: DomSanitizer) {
23
24 }
25
26 get html () {
27 return this.sanitize.bypassSecurityTrustHtml(images[this.imageName])
28 }
29}
diff --git a/client/src/app/+signup/shared/signup-step-title.component.html b/client/src/app/+signup/shared/signup-step-title.component.html
new file mode 100644
index 000000000..9cf4c4826
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-step-title.component.html
@@ -0,0 +1,9 @@
1<div class="step-content-title">
2 <my-signup-mascot [imageName]="mascotImageName"></my-signup-mascot>
3
4 <h2>
5 <ng-content></ng-content>
6 </h2>
7
8 <div class="step-content-title-separator"></div>
9</div>
diff --git a/client/src/app/+signup/shared/signup-step-title.component.scss b/client/src/app/+signup/shared/signup-step-title.component.scss
new file mode 100644
index 000000000..1e0cb2440
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-step-title.component.scss
@@ -0,0 +1,23 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.step-content-title {
5 text-align: center;
6 margin: auto;
7 margin-bottom: 45px;
8
9 h2 {
10 font-size: 32px;
11 font-weight: normal;
12 max-width: 300px;
13 margin: 15px auto 0;
14 }
15}
16
17.step-content-title-separator {
18 height: 6px;
19 width: 60px;
20 border-radius: 4px;
21 background-color: pvar(--mainColor);
22 margin: 5px auto 0;
23}
diff --git a/client/src/app/+signup/shared/signup-step-title.component.ts b/client/src/app/+signup/shared/signup-step-title.component.ts
new file mode 100644
index 000000000..9664eb7f3
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-step-title.component.ts
@@ -0,0 +1,12 @@
1import { Component, Input } from '@angular/core'
2import { MascotImageName } from './signup-mascot.component'
3
4@Component({
5 selector: 'my-signup-step-title',
6 templateUrl: './signup-step-title.component.html',
7 styleUrls: [ './signup-step-title.component.scss' ]
8})
9export class SignupStepTitleComponent {
10 @Input() mascotImageName: MascotImageName
11
12}
diff --git a/client/src/app/+signup/shared/signup-success.component.html b/client/src/app/+signup/shared/signup-success.component.html
index d66e8b568..c14889c72 100644
--- a/client/src/app/+signup/shared/signup-success.component.html
+++ b/client/src/app/+signup/shared/signup-success.component.html
@@ -1,20 +1,22 @@
1<!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO --> 1<my-signup-step-title mascotImageName="success" i18n>
2 <strong>Welcome</strong>
3 <div>on {{ instanceName }}</div>
4</my-signup-step-title>
2 5
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2"> 6<div class="alert pt-alert-primary">
4 <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/> 7 <p i18n>Your account has been created!</p>
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
8<p i18n class="bottom-message">Welcome to PeerTube!</p> 9 <p i18n *ngIf="requiresEmailVerification">
9 10 <strong>Check your emails</strong> to validate your account and complete your inscription.
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 have a look at the <a href="https://docs.joinpeertube.org/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>.
15 </p> 11 </p>
16 12
17 <p i18n> 13 <ng-container *ngIf="!requiresEmailVerification">
18 To help moderators and other users to know <strong>who you are</strong>, don't forget to <a routerLink="/my-account/settings">set up your account profile</a> by adding an <strong>avatar</strong> and a <strong>description</strong>. 14 <p i18n>
19 </p> 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>
20</div> 22</div>
diff --git a/client/src/app/+signup/shared/signup-success.component.scss b/client/src/app/+signup/shared/signup-success.component.scss
index b302366e2..918349ba0 100644
--- a/client/src/app/+signup/shared/signup-success.component.scss
+++ b/client/src/app/+signup/shared/signup-success.component.scss
@@ -1,54 +1,6 @@
1svg {
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 animation: dash .9s ease-in-out;
13 }
14
15 &.line {
16 stroke-dashoffset: 1000;
17 animation: dash .9s .35s ease-in-out forwards;
18 }
19
20 &.check {
21 stroke-dashoffset: -100;
22 animation: dash-check .9s .35s ease-in-out forwards;
23 }
24}
25
26.bottom-message {
27 text-align: center;
28 margin: 20px 0 60px;
29 font-size: 1.25em;
30 color: #73AF55;
31}
32
33.alert { 1.alert {
34 font-size: 15px; 2 font-size: 18px;
3 max-width: 900px;
35 text-align: center; 4 text-align: center;
36} 5 margin: auto;
37
38@keyframes dash {
39 0% {
40 stroke-dashoffset: 1000;
41 }
42 100% {
43 stroke-dashoffset: 0;
44 }
45}
46
47@keyframes dash-check {
48 0% {
49 stroke-dashoffset: -100;
50 }
51 100% {
52 stroke-dashoffset: 900;
53 }
54} 6}
diff --git a/client/src/app/+signup/shared/signup-success.component.ts b/client/src/app/+signup/shared/signup-success.component.ts
index 19fb5922a..a03f3819d 100644
--- a/client/src/app/+signup/shared/signup-success.component.ts
+++ b/client/src/app/+signup/shared/signup-success.component.ts
@@ -1,4 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { ServerService } from '@app/core'
2 3
3@Component({ 4@Component({
4 selector: 'my-signup-success', 5 selector: 'my-signup-success',
@@ -6,5 +7,13 @@ import { Component, Input } from '@angular/core'
6 styleUrls: [ './signup-success.component.scss' ] 7 styleUrls: [ './signup-success.component.scss' ]
7}) 8})
8export class SignupSuccessComponent { 9export class SignupSuccessComponent {
9 @Input() message: string 10 @Input() requiresEmailVerification: boolean
11
12 constructor (private serverService: ServerService) {
13
14 }
15
16 get instanceName () {
17 return this.serverService.getHTMLConfig().instance.name
18 }
10} 19}
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts
index 6d0dea64e..3262853d8 100644
--- a/client/src/app/shared/form-validators/user-validators.ts
+++ b/client/src/app/shared/form-validators/user-validators.ts
@@ -61,7 +61,7 @@ export const USER_EXISTING_PASSWORD_VALIDATOR: BuildFormValidator = {
61 } 61 }
62} 62}
63 63
64export const USER_PASSWORD_VALIDATOR: BuildFormValidator = { 64export const USER_PASSWORD_VALIDATOR = {
65 VALIDATORS: [ 65 VALIDATORS: [
66 Validators.required, 66 Validators.required,
67 Validators.minLength(6), 67 Validators.minLength(6),
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.html b/client/src/app/shared/shared-instance/instance-about-accordion.component.html
index 73e511d1c..466d73ca4 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.html
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.html
@@ -1,6 +1,6 @@
1<h2 class="instance-name">{{ about?.instance.name }}</h2> 1<h2 *ngIf="displayInstanceName" class="instance-name">{{ about?.instance.name }}</h2>
2 2
3<div class="instance-short-description">{{ about?.instance.shortDescription }}</div> 3<div *ngIf="displayInstanceShortDescription" class="instance-short-description">{{ about?.instance.shortDescription }}</div>
4 4
5<ngb-accordion #accordion="ngbAccordion" [closeOthers]="true"> 5<ngb-accordion #accordion="ngbAccordion" [closeOthers]="true">
6 <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance"> 6 <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance">
@@ -32,7 +32,7 @@
32 </ng-template> 32 </ng-template>
33 </ngb-panel> 33 </ngb-panel>
34 34
35 <ngb-panel *ngIf="termsPanel" id="terms" i18n-title title="Terms"> 35 <ngb-panel *ngIf="termsPanel" id="terms" [title]="getTermsTitle()">
36 <ng-template ngbPanelContent> 36 <ng-template ngbPanelContent>
37 <div class="block" [innerHTML]="aboutHtml.terms"></div> 37 <div class="block" [innerHTML]="aboutHtml.terms"></div>
38 </ng-template> 38 </ng-template>
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
index 8e5dfb064..0da7aede9 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
@@ -8,8 +8,7 @@
8.instance-short-description { 8.instance-short-description {
9 @include ellipsis-multiline(1rem, 3); 9 @include ellipsis-multiline(1rem, 3);
10 10
11 margin-top: 20px; 11 margin: 25px 0;
12 margin-bottom: 20px;
13} 12}
14 13
15.block { 14.block {
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
index b9f57e2a4..e13703c03 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
@@ -15,6 +15,9 @@ export class InstanceAboutAccordionComponent implements OnInit {
15 15
16 @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>() 16 @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>()
17 17
18 @Input() displayInstanceName = true
19 @Input() displayInstanceShortDescription = true
20
18 @Input() pluginScope: PluginClientScope 21 @Input() pluginScope: PluginClientScope
19 @Input() pluginHook: ClientFilterHookName 22 @Input() pluginHook: ClientFilterHookName
20 23
@@ -66,6 +69,10 @@ export class InstanceAboutAccordionComponent implements OnInit {
66 return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel) 69 return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel)
67 } 70 }
68 71
72 getTermsTitle () {
73 return $localize`Terms of ${this.about.instance.name}`
74 }
75
69 get moderationPanel () { 76 get moderationPanel () {
70 return this.panels.moderation && !!this.aboutHtml.moderationInformation 77 return this.panels.moderation && !!this.aboutHtml.moderationInformation
71 } 78 }