Skip to content

Commit 7da1758

Browse files
authored
Merge pull request #895 from Codeinwp/prf/admin-widget
feat: adds dashboard widget
2 parents f7ecd32 + 8e0963a commit 7da1758

File tree

13 files changed

+425
-41
lines changed

13 files changed

+425
-41
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Icon } from '@wordpress/components';
2+
3+
import classnames from 'classnames';
4+
5+
export default function DashboardMetricBox({ value, label, description, icon, compact = false }) {
6+
const wrapClasses = classnames(
7+
'flex bg-light-blue border border-blue-300 rounded-md basis-1/4 flex-col items-start',
8+
{
9+
'p-4': compact,
10+
'p-6': ! compact
11+
}
12+
);
13+
14+
return (
15+
<div className={wrapClasses}>
16+
<Icon icon={ icon } />
17+
18+
<div className="flex w-full flex-col">
19+
<div className="not-italic font-bold text-xl py-2 text-gray-800">
20+
{ value }
21+
</div>
22+
23+
<div className="not-italic font-normal text-sm text-gray-800">
24+
{ label }
25+
</div>
26+
27+
<div className="font-normal text-xs text-gray-600">
28+
{ description }
29+
</div>
30+
</div>
31+
</div>
32+
);
33+
}

assets/src/dashboard/parts/components/ProgressBar.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const ProgressBar = ({
77
value,
88
max = 100,
99
className,
10+
colorOverage = false,
1011
...props
1112
}) => {
1213
const progress = Math.round( ( value / max ) * 100 );
@@ -17,6 +18,15 @@ const ProgressBar = ({
1718
className
1819
);
1920

21+
const progressClasses = classnames(
22+
'absolute left-0 h-full',
23+
{
24+
'bg-info': colorOverage ? 70 > progress : true,
25+
'bg-red-500': colorOverage && 100 < progress,
26+
'bg-amber-500': colorOverage && 70 < progress && 100 > progress
27+
}
28+
);
29+
2030
return (
2131
<div
2232
className={ wrapClasses }
@@ -26,7 +36,7 @@ const ProgressBar = ({
2636
aria-valuenow={ value }
2737
{ ...props }
2838
>
29-
<div className="absolute left-0 h-full bg-info" style={{ width: `${progress}%` }}></div>
39+
<div className={ progressClasses } style={{ width: `${progress}%` }}></div>
3040

3141
</div>
3242
);

assets/src/dashboard/parts/connected/dashboard/index.js

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from '../../../utils/icons';
2727

2828
import ProgressBar from '../../components/ProgressBar';
29+
import DashboardMetricBox from '../../components/DashboardMetricBox';
2930

3031
import LastImages from './LastImages';
3132

@@ -98,34 +99,30 @@ const Dashboard = () => {
9899

99100
const visitorsLimitPercent = ( ( userData.visitors / userData.visitors_limit ) * 100 ).toFixed( 0 );
100101

101-
const getMetricValue = metric => {
102-
if ( undefined !== userData[ metric ]) {
103-
return userData[ metric ];
104-
}
102+
const getFormattedMetric = ( metric ) => {
103+
let metricValue = userData[ metric ];
105104

106-
if ( 'saved_size' === metric ) {
107-
return Math.floor( Math.random() * 2500 ) + 500;
105+
// Fallback for missing data
106+
if ( undefined === metricValue ) {
107+
metricValue = 'saved_size' === metric ?
108+
Math.floor( Math.random() * 2500 ) + 500 :
109+
Math.floor( Math.random() * 40 ) + 10;
108110
}
109111

110-
return Math.floor( Math.random() * 40 ) + 10;
111-
};
112-
113-
const formatMetricValue = metric => {
114-
const value = getMetricValue( metric );
115-
112+
// Format based on metric type
116113
if ( 'saved_size' === metric ) {
117-
return ( value / 1000 ).toFixed( 2 ) + 'MB';
114+
return ( metricValue / 1000 ).toFixed( 2 ) + 'MB';
118115
}
119116

120117
if ( 'compression_percentage' === metric ) {
121-
return value.toFixed( 2 ) + '%';
118+
return metricValue.toFixed( 2 ) + '%';
122119
}
123120

124121
if ( 'traffic' === metric ) {
125-
return value.toFixed( 2 ) + 'MB';
122+
return metricValue.toFixed( 2 ) + 'MB';
126123
}
127124

128-
return value;
125+
return metricValue;
129126
};
130127

131128
return (
@@ -188,31 +185,15 @@ const Dashboard = () => {
188185
<div className="flex py-5 gap-5 flex-col md:flex-row">
189186
{ metrics.map( metric => {
190187
return (
191-
<div
188+
<DashboardMetricBox
192189
key={ metric.value }
193-
className={ classNames(
194-
cardClasses,
195-
'basis-1/4 flex-col items-start'
196-
) }
197-
>
198-
<Icon icon={ metric.icon } />
199-
200-
<div className="flex w-full flex-col">
201-
<div className="not-italic font-bold text-xl py-2 text-gray-800">
202-
{ formatMetricValue( metric.value ) }
203-
</div>
204-
205-
<div className="not-italic font-normal text-sm text-gray-800">
206-
{ metric.label }
207-
</div>
208-
209-
<div className="font-normal text-xs text-gray-600">
210-
{ metric.description }
211-
</div>
212-
</div>
213-
</div>
190+
value={ getFormattedMetric( metric.value ) }
191+
label={ metric.label }
192+
description={ metric.description }
193+
icon={ metric.icon }
194+
/>
214195
);
215-
}) }
196+
})}
216197
</div>
217198

218199
{ 'yes' !== optimoleDashboardApp.remove_latest_images && (

assets/src/widget/App.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import MetricBoxes from './components/MetricBoxes';
2+
import Usage from './components/Usage';
3+
import WidgetFooter from './components/WidgetFooter';
4+
5+
6+
export default function App() {
7+
return (
8+
<div className="antialiased">
9+
<div className="p-4 flex flex-col gap-4">
10+
<Usage />
11+
<MetricBoxes/>
12+
</div>
13+
14+
<hr className="border-gray-300 m-0 border-0 border-b"/>
15+
16+
<WidgetFooter />
17+
</div>
18+
);
19+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useCallback } from '@wordpress/element';
2+
3+
import {
4+
compressionPercentage,
5+
traffic
6+
} from '../../dashboard/utils/icons';
7+
8+
import DashboardMetricBox from '../../dashboard/parts/components/DashboardMetricBox';
9+
10+
export default function MetricBoxes() {
11+
const { i18n, serviceData } = optimoleDashboardWidget;
12+
13+
const METRICS = [
14+
{
15+
label: i18n.averageCompression,
16+
description: i18n.duringLastMonth,
17+
value: 'compression_percentage',
18+
icon: compressionPercentage
19+
},
20+
{
21+
label: i18n.traffic,
22+
description: i18n.duringLastMonth,
23+
value: 'traffic',
24+
icon: traffic
25+
}
26+
];
27+
28+
const getMetricValue = useCallback( ( metric ) => {
29+
const metricValue = serviceData[ metric ];
30+
31+
if ( 'compression_percentage' === metric ) {
32+
return metricValue.toFixed( 2 ) + '%';
33+
}
34+
35+
if ( 'traffic' === metric ) {
36+
if ( 1000 < metricValue ) {
37+
return ( metricValue / 1000 ).toFixed( 2 ) + 'GB';
38+
}
39+
40+
return metricValue.toFixed( 2 ) + 'MB';
41+
}
42+
43+
return metricValue;
44+
}, [ serviceData ]);
45+
46+
return (
47+
<div className="grid grid-cols-2 gap-4">
48+
{ METRICS.map( metric => (
49+
<DashboardMetricBox
50+
key={ metric.value }
51+
value={ getMetricValue( metric.value ) }
52+
label={ metric.label }
53+
description={ metric.description }
54+
icon={ metric.icon }
55+
compact={true}
56+
/>
57+
) ) }
58+
</div>
59+
);
60+
}

assets/src/widget/components/Usage.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Icon } from '@wordpress/components';
2+
import { external } from '@wordpress/icons';
3+
4+
import { quota } from '../../dashboard/utils/icons';
5+
import ProgressBar from '../../dashboard/parts/components/ProgressBar';
6+
7+
export default function Usage() {
8+
9+
const { serviceData } = optimoleDashboardWidget;
10+
11+
const {
12+
visitors_pretty,
13+
visitors_limit_pretty,
14+
visitors_limit,
15+
visitors
16+
} = serviceData;
17+
18+
const progress = Math.round( ( visitors / visitors_limit ) * 100 );
19+
20+
return (
21+
<div className="flex items-center gap-4 p-4 bg-light-blue border border-blue-300 rounded-md">
22+
<span className="flex items-center justify-center w-12 h-12">
23+
<Icon icon={ quota } />
24+
</span>
25+
26+
<div className="flex flex-col w-full gap-2">
27+
<div className="flex items-center gap-2">
28+
<span className="text-lg text-gray-800 font-bold">
29+
{ visitors_pretty } / { visitors_limit_pretty }
30+
</span>
31+
32+
<span className="text-sm text-gray-600">
33+
{ optimoleDashboardWidget.i18n.monthlyVisitsQuota }
34+
</span>
35+
</div>
36+
37+
38+
<div className="w-full relative flex items-center gap-2">
39+
<ProgressBar value={ visitors } max={ visitors_limit } colorOverage={true} />
40+
<span className="font-semibold text-sm text-gray-800">
41+
{ progress }%
42+
</span>
43+
</div>
44+
45+
{ 70 < progress && (
46+
<a href={ optimoleDashboardWidget.billingURL } target="_blank" rel="noopener noreferrer" className="font-semibold text-sm hover:text-dark-blue text-info flex items-center gap-1">
47+
{ optimoleDashboardWidget.i18n.upgrade }
48+
<Icon icon={ external } className="w-5 h-5 fill-current transition-colors duration-300"/>
49+
</a>
50+
)}
51+
</div>
52+
</div>
53+
);
54+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Icon, external } from '@wordpress/icons';
2+
3+
export default function WidgetFooter() {
4+
const { i18n, dashboardURL, adminPageURL } = optimoleDashboardWidget;
5+
6+
return (
7+
<div className="flex justify-between gap-4 items-center px-4 py-2 bg-gray-50">
8+
<a href={dashboardURL} target="_blank" rel="noopener noreferrer" className="flex items-center gap-2 no-underline cursor-pointer group">
9+
<img src={ optimoleDashboardWidget.assetsURL + 'img/logo.svg' } alt="Optimole" className="w-8 h-8"/>
10+
<span className="text-base font-bold text-gray-800 group-hover:text-dark-blue transition-colors duration-300">Optimole</span>
11+
</a>
12+
13+
<a href={ adminPageURL } className="text-sm text-info font-medium cursor-pointer hover:text-dark-blue no-underline transition-colors duration-300">
14+
{ i18n.viewAllStats }
15+
</a>
16+
</div>
17+
);
18+
}

assets/src/widget/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createRoot } from '@wordpress/element';
2+
import App from './App';
3+
import './style.scss';
4+
5+
const root = createRoot( document.getElementById( 'optimole-dashboard-widget-root' ) );
6+
7+
root.render( <App /> );

assets/src/widget/style.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@import 'tailwindcss/components';
2+
@import 'tailwindcss/utilities';
3+
4+
#optml-dashboard-widget {
5+
.inside {
6+
@apply p-0 m-0;
7+
}
8+
}

inc/admin.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public function __construct() {
5252
$this->settings = new Optml_Settings();
5353
$this->conflicting_plugins = new Optml_Conflicting_Plugins();
5454

55+
$dashboard_widget = new Optml_Dashboard_Widget();
56+
$dashboard_widget->init();
57+
5558
add_filter( 'plugin_action_links_' . plugin_basename( OPTML_BASEFILE ), [ $this, 'add_action_links' ] );
5659
add_action( 'admin_menu', [ $this, 'add_dashboard_page' ] );
5760
add_action( 'admin_menu', [ $this, 'add_settings_subpage' ], 99 );

0 commit comments

Comments
 (0)