Added free membership settings modal
refs https://github.com/TryGhost/Team/issues/648 All sites will include a default Free "Product" which is used for free memberships. This change adds UI for handling free membership settings. Also - - Updates product icons in list and responsive sizes - Copy updates
This commit is contained in:
parent
2a6d7226d2
commit
22de604ede
@ -150,81 +150,74 @@
|
||||
{{/if}}
|
||||
|
||||
<h4 class="gh-main-section-header small bn">Products</h4>
|
||||
<div class="gh-main-section-content">
|
||||
<ol class="gh-memberproduct-list gh-list">
|
||||
<li class="gh-list-row header empty">
|
||||
<div class="gh-list-header"></div>
|
||||
</li>
|
||||
{{#unless this.products}}
|
||||
<li class="gh-list-row">
|
||||
<div class="gh-list-data gh-cp-memberproduct-noproduct">
|
||||
<div class="mb2">This member doesn't have any products.</div>
|
||||
{{#unless this.member.isNew}}
|
||||
<button type="button" class="gh-btn gh-btn-text green gh-btn-icon gh-btn-addproduct" {{action (toggle "showMemberProductModal" this)}}>
|
||||
<span>{{svg-jar "add"}} Add product</span>
|
||||
</button>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</li>
|
||||
{{/unless}}
|
||||
{{#each this.products as |product|}}
|
||||
<li class="gh-list-row">
|
||||
<div class="gh-list-data gh-cp-memberproduct-title">
|
||||
<h3 class="gh-memberproduct-name">
|
||||
{{product.name}}
|
||||
</h3>
|
||||
{{#each product.subscriptions as |sub|}}
|
||||
<div class="gh-memberproduct-description gh-cp-memberproduct-showmore">
|
||||
<input type="checkbox" id="show-more-{{sub.id}}" role="button">
|
||||
<label for="show-more-{{sub.id}}">
|
||||
{{svg-jar "arrow-right"}}
|
||||
<span class="gh-cp-memberproduct-pricelabel">{{sub.price.nickname}}</span>
|
||||
–
|
||||
<span class="gh-cp-memberproduct-price">{{sub.price.currencySymbol}}{{sub.price.nonDecimalAmount}}/{{sub.price.interval}}</span>
|
||||
–
|
||||
{{#if sub.cancel_at_period_end}}
|
||||
<span class="gh-cp-memberproduct-renewal">Cancels {{sub.validUntil}}</span>
|
||||
<span class="gh-badge archived">Cancelled</span>
|
||||
{{else}}
|
||||
<span class="gh-cp-memberproduct-renewal">Renews {{sub.validUntil}}</span>
|
||||
<span class="gh-badge active">Active</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<div class="details">
|
||||
<span>Created on {{sub.startDate}}</span>
|
||||
<div class="actions">
|
||||
<a href="https://dashboard.stripe.com/subscriptions/{{sub.id}}" target="_blank" rel="noopener">
|
||||
View on Stripe
|
||||
</a>
|
||||
<span class="ml1 mr1 dib">–</span>
|
||||
{{#if sub.cancel_at_period_end}}
|
||||
<button class="gh-btn gh-btn-link gh-btn-text green" {{action "continueSubscription" sub.id}}>
|
||||
<span>Continue subscription</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button class="gh-btn gh-btn-link gh-btn-text red" {{action "cancelSubscription" sub.id}}>
|
||||
<span>Cancel subscription</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#if this.products}}
|
||||
<tr class="gh-list-row gh-memberproduct-add-product">
|
||||
<td colspan="2" class="gh-list-data">
|
||||
<button type="button" class="gh-btn gh-btn-text green gh-btn-icon gh-btn-addproduct" {{action (toggle "showMemberProductModal" this)}}>
|
||||
<span>{{svg-jar "add"}} Add product</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</ol>
|
||||
{{#unless this.products}}
|
||||
<div class="gh-main-section-content bordered">
|
||||
<div class="gh-cp-memberproduct-noproduct">
|
||||
<div class="mb2">This member doesn't have any products.</div>
|
||||
{{#unless this.member.isNew}}
|
||||
<button type="button" class="gh-btn gh-btn-text green gh-btn-icon gh-btn-addproduct" {{action (toggle "showMemberProductModal" this)}}>
|
||||
<span>{{svg-jar "add"}} Add product</span>
|
||||
</button>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#each this.products as |product|}}
|
||||
<div class="gh-main-section-content bordered">
|
||||
<div class="gh-cp-memberproduct">
|
||||
<h3 class="gh-memberproduct-name">
|
||||
{{product.name}}
|
||||
</h3>
|
||||
{{#each product.subscriptions as |sub|}}
|
||||
<div class="gh-memberproduct-subscription">
|
||||
<div>
|
||||
<span class="gh-cp-memberproduct-pricelabel">{{sub.price.nickname}}</span>
|
||||
–
|
||||
<span class="gh-cp-memberproduct-price">{{sub.price.currencySymbol}}{{sub.price.nonDecimalAmount}}/{{sub.price.interval}}</span>
|
||||
–
|
||||
{{#if sub.cancel_at_period_end}}
|
||||
<span class="gh-cp-memberproduct-renewal">Cancels {{sub.validUntil}}</span>
|
||||
<span class="gh-badge archived">Cancelled</span>
|
||||
{{else}}
|
||||
<span class="gh-cp-memberproduct-renewal">Renews {{sub.validUntil}}</span>
|
||||
<span class="gh-badge active">Active</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>Created on {{sub.startDate}}</div>
|
||||
|
||||
<span class="action-menu">
|
||||
<GhDropdownButton @dropdownName="subscription-menu-{{sub.id}}" @classNames="gh-btn gh-btn-outline gh-btn-icon only-has-icon" @title="User Actions">
|
||||
<span>
|
||||
{{svg-jar "dotdotdot"}}
|
||||
<span class="hidden">Subscription menu</span>
|
||||
</span>
|
||||
</GhDropdownButton>
|
||||
<GhDropdown @name="subscription-menu-{{sub.id}}" @tagName="ul" @classNames="product-actions-menu dropdown-menu dropdown-align-right">
|
||||
<li>
|
||||
<a href="https://dashboard.stripe.com/subscriptions/{{sub.id}}" target="_blank" rel="noopener">
|
||||
View on Stripe
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{#if sub.cancel_at_period_end}}
|
||||
<button class="gh-btn gh-btn-link gh-btn-text green" {{action "continueSubscription" sub.id}}>
|
||||
<span>Continue subscription</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button class="gh-btn gh-btn-link gh-btn-text red" {{action "cancelSubscription" sub.id}}>
|
||||
<span>Cancel subscription</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</li>
|
||||
</GhDropdown>
|
||||
</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="gh-main-section-block">
|
||||
<div class="gh-main-section-content bordered">
|
||||
|
@ -0,0 +1,61 @@
|
||||
<header class="modal-header" data-test-modal="webhook-form">
|
||||
<h1 data-test-text="title">Free membership settings</h1>
|
||||
</header>
|
||||
<button class="close" href title="Close" {{action "closeModal"}} {{action (optional this.noop) on="mouseDown"}}>
|
||||
{{svg-jar "close"}}
|
||||
</button>
|
||||
|
||||
<form>
|
||||
<div class="modal-body">
|
||||
<div class="gh-main-section-block">
|
||||
<div class="gh-main-section-content grey gh-product-priceform-block">
|
||||
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="name">
|
||||
<label for="name" class="fw6">Portal display name</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.price.nickname}}
|
||||
{{!-- @input={{action (mut this.price.nickname) value="target.value"}} --}}
|
||||
@name="name"
|
||||
@id="name"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.price.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="description">
|
||||
<label for="description" class="fw6">Description</label>
|
||||
<GhTextInput
|
||||
@value=''
|
||||
@name="description"
|
||||
@id="description"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.price.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="welcome-page">
|
||||
<label for="welcome-page" class="fw6">Welcome page</label>
|
||||
<GhTextInput
|
||||
@value=''
|
||||
@name="welcome-page"
|
||||
@id="welcome-page"
|
||||
@class="gh-input" />
|
||||
<p>Redirect to this URL after signing up for a free membership</p>
|
||||
<GhErrorMessage @errors={{this.price.errors}} @property="welcome-page" />
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="gh-btn"
|
||||
{{action "closeModal"}}
|
||||
{{!-- disable mouseDown so it doesn't trigger focus-out validations --}}
|
||||
{{!-- {{action (optional this.noop) on="mouseDown"}} --}}
|
||||
data-test-button="cancel-webhook"
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<GhTaskButton @buttonText="Save"
|
||||
{{!-- @successText={{this.successText}} --}}
|
||||
{{!-- @task={{this.savePrice}} --}}
|
||||
@class="gh-btn gh-btn-black gh-btn-icon"
|
||||
data-test-button="save-price" />
|
||||
</div>
|
24
ghost/admin/app/components/modal-free-membership-settings.js
Normal file
24
ghost/admin/app/components/modal-free-membership-settings.js
Normal file
@ -0,0 +1,24 @@
|
||||
import ModalBase from 'ghost-admin/components/modal-base';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {action} from '@ember/object';
|
||||
|
||||
// TODO: update modals to work fully with Glimmer components
|
||||
@classic
|
||||
export default class ModalFreeMembershipSettings extends ModalBase {
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
}
|
||||
|
||||
@action
|
||||
close(event) {
|
||||
event?.preventDefault?.();
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
actions = {
|
||||
// needed because ModalBase uses .send() for keyboard events
|
||||
closeModal() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -71,18 +71,6 @@
|
||||
|
||||
<h4 class="gh-main-section-header small bn">Advanced</h4>
|
||||
<div class="gh-main-section-content grey gh-product-priceform-block">
|
||||
{{!-- <GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="trial-period">
|
||||
<label for="trial-period" class="fw6">Trial period</label>
|
||||
<div class="flex items-center justify-center gh-input-group gh-labs-price-label">
|
||||
<GhTextInput
|
||||
@value=''
|
||||
@name="trial-period"
|
||||
@id="trial-period"
|
||||
@class="gh-input" />
|
||||
<span class="gh-input-append">days</span>
|
||||
</div>
|
||||
<GhErrorMessage @errors={{this.price.errors}} @property="trial-period" />
|
||||
</GhFormGroup> --}}
|
||||
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="welcome-page">
|
||||
<label for="welcome-page" class="fw6">Welcome page</label>
|
||||
<GhTextInput
|
||||
|
@ -1,7 +1,35 @@
|
||||
import Controller from '@ember/controller';
|
||||
import {action} from '@ember/object';
|
||||
import {htmlSafe} from '@ember/string';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class ProductsController extends Controller {
|
||||
@service config;
|
||||
|
||||
@tracked iconStyle = '';
|
||||
@tracked showFreeMembershipModal = false;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.iconStyle = this.setIconStyle();
|
||||
}
|
||||
|
||||
get products() {
|
||||
return this.model.sortBy('name');
|
||||
}
|
||||
|
||||
setIconStyle() {
|
||||
let icon = this.config.get('icon');
|
||||
if (icon) {
|
||||
return htmlSafe(`background-image: url(${icon})`);
|
||||
}
|
||||
icon = 'https://static.ghost.org/v4.0.0/images/ghost-orb-2.png';
|
||||
return htmlSafe(`background-image: url(${icon})`);
|
||||
}
|
||||
|
||||
@action
|
||||
closeFreeMembershipModal() {
|
||||
this.showFreeMembershipModal = false;
|
||||
}
|
||||
}
|
||||
|
@ -1566,7 +1566,8 @@ p.gh-members-import-errordetail:first-of-type {
|
||||
|
||||
/* Member's product list */
|
||||
.gh-memberproduct-name {
|
||||
margin-bottom: 6px !important;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
.gh-memberproduct-actionlist {
|
||||
@ -1715,4 +1716,22 @@ p.gh-members-import-errordetail:first-of-type {
|
||||
|
||||
.gh-btn-add-memberproduct[disabled] span {
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-memberproduct-subscription {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gh-memberproduct-subscription .action-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.gh-memberproduct-subscription .action-menu .gh-btn span {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.gh-memberproduct-subscription .action-menu .gh-btn svg {
|
||||
margin: 0;
|
||||
}
|
@ -1,24 +1,69 @@
|
||||
/* Product list */
|
||||
.gh-product-list .gh-list-row.header .gh-list-header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 0;
|
||||
line-height: 0;
|
||||
.gh-product-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 32px;
|
||||
}
|
||||
|
||||
.gh-product-list-chevron {
|
||||
padding-right: 0;
|
||||
@media (max-width: 980px) {
|
||||
.gh-product-list {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.gh-product-chevron {
|
||||
.gh-product-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 2px;
|
||||
padding: 8vmin 48px;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.gh-product-card {
|
||||
padding: 4vmin 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.gh-product-card-name {
|
||||
font-size: 1.75rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gh-product-card-description {
|
||||
margin: 0 0 16px;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-product-chevron span {
|
||||
line-height: 0;
|
||||
.gh-product-list-icon {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
color: var(--green);
|
||||
margin-bottom: 8px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.gh-product-list-icon svg {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.gh-product-list-siteicon {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
background-color: transparent;
|
||||
background-size: 54px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.gh-product-list-icon svg circle,
|
||||
.gh-product-list-icon svg path {
|
||||
stroke-width: 1px !important;
|
||||
}
|
||||
|
||||
/* Product details */
|
||||
|
@ -11,32 +11,41 @@
|
||||
</GhCanvasHeader>
|
||||
|
||||
<section class="view-container">
|
||||
<section class="content-list">
|
||||
<ol class="gh-product-list gh-list">
|
||||
<li class="gh-list-row header">
|
||||
<div class="gh-list-header gh-list-cellwidth-70"></div>
|
||||
<div class="gh-list-header gh-list-cellwidth-30"></div>
|
||||
</li>
|
||||
{{#each this.products as |product|}}
|
||||
<li class="gh-list-row">
|
||||
<LinkTo @route="settings.product" @model={{product}} class="gh-list-data gh-list-cellwidth-70 gh-product-list-title">
|
||||
<h3 class="gh-product-list-name">
|
||||
{{product.name}}
|
||||
</h3>
|
||||
<p class="ma0 pa0 f8 midgrey gh-product-list-description">
|
||||
Product description
|
||||
</p>
|
||||
</LinkTo>
|
||||
<div class="gh-product-list">
|
||||
<div class="gh-product-card">
|
||||
<span class="gh-product-list-icon">{{svg-jar "members"}}</span>
|
||||
<h3 class="gh-product-card-name">
|
||||
Free membership
|
||||
</h3>
|
||||
<p class="gh-product-card-description">
|
||||
Product description
|
||||
</p>
|
||||
<LinkTo @route="settings.products" class="gh-btn" {{action (toggle "showFreeMembershipModal" this)}}>
|
||||
<span>Customize</span>
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
<LinkTo @route="settings.product" @model={{product}} class="gh-list-data gh-list-cellwidth-30 gh-product-list-chevron">
|
||||
<div class="gh-product-chevron">
|
||||
{{!-- <span>330 members</span> --}}
|
||||
<span>{{svg-jar "arrow-right" class="w6 h6 fill-midgrey pa1"}}</span>
|
||||
</div>
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</section>
|
||||
{{#each this.products as |product|}}
|
||||
<div class="gh-product-card">
|
||||
<span class="gh-product-list-icon"><div class="gh-product-list-siteicon" style={{this.iconStyle}}></div></span>
|
||||
<h3 class="gh-product-card-name">
|
||||
{{product.name}}
|
||||
</h3>
|
||||
<p class="gh-product-card-description">
|
||||
Product description
|
||||
</p>
|
||||
<LinkTo @route="settings.product" @model={{product}} class="gh-btn">
|
||||
<span>Customize</span>
|
||||
</LinkTo>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{{#if this.showFreeMembershipModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="free-membership-settings"
|
||||
@close={{this.closeFreeMembershipModal}}
|
||||
@modifier="action wide product-ssprice" />
|
||||
{{/if}}
|
||||
|
Loading…
Reference in New Issue
Block a user