diff options
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 | ||
4 | label { | 7 | label { |
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 | |||
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | 4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance' | ||
6 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' | 7 | import { LOGIN_PASSWORD_VALIDATOR, LOGIN_USERNAME_VALIDATOR } from '@app/shared/form-validators/login-validators' |
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 8 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
8 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 9 | import { NgbAccordion, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
9 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | 10 | import { 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 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { SharedFormModule } from '@app/shared/shared-forms' | 2 | import { SharedFormModule } from '@app/shared/shared-forms' |
3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' | 3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' |
4 | import { SharedInstanceModule } from '@app/shared/shared-instance' | ||
4 | import { SharedMainModule } from '@app/shared/shared-main' | 5 | import { SharedMainModule } from '@app/shared/shared-main' |
5 | import { LoginRoutingModule } from './login-routing.module' | 6 | import { LoginRoutingModule } from './login-routing.module' |
6 | import { LoginComponent } from './login.component' | 7 | import { 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 | |||
7 | header { | 13 | header { |
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 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||
2 | import { FormGroup } from '@angular/forms' | ||
3 | import { | ||
4 | USER_TERMS_VALIDATOR | ||
5 | } from '@app/shared/form-validators/user-validators' | ||
6 | import { 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 | }) | ||
13 | export 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' |
13 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 12 | import { 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 | }) |
20 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | 19 | export 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 | |||
68 | input:not([type=submit]) { | 44 | input: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 | |||
87 | input[type=submit], | 55 | input[type=submit], |
88 | button { | 56 | button { |
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 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { FormGroup } from '@angular/forms' | 2 | import { FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { AuthService, Notifier, UserService } from '@app/core' | 4 | import { AuthService, UserService } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { InstanceService } from '@app/shared/shared-instance' | ||
7 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' |
8 | import { UserRegister } from '@shared/models' | 7 | import { UserRegister } from '@shared/models' |
9 | import { About, ServerConfig } from '@shared/models/server' | 8 | import { ServerConfig } from '@shared/models/server' |
9 | import { 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 | }) |
16 | export class RegisterComponent implements OnInit { | 16 | export 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' | |||
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | 3 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' |
4 | import { SharedInstanceModule } from '@app/shared/shared-instance' | 4 | import { SharedInstanceModule } from '@app/shared/shared-instance' |
5 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | ||
6 | import { CustomStepperComponent } from './custom-stepper.component' | 5 | import { CustomStepperComponent } from './custom-stepper.component' |
7 | import { RegisterRoutingModule } from './register-routing.module' | 6 | import { RegisterRoutingModule } from './register-routing.module' |
8 | import { RegisterStepChannelComponent } from './register-step-channel.component' | 7 | import { RegisterStepChannelComponent } from './register-step-channel.component' |
8 | import { RegisterStepTermsComponent } from './register-step-terms.component' | ||
9 | import { RegisterStepUserComponent } from './register-step-user.component' | 9 | import { RegisterStepUserComponent } from './register-step-user.component' |
10 | import { RegisterComponent } from './register.component' | 10 | import { 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 | ||
117 | export const USER_TERMS_VALIDATOR: BuildFormValidator = { | 117 | export 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 @@ | |||
1 | export * from './feature-boolean.component' | 1 | export * from './feature-boolean.component' |
2 | export * from './instance-about-accordion.component' | ||
2 | export * from './instance-features-table.component' | 3 | export * from './instance-features-table.component' |
3 | export * from './instance-follow.service' | 4 | export * from './instance-follow.service' |
4 | export * from './instance-statistics.component' | 5 | export * 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 | |||
25 | ngb-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 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | ||
2 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' | ||
3 | import { InstanceService } from './instance.service' | ||
4 | import { Notifier } from '@app/core' | ||
5 | import { 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 | }) | ||
12 | export 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 | ||
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | ||
3 | import { SharedMainModule } from '../shared-main/shared-main.module' | 4 | import { SharedMainModule } from '../shared-main/shared-main.module' |
4 | import { FeatureBooleanComponent } from './feature-boolean.component' | 5 | import { FeatureBooleanComponent } from './feature-boolean.component' |
6 | import { InstanceAboutAccordionComponent } from './instance-about-accordion.component' | ||
5 | import { InstanceFeaturesTableComponent } from './instance-features-table.component' | 7 | import { InstanceFeaturesTableComponent } from './instance-features-table.component' |
6 | import { InstanceFollowService } from './instance-follow.service' | 8 | import { InstanceFollowService } from './instance-follow.service' |
7 | import { InstanceStatisticsComponent } from './instance-statistics.component' | 9 | import { 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 | ], |