1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-17 18:32:35 +00:00

Resolve "Show modal with information about premium when clicking on a disabled feature"

This commit is contained in:
Bram Wiepjes 2022-07-27 11:33:07 +00:00
parent bdbcbdc51a
commit a74ee4a06a
28 changed files with 295 additions and 111 deletions

View file

@ -49,6 +49,7 @@ For example:
* Tables can now be duplicated. [#961](https://gitlab.com/bramw/baserow/-/issues/961)
* Introduced environment variable to disable Google docs file preview. [#1074](https://gitlab.com/bramw/baserow/-/issues/1074)
* Made it possible to select the entire row via the row context menu. [#1061](https://gitlab.com/bramw/baserow/-/issues/1061)
* Show modal when the users clicks on a deactivated premium features. [#1066](https://gitlab.com/bramw/baserow/-/issues/1066)
* Replaced all custom alert code with `Alert` component [#1016](https://gitlab.com/bramw/baserow/-/issues/1016)
* Add ability to create and restore snapshots. [#141](https://gitlab.com/bramw/baserow/-/issues/141)

View file

@ -1,3 +1,4 @@
@import 'premium_features';
@import 'admin_dashboard';
@import 'user_admin';
@import 'group_admin';

View file

@ -91,21 +91,8 @@
.licenses__features {
display: inline-block;
list-style: none;
margin: 0 auto 30px auto;
padding: 0;
text-align: left;
}
.licenses__feature {
margin-bottom: 6px;
font-size: 15px;
font-weight: 600;
}
.licenses__feature-icon {
margin-right: 4px;
color: $color-success-500;
margin: 0 auto;
}
.licenses__instance-id {

View file

@ -0,0 +1,17 @@
.premium-features {
display: block;
list-style: none;
padding: 0;
margin: 0;
}
.premium-features__feature {
margin-bottom: 6px;
font-size: 15px;
font-weight: 600;
}
.premium-features__feature-icon {
margin-right: 4px;
color: $color-success-500;
}

View file

@ -30,6 +30,7 @@
.row-comments__empty-text {
font-size: 14px;
line-height: 160%;
margin-bottom: 30px;
}
.row-comments__end-line {

View file

@ -0,0 +1,24 @@
<template>
<ul class="premium-features">
<li class="premium-features__feature">
<i class="fas fa-fw fa-check premium-features__feature-icon"></i>
{{ $t('premiumFeatures.rowComments') }}
</li>
<li class="premium-features__feature">
<i class="fas fa-fw fa-check premium-features__feature-icon"></i>
{{ $t('premiumFeatures.kanbanView') }}
</li>
<li class="premium-features__feature">
<i class="fas fa-fw fa-check premium-features__feature-icon"></i>
{{ $t('premiumFeatures.exports') }}
</li>
<li class="premium-features__feature">
<i class="fas fa-fw fa-check premium-features__feature-icon"></i>
{{ $t('premiumFeatures.admin') }}
</li>
<li class="premium-features__feature">
<i class="fas fa-fw fa-check premium-features__feature-icon"></i>
{{ $t('premiumFeatures.rowColoring') }}
</li>
</ul>
</template>

View file

@ -0,0 +1,36 @@
<template>
<Modal>
<h2 class="box__title">
{{ $t('premiumModal.title', { name }) }}
</h2>
<div>
<p>
{{ $t('premiumModal.description', { name }) }}
</p>
<PremiumFeatures class="margin-bottom-3"></PremiumFeatures>
<a
href="https://baserow.io/pricing"
target="_blank"
class="button button--primary button--large"
>{{ $t('premiumModal.viewPricing') }}</a
>
</div>
</Modal>
</template>
<script>
import modal from '@baserow/modules/core/mixins/modal'
import PremiumFeatures from '@baserow_premium/components/PremiumFeatures'
export default {
name: 'PremiumModal',
components: { PremiumFeatures },
mixins: [modal],
props: {
name: {
type: String,
required: true,
},
},
}
</script>

View file

@ -7,7 +7,15 @@
<div class="row-comments__empty-text">
{{ $t('rowCommentSidebar.onlyPremium') }}
</div>
<a class="button button--primary" @click="$refs.premiumModal.show()">
{{ $t('rowCommentSidebar.more') }}
<i class="fas fa-unlock"></i>
</a>
</div>
<PremiumModal
ref="premiumModal"
:name="$t('rowCommentSidebar.name')"
></PremiumModal>
</div>
</template>
<template v-else>
@ -68,6 +76,7 @@ import { PremiumPlugin } from '@baserow_premium/plugins'
import RowComment from '@baserow_premium/components/row_comments/RowComment'
import InfiniteScroll from '@baserow/modules/core/components/helpers/InfiniteScroll'
import AutoExpandableTextareaInput from '@baserow/modules/core/components/helpers/AutoExpandableTextareaInput'
import PremiumModal from '@baserow_premium/components/PremiumModal'
export default {
name: 'RowCommentsSidebar',
@ -75,6 +84,7 @@ export default {
AutoExpandableTextareaInput,
InfiniteScroll,
RowComment,
PremiumModal,
},
props: {
database: {

View file

@ -18,13 +18,21 @@
"exporterType": {
"json": "Export to JSON",
"xml": "Export to XML"
}
},
"deactivated": "Available in premium version"
},
"premiumModal": {
"title": "Upgrade to use the {name}",
"description": "Your account doesnt have access to the premium features. Upgrade to the premium version to begin using {name}. You can upgrade your account by getting a license. Click on the button below to view the pricing.",
"viewPricing": "View pricing"
},
"rowCommentSidebar": {
"onlyPremium": "Row comments are available in the premium version.",
"readOnlyNoComment": "No comments for this row.",
"noComment": "No comments for this row yet. Use the form below to add a comment.",
"comment": "Comment"
"comment": "Comment",
"more": "More information",
"name": "Row comments"
},
"rowComment": {
"you": "You",
@ -176,15 +184,17 @@
"activeUsers30days": "Active users last 30 days",
"viewAll": "View all"
},
"licenses": {
"titleNoLicenses": "No licenses found",
"titleLicenses": "Licenses",
"noLicensesDescription": "Your Baserow instance doesnt have any licenses registered. A premium license gives you immediate access to all of the additional features. If you already have a license, you can register it here. Alternatively you can get one by clicking on the button below.",
"premiumFeatures": {
"rowComments": "Row comments",
"kanbanView": "Kanban view",
"exports": "JSON and XML export",
"admin": "Admin functionality",
"rowColoring": "Row coloring",
"rowColoring": "Row coloring"
},
"licenses": {
"titleNoLicenses": "No licenses found",
"titleLicenses": "Licenses",
"noLicensesDescription": "Your Baserow instance doesnt have any licenses registered. A premium license gives you immediate access to all of the additional features. If you already have a license, you can register it here. Alternatively you can get one by clicking on the button below.",
"getLicense": "Get a license",
"baserowInstanceId": "Your Baserow instance ID is:",
"registerLicense": "Register license",

View file

@ -10,28 +10,9 @@
<p class="placeholder__content">
{{ $t('licenses.noLicensesDescription') }}
</p>
<ul class="licenses__features">
<li class="licenses__feature">
<i class="fas fa-fw fa-check licenses__feature-icon"></i>
{{ $t('licenses.rowComments') }}
</li>
<li class="licenses__feature">
<i class="fas fa-fw fa-check licenses__feature-icon"></i>
{{ $t('licenses.kanbanView') }}
</li>
<li class="licenses__feature">
<i class="fas fa-fw fa-check licenses__feature-icon"></i>
{{ $t('licenses.exports') }}
</li>
<li class="licenses__feature">
<i class="fas fa-fw fa-check licenses__feature-icon"></i>
{{ $t('licenses.admin') }}
</li>
<li class="licenses__feature">
<i class="fas fa-fw fa-check licenses__feature-icon"></i>
{{ $t('licenses.rowColoring') }}
</li>
</ul>
<PremiumFeatures
class="licenses__features margin-bottom-2"
></PremiumFeatures>
<div class="placeholder__action">
<a class="button button--large" @click="$refs.registerModal.show()">
<i class="fas fa-plus"></i>
@ -160,12 +141,13 @@
import LicenseService from '@baserow_premium/services/license'
import RegisterLicenseModal from '@baserow_premium/components/license/RegisterLicenseModal'
import RedirectToBaserowModal from '@baserow_premium/components/RedirectToBaserowModal'
import PremiumFeatures from '@baserow_premium/components/PremiumFeatures'
import moment from '@baserow/modules/core/moment'
import SettingsService from '@baserow/modules/core/services/settings'
import { copyToClipboard } from '@baserow/modules/database/utils/clipboard'
export default {
components: { RedirectToBaserowModal, RegisterLicenseModal },
components: { RedirectToBaserowModal, RegisterLicenseModal, PremiumFeatures },
layout: 'app',
middleware: 'staff',
async asyncData({ app, error }) {

View file

@ -3,12 +3,17 @@ import { GridViewType } from '@baserow/modules/database/viewTypes'
import { PremiumPlugin } from '@baserow_premium/plugins'
import TableJSONExporter from '@baserow_premium/components/exporter/TableJSONExporter'
import TableXMLExporter from '@baserow_premium/components/exporter/TableXMLExporter'
import PremiumModal from '@baserow_premium/components/PremiumModal'
class PremiumTableExporterType extends TableExporterType {
getDeactivatedText() {
return this.app.i18n.t('premium.deactivated')
}
getDeactivatedClickModal() {
return PremiumModal
}
isDeactivated(groupId) {
return !PremiumPlugin.hasValidPremiumLicense(
this.app.store.getters['auth/getAdditionalUserData'],

View file

@ -1,4 +1,5 @@
import { ViewDecoratorType } from '@baserow/modules/database/viewDecorators'
import PremiumModal from '@baserow_premium/components/PremiumModal'
import { PremiumPlugin } from '@baserow_premium/plugins'
import {
@ -42,6 +43,10 @@ export class LeftBorderColorViewDecoratorType extends ViewDecoratorType {
return i18n.t('viewDecoratorType.onlyForPremium')
}
getDeactivatedClickModal() {
return PremiumModal
}
isDeactivated(groupId) {
const { store } = this.app
@ -141,6 +146,10 @@ export class BackgroundColorViewDecoratorType extends ViewDecoratorType {
return i18n.t('viewDecoratorType.onlyForPremium')
}
getDeactivatedClickModal() {
return PremiumModal
}
isDeactivated(groupId) {
const { store } = this.app

View file

@ -5,6 +5,7 @@ import {
import { SingleSelectFieldType } from '@baserow/modules/database/fieldTypes'
import KanbanView from '@baserow_premium/components/views/kanban/KanbanView'
import KanbanViewHeader from '@baserow_premium/components/views/kanban/KanbanViewHeader'
import PremiumModal from '@baserow_premium/components/PremiumModal'
import { PremiumPlugin } from '@baserow_premium/plugins'
class PremiumViewType extends ViewType {
@ -12,6 +13,10 @@ class PremiumViewType extends ViewType {
return this.app.i18n.t('premium.deactivated')
}
getDeactivatedClickModal() {
return PremiumModal
}
isDeactivated(groupId) {
return !PremiumPlugin.hasValidPremiumLicense(
this.app.store.getters['auth/getAdditionalUserData'],

View file

@ -154,9 +154,6 @@
"formLinkName": "form"
}
},
"premium": {
"deactivated": "Available in premium version"
},
"trashType": {
"group": "group",
"application": "application",

View file

@ -98,4 +98,5 @@
@import 'webhook';
@import 'tab';
@import 'preview';
@import 'deactivated_label';
@import 'snapshots_modal';

View file

@ -47,7 +47,7 @@
text-decoration: none;
}
&:not(.disabled):not(.deactivated):not(.active):hover {
&:not(.disabled):not(.active):hover {
background-color: $color-neutral-100;
color: $color-primary-900;
}
@ -60,15 +60,6 @@
}
}
&.deactivated {
background-color: $color-neutral-50;
color: $color-neutral-400;
&:hover {
cursor: inherit;
}
}
&.active {
color: $color-primary-900;
background-color: $color-primary-100;

View file

@ -0,0 +1,9 @@
.deactivated-label {
display: inline-block;
background-color: $color-primary-200;
border-radius: 3px;
color: $color-primary-500;
margin: 0 4px;
@include center-text(14px, 9px);
}

View file

@ -240,15 +240,6 @@
text-decoration: none;
background-color: $color-neutral-100;
}
&.select__footer-create-link--disabled {
color: $color-neutral-400;
&:hover {
cursor: inherit;
text-decoration: none;
}
}
}
.select__footer-create-icon {

View file

@ -3,7 +3,7 @@
<a
v-tooltip="deactivated ? deactivatedText : null"
class="choice-items__link"
:class="{ active, disabled, deactivated }"
:class="{ active, disabled }"
@click="select(exporterType)"
>
<i
@ -11,7 +11,16 @@
:class="'fa-' + exporterType.iconClass"
></i>
{{ exporterType.getName() }}
<div v-if="deactivated" class="deactivated-label">
<i class="fas fa-lock"></i>
</div>
</a>
<component
v-if="deactivatedClickModal !== null"
:is="deactivatedClickModal"
ref="deactivatedClickModal"
:name="exporterType.getName()"
></component>
</li>
</template>
@ -47,14 +56,19 @@ export default {
.get('exporter', this.exporterType.type)
.isDeactivated(this.database.group.id)
},
deactivatedClickModal() {
return this.$registry
.get('exporter', this.exporterType.type)
.getDeactivatedClickModal()
},
},
methods: {
select(exporterType) {
if (this.disabled || this.deactivated) {
return
if (this.deactivated && this.deactivatedClickModal) {
this.$refs.deactivatedClickModal.show()
} else if (!this.disabled && !this.deactivated) {
this.$emit('selected', exporterType)
}
this.$emit('selected', exporterType)
},
},
}

View file

@ -3,22 +3,28 @@
ref="createViewLink"
v-tooltip="deactivated ? deactivatedText : null"
class="select__footer-create-link"
:class="{
'select__footer-create-link--disabled': deactivated,
}"
@click="!deactivated && $refs.createModal.show($refs.createViewLink)"
@click="select"
>
<i
class="select__footer-create-icon fas"
:class="'fa-' + viewType.iconClass"
></i>
{{ viewType.getName() }}
<div v-if="deactivated" class="deactivated-label">
<i class="fas fa-lock"></i>
</div>
<CreateViewModal
ref="createModal"
:table="table"
:view-type="viewType"
@created="$emit('created', $event)"
></CreateViewModal>
<component
v-if="deactivatedClickModal !== null"
:is="deactivatedClickModal"
ref="deactivatedClickModal"
:name="viewType.getName()"
></component>
</a>
</template>
@ -51,6 +57,18 @@ export default {
deactivated() {
return this.viewType.isDeactivated(this.database.group.id)
},
deactivatedClickModal() {
return this.viewType.getDeactivatedClickModal()
},
},
methods: {
select() {
if (!this.deactivated) {
this.$refs.createModal.show(this.$refs.createViewLink)
} else if (this.deactivated && this.deactivatedClickModal) {
this.$refs.deactivatedClickModal.show()
}
},
},
}
</script>

View file

@ -4,6 +4,9 @@
<div class="decorator-item__content">
<div class="decorator-item__title">
{{ props.decoratorType.getName() }}
<div v-if="props.deactivated" class="deactivated-label">
<i class="fas fa-lock"></i>
</div>
</div>
<div class="decorator-item__description">
{{ props.decoratorType.getDescription() }}
@ -20,6 +23,11 @@ export default {
type: Object,
required: true,
},
deactivated: {
type: Boolean,
required: false,
default: false,
},
},
}
</script>

View file

@ -1,14 +1,23 @@
<template>
<div class="decorator-list">
<div
v-for="decoratorType in viewDecoratorTypes"
v-for="(decoratorType, index) in viewDecoratorTypes"
:key="decoratorType.getType()"
v-tooltip="getTooltip(decoratorType)"
class="decorator-list__item"
:class="{ 'decorator-list__item--disabled': isDisabled(decoratorType) }"
@click="addDecoration(decoratorType)"
@click="addDecoration(decoratorType, index)"
>
<ViewDecoratorItem :decorator-type="decoratorType" />
<ViewDecoratorItem
:deactivated="isDeactivated(decoratorType)"
:decorator-type="decoratorType"
/>
<component
v-if="decoratorType.getDeactivatedClickModal() !== null"
:is="decoratorType.getDeactivatedClickModal()"
ref="deactivatedClickModal"
:name="decoratorType.getName()"
></component>
</div>
</div>
</template>
@ -37,25 +46,30 @@ export default {
},
},
methods: {
isDeactivated(decoratorType) {
return decoratorType.isDeactivated(this.database.group.id)
},
isDisabled(decoratorType) {
return (
decoratorType.isDeactivated(this.database.group.id) ||
!decoratorType.canAdd({ view: this.view })[0]
)
return !decoratorType.canAdd({ view: this.view })[0]
},
getTooltip(decoratorType) {
if (decoratorType.isDeactivated(this.database.group.id)) {
return decoratorType.getDeactivatedText()
}
const [canAdd, disabledReason] = decoratorType.canAdd({ view: this.view })
if (!canAdd) {
return disabledReason
}
if (this.isDeactivated(decoratorType)) {
return decoratorType.getDeactivatedText()
}
return ''
},
addDecoration(decoratorType) {
addDecoration(decoratorType, index) {
if (this.isDisabled(decoratorType)) {
return
} else if (this.isDeactivated(decoratorType)) {
if (decoratorType.getDeactivatedClickModal() !== null) {
this.$refs.deactivatedClickModal[index].show()
}
return
}
this.$emit('select', decoratorType)
},

View file

@ -6,25 +6,26 @@
active: view._.selected,
'select__item--loading': view._.loading,
'select__item--no-options': readOnly,
disabled: deactivated,
}"
>
<a
class="select__item-link"
@click="!deactivated && $emit('selected', view)"
>
<a class="select__item-link" @click="select(view)">
<div class="select__item-name">
<i
class="select__item-icon fas fa-fw"
:class="
(deactivated ? '' : view._.type.colorClass) +
' fa-' +
view._.type.iconClass
"
:class="view._.type.colorClass + ' fa-' + view._.type.iconClass"
></i>
<EditableViewName ref="rename" :view="view"></EditableViewName>
<div v-if="deactivated" class="deactivated-label">
<i class="fas fa-lock"></i>
</div>
</div>
</a>
<component
v-if="deactivatedClickModal !== null"
:is="deactivatedClickModal"
ref="deactivatedClickModal"
:name="viewType.getName()"
></component>
<template v-if="!readOnly">
<a
ref="contextLink"
@ -74,24 +75,32 @@ export default {
},
},
computed: {
viewType() {
return this.$registry.get('view', this.view.type)
},
deactivatedText() {
return this.$registry
.get('view', this.view.type)
.getDeactivatedText({ view: this.view })
return this.viewType.getDeactivatedText({ view: this.view })
},
deactivated() {
return (
!this.readOnly &&
this.$registry
.get('view', this.view.type)
.isDeactivated(this.database.group.id)
!this.readOnly && this.viewType.isDeactivated(this.database.group.id)
)
},
deactivatedClickModal() {
return this.deactivated ? this.viewType.getDeactivatedClickModal() : null
},
},
methods: {
enableRename() {
this.$refs.rename.edit()
},
select(view) {
if (this.deactivated) {
this.$refs.deactivatedClickModal.show()
return
}
this.$emit('selected', view)
},
},
}
</script>

View file

@ -72,6 +72,13 @@ export class TableExporterType extends Registerable {
*/
getDeactivatedText() {}
/**
* When the disabled exporter is clicked, this modal will be shown.
*/
getDeactivatedClickModal() {
return null
}
/**
* Indicates if the exporter type is disabled.
*/

View file

@ -27,6 +27,13 @@ export class ViewDecoratorType extends Registerable {
*/
getDeactivatedText({ view }) {}
/**
* When the deactivated view decorator is clicked, this modal will be shown.
*/
getDeactivatedClickModal() {
return null
}
/**
* Indicates if the decorator type is disabled.
*/

View file

@ -278,6 +278,13 @@ export class ViewType extends Registerable {
*/
getDeactivatedText() {}
/**
* When the disabled view type is clicked, this modal will be shown.
*/
getDeactivatedClickModal() {
return null
}
/**
* Indicates if the view type is disabled.
*/

View file

@ -146,8 +146,11 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
/>
exporterType.csv
<!---->
</a>
<!---->
</li>
</ul>
</div>
@ -1225,8 +1228,11 @@ exports[`Preview exportTableModal Modal with view 1`] = `
/>
exporterType.csv
<!---->
</a>
<!---->
</li>
</ul>
</div>

View file

@ -26,7 +26,8 @@ exports[`GridViewRows component with decoration Default component 1`] = `
>
Fake decorator
<!---->
</div>
<div
@ -38,6 +39,8 @@ exports[`GridViewRows component with decoration Default component 1`] = `
</div>
</div>
</div>
<!---->
</div>
</div>
</div>
@ -80,7 +83,8 @@ exports[`GridViewRows component with decoration Should show cant add decorator t
>
Fake decorator
<!---->
</div>
<div
@ -92,6 +96,8 @@ exports[`GridViewRows component with decoration Should show cant add decorator t
</div>
</div>
</div>
<!---->
</div>
</div>
</div>
@ -117,7 +123,7 @@ exports[`GridViewRows component with decoration Should show unavailable decorato
class="decorator-list"
>
<div
class="decorator-list__item decorator-list__item--disabled"
class="decorator-list__item"
>
<div
class="decorator-item"
@ -135,7 +141,14 @@ exports[`GridViewRows component with decoration Should show unavailable decorato
>
Fake decorator
<div
class="deactivated-label"
>
<i
class="fas fa-lock"
/>
</div>
</div>
<div
@ -147,6 +160,8 @@ exports[`GridViewRows component with decoration Should show unavailable decorato
</div>
</div>
</div>
<!---->
</div>
</div>
</div>
@ -188,7 +203,8 @@ exports[`GridViewRows component with decoration View with decoration configured
>
Fake decorator
<!---->
</div>
<div
@ -267,7 +283,8 @@ exports[`GridViewRows component with decoration View with decoration configured
>
Fake decorator
<!---->
</div>
<div