mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Wired UI for archiving tiers (#2231)
refs https://github.com/TryGhost/Team/issues/1252 - allows site owners to (un)archive existing tiers via Admin UI - adds option to switch between archived or active tiers view Co-authored-by: Djordje Vlaisavljevic <dzvlais@gmail.com>
This commit is contained in:
parent
0f718c5a99
commit
baf6ec07a8
@ -1,15 +1,43 @@
|
||||
<label>Tiers</label>
|
||||
<div class="gh-product-cards">
|
||||
{{#each this.products as |product productIdx|}}
|
||||
<GhProductCard
|
||||
@product={{product}}
|
||||
@openEditProduct={{this.openEditProduct}}
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<div class="gh-product-cards-footer">
|
||||
<button class="gh-btn gh-btn-link gh-btn-text gh-btn-icon gh-btn-add-product green" {{action "openNewProduct" product}}><span>{{svg-jar "add-stroke" class="stroke-green"}}Add tier</span></button>
|
||||
<div class="flex justify-between items-center">
|
||||
<label>Tiers</label>
|
||||
<div>
|
||||
<div class="gh-contentfilter-menu gh-contentfilter-type {{if (not (eq this.selectedType.value "active")) "gh-contentfilter-selected"}}" data-test-type-select="true">
|
||||
<PowerSelect
|
||||
@selected={{this.selectedType}}
|
||||
@options={{this.availableTypes}}
|
||||
@searchEnabled={{false}}
|
||||
@onChange={{this.onTypeChange}}
|
||||
@triggerComponent="gh-power-select/trigger"
|
||||
@triggerClass="gh-contentfilter-menu-trigger gh-contentfilter-menu-trigger-tiers"
|
||||
@dropdownClass="gh-contentfilter-menu-dropdown"
|
||||
@matchTriggerWidth={{false}}
|
||||
as |type|
|
||||
>
|
||||
{{#if type.name}}{{type.name}}{{else}}<span class="red">Unknown type</span>{{/if}}
|
||||
</PowerSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-product-cards">
|
||||
{{#if this.isEmptyList}}
|
||||
<div class="flex justify-center items-center gh-main-content-card">
|
||||
<p style="color:#7c8b9a" class="mb0 pa8">You have no {{this.selectedType.value}} tiers.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#each this.products as |product productIdx|}}
|
||||
<GhProductCard
|
||||
@product={{product}}
|
||||
@openEditProduct={{this.openEditProduct}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{#if (eq this.type "active" )}}
|
||||
<div class="gh-product-cards-footer">
|
||||
<button class="gh-btn gh-btn-link gh-btn-text gh-btn-icon gh-btn-add-product green" {{action "openNewProduct" product}}>
|
||||
<span>{{svg-jar "add-stroke" class="stroke-green"}}Add tier</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.showProductModal}}
|
||||
|
@ -3,6 +3,14 @@ import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const TYPES = [{
|
||||
name: 'Active',
|
||||
value: 'active'
|
||||
},{
|
||||
name: 'Archived',
|
||||
value: 'archived'
|
||||
}];
|
||||
|
||||
export default class extends Component {
|
||||
@service membersUtils;
|
||||
@service ghostPaths;
|
||||
@ -12,9 +20,35 @@ export default class extends Component {
|
||||
|
||||
@tracked showProductModal = false;
|
||||
@tracked productModel = null;
|
||||
@tracked type = 'active';
|
||||
|
||||
get products() {
|
||||
return this.args.products;
|
||||
return this.args.products.filter((product) => {
|
||||
if (this.type === 'active') {
|
||||
return !!product.active;
|
||||
} else if (this.type === 'archived') {
|
||||
return !product.active;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get availableTypes() {
|
||||
return TYPES;
|
||||
}
|
||||
|
||||
get selectedType() {
|
||||
return this.type ? TYPES.find((d) => {
|
||||
return this.type === d.value;
|
||||
}) : TYPES[0];
|
||||
}
|
||||
|
||||
get isEmptyList() {
|
||||
return this.products.length === 0;
|
||||
}
|
||||
|
||||
@action
|
||||
onTypeChange(type) {
|
||||
this.type = type.value;
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -1,7 +1,4 @@
|
||||
<div class="gh-main-content-card gh-product-card">
|
||||
<button class="gh-product-card-editbutton gh-btn gh-btn-text gh-btn-link green" {{action "openEditProduct" this.product}}>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<div class="gh-product-card-block title-block">
|
||||
<h3 class="gh-product-card-name">
|
||||
{{this.product.name}}
|
||||
@ -54,4 +51,35 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="gh-product-card-editbutton-container">
|
||||
<span class="dropdown">
|
||||
<GhDropdownButton
|
||||
@dropdownName="tiers-actions-menu-{{this.product.name}}"
|
||||
@classNames="gh-btn gh-btn-action-icon gh-btn-icon gh-btn-outline gh-product-card-editbutton icon-only"
|
||||
@title="Tiers Actions"
|
||||
data-test-button="tiers-actions"
|
||||
>
|
||||
<span>
|
||||
{{svg-jar "dotdotdot"}}
|
||||
<span class="hidden">Actions</span>
|
||||
</span>
|
||||
</GhDropdownButton>
|
||||
{{#if (eq this.product.type "paid" )}}
|
||||
<GhDropdown
|
||||
@name="tiers-actions-menu-{{this.product.name}}"
|
||||
@tagName="ul"
|
||||
@classNames="gh-tier-actions-menu dropdown-menu dropdown-triangle-top-right"
|
||||
>
|
||||
<li>
|
||||
<button class="mr2" {{action "openEditProduct" this.product}}>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<Settings::Members::ArchiveTier @product={{this.product}} />
|
||||
</li>
|
||||
</GhDropdown>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Reactivating <strong>{{@data.offer.name}}</strong> will immediately allow new members to subscribe using this offer.
|
||||
Reactivating <strong>{{@data.offer.name}}</strong> will allow new members to subscribe to this tier. Existing members will remain unchanged.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
23
ghost/admin/app/components/modals/tiers/archive.hbs
Normal file
23
ghost/admin/app/components/modals/tiers/archive.hbs
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
<div class="modal-content" {{on-key "Enter" (perform this.archiveTierTask)}}>
|
||||
<header class="modal-header">
|
||||
<h1>Archive tier</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Members will no longer be able to subscribe to <strong>{{@data.product.name}}</strong> and it will be removed from the list of available tiers in portal. Existing members on this tier will remain unchanged.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="gh-btn" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Archive"
|
||||
@successText="Archived"
|
||||
@task={{this.archiveTierTask}}
|
||||
@class="gh-btn gh-btn-black gh-btn-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
31
ghost/admin/app/components/modals/tiers/archive.js
Normal file
31
ghost/admin/app/components/modals/tiers/archive.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
|
||||
export default class ArchiveTierModalComponent extends Component {
|
||||
@service notifications;
|
||||
@service router;
|
||||
|
||||
get isActive() {
|
||||
const {product} = this.args.data;
|
||||
return !!product.active;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*archiveTierTask() {
|
||||
const {product} = this.args.data;
|
||||
product.active = false;
|
||||
|
||||
try {
|
||||
yield product.save();
|
||||
|
||||
return product;
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
this.notifications.showAPIError(error, {key: 'tier.archive.failed'});
|
||||
}
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
22
ghost/admin/app/components/modals/tiers/unarchive.hbs
Normal file
22
ghost/admin/app/components/modals/tiers/unarchive.hbs
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
<div class="modal-content" {{on-key "Enter" (perform this.unarchiveTask)}}>
|
||||
<header class="modal-header">
|
||||
<h1>Reactivate tier</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Reactivating <strong>{{@data.product.name}}</strong> will immediately allow new members to subscribe to this.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="gh-btn" {{on "click" @close}}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Reactivate"
|
||||
@task={{this.unarchiveTask}}
|
||||
@class="gh-btn gh-btn-black gh-btn-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
31
ghost/admin/app/components/modals/tiers/unarchive.js
Normal file
31
ghost/admin/app/components/modals/tiers/unarchive.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
|
||||
export default class UnarchiveTierModalComponent extends Component {
|
||||
@service notifications;
|
||||
@service router;
|
||||
|
||||
get isActive() {
|
||||
const {product} = this.args.data;
|
||||
return !!product.active;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*unarchiveTask() {
|
||||
const {product} = this.args.data;
|
||||
product.active = true;
|
||||
|
||||
try {
|
||||
yield product.save();
|
||||
|
||||
return product;
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
this.notifications.showAPIError(error, {key: 'tier.unarchive.failed'});
|
||||
}
|
||||
} finally {
|
||||
this.args.close();
|
||||
}
|
||||
}
|
||||
}
|
17
ghost/admin/app/components/settings/members/archive-tier.hbs
Normal file
17
ghost/admin/app/components/settings/members/archive-tier.hbs
Normal file
@ -0,0 +1,17 @@
|
||||
{{#if this.product.active}}
|
||||
{{#if (not this.product.isNew)}}
|
||||
<button
|
||||
type="button"
|
||||
{{on "click" this.handleArchiveTier}}
|
||||
>
|
||||
<span>Archive</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<button
|
||||
type="button"
|
||||
{{on "click" this.handleUnarchiveTier}}
|
||||
>
|
||||
<span>Reactivate</span>
|
||||
</button>
|
||||
{{/if}}
|
40
ghost/admin/app/components/settings/members/archive-tier.js
Normal file
40
ghost/admin/app/components/settings/members/archive-tier.js
Normal file
@ -0,0 +1,40 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class ArchiveTierComponent extends Component {
|
||||
@service notifications;
|
||||
@service router;
|
||||
@service modals;
|
||||
|
||||
get isActive() {
|
||||
const {product} = this.args;
|
||||
return !!product.active;
|
||||
}
|
||||
|
||||
get product() {
|
||||
return this.args.product;
|
||||
}
|
||||
|
||||
@action
|
||||
handleArchiveTier() {
|
||||
if (!this.product.isNew) {
|
||||
this.modals.open('modals/tiers/archive', {
|
||||
product: this.product
|
||||
}, {
|
||||
className: 'fullscreen-modal fullscreen-modal-action fullscreen-modal-wide'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
handleUnarchiveTier() {
|
||||
if (!this.product.isNew) {
|
||||
this.modals.open('modals/tiers/unarchive', {
|
||||
product: this.product
|
||||
}, {
|
||||
className: 'fullscreen-modal fullscreen-modal-action fullscreen-modal-wide'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
}
|
||||
|
||||
.epm-modal-container {
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: auto;
|
||||
|
@ -11,6 +11,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gh-contentfilter-menu-trigger-tiers {
|
||||
margin-right: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.gh-product-cards {
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
@ -28,10 +33,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gh-product-card-editbutton {
|
||||
.gh-product-card-editbutton-container {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 16px;
|
||||
top: 24px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.gh-product-card-editbutton {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.gh-product-card-editbutton.gh-btn span {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.gh-tier-actions-menu {
|
||||
top: calc(100% + 6px);
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.gh-product-card-block {
|
||||
|
Loading…
Reference in New Issue
Block a user