aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorKimsible <1877318+kimsible@users.noreply.github.com>2020-12-07 16:34:07 +0100
committerGitHub <noreply@github.com>2020-12-07 16:34:07 +0100
commit40360c17d82b33accb34ea974c275e17880c37aa (patch)
treee0ddac1174e54897b4871daa4dca43dce121f590 /client
parent10f26f4203b8cef32778bf3435d8112eaea3c093 (diff)
downloadPeerTube-40360c17d82b33accb34ea974c275e17880c37aa.tar.gz
PeerTube-40360c17d82b33accb34ea974c275e17880c37aa.tar.zst
PeerTube-40360c17d82b33accb34ea974c275e17880c37aa.zip
improvements to login and sign-up pages (#3357)
* New login form ui * Move InstanceAboutAccordion to shared components * Update closed registration alert text * Add alert for opened registration and move them bellow login form * Adjust flex block on signup and login views * Replace toggle accordion with expand on links in signup and login + scrollTo * Improve display of login alerts * Fix missing Component suffix * Define min-width instance-information block sign-up and login for mobile screens * Add ability to select specific panels in instanceAboutAccorddion * Add instance title and short-description to common instanceAboutAccordion * Clarify title alert in login page * Add step terms for signup Co-authored-by: kimsible <kimsible@users.noreply.github.com> Co-authored-by: Rigel Kent <sendmemail@rigelk.eu>
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+login/login.component.html108
-rw-r--r--client/src/app/+login/login.component.scss150
-rw-r--r--client/src/app/+login/login.component.ts25
-rw-r--r--client/src/app/+login/login.module.ts5
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.html2
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.scss10
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.ts7
-rw-r--r--client/src/app/+signup/+register/register-step-terms.component.html18
-rw-r--r--client/src/app/+signup/+register/register-step-terms.component.ts47
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.html16
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.ts17
-rw-r--r--client/src/app/+signup/+register/register.component.html77
-rw-r--r--client/src/app/+signup/+register/register.component.scss67
-rw-r--r--client/src/app/+signup/+register/register.component.ts62
-rw-r--r--client/src/app/+signup/+register/register.module.ts4
-rw-r--r--client/src/app/shared/form-validators/user-validators.ts4
-rw-r--r--client/src/app/shared/shared-instance/index.ts1
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.html53
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.scss46
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.ts71
-rw-r--r--client/src/app/shared/shared-instance/shared-instance.module.ts7
21 files changed, 543 insertions, 254 deletions
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html
index 390d77dd7..a2362d978 100644
--- a/client/src/app/+login/login.component.html
+++ b/client/src/app/+login/login.component.html
@@ -8,73 +8,81 @@
8 </div> 8 </div>
9 9
10 <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> 10 <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth">
11 <div class="looking-for-account alert alert-info" *ngIf="signupAllowed === false" role="alert">
12 <h6 class="alert-heading" i18n>
13 If you are looking for an account…
14 </h6>
15
16 <div i18n>
17 Currently this instance doesn't allow for user registration, but you can find an instance
18 that gives you the possibility to sign up for an account and upload your videos there.
19
20 <br />
21
22 Find yours among multiple instances at <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
23 </div>
24 </div>
25
26 <div *ngIf="error" class="alert alert-danger">{{ error }} 11 <div *ngIf="error" class="alert alert-danger">{{ error }}
27 <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> 12 <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span>
28 </div> 13 </div>
29 14
30 <div class="login-form-and-externals"> 15 <div class="wrapper">
31 16 <div class="login-form-and-externals">
32 <form role="form" (ngSubmit)="login()" [formGroup]="form"> 17
33 <div class="form-group"> 18 <form role="form" (ngSubmit)="login()" [formGroup]="form">
34 <div> 19 <div class="form-group">
35 <label i18n for="username">User</label> 20 <div>
36 <input 21 <label i18n for="username">User</label>
37 type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" 22 <input
38 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput 23 type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1"
39 > 24 formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput
40 <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account"> 25 >
41 or create an account 26 </div>
42 </a> 27
28 <div *ngIf="formErrors.username" class="form-error">
29 {{ formErrors.username }}
30 </div>
43 </div> 31 </div>
44 32
45 <div *ngIf="formErrors.username" class="form-error"> 33 <div class="form-group">
46 {{ formErrors.username }} 34 <label i18n for="password">Password</label>
47 </div>
48 </div>
49
50 <div class="form-group">
51 <label i18n for="password">Password</label>
52 <div>
53 <my-input-toggle-hidden formControlName="password" id="password" 35 <my-input-toggle-hidden formControlName="password" id="password"
54 i18n-placeholder placeholder="Password" 36 i18n-placeholder placeholder="Password"
55 [ngClass]="{ 'input-error': formErrors['password'] }" 37 [ngClass]="{ 'input-error': formErrors['password'] }"
56 autocomplete="current-password"></my-input-toggle-hidden> 38 autocomplete="current-password" tabindex="2"></my-input-toggle-hidden>
57 <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a> 39 <div *ngIf="formErrors.password" class="form-error">
40 {{ formErrors.password }}
41 </div>
58 </div> 42 </div>
59 <div *ngIf="formErrors.password" class="form-error"> 43
60 {{ formErrors.password }} 44 <input type="submit" i18n-value value="Login" [disabled]="!form.valid">
45
46 <div class="additionnal-links">
47 <a class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a>
48 <div *ngIf="signupAllowed" class="signup-link">
49 <span>·</span>
50 <a i18n routerLink="/signup" class="create-an-account">Create an account</a>
51 </div>
61 </div> 52 </div>
62 </div>
63 53
64 <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> 54 <div class="looking-for-account alert alert-info" role="alert">
65 </form> 55 <h6 class="alert-heading" i18n>
56 Logging into an account lets you publish content
57 </h6>
58
59 <div *ngIf="signupAllowed" i18n>
60 This instance allows registration. However, be careful to check the <a class="terms-anchor" (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a><a class="terms-link" target="_blank" routerLink="/about/instance" fragment="terms">Terms</a> before creating an account.
61 You may also search for another instance to match your exact needs at: <br /><a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
62 </div>
63
64 <div *ngIf="!signupAllowed" i18n>
65 Currently this instance doesn't allow for user registration, you may check the <a (click)="onTermsClick($event, instanceInformation)" href='#'>Terms</a> for more details or find an instance that gives you the possibility to sign up for an account and upload your videos there.
66 Find yours among multiple instances at: <br /> <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>.
67 </div>
68 </div>
69 </form>
66 70
67 <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0"> 71 <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0">
68 <div class="block-title" i18n>Or sign in with</div> 72 <div class="block-title" i18n>Or sign in with</div>
69 73
70 <div> 74 <div>
71 <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button"> 75 <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button">
72 {{ auth.authDisplayName }} 76 {{ auth.authDisplayName }}
73 </a> 77 </a>
78 </div>
74 </div> 79 </div>
75 </div> 80 </div>
76 </div>
77 81
82 <div #instanceInformation class="instance-information">
83 <my-instance-about-accordion (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"></my-instance-about-accordion>
84 </div>
85 </div>
78 </ng-container> 86 </ng-container>
79</div> 87</div>
80 88
diff --git a/client/src/app/+login/login.component.scss b/client/src/app/+login/login.component.scss
index 9ba09e9e4..3cc302aec 100644
--- a/client/src/app/+login/login.component.scss
+++ b/client/src/app/+login/login.component.scss
@@ -1,5 +1,8 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3@import './_bootstrap-variables';
4@import '~bootstrap/scss/functions';
5@import '~bootstrap/scss/variables';
3 6
4label { 7label {
5 display: block; 8 display: block;
@@ -57,39 +60,138 @@ input[type=submit] {
57 } 60 }
58} 61}
59 62
60.login-form-and-externals { 63.wrapper {
61 display: flex; 64 display: flex;
65 justify-content: space-around;
62 flex-wrap: wrap; 66 flex-wrap: wrap;
63 font-size: 15px;
64 67
65 form { 68 & > div {
66 margin: 0 50px 20px 0; 69 flex: 1 1;
67 } 70 }
68 71
69 .external-login-blocks { 72 .login-form-and-externals {
70 min-width: 200px; 73 display: flex;
74 flex-wrap: wrap;
75 font-size: 15px;
76 max-width: 450px;
77 margin-bottom: 40px;
78 margin-left: 10px;
79 margin-right: 10px;
71 80
72 .block-title { 81 form {
73 font-weight: $font-semibold; 82 margin: 0;
83
84 &, input {
85 width: 100%;
86 }
87
88 .additionnal-links {
89 display: block;
90 text-align: center;
91 margin-top: 20px;
92 margin-bottom: 20px;
93
94 .forgot-password-button,
95 .create-an-account {
96 padding: 4px;
97 display: inline-block;
98
99 color: var(--mainColor);
100
101 &:hover, &:active {
102 color: var(--mainHoverColor);
103 }
104 }
105 }
74 } 106 }
75 107
76 .external-login-block { 108 .external-login-blocks {
77 @include disable-default-a-behaviour; 109 min-width: 200px;
78 110
79 cursor: pointer; 111 .block-title {
80 border: 1px solid #d1d7e0; 112 font-weight: $font-semibold;
81 border-radius: 5px; 113 }
82 color: pvar(--mainForegroundColor); 114
83 margin: 10px 10px 0 0; 115 .external-login-block {
84 display: flex; 116 @include disable-default-a-behaviour;
85 justify-content: center; 117
86 align-items: center; 118 cursor: pointer;
87 min-height: 35px; 119 border: 1px solid #d1d7e0;
88 min-width: 100px; 120 border-radius: 5px;
89 121 color: pvar(--mainForegroundColor);
90 &:hover { 122 margin: 10px 10px 0 0;
91 background-color: rgba(209, 215, 224, 0.5) 123 display: flex;
124 justify-content: center;
125 align-items: center;
126 min-height: 35px;
127 min-width: 100px;
128
129 &:hover {
130 background-color: rgba(209, 215, 224, 0.5)
131 }
92 } 132 }
93 } 133 }
134
135 .signup-link {
136 display: inline-block;
137 }
138 }
139
140 .instance-information {
141 max-width: 600px;
142 min-width: 350px;
143 margin-bottom: 40px;
144 margin-left: 10px;
145 margin-right: 10px;
146 }
147
148 .terms-anchor {
149 display: inline;
150 }
151
152 .terms-link {
153 display: none;
154 }
155}
156
157@mixin columnReverseDisplay {
158 flex-direction: column-reverse;
159
160 .login-form-and-externals,
161 .instance-information {
162 width: 100%;
163 margin-left: 0;
164 margin-right: 0;
165 max-width: 450px;
166 min-width: unset;
167 align-self: center;
168 }
169
170 .instance-information {
171 ::ng-deep .accordion {
172 display: none;
173 }
174 }
175
176 .terms-anchor {
177 display: none;
178 }
179
180 .terms-link {
181 display: inline;
182 }
183}
184
185@media screen and (max-width: breakpoint(md)) {
186 .wrapper {
187 @include columnReverseDisplay();
188 }
189}
190
191@media screen and (max-width: breakpoint(md) + $menu-width) {
192 :host-context(.main-col:not(.expanded)) {
193 .wrapper {
194 @include columnReverseDisplay();
195 }
94 } 196 }
95} 197}
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts
index 351750453..2567f21f1 100644
--- a/client/src/app/+login/login.component.ts
+++ b/client/src/app/+login/login.component.ts
@@ -3,9 +3,10 @@ import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angula
3import { ActivatedRoute } from '@angular/router' 3import { ActivatedRoute } from '@angular/router'
4import { AuthService, Notifier, RedirectService, UserService } from '@app/core' 4import { AuthService, Notifier, RedirectService, UserService } from '@app/core'
5import { HooksService } from '@app/core/plugins/hooks.service' 5import { HooksService } from '@app/core/plugins/hooks.service'
6import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
6import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' 7import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 8import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' 9import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
9import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' 10import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models'
10 11
11@Component({ 12@Component({
@@ -18,6 +19,7 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
18 @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef 19 @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef
19 @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef 20 @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef
20 21
22 accordion: NgbAccordion
21 error: string = null 23 error: string = null
22 forgotPasswordEmail = '' 24 forgotPasswordEmail = ''
23 25
@@ -25,6 +27,14 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
25 externalAuthError = false 27 externalAuthError = false
26 externalLogins: string[] = [] 28 externalLogins: string[] = []
27 29
30 instanceInformationPanels = {
31 terms: true,
32 administrators: false,
33 features: false,
34 moderation: false,
35 codeOfConduct: false
36 }
37
28 private openedForgotPasswordModal: NgbModalRef 38 private openedForgotPasswordModal: NgbModalRef
29 private serverConfig: ServerConfig 39 private serverConfig: ServerConfig
30 40
@@ -45,6 +55,15 @@ export class LoginComponent extends FormReactive implements OnInit, AfterViewIni
45 return this.serverConfig.signup.allowed === true 55 return this.serverConfig.signup.allowed === true
46 } 56 }
47 57
58 onTermsClick (event: Event, instanceInformation: HTMLElement) {
59 event.preventDefault()
60
61 if (this.accordion) {
62 this.accordion.expand('terms')
63 instanceInformation.scrollIntoView({ behavior: 'smooth' })
64 }
65 }
66
48 isEmailDisabled () { 67 isEmailDisabled () {
49 return this.serverConfig.email.enabled === false 68 return this.serverConfig.email.enabled === false
50 } 69 }
@@ -122,6 +141,10 @@ The link will expire within 1 hour.`
122 this.openedForgotPasswordModal.close() 141 this.openedForgotPasswordModal.close()
123 } 142 }
124 143
144 onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) {
145 this.accordion = instanceAboutAccordion.accordion
146 }
147
125 private loadExternalAuthToken (username: string, token: string) { 148 private loadExternalAuthToken (username: string, token: string) {
126 this.isAuthenticatedWithExternalAuth = true 149 this.isAuthenticatedWithExternalAuth = true
127 150
diff --git a/client/src/app/+login/login.module.ts b/client/src/app/+login/login.module.ts
index c41902426..9d8607e5d 100644
--- a/client/src/app/+login/login.module.ts
+++ b/client/src/app/+login/login.module.ts
@@ -1,6 +1,7 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { SharedFormModule } from '@app/shared/shared-forms' 2import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedGlobalIconModule } from '@app/shared/shared-icons' 3import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedInstanceModule } from '@app/shared/shared-instance'
4import { SharedMainModule } from '@app/shared/shared-main' 5import { SharedMainModule } from '@app/shared/shared-main'
5import { LoginRoutingModule } from './login-routing.module' 6import { LoginRoutingModule } from './login-routing.module'
6import { LoginComponent } from './login.component' 7import { LoginComponent } from './login.component'
@@ -11,7 +12,9 @@ import { LoginComponent } from './login.component'
11 12
12 SharedMainModule, 13 SharedMainModule,
13 SharedFormModule, 14 SharedFormModule,
14 SharedGlobalIconModule 15 SharedGlobalIconModule,
16
17 SharedInstanceModule
15 ], 18 ],
16 19
17 declarations: [ 20 declarations: [
diff --git a/client/src/app/+signup/+register/custom-stepper.component.html b/client/src/app/+signup/+register/custom-stepper.component.html
index 4cfe54152..aad2f31d3 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.html
+++ b/client/src/app/+signup/+register/custom-stepper.component.html
@@ -2,7 +2,7 @@
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 <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
4 <div 4 <div
5 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }" [attr.aria-current]="selectedIndex === i" 5 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step), 'c-hand': isAccessible(i) }" [attr.aria-current]="selectedIndex === i"
6 (click)="onClick(i)" 6 (click)="onClick(i)"
7 > 7 >
8 <div class="step-index"> 8 <div class="step-index">
diff --git a/client/src/app/+signup/+register/custom-stepper.component.scss b/client/src/app/+signup/+register/custom-stepper.component.scss
index cc1591ee8..3b4791d08 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.scss
+++ b/client/src/app/+signup/+register/custom-stepper.component.scss
@@ -4,6 +4,12 @@
4$grey-color: #9CA3AB; 4$grey-color: #9CA3AB;
5$index-block-height: 32px; 5$index-block-height: 32px;
6 6
7.container {
8 padding-left: 0;
9 padding-right: 0;
10 max-width: unset !important;
11}
12
7header { 13header {
8 display: flex; 14 display: flex;
9 justify-content: space-between; 15 justify-content: space-between;
@@ -17,6 +23,10 @@ header {
17 align-items: center; 23 align-items: center;
18 width: $index-block-height; 24 width: $index-block-height;
19 25
26 &:not(.c-hand) {
27 cursor: default;
28 }
29
20 .step-index { 30 .step-index {
21 display: flex; 31 display: flex;
22 justify-content: center; 32 justify-content: center;
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts
index 2ae40f3a9..5a80895f9 100644
--- a/client/src/app/+signup/+register/custom-stepper.component.ts
+++ b/client/src/app/+signup/+register/custom-stepper.component.ts
@@ -16,4 +16,11 @@ export class CustomStepperComponent extends CdkStepper {
16 isCompleted (step: CdkStep) { 16 isCompleted (step: CdkStep) {
17 return step.stepControl && step.stepControl.dirty && step.stepControl.valid 17 return step.stepControl && step.stepControl.dirty && step.stepControl.valid
18 } 18 }
19
20 isAccessible (index: number) {
21 const stepsCompletedMap = this.steps.map(step => this.isCompleted(step))
22 return index === 0
23 ? true
24 : stepsCompletedMap[ index - 1 ]
25 }
19} 26}
diff --git a/client/src/app/+signup/+register/register-step-terms.component.html b/client/src/app/+signup/+register/register-step-terms.component.html
new file mode 100644
index 000000000..1cfdc0a3a
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-terms.component.html
@@ -0,0 +1,18 @@
1<form role="form" [formGroup]="form">
2 <div class="form-group form-group-terms">
3 <my-peertube-checkbox inputName="terms" formControlName="terms">
4 <ng-template ptTemplate="label">
5 <ng-container i18n>
6 I am at least 16 years old and agree
7 to the <a class="terms-anchor" (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>
9 of this instance
10 </ng-container>
11 </ng-template>
12 </my-peertube-checkbox>
13
14 <div *ngIf="formErrors.terms" class="form-error">
15 {{ formErrors.terms }}
16 </div>
17 </div>
18</form>
diff --git a/client/src/app/+signup/+register/register-step-terms.component.ts b/client/src/app/+signup/+register/register-step-terms.component.ts
new file mode 100644
index 000000000..db834c68d
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-terms.component.ts
@@ -0,0 +1,47 @@
1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
2import { FormGroup } from '@angular/forms'
3import {
4 USER_TERMS_VALIDATOR
5} from '@app/shared/form-validators/user-validators'
6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7
8@Component({
9 selector: 'my-register-step-terms',
10 templateUrl: './register-step-terms.component.html',
11 styleUrls: [ './register.component.scss' ]
12})
13export class RegisterStepTermsComponent extends FormReactive implements OnInit {
14 @Input() hasCodeOfConduct = false
15
16 @Output() formBuilt = new EventEmitter<FormGroup>()
17 @Output() termsClick = new EventEmitter<void>()
18 @Output() codeOfConductClick = new EventEmitter<void>()
19
20 constructor (
21 protected formValidatorService: FormValidatorService
22 ) {
23 super()
24 }
25
26 get instanceHost () {
27 return window.location.host
28 }
29
30 ngOnInit () {
31 this.buildForm({
32 terms: USER_TERMS_VALIDATOR
33 })
34
35 setTimeout(() => this.formBuilt.emit(this.form))
36 }
37
38 onTermsClick (event: Event) {
39 event.preventDefault()
40 this.termsClick.emit()
41 }
42
43 onCodeOfConductClick (event: Event) {
44 event.preventDefault()
45 this.codeOfConductClick.emit()
46 }
47}
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html
index 6eab4df0c..ac341666b 100644
--- a/client/src/app/+signup/+register/register-step-user.component.html
+++ b/client/src/app/+signup/+register/register-step-user.component.html
@@ -63,20 +63,4 @@
63 </div> 63 </div>
64 </div> 64 </div>
65 65
66 <div class="form-group form-group-terms">
67 <my-peertube-checkbox inputName="terms" formControlName="terms">
68 <ng-template ptTemplate="label">
69 <ng-container i18n>
70 I am at least 16 years old and agree
71 to the <a (click)="onTermsClick($event)" href='#'>Terms</a>
72 <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container>
73 of this instance
74 </ng-container>
75 </ng-template>
76 </my-peertube-checkbox>
77
78 <div *ngIf="formErrors.terms" class="form-error">
79 {{ formErrors.terms }}
80 </div>
81 </div>
82</form> 66</form>
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts
index 9193540b4..716cd8c78 100644
--- a/client/src/app/+signup/+register/register-step-user.component.ts
+++ b/client/src/app/+signup/+register/register-step-user.component.ts
@@ -7,7 +7,6 @@ import {
7 USER_DISPLAY_NAME_REQUIRED_VALIDATOR, 7 USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
8 USER_EMAIL_VALIDATOR, 8 USER_EMAIL_VALIDATOR,
9 USER_PASSWORD_VALIDATOR, 9 USER_PASSWORD_VALIDATOR,
10 USER_TERMS_VALIDATOR,
11 USER_USERNAME_VALIDATOR 10 USER_USERNAME_VALIDATOR
12} from '@app/shared/form-validators/user-validators' 11} from '@app/shared/form-validators/user-validators'
13import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 12import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
@@ -18,12 +17,9 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
18 styleUrls: [ './register.component.scss' ] 17 styleUrls: [ './register.component.scss' ]
19}) 18})
20export class RegisterStepUserComponent extends FormReactive implements OnInit { 19export class RegisterStepUserComponent extends FormReactive implements OnInit {
21 @Input() hasCodeOfConduct = false
22 @Input() videoUploadDisabled = false 20 @Input() videoUploadDisabled = false
23 21
24 @Output() formBuilt = new EventEmitter<FormGroup>() 22 @Output() formBuilt = new EventEmitter<FormGroup>()
25 @Output() termsClick = new EventEmitter<void>()
26 @Output() codeOfConductClick = new EventEmitter<void>()
27 23
28 constructor ( 24 constructor (
29 protected formValidatorService: FormValidatorService, 25 protected formValidatorService: FormValidatorService,
@@ -41,8 +37,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
41 displayName: USER_DISPLAY_NAME_REQUIRED_VALIDATOR, 37 displayName: USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
42 username: USER_USERNAME_VALIDATOR, 38 username: USER_USERNAME_VALIDATOR,
43 password: USER_PASSWORD_VALIDATOR, 39 password: USER_PASSWORD_VALIDATOR,
44 email: USER_EMAIL_VALIDATOR, 40 email: USER_EMAIL_VALIDATOR
45 terms: USER_TERMS_VALIDATOR
46 }) 41 })
47 42
48 setTimeout(() => this.formBuilt.emit(this.form)) 43 setTimeout(() => this.formBuilt.emit(this.form))
@@ -54,16 +49,6 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
54 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) 49 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
55 } 50 }
56 51
57 onTermsClick (event: Event) {
58 event.preventDefault()
59 this.termsClick.emit()
60 }
61
62 onCodeOfConductClick (event: Event) {
63 event.preventDefault()
64 this.codeOfConductClick.emit()
65 }
66
67 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 52 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
68 const username = this.form.value['username'] || '' 53 const username = this.form.value['username'] || ''
69 54
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html
index 7e3704124..bebc5d926 100644
--- a/client/src/app/+signup/+register/register.component.html
+++ b/client/src/app/+signup/+register/register.component.html
@@ -10,19 +10,26 @@
10 <div class="wrapper" [hidden]="signupDone"> 10 <div class="wrapper" [hidden]="signupDone">
11 <div class="register-form"> 11 <div class="register-form">
12 <my-custom-stepper linear *ngIf="!signupDone"> 12 <my-custom-stepper linear *ngIf="!signupDone">
13 <cdk-step [stepControl]="formStepUser" i18n-label label="User"> 13 <cdk-step [stepControl]="formStepTerms" i18n-label="Stepper label for the registration page describing terms of service" label="Terms">
14 <my-register-step-user 14 <div class="instance-information">
15 <my-instance-about-accordion (init)="onInstanceAboutAccordionInit($event)" [panels]="instanceInformationPanels"></my-instance-about-accordion>
16 </div>
17
18 <my-register-step-terms
15 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" 19 [hasCodeOfConduct]="!!aboutHtml.codeOfConduct"
16 [videoUploadDisabled]="videoUploadDisabled" 20 (formBuilt)="onTermsFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()"
17 (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" 21 ></my-register-step-terms>
18 > 22
19 </my-register-step-user> 23 <button cdkStepperNext [disabled]="!formStepTerms || !formStepTerms.valid">{{ defaultNextStepButtonLabel }}</button>
24 </cdk-step>
25
26 <cdk-step [stepControl]="formStepUser" i18n-label="Stepper label for the registration page asking user informations" label="User">
27 <my-register-step-user (formBuilt)="onUserFormBuilt($event)" [videoUploadDisabled]="videoUploadDisabled"></my-register-step-user>
20 28
21 <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" 29 <button cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid" (click)="videoUploadDisabled && signup()">{{ stepUserButtonLabel }}</button>
22 (click)="signup()">{{ videoUploadDisabled ? 'Signup' : 'Next' }}</button>
23 </cdk-step> 30 </cdk-step>
24 31
25 <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel" *ngIf="!videoUploadDisabled"> 32 <cdk-step [stepControl]="formStepChannel" i18n-label="Stepper label for the registration page asking information about the default channel" label="Channel" *ngIf="!videoUploadDisabled">
26 <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel> 33 <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel>
27 34
28 <button i18n cdkStepperNext (click)="signup()" 35 <button i18n cdkStepperNext (click)="signup()"
@@ -43,58 +50,6 @@
43 </cdk-step> 50 </cdk-step>
44 </my-custom-stepper> 51 </my-custom-stepper>
45 </div> 52 </div>
46
47 <div class="instance-information">
48 <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion">
49 <ngb-panel id="instance-features" i18n-title title="Features found on this instance">
50 <ng-template ngbPanelContent>
51 <my-instance-features-table></my-instance-features-table>
52 </ng-template>
53 </ngb-panel>
54
55 <ng-container *ngIf="about">
56 <ngb-panel
57 *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel"
58 id="admin-sustainability" i18n-title title="Administrators & Sustainability"
59 >
60 <ng-template ngbPanelContent>
61 <div class="block">
62 <strong i18n>Who are we?</strong>
63 <div [innerHTML]="aboutHtml.administrator"></div>
64 </div>
65
66 <div class="block">
67 <strong i18n>How long do we plan to maintain this instance?</strong>
68 <div [innerHTML]="about.instance.maintenanceLifetime"></div>
69 </div>
70
71 <div class="block">
72 <strong i18n>How will we finance this instance?</strong>
73 <div [innerHTML]="about.instance.businessModel"></div>
74 </div>
75 </ng-template>
76 </ngb-panel>
77
78 <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information">
79 <ng-template ngbPanelContent>
80 <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div>
81 </ng-template>
82 </ngb-panel>
83
84 <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct">
85 <ng-template ngbPanelContent>
86 <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div>
87 </ng-template>
88 </ngb-panel>
89
90 <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms">
91 <ng-template ngbPanelContent>
92 <div class="block" [innerHTML]="aboutHtml.terms"></div>
93 </ng-template>
94 </ngb-panel>
95 </ng-container>
96 </ngb-accordion>
97 </div>
98 </div> 53 </div>
99 54
100</div> 55</div>
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss
index 4221354ee..d22d58c4a 100644
--- a/client/src/app/+signup/+register/register.component.scss
+++ b/client/src/app/+signup/+register/register.component.scss
@@ -1,9 +1,5 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3@import "./_bootstrap-variables";
4
5@import '~bootstrap/scss/functions';
6@import '~bootstrap/scss/variables';
7 3
8.alert { 4.alert {
9 font-size: 15px; 5 font-size: 15px;
@@ -12,44 +8,20 @@
12 8
13.wrapper { 9.wrapper {
14 display: flex; 10 display: flex;
15 justify-content: space-between; 11 flex-direction: column;
16 flex-wrap: wrap;
17
18 & > div {
19 margin-bottom: 40px;
20 12
21 &.register-form { 13 .register-form {
22 width: 450px; 14 max-width: 600px;
15 align-self: center;
16 }
23 17
24 @media screen and (max-width: $mobile-view) { 18 .register-form,
25 width: 100%; 19 .instance-information {
26 } 20 width: 100%;
27 } 21 }
28 22
29 &.instance-information { 23 .instance-information {
30 width: 600px; 24 margin-bottom: 15px;
31 margin-bottom: 40px;
32
33 .block {
34 font-size: 15px;
35 margin-bottom: 15px;
36 padding: 0 $btn-padding-x;
37 }
38
39 @media screen and (max-width: 1500px) {
40 width: 450px;
41 }
42 @media screen and (max-width: $mobile-view) {
43 width: 100%;
44 }
45
46 ngb-accordion ::ng-deep {
47 .btn {
48 font-weight: $font-semibold !important;
49 color: pvar(--mainForegroundColor) !important;
50 }
51 }
52 }
53 } 25 }
54} 26}
55 27
@@ -58,15 +30,19 @@
58} 30}
59 31
60.input-group { 32.input-group {
61 @include peertube-input-group(400px); 33 @include peertube-input-group(100%);
62} 34}
63 35
64.input-group-append { 36.input-group-append {
65 height: 30px; 37 height: 30px;
66} 38}
67 39
40.form-group-terms {
41 width: 100% !important;
42}
43
68input:not([type=submit]) { 44input:not([type=submit]) {
69 @include peertube-input-text(400px); 45 @include peertube-input-text(100%);
70 display: block; 46 display: block;
71 47
72 &#username, 48 &#username,
@@ -76,19 +52,10 @@ input:not([type=submit]) {
76 } 52 }
77} 53}
78 54
79@media screen and (max-width: $mobile-view) {
80 .form-group-terms,
81 .input-group,
82 input:not([type=submit]) {
83 width: 100%;
84 }
85}
86
87input[type=submit], 55input[type=submit],
88button { 56button {
89 @include peertube-button; 57 @include peertube-button;
90 @include orange-button; 58 @include orange-button;
91
92} 59}
93 60
94.name-information { 61.name-information {
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index 5b6762631..b1f5d8e93 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -1,12 +1,12 @@
1import { Component, OnInit, ViewChild } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { FormGroup } from '@angular/forms' 2import { FormGroup } from '@angular/forms'
3import { ActivatedRoute } from '@angular/router' 3import { ActivatedRoute } from '@angular/router'
4import { AuthService, Notifier, UserService } from '@app/core' 4import { AuthService, UserService } from '@app/core'
5import { HooksService } from '@app/core/plugins/hooks.service' 5import { HooksService } from '@app/core/plugins/hooks.service'
6import { InstanceService } from '@app/shared/shared-instance'
7import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' 6import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
8import { UserRegister } from '@shared/models' 7import { UserRegister } from '@shared/models'
9import { About, ServerConfig } from '@shared/models/server' 8import { ServerConfig } from '@shared/models/server'
9import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
10 10
11@Component({ 11@Component({
12 selector: 'my-register', 12 selector: 'my-register',
@@ -14,35 +14,39 @@ import { About, ServerConfig } from '@shared/models/server'
14 styleUrls: [ './register.component.scss' ] 14 styleUrls: [ './register.component.scss' ]
15}) 15})
16export class RegisterComponent implements OnInit { 16export class RegisterComponent implements OnInit {
17 @ViewChild('accordion', { static: true }) accordion: NgbAccordion 17 accordion: NgbAccordion
18
19 info: string = null 18 info: string = null
20 error: string = null 19 error: string = null
21 success: string = null 20 success: string = null
22 signupDone = false 21 signupDone = false
23 22
24 about: About
25 aboutHtml = {
26 description: '',
27 terms: '',
28 codeOfConduct: '',
29 moderationInformation: '',
30 administrator: ''
31 }
32
33 videoUploadDisabled: boolean 23 videoUploadDisabled: boolean
34 24
25 formStepTerms: FormGroup
35 formStepUser: FormGroup 26 formStepUser: FormGroup
36 formStepChannel: FormGroup 27 formStepChannel: FormGroup
37 28
29 aboutHtml = {
30 codeOfConduct: ''
31 }
32
33 instanceInformationPanels = {
34 codeOfConduct: true,
35 terms: true,
36 administrators: false,
37 features: false,
38 moderation: false
39 }
40
41 defaultNextStepButtonLabel = $localize`Next`
42 stepUserButtonLabel = this.defaultNextStepButtonLabel
43
38 private serverConfig: ServerConfig 44 private serverConfig: ServerConfig
39 45
40 constructor ( 46 constructor (
41 private route: ActivatedRoute, 47 private route: ActivatedRoute,
42 private authService: AuthService, 48 private authService: AuthService,
43 private notifier: Notifier,
44 private userService: UserService, 49 private userService: UserService,
45 private instanceService: InstanceService,
46 private hooks: HooksService 50 private hooks: HooksService
47 ) { 51 ) {
48 } 52 }
@@ -55,19 +59,12 @@ export class RegisterComponent implements OnInit {
55 this.serverConfig = this.route.snapshot.data.serverConfig 59 this.serverConfig = this.route.snapshot.data.serverConfig
56 60
57 this.videoUploadDisabled = this.serverConfig.user.videoQuota === 0 61 this.videoUploadDisabled = this.serverConfig.user.videoQuota === 0
58 62 this.stepUserButtonLabel = this.videoUploadDisabled
59 this.instanceService.getAbout() 63 ? $localize`Signup`
60 .subscribe( 64 : this.defaultNextStepButtonLabel
61 async about => {
62 this.about = about
63
64 this.aboutHtml = await this.instanceService.buildHtml(about)
65 },
66
67 err => this.notifier.error(err.message)
68 )
69 65
70 this.hooks.runAction('action:signup.register.init', 'signup') 66 this.hooks.runAction('action:signup.register.init', 'signup')
67
71 } 68 }
72 69
73 hasSameChannelAndAccountNames () { 70 hasSameChannelAndAccountNames () {
@@ -86,6 +83,10 @@ export class RegisterComponent implements OnInit {
86 return this.formStepChannel.value['name'] 83 return this.formStepChannel.value['name']
87 } 84 }
88 85
86 onTermsFormBuilt (form: FormGroup) {
87 this.formStepTerms = form
88 }
89
89 onUserFormBuilt (form: FormGroup) { 90 onUserFormBuilt (form: FormGroup) {
90 this.formStepUser = form 91 this.formStepUser = form
91 } 92 }
@@ -102,6 +103,11 @@ export class RegisterComponent implements OnInit {
102 if (this.accordion) this.accordion.toggle('code-of-conduct') 103 if (this.accordion) this.accordion.toggle('code-of-conduct')
103 } 104 }
104 105
106 onInstanceAboutAccordionInit (instanceAboutAccordion: InstanceAboutAccordionComponent) {
107 this.accordion = instanceAboutAccordion.accordion
108 this.aboutHtml = instanceAboutAccordion.aboutHtml
109 }
110
105 async signup () { 111 async signup () {
106 this.error = null 112 this.error = null
107 113
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts
index 608045f58..c36da53d5 100644
--- a/client/src/app/+signup/+register/register.module.ts
+++ b/client/src/app/+signup/+register/register.module.ts
@@ -2,10 +2,10 @@ import { CdkStepperModule } from '@angular/cdk/stepper'
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' 3import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module'
4import { SharedInstanceModule } from '@app/shared/shared-instance' 4import { SharedInstanceModule } from '@app/shared/shared-instance'
5import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
6import { CustomStepperComponent } from './custom-stepper.component' 5import { CustomStepperComponent } from './custom-stepper.component'
7import { RegisterRoutingModule } from './register-routing.module' 6import { RegisterRoutingModule } from './register-routing.module'
8import { RegisterStepChannelComponent } from './register-step-channel.component' 7import { RegisterStepChannelComponent } from './register-step-channel.component'
8import { RegisterStepTermsComponent } from './register-step-terms.component'
9import { RegisterStepUserComponent } from './register-step-user.component' 9import { RegisterStepUserComponent } from './register-step-user.component'
10import { RegisterComponent } from './register.component' 10import { RegisterComponent } from './register.component'
11 11
@@ -14,7 +14,6 @@ import { RegisterComponent } from './register.component'
14 RegisterRoutingModule, 14 RegisterRoutingModule,
15 15
16 CdkStepperModule, 16 CdkStepperModule,
17 NgbAccordionModule,
18 17
19 SignupSharedModule, 18 SignupSharedModule,
20 19
@@ -25,6 +24,7 @@ import { RegisterComponent } from './register.component'
25 RegisterComponent, 24 RegisterComponent,
26 CustomStepperComponent, 25 CustomStepperComponent,
27 RegisterStepChannelComponent, 26 RegisterStepChannelComponent,
27 RegisterStepTermsComponent,
28 RegisterStepUserComponent 28 RegisterStepUserComponent
29 ], 29 ],
30 30
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts
index 115930146..a27290035 100644
--- a/client/src/app/shared/form-validators/user-validators.ts
+++ b/client/src/app/shared/form-validators/user-validators.ts
@@ -115,9 +115,7 @@ export const USER_DESCRIPTION_VALIDATOR: BuildFormValidator = {
115} 115}
116 116
117export const USER_TERMS_VALIDATOR: BuildFormValidator = { 117export const USER_TERMS_VALIDATOR: BuildFormValidator = {
118 VALIDATORS: [ 118 VALIDATORS: [ Validators.requiredTrue ],
119 Validators.requiredTrue
120 ],
121 MESSAGES: { 119 MESSAGES: {
122 'required': $localize`You must agree with the instance terms in order to register on it.` 120 'required': $localize`You must agree with the instance terms in order to register on it.`
123 } 121 }
diff --git a/client/src/app/shared/shared-instance/index.ts b/client/src/app/shared/shared-instance/index.ts
index 1aeed357e..d7172f7b6 100644
--- a/client/src/app/shared/shared-instance/index.ts
+++ b/client/src/app/shared/shared-instance/index.ts
@@ -1,4 +1,5 @@
1export * from './feature-boolean.component' 1export * from './feature-boolean.component'
2export * from './instance-about-accordion.component'
2export * from './instance-features-table.component' 3export * from './instance-features-table.component'
3export * from './instance-follow.service' 4export * from './instance-follow.service'
4export * from './instance-statistics.component' 5export * from './instance-statistics.component'
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
new file mode 100644
index 000000000..e91e44656
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.html
@@ -0,0 +1,53 @@
1<h2 class="instance-name">{{ about?.instance.name }}</h2>
2
3<div class="instance-short-description">{{ about?.instance.shortDescription }}</div>
4
5<ngb-accordion #accordion="ngbAccordion" [closeOthers]="true">
6 <ngb-panel *ngIf="panels.features" id="instance-features" i18n-title title="Features found on this instance">
7 <ng-template ngbPanelContent>
8 <my-instance-features-table></my-instance-features-table>
9 </ng-template>
10 </ngb-panel>
11
12 <ng-container *ngIf="about">
13 <ngb-panel
14 *ngIf="getAdministratorsPanel()"
15 id="admin-sustainability" i18n-title title="Administrators & Sustainability"
16 >
17 <ng-template ngbPanelContent>
18 <div class="block">
19 <strong i18n>Who are we?</strong>
20 <div [innerHTML]="aboutHtml.administrator"></div>
21 </div>
22
23 <div class="block">
24 <strong i18n>How long do we plan to maintain this instance?</strong>
25 <div [innerHTML]="about.instance.maintenanceLifetime"></div>
26 </div>
27
28 <div class="block">
29 <strong i18n>How will we finance this instance?</strong>
30 <div [innerHTML]="about.instance.businessModel"></div>
31 </div>
32 </ng-template>
33 </ngb-panel>
34
35 <ngb-panel *ngIf="termsPanel" id="terms" i18n-title title="Terms">
36 <ng-template ngbPanelContent>
37 <div class="block" [innerHTML]="aboutHtml.terms"></div>
38 </ng-template>
39 </ngb-panel>
40
41 <ngb-panel *ngIf="moderationPanel" id="moderation-information" i18n-title title="Moderation information">
42 <ng-template ngbPanelContent>
43 <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div>
44 </ng-template>
45 </ngb-panel>
46
47 <ngb-panel *ngIf="codeOfConductPanel" id="code-of-conduct" i18n-title title="Code of conduct">
48 <ng-template ngbPanelContent>
49 <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div>
50 </ng-template>
51 </ngb-panel>
52 </ng-container>
53</ngb-accordion>
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
new file mode 100644
index 000000000..275600d60
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
@@ -0,0 +1,46 @@
1@import '_variables';
2@import '_mixins';
3@import "./_bootstrap-variables";
4
5@import '~bootstrap/scss/functions';
6@import '~bootstrap/scss/variables';
7
8.instance-name {
9 line-height: 1.7rem;
10}
11
12.instance-short-description {
13 @include ellipsis-multiline(1rem, 3);
14
15 margin-top: 20px;
16 margin-bottom: 20px;
17}
18
19.block {
20 font-size: 15px;
21 margin-bottom: 15px;
22 padding: 0 $btn-padding-x;
23}
24
25ngb-accordion ::ng-deep {
26 .card {
27 border-color: var(--mainBackgroundColor);
28
29 .card-header {
30 background-color: unset;
31 padding: 0;
32
33 & + .collapse.show {
34 background-color: var(--submenuColor);
35 }
36 }
37 }
38
39 .btn {
40 @include peertube-button;
41 @include grey-button;
42
43 border-radius: unset;
44 width: 100%;
45 }
46}
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
new file mode 100644
index 000000000..ab75c31ae
--- /dev/null
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.ts
@@ -0,0 +1,71 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
3import { InstanceService } from './instance.service'
4import { Notifier } from '@app/core'
5import { About } from '@shared/models/server'
6
7@Component({
8 selector: 'my-instance-about-accordion',
9 templateUrl: './instance-about-accordion.component.html',
10 styleUrls: ['./instance-about-accordion.component.scss']
11})
12export class InstanceAboutAccordionComponent implements OnInit {
13 @ViewChild('accordion', { static: true }) accordion: NgbAccordion
14 @Output() init: EventEmitter<InstanceAboutAccordionComponent> = new EventEmitter<InstanceAboutAccordionComponent>()
15
16 @Input() panels = {
17 features: true,
18 administrators: true,
19 moderation: true,
20 codeOfConduct: true,
21 terms: true
22 }
23
24 about: About
25 aboutHtml = {
26 description: '',
27 terms: '',
28 codeOfConduct: '',
29 moderationInformation: '',
30 administrator: ''
31 }
32
33 constructor (
34 private instanceService: InstanceService,
35 private notifier: Notifier
36 ) { }
37
38 ngOnInit (): void {
39 this.instanceService.getAbout()
40 .subscribe(
41 async about => {
42 this.about = about
43
44 this.aboutHtml = await this.instanceService.buildHtml(about)
45
46 this.init.emit(this)
47 },
48
49 err => this.notifier.error(err.message)
50 )
51 }
52
53 getAdministratorsPanel () {
54 if (!this.about) return false
55 if (!this.panels.administrators) return false
56
57 return !!(this.aboutHtml?.administrator || this.about?.instance.maintenanceLifetime || this.about?.instance.businessModel)
58 }
59
60 get moderationPanel () {
61 return this.panels.moderation && !!this.aboutHtml.moderationInformation
62 }
63
64 get codeOfConductPanel () {
65 return this.panels.codeOfConduct && !!this.aboutHtml.codeOfConduct
66 }
67
68 get termsPanel () {
69 return this.panels.terms && !!this.aboutHtml.terms
70 }
71}
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts
index b75ad1a12..13c681ab8 100644
--- a/client/src/app/shared/shared-instance/shared-instance.module.ts
+++ b/client/src/app/shared/shared-instance/shared-instance.module.ts
@@ -1,7 +1,9 @@
1 1
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
3import { SharedMainModule } from '../shared-main/shared-main.module' 4import { SharedMainModule } from '../shared-main/shared-main.module'
4import { FeatureBooleanComponent } from './feature-boolean.component' 5import { FeatureBooleanComponent } from './feature-boolean.component'
6import { InstanceAboutAccordionComponent } from './instance-about-accordion.component'
5import { InstanceFeaturesTableComponent } from './instance-features-table.component' 7import { InstanceFeaturesTableComponent } from './instance-features-table.component'
6import { InstanceFollowService } from './instance-follow.service' 8import { InstanceFollowService } from './instance-follow.service'
7import { InstanceStatisticsComponent } from './instance-statistics.component' 9import { InstanceStatisticsComponent } from './instance-statistics.component'
@@ -9,17 +11,20 @@ import { InstanceService } from './instance.service'
9 11
10@NgModule({ 12@NgModule({
11 imports: [ 13 imports: [
12 SharedMainModule 14 SharedMainModule,
15 NgbAccordionModule
13 ], 16 ],
14 17
15 declarations: [ 18 declarations: [
16 FeatureBooleanComponent, 19 FeatureBooleanComponent,
20 InstanceAboutAccordionComponent,
17 InstanceFeaturesTableComponent, 21 InstanceFeaturesTableComponent,
18 InstanceStatisticsComponent 22 InstanceStatisticsComponent
19 ], 23 ],
20 24
21 exports: [ 25 exports: [
22 FeatureBooleanComponent, 26 FeatureBooleanComponent,
27 InstanceAboutAccordionComponent,
23 InstanceFeaturesTableComponent, 28 InstanceFeaturesTableComponent,
24 InstanceStatisticsComponent 29 InstanceStatisticsComponent
25 ], 30 ],