From 223beb93ccc999b0e5d8f53c87f3b27e27923b34 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 15:38:36 +0600 Subject: [PATCH 01/12] added filter for customization and orignize the gateway logos --- .../images/{ => gateways}/clickatell-logo.png | Bin assets/images/{ => gateways}/clickatell.svg | 0 assets/images/gateways/common-sms.png | Bin 0 -> 406 bytes assets/images/{ => gateways}/plivo-logo.png | Bin assets/images/{ => gateways}/plivo.svg | 0 assets/images/{ => gateways}/twilio-logo.png | Bin assets/images/{ => gateways}/twilio.svg | 0 assets/images/{ => gateways}/vonage-logo.png | Bin assets/images/{ => gateways}/vonage.svg | 0 includes/Gateways/Clickatell.php | 2 +- includes/Gateways/Fake.php | 2 +- includes/Gateways/Plivo.php | 2 +- includes/Gateways/Twilio.php | 2 +- includes/Gateways/Vonage.php | 2 +- src/pages/notifications/index.tsx | 57 ++++++++++++------ 15 files changed, 43 insertions(+), 24 deletions(-) rename assets/images/{ => gateways}/clickatell-logo.png (100%) rename assets/images/{ => gateways}/clickatell.svg (100%) create mode 100644 assets/images/gateways/common-sms.png rename assets/images/{ => gateways}/plivo-logo.png (100%) rename assets/images/{ => gateways}/plivo.svg (100%) rename assets/images/{ => gateways}/twilio-logo.png (100%) rename assets/images/{ => gateways}/twilio.svg (100%) rename assets/images/{ => gateways}/vonage-logo.png (100%) rename assets/images/{ => gateways}/vonage.svg (100%) diff --git a/assets/images/clickatell-logo.png b/assets/images/gateways/clickatell-logo.png similarity index 100% rename from assets/images/clickatell-logo.png rename to assets/images/gateways/clickatell-logo.png diff --git a/assets/images/clickatell.svg b/assets/images/gateways/clickatell.svg similarity index 100% rename from assets/images/clickatell.svg rename to assets/images/gateways/clickatell.svg diff --git a/assets/images/gateways/common-sms.png b/assets/images/gateways/common-sms.png new file mode 100644 index 0000000000000000000000000000000000000000..e1b4e4eabcf8cdc7f47e511de957f67cac025a00 GIT binary patch literal 406 zcmV;H0crk;P)UfLEXAqvhPvmLbNibce4^2afGk*cC9@7V=!BtYtm=oxd z2j(BujS;X}mN%(`vItQ6uq*rcN2$0RNKFSu*^ia4!by~8(3Uz%@s=x~M?8rZS9s(U$-S^jK z)Z~8`n4%cC { + const tabs = [ + { + value: 'user-events', + label: __('User Events', 'texty'), + content: , + }, + { + value: 'integrations', + label: __('Integrations', 'texty'), + content: , + }, + ...(applyFilters('texty_notifications_tabs', []) as NotificationTab[]), + { + value: 'settings', + label: __('Settings', 'texty'), + content: , + }, + ] as NotificationTab[]; + return (
- + - - {__('User Events', 'texty')} - - - {__('Integrations', 'texty')} - - {__('Settings', 'texty')} + {tabs.map((tab) => ( + + {tab.label} + + ))} - - - - - - - - - - - + {tabs.map((tab) => ( + + {tab.content} + + ))}
); From 7188f1905a01a417ed8820bf0047555b4d93be87 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 16:53:00 +0600 Subject: [PATCH 02/12] feat(components): export reusable settings UI for add-ons (dokan-lite style) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose Texty's schema-driven settings UI so add-ons (Texty Pro) can import it instead of duplicating the renderer: - Build a `components` webpack entry from src/components/index.tsx as a window library `textyComponents` (handle `texty-components`), and wire a custom DependencyExtractionWebpackPlugin via webpack-dependency-mapping.js so `@texty/components` externalizes to that global + handle. - Register the `texty-components` script in Admin/Menu so dependents load it. - Generalize NotificationGroupSettings: optional schemaPath/savePath props and a generic save-fold (collapse any `_` / `::` children into the per-id payload), so it renders any feature's {schema, values} — not just the built-in notifications groups. Exported as `SchemaSettings`. --- includes/Admin/Menu.php | 15 +++ src/components/index.tsx | 8 ++ .../components/NotificationGroupSettings.tsx | 93 +++++++++---------- webpack-dependency-mapping.js | 33 +++++++ webpack.config.js | 24 +++++ 5 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 src/components/index.tsx create mode 100644 webpack-dependency-mapping.js diff --git a/includes/Admin/Menu.php b/includes/Admin/Menu.php index 8187561..bc8932d 100644 --- a/includes/Admin/Menu.php +++ b/includes/Admin/Menu.php @@ -101,6 +101,21 @@ public function render_page() { public function enqueue_scripts() { $asset_file = include TEXTY_DIR . '/dist/index.asset.php'; + // Reusable components bundle (window.textyComponents). Registered so + // add-ons that depend on the `texty-components` handle (via webpack + // dependency extraction) get it loaded automatically. + if ( file_exists( TEXTY_DIR . '/dist/components.asset.php' ) ) { + $components_asset = include TEXTY_DIR . '/dist/components.asset.php'; + + wp_register_script( + 'texty-components', + TEXTY_URL . '/dist/components.js', + $components_asset['dependencies'], + $components_asset['version'], + true + ); + } + wp_register_script( 'texty-admin', TEXTY_URL . '/dist/index.js', diff --git a/src/components/index.tsx b/src/components/index.tsx new file mode 100644 index 0000000..a17b88d --- /dev/null +++ b/src/components/index.tsx @@ -0,0 +1,8 @@ +/** + * Public component surface for add-ons. + * + * Built as the `components` webpack entry (library `textyComponents`, handle + * `texty-components`) and consumed by add-ons via `import { … } from + * '@texty/components'`, which webpack externalizes to the shared global. + */ +export { default as SchemaSettings } from '../pages/notifications/components/NotificationGroupSettings'; diff --git a/src/pages/notifications/components/NotificationGroupSettings.tsx b/src/pages/notifications/components/NotificationGroupSettings.tsx index 6ed899b..8d23dcf 100644 --- a/src/pages/notifications/components/NotificationGroupSettings.tsx +++ b/src/pages/notifications/components/NotificationGroupSettings.tsx @@ -8,20 +8,18 @@ import apiFetch from '@wordpress/api-fetch'; import { useEffect, useState } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; -import { Bell, Globe, ShoppingCart, Store } from 'lucide-react'; +import { Bell, Globe, ShoppingCart, Store, Zap } from 'lucide-react'; import type { ComponentType } from 'react'; import NotificationGroupSkeleton from './NotificationGroupSkeleton'; -// Separator for child field ids — `_message`, `_recipients`. -const SEP = '_'; - // Maps the backend `icon` string on the page element to a lucide component. const ICON_MAP: Record> = { Globe, ShoppingCart, Store, Bell, + Zap, }; const assetUrl: string = window.texty?.asset_url ?? ''; @@ -38,22 +36,26 @@ type SchemaResponse = { values: Record; }; -type SavePayloadEntry = { - enabled: boolean; - message: string; - recipients?: string[]; -}; - -type SavePayload = Record; +type SavePayload = Record>; type Props = { - groupId: string; + // Built-in notifications group (renders /notifications/schema?group=…). + groupId?: string; + // Or point at any endpoint returning { schema, values } (e.g. Texty Pro's + // Automations), with a matching save endpoint. + schemaPath?: string; + savePath?: string; }; -// The full plugin-ui Settings schema is built on the server -// (`GET /texty/v1/notifications/schema?group=…`). This component only fetches -// it, renders it, and maps edits back into the save payload. -const NotificationGroupSettings = ({ groupId }: Props) => { +// The full plugin-ui Settings schema is built on the server. This component only +// fetches it, renders it, and folds each collapsible card's children back into +// the per-id save payload — generic over any group/feature that follows the +// `` + `_` / `::` field convention. +const NotificationGroupSettings = ({ groupId, schemaPath, savePath }: Props) => { + const fetchPath = + schemaPath ?? + `/texty/v1/notifications/schema?group=${encodeURIComponent(groupId ?? '')}`; + const postPath = savePath ?? '/texty/v1/notifications'; const [schema, setSchema] = useState(null); // plugin-ui is controlled for `values` (external values take // precedence over its internal edits), so edits must be lifted into state @@ -68,11 +70,7 @@ const NotificationGroupSettings = ({ groupId }: Props) => { const load = async (): Promise => { setLoading(true); try { - const resp = await apiFetch({ - path: `/texty/v1/notifications/schema?group=${encodeURIComponent( - groupId - )}`, - }); + const resp = await apiFetch({ path: fetchPath }); if (cancelled) { return; } @@ -96,7 +94,7 @@ const NotificationGroupSettings = ({ groupId }: Props) => { return () => { cancelled = true; }; - }, [groupId]); + }, [fetchPath]); const handleChange = ( _scopeId: string, @@ -106,39 +104,44 @@ const NotificationGroupSettings = ({ groupId }: Props) => { setValues((prev: Record) => ({ ...prev, [key]: value })); }; - // Each `collapsible_switch` field id is a notification id; only role-type - // notifications carry a recipients child. Posts only this group's ids — the - // backend merges them over the stored option, preserving the other groups. + // Each `collapsible_switch` field id is a record id; its children + // (`_message`, `_recipients`, or `::`) are folded back into a + // nested entry keyed by the part after the id + separator. Posts only this + // group's ids — the backend merges them over the stored option. const handleSave = async (): Promise => { if (!schema) { return; } const cur = values; - const roleIds = new Set( - schema - .filter((el: SettingsElement) => el.variant === 'multicheck') - .map((el: SettingsElement) => String(el.field_group_id)) - ); - const payload: SavePayload = {}; + for (const el of schema) { if (el.variant !== 'collapsible_switch') { continue; } const id = el.id; - const message = cur[`${id}${SEP}message`]; - const entry: SavePayloadEntry = { - enabled: Boolean(cur[id]), - message: typeof message === 'string' ? message : '', - }; - - if (roleIds.has(id)) { - const recipients = cur[`${id}${SEP}recipients`]; - entry.recipients = Array.isArray(recipients) - ? (recipients as string[]) - : []; + const entry: Record = { enabled: Boolean(cur[id]) }; + + for (const child of schema) { + if (child.field_group_id !== id || child.variant === 'info') { + continue; + } + + const childId = String(child.id); + const key = ( + childId.startsWith(id) ? childId.slice(id.length) : childId + ).replace(/^(::|_)/, ''); + + let value = cur[childId]; + if (child.variant === 'multicheck') { + value = Array.isArray(value) ? value : []; + } else if (key === 'message') { + value = typeof value === 'string' ? value : ''; + } + + entry[key] = value; } payload[id] = entry; @@ -146,11 +149,7 @@ const NotificationGroupSettings = ({ groupId }: Props) => { setSaving(true); try { - await apiFetch({ - path: '/texty/v1/notifications', - method: 'POST', - data: payload, - }); + await apiFetch({ path: postPath, method: 'POST', data: payload }); toast.success(__('Changes saved.', 'texty')); } catch (err) { console.error('Failed to save notifications', err); diff --git a/webpack-dependency-mapping.js b/webpack-dependency-mapping.js new file mode 100644 index 0000000..af8feaa --- /dev/null +++ b/webpack-dependency-mapping.js @@ -0,0 +1,33 @@ +/** + * Maps Texty's reusable packages to the globals/handles they are exposed under, + * so add-ons (e.g. Texty Pro) can `import` them and have webpack externalize the + * import to the shared runtime (dependency extraction) instead of bundling a + * second copy. + * + * Mirrors the Dokan lite → pro pattern. + */ + +/** + * @param {string} request Import request. + * @return {string|undefined} Global variable the request resolves to. + */ +const requestToExternal = ( request ) => { + if ( request === '@texty/components' ) { + return 'textyComponents'; + } +}; + +/** + * @param {string} request Import request. + * @return {string|undefined} Script handle the request depends on. + */ +const requestToHandle = ( request ) => { + if ( request === '@texty/components' ) { + return 'texty-components'; + } +}; + +module.exports = { + requestToExternal, + requestToHandle, +}; diff --git a/webpack.config.js b/webpack.config.js index a39d620..b0c9ba1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,21 @@ const path = require('path'); const defaultConfig = require('@wordpress/scripts/config/webpack.config'); +const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extraction-webpack-plugin'); +const { requestToExternal, requestToHandle } = require('./webpack-dependency-mapping'); module.exports = { ...defaultConfig, + entry: { + index: './src/index.tsx', + // Reusable components exposed for add-ons (window.textyComponents). + components: { + import: './src/components/index.tsx', + library: { + name: 'textyComponents', + type: 'window', + }, + }, + }, output: { ...defaultConfig.output, path: path.resolve(__dirname, 'dist'), @@ -24,4 +37,15 @@ module.exports = { watchOptions: { ignored: ["**/dist/**"], // Ignore the generated build files to avoid unnecessary rebuilds }, + plugins: [ + // Replace the default dependency-extraction plugin with one that also knows + // how to externalize Texty's own packages (@texty/*) for add-ons. + ...defaultConfig.plugins.filter( + (plugin) => plugin.constructor.name !== 'DependencyExtractionWebpackPlugin' + ), + new DependencyExtractionWebpackPlugin({ + requestToExternal, + requestToHandle, + }), + ], }; From 54c512dd3842d44fb0aa4f3d3b1124e9a7077461 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 16:58:17 +0600 Subject: [PATCH 03/12] refactor(components): expose under window.texty.components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the nested window.texty.components global (Dokan style) instead of a flat textyComponents. The library assignment is merge-safe ((window.texty = window.texty || {}).components = …), and texty-components now depends on texty-admin so it loads after the localized `var texty = {…}` data and merges onto it rather than being clobbered. --- includes/Admin/Menu.php | 8 +++++--- webpack-dependency-mapping.js | 2 +- webpack.config.js | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/Admin/Menu.php b/includes/Admin/Menu.php index bc8932d..a7cdd3f 100644 --- a/includes/Admin/Menu.php +++ b/includes/Admin/Menu.php @@ -101,16 +101,18 @@ public function render_page() { public function enqueue_scripts() { $asset_file = include TEXTY_DIR . '/dist/index.asset.php'; - // Reusable components bundle (window.textyComponents). Registered so + // Reusable components bundle (window.texty.components). Registered so // add-ons that depend on the `texty-components` handle (via webpack - // dependency extraction) get it loaded automatically. + // dependency extraction) get it loaded automatically. Depends on + // `texty-admin` so it merges onto window.texty *after* the localized + // `var texty = {…}` data, rather than being clobbered by it. if ( file_exists( TEXTY_DIR . '/dist/components.asset.php' ) ) { $components_asset = include TEXTY_DIR . '/dist/components.asset.php'; wp_register_script( 'texty-components', TEXTY_URL . '/dist/components.js', - $components_asset['dependencies'], + array_merge( $components_asset['dependencies'], [ 'texty-admin' ] ), $components_asset['version'], true ); diff --git a/webpack-dependency-mapping.js b/webpack-dependency-mapping.js index af8feaa..aeb2450 100644 --- a/webpack-dependency-mapping.js +++ b/webpack-dependency-mapping.js @@ -13,7 +13,7 @@ */ const requestToExternal = ( request ) => { if ( request === '@texty/components' ) { - return 'textyComponents'; + return [ 'texty', 'components' ]; } }; diff --git a/webpack.config.js b/webpack.config.js index b0c9ba1..cf83c64 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,11 +7,11 @@ module.exports = { ...defaultConfig, entry: { index: './src/index.tsx', - // Reusable components exposed for add-ons (window.textyComponents). + // Reusable components exposed for add-ons (window.texty.components). components: { import: './src/components/index.tsx', library: { - name: 'textyComponents', + name: ['texty', 'components'], type: 'window', }, }, From 02d6c443bec5ea7720223c1d87a009b790c22e07 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 17:14:51 +0600 Subject: [PATCH 04/12] refactor(components): rename exported component to NotificationGroupSettings --- src/components/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/index.tsx b/src/components/index.tsx index a17b88d..289554c 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -5,4 +5,4 @@ * `texty-components`) and consumed by add-ons via `import { … } from * '@texty/components'`, which webpack externalizes to the shared global. */ -export { default as SchemaSettings } from '../pages/notifications/components/NotificationGroupSettings'; +export { default as NotificationGroupSettings } from '../pages/notifications/components/NotificationGroupSettings'; From 4b62286243dcadfa9aebd0759e97e2ff006321d0 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 17:15:07 +0600 Subject: [PATCH 05/12] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bec6a3b..bbb5131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "texty", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "texty", - "version": "2.0.0", + "version": "2.0.1", "license": "ISC", "dependencies": { "@base-ui/react": "^1.4.1", @@ -7295,7 +7295,7 @@ }, "node_modules/@wedevs/plugin-ui": { "version": "2.0.0", - "resolved": "git+ssh://git@github.com/getdokan/plugin-ui.git#f5d308f31543a376eca856c97104753550918eed", + "resolved": "git+ssh://git@github.com/getdokan/plugin-ui.git#fd1ef01a4ff31b478396a1113571b128a40a4958", "license": "GPL-2.0-or-later", "dependencies": { "@base-ui/react": "^1.1.0", From 76c081c5909a2c7e2a1c2ab4287ebb135d660599 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 17:16:49 +0600 Subject: [PATCH 06/12] feat(components): export PhoneField for add-ons --- src/components/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/index.tsx b/src/components/index.tsx index 289554c..990c540 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -6,3 +6,4 @@ * '@texty/components'`, which webpack externalizes to the shared global. */ export { default as NotificationGroupSettings } from '../pages/notifications/components/NotificationGroupSettings'; +export { default as PhoneField } from './PhoneField'; From d36b30b52c528e1a9d924cadaf85160b671dc6e4 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 17:21:24 +0600 Subject: [PATCH 07/12] refactor(notifications): filterable icon/logo maps + @wordpress/url query args - Apply `texty_notification_icon_map` / `texty_notification_logo_map` JS filters to the header ICON_MAP / LOGO_MAP so add-ons can register icons/logos for their own groups. - Build REST query strings with @wordpress/url's addQueryArgs instead of manual concatenation / encodeURIComponent (schema?group, notifications?context). --- .../components/NotificationGroupSettings.tsx | 22 +++++++++++++++---- .../notifications/hooks/useNotifications.ts | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/pages/notifications/components/NotificationGroupSettings.tsx b/src/pages/notifications/components/NotificationGroupSettings.tsx index 8d23dcf..cd58b1c 100644 --- a/src/pages/notifications/components/NotificationGroupSettings.tsx +++ b/src/pages/notifications/components/NotificationGroupSettings.tsx @@ -8,6 +8,7 @@ import apiFetch from '@wordpress/api-fetch'; import { useEffect, useState } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; import { Bell, Globe, ShoppingCart, Store, Zap } from 'lucide-react'; import type { ComponentType } from 'react'; @@ -51,10 +52,14 @@ type Props = { // fetches it, renders it, and folds each collapsible card's children back into // the per-id save payload — generic over any group/feature that follows the // `` + `_` / `::` field convention. -const NotificationGroupSettings = ({ groupId, schemaPath, savePath }: Props) => { +const NotificationGroupSettings = ({ + groupId, + schemaPath, + savePath, +}: Props) => { const fetchPath = schemaPath ?? - `/texty/v1/notifications/schema?group=${encodeURIComponent(groupId ?? '')}`; + addQueryArgs('/texty/v1/notifications/schema', { group: groupId ?? '' }); const postPath = savePath ?? '/texty/v1/notifications'; const [schema, setSchema] = useState(null); // plugin-ui is controlled for `values` (external values take @@ -168,9 +173,18 @@ const NotificationGroupSettings = ({ groupId, schemaPath, savePath }: Props) => } // Heading is rendered here (plugin-ui's built-in heading has no icon slot). + // Add-ons can register icons/logos for their own groups via these filters. const page = schema.find((el: SettingsElement) => el.type === 'page'); - const HeaderIcon = ICON_MAP[String(page?.icon ?? '')] ?? Bell; - const logoFile = LOGO_MAP[String(page?.id ?? '')]; + const iconMap = applyFilters( + 'texty_notification_icon_map', + ICON_MAP + ) as Record>; + const logoMap = applyFilters( + 'texty_notification_logo_map', + LOGO_MAP + ) as Record; + const HeaderIcon = iconMap[String(page?.icon ?? '')] ?? Bell; + const logoFile = logoMap[String(page?.id ?? '')]; const title = page?.label ?? ''; const description = page?.description ?? ''; diff --git a/src/pages/notifications/hooks/useNotifications.ts b/src/pages/notifications/hooks/useNotifications.ts index 8f6fe01..34cb510 100644 --- a/src/pages/notifications/hooks/useNotifications.ts +++ b/src/pages/notifications/hooks/useNotifications.ts @@ -2,6 +2,7 @@ import { toast } from '@wedevs/plugin-ui'; import apiFetch from '@wordpress/api-fetch'; import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; import type { NotificationItem, @@ -51,7 +52,7 @@ export const useNotifications = (): UseNotifications => { const load = async (): Promise => { try { const response = await apiFetch({ - path: '/texty/v1/notifications?context=edit', + path: addQueryArgs('/texty/v1/notifications', { context: 'edit' }), method: 'GET', }); if (cancelled) { From 4e2c4966b32edc7b339628ced1a62b9ed533fd54 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 29 Jun 2026 17:36:27 +0600 Subject: [PATCH 08/12] fix design/padding issue --- .../notifications/components/NotificationGroupSettings.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/notifications/components/NotificationGroupSettings.tsx b/src/pages/notifications/components/NotificationGroupSettings.tsx index cd58b1c..d4ece51 100644 --- a/src/pages/notifications/components/NotificationGroupSettings.tsx +++ b/src/pages/notifications/components/NotificationGroupSettings.tsx @@ -43,7 +43,6 @@ type Props = { // Built-in notifications group (renders /notifications/schema?group=…). groupId?: string; // Or point at any endpoint returning { schema, values } (e.g. Texty Pro's - // Automations), with a matching save endpoint. schemaPath?: string; savePath?: string; }; @@ -190,7 +189,7 @@ const NotificationGroupSettings = ({ return (
-
+
{logoFile ? ( Date: Wed, 1 Jul 2026 15:22:46 +0600 Subject: [PATCH 09/12] add te active tab persistnce value --- src/pages/notifications/index.tsx | 63 +++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/pages/notifications/index.tsx b/src/pages/notifications/index.tsx index 589b5d9..dc7633c 100644 --- a/src/pages/notifications/index.tsx +++ b/src/pages/notifications/index.tsx @@ -1,7 +1,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@wedevs/plugin-ui'; +import { useMemo } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; import { type ReactNode } from 'react'; +import { useSearchParams } from 'react-router-dom'; import Integrations from './Integrations'; import Settings from './Settings'; @@ -13,29 +15,52 @@ type NotificationTab = { content: ReactNode; }; +const TAB_PARAM = 'tab'; + const Notifications = () => { - const tabs = [ - { - value: 'user-events', - label: __('User Events', 'texty'), - content: , - }, - { - value: 'integrations', - label: __('Integrations', 'texty'), - content: , - }, - ...(applyFilters('texty_notifications_tabs', []) as NotificationTab[]), - { - value: 'settings', - label: __('Settings', 'texty'), - content: , - }, - ] as NotificationTab[]; + const [searchParams, setSearchParams] = useSearchParams(); + + const tabs = useMemo( + () => [ + { + value: 'user-events', + label: __('User Events', 'texty'), + content: , + }, + { + value: 'integrations', + label: __('Integrations', 'texty'), + content: , + }, + ...(applyFilters('texty_notifications_tabs', []) as NotificationTab[]), + { + value: 'settings', + label: __('Settings', 'texty'), + content: , + }, + ], + [], + ); + + const requested = searchParams.get(TAB_PARAM); + const activeTab = tabs.some((tab) => tab.value === requested) + ? (requested as string) + : (tabs[0]?.value ?? ''); + + const handleTabChange = (value: string): void => { + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev); + next.set(TAB_PARAM, value); + return next; + }, + { replace: true }, + ); + }; return (
- + {tabs.map((tab) => ( From 6cd7d72b26080930c5fdce92be7bb9bed05f4306 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Wed, 1 Jul 2026 16:18:08 +0600 Subject: [PATCH 10/12] added the `texty_datalayer_init` hooks --- texty.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/texty.php b/texty.php index e3addab..add5a2c 100644 --- a/texty.php +++ b/texty.php @@ -114,6 +114,16 @@ private function init_datalayer() { Texty\Models\SmsStat::class, Texty\Models\SmsStatStore::class ); + + /** + * Fires after the DataLayerFactory is initialized and Texty's own + * stores are registered. Add-ons should register their DataLayer + * models/stores here (via DataLayerFactory::register_store) so the + * factory is guaranteed ready and prefixed. Runs before texty_loaded. + * + * @param Texty $texty The main plugin instance. + */ + do_action( 'texty_datalayer_init', $this ); } catch ( \Exception $e ) { error_log( 'Texty DataLayer Init Error: ' . $e->getMessage() ); } From eaab41268d8f3611d3b0b1127bdc0fb88f5ab006 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Mon, 6 Jul 2026 11:56:09 +0600 Subject: [PATCH 11/12] feat: add shared asset registration for reusable components --- includes/Admin/Menu.php | 20 ++-------- includes/Assets.php | 80 ++++++++++++++++++++++++++++++++++++++++ src/components/index.tsx | 13 +++++++ texty.php | 1 + 4 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 includes/Assets.php diff --git a/includes/Admin/Menu.php b/includes/Admin/Menu.php index a7cdd3f..2d35aff 100644 --- a/includes/Admin/Menu.php +++ b/includes/Admin/Menu.php @@ -101,22 +101,10 @@ public function render_page() { public function enqueue_scripts() { $asset_file = include TEXTY_DIR . '/dist/index.asset.php'; - // Reusable components bundle (window.texty.components). Registered so - // add-ons that depend on the `texty-components` handle (via webpack - // dependency extraction) get it loaded automatically. Depends on - // `texty-admin` so it merges onto window.texty *after* the localized - // `var texty = {…}` data, rather than being clobbered by it. - if ( file_exists( TEXTY_DIR . '/dist/components.asset.php' ) ) { - $components_asset = include TEXTY_DIR . '/dist/components.asset.php'; - - wp_register_script( - 'texty-components', - TEXTY_URL . '/dist/components.js', - array_merge( $components_asset['dependencies'], [ 'texty-admin' ] ), - $components_asset['version'], - true - ); - } + // The reusable `texty-components` bundle (window.texty.components) is + // registered globally by Texty\Assets so add-ons can depend on it on + // both admin and storefront. On this page it is pulled in on demand by + // whichever add-on script declares it as a dependency. wp_register_script( 'texty-admin', diff --git a/includes/Assets.php b/includes/Assets.php new file mode 100644 index 0000000..f28c8ed --- /dev/null +++ b/includes/Assets.php @@ -0,0 +1,80 @@ + Date: Mon, 6 Jul 2026 12:00:58 +0600 Subject: [PATCH 12/12] fix: register texty-components on the login page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add-ons that enqueue on wp-login.php (e.g. the registration OTP widget) declare a texty-components dependency, but the handle was only registered on admin_enqueue_scripts and wp_enqueue_scripts — never login_enqueue_scripts. On the registration form the script dep and its stylesheet went unresolved. Register on login_enqueue_scripts too. --- includes/Assets.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/includes/Assets.php b/includes/Assets.php index f28c8ed..46993f6 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -8,20 +8,23 @@ * Shared asset registration. * * Registers the reusable components bundle (`window.texty.components`, - * handle `texty-components`) on both wp-admin and the front-end so add-ons - * that declare a `texty-components` script dependency get it loaded on - * demand — including on storefront surfaces (checkout, my-account) where the - * admin SPA never loads. Nothing is enqueued here; consumers pull the bundle - * in by depending on the handle (or calling wp_enqueue_script directly). + * handle `texty-components`) on wp-admin, the front-end, and the login page + * so add-ons that declare a `texty-components` dependency get it loaded on + * demand — including on storefront surfaces (checkout, my-account) and the + * wp-login.php registration form, where the admin SPA never loads. Nothing is + * enqueued here; consumers pull the bundle in by depending on the handle (or + * calling wp_enqueue_script directly). */ class Assets { /** - * Wire the global registration on both request contexts. + * Wire the global registration on every request context an add-on may + * enqueue from: wp-admin, the front-end, and wp-login.php. */ public function __construct() { add_action( 'admin_enqueue_scripts', [ $this, 'register_components' ], 5 ); add_action( 'wp_enqueue_scripts', [ $this, 'register_components' ], 5 ); + add_action( 'login_enqueue_scripts', [ $this, 'register_components' ], 5 ); } /**