Skip to content

Commit be0a9bd

Browse files
feat: add obfx modules
1 parent 1b521a8 commit be0a9bd

File tree

9 files changed

+425
-1
lines changed

9 files changed

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