Skip to content

Feature/dashboard#57

Open
Shamim-97 wants to merge 10 commits intodevelopfrom
feature/dashboard
Open

Feature/dashboard#57
Shamim-97 wants to merge 10 commits intodevelopfrom
feature/dashboard

Conversation

@Shamim-97
Copy link
Contributor

@Shamim-97 Shamim-97 commented Feb 24, 2026

Summary by CodeRabbit

  • New Features
    • Dashboard page with stats, charts, quick-send UI and help resources; new metrics API providing gateway status, usage, delivery rate and volume chart.
  • Chores
    • Installer now creates a table to record SMS send activity.
  • New Behavior
    • SMS sends are logged (sent/failed) with optional reference IDs.
  • Refactor
    • Gateways now return structured success responses including reference IDs; UI theme and styling updates for the dashboard.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Registers a Metrics REST controller and dashboard; changes gateway send() success returns to structured arrays; logs SMS send results into a new texty_sms_stat table; adds dashboard UI components, styles, theme provider, and related frontend dependencies.

Changes

Cohort / File(s) Summary
API: Registration & Controller
includes/Api.php, includes/Api/Metrics.php
Adds Api\Metrics to API registration and implements /texty/v1/metrics REST controller that aggregates gateway status, current/last-month usage, 30-day delivery rate, and 12-month volume data.
SMS Logging & Install
includes/Dispatcher.php, includes/Tools.php, includes/Install.php
Adds Dispatcher::log_sms() and extract_reference_id(); Hooks texty_after_send_sms and calls logging from Tools for successful sends; Install now creates texty_sms_stat table with receiver, gateway, status, timestamps, and reference_id.
Gateways: send() Return Shape
includes/Gateways/Clickatell.php, includes/Gateways/Fake.php, includes/Gateways/Plivo.php, includes/Gateways/Twilio.php, includes/Gateways/Vonage.php
All gateway send() implementations/docblocks changed to return `WP_Error
Frontend: Dashboard, Routing, Theme
src/pages/Dashboard.js, src/App.js, src/index.js, package.json
Adds Dashboard page fetching /texty/v1/metrics, updates routing to /dashboard, wraps app in ThemeProvider with new tokens, and adds dependencies (lucide-react, recharts, @wedevs/plugin-ui).
Frontend: Components & UI
src/components/StatCard.js, src/components/VolumeChart.js, src/components/HelpResources.js, src/components/Header.js, src/components/QuickSend.js
Introduces StatCard, VolumeChart, HelpResources; updates Header navigation; QuickSend migrated to @wedevs/plugin-ui components and clears inputs on successful send.
Styling
src/app.css, src/style.scss
Adds comprehensive dashboard CSS (app.css) and small SCSS EOF formatting change.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client App
    participant Tools as Tools.php
    participant Gateway as Gateway
    participant Dispatcher as Dispatcher
    participant DB as Database

    Client->>Tools: request send SMS (to, message)
    Tools->>Gateway: send(to, message)
    Gateway-->>Tools: return WP_Error OR { success, reference_id }
    Tools->>Dispatcher: do_action('texty_after_send_sms', result, to, message, gateway)
    Dispatcher->>DB: INSERT INTO texty_sms_stat(receiver,gateway,status,reference_id,created_at,updated_at)
    DB-->>Dispatcher: insert result
    Tools-->>Client: return send result
Loading
sequenceDiagram
    participant Browser as Browser
    participant Dashboard as Dashboard.js
    participant WPAPI as WordPress REST API
    participant Metrics as Metrics.php
    participant DB as Database

    Browser->>Dashboard: mount
    Dashboard->>WPAPI: GET /texty/v1/metrics
    WPAPI->>Metrics: handle request -> get_metrics
    Metrics->>DB: queries for monthly, last_month, 30-day delivery, 12-month aggregates
    DB-->>Metrics: query results
    Metrics-->>WPAPI: metrics payload
    WPAPI-->>Dashboard: metrics JSON
    Dashboard->>Browser: render StatCards + VolumeChart + QuickSend
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through logs and rows so fine,
I chased each send and saved its sign,
Charts now bloom where numbers play,
Reference IDs tucked away,
A rabbit cheers — metrics align!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and does not convey specific information about the primary changes—it merely names the feature branch without describing what the dashboard actually implements. Use a more descriptive title that explains the main functionality, such as 'Add SMS metrics dashboard with volume charts and delivery statistics' or 'Implement dashboard UI with real-time SMS metrics tracking'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/dashboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

♻️ Duplicate comments (2)
includes/Api/Tools.php (1)

64-68: Duplicate logging logic — same as Send.php.

This block is identical to the one in includes/Api/Send.php (lines 69–73). See the refactoring suggestion in the Send.php review to centralize this into Dispatcher::maybe_log_sms().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Tools.php` around lines 64 - 68, The SMS-success logging block
in Tools.php duplicates the logic in Send.php; replace the inline check and call
to Dispatcher::log_sms(...) with a single call to the centralized helper
Dispatcher::maybe_log_sms($to, $status) (created per the Send.php review), and
ensure maybe_log_sms implements the status validation (is_wp_error, is_array,
isset success) and calls Dispatcher::log_sms($to, $reference_id) internally so
this file no longer repeats the conditional logic.
includes/Dispatcher.php (1)

87-104: ⚠️ Potential issue | 🔴 Critical

Column name timestamp does not exist in the table schema — see related comment on Install.php.

The insert on line 97 references a timestamp column, but Install.php::create_tables() defines created_at and updated_at instead. Additionally, updated_at is NOT NULL with no default and is never provided. This will cause every $wpdb->insert() call to fail. See the detailed fix options in the Install.php review comment.

Also consider specifying $format in $wpdb->insert() for explicitness and type safety:

♻️ Example with format parameter
-        $result = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+        $format = [ '%s', '%s', '%s', '%s', '%s' ];
+        $result = $wpdb->insert( $table_name, $data, $format ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` around lines 87 - 104, In log_sms() the insert uses
a non-existent 'timestamp' column and omits the NOT NULL 'updated_at' column
defined in Install.php::create_tables(), causing inserts to fail; replace the
'timestamp' key with 'created_at' and also provide an 'updated_at' value (e.g.,
same current_time('mysql') value) in the $data array, and call $wpdb->insert(
$table_name, $data, $format ) with an appropriate $format array to match column
types for explicitness and type safety.
🧹 Nitpick comments (8)
includes/Gateways/Clickatell.php (1)

132-135: reference_id is always null — Clickatell 202 responses include message IDs.

Other gateways extract the provider's message ID from the response body (e.g., Twilio's sid, Vonage's message-id, Plivo's message_uuid). For Clickatell, the 202 response body contains message identifiers that could be parsed and returned here for consistent traceability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Gateways/Clickatell.php` around lines 132 - 135, The current return
hardcodes 'reference_id' => null in the Clickatell gateway; update the
send/response-handling logic in the Clickatell class to parse the provider
response body (typically JSON) when a 202 is received and extract the message
identifier (e.g., look for a top-level "messages" array and use messages[0].id
or messages[0].messageId, or fallback to any provider-specific id field) and
return it as 'reference_id' instead of null; also include a sensible fallback
(e.g., check response headers like X-Message-Id or return null only if no id
found) so the returned array mirrors other gateways' use of the provider message
ID.
includes/Gateways/Fake.php (1)

7-11: Nit: Class docblock still says "Twilio Class" — likely a copy-paste leftover.

 /**
- * Twilio Class
+ * Fake Gateway Class
  *
- * `@see` https://www.twilio.com/docs/sms/api/message
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Gateways/Fake.php` around lines 7 - 11, The class docblock
incorrectly labels this as "Twilio Class"—update the docblock above the Fake
gateway class (class name Fake) to accurately describe this gateway (e.g., "Fake
Gateway" or "Fake SMS gateway") and adjust or remove the Twilio-specific `@see`
URL so the comment matches the implementation and intent.
includes/Api/Send.php (1)

64-75: Duplicated logging logic — extract to a shared helper.

The logging block (lines 69–73) is identical to the one in includes/Api/Tools.php (lines 64–68). Consider extracting this into a reusable method (e.g., on Dispatcher or a base class) to avoid copy-paste drift.

♻️ Example: centralize logging in Dispatcher

Add a helper to Dispatcher:

public static function maybe_log_sms( $to, $status ) {
    if ( ! is_wp_error( $status ) && is_array( $status ) && ! empty( $status['success'] ) ) {
        $reference_id = isset( $status['reference_id'] ) ? $status['reference_id'] : null;
        self::log_sms( $to, $reference_id );
    }
}

Then both call sites simplify to:

-        // Log the SMS if it was sent successfully
-        if ( ! is_wp_error( $status ) && is_array( $status ) && isset( $status['success'] ) && $status['success'] ) {
-            $reference_id = isset( $status['reference_id'] ) ? $status['reference_id'] : null;
-            Dispatcher::log_sms( $to, $reference_id );
-        }
+        Dispatcher::maybe_log_sms( $to, $status );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Send.php` around lines 64 - 75, Extract the duplicated
SMS-success logging into a shared helper on Dispatcher and replace both inline
blocks with calls to it: add a public static function maybe_log_sms($to,
$status) on class Dispatcher that checks ! is_wp_error($status),
is_array($status) and ! empty($status['success']), pulls $reference_id if set,
then calls self::log_sms($to, $reference_id); update the call sites in Send.php
and Tools.php to call Dispatcher::maybe_log_sms($to, $status) instead of
duplicating the conditional logging logic.
src/components/HelpResources.js (1)

5-30: Move the resources array outside the component to avoid re-allocating it on every render.

The array is pure static data and doesn't depend on props or state. Defining it at module scope avoids the repeated allocation and makes the component body easier to scan.

♻️ Proposed refactor
+const resources = [
+  {
+    id: 'docs',
+    title: __('Documentation', 'texty'),
+    // ...
+  },
+  // ...
+];
+
 function HelpResources() {
-  const resources = [
-    {
-      id: 'docs',
-      // ...
-    },
-    // ...
-  ];

Note: __() calls are safe to evaluate at module level in a WordPress JS context — they read from the loaded translation data synchronously.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HelpResources.js` around lines 5 - 30, The resources array is
recreated on each render; move the const resources = [...] definition out of the
component function to module scope so it is allocated once (keep the same
identifier resources and the existing item keys like
id/title/description/url/buttonText/icon), leaving the component to reference
resources without changes; keep the __() calls in place (they are safe at module
level) and ensure no props/state are referenced inside resources after the move.
src/components/VolumeChart.js (1)

35-41: Hardcoded Y-axis domain [0, 10000] with static ticks won't adapt to actual data.

For most WordPress plugin installs with low SMS volume, all bars will render near zero height. For high-volume installs (>10,000/month), bars will be clipped. Use Recharts' 'auto' upper bound so the axis scales to the data.

♻️ Proposed fix
-            <YAxis
-              domain={[0, 10000]}
-              ticks={[0, 2500, 5000, 7500, 10000]}
-              tick={{ fill: '#9ca3af', fontSize: 13 }}
-              axisLine={false}
-              tickLine={false}
-            />
+            <YAxis
+              domain={[0, 'auto']}
+              tick={{ fill: '#9ca3af', fontSize: 13 }}
+              axisLine={false}
+              tickLine={false}
+              allowDecimals={false}
+            />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/VolumeChart.js` around lines 35 - 41, The YAxis in
VolumeChart.js is hardcoded to domain={[0, 10000]} and static ticks, so it won’t
scale to real data; update the YAxis (component name: YAxis in VolumeChart) to
use an adaptive upper bound (e.g., domain={[0, 'auto']}) and remove or compute
static ticks so Recharts will scale the axis to the data (or compute ticks from
the dataset inside the VolumeChart component before rendering) ensuring bars
don’t clip or sit near zero.
src/app.css (1)

371-377: button-primary in the quick-send form uses amber (#f59e0b) while the rest of the stylesheet uses green (#16a34a) as the brand color.

All other interactive elements (stat card icons, header active links, progress fill, help resource card hover) use the green palette. The amber here appears inconsistent unless it was intentional to visually distinguish the send action.

♻️ Align with brand color if unintentional
 .texty-quick-send-form .button-primary {
-  background-color: `#f59e0b`;
-  color: white;
-  border: 1px solid `#f59e0b`;
+  background-color: `#16a34a`;
+  color: white;
+  border: 1px solid `#16a34a`;
   width: 100%;
   padding: 10px 16px;
 }

 .texty-quick-send-form .button-primary:hover:not(:disabled) {
-  background-color: `#d97706`;
-  border-color: `#d97706`;
+  background-color: `#15803d`;
+  border-color: `#15803d`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app.css` around lines 371 - 377, The .texty-quick-send-form
.button-primary rule uses amber (`#f59e0b`) which is inconsistent with the
project's brand green; update the CSS selector .texty-quick-send-form
.button-primary to use the brand green (`#16a34a`) for background-color and border
(keep color: white and existing width/padding), so the quick-send button matches
other interactive elements like stat card icons/Header active links/progress
fill/help card hover.
includes/Api.php (1)

27-27: get_volume_chart() issues 12 sequential SELECT COUNT(*) queries — consolidate into one.

The private helper in includes/Api/Metrics.php (lines ~107–121) executes a separate $wpdb->get_var() per month inside a foreach. Replace with a single aggregation query and merge the results against the expected month list to fill zeros:

♻️ Proposed refactor
private function get_volume_chart( $table_name ) {
    global $wpdb;

    // Generate expected months first (using corrected strtotime approach)
    $months     = [];
    $chart_data = [];
    for ( $i = 11; $i >= 0; $i-- ) {
        $months[] = gmdate( 'Y-m', strtotime( "-{$i} months", current_time( 'timestamp' ) ) );
    }

    // Single aggregation query
    $placeholders = implode( ',', array_fill( 0, count( $months ), '%s' ) );
    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    $rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT DATE_FORMAT(timestamp, '%%Y-%%m') AS month, COUNT(*) AS count
             FROM {$table_name}
             WHERE DATE_FORMAT(timestamp, '%%Y-%%m') IN ($placeholders)
             GROUP BY DATE_FORMAT(timestamp, '%%Y-%%m')",
            ...$months
        )
    );

    $counts = [];
    foreach ( $rows as $row ) {
        $counts[ $row->month ] = (int) $row->count;
    }

    foreach ( $months as $month ) {
        $chart_data[] = [
            'month' => gmdate( 'M', strtotime( $month . '-01' ) ),
            'count' => isset( $counts[ $month ] ) ? $counts[ $month ] : 0,
        ];
    }

    return $chart_data;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api.php` at line 27, The get_volume_chart private helper in
includes/Api/Metrics.php currently issues one $wpdb->get_var() per month in a
loop; replace that with a single aggregated query: first build the 12-month list
using gmdate and strtotime with current_time('timestamp'), create a placeholder
list for $wpdb->prepare, run one SELECT DATE_FORMAT(timestamp, '%Y-%m') AS
month, COUNT(*) AS count FROM {$table_name} WHERE DATE_FORMAT(timestamp,
'%Y-%m') IN (...) GROUP BY DATE_FORMAT(timestamp, '%Y-%m'), then map rows into
an associative counts array and iterate the original months list to produce
chart_data entries with month (gmdate('M', strtotime($month.'-01'))) and count
(0 when missing) and return chart_data.
includes/Api/Metrics.php (1)

39-46: Silence PHPMD’s unused $request warning.

If the parameter is intentionally unused, suppress the warning (or rename to $_request) to keep static analysis clean.

🔧 Optional PHPMD suppression
-     * `@return` WP_Rest_Response|WP_Error
+     * `@return` WP_Rest_Response|WP_Error
+     * `@SuppressWarnings`(PHPMD.UnusedFormalParameter)
      */
     public function get_metrics( $request ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Metrics.php` around lines 39 - 46, The get_metrics method
currently declares an unused parameter $request which triggers PHPMD; fix by
either renaming the parameter to $_request (or $_unusedRequest) to signal
intentional unusedness, or add a PHPMD suppression annotation on the method
(e.g., a `@SuppressWarnings`("PHPMD.UnusedFormalParameter") docblock) so static
analysis stops flagging get_metrics($request).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/Api.php`:
- Line 27: The month iteration in Api\Metrics::get_volume_chart() and the
$last_month calculation in get_metrics() use 30*DAY_IN_SECONDS which causes
duplicate/missing months; replace that arithmetic with calendar-month operations
(e.g., use strtotime("-{$i} months", $baseDate) or DateTime::modify("-{$i}
months") and normalize to the first day of each month) so each loop iteration
produces distinct month boundaries; update both get_volume_chart() and
get_metrics() to compute the month label and start/end timestamps via
calendar-aware functions and ensure the loop uses the month index (i) rather
than fixed 30-day offsets.

In `@includes/Api/Metrics.php`:
- Around line 51-52: The current calculation uses a fixed 30‑day offset
(current_time + gmdate with DAY_IN_SECONDS) which can produce incorrect calendar
months; replace that logic for $current_month and $last_month with
calendar-month arithmetic (e.g. use PHP DateTime/DateInterval or strtotime with
"first day of previous month"/"last month" semantics, then format with 'Y-m') so
you always get the previous calendar month, and apply the same change to the
other occurrences around the $... calculations at the 107-111 area; target
symbols: $current_month, $last_month, current_time, gmdate, DAY_IN_SECONDS.

In `@includes/Gateways/Plivo.php`:
- Around line 114-117: The return value currently assigns $body->message_uuid
directly to 'reference_id' but Plivo returns message_uuid as an array, causing
type mismatch for Dispatcher::log_sms(string $reference_id); update the code in
includes/Gateways/Plivo.php (the method that builds this return) to detect if
$body->message_uuid is an array and, if so, extract the first element (or null
if empty), otherwise cast to string; ensure 'reference_id' is always a string or
null before returning so it matches Dispatcher::log_sms's expected type.

In `@includes/Install.php`:
- Around line 40-48: The schema defines created_at and updated_at but
Dispatcher::log_sms inserts a non-existent timestamp column and never supplies
updated_at; update Dispatcher::log_sms to insert into the actual columns
(created_at and updated_at) rather than timestamp, and ensure updated_at gets a
value (e.g., set updated_at = created_at or current time) so the $wpdb->insert()
matches the Install.php table definition; adjust the array keys and values in
Dispatcher::log_sms and verify the columns used in any other $wpdb->insert()
calls reference created_at/updated_at, or alternatively modify Install.php to
add a timestamp column and make updated_at nullable/default CURRENT_TIMESTAMP if
you prefer the schema change.
- Around line 14-21: The installer is calling create_tables() twice and contains
leftover debug error_log() calls; remove the unconditional first call to
create_tables() so that create_tables() is only invoked inside the if (!
$installed) block (which also calls update_option('texty_installed')), and
delete any error_log(...) debug lines related to the installer to avoid noisy
logs (references: create_tables(), update_option('texty_installed'), and
error_log()).
- Line 40: Remove the "IF NOT EXISTS" clause from the CREATE TABLE SQL string so
dbDelta() can correctly parse the table name; locate the SQL assignment that
builds "$sql = \"CREATE TABLE IF NOT EXISTS {$table_name} ( ...\" (referencing
$sql and $table_name) and change it to "CREATE TABLE {$table_name} (...)" before
calling dbDelta() so schema diffs and updates are detected properly by
dbDelta().

In `@src/components/QuickSend.js`:
- Around line 39-42: The catch block in the QuickSend component currently only
resets setIsSending(false) and console.logs the error, so users get no feedback
on network/gateway failures; update the catch handler where apiFetch is called
(inside QuickSend's send flow / sendMessage handler) to call toast.error(...)
with a user-friendly message (e.g., "Failed to send message. Please try again.")
and include the error detail in the log or as part of the toast for debugging,
while still ensuring setIsSending(false) runs; reference the apiFetch call, the
catch callback, setIsSending, and toast.error when making the change.

In `@src/components/StatCard.js`:
- Around line 21-24: Wrap the hardcoded indicator labels in the translation
helper: change the JSX that renders {indicator === 'connected' ? 'Active' :
'Inactive'} inside the StatCard component to use the __('...') function for both
branches so they are localized; update the span rendering where the indicator
variable is used (the texty-stat-badge block) to call __('Active') and
__('Inactive') instead of raw strings and ensure the translation helper is
imported/available in this module if not already.

In `@src/components/VolumeChart.js`:
- Around line 1-9: The project imports Recharts components (BarChart, Bar,
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer) in VolumeChart.js and
icons from lucide-react in StatCard.js and HelpResources.js but neither
"recharts" nor "lucide-react" are declared in package.json; add both packages to
the dependencies in package.json (choose appropriate semver ranges or latest
stable versions), then run your package manager (npm install or yarn install) to
update lockfile and ensure the imports resolve during build and tests.

In `@src/pages/Dashboard.js`:
- Around line 73-78: The subLabel currently treats any non-positive
metrics.usage_change as "No change", which hides decreases; update the StatCard
subLabel logic in Dashboard.js to handle three cases: positive (prefix with +
and "from last month"), negative (format as e.g. "-X.X% decrease from last
month" or localised equivalent), and zero (keep "No change from last month");
modify the expression that sets subLabel for the StatCard (where title is
"Monthly Usage" and props include value={formatUsage(metrics.monthly_usage)} and
loading={isLoading}) to inspect metrics.usage_change and render the appropriate
signed percentage text for negative values.

---

Duplicate comments:
In `@includes/Api/Tools.php`:
- Around line 64-68: The SMS-success logging block in Tools.php duplicates the
logic in Send.php; replace the inline check and call to Dispatcher::log_sms(...)
with a single call to the centralized helper Dispatcher::maybe_log_sms($to,
$status) (created per the Send.php review), and ensure maybe_log_sms implements
the status validation (is_wp_error, is_array, isset success) and calls
Dispatcher::log_sms($to, $reference_id) internally so this file no longer
repeats the conditional logic.

In `@includes/Dispatcher.php`:
- Around line 87-104: In log_sms() the insert uses a non-existent 'timestamp'
column and omits the NOT NULL 'updated_at' column defined in
Install.php::create_tables(), causing inserts to fail; replace the 'timestamp'
key with 'created_at' and also provide an 'updated_at' value (e.g., same
current_time('mysql') value) in the $data array, and call $wpdb->insert(
$table_name, $data, $format ) with an appropriate $format array to match column
types for explicitness and type safety.

---

Nitpick comments:
In `@includes/Api.php`:
- Line 27: The get_volume_chart private helper in includes/Api/Metrics.php
currently issues one $wpdb->get_var() per month in a loop; replace that with a
single aggregated query: first build the 12-month list using gmdate and
strtotime with current_time('timestamp'), create a placeholder list for
$wpdb->prepare, run one SELECT DATE_FORMAT(timestamp, '%Y-%m') AS month,
COUNT(*) AS count FROM {$table_name} WHERE DATE_FORMAT(timestamp, '%Y-%m') IN
(...) GROUP BY DATE_FORMAT(timestamp, '%Y-%m'), then map rows into an
associative counts array and iterate the original months list to produce
chart_data entries with month (gmdate('M', strtotime($month.'-01'))) and count
(0 when missing) and return chart_data.

In `@includes/Api/Metrics.php`:
- Around line 39-46: The get_metrics method currently declares an unused
parameter $request which triggers PHPMD; fix by either renaming the parameter to
$_request (or $_unusedRequest) to signal intentional unusedness, or add a PHPMD
suppression annotation on the method (e.g., a
`@SuppressWarnings`("PHPMD.UnusedFormalParameter") docblock) so static analysis
stops flagging get_metrics($request).

In `@includes/Api/Send.php`:
- Around line 64-75: Extract the duplicated SMS-success logging into a shared
helper on Dispatcher and replace both inline blocks with calls to it: add a
public static function maybe_log_sms($to, $status) on class Dispatcher that
checks ! is_wp_error($status), is_array($status) and !
empty($status['success']), pulls $reference_id if set, then calls
self::log_sms($to, $reference_id); update the call sites in Send.php and
Tools.php to call Dispatcher::maybe_log_sms($to, $status) instead of duplicating
the conditional logging logic.

In `@includes/Gateways/Clickatell.php`:
- Around line 132-135: The current return hardcodes 'reference_id' => null in
the Clickatell gateway; update the send/response-handling logic in the
Clickatell class to parse the provider response body (typically JSON) when a 202
is received and extract the message identifier (e.g., look for a top-level
"messages" array and use messages[0].id or messages[0].messageId, or fallback to
any provider-specific id field) and return it as 'reference_id' instead of null;
also include a sensible fallback (e.g., check response headers like X-Message-Id
or return null only if no id found) so the returned array mirrors other
gateways' use of the provider message ID.

In `@includes/Gateways/Fake.php`:
- Around line 7-11: The class docblock incorrectly labels this as "Twilio
Class"—update the docblock above the Fake gateway class (class name Fake) to
accurately describe this gateway (e.g., "Fake Gateway" or "Fake SMS gateway")
and adjust or remove the Twilio-specific `@see` URL so the comment matches the
implementation and intent.

In `@src/app.css`:
- Around line 371-377: The .texty-quick-send-form .button-primary rule uses
amber (`#f59e0b`) which is inconsistent with the project's brand green; update the
CSS selector .texty-quick-send-form .button-primary to use the brand green
(`#16a34a`) for background-color and border (keep color: white and existing
width/padding), so the quick-send button matches other interactive elements like
stat card icons/Header active links/progress fill/help card hover.

In `@src/components/HelpResources.js`:
- Around line 5-30: The resources array is recreated on each render; move the
const resources = [...] definition out of the component function to module scope
so it is allocated once (keep the same identifier resources and the existing
item keys like id/title/description/url/buttonText/icon), leaving the component
to reference resources without changes; keep the __() calls in place (they are
safe at module level) and ensure no props/state are referenced inside resources
after the move.

In `@src/components/VolumeChart.js`:
- Around line 35-41: The YAxis in VolumeChart.js is hardcoded to domain={[0,
10000]} and static ticks, so it won’t scale to real data; update the YAxis
(component name: YAxis in VolumeChart) to use an adaptive upper bound (e.g.,
domain={[0, 'auto']}) and remove or compute static ticks so Recharts will scale
the axis to the data (or compute ticks from the dataset inside the VolumeChart
component before rendering) ensuring bars don’t clip or sit near zero.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 258463a and 0d320fa.

📒 Files selected for processing (20)
  • includes/Api.php
  • includes/Api/Metrics.php
  • includes/Api/Send.php
  • includes/Api/Tools.php
  • includes/Dispatcher.php
  • includes/Gateways/Clickatell.php
  • includes/Gateways/Fake.php
  • includes/Gateways/Plivo.php
  • includes/Gateways/Twilio.php
  • includes/Gateways/Vonage.php
  • includes/Install.php
  • src/App.js
  • src/app.css
  • src/components/Header.js
  • src/components/HelpResources.js
  • src/components/QuickSend.js
  • src/components/StatCard.js
  • src/components/VolumeChart.js
  • src/index.js
  • src/pages/Dashboard.js

Comment on lines +114 to +117
return [
'success' => true,
'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null,
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the Dispatcher class and log_sms method
find . -type f -name "*.php" | head -20

Repository: getdokan/texty

Length of output: 609


🏁 Script executed:

# Search for Dispatcher::log_sms or log_sms definition
rg "log_sms" --type php -B 3 -A 10

Repository: getdokan/texty

Length of output: 2135


🏁 Script executed:

# Find the Plivo.php file and examine surrounding context
rg "message_uuid" --type php -B 5 -A 5

Repository: getdokan/texty

Length of output: 643


🏁 Script executed:

cat -n includes/Gateways/Plivo.php

Repository: getdokan/texty

Length of output: 5766


🌐 Web query:

Plivo SMS API send message response message_uuid format

💡 Result:

In Plivo’s Send Message (SMS/MMS/WhatsApp) API response, message_uuid is returned as a JSON array of UUID strings (even if you send to a single destination). Example: {"message_uuid":["db3ce55a-7f1d-11e1-8ea7-1231380bc196"]}. [1][2]

Each message_uuid value is a 36-character UUID (standard 8-4-4-4-12 with hyphens) that uniquely identifies a message detail record. [3]

Multiple recipients in one request will return multiple UUIDs in the message_uuid array. [2]

Sources: [1] [2] [3]


$body->message_uuid is a JSON array — reference_id will hold an array, not a string.

The Plivo send-message response returns "message_uuid" as an array of UUIDs (e.g., ["db3ce55a-7f1d-11e1-8ea7-1231380bc196"]), even for single recipients. json_decode() at line 108 (without the true flag) converts this to a PHP array, so $body->message_uuid is array, not string. Assigning it directly to 'reference_id' violates the type hint in Dispatcher::log_sms(), which declares @param string $reference_id. Storing an array in a VARCHAR/TEXT column will serialize it as the literal string "Array", breaking message tracking.

🐛 Proposed fix — extract first element
-        return [
-            'success'      => true,
-            'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null,
-        ];
+        return [
+            'success'      => true,
+            'reference_id' => isset( $body->message_uuid[0] ) ? $body->message_uuid[0] : null,
+        ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return [
'success' => true,
'reference_id' => isset( $body->message_uuid ) ? $body->message_uuid : null,
];
return [
'success' => true,
'reference_id' => isset( $body->message_uuid[0] ) ? $body->message_uuid[0] : null,
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Gateways/Plivo.php` around lines 114 - 117, The return value
currently assigns $body->message_uuid directly to 'reference_id' but Plivo
returns message_uuid as an array, causing type mismatch for
Dispatcher::log_sms(string $reference_id); update the code in
includes/Gateways/Plivo.php (the method that builds this return) to detect if
$body->message_uuid is an array and, if so, extract the first element (or null
if empty), otherwise cast to string; ensure 'reference_id' is always a string or
null before returning so it matches Dispatcher::log_sms's expected type.

Comment on lines +14 to 21
$this->create_tables();
$installed = get_option( 'texty_installed' );
error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) );

if ( ! $installed ) {
update_option( 'texty_installed', time() );
$this->create_tables();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

create_tables() is called twice on first install; debug error_log calls left in.

Line 14 calls create_tables() unconditionally, and line 20 calls it again inside the ! $installed block. The second call is redundant. Also, the error_log statements on lines 16 and 37 should be removed before merging — they're debug artifacts.

♻️ Suggested fix
     public function run() {
-         $this->create_tables();
         $installed = get_option( 'texty_installed' );
-        error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) );

         if ( ! $installed ) {
             update_option( 'texty_installed', time() );
             $this->create_tables();
         }

         update_option( 'texty_version', TEXTY_VERSION );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$this->create_tables();
$installed = get_option( 'texty_installed' );
error_log( 'Texty Installer: ' . ( $installed ? 'Already installed' : 'Running installer' ) );
if ( ! $installed ) {
update_option( 'texty_installed', time() );
$this->create_tables();
}
$installed = get_option( 'texty_installed' );
if ( ! $installed ) {
update_option( 'texty_installed', time() );
$this->create_tables();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` around lines 14 - 21, The installer is calling
create_tables() twice and contains leftover debug error_log() calls; remove the
unconditional first call to create_tables() so that create_tables() is only
invoked inside the if (! $installed) block (which also calls
update_option('texty_installed')), and delete any error_log(...) debug lines
related to the installer to avoid noisy logs (references: create_tables(),
update_option('texty_installed'), and error_log()).

error_log ( "Creating table with charset: {$charset_collate}" );
$table_name = $wpdb->prefix . 'texty_sms_stat';

$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

WordPress dbDelta IF NOT EXISTS compatibility

💡 Result:

dbDelta() is not reliably compatible with MySQL’s CREATE TABLE IF NOT EXISTS syntax. Its SQL parser can mis-detect the table name (breaking the internal DESCRIBE checks / diff logic), which can lead to incorrect behavior. [1] It’s a known longstanding issue/limitation in core. [2]

Recommended patterns:

  • Use plain CREATE TABLE ... with dbDelta() (no IF NOT EXISTS). dbDelta() is designed to create the table if missing and alter it if it exists, so the clause is unnecessary and can confuse parsing. [3]
  • If you specifically want an “IF NOT EXISTS”-style guard, use maybe_create_table() (WordPress checks for the table first instead of relying on MySQL’s IF NOT EXISTS). [4]

Practical workaround (common):

  1. maybe_create_table( $table, $create_sql_without_IF_NOT_EXISTS ) to ensure the table exists, then
  2. dbDelta( $create_sql_without_IF_NOT_EXISTS ) for schema updates (columns/indexes).

Sources: [1] [2] [3] [4]


Remove IF NOT EXISTS from CREATE TABLE — incompatible with dbDelta().

WordPress dbDelta() has known parsing issues with the IF NOT EXISTS clause. Its SQL parser can fail to correctly detect the table name, breaking the diff logic and causing schema updates to be skipped on subsequent runs. dbDelta() is designed to create the table if missing, making the clause redundant.

Fix
-        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
+        $sql = "CREATE TABLE {$table_name} (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
$sql = "CREATE TABLE {$table_name} (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` at line 40, Remove the "IF NOT EXISTS" clause from the
CREATE TABLE SQL string so dbDelta() can correctly parse the table name; locate
the SQL assignment that builds "$sql = \"CREATE TABLE IF NOT EXISTS
{$table_name} ( ...\" (referencing $sql and $table_name) and change it to
"CREATE TABLE {$table_name} (...)" before calling dbDelta() so schema diffs and
updates are detected properly by dbDelta().

Comment on lines 39 to 42
.catch((err) => {
setIsSending(false);
console.log(err);
// toast.error(err.message);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent failure: no user-facing error message in the catch block.

apiFetch rejects the promise for any non-2xx response, making this catch the sole handler for network errors and gateway failures. Without a toast.error() call here, the button simply resets from "Sending…" to "Send Message" with no feedback.

🐛 Proposed fix
     .catch((err) => {
       setIsSending(false);
       console.log(err);
+      toast.error(err.message || __('An error occurred. Please try again.', 'texty'));
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.catch((err) => {
setIsSending(false);
console.log(err);
// toast.error(err.message);
});
.catch((err) => {
setIsSending(false);
console.log(err);
toast.error(err.message || __('An error occurred. Please try again.', 'texty'));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/QuickSend.js` around lines 39 - 42, The catch block in the
QuickSend component currently only resets setIsSending(false) and console.logs
the error, so users get no feedback on network/gateway failures; update the
catch handler where apiFetch is called (inside QuickSend's send flow /
sendMessage handler) to call toast.error(...) with a user-friendly message
(e.g., "Failed to send message. Please try again.") and include the error detail
in the log or as part of the toast for debugging, while still ensuring
setIsSending(false) runs; reference the apiFetch call, the catch callback,
setIsSending, and toast.error when making the change.

Comment on lines +21 to +24
{indicator && (
<span className={`texty-stat-badge texty-stat-badge--${indicator}`}>
{indicator === 'connected' ? 'Active' : 'Inactive'}
</span>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/StatCard.js

Repository: getdokan/texty

Length of output: 2671


🏁 Script executed:

# Check what other components import from `@wordpress/i18n`
rg "from '@wordpress/i18n'" --type js -A 2

Repository: getdokan/texty

Length of output: 1974


🏁 Script executed:

# Check if __ is already used in other components for similar labels
rg "__\(" src/components/ --type js -B 2 -A 2

Repository: getdokan/texty

Length of output: 9425


Localize indicator labels.

The hardcoded "Active" and "Inactive" strings on line 23 should be wrapped with the __() function for translation support, consistent with the rest of the codebase.

Suggested localization update
import { Fragment } from 'react';
+import { __ } from '@wordpress/i18n';
import { Activity, BarChart3, TrendingUp } from 'lucide-react';
-              {indicator === 'connected' ? 'Active' : 'Inactive'}
+              {indicator === 'connected' ? __('Active', 'texty') : __('Inactive', 'texty')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/StatCard.js` around lines 21 - 24, Wrap the hardcoded
indicator labels in the translation helper: change the JSX that renders
{indicator === 'connected' ? 'Active' : 'Inactive'} inside the StatCard
component to use the __('...') function for both branches so they are localized;
update the span rendering where the indicator variable is used (the
texty-stat-badge block) to call __('Active') and __('Inactive') instead of raw
strings and ensure the translation helper is imported/available in this module
if not already.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
includes/Install.php (1)

37-37: ⚠️ Potential issue | 🟠 Major

Remove IF NOT EXISTS from the dbDelta() statement.

Line 37 still uses CREATE TABLE IF NOT EXISTS ...; dbDelta() can misparse this and skip schema diffs.

🐛 Suggested fix
-        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
+        $sql = "CREATE TABLE {$table_name} (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` at line 37, The SQL used to create the table is built
into the $sql variable and passed to dbDelta(), but it still contains "IF NOT
EXISTS" which can cause dbDelta() to misparse and skip schema changes; remove
the "IF NOT EXISTS" clause from the CREATE TABLE statement (the string assigned
to $sql that uses {$table_name}) so dbDelta() receives a standard CREATE TABLE
definition and can correctly apply schema diffs.
includes/Api/Metrics.php (1)

52-53: ⚠️ Potential issue | 🟠 Major

Use calendar-month arithmetic instead of 30-day offsets.

Line 53 and Line 151 use 30-day subtraction; that can skip or repeat months around month-length boundaries and skew monthly_usage/volume_chart.

🗓️ Suggested fix
-        $current_month   = current_time( 'Y-m' );
-        $last_month      = gmdate( 'Y-m', current_time( 'timestamp' ) - 30 * DAY_IN_SECONDS );
+        $current         = new \DateTimeImmutable( 'first day of this month', wp_timezone() );
+        $current_month   = $current->format( 'Y-m' );
+        $last_month      = $current->modify( '-1 month' )->format( 'Y-m' );
...
-        for ( $i = 11; $i >= 0; $i-- ) {
-            $timestamp = current_time( 'timestamp' ) - ( $i * 30 * DAY_IN_SECONDS );
-            $months[]  = gmdate( 'Y-m', $timestamp );
-        }
+        $current = new \DateTimeImmutable( 'first day of this month', wp_timezone() );
+        for ( $i = 11; $i >= 0; $i-- ) {
+            $months[] = $current->modify( "-{$i} months" )->format( 'Y-m' );
+        }

Also applies to: 150-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Metrics.php` around lines 52 - 53, Replace the 30-day offset
arithmetic used to compute $last_month (and the similar code used for
monthly_usage/volume_chart) with calendar-month arithmetic: use
current_time('timestamp') as the base, create a DateTime/DateTimeImmutable (or
use strtotime with relative strings) to compute the first day of the current
month and then subtract one calendar month (e.g. "first day of last month") to
produce the previous month; format the result with gmdate('Y-m', $timestamp) or
DateTime::format and update the variables $current_month and $last_month and the
other occurrences so month boundaries use calendar months rather than 30-day
offsets.
🧹 Nitpick comments (2)
includes/Install.php (1)

37-45: Add index coverage for metrics read paths.

The new metrics queries heavily filter by created_at and status. Without indexes, this table will become a scan hotspot as SMS volume grows.

📈 Suggested schema improvement
         $sql = "CREATE TABLE {$table_name} (
             id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
             receiver VARCHAR(20) NOT NULL,
             gateway VARCHAR(50) NOT NULL,
             status VARCHAR(20) DEFAULT NULL,
             created_at DATETIME NOT NULL,
             updated_at DATETIME NOT NULL,
-            reference_id VARCHAR(100) DEFAULT NULL
+            reference_id VARCHAR(100) DEFAULT NULL,
+            KEY idx_created_at (created_at),
+            KEY idx_status_created_at (status, created_at)
         ) {$charset_collate};";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` around lines 37 - 45, The CREATE TABLE statement built
in $sql (in Install.php) lacks indexes for the metrics read paths that filter by
created_at and status; add a composite index such as INDEX idx_status_created_at
(status, created_at) to the table definition (and optionally a standalone INDEX
idx_created_at (created_at) if you expect many range-only queries) so queries
filtering/sorting by status and created_at use indexes instead of full table
scans.
includes/Api/Metrics.php (1)

56-71: Avoid DATE_FORMAT(created_at, ...) filters and per-month query loops.

This query shape is index-unfriendly and adds avoidable request latency (12 separate COUNTs). Prefer range predicates and one grouped query for the 12-month series.

Also applies to: 155-162

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Metrics.php` around lines 56 - 71, The current COUNT queries for
$monthly_usage and $last_month_usage use DATE_FORMAT(created_at, ...) which is
index-unfriendly and repeats per-month queries; replace these by using date
range predicates on created_at (e.g., >= month_start AND < next_month_start) in
the $wpdb->prepare calls and refactor the 12-month loop into a single grouped
query that SELECTs COUNT(*) GROUP BY DATE_FORMAT(created_at, '%Y-%m') or better
yet GROUP BY YEAR(created_at), MONTH(created_at) with appropriate range bounds
to return the 12-month series in one call; update the code paths that set
$monthly_usage, $last_month_usage and the 12-month collection to consume results
from that single grouped query and preserve the status = 'sent' filter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/Dispatcher.php`:
- Line 126: The $wpdb->insert call can fail and is currently ignored; update the
code in Dispatcher.php to check the return value of $wpdb->insert($table_name,
$data) and handle failures explicitly: if the insert returns false, capture
$wpdb->last_error (and/or $wpdb->print_error()), log the error with context
(include $table_name and $data identifiers), and return or surface a failure
(e.g., return false or a WP_Error) so callers know the SMS event wasn't
persisted and metrics can be corrected; ensure you reference the same
$wpdb->insert, $table_name and $data symbols and add no-op behavior only when
insert succeeds.
- Around line 100-112: The current status logic sets $status =
is_wp_error($result) ? 'failed' : 'sent', which misclassifies falsey or explicit
failure responses as 'sent'; update the determination of $status (where $status
and $result are used just before the reference_id extraction block) to mark as
'failed' when is_wp_error($result) OR $result === false OR (is_array($result) &&
isset($result['success']) && !$result['success']) OR empty($result); otherwise
set 'sent', leaving the reference_id extraction code (checking ['sid'],
['message-id'], ['message_uuid']) unchanged.

In `@package.json`:
- Line 25: The dependency "@wedevs/plugin-ui" is referenced as a floating Git
URL; update the package.json entry for "@wedevs/plugin-ui" to pin it to an
immutable revision by appending a tag or commit SHA (e.g.
git+https://github.com/mrabbani/plugin-ui.git#v1.2.3 or #<commit-sha>), then
reinstall/update the lockfile so the lock reflects the pinned revision; ensure
the package name "@wedevs/plugin-ui" is the one you change and commit the
updated package.json and lockfile.

---

Duplicate comments:
In `@includes/Api/Metrics.php`:
- Around line 52-53: Replace the 30-day offset arithmetic used to compute
$last_month (and the similar code used for monthly_usage/volume_chart) with
calendar-month arithmetic: use current_time('timestamp') as the base, create a
DateTime/DateTimeImmutable (or use strtotime with relative strings) to compute
the first day of the current month and then subtract one calendar month (e.g.
"first day of last month") to produce the previous month; format the result with
gmdate('Y-m', $timestamp) or DateTime::format and update the variables
$current_month and $last_month and the other occurrences so month boundaries use
calendar months rather than 30-day offsets.

In `@includes/Install.php`:
- Line 37: The SQL used to create the table is built into the $sql variable and
passed to dbDelta(), but it still contains "IF NOT EXISTS" which can cause
dbDelta() to misparse and skip schema changes; remove the "IF NOT EXISTS" clause
from the CREATE TABLE statement (the string assigned to $sql that uses
{$table_name}) so dbDelta() receives a standard CREATE TABLE definition and can
correctly apply schema diffs.

---

Nitpick comments:
In `@includes/Api/Metrics.php`:
- Around line 56-71: The current COUNT queries for $monthly_usage and
$last_month_usage use DATE_FORMAT(created_at, ...) which is index-unfriendly and
repeats per-month queries; replace these by using date range predicates on
created_at (e.g., >= month_start AND < next_month_start) in the $wpdb->prepare
calls and refactor the 12-month loop into a single grouped query that SELECTs
COUNT(*) GROUP BY DATE_FORMAT(created_at, '%Y-%m') or better yet GROUP BY
YEAR(created_at), MONTH(created_at) with appropriate range bounds to return the
12-month series in one call; update the code paths that set $monthly_usage,
$last_month_usage and the 12-month collection to consume results from that
single grouped query and preserve the status = 'sent' filter.

In `@includes/Install.php`:
- Around line 37-45: The CREATE TABLE statement built in $sql (in Install.php)
lacks indexes for the metrics read paths that filter by created_at and status;
add a composite index such as INDEX idx_status_created_at (status, created_at)
to the table definition (and optionally a standalone INDEX idx_created_at
(created_at) if you expect many range-only queries) so queries filtering/sorting
by status and created_at use indexes instead of full table scans.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d320fa and 253872a.

📒 Files selected for processing (4)
  • includes/Api/Metrics.php
  • includes/Dispatcher.php
  • includes/Install.php
  • package.json

Comment on lines +100 to +112
$status = is_wp_error( $result ) ? 'failed' : 'sent';

// Extract reference_id from result
$reference_id = null;
if ( is_array( $result ) ) {
// Try different key names used by various gateways
if ( isset( $result['sid'] ) ) {
$reference_id = $result['sid'];
} elseif ( isset( $result['message-id'] ) ) {
$reference_id = $result['message-id'];
} elseif ( isset( $result['message_uuid'] ) ) {
$reference_id = $result['message_uuid'];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Status logging can overcount sent SMS.

Line 100 marks every non-WP_Error result as sent. Failed but non-error responses (e.g., false or ['success' => false]) will be misclassified and skew metrics.

🐛 Suggested fix
-        $status = is_wp_error( $result ) ? 'failed' : 'sent';
+        $status = 'failed';
+        if ( ! is_wp_error( $result ) ) {
+            if ( is_array( $result ) ) {
+                $status = ! empty( $result['success'] ) ? 'sent' : 'failed';
+            } elseif ( true === $result ) {
+                $status = 'sent';
+            }
+        }

         // Extract reference_id from result
         $reference_id = null;
         if ( is_array( $result ) ) {
-            // Try different key names used by various gateways
-            if ( isset( $result['sid'] ) ) {
+            if ( isset( $result['reference_id'] ) ) {
+                $reference_id = $result['reference_id'];
+            } elseif ( isset( $result['sid'] ) ) {
                 $reference_id = $result['sid'];
             } elseif ( isset( $result['message-id'] ) ) {
                 $reference_id = $result['message-id'];
             } elseif ( isset( $result['message_uuid'] ) ) {
                 $reference_id = $result['message_uuid'];
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$status = is_wp_error( $result ) ? 'failed' : 'sent';
// Extract reference_id from result
$reference_id = null;
if ( is_array( $result ) ) {
// Try different key names used by various gateways
if ( isset( $result['sid'] ) ) {
$reference_id = $result['sid'];
} elseif ( isset( $result['message-id'] ) ) {
$reference_id = $result['message-id'];
} elseif ( isset( $result['message_uuid'] ) ) {
$reference_id = $result['message_uuid'];
}
$status = 'failed';
if ( ! is_wp_error( $result ) ) {
if ( is_array( $result ) ) {
$status = ! empty( $result['success'] ) ? 'sent' : 'failed';
} elseif ( true === $result ) {
$status = 'sent';
}
}
// Extract reference_id from result
$reference_id = null;
if ( is_array( $result ) ) {
if ( isset( $result['reference_id'] ) ) {
$reference_id = $result['reference_id'];
} elseif ( isset( $result['sid'] ) ) {
$reference_id = $result['sid'];
} elseif ( isset( $result['message-id'] ) ) {
$reference_id = $result['message-id'];
} elseif ( isset( $result['message_uuid'] ) ) {
$reference_id = $result['message_uuid'];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` around lines 100 - 112, The current status logic
sets $status = is_wp_error($result) ? 'failed' : 'sent', which misclassifies
falsey or explicit failure responses as 'sent'; update the determination of
$status (where $status and $result are used just before the reference_id
extraction block) to mark as 'failed' when is_wp_error($result) OR $result ===
false OR (is_array($result) && isset($result['success']) && !$result['success'])
OR empty($result); otherwise set 'sent', leaving the reference_id extraction
code (checking ['sid'], ['message-id'], ['message_uuid']) unchanged.

'reference_id' => $reference_id,
];

$wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle $wpdb->insert() failures explicitly.

Line 126 ignores insert failure. If DB writes fail, SMS events are silently lost and dashboard metrics drift.

🛠️ Suggested fix
-        $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+        $inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+        if ( false === $inserted ) {
+            do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $data );
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
if ( false === $inserted ) {
do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $data );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` at line 126, The $wpdb->insert call can fail and is
currently ignored; update the code in Dispatcher.php to check the return value
of $wpdb->insert($table_name, $data) and handle failures explicitly: if the
insert returns false, capture $wpdb->last_error (and/or $wpdb->print_error()),
log the error with context (include $table_name and $data identifiers), and
return or surface a failure (e.g., return false or a WP_Error) so callers know
the SMS event wasn't persisted and metrics can be corrected; ensure you
reference the same $wpdb->insert, $table_name and $data symbols and add no-op
behavior only when insert succeeds.

"react-select": "^5.8.0",
"react-toastify": "^6.2.0"
"react-toastify": "^6.2.0",
"@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the package.json file to see the current state
cat -n package.json | head -40

Repository: getdokan/texty

Length of output: 1531


🌐 Web query:

github.com/mrabbani/plugin-ui commits tags releases

💡 Result:

  • Repo: github.com/mrabbani/plugin-ui redirects to getdokan/plugin-ui. [1]
  • Commits: 187 commits on main. [1]
    • Latest commit activity shown: Feb 19, 2026 (e.g., “Merge pull request #54…”). [2]
  • Releases: No GitHub Releases published (“There aren’t any releases here”). [3]
  • Tags: GitHub’s /tags view (in this crawl) does not show any tag list—it appears there may be no tags, but I can’t fully confirm without the GitHub API / a git fetch. [3]

Sources: [1] [2] [3]


Pin @wedevs/plugin-ui to an immutable revision.

The Git dependency on line 25 lacks a commit SHA or tag reference, meaning builds will resolve to whatever is the latest commit on the default branch. This creates non-deterministic builds and weakens supply-chain reproducibility.

Suggest pinning to a specific commit or tag:

♻️ Suggested fix
-    "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git"
+    "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git#<commit-sha>"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 25, The dependency "@wedevs/plugin-ui" is referenced as
a floating Git URL; update the package.json entry for "@wedevs/plugin-ui" to pin
it to an immutable revision by appending a tag or commit SHA (e.g.
git+https://github.com/mrabbani/plugin-ui.git#v1.2.3 or #<commit-sha>), then
reinstall/update the lockfile so the lock reflects the pinned revision; ensure
the package name "@wedevs/plugin-ui" is the one you change and commit the
updated package.json and lockfile.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/QuickSend.js (1)

40-43: ⚠️ Potential issue | 🟡 Minor

Surface API failures to users in the catch path.

Line [40]-[43] only resets loading and logs to console; users get no feedback on network/request failures.

🐛 Proposed fix
       .catch((err) => {
         setIsSending(false);
-        console.log(err);
+        console.error(err);
+        toast.error(
+          err?.message || __('Failed to send message. Please try again.', 'texty')
+        );
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/QuickSend.js` around lines 40 - 43, The catch block currently
only calls setIsSending(false) and console.log(err) which leaves users unaware
of failures; update the catch in the QuickSend request path to surface the error
to the UI by passing the error message onward — either call an existing error
handler prop (e.g., onError(err)) if QuickSend accepts one, or set a component
error state (e.g., setErrorMessage(err.message || String(err))) and ensure the
component renders that state or triggers a toast/notification; keep the existing
setIsSending(false) and include the actual err.message in the user-visible
message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/Api/Metrics.php`:
- Line 59: Replace UTC-based gmdate() usage with site-local DateTimeImmutable
using wp_timezone() so metrics use the same timezone as created_at;
specifically, change the $current_day assignment (and the similar occurrences at
the other two locations) to construct a DateTimeImmutable('now', wp_timezone())
and extract the day via ->format('d') cast to int, ensuring all comparisons
(e.g., DAY(created_at) checks that feed usage_change, delivery_rate, and charts)
use the same timezone basis as Dispatcher.php's current_time('mysql').

In `@src/style.scss`:
- Line 468: Remove the duplicate stylesheet import
"@wedevs/plugin-ui/styles.css" from src/style.scss (the trailing `@import` at the
end of the file) because it violates the no-invalid-position-at-import-rule by
appearing after CSS rules and is redundant with the import already present in
src/index.js; simply delete the lone `@import` "@wedevs/plugin-ui/styles.css" line
at the end of style.scss.

---

Duplicate comments:
In `@src/components/QuickSend.js`:
- Around line 40-43: The catch block currently only calls setIsSending(false)
and console.log(err) which leaves users unaware of failures; update the catch in
the QuickSend request path to surface the error to the UI by passing the error
message onward — either call an existing error handler prop (e.g., onError(err))
if QuickSend accepts one, or set a component error state (e.g.,
setErrorMessage(err.message || String(err))) and ensure the component renders
that state or triggers a toast/notification; keep the existing
setIsSending(false) and include the actual err.message in the user-visible
message.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 253872a and 9a61633.

📒 Files selected for processing (9)
  • includes/Api/Metrics.php
  • src/app.css
  • src/components/Header.js
  • src/components/HelpResources.js
  • src/components/QuickSend.js
  • src/components/VolumeChart.js
  • src/index.js
  • src/pages/Dashboard.js
  • src/style.scss
✅ Files skipped from review due to trivial changes (1)
  • src/app.css
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/Header.js
  • src/components/VolumeChart.js
  • src/components/HelpResources.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
includes/Dispatcher.php (2)

116-116: ⚠️ Potential issue | 🟠 Major

Handle $wpdb->insert() failure to avoid silent data loss.

Insert failures are silently ignored. If the DB write fails, SMS events are lost and dashboard metrics become inaccurate.

🛠️ Proposed fix
-        $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+        $inserted = $wpdb->insert( $table_name, $data ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+        if ( false === $inserted ) {
+            do_action( 'texty_sms_log_insert_failed', $wpdb->last_error, $data );
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` at line 116, The $wpdb->insert($table_name, $data)
call can fail silently; check its return value and handle failures by inspecting
$wpdb->last_error and surface/log the failure so SMS events are not lost. Modify
the function containing the $wpdb->insert call to capture the return (e.g.
$result = $wpdb->insert(...)); if $result === false, log a descriptive error
containing $wpdb->last_error plus context (table name $table_name and encoded
$data), and then either return/throw an error or mark the event as failed so
callers can handle it (preserve the phpcs ignore comment if needed). Ensure you
reference the same $wpdb, $table_name and $data variables when implementing this
check.

99-100: ⚠️ Potential issue | 🟠 Major

Status logic can misclassify failed sends as 'sent'.

Non-WP_Error failures (e.g., false, ['success' => false]) will be marked as 'sent', skewing delivery metrics.

🐛 Proposed fix
         // Determine status
-        $status = is_wp_error( $result ) ? 'failed' : 'sent';
+        $status = 'failed';
+        if ( ! is_wp_error( $result ) ) {
+            if ( is_array( $result ) && ! empty( $result['success'] ) ) {
+                $status = 'sent';
+            } elseif ( true === $result ) {
+                $status = 'sent';
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` around lines 99 - 100, The current status assignment
uses only is_wp_error($result) which will mark non-WP_Error failures (false,
null, or arrays like ['success'=>false]) as 'sent'; update the logic in
includes/Dispatcher.php (class Dispatcher, around the $status and $result
handling) to treat any falsy result or an explicit failure indicator as 'failed'
— e.g., set $status to 'failed' when is_wp_error($result) OR $result === false
OR $result === null OR (is_array($result) && array_key_exists('success',
$result) && !$result['success']); otherwise set 'sent'. Ensure you reference and
update the $status assignment and any downstream code that relies on $status.
🧹 Nitpick comments (2)
includes/Dispatcher.php (1)

118-130: LGTM!

The extract_reference_id() helper cleanly handles multiple gateway response formats. The key list covers common providers (Twilio's sid, Vonage's message-id, Plivo's message_uuid, Clickatell's apiMsgId).

Minor style nit: add a blank line after the method signature for consistency with other methods.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Dispatcher.php` around lines 118 - 130, The method
extract_reference_id currently has no blank line after its signature; add a
single blank line immediately after the function declaration (private function
extract_reference_id( $result ) {) so the body starts with an empty line for
consistency with the project's method formatting conventions; keep the function
name and existing logic unchanged.
includes/Install.php (1)

13-22: Minor: Consider adding index for query performance.

Once the dbDelta() call is added, consider adding an index on created_at and status columns since Metrics.php queries heavily filter by these fields with WHERE created_at >= ... AND status = 'sent'.

📊 Schema with index
         $sql = "CREATE TABLE {$table_name} (
             id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
             receiver VARCHAR(20) NOT NULL,
             gateway VARCHAR(50) NOT NULL,
             status VARCHAR(20) DEFAULT NULL,
             created_at DATETIME NOT NULL,
             updated_at DATETIME NOT NULL,
-            reference_id VARCHAR(100) DEFAULT NULL
+            reference_id VARCHAR(100) DEFAULT NULL,
+            KEY status_created (status, created_at)
         ) {$charset_collate};";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` around lines 13 - 22, The table created in
create_tables() needs an index on created_at and status to speed up the
Metrics.php queries that use WHERE created_at >= ... AND status = 'sent'; update
the SQL used in create_tables() (the string passed to dbDelta()) to add a
composite index (or separate indexes) on (created_at, status) and then call
dbDelta() so WordPress will create the index on existing installs; ensure the
index name is unique (e.g., idx_created_at_status) and leave
update_option('texty_version', TEXTY_VERSION) intact so migrations can be
detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/Install.php`:
- Around line 29-46: The create_tables() method builds $sql but never executes
it; update create_tables() so it constructs a dbDelta-compatible CREATE TABLE
statement (remove the "IF NOT EXISTS" from $sql), keep the existing require_once
ABSPATH . 'wp-admin/includes/upgrade.php' and $charset_collate usage, then call
dbDelta($sql) to actually create/update the table; refer to the create_tables()
function and the $sql variable so you remove IF NOT EXISTS and add the
dbDelta($sql) invocation after $sql is set.

---

Duplicate comments:
In `@includes/Dispatcher.php`:
- Line 116: The $wpdb->insert($table_name, $data) call can fail silently; check
its return value and handle failures by inspecting $wpdb->last_error and
surface/log the failure so SMS events are not lost. Modify the function
containing the $wpdb->insert call to capture the return (e.g. $result =
$wpdb->insert(...)); if $result === false, log a descriptive error containing
$wpdb->last_error plus context (table name $table_name and encoded $data), and
then either return/throw an error or mark the event as failed so callers can
handle it (preserve the phpcs ignore comment if needed). Ensure you reference
the same $wpdb, $table_name and $data variables when implementing this check.
- Around line 99-100: The current status assignment uses only
is_wp_error($result) which will mark non-WP_Error failures (false, null, or
arrays like ['success'=>false]) as 'sent'; update the logic in
includes/Dispatcher.php (class Dispatcher, around the $status and $result
handling) to treat any falsy result or an explicit failure indicator as 'failed'
— e.g., set $status to 'failed' when is_wp_error($result) OR $result === false
OR $result === null OR (is_array($result) && array_key_exists('success',
$result) && !$result['success']); otherwise set 'sent'. Ensure you reference and
update the $status assignment and any downstream code that relies on $status.

---

Nitpick comments:
In `@includes/Dispatcher.php`:
- Around line 118-130: The method extract_reference_id currently has no blank
line after its signature; add a single blank line immediately after the function
declaration (private function extract_reference_id( $result ) {) so the body
starts with an empty line for consistency with the project's method formatting
conventions; keep the function name and existing logic unchanged.

In `@includes/Install.php`:
- Around line 13-22: The table created in create_tables() needs an index on
created_at and status to speed up the Metrics.php queries that use WHERE
created_at >= ... AND status = 'sent'; update the SQL used in create_tables()
(the string passed to dbDelta()) to add a composite index (or separate indexes)
on (created_at, status) and then call dbDelta() so WordPress will create the
index on existing installs; ensure the index name is unique (e.g.,
idx_created_at_status) and leave update_option('texty_version', TEXTY_VERSION)
intact so migrations can be detected.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a61633 and 2438b3f.

📒 Files selected for processing (4)
  • includes/Api/Metrics.php
  • includes/Dispatcher.php
  • includes/Install.php
  • src/style.scss
✅ Files skipped from review due to trivial changes (1)
  • src/style.scss

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
includes/Install.php (1)

37-45: ⚠️ Potential issue | 🟠 Major

Remove IF NOT EXISTS from the dbDelta() DDL.

dbDelta() can mis-parse CREATE TABLE IF NOT EXISTS ..., which can prevent future schema diffs from applying reliably.

Suggested fix
-        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
+        $sql = "CREATE TABLE {$table_name} (
             id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
             receiver VARCHAR(20) NOT NULL,
             gateway VARCHAR(50) NOT NULL,
             status VARCHAR(20) DEFAULT NULL,
             created_at DATETIME NOT NULL,
             updated_at DATETIME NOT NULL,
             reference_id VARCHAR(100) DEFAULT NULL
         ) {$charset_collate};";
WordPress dbDelta CREATE TABLE syntax requirements: is "IF NOT EXISTS" recommended or known to break dbDelta table parsing/diff detection?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` around lines 37 - 45, The CREATE TABLE statement
assigned to $sql in includes/Install.php uses "CREATE TABLE IF NOT EXISTS",
which breaks WordPress dbDelta() parsing; remove the "IF NOT EXISTS" token so
the SQL reads "CREATE TABLE {$table_name} (... ) {$charset_collate};" (leave all
columns, $table_name and $charset_collate intact) before calling dbDelta() to
ensure future schema diffs are detected correctly.
🧹 Nitpick comments (1)
includes/Install.php (1)

38-45: Add indexes for metrics query paths.

Current schema lacks indexes aligned with dashboard reads (status, created_at filters). This will degrade as texty_sms_stat grows.

Suggested schema indexing
         $sql = "CREATE TABLE {$table_name} (
             id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
             receiver VARCHAR(20) NOT NULL,
             gateway VARCHAR(50) NOT NULL,
             status VARCHAR(20) DEFAULT NULL,
             created_at DATETIME NOT NULL,
             updated_at DATETIME NOT NULL,
-            reference_id VARCHAR(100) DEFAULT NULL
+            reference_id VARCHAR(100) DEFAULT NULL,
+            KEY idx_created_at (created_at),
+            KEY idx_status_created_at (status, created_at)
         ) {$charset_collate};";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Install.php` around lines 38 - 45, Add appropriate indexes to the
texty_sms_stat table to support dashboard queries that filter by status and
created_at: alter the CREATE TABLE statement in includes/Install.php (the block
defining texty_sms_stat with columns id, receiver, gateway, status, created_at,
updated_at, reference_id) to include an index on status and a composite index on
(created_at, status) (or created_at alone if preferred) so reads for
metrics/dashboards are efficient; update the schema string accordingly and
ensure index names are unique (e.g., idx_texty_status,
idx_texty_created_at_status).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/Api/Metrics.php`:
- Line 196: Replace the DateTimeImmutable->format('M') usage with a call to
wp_date so month labels are localized; specifically, where the array sets
'month' (using new \DateTimeImmutable($month_key . '-01', wp_timezone())), pass
that date's timestamp (or the equivalent strtotime of "$month_key-01") into
wp_date('M', ...) with wp_timezone() so WordPress returns translated month
abbreviations for the dashboard output.

---

Duplicate comments:
In `@includes/Install.php`:
- Around line 37-45: The CREATE TABLE statement assigned to $sql in
includes/Install.php uses "CREATE TABLE IF NOT EXISTS", which breaks WordPress
dbDelta() parsing; remove the "IF NOT EXISTS" token so the SQL reads "CREATE
TABLE {$table_name} (... ) {$charset_collate};" (leave all columns, $table_name
and $charset_collate intact) before calling dbDelta() to ensure future schema
diffs are detected correctly.

---

Nitpick comments:
In `@includes/Install.php`:
- Around line 38-45: Add appropriate indexes to the texty_sms_stat table to
support dashboard queries that filter by status and created_at: alter the CREATE
TABLE statement in includes/Install.php (the block defining texty_sms_stat with
columns id, receiver, gateway, status, created_at, updated_at, reference_id) to
include an index on status and a composite index on (created_at, status) (or
created_at alone if preferred) so reads for metrics/dashboards are efficient;
update the schema string accordingly and ensure index names are unique (e.g.,
idx_texty_status, idx_texty_created_at_status).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2438b3f and e38e5c5.

📒 Files selected for processing (2)
  • includes/Api/Metrics.php
  • includes/Install.php

$chart_data = [];
foreach ( $months_map as $month_key => $count ) {
$chart_data[] = [
'month' => ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->format( 'M' ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

WordPress wp_date() function localization month names compared to PHP DateTimeImmutable format M

💡 Result:

  • wp_date( 'M', … ) is locale-aware: WordPress rewrites format chars like M/F/D/l using WP_Locale translated month/weekday names (and can also apply genitive/declension rules via wp_maybe_decline_date()). [1] [3] [4]
  • DateTimeImmutable->format('M') is not locale-aware: PHP defines M as a fixed “Jan … Dec” English abbreviation, regardless of locale. [2]

So if you want localized month abbreviations like WordPress produces, format via WordPress:

$dt = new DateTimeImmutable('2026-02-26 12:00:00', wp_timezone());
echo wp_date('M', $dt->getTimestamp(), $dt->getTimezone()); // localized "Feb" (or equivalent)

If you instead need localization purely in PHP (outside WP’s locale system), use an ICU-based formatter (e.g., IntlDateFormatter), because format('M') won’t translate. [2]

Sources: [1] WordPress wp_date() reference, [2] PHP DateTimeInterface::format() reference, [3] WordPress WP_Locale reference, [4] WordPress wp_maybe_decline_date() reference.


🏁 Script executed:

head -n 200 includes/Api/Metrics.php | tail -n 20

Repository: getdokan/texty

Length of output: 674


Use localized month labels for dashboard output.

Line 196 uses PHP DateTimeImmutable::format('M'), which returns English month abbreviations ("Jan", "Feb", etc.) regardless of site locale. For UI strings, use wp_date() instead, which respects the WordPress locale and applies translated month names.

Suggested fix
-                'month' => ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->format( 'M' ),
+                'month' => wp_date(
+                    'M',
+                    ( new \DateTimeImmutable( $month_key . '-01', wp_timezone() ) )->getTimestamp(),
+                    wp_timezone()
+                ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/Api/Metrics.php` at line 196, Replace the
DateTimeImmutable->format('M') usage with a call to wp_date so month labels are
localized; specifically, where the array sets 'month' (using new
\DateTimeImmutable($month_key . '-01', wp_timezone())), pass that date's
timestamp (or the equivalent strtotime of "$month_key-01") into wp_date('M',
...) with wp_timezone() so WordPress returns translated month abbreviations for
the dashboard output.

@Shamim-97 Shamim-97 self-assigned this Feb 26, 2026
@Shamim-97 Shamim-97 added the invalid This doesn't seem right label Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid This doesn't seem right

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants