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>
This commit is contained in:
parent
644800ef55
commit
4e1592daa4
|
@ -4,7 +4,7 @@
|
||||||
<div class="about-instance-title">
|
<div class="about-instance-title">
|
||||||
<h1 i18n class="title">About {{ instanceName }}</h1>
|
<h1 i18n class="title">About {{ instanceName }}</h1>
|
||||||
|
|
||||||
<a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="contact-admin">Contact administrator</a>
|
<a routerLink="/about/contact" i18n *ngIf="isContactFormEnabled" class="contact-admin">Contact us</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0">
|
<div class="instance-badges" *ngIf="categories.length !== 0 || languages.length !== 0">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 i18n class="modal-title">Contact {{ instanceName }} administrator</h1>
|
<h1 i18n class="modal-title">Contact the administrator(s)<p class="modal-subtitle">{{ instanceName }}</p></h1>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon>
|
<my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
@use '_variables' as *;
|
@use '_variables' as *;
|
||||||
@use '_mixins' as *;
|
@use '_mixins' as *;
|
||||||
|
|
||||||
|
.modal-subtitle {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,43 @@
|
||||||
<div *ngIf="user.isUploadDisabled()" class="no-upload">
|
<ng-template #AlertButtons>
|
||||||
<div class="alert alert-warning">
|
<a i18n routerLink="/about/instance" *ngIf="!isContactFormEnabled" class="about-link">Read instance rules for help</a>
|
||||||
<div i18n>Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.</div>
|
<a i18n routerLink="/about/contact" *ngIf="isContactFormEnabled" class="contact-link">Contact us</a>
|
||||||
<a i18n routerLink="/about/instance" class="about-link">Read instance rules for help</a>
|
</ng-template>
|
||||||
</div>
|
|
||||||
<img src="/client/assets/images/mascot/defeated.svg" alt="defeated mascot">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="!user.isUploadDisabled()" class="margin-content">
|
<ng-container *ngIf="user.isUploadDisabled()">
|
||||||
<div class="alert alert-warning" *ngIf="isRootUser()" i18n>
|
<div class="upload-message upload-disabled alert alert-warning">
|
||||||
|
<div>{{ uploadMessages.noQuota }}</div>
|
||||||
|
<ng-template [ngTemplateOutlet]="AlertButtons"></ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-image">
|
||||||
|
<img src="/client/assets/images/mascot/defeated.svg" alt="defeated mascot">
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!user.isUploadDisabled()">
|
||||||
|
<div *ngIf="user.isAutoBlocked()" class="upload-message auto-blocked alert alert-warning">
|
||||||
|
<div>{{ uploadMessages.autoBlock }}</div>
|
||||||
|
<ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!user.hasNoQuotaLeft() && !user.hasNoQuotaLeftDaily()"></ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="user.hasNoQuotaLeft()" class="upload-message quota-daily-left alert alert-warning">
|
||||||
|
<div>{{ uploadMessages.quotaLeftDaily }}</div>
|
||||||
|
<ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!user.hasNoQuotaLeft()"></ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="user.hasNoQuotaLeft()" class="upload-message quota-left alert alert-warning">
|
||||||
|
<div>{{ uploadMessages.quotaLeft }}</div>
|
||||||
|
<ng-template [ngTemplateOutlet]="AlertButtons"></ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="isRootUser()" class="upload-message root-user alert alert-warning" i18n>
|
||||||
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.
|
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.
|
||||||
<br />
|
<br />
|
||||||
Instead, <a routerLink="/admin/users">create a dedicated account</a> to upload your videos.
|
Instead, <a routerLink="/admin/users">create a dedicated account</a> to upload your videos.
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="!user.isUploadDisabled()" class="margin-content">
|
||||||
<my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota>
|
<my-user-quota *ngIf="!isInSecondStep() || secondStepType === 'go-live'" [user]="user" [userInformationLoaded]="userInformationLoaded"></my-user-quota>
|
||||||
|
|
||||||
<div class="title-page title-page-single" *ngIf="isInSecondStep()">
|
<div class="title-page title-page-single" *ngIf="isInSecondStep()">
|
||||||
|
|
|
@ -6,18 +6,29 @@ $border-type: solid;
|
||||||
$border-color: #EAEAEA;
|
$border-color: #EAEAEA;
|
||||||
$nav-link-height: 40px;
|
$nav-link-height: 40px;
|
||||||
|
|
||||||
.no-upload {
|
.upload-message {
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
.about-link {
|
&:last-child {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-link,
|
||||||
|
.contact-link {
|
||||||
@include peertube-button-link;
|
@include peertube-button-link;
|
||||||
@include orange-button;
|
@include orange-button;
|
||||||
|
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-image {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
@ -38,10 +49,6 @@ $nav-link-height: 40px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep .video-add-nav {
|
::ng-deep .video-add-nav {
|
||||||
border-bottom: $border-width $border-type $border-color;
|
border-bottom: $border-width $border-type $border-color;
|
||||||
margin: 20px 0 0 !important;
|
margin: 20px 0 0 !important;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
|
import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, AuthUser, CanComponentDeactivate, ServerService } from '@app/core'
|
import { AuthService, AuthUser, CanComponentDeactivate, HooksService, ServerService } from '@app/core'
|
||||||
import { HTMLServerConfig } from '@shared/models'
|
import { HTMLServerConfig } from '@shared/models'
|
||||||
import { VideoEditType } from './shared/video-edit.type'
|
import { VideoEditType } from './shared/video-edit.type'
|
||||||
import { VideoGoLiveComponent } from './video-add-components/video-go-live.component'
|
import { VideoGoLiveComponent } from './video-add-components/video-go-live.component'
|
||||||
|
@ -26,15 +26,27 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
|
||||||
|
|
||||||
activeNav: string
|
activeNav: string
|
||||||
|
|
||||||
|
uploadMessages: {
|
||||||
|
noQuota: string
|
||||||
|
autoBlock: string
|
||||||
|
quotaLeftDaily: string
|
||||||
|
quotaLeft: string
|
||||||
|
}
|
||||||
|
|
||||||
private serverConfig: HTMLServerConfig
|
private serverConfig: HTMLServerConfig
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private auth: AuthService,
|
private auth: AuthService,
|
||||||
|
private hooks: HooksService,
|
||||||
private serverService: ServerService,
|
private serverService: ServerService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
get isContactFormEnabled () {
|
||||||
|
return this.serverConfig.email.enabled && this.serverConfig.contactForm.enabled
|
||||||
|
}
|
||||||
|
|
||||||
get userInformationLoaded () {
|
get userInformationLoaded () {
|
||||||
return this.auth.userInformationLoaded
|
return this.auth.userInformationLoaded
|
||||||
}
|
}
|
||||||
|
@ -49,6 +61,28 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
|
||||||
if (this.route.snapshot.fragment) {
|
if (this.route.snapshot.fragment) {
|
||||||
this.onNavChange(this.route.snapshot.fragment)
|
this.onNavChange(this.route.snapshot.fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.buildUploadMessages()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildUploadMessages () {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const noQuota = $localize`Sorry, the upload feature is disabled for your account. If you want to add videos, an admin must unlock your quota.`
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
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.`
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
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.`
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const quotaLeft = $localize`Your video quota is insufficient. If you want to add more videos, an admin must increase your quota.`
|
||||||
|
|
||||||
|
const uploadMessages = {
|
||||||
|
noQuota,
|
||||||
|
autoBlock,
|
||||||
|
quotaLeftDaily,
|
||||||
|
quotaLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uploadMessages = await this.hooks.wrapObject(uploadMessages, 'common', 'filter:upload-page.alert-messages.edit.result')
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavChange (newActiveNav: string) {
|
onNavChange (newActiveNav: string) {
|
||||||
|
|
|
@ -133,4 +133,30 @@ export class User implements UserServerModel {
|
||||||
isUploadDisabled () {
|
isUploadDisabled () {
|
||||||
return this.videoQuota === 0 || this.videoQuotaDaily === 0
|
return this.videoQuota === 0 || this.videoQuotaDaily === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAutoBlocked () {
|
||||||
|
return this.role === UserRole.USER && this.adminFlags !== UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNoQuotaLeft () {
|
||||||
|
// unlimited videoQuota
|
||||||
|
if (this.videoQuota === -1) return false
|
||||||
|
|
||||||
|
// no more videoQuota
|
||||||
|
if (!this.videoQuotaUsed) return true
|
||||||
|
|
||||||
|
// videoQuota left lower than 10%
|
||||||
|
return this.videoQuotaUsed > this.videoQuota * 0.9
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNoQuotaLeftDaily () {
|
||||||
|
// unlimited videoQuotaDaily
|
||||||
|
if (this.videoQuotaDaily === -1) return false
|
||||||
|
|
||||||
|
// no more videoQuotaDaily
|
||||||
|
if (!this.videoQuotaUsedDaily) return true
|
||||||
|
|
||||||
|
// videoQuotaDaily left lower than 10%
|
||||||
|
return this.videoQuotaUsedDaily > this.videoQuotaDaily * 0.9
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -958,7 +958,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
toMeFormattedJSON (this: MMyUserFormattable): MyUser {
|
toMeFormattedJSON (this: MMyUserFormattable): MyUser {
|
||||||
const formatted = this.toFormattedJSON()
|
const formatted = this.toFormattedJSON({ withAdminFlags: true })
|
||||||
|
|
||||||
const specialPlaylists = this.Account.VideoPlaylists
|
const specialPlaylists = this.Account.VideoPlaylists
|
||||||
.map(p => ({ id: p.id, name: p.name, type: p.type }))
|
.map(p => ({ id: p.id, name: p.name, type: p.type }))
|
||||||
|
|
|
@ -290,7 +290,7 @@ describe('Test users', function () {
|
||||||
expect(user.account.description).to.be.null
|
expect(user.account.description).to.be.null
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(userMe.adminFlags).to.be.undefined
|
expect(userMe.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
|
||||||
expect(userGet.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
|
expect(userGet.adminFlags).to.equal(UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST)
|
||||||
|
|
||||||
expect(userMe.specialPlaylists).to.have.lengthOf(1)
|
expect(userMe.specialPlaylists).to.have.lengthOf(1)
|
||||||
|
|
|
@ -58,6 +58,9 @@ export const clientFilterHookObject = {
|
||||||
// Filter left menu links
|
// Filter left menu links
|
||||||
'filter:left-menu.links.create.result': true,
|
'filter:left-menu.links.create.result': true,
|
||||||
|
|
||||||
|
// Filter upload page alert messages
|
||||||
|
'filter:upload-page.alert-messages.edit.result': true,
|
||||||
|
|
||||||
// Filter videojs options built for PeerTube player
|
// Filter videojs options built for PeerTube player
|
||||||
'filter:internal.player.videojs.options.result': true
|
'filter:internal.player.videojs.options.result': true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue