aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMs Kimsible <1877318+kimsible@users.noreply.github.com>2021-08-25 11:38:10 +0200
committerGitHub <noreply@github.com>2021-08-25 11:38:10 +0200
commit4e1592daa41f81667f914f37d36795e8c6c046c3 (patch)
tree8bed3af237b6d5d4da08af989c3824a168e6f3b6
parent644800ef5588e08da2a8227f6d72751d3dca85db (diff)
downloadPeerTube-4e1592daa41f81667f914f37d36795e8c6c046c3.tar.gz
PeerTube-4e1592daa41f81667f914f37d36795e8c6c046c3.tar.zst
PeerTube-4e1592daa41f81667f914f37d36795e8c6c046c3.zip
Alert user for low quota and video auto-block on upload page (#4336)
* Replace wording of instance contact * Add contact-us button to no-quota alert on upload page * Add alert for accounts with auto-blocked videos on upload page * Add alert for accounts without enough quota + refacto on upload page * Using ng-container and ng-template * Add alert for daily quota * Add hook filter for upload page alert messages * Add instance name as subtitle in contact modal * Fix eslint max-len on string * Fix missing word in quota left daily message - upload page Co-authored-by: Kimsible <kimsible@users.noreply.github.com>
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.html2
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.html2
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.scss6
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.html41
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.scss21
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.ts36
-rw-r--r--client/src/app/core/users/user.model.ts26
-rw-r--r--server/models/user/user.ts2
-rw-r--r--server/tests/api/users/users.ts2
-rw-r--r--shared/models/plugins/client/client-hook.model.ts3
10 files changed, 121 insertions, 20 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html
index 436d486ab..1026c4e0d 100644
--- a/client/src/app/+about/about-instance/about-instance.component.html
+++ b/client/src/app/+about/about-instance/about-instance.component.html
@@ -4,7 +4,7 @@
4 <div class="about-instance-title"> 4 <div class="about-instance-title">
5 <h1 i18n class="title">About {{ instanceName }}</h1> 5 <h1 i18n class="title">About {{ instanceName }}</h1>
6 6
7 <a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="contact-admin">Contact administrator</a> 7 <a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="contact-admin">Contact us</a>
8 </div> 8 </div>
9 9
10 <div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0"> 10 <div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0">
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html
index 8b6b707af..ed027af44 100644
--- a/client/src/app/+about/about-instance/contact-admin-modal.component.html
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.html
@@ -1,6 +1,6 @@
1<ng-template #modal> 1<ng-template #modal>
2 <div class="modal-header"> 2 <div class="modal-header">
3 <h1 i18n class="modal-title">Contact {{ instanceName }} administrator</h1> 3 <h1 i18n class="modal-title">Contact the administrator(s)<p class="modal-subtitle">{{ instanceName }}</p></h1>
4 <my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon> 4 <my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon>
5 </div> 5 </div>
6 6
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss
index c0b451b4e..e143a9dc6 100644
--- a/client/src/app/+about/about-instance/contact-admin-modal.component.scss
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.scss
@@ -1,6 +1,12 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.modal-subtitle {
5 font-size: 16px;
6 line-height: 1rem;
7 margin-bottom: 0;
8}
9
4.modal-body { 10.modal-body {
5 text-align: left; 11 text-align: left;
6} 12}
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html
index ac75d9ff8..b056c6e7a 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.html
+++ b/client/src/app/+videos/+video-edit/video-add.component.html
@@ -1,18 +1,43 @@
1<div *ngIf="user.isUploadDisabled()" class="no-upload"> 1<ng-template #AlertButtons>
2 <div class="alert alert-warning"> 2 <a i18n routerLink="/about/instance" *ngIf="!isContactFormEnabled" class="about-link">Read instance rules for help</a>
3 <div i18n>Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.</div> 3 <a i18n routerLink="/about/contact" *ngIf="isContactFormEnabled" class="contact-link">Contact us</a>
4 <a i18n routerLink="/about/instance" class="about-link">Read instance rules for help</a> 4</ng-template>
5
6<ng-container *ngIf="user.isUploadDisabled()">
7 <div class="upload-message upload-disabled alert alert-warning">
8 <div>{{ uploadMessages.noQuota }}</div>
9 <ng-template [ngTemplateOutlet]="AlertButtons"></ng-template>
5 </div> 10 </div>
6 <img src="/client/assets/images/mascot/defeated.svg" alt="defeated mascot">
7</div>
8 11
9<div *ngIf="!user.isUploadDisabled()" class="margin-content"> 12 <div class="upload-image">
10 <div class="alert alert-warning" *ngIf="isRootUser()" i18n> 13 <img src="/client/assets/images/mascot/defeated.svg" alt="defeated mascot">
14 </div>
15</ng-container>
16
17<ng-container *ngIf="!user.isUploadDisabled()">
18 <div *ngIf="user.isAutoBlocked()" class="upload-message auto-blocked alert alert-warning">
19 <div>{{ uploadMessages.autoBlock }}</div>
20 <ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!user.hasNoQuotaLeft() && !user.hasNoQuotaLeftDaily()"></ng-template>
21 </div>
22
23 <div *ngIf="user.hasNoQuotaLeft()" class="upload-message quota-daily-left alert alert-warning">
24 <div>{{ uploadMessages.quotaLeftDaily }}</div>
25 <ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!user.hasNoQuotaLeft()"></ng-template>
26 </div>
27
28 <div *ngIf="user.hasNoQuotaLeft()" class="upload-message quota-left alert alert-warning">
29 <div>{{ uploadMessages.quotaLeft }}</div>
30 <ng-template [ngTemplateOutlet]="AlertButtons"></ng-template>
31 </div>
32
33 <div *ngIf="isRootUser()" class="upload-message root-user alert alert-warning" i18n>
11 We recommend you to not use the <strong>root</strong> user to publish your videos, since it's the super-admin account of your instance. 34 We recommend you to not use the <strong>root</strong> user to publish your videos, since it's the super-admin account of your instance.
12 <br /> 35 <br />
13 Instead, <a routerLink="/admin/users">create a dedicated account</a> to upload your videos. 36 Instead, <a routerLink="/admin/users">create a dedicated account</a> to upload your videos.
14 </div> 37 </div>
38</ng-container>
15 39
40<div *ngIf="!user.isUploadDisabled()" class="margin-content">
16 <my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota> 41 <my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota>
17 42
18 <div class="title-page title-page-single" *ngIf="isInSecondStep()"> 43 <div class="title-page title-page-single" *ngIf="isInSecondStep()">
diff --git a/client/src/app/+videos/+video-edit/video-add.component.scss b/client/src/app/+videos/+video-edit/video-add.component.scss
index dea6fde36..26be86d29 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.scss
+++ b/client/src/app/+videos/+video-edit/video-add.component.scss
@@ -6,18 +6,29 @@ $border-type: solid;
6$border-color: #EAEAEA; 6$border-color: #EAEAEA;
7$nav-link-height: 40px; 7$nav-link-height: 40px;
8 8
9.no-upload { 9.upload-message {
10 height: 100%;
11 width: 100%; 10 width: 100%;
12 text-align: center; 11 text-align: center;
12 font-size: 15px;
13 margin-bottom: 0;
14
15 &:last-child {
16 margin-bottom: 1rem;
17 }
13 18
14 .about-link { 19 .about-link,
20 .contact-link {
15 @include peertube-button-link; 21 @include peertube-button-link;
16 @include orange-button; 22 @include orange-button;
17 23
18 height: fit-content; 24 height: fit-content;
19 margin-top: 10px; 25 margin-top: 10px;
20 } 26 }
27}
28
29.upload-image {
30 width: 100%;
31 text-align: center;
21 32
22 img { 33 img {
23 margin-top: 10px; 34 margin-top: 10px;
@@ -38,10 +49,6 @@ $nav-link-height: 40px;
38 padding-top: 20px; 49 padding-top: 20px;
39} 50}
40 51
41.alert {
42 font-size: 15px;
43}
44
45::ng-deep .video-add-nav { 52::ng-deep .video-add-nav {
46 border-bottom: $border-width $border-type $border-color; 53 border-bottom: $border-width $border-type $border-color;
47 margin: 20px 0 0 !important; 54 margin: 20px 0 0 !important;
diff --git a/client/src/app/+videos/+video-edit/video-add.component.ts b/client/src/app/+videos/+video-edit/video-add.component.ts
index 8606b8222..1443c64af 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add.component.ts
@@ -1,6 +1,6 @@
1import { Component, HostListener, OnInit, ViewChild } from '@angular/core' 1import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core' 3import { AuthService, AuthUser, CanComponentDeactivate, HooksService, ServerService } from '@app/core'
4import { HTMLServerConfig } from '@shared/models' 4import { HTMLServerConfig } from '@shared/models'
5import { VideoEditType } from './shared/video-edit.type' 5import { VideoEditType } from './shared/video-edit.type'
6import { VideoGoLiveComponent } from './video-add-components/video-go-live.component' 6import { VideoGoLiveComponent } from './video-add-components/video-go-live.component'
@@ -26,15 +26,27 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
26 26
27 activeNav: string 27 activeNav: string
28 28
29 uploadMessages: {
30 noQuota: string
31 autoBlock: string
32 quotaLeftDaily: string
33 quotaLeft: string
34 }
35
29 private serverConfig: HTMLServerConfig 36 private serverConfig: HTMLServerConfig
30 37
31 constructor ( 38 constructor (
32 private auth: AuthService, 39 private auth: AuthService,
40 private hooks: HooksService,
33 private serverService: ServerService, 41 private serverService: ServerService,
34 private route: ActivatedRoute, 42 private route: ActivatedRoute,
35 private router: Router 43 private router: Router
36 ) {} 44 ) {}
37 45
46 get isContactFormEnabled () {
47 return this.serverConfig.email.enabled && this.serverConfig.contactForm.enabled
48 }
49
38 get userInformationLoaded () { 50 get userInformationLoaded () {
39 return this.auth.userInformationLoaded 51 return this.auth.userInformationLoaded
40 } 52 }
@@ -49,6 +61,28 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
49 if (this.route.snapshot.fragment) { 61 if (this.route.snapshot.fragment) {
50 this.onNavChange(this.route.snapshot.fragment) 62 this.onNavChange(this.route.snapshot.fragment)
51 } 63 }
64
65 this.buildUploadMessages()
66 }
67
68 private async buildUploadMessages () {
69 // eslint-disable-next-line max-len
70 const noQuota = $localize`Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.`
71 // eslint-disable-next-line max-len
72 const autoBlock = $localize`Uploaded videos are reviewed before publishing for your account. If you want to add videos without moderation review, an admin must turn off your videos auto-block.`
73 // eslint-disable-next-line max-len
74 const quotaLeftDaily = $localize`Your daily video quota is insufficient. If you want to add more videos, you must wait for 24 hours or an admin must increase your daily quota.`
75 // eslint-disable-next-line max-len
76 const quotaLeft = $localize`Your video quota is insufficient. If you want to add more videos, an admin must increase your quota.`
77
78 const uploadMessages = {
79 noQuota,
80 autoBlock,
81 quotaLeftDaily,
82 quotaLeft
83 }
84
85 this.uploadMessages = await this.hooks.wrapObject(uploadMessages, 'common', 'filter:upload-page.alert-messages.edit.result')
52 } 86 }
53 87
54 onNavChange (newActiveNav: string) { 88 onNavChange (newActiveNav: string) {
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts
index 7d03e1c40..5e1fb1c8d 100644
--- a/client/src/app/core/users/user.model.ts
+++ b/client/src/app/core/users/user.model.ts
@@ -133,4 +133,30 @@ export class User implements UserServerModel {
133 isUploadDisabled () { 133 isUploadDisabled () {
134 return this.videoQuota === 0 || this.videoQuotaDaily === 0 134 return this.videoQuota === 0 || this.videoQuotaDaily === 0
135 } 135 }
136
137 isAutoBlocked () {
138 return this.role === UserRole.USER && this.adminFlags !== UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST
139 }
140
141 hasNoQuotaLeft () {
142 // unlimited videoQuota
143 if (this.videoQuota === -1) return false
144
145 // no more videoQuota
146 if (!this.videoQuotaUsed) return true
147
148 // videoQuota left lower than 10%
149 return this.videoQuotaUsed > this.videoQuota * 0.9
150 }
151
152 hasNoQuotaLeftDaily () {
153 // unlimited videoQuotaDaily
154 if (this.videoQuotaDaily === -1) return false
155
156 // no more videoQuotaDaily
157 if (!this.videoQuotaUsedDaily) return true
158
159 // videoQuotaDaily left lower than 10%
160 return this.videoQuotaUsedDaily > this.videoQuotaDaily * 0.9
161 }
136} 162}
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index 20696b1f4..069d7266e 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -958,7 +958,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
958 } 958 }
959 959
960 toMeFormattedJSON (this: MMyUserFormattable): MyUser { 960 toMeFormattedJSON (this: MMyUserFormattable): MyUser {
961 const formatted = this.toFormattedJSON() 961 const formatted = this.toFormattedJSON({ withAdminFlags: true })
962 962
963 const specialPlaylists = this.Account.VideoPlaylists 963 const specialPlaylists = this.Account.VideoPlaylists
964 .map(p => ({ id: p.id, name: p.name, type: p.type })) 964 .map(p => ({ id: p.id, name: p.name, type: p.type }))
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 1419ae820..318ff832a 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -290,7 +290,7 @@ describe('Test users', function () {
290 expect(user.account.description).to.be.null 290 expect(user.account.description).to.be.null
291 } 291 }
292 292
293 expect(userMe.adminFlags).to.be.undefined 293 expect(userMe.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
294 expect(userGet.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST) 294 expect(userGet.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
295 295
296 expect(userMe.specialPlaylists).to.have.lengthOf(1) 296 expect(userMe.specialPlaylists).to.have.lengthOf(1)
diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts
index cedd1be61..aafc8c72b 100644
--- a/shared/models/plugins/client/client-hook.model.ts
+++ b/shared/models/plugins/client/client-hook.model.ts
@@ -58,6 +58,9 @@ export const clientFilterHookObject = {
58 // Filter left menu links 58 // Filter left menu links
59 'filter:left-menu.links.create.result': true, 59 'filter:left-menu.links.create.result': true,
60 60
61 // Filter upload page alert messages
62 'filter:upload-page.alert-messages.edit.result': true,
63
61 // Filter videojs options built for PeerTube player 64 // Filter videojs options built for PeerTube player
62 'filter:internal.player.videojs.options.result': true 65 'filter:internal.player.videojs.options.result': true
63} 66}