+
+
+ { __( 'Messages', 'wedocs' ) }
+
+
+ { meta.total > 0 && (
+ <>{ meta.total } { meta.total === 1 ? __( 'message', 'wedocs' ) : __( 'messages', 'wedocs' ) }>
+ ) }
+
+
+
+ { /* Search and filters */ }
+
+
+
+
+
+ { /* Error message */ }
+ { apiError && (
+
+ ) }
+
+ { /* Messages table */ }
+
+ { loading ? (
+
+ { __( 'Loading messages...', 'wedocs' ) }
+
+ ) : messages.length === 0 ? (
+
+ { __( 'No messages found.', 'wedocs' ) }
+
+ ) : (
+
+
+
+ |
+ { __( 'Name', 'wedocs' ) }
+ |
+
+ { __( 'Email', 'wedocs' ) }
+ |
+
+ { __( 'Subject', 'wedocs' ) }
+ |
+
+ { __( 'Message', 'wedocs' ) }
+ |
+
+ { __( 'Source', 'wedocs' ) }
+ |
+
+ { __( 'Date', 'wedocs' ) }
+ |
+
+ { __( 'Actions', 'wedocs' ) }
+ |
+
+
+
+ { messages.map( ( message ) => (
+ setSelectedMessage( message ) }
+ >
+ |
+ { message.name }
+ |
+
+ { message.email }
+ |
+
+ { truncateText( message.subject, 30 ) || '—' }
+ |
+
+ { truncateText( message.message ) }
+ |
+
+
+ { message.source === 'widget' ? __( 'Widget', 'wedocs' ) : __( 'Modal', 'wedocs' ) }
+
+ |
+
+ { formatDate( message.submitted_at ) }
+ |
+
+
+ |
+
+ ) ) }
+
+
+ ) }
+
+
+ { /* Pagination */ }
+ { meta.totalPages > 1 && (
+
+
+ { __( 'Page', 'wedocs' ) } { currentPage } { __( 'of', 'wedocs' ) } { meta.totalPages }
+
+
+
+
+
+
+ ) }
+
+ { /* Message detail modal */ }
+
setSelectedMessage( null ) }
+ />
+
+ { /* Delete confirmation modal */ }
+
+
+
+
+ );
+};
+
+export default Messages;
diff --git a/src/components/Settings/GdprSettings.js b/src/components/Settings/GdprSettings.js
new file mode 100644
index 00000000..8253302b
--- /dev/null
+++ b/src/components/Settings/GdprSettings.js
@@ -0,0 +1,397 @@
+// DESCRIPTION: GDPR Compliance settings tab component.
+// Provides admin controls for enabling GDPR features, data retention, and consent configuration.
+
+import { __ } from '@wordpress/i18n';
+import Switcher from '../Switcher';
+import { useEffect, useState, Fragment } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
+import { Listbox, Transition } from '@headlessui/react';
+import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid';
+import docsStore from '../../data/docs';
+
+const GdprSettings = ( {
+ settingsData,
+ gdprSettingsData,
+ setSettings,
+} ) => {
+ const [ gdprSettings, setGdprSettings ] = useState( {
+ ...gdprSettingsData,
+ } );
+
+ const [ validationError, setValidationError ] = useState( '' );
+
+ const pages = useSelect( ( select ) => select( docsStore ).getPages(), [] );
+ const [ pageOptions, setPageOptions ] = useState( [] );
+ const [ selectedPolicyPage, setSelectedPolicyPage ] = useState( {} );
+ const [ selectedRequestPage, setSelectedRequestPage ] = useState( {} );
+
+ useEffect( () => {
+ setGdprSettings( { ...gdprSettingsData } );
+ }, [ gdprSettingsData ] );
+
+ useEffect( () => {
+ const options = pages?.map( ( page ) => ( {
+ id: page?.id,
+ name: page?.title?.rendered,
+ } ) );
+
+ setPageOptions( options || [] );
+
+ if ( gdprSettingsData?.privacy_policy_page ) {
+ const policyPage = options?.find(
+ ( page ) => page?.id === parseInt( gdprSettingsData.privacy_policy_page )
+ );
+ if ( policyPage ) {
+ setSelectedPolicyPage( policyPage );
+ }
+ }
+
+ if ( gdprSettingsData?.data_request_page ) {
+ const requestPage = options?.find(
+ ( page ) => page?.id === parseInt( gdprSettingsData.data_request_page )
+ );
+ if ( requestPage ) {
+ setSelectedRequestPage( requestPage );
+ }
+ }
+ }, [ pages, gdprSettingsData?.privacy_policy_page, gdprSettingsData?.data_request_page ] );
+
+ const handleFieldChange = ( field, value ) => {
+ setValidationError( '' );
+ setSettings( {
+ ...settingsData,
+ gdpr: { ...gdprSettingsData, [ field ]: value },
+ } );
+ };
+
+ const handlePolicyPageChange = ( page ) => {
+ setSelectedPolicyPage( page );
+ setValidationError( '' );
+ setSettings( {
+ ...settingsData,
+ gdpr: { ...gdprSettingsData, privacy_policy_page: page?.id },
+ } );
+ };
+
+ const handleRequestPageChange = ( page ) => {
+ setSelectedRequestPage( page );
+ setSettings( {
+ ...settingsData,
+ gdpr: { ...gdprSettingsData, data_request_page: page?.id },
+ } );
+ };
+
+ const isEnabled = gdprSettings?.enabled === 'on';
+
+ return (
+ {
+ switch ( action.type ) {
+ case 'SET_MESSAGES':
+ return {
+ ...state,
+ messages: [ ...action.messages ],
+ };
+
+ case 'SET_MESSAGE':
+ return {
+ ...state,
+ message: action.message,
+ };
+
+ case 'SET_MESSAGES_LOADING':
+ return {
+ ...state,
+ loading: action.loading,
+ };
+
+ case 'SET_MESSAGES_META':
+ return {
+ ...state,
+ meta: { ...action.meta },
+ };
+
+ case 'SET_MESSAGES_ERROR':
+ return {
+ ...state,
+ error: action.error,
+ };
+
+ case 'REMOVE_MESSAGE':
+ return {
+ ...state,
+ messages: state.messages.filter(
+ ( msg ) => msg.id !== action.messageId
+ ),
+ meta: {
+ ...state.meta,
+ total: Math.max( 0, state.meta.total - 1 ),
+ },
+ };
+
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/data/messages/resolvers.js b/src/data/messages/resolvers.js
new file mode 100644
index 00000000..e67fef3f
--- /dev/null
+++ b/src/data/messages/resolvers.js
@@ -0,0 +1,7 @@
+// DESCRIPTION: Redux resolvers for the messages data store.
+// Intentionally empty — messages are fetched manually via fetchMessages action
+// to support pagination, search, and proper error handling in the component.
+
+const resolvers = {};
+
+export default resolvers;
diff --git a/src/data/messages/selectors.js b/src/data/messages/selectors.js
new file mode 100644
index 00000000..1813c992
--- /dev/null
+++ b/src/data/messages/selectors.js
@@ -0,0 +1,26 @@
+// DESCRIPTION: Redux selectors for the messages data store.
+// Provides accessor functions for messages state.
+
+const selectors = {
+ getMessages( state ) {
+ return state.messages;
+ },
+
+ getMessage( state ) {
+ return state.message;
+ },
+
+ getMessagesLoading( state ) {
+ return state.loading;
+ },
+
+ getMessagesMeta( state ) {
+ return state.meta;
+ },
+
+ getMessagesError( state ) {
+ return state.error;
+ },
+};
+
+export default selectors;
diff --git a/src/data/settings/reducer.js b/src/data/settings/reducer.js
index afba2933..b3ecfccd 100644
--- a/src/data/settings/reducer.js
+++ b/src/data/settings/reducer.js
@@ -59,6 +59,15 @@ const DEFAULT_SETTINGS_STATE = {
active_nav_bg: { r: 59, g: 130, b: 246, a: 1 },
active_nav_text: { r: 255, g: 255, b: 255, a: 1 },
},
+ gdpr: {
+ enabled: 'off',
+ retention: 'manual',
+ consent_text: 'I agree that my data will be processed and stored only for as long as necessary for the stated purpose. I can request access to or deletion of my data at any time. Read our {privacy_policy}.',
+ privacy_policy_page: '',
+ data_request_page: '',
+ modal_enabled: 'off',
+ messaging_enabled: 'off',
+ },
ai: {
default_provider: 'openai',
providers: (() => {
diff --git a/src/data/store.js b/src/data/store.js
index 2a485daf..039ad794 100644
--- a/src/data/store.js
+++ b/src/data/store.js
@@ -1,6 +1,8 @@
import { register } from '@wordpress/data';
import docStore from './docs';
import settingsStore from './settings';
+import messagesStore from './messages';
register( docStore );
register( settingsStore );
+register( messagesStore );
diff --git a/templates/content-modal.php b/templates/content-modal.php
index 8e9b0500..cae60e1c 100644
--- a/templates/content-modal.php
+++ b/templates/content-modal.php
@@ -50,6 +50,35 @@
+ ' . __( 'Privacy Policy', 'wedocs' ) . '',
+ '' . __( 'request your data', 'wedocs' ) . '',
+ ],
+ $consent_text
+ );
+ ?>
+
+
+
+
+
diff --git a/wedocs.php b/wedocs.php
index 3f27872f..a914a3e8 100644
--- a/wedocs.php
+++ b/wedocs.php
@@ -105,8 +105,10 @@ private function __construct() {
$this->init_actions();
register_activation_hook( __FILE__, [ $this, 'activate' ] );
+ register_deactivation_hook( __FILE__, [ $this, 'deactivate' ] );
add_action( 'after_setup_theme', [ $this, 'init_classes' ] );
+ add_action( 'wedocs_daily_message_cleanup', 'wedocs_cleanup_expired_messages' );
$this->init_action_scheduler();
}
@@ -290,6 +292,22 @@ public function activate() {
// Set the redirect option to true when the plugin is activated.
update_option( 'wedocs_activation_redirect', true );
+
+ // Schedule daily message cleanup cron.
+ if ( ! wp_next_scheduled( 'wedocs_daily_message_cleanup' ) ) {
+ wp_schedule_event( strtotime( 'tomorrow 02:00:00' ), 'daily', 'wedocs_daily_message_cleanup' );
+ }
+ }
+
+ /**
+ * The plugin deactivation function.
+ *
+ * @since WEDOCS_SINCE
+ *
+ * @return void
+ */
+ public function deactivate() {
+ wp_clear_scheduled_hook( 'wedocs_daily_message_cleanup' );
}
/**
@@ -318,6 +336,7 @@ public function init_classes() {
$this->container['upgrader'] = new WeDevs\WeDocs\Upgrader\Upgrader();
$this->container['capability'] = new Capability();
$this->container['templates'] = new WeDevs\WeDocs\Templates\TemplateManager();
+ $this->container['privacy'] = new WeDevs\WeDocs\Privacy();
// Initialize Elementor integration if Elementor is active
if ( did_action( 'elementor/loaded' ) ) {