First round of getting better styles into the new dashboard

refs: https://github.com/TryGhost/Team/issues/1462

- remove dummy percentages where not necessary
- trying out a narrower width for the dashboard
- added in a basic layout for the various charts
- added more specific styles to each of the graphs
- tried out a different way of displaying metrics
- different way to show headers for each module
- made the grid and flex layouts a little more flexible
- there are some bits to still clean up with code
This commit is contained in:
James Morris 2022-03-29 16:53:50 +01:00
parent 60a3b742ea
commit 3b9525dd63
19 changed files with 841 additions and 163 deletions

View File

@ -1,90 +1,95 @@
<section {{did-insert this.onInsert}} class="gh-dashboard5"> <section {{did-insert this.onInsert}}>
{{#if this.isLoading }} {{#if this.isLoading }}
<GhLoadingSpinner /> <GhLoadingSpinner />
{{else}} {{else}}
{{#if this.areMembersEnabled}} {{#if this.areMembersEnabled}}
<section class="gh-dashboard5-section"> <section class="gh-dashboard5-section gh-dashboard5-overview">
<Dashboard::V5::ChartMembersCounts /> <article class="gh-dashboard5-box">
<Dashboard::V5::ChartTotalMembers @days={{this.days}} />
</article>
<div class="prototype-selection">
<PowerSelect
@selected={{this.selectedDaysOption}}
@options={{this.daysOptions}}
@searchEnabled={{false}}
@onChange={{this.onDaysChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |option|
>
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
</PowerSelect>
</div>
</section> </section>
<section class="gh-dashboard5-section"> {{#if this.hasPaidTiers}}
<div class="gh-dashboard5-header"> <div class="gh-dashboard5-paid">
<h3>Members</h3> <section class="gh-dashboard5-section">
<div class="prototype-selection"> <div class="gh-dashboard5-growth">
<PowerSelect <article class="gh-dashboard5-box">
@selected={{this.selectedDaysOption}} <Dashboard::V5::ChartMonthlyRevenue @days={{this.days}} />
@options={{this.daysOptions}} </article>
@searchEnabled={{false}}
@onChange={{this.onDaysChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |option|
>
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
</PowerSelect>
</div>
</div>
<div class="gh-dashboard5-growth"> <article class="gh-dashboard5-box">
<Dashboard::V5::ChartTotalPaid @days={{this.days}} />
</article>
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartPaidMembers @days={{this.days}}/>
</article>
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartPaidMix />
</article>
</div>
</section>
</div>
{{/if}}
{{#if this.areNewslettersEnabled}}
<section class="gh-dashboard5-split is-third">
<section class="gh-dashboard5-section gh-dashboard5-email">
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartEmail />
</article>
</section>
<section class="gh-dashboard5-section gh-dashboard5-engagement">
<Dashboard::V5::ChartEngagement />
</section>
</section>
{{else}}
<section class="gh-dashboard5-section gh-dashboard5-engagement">
<Dashboard::V5::ChartEngagement />
</section>
{{/if}}
{{else}}
{{#if this.areNewslettersEnabled}}
<section class="gh-dashboard5-section gh-dashboard5-email">
<article class="gh-dashboard5-box"> <article class="gh-dashboard5-box">
<Dashboard::V5::ChartTotalMembers @days={{this.days}} /> <Dashboard::V5::ChartEmail />
</article> </article>
</section>
{{#if this.hasPaidTiers}} {{/if}}
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartMonthlyRevenue @days={{this.days}} />
</article>
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartTotalPaid @days={{this.days}} />
</article>
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartPaidMembers @days={{this.days}}/>
</article>
<article class="gh-dashboard5-box">
<Dashboard::V5::ChartPaidMix />
</article>
{{/if}}
</div>
</section>
<section class="gh-dashboard5-section">
<Dashboard::V5::ChartEngagement />
</section>
{{/if}}
{{#if this.areNewslettersEnabled}}
<section class="gh-dashboard5-section">
<Dashboard::V5::ChartEmail />
</section>
{{/if}} {{/if}}
<section class="gh-dashboard5-split"> <section class="gh-dashboard5-split">
<section class="gh-dashboard5-section"> <section class="gh-dashboard5-section gh-dashboard5-posts">
<div class="gh-dashboard5-header">
<h3>Recent posts</h3>
</div>
<article class="gh-dashboard5-box"> <article class="gh-dashboard5-box">
<Dashboard::V5::RecentPosts /> <Dashboard::V5::RecentPosts />
</article> </article>
</section> </section>
<section class="gh-dashboard5-section"> <section class="gh-dashboard5-section gh-dashboard5-activity">
<div class="gh-dashboard5-header"> <Dashboard::V5::RecentActivity />
<h3>Recent activity</h3>
</div>
<Dashboard::LatestMemberActivity />
</section> </section>
</section> </section>
<section class="gh-dashboard5-section"> <section class="gh-dashboard5-section">
<div class="gh-dashboard5-header">
<h3>Resources</h3>
</div>
<div class="gh-main-section gh-offers-help"> <div class="gh-main-section gh-offers-help">
<div class="gh-main-section-block"> <div class="gh-main-section-block">
<div class="gh-main-section-content grey"> <div class="gh-main-section-content grey">

View File

@ -1,21 +1,23 @@
<div class="gh-dashboard5-header"> <h3 {{did-insert this.loadCharts}}>Email</h3>
<h3 {{did-insert this.loadCharts}}>Email</h3> <div class="gh-dashboard5-insert">
</div>
<div class="gh-dashboard5-container gh-dashboard5-email">
<div class="gh-dashboard5-box"> <div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">Newsletter subscribers</h4> {{!-- <h4 class="gh-dashboard5-metric">Newsletter subscribers</h4> --}}
<div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.total}}</div> {{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.total}}</div> --}}
<div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.paid}} paid</div> {{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.paid}} paid</div> --}}
<div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.free}} free</div> {{!-- <div class="gh-dashboard5-number is-small">{{format-number this.dataSubscribers.free}} free</div> --}}
<h4 class="gh-dashboard5-metric is-split">Email</h4>
<div class="gh-dashboard5-number">{{format-number this.dataSubscribers.total}}</div>
<small class="gh-dashboard5-info">Newsletter subscribers</small>
</div> </div>
<div class="gh-dashboard5-box"> <div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">Emails sent in the past 30 days</h4> <h4 class="gh-dashboard5-metric is-split">&nbsp;</h4>
<div class="gh-dashboard5-number is-small">{{format-number this.dataEmailsSent}}</div> <div class="gh-dashboard5-number">{{format-number this.dataEmailsSent}}</div>
<small class="gh-dashboard5-info">Sent in the past 30 days</small>
</div> </div>
<div class="gh-dashboard5-box"> <div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric is-main">Email open rate</h4>
{{#if this.loading}} {{#if this.loading}}
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/> <div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
{{else}} {{else}}
@ -25,5 +27,9 @@
@options={{this.chartOptions}} @options={{this.chartOptions}}
@height={{this.chartHeight}} /> @height={{this.chartHeight}} />
{{/if}} {{/if}}
<div>
<div class="gh-dashboard5-number">58%</div>
<small class="gh-dashboard5-info">Open rate</small>
</div>
</div> </div>
</div> </div>

View File

@ -49,20 +49,61 @@ export default class ChartEmailOpenRate extends Component {
data, data,
fill: false, fill: false,
backgroundColor: '#14b8ff', backgroundColor: '#14b8ff',
tension: 0.1 tension: 0.1,
cubicInterpolationMode: 'monotone',
fill: false,
pointRadius: 0,
pointHitRadius: 10,
borderColor: '#14b8ff',
borderJoinStyle: 'miter',
maxBarThickness: 20,
minBarLength: 2
}] }]
}; };
} }
get chartOptions() { get chartOptions() {
return { return {
title: {
display: false
},
legend: { legend: {
display: false display: false
},
scales: {
yAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
fontColor: '#7C8B9A',
padding: 8,
precision: 0
}
}],
xAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
autoSkip: true,
maxRotation: 0,
minRotation: 0
}
}]
} }
}; };
} }
get chartHeight() { get chartHeight() {
return 100; return 175;
} }
} }

View File

@ -1,30 +1,33 @@
<div class="gh-dashboard5-header"> <h3 {{did-insert this.loadCharts}}>Engagement</h3>
<h3 {{did-insert this.loadCharts}}>Engagement</h3>
<div class="prototype-selection"> <article class="gh-dashboard5-box">
<PowerSelect <div class="gh-dashboard5-insert">
@selected={{this.selectedStatusOption}} <div class="gh-dashboard5-box">
@options={{this.statusOptions}} <h4 class="gh-dashboard5-metric is-split">Engagement</h4>
@searchEnabled={{false}} <div class="gh-dashboard5-number">{{this.data30Days}}</div>
@onChange={{this.onSwitchStatus}} <small class="gh-dashboard5-info">Last 30 days</small>
@triggerComponent="gh-power-select/trigger" </div>
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown" <div class="gh-dashboard5-box">
@matchTriggerWidth={{false}} <h4 class="gh-dashboard5-metric is-split">&nbsp;</h4>
as |option| <div class="gh-dashboard5-number">{{this.data7Days}}</div>
> <small class="gh-dashboard5-info">Last 7 days</small>
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}} </div>
</PowerSelect>
</div> </div>
</article>
<div class="prototype-selection">
<PowerSelect
@selected={{this.selectedStatusOption}}
@options={{this.statusOptions}}
@searchEnabled={{false}}
@onChange={{this.onSwitchStatus}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |option|
>
{{#if option.name}}{{option.name}}{{else}}<span class="red">Unknown option</span>{{/if}}
</PowerSelect>
</div> </div>
<div class="gh-dashboard5-container">
<div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">Engaged in past 30 days</h4>
<div class="gh-dashboard5-number is-small">{{this.data30Days}}</div>
</div>
<div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">Engaged in past 7 days</h4>
<div class="gh-dashboard5-number is-small">{{this.data7Days}}</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
<div class="gh-dashboard5-insert">
<div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">{{gh-pluralize this.totalMembers "Total member" without-count=true}}</h4>
<div class="gh-dashboard5-number">{{format-number this.totalMembers}}</div>
{{#if this.hasTrends}}
<Dashboard::v5::parts::ChartPercentage @percentage={{this.totalMembersTrend}}/>
{{/if}}
</div>
<div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">{{gh-pluralize this.paidMembers "Paid member" without-count=true}}</h4>
<div class="gh-dashboard5-number">{{format-number this.paidMembers}}</div>
{{#if this.hasTrends}}
<Dashboard::v5::parts::ChartPercentage @percentage={{this.paidMembersTrend}}/>
{{/if}}
</div>
<div class="gh-dashboard5-box">
<h4 class="gh-dashboard5-metric">{{gh-pluralize this.freeMembers "Free member" without-count=true}}</h4>
<div class="gh-dashboard5-number">{{format-number this.freeMembers}}</div>
{{#if this.hasTrends}}
<Dashboard::v5::parts::ChartPercentage @percentage={{this.freeMembersTrend}}/>
{{/if}}
</div>
</div>

View File

@ -0,0 +1,51 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class ChartMembersCountInsert extends Component {
@service dashboardStats;
@action
loadCharts() {
this.dashboardStats.loadMembersCounts();
}
get totalMembers() {
return this.dashboardStats.memberCounts?.total ?? 0;
}
get paidMembers() {
return this.dashboardStats.memberCounts?.paid ?? 0;
}
get freeMembers() {
return this.dashboardStats.memberCounts?.free ?? 0;
}
get hasTrends() {
return this.dashboardStats.memberCounts !== null && this.dashboardStats.memberCountsTrend !== null;
}
get totalMembersTrend() {
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.total, this.dashboardStats.memberCounts.total);
}
get paidMembersTrend() {
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.paid, this.dashboardStats.memberCounts.paid);
}
get freeMembersTrend() {
return this.calculatePercentage(this.dashboardStats.memberCountsTrend.free, this.dashboardStats.memberCounts.free);
}
calculatePercentage(from, to) {
if (from === 0) {
if (to > 0) {
return 100;
}
return 0;
}
return Math.round((to - from) / from * 100);
}
}

View File

@ -1,10 +1,9 @@
<h4 <div class="gh-dashboard5-number"
class="gh-dashboard5-metric is-main"
{{did-insert this.loadCharts}} {{did-insert this.loadCharts}}
{{did-update this.loadCharts @days}} {{did-update this.loadCharts @days}}
> >$32</div>
Monthly revenue (MMR) <small class="gh-dashboard5-info">Monthly revenue (MMR)</small>
</h4>
{{#if this.loading}} {{#if this.loading}}
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/> <div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
{{else}} {{else}}

View File

@ -32,22 +32,77 @@ export default class ChartMonthlyRevenue extends Component {
labels: labels, labels: labels,
datasets: [{ datasets: [{
data: data, data: data,
fill: false, tension: 0.1,
cubicInterpolationMode: 'monotone',
fill: true,
fillColor: '#F5FBFF',
backgroundColor: '#F5FBFF',
pointRadius: 0,
pointHitRadius: 10,
borderColor: '#14b8ff', borderColor: '#14b8ff',
tension: 0.1 borderJoinStyle: 'miter',
maxBarThickness: 20,
minBarLength: 2
}] }]
}; };
} }
get chartOptions() { get chartOptions() {
return { return {
title: {
display: false
},
legend: { legend: {
display: false display: false
},
scales: {
yAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
fontColor: '#7C8B9A',
padding: 8,
precision: 0
}
}],
xAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
autoSkip: true,
maxRotation: 0,
minRotation: 0
},
type: 'time',
time: {
displayFormats: {
'millisecond': 'MMM DD',
'second': 'MMM DD',
'minute': 'MMM DD',
'hour': 'MMM DD',
'day': 'MMM DD',
'week': 'MMM DD',
'month': 'MMM DD',
'quarter': 'MMM DD',
'year': 'MMM DD',
}
}
}]
} }
}; };
} }
get chartHeight() { get chartHeight() {
return 100; return 200;
} }
} }

View File

@ -5,6 +5,7 @@
> >
Paid members Paid members
</h4> </h4>
{{#if this.loading}} {{#if this.loading}}
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/> <div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
{{else}} {{else}}

View File

@ -34,22 +34,75 @@ export default class ChartPaidMembers extends Component {
datasets: [ datasets: [
{ {
data: newData, data: newData,
fill: false, fill: true,
backgroundColor: '#14b8ff', backgroundColor: '#14b8ff',
tension: 0.1 tension: 0.1,
borderWidth: 0,
barThickness: 10,
minBarLength: 3
},{ },{
data: canceledData, data: canceledData,
fill: false, fill: true,
backgroundColor: '#E16262', backgroundColor: '#E16262',
tension: 0.1 tension: 0.1,
borderWidth: 0,
barThickness: 10,
minBarLength: 3
}] }]
}; };
} }
get chartOptions() { get chartOptions() {
return { return {
title: {
display: false
},
legend: { legend: {
display: false display: false
},
scales: {
yAxes: [{
gridLines: {
drawTicks: false,
display: true,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
fontColor: '#7C8B9A',
padding: 8,
precision: 0
}
}],
xAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
autoSkip: true,
maxRotation: 0,
minRotation: 0
},
type: 'time',
time: {
displayFormats: {
'millisecond': 'MMM DD',
'second': 'MMM DD',
'minute': 'MMM DD',
'hour': 'MMM DD',
'day': 'MMM DD',
'week': 'MMM DD',
'month': 'MMM DD',
'quarter': 'MMM DD',
'year': 'MMM DD',
}
}
}]
} }
}; };
} }

View File

@ -1,13 +1,14 @@
<h4 {{!-- <h4
class="gh-dashboard5-metric is-main" class="gh-dashboard5-metric is-main"
{{did-insert this.loadCharts}} {{did-insert this.loadCharts}}
{{did-update this.loadCharts @days}} {{did-update this.loadCharts @days}}
> >
Total members Total members
</h4> </h4> --}}
{{#if this.loading}} {{#if this.loading}}
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/> <div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>
{{else}} {{else}}
<Dashboard::V5::ChartMembersCountsInsert />
<EmberChart <EmberChart
@type={{this.chartType}} @type={{this.chartType}}
@data={{this.chartData}} @data={{this.chartData}}

View File

@ -32,22 +32,77 @@ export default class ChartTotalMembers extends Component {
labels: labels, labels: labels,
datasets: [{ datasets: [{
data: data, data: data,
fill: false, tension: 0.1,
cubicInterpolationMode: 'monotone',
fill: true,
fillColor: '#F5FBFF',
backgroundColor: '#F5FBFF',
pointRadius: 0,
pointHitRadius: 10,
borderColor: '#14b8ff', borderColor: '#14b8ff',
tension: 0.1 // borderJoinStyle: 'miter',
maxBarThickness: 20,
minBarLength: 2
}] }]
}; };
} }
get chartOptions() { get chartOptions() {
return { return {
title: {
display: false
},
legend: { legend: {
display: false display: false
},
scales: {
yAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
fontColor: '#7C8B9A',
padding: 8,
precision: 0
}
}],
xAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
autoSkip: true,
maxRotation: 0,
minRotation: 0
},
type: 'time',
time: {
displayFormats: {
'millisecond': 'MMM DD',
'second': 'MMM DD',
'minute': 'MMM DD',
'hour': 'MMM DD',
'day': 'MMM DD',
'week': 'MMM DD',
'month': 'MMM DD',
'quarter': 'MMM DD',
'year': 'MMM DD',
}
}
}]
} }
}; };
} }
get chartHeight() { get chartHeight() {
return 75; return 50;
} }
} }

View File

@ -1,10 +1,8 @@
<h4 <div class="gh-dashboard5-number"
class="gh-dashboard5-metric is-main"
{{did-insert this.loadCharts}} {{did-insert this.loadCharts}}
{{did-update this.loadCharts @days}} {{did-update this.loadCharts @days}}
> >$999</div>
Total paid <small class="gh-dashboard5-info">Total paid</small>
</h4>
{{#if this.loading}} {{#if this.loading}}
<div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/> <div class="gh-dashboard5-loading" style={{html-safe (concat "height: " this.chartHeight "px;")}}/>

View File

@ -32,22 +32,77 @@ export default class ChartTotalPaid extends Component {
labels, labels,
datasets: [{ datasets: [{
data, data,
fill: false, tension: 0.1,
cubicInterpolationMode: 'monotone',
fill: true,
fillColor: '#F5FBFF',
backgroundColor: '#F5FBFF',
pointRadius: 0,
pointHitRadius: 10,
borderColor: '#14b8ff', borderColor: '#14b8ff',
tension: 0.1 borderJoinStyle: 'miter',
maxBarThickness: 20,
minBarLength: 2
}] }]
}; };
} }
get chartOptions() { get chartOptions() {
return { return {
title: {
display: false
},
legend: { legend: {
display: false display: false
},
scales: {
yAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
fontColor: '#7C8B9A',
padding: 8,
precision: 0
}
}],
xAxes: [{
gridLines: {
drawTicks: false,
display: false,
drawBorder: false
},
ticks: {
display: false,
maxTicksLimit: 5,
autoSkip: true,
maxRotation: 0,
minRotation: 0
},
type: 'time',
time: {
displayFormats: {
'millisecond': 'MMM DD',
'second': 'MMM DD',
'minute': 'MMM DD',
'hour': 'MMM DD',
'day': 'MMM DD',
'week': 'MMM DD',
'month': 'MMM DD',
'quarter': 'MMM DD',
'year': 'MMM DD',
}
}
}]
} }
}; };
} }
get chartHeight() { get chartHeight() {
return 100; return 50;
} }
} }

View File

@ -0,0 +1,66 @@
{{#if this.shouldDisplay}}
<div class="gh-dashboard-box activity gh-list" data-test-dashboard-member-activity>
{{!-- <li class="gh-list-row header">
<div class="gh-list-header gh-posts-title-header">Recent Activity</div>
</li> --}}
<h4 class="gh-dashboard5-metric is-split">Recent activity</h4>
<div class="content">
{{#let (members-event-fetcher filter=(if (feature "membersActivityFeed") (members-event-filter excludeEmailEvents=true)) pageSize=5) as |eventsFetcher|}}
{{#if eventsFetcher.isLoading}}
Loading...
{{/if}}
{{#if eventsFetcher.isError}}
<p class="error">
There was an error loading events
{{#if eventsFetcher.errorMessage}}
<code>{{eventsFetcher.errorMessage}}</code>
{{/if}}
</p>
{{/if}}
{{#unless (or eventsFetcher.isLoading eventsFetcher.isError)}}
<div class="gh-event-timeline">
{{#if eventsFetcher.data}}
<ul class="gh-dashboard-activity-list">
{{#each eventsFetcher.data as |event|}}
{{#let (parse-member-event event) as |parsedEvent|}}
<li class="gh-dashboard-activity-item" data-test-dashboard-member-activity-item>
<LinkTo class="member-details" @route="member" @model="{{parsedEvent.memberId}}">
<div class="gh-dashboard-activity-container">
{{svg-jar parsedEvent.icon}}
<div class="gh-dashboard-activity-detail">
<div class="gh-dashboard-activity-name">
{{parsedEvent.subject}}
</div>
<div class="gh-dashboard-activity-event">
{{parsedEvent.action}}
{{parsedEvent.object}}
<span class="highlight">{{parsedEvent.info}}</span>
</div>
</div>
</div>
</LinkTo>
<span class="gh-dashboard-activity-time">{{moment-from-now parsedEvent.timestamp}}</span>
</li>
{{/let}}
{{/each}}
</ul>
{{else}}
<div class="gh-no-data-list" data-test-no-member-activities>
{{svg-jar "no-data-list"}}
<span>No member activity available.</span>
</div>
{{/if}}
</div>
{{#if (feature "membersActivityFeed")}}
<div class="footer">
<LinkTo @route="members-activity" @query={{reset-query-params "members-activity"}}>See all activity →</LinkTo>
</div>
{{/if}}
{{/unless}}
{{/let}}
</div>
</div>
{{/if}}

View File

@ -0,0 +1,23 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
export default class RecentActivity extends Component {
@service feature;
@service session;
@service settings;
get shouldDisplay() {
if (this.feature.improvedOnboarding) {
return true;
}
const isOwner = this.session.user?.isOwnerOnly;
const hasCompletedLaunchWizard = this.settings.get('editorIsLaunchComplete');
if (isOwner && !hasCompletedLaunchWizard) {
return false;
}
return true;
}
}

View File

@ -1,8 +1,12 @@
<ol class="gh-list" {{did-insert this.loadPosts}}> <ol class="gh-list" {{did-insert this.loadPosts}}>
<li class="gh-list-row header"> <li class="gh-list-row header">
<h4 class="gh-dashboard5-metric is-split">Recent posts</h4>
{{!--
<div class="gh-list-header gh-posts-title-header">Title</div> <div class="gh-list-header gh-posts-title-header">Title</div>
<div class="gh-list-header gh-posts-sends-header">Sends</div> <div class="gh-list-header gh-posts-sends-header">Sends</div>
<div class="gh-list-header gh-posts-opens-header">Opens</div> <div class="gh-list-header gh-posts-opens-header">Opens</div>
--}}
</li> </li>
{{#each this.posts as |post|}} {{#each this.posts as |post|}}
<GhPostsListItem <GhPostsListItem

View File

@ -1138,16 +1138,26 @@ a.gh-dashboard-container {
font-size: 24px; font-size: 24px;
} }
.gh-dashboard5 {
max-width: 1230px;
margin: 0 auto;
}
.gh-dashboard5-split { .gh-dashboard5-split {
display: flex; display: grid;
gap: 24px; gap: 24px;
grid-template-columns: 1fr 1fr;
}
.gh-dashboard5-split.is-third {
grid-template-columns: 2fr 1fr;
} }
.gh-dashboard5-section { .gh-dashboard5-section {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 48px; margin-bottom: 24px;
} }
.gh-dashboard5-container { .gh-dashboard5-container {
@ -1185,7 +1195,7 @@ a.gh-dashboard-container {
.gh-dashboard5-number { .gh-dashboard5-number {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
font-size: 3.9rem; font-size: 3rem;
line-height: 4rem; line-height: 4rem;
font-weight: 600; font-weight: 600;
color: #15171a; color: #15171a;
@ -1196,7 +1206,7 @@ a.gh-dashboard-container {
} }
.gh-dashboard5-number.is-small { .gh-dashboard5-number.is-small {
font-size: 2.8rem; font-size: 2.2rem;
} }
.gh-dashboard5-metric { .gh-dashboard5-metric {
@ -1215,6 +1225,35 @@ a.gh-dashboard-container {
margin-bottom: 24px; margin-bottom: 24px;
} }
.gh-dashboard5-metric.is-split {
display: flex;
flex-direction: column;
align-items: flex-start;
font-size: 1.8rem;
text-transform: none;
margin: 0 0 32px;
}
.gh-dashboard5-metric.is-split small {
font-size: 1.1rem;
text-transform: none;
font-weight: 500;
letter-spacing: .2px;
margin: 4px 0 0 0;
padding: 0;
color: var(--midgrey);
}
.gh-dashboard5-info {
font-size: 1.3rem;
text-transform: none;
font-weight: 500;
letter-spacing: .2px;
margin: 2px 0 0;
padding: 0;
color: var(--midgrey);
}
.gh-dashboard5-percentage { .gh-dashboard5-percentage {
flex: 0; flex: 0;
background: var(--whitegrey-d1); background: var(--whitegrey-d1);
@ -1256,13 +1295,17 @@ a.gh-dashboard-container {
margin: 0 -8px 6px 0; margin: 0 -8px 6px 0;
} }
.gh-dashboard5-email { .gh-dashboard5-email > h3 {
display: none;
}
.gh-dashboard5-email .gh-dashboard5-container {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr; grid-template-rows: 1fr 1fr;
gap: 0; gap: 0;
} }
/*
.gh-dashboard5-email .gh-dashboard5-box { .gh-dashboard5-email .gh-dashboard5-box {
border-width: 1px 0 1px 1px; border-width: 1px 0 1px 1px;
border-radius: 0; border-radius: 0;
@ -1284,46 +1327,49 @@ a.gh-dashboard-container {
.gh-dashboard5-email .gh-dashboard5-box:nth-child(3) { .gh-dashboard5-email .gh-dashboard5-box:nth-child(3) {
grid-column: 2; grid-column: 2;
grid-row: 1 / span 2; grid-row: 1 / span 2;
} } */
.gh-dashboard5-growth { .gh-dashboard5-growth {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: auto auto auto; grid-template-rows: auto auto;
gap: 0; gap: 0;
margin-top: -25px;
} }
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(1) { /* .gh-dashboard5-growth .gh-dashboard5-box:nth-child(1) {
grid-column: 1 / span 2; grid-column: 1 / span 2;
grid-row: 1; grid-row: 1;
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
border-width: 1px; border-width: 1px;
} */
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(1) {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
border-radius: 3px 0 0 0;
border-width: 1px;
padding-top: 32px;
} }
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(2) { .gh-dashboard5-growth .gh-dashboard5-box:nth-child(2) {
grid-column: 1; grid-column: 3 / span 2;
grid-row: 2; grid-row: 1;
border-radius: 0; border-radius: 0 3px 0 0;
border-width: 0 1px 1px 1px; border-width: 1px 1px 1px 0;
padding-top: 32px;
} }
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(3) { .gh-dashboard5-growth .gh-dashboard5-box:nth-child(3) {
grid-column: 2; grid-column: 3;
grid-row: 2; grid-row: 2;
border-radius: 0; border-radius: 0 0 0 3px;
border-width: 0 1px 1px 0; border-width: 0 1px 1px 0;
} }
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(4) { .gh-dashboard5-growth .gh-dashboard5-box:nth-child(4) {
grid-column: 1; grid-column: 4;
grid-row: 3; grid-row: 2;
border-radius: 0 0 0 3px;
border-width: 0 1px 1px 1px;
}
.gh-dashboard5-growth .gh-dashboard5-box:nth-child(5) {
grid-column: 2;
grid-row: 3;
border-radius: 0 0 3px 0; border-radius: 0 0 3px 0;
border-width: 0 1px 1px 0; border-width: 0 1px 1px 0;
} }
@ -1338,4 +1384,191 @@ a.gh-dashboard-container {
.gh-dashboard5 .gh-offers-help { .gh-dashboard5 .gh-offers-help {
margin: 0; margin: 0;
}
.gh-dashboard5-overview {
position: relative;
}
.gh-dashboard5-overview > .gh-dashboard5-box {
padding: 0;
/* min-height: 346px; */
}
.gh-dashboard5-overview .gh-dashboard5-insert {
width: 75%;
display: flex;
flex-direction: row;
margin: 20px 28px 0px;
}
.gh-dashboard5-overview .gh-dashboard5-insert .gh-dashboard5-box {
border-radius: 0;
border-width: 0 1px 0 0;
padding-top: 8px;
padding-bottom: 8px;
}
.gh-dashboard5-overview .gh-dashboard5-insert .gh-dashboard5-box:first-child {
border-radius: 0;
border-width: 0 1px 0 0;
padding-left: 0;
}
.gh-dashboard5-overview .gh-dashboard5-insert .gh-dashboard5-box:last-child {
border-radius: 0;
border-width: 0;
}
.gh-dashboard5-overview .prototype-selection {
position: absolute;
top: 20px;
right: 20px;
}
.gh-dashboard5-engagement {
position: relative;
}
.gh-dashboard5-engagement > h3 {
display: none;
}
.gh-dashboard5-engagement > .gh-dashboard5-box {
padding: 0;
align-items: stretch;
}
.gh-dashboard5-engagement .gh-dashboard5-insert {
display: flex;
flex-direction: row;
margin: 20px 28px;
flex: 1;
}
.gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box {
border-radius: 0;
border-width: 0 1px 0 0;
padding: 8px 24px 0 0;
}
.gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
border-left-width: 0;
flex-direction: column;
justify-content: flex-end;
padding-left: 24px;
}
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert {
display: flex;
flex-direction: column;
margin: 20px 28px;
flex: 1;
}
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box {
border-radius: 0;
border-width: 0 0 1px;
padding: 8px 24px 24px 0;
}
.gh-dashboard5-split .gh-dashboard5-engagement .gh-dashboard5-insert .gh-dashboard5-box:last-child {
border-bottom-width: 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-bottom: 0;
}
.gh-dashboard5-section .prototype-selection {
position: absolute;
top: 18px;
right: 16px;
}
.gh-dashboard5-section .ember-power-select-selected-item {
font-size: 1.1rem;
text-transform: uppercase;
font-weight: 500;
letter-spacing: .2px;
margin: 0 0 8px;
padding: 0;
color: #15171a;
}
.gh-dashboard5 .gh-list-header {
border-bottom: 0;
}
.gh-dashboard5-posts .gh-content-entry-title {
font-weight: 600;
font-size: 14px !important;
color: rgb(21, 23, 26);
/* white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%; */
}
.gh-dashboard5-posts .gh-dashboard5-box {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-bottom: 16px;
}
.gh-dashboard5-posts .footer {
padding-top: 12px;
}
.gh-dashboard5-email h3 {
display: none;
}
.gh-dashboard5-email canvas {
border-bottom: 1px solid #ebeef0;
}
.gh-dashboard5-email > .gh-dashboard5-box {
padding: 0;
align-items: stretch;
}
.gh-dashboard5-email .gh-dashboard5-insert {
margin: 20px 28px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 0;
}
.gh-dashboard5-email .gh-dashboard5-insert .gh-dashboard5-box:nth-child(1) {
grid-column: 1;
grid-row: 1;
border-radius: 0;
border-width: 0 1px 1px 0;
padding: 8px 24px 24px 0;
}
.gh-dashboard5-email .gh-dashboard5-insert .gh-dashboard5-box:nth-child(2) {
grid-column: 1;
grid-row: 2;
border-radius: 0;
border-width: 0 1px 0 0;
padding: 20px 24px 0 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.gh-dashboard5-email .gh-dashboard5-insert .gh-dashboard5-box:nth-child(3) {
grid-column: 2;
grid-row: 1 / span 2;
border-radius: 0;
border-width: 0;
padding: 8px 0 0 28px;
display: flex;
flex-direction: column;
justify-content: space-between;
} }

View File

@ -1,13 +1,19 @@
<section class="gh-canvas" {{scroll-top}}> <section class="gh-canvas" {{scroll-top}}>
<GhCanvasHeader class="gh-canvas-header">
<h2 class="gh-canvas-title" data-test-screen-title>
Dashboard
</h2>
</GhCanvasHeader>
{{#if (feature "dashboardV5")}} {{#if (feature "dashboardV5")}}
<Dashboard::DashboardV5 /> <div class="gh-dashboard5">
<GhCanvasHeader class="gh-canvas-header">
<h2 class="gh-canvas-title" data-test-screen-title>
Dashboard
</h2>
</GhCanvasHeader>
<Dashboard::DashboardV5 />
</div>
{{else}} {{else}}
<GhCanvasHeader class="gh-canvas-header">
<h2 class="gh-canvas-title" data-test-screen-title>
Dashboard
</h2>
</GhCanvasHeader>
<div class="view-container gh-dashboard"> <div class="view-container gh-dashboard">
{{#if (and this.session.user.isOwnerOnly (not this.settings.editorIsLaunchComplete) (not (feature "improvedOnboarding")))}} {{#if (and this.session.user.isOwnerOnly (not this.settings.editorIsLaunchComplete) (not (feature "improvedOnboarding")))}}
<section class="gh-dashboard-area lw-banner"> <section class="gh-dashboard-area lw-banner">