Skip to content

Commit 7f9dc65

Browse files
authored
Merge pull request #4447 from Codeinwp/feat/pro/3086
feat: add obfx modules
2 parents 8d121bd + ee0d0f7 commit 7f9dc65

File tree

9 files changed

+443
-1
lines changed

9 files changed

+443
-1
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/* global neveDash */
2+
import { __, sprintf } from '@wordpress/i18n';
3+
import {
4+
NEVE_AVAILABLE_MODULES_ICON_MAP,
5+
NEVE_STORE,
6+
} from '../../utils/constants';
7+
import { ArrowRight, LoaderCircle, LucideSettings } from 'lucide-react';
8+
import Card from '../../Layout/Card';
9+
import { useState } from '@wordpress/element';
10+
import { useDispatch, useSelect } from '@wordpress/data';
11+
import Toggle from '../Common/Toggle';
12+
import { send } from '../../utils/rest';
13+
14+
const Toast = ({ message }) => {
15+
return (
16+
<div className="fixed flex items-center gap-2 z-[99999] top-10 right-5 bg-white text-black text-base font-medium px-4 py-2 rounded shadow border border-solid border-blue-600">
17+
{message}
18+
</div>
19+
);
20+
};
21+
22+
const ModuleToggle = ({
23+
slug,
24+
moduleData,
25+
setMessage,
26+
isActive,
27+
setIsActive,
28+
isInstalled,
29+
setIsInstalled,
30+
}) => {
31+
const [loading, setLoading] = useState(false);
32+
const { changeModuleStatus, setObfxModuleStatus, setToast } =
33+
useDispatch(NEVE_STORE);
34+
const { moduleStatus } = useSelect((select) => {
35+
const { getObfxModuleStatus } = select(NEVE_STORE);
36+
37+
return {
38+
moduleStatus: getObfxModuleStatus(slug) || false,
39+
};
40+
});
41+
42+
const { api } = neveDash;
43+
const { title } = moduleData;
44+
const toastMessage = {
45+
//translators: %s - Plugin name
46+
installing: sprintf(__('Installing %s', 'neve'), 'Orbit Fox Plugin'),
47+
//translators: %s - Plugin name
48+
activating: sprintf(__('Activating %s', 'neve'), 'Orbit Fox Plugin'),
49+
};
50+
51+
const handleToggle = async (value) => {
52+
try {
53+
setLoading(true);
54+
changeModuleStatus(slug, value);
55+
56+
let isPluginActive = true;
57+
// Handle plugin installation or activation
58+
if (!isInstalled) {
59+
setMessage(toastMessage.installing);
60+
isPluginActive = false;
61+
} else if (!isActive) {
62+
setMessage(toastMessage.activating);
63+
isPluginActive = false;
64+
}
65+
66+
if (!isPluginActive) {
67+
await send(api + 'activate-plugin', {
68+
slug: 'themeisle-companion',
69+
}).then((res) => {
70+
if (res.success) {
71+
setIsInstalled(true);
72+
setIsActive(true);
73+
}
74+
});
75+
}
76+
77+
// Fire the send method after install/activate or immediately if both are done
78+
const response = await send(api + 'activate-module', {
79+
slug,
80+
value,
81+
});
82+
83+
setObfxModuleStatus(slug, response.success ? value : !value);
84+
setToast(
85+
response.success
86+
? (value
87+
? __('Module Activated', 'neve')
88+
: __('Module Deactivated.', 'neve')) + ` (${title})`
89+
: response.data
90+
);
91+
} catch (error) {
92+
setToast(
93+
__('Something went wrong while activating the module.', 'neve')
94+
);
95+
} finally {
96+
setLoading(false);
97+
setMessage('');
98+
}
99+
};
100+
101+
return (
102+
<div className="flex gap-2 items-center">
103+
<Toggle
104+
checked={moduleStatus}
105+
onToggle={handleToggle}
106+
disabled={loading}
107+
/>
108+
</div>
109+
);
110+
};
111+
112+
const AvailableModuleCard = ({
113+
moduleData,
114+
slug,
115+
setMessage,
116+
isActive,
117+
setIsActive,
118+
isInstalled,
119+
setIsInstalled,
120+
}) => {
121+
const { title, description } = moduleData;
122+
const CardIcon = NEVE_AVAILABLE_MODULES_ICON_MAP[slug] || LucideSettings;
123+
124+
return (
125+
<Card
126+
icon={<CardIcon size={18} />}
127+
title={title}
128+
className="bg-white p-6 rounded-lg shadow-sm"
129+
afterTitle={
130+
<ModuleToggle
131+
slug={slug}
132+
moduleData={moduleData}
133+
setMessage={setMessage}
134+
isActive={isActive}
135+
setIsActive={setIsActive}
136+
isInstalled={isInstalled}
137+
setIsInstalled={setIsInstalled}
138+
/>
139+
}
140+
id={`module-${slug}`}
141+
>
142+
<p className="text-gray-600 text-sm leading-relaxed">
143+
{description}
144+
</p>
145+
{!isActive ? (
146+
<p className="mt-2 italic text-sm text-gray-500">
147+
{__(
148+
'This feature is part of OrbitFox plugin, built by the Neve team. Enabling the toggle will automatically install and activate the plugin.',
149+
'neve'
150+
)}
151+
</p>
152+
) : (
153+
<a
154+
className="flex mt-2 text-blue-600 gap-2 align-middle"
155+
href="admin.php?page=obfx_companion"
156+
>
157+
{__('Go to Settings to Edit', 'neve')}
158+
<ArrowRight size={18} />
159+
</a>
160+
)}
161+
</Card>
162+
);
163+
};
164+
165+
export default () => {
166+
const [message, setMessage] = useState('');
167+
const [isInstalled, setIsInstalled] = useState(
168+
neveDash.orbitFox.isInstalled
169+
);
170+
const [isActive, setIsActive] = useState(neveDash.orbitFox.isActive);
171+
172+
return (
173+
<>
174+
<div className="mb-6">
175+
<div className="flex items-center justify-between">
176+
<h2 className="text-lg font-semibold">
177+
{__('Available Modules', 'neve')}
178+
</h2>
179+
</div>
180+
<div className="grid xl:grid-cols-2 gap-6">
181+
{Object.entries(neveDash.availableModules).map(
182+
([slug, moduleData]) => (
183+
<AvailableModuleCard
184+
key={slug}
185+
slug={slug}
186+
moduleData={moduleData}
187+
setMessage={setMessage}
188+
isActive={isActive}
189+
setIsActive={setIsActive}
190+
isInstalled={isInstalled}
191+
setIsInstalled={setIsInstalled}
192+
/>
193+
)
194+
)}
195+
</div>
196+
</div>
197+
{message && (
198+
<Toast
199+
message={
200+
<>
201+
<LoaderCircle
202+
size={18}
203+
className="animate-spin text-blue-800"
204+
/>
205+
{message}
206+
</>
207+
}
208+
/>
209+
)}
210+
</>
211+
);
212+
};
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import AvailableModule from '../AvailableModule';
12
import ModuleGrid from '../ModuleGrid';
23

34
export default () => {
4-
return <ModuleGrid />;
5+
return (
6+
<>
7+
<AvailableModule />
8+
<ModuleGrid />
9+
</>
10+
);
511
};

assets/apps/dashboard/src/store/actions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,10 @@ export default {
5050
payload: loggerStatus,
5151
};
5252
},
53+
setObfxModuleStatus(slug, value) {
54+
return {
55+
type: 'SET_OBFX_MODULE_STATUS',
56+
payload: { slug, value },
57+
};
58+
},
5359
};

assets/apps/dashboard/src/store/reducer.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const initialState = {
88
currentTab: 'start',
99
license: neveDash.pro ? neveDash.license : {},
1010
notifications: neveDash.notifications || {},
11+
obfxModuleStatus: neveDash.orbitFox?.data?.module_status || {},
1112
};
1213

1314
const hash = getTabHash();
@@ -73,6 +74,16 @@ const reducer = (state = initialState, action) => {
7374
neve_logger_flag: action.payload,
7475
},
7576
};
77+
case 'SET_OBFX_MODULE_STATUS':
78+
return {
79+
...state,
80+
obfxModuleStatus: {
81+
...state.obfxModuleStatus,
82+
[action.payload.slug]: {
83+
active: action.payload.value,
84+
},
85+
},
86+
};
7687
}
7788
return state;
7889
};

assets/apps/dashboard/src/store/selectors.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,15 @@ export default {
2525

2626
return shownNotifications;
2727
},
28+
getObfxModuleStatus: (state, slug) => {
29+
if (!state.obfxModuleStatus) {
30+
return false;
31+
}
32+
33+
if (state.obfxModuleStatus[slug]) {
34+
return state.obfxModuleStatus[slug]?.active || false;
35+
}
36+
37+
return false;
38+
},
2839
};

assets/apps/dashboard/src/utils/constants.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
LucideGraduationCap,
1111
LucideImage,
1212
LucideLayoutTemplate,
13+
LucideLock,
1314
LucideNewspaper,
1415
LucidePalette,
1516
LucidePanelRightDashed,
17+
LucidePanelsTopLeft,
1618
LucidePanelTopDashed,
1719
LucidePin,
1820
LucideRss,
@@ -22,6 +24,7 @@ import {
2224
LucideShoppingCart,
2325
LucideTimer,
2426
LucideToyBrick,
27+
LucideType,
2528
LucideTypeOutline,
2629
} from 'lucide-react';
2730

@@ -67,3 +70,10 @@ export const NEVE_PLUGIN_ICON_MAP = {
6770
'hyve-lite': LucideBotMessageSquare,
6871
// 'sparks'
6972
};
73+
74+
export const NEVE_AVAILABLE_MODULES_ICON_MAP = {
75+
'login-customizer': LucideLock,
76+
'custom-fonts': LucideType,
77+
'policy-notice': LucideShield,
78+
'post-duplicator': LucidePanelsTopLeft,
79+
};

inc/admin/dashboard/main.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,14 @@ private function get_localization() {
364364
'canActivatePlugins' => current_user_can( 'activate_plugins' ),
365365
'rootUrl' => get_site_url(),
366366
'sparksActive' => defined( 'SPARKS_WC_VERSION' ) ? 'yes' : 'no',
367+
'api' => esc_url( rest_url( '/nv/v1/dashboard/' ) ),
368+
'availableModules' => $this->get_available_modules(),
369+
'orbitFox' => array(
370+
'isInstalled' => file_exists( WP_PLUGIN_DIR . '/themeisle-companion/themeisle-companion.php' ),
371+
'isActive' => class_exists( 'Orbit_Fox' ),
372+
'activationUrl' => $this->plugin_helper->get_plugin_action_link( 'themeisle-companion' ),
373+
'data' => class_exists( 'Orbit_Fox' ) ? get_option( 'obfx_data' ) : array(),
374+
),
367375
];
368376

369377
if ( defined( 'NEVE_PRO_PATH' ) ) {
@@ -824,6 +832,34 @@ private function get_external_plugins_data() {
824832
return $plugins;
825833
}
826834

835+
/**
836+
* Get available modules.
837+
*
838+
* @return array<mixed>
839+
*/
840+
private function get_available_modules() {
841+
$modules = array(
842+
'login-customizer' => array(
843+
'title' => __( 'Login Customizer', 'neve' ),
844+
'description' => __( 'Customize your WordPress login page with branding and styling options.', 'neve' ),
845+
),
846+
'custom-fonts' => array(
847+
'title' => __( 'Custom Fonts/Scripts', 'neve' ),
848+
'description' => __( 'Add custom fonts and scripts to your website easily.', 'neve' ),
849+
),
850+
'policy-notice' => array(
851+
'title' => __( 'Cookie Notice', 'neve' ),
852+
'description' => __( 'Display a customizable cookie consent notice for GDPR compliance.', 'neve' ),
853+
),
854+
'post-duplicator' => array(
855+
'title' => __( 'Duplicate Page', 'neve' ),
856+
'description' => __( 'Quickly duplicate posts, pages, and custom post types.', 'neve' ),
857+
),
858+
);
859+
860+
return apply_filters( 'neve_available_modules', $modules );
861+
}
862+
827863
/**
828864
* Renders the custom layout header section in the admin dashboard for Custom Layouts
829865
*

0 commit comments

Comments
 (0)