mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Added static components for custom products
- added link to Products in Settings main screen - added product list page with static content - added product detail page with static content
This commit is contained in:
parent
d350a58769
commit
6ee31e206b
37
ghost/admin/app/controllers/settings/product.js
Normal file
37
ghost/admin/app/controllers/settings/product.js
Normal file
@ -0,0 +1,37 @@
|
||||
import Controller from '@ember/controller';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class ProductController extends Controller {
|
||||
@service settings;
|
||||
|
||||
@tracked showLeaveSettingsModal = false;
|
||||
|
||||
leaveRoute(transition) {
|
||||
if (this.settings.get('hasDirtyAttributes')) {
|
||||
transition.abort();
|
||||
this.leaveSettingsTransition = transition;
|
||||
this.showLeaveSettingsModal = true;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async confirmLeave() {
|
||||
this.settings.rollbackAttributes();
|
||||
this.showLeaveSettingsModal = false;
|
||||
this.leaveSettingsTransition.retry();
|
||||
}
|
||||
|
||||
@action
|
||||
cancelLeave() {
|
||||
this.showLeaveSettingsModal = false;
|
||||
this.leaveSettingsTransition = null;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*saveTask() {
|
||||
return yield this.settings.save();
|
||||
}
|
||||
}
|
3
ghost/admin/app/controllers/settings/products.js
Normal file
3
ghost/admin/app/controllers/settings/products.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class ProductsController extends Controller {}
|
@ -52,6 +52,11 @@ Router.map(function () {
|
||||
this.route('settings.members-email', {path: '/settings/members-email'});
|
||||
this.route('settings.members-payments', {path: '/settings/members-payments'});
|
||||
this.route('settings.code-injection', {path: '/settings/code-injection'});
|
||||
|
||||
this.route('settings.products', {path: '/settings/products'});
|
||||
this.route('settings.product', {path: '/settings/product'});
|
||||
this.route('settings.price', {path: '/settings/price'});
|
||||
|
||||
this.route('settings.theme', {path: '/settings/theme'}, function () {
|
||||
this.route('uploadtheme');
|
||||
this.route('install');
|
||||
|
22
ghost/admin/app/routes/settings/product.js
Normal file
22
ghost/admin/app/routes/settings/product.js
Normal file
@ -0,0 +1,22 @@
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class ProductRoute extends AuthenticatedRoute {
|
||||
@service settings;
|
||||
|
||||
model() {
|
||||
this.settings.reload();
|
||||
}
|
||||
|
||||
actions = {
|
||||
willTransition(transition) {
|
||||
return this.controller.leaveRoute(transition);
|
||||
}
|
||||
}
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
titleToken: 'Settings - Products'
|
||||
};
|
||||
}
|
||||
}
|
9
ghost/admin/app/routes/settings/products.js
Normal file
9
ghost/admin/app/routes/settings/products.js
Normal file
@ -0,0 +1,9 @@
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
|
||||
export default class ProductsRoute extends AuthenticatedRoute {
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
titleToken: 'Settings - Products'
|
||||
};
|
||||
}
|
||||
}
|
@ -66,6 +66,7 @@
|
||||
@import "layouts/fullscreen-wizard.css";
|
||||
@import "layouts/post-preview.css";
|
||||
@import "layouts/dashboard.css";
|
||||
@import "layouts/products.css";
|
||||
|
||||
|
||||
:root {
|
||||
|
@ -66,6 +66,7 @@
|
||||
@import "layouts/fullscreen-wizard.css";
|
||||
@import "layouts/post-preview.css";
|
||||
@import "layouts/dashboard.css";
|
||||
@import "layouts/products.css";
|
||||
|
||||
|
||||
/* ---------------------------✈️----------------------------- */
|
||||
|
@ -1108,6 +1108,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gh-main-section-block.span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.gh-main-section-block.with-margin,
|
||||
.gh-main-section-block:not(:last-of-type) {
|
||||
margin-bottom: 32px;
|
||||
@ -1135,6 +1139,12 @@
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-main-section-content.bordered {
|
||||
padding: 24px;
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-main-section-content.columns-2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-column-gap: 40px;
|
||||
@ -1145,6 +1155,7 @@
|
||||
grid-column-gap: 24px;
|
||||
}
|
||||
|
||||
|
||||
.gh-main-section-content.grey {
|
||||
background: var(--main-color-content-greybg);
|
||||
}
|
||||
|
83
ghost/admin/app/styles/layouts/products.css
Normal file
83
ghost/admin/app/styles/layouts/products.css
Normal file
@ -0,0 +1,83 @@
|
||||
/* Product list */
|
||||
.gh-product-list-chevron {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.gh-product-chevron {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-product-chevron span {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* Product details */
|
||||
.gh-product-details {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
grid-gap: 32px;
|
||||
margin-bottom: 3vw;
|
||||
}
|
||||
|
||||
.gh-product-details-form {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: flex-start;
|
||||
padding-top: 20px !important;
|
||||
}
|
||||
|
||||
.gh-product-icon-container {
|
||||
width: unset;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.gh-product-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--white);
|
||||
width: 124px;
|
||||
height: 124px;
|
||||
margin-right: 24px;
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-product-details-fields {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gh-product-description-container {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.gh-product-details section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.gh-product-stat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
border: 1px solid var(--whitegrey);
|
||||
padding: 24px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-product-chart {
|
||||
color: var(--whitegrey);
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-top-color: transparent;
|
||||
height: 90px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 12px 0;
|
||||
}
|
@ -48,9 +48,16 @@
|
||||
<p>Configure members usage and default access levels</p>
|
||||
</div>
|
||||
</LinkTo>
|
||||
<LinkTo class="gh-setting-group" @route="settings.products" data-test-nav="members-access">
|
||||
<span class="color-4">{{svg-jar "module"}}</span>
|
||||
<div>
|
||||
<h4>Products</h4>
|
||||
<p>Set up subscription products and prices</p>
|
||||
</div>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
<button type="button" class="gh-setting-group portal" {{action (toggle "showPortalSettings" this)}} data-test-toggle-membersFrom>
|
||||
<span class="color-3">{{svg-jar "portal-logo-stroke"}}</span>
|
||||
<span class="color-2">{{svg-jar "portal-logo-stroke"}}</span>
|
||||
<div>
|
||||
<h4>Portal</h4>
|
||||
<p>Customize members modal signup flow</p>
|
||||
|
125
ghost/admin/app/templates/settings/product.hbs
Normal file
125
ghost/admin/app/templates/settings/product.hbs
Normal file
@ -0,0 +1,125 @@
|
||||
<section class="gh-canvas">
|
||||
<GhCanvasHeader class="gh-canvas-header">
|
||||
<h2 class="gh-canvas-title" data-test-screen-title>
|
||||
<LinkTo @route="settings">Settings</LinkTo>
|
||||
<span>{{svg-jar "arrow-right"}}</span>
|
||||
<LinkTo @route="settings.products">Products</LinkTo>
|
||||
<span>{{svg-jar "arrow-right"}}</span>
|
||||
Product name
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<GhTaskButton @buttonText="Save product"
|
||||
@task={{this.saveTask}}
|
||||
@successText="Saved"
|
||||
@runningText="Saving"
|
||||
@class="gh-btn gh-btn-primary gh-btn-icon"
|
||||
data-test-button="save-settings"
|
||||
/>
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
<form>
|
||||
<div class="gh-product-details">
|
||||
<section class="gh-product-config">
|
||||
<div class="gh-main-section-content grey gh-product-details-form">
|
||||
<GhFormGroup @property="name" @class="gh-product-icon-container">
|
||||
<label for="member-name">Icon</label>
|
||||
<div class="gh-product-icon whitegrey">Icon</div>
|
||||
</GhFormGroup>
|
||||
<div class="gh-product-details-fields">
|
||||
<GhFormGroup @property="name" @classNames="max-width">
|
||||
<label for="member-name">Product name</label>
|
||||
<GhTextInput data-test-input="member-name" />
|
||||
<GhErrorMessage @property="name" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @property="name" @classNames="max-width gh-product-description-container">
|
||||
<label for="member-name">Description</label>
|
||||
<GhTextInput data-test-input="member-name" />
|
||||
<GhErrorMessage @property="name" />
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="gh-product-stats">
|
||||
<div class="gh-product-stat-container">
|
||||
<h4 class="gh-dashboard-header">Members</h4>
|
||||
<div class="gh-product-chart">CHART</div>
|
||||
<div class="gh-product-stat-details">
|
||||
<span class="data">330</span>
|
||||
<span class="info">members have access to this product</span>
|
||||
</div>
|
||||
<LinkTo @route="members">See members →</LinkTo>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="gh-main-section">
|
||||
<div class="gh-main-section-block p0">
|
||||
<ol class="gh-price-list gh-list">
|
||||
<li class="gh-list-row header">
|
||||
<div class="gh-list-header gh-list-cellwidth-70">Prices</div>
|
||||
<div class="gh-list-header gh-list-cellwidth-10"></div>
|
||||
</li>
|
||||
<li class="gh-list-row">
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-70 gh-price-list-title">
|
||||
<h3 class="gh-price-list-name">
|
||||
Monthly
|
||||
</h3>
|
||||
<p class="ma0 pa0 f8 midgrey gh-price-list-description">
|
||||
$5 USD / month • Full access
|
||||
</p>
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-30">
|
||||
<div class="gh-price-list-actions">
|
||||
|
||||
</div>
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="gh-list-row">
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-70 gh-price-list-title">
|
||||
<h3 class="gh-price-list-name">
|
||||
Yearly
|
||||
</h3>
|
||||
<p class="ma0 pa0 f8 midgrey gh-price-list-description">
|
||||
$50 USD / month • 37% discount
|
||||
</p>
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-30">
|
||||
<div class="gh-price-list-actions">
|
||||
|
||||
</div>
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li class="gh-list-row">
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-70 gh-price-list-title">
|
||||
<h3 class="gh-price-list-name">
|
||||
Complimentary
|
||||
</h3>
|
||||
<p class="ma0 pa0 f8 midgrey gh-price-list-description">
|
||||
$0 USD / year • Free of charge subscription
|
||||
</p>
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="settings.product" class="gh-list-data gh-list-cellwidth-30">
|
||||
<div class="gh-price-list-actions">
|
||||
|
||||
</div>
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.showLeaveSettingsModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="leave-settings"
|
||||
@confirm={{this.confirmLeave}}
|
||||
@close={{this.cancelLeave}}
|
||||
@modifier="action wide"
|
||||
/>
|
||||
{{/if}}
|
||||
</section>
|
40
ghost/admin/app/templates/settings/products.hbs
Normal file
40
ghost/admin/app/templates/settings/products.hbs
Normal file
@ -0,0 +1,40 @@
|
||||
<section class="gh-canvas">
|
||||
<GhCanvasHeader class="gh-canvas-header">
|
||||
<h2 class="gh-canvas-title" data-test-screen-title>
|
||||
<LinkTo @route="settings">Settings</LinkTo>
|
||||
<span>{{svg-jar "arrow-right"}}</span>
|
||||
Products
|
||||
</h2>
|
||||
<section class="view-actions">
|
||||
<LinkTo @route="settings.products" class="gh-btn gh-btn-primary" data-test-new-member-button="true"><span>New product</span></LinkTo>
|
||||
</section>
|
||||
</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>
|
||||
<li class="gh-list-row">
|
||||
<LinkTo @route="settings.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>
|
||||
|
||||
<LinkTo @route="settings.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>
|
||||
</ol>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
Loading…
Reference in New Issue
Block a user