| 1 |
<?php |
| 2 |
/** |
| 3 |
* Standalone subscription details page view. |
| 4 |
* |
| 5 |
* Rendered by Menu::render_subscription_details_page(). Matches the |
| 6 |
* WPSubscription admin design system (wpsubs-* components + :root tokens). |
| 7 |
* |
| 8 |
* @package SpringDevs\Subscription\Admin |
| 9 |
* |
| 10 |
* @var int $subscription_id Subscription post ID. |
| 11 |
* @var array $subscription_data Structured data from Helper::get_subscription_data(). |
| 12 |
* @var array $rows Filtered info rows from Subscriptions::get_info_rows(). |
| 13 |
* @var \WC_Order $order Related WooCommerce order. |
| 14 |
* @var mixed $order_item Related order line item. |
| 15 |
* @var array $actions Allowed action slugs for the current status. |
| 16 |
* @var array $actions_data Map of action slug => label/value. |
| 17 |
* @var array $order_histories Related order relation rows. |
| 18 |
* @var string $list_url Subscriptions list URL. |
| 19 |
* @var string $form_action URL the status form posts to. |
| 20 |
*/ |
| 21 |
|
| 22 |
if ( ! defined( 'ABSPATH' ) ) { |
| 23 |
exit; |
| 24 |
} |
| 25 |
|
| 26 |
use SpringDevs\Subscription\Illuminate\Helper; |
| 27 |
|
| 28 |
$product_name = $order_item ? $order_item->get_name() : '-'; |
| 29 |
$product_id = $order_item ? $order_item->get_product_id() : 0; |
| 30 |
$product_link = $product_id ? get_the_permalink( $product_id ) : ''; |
| 31 |
$product_obj = $product_id ? wc_get_product( $product_id ) : null; |
| 32 |
$product_image = $product_obj ? $product_obj->get_image( 'thumbnail', array( 'class' => 'subscrpt-plan-img' ) ) : ''; |
| 33 |
|
| 34 |
$subscrpt_status = $subscription_data['status'] ?? get_post_status( $subscription_id ); |
| 35 |
$verbose_status = Helper::get_verbose_status( $subscrpt_status ); |
| 36 |
|
| 37 |
$badge_mod_map = array( |
| 38 |
'active' => 'active', |
| 39 |
'pending' => 'pending', |
| 40 |
'pe_cancelled' => 'pending-cancel', |
| 41 |
'cancelled' => 'cancelled', |
| 42 |
'expired' => 'expired', |
| 43 |
'completed' => 'completed', |
| 44 |
'draft' => 'draft', |
| 45 |
'trash' => 'trash', |
| 46 |
); |
| 47 |
$badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired'; |
| 48 |
|
| 49 |
$is_grace_period = isset( $subscription_data['grace_period'] ); |
| 50 |
$grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0; |
| 51 |
$grace_end_date = $subscription_data['grace_period']['end_date'] ?? ''; |
| 52 |
$grace_end_date = ! empty( $grace_end_date ) ? wp_date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ), strtotime( $grace_end_date ) ) : ''; |
| 53 |
|
| 54 |
if ( $is_grace_period && $grace_remaining > 0 ) { |
| 55 |
$badge_mod = 'active'; |
| 56 |
$verbose_status = __( 'Active', 'subscription' ); |
| 57 |
} |
| 58 |
|
| 59 |
$customer_id = $order ? $order->get_customer_id() : 0; |
| 60 |
$customer_name = $order ? $order->get_formatted_billing_full_name() : '-'; |
| 61 |
$customer_email = $order ? $order->get_billing_email() : ''; |
| 62 |
$customer_phone = $order ? $order->get_billing_phone() : ''; |
| 63 |
$customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : ''; |
| 64 |
|
| 65 |
// Page header: product title + meta description (badge · #id · email · next payment). |
| 66 |
$header_title = ( $product_name && '-' !== $product_name ) |
| 67 |
? $product_name |
| 68 |
/* translators: %s: Subscription ID */ |
| 69 |
: sprintf( __( 'Subscription #%s', 'subscription' ), $subscription_id ); |
| 70 |
|
| 71 |
$next_payment_date = $subscription_data['next_date'] ?? ''; |
| 72 |
$next_payment_date = ! empty( $next_payment_date ) ? wp_date( get_option( 'date_format' ), strtotime( $next_payment_date ) ) : ''; |
| 73 |
|
| 74 |
$header_badge = '<span class="wpsubs-badge wpsubs-badge--' . esc_attr( $badge_mod ) . '">' . esc_html( $verbose_status ); |
| 75 |
if ( $is_grace_period && $grace_remaining > 0 ) { |
| 76 |
/* translators: %d: days remaining */ |
| 77 |
$grace_title = sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ); |
| 78 |
$header_badge .= ' <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="' . esc_attr( $grace_title ) . '"></span>'; |
| 79 |
} |
| 80 |
$header_badge .= '</span>'; |
| 81 |
|
| 82 |
$header_segments = array( |
| 83 |
$header_badge, |
| 84 |
'#' . (int) $subscription_id, |
| 85 |
); |
| 86 |
if ( $customer_email ) { |
| 87 |
$header_segments[] = esc_html( $customer_email ); |
| 88 |
} |
| 89 |
if ( $next_payment_date ) { |
| 90 |
/* translators: %s: next payment date */ |
| 91 |
$header_segments[] = esc_html( sprintf( __( 'Next payment on %s', 'subscription' ), $next_payment_date ) ); |
| 92 |
} |
| 93 |
$header_desc_html = implode( '<span class="subscrpt-detail-head__sep">·</span>', $header_segments ); |
| 94 |
|
| 95 |
$rows = is_array( $rows ) ? $rows : array(); |
| 96 |
|
| 97 |
// Top summary tiles — pulled from the info rows so Pro's filtered values stay |
| 98 |
// in sync. Their keys are marked used so they are not repeated below. |
| 99 |
$summary_tiles = array(); |
| 100 |
if ( $order && isset( $subscription_data['price'] ) && '' !== $subscription_data['price'] ) { |
| 101 |
// Build the recurring price from structured data so the price and period can |
| 102 |
// be styled separately (price prominent, "/ period" small and muted). |
| 103 |
$timing_per = (int) ( $subscription_data['schedule']['timing_per'] ?? 1 ); |
| 104 |
$timing_option = $subscription_data['schedule']['timing_option'] ?? ''; |
| 105 |
$period_label = ''; |
| 106 |
if ( $timing_option ) { |
| 107 |
$timing_unit = ucfirst( Helper::get_typos( $timing_per, $timing_option ) ); |
| 108 |
$period_label = $timing_per > 1 |
| 109 |
? $timing_per . ' ' . $timing_unit |
| 110 |
: $timing_unit; |
| 111 |
} |
| 112 |
|
| 113 |
// Net of any discount that carries into renewals, with the original struck through. |
| 114 |
$recurring_totals = Helper::get_subscription_display_totals( $subscription_id, $order_item ); |
| 115 |
$recurring_full = $recurring_totals['full_excl'] + $recurring_totals['tax'] + $recurring_totals['discount_tax']; |
| 116 |
|
| 117 |
$recurring_value = wc_price( (float) $recurring_totals['total'], array( 'currency' => $order->get_currency() ) ); |
| 118 |
|
| 119 |
if ( $recurring_totals['has_discount'] ) { |
| 120 |
$recurring_value = '<del aria-hidden="true" class="subscrpt-summary__was">' |
| 121 |
. wc_price( $recurring_full, array( 'currency' => $order->get_currency() ) ) |
| 122 |
. '</del> ' . $recurring_value; |
| 123 |
} |
| 124 |
|
| 125 |
if ( $period_label ) { |
| 126 |
$recurring_value .= '<span class="subscrpt-summary__unit"> / ' . esc_html( $period_label ) . '</span>'; |
| 127 |
} |
| 128 |
|
| 129 |
$summary_tiles[] = array( |
| 130 |
'label' => __( 'Recurring', 'subscription' ), |
| 131 |
'value' => $recurring_value, |
| 132 |
); |
| 133 |
} |
| 134 |
if ( ! empty( $subscription_data['start_date'] ) ) { |
| 135 |
$summary_tiles[] = array( |
| 136 |
'label' => __( 'Started', 'subscription' ), |
| 137 |
'value' => esc_html( wp_date( get_option( 'date_format' ), strtotime( $subscription_data['start_date'] ) ) ), |
| 138 |
); |
| 139 |
} |
| 140 |
// Always show the Next Payment tile; dash when there is no next date. |
| 141 |
$summary_tiles[] = array( |
| 142 |
'label' => __( 'Next Payment', 'subscription' ), |
| 143 |
'value' => ! empty( $subscription_data['next_date'] ) |
| 144 |
? esc_html( wp_date( get_option( 'date_format' ), strtotime( $subscription_data['next_date'] ) ) ) |
| 145 |
: '-', |
| 146 |
); |
| 147 |
// Total payments is not part of $subscription_data; pull it from the info rows. |
| 148 |
if ( isset( $rows['total_payments'] ) ) { |
| 149 |
$summary_tiles[] = array( |
| 150 |
'label' => __( 'Total Payments', 'subscription' ), |
| 151 |
'value' => $rows['total_payments']['value'], |
| 152 |
); |
| 153 |
} |
| 154 |
|
| 155 |
// Group the remaining info rows into cards. Unmapped keys — including rows added |
| 156 |
// through the subscrpt_admin_info_rows filter (Pro) — fall into "Additional |
| 157 |
// Details" so nothing is dropped. Status is shown as the header badge. |
| 158 |
$used_keys = array( |
| 159 |
'cost' => true, |
| 160 |
'start_date' => true, |
| 161 |
'next_date' => true, |
| 162 |
'total_payments' => true, |
| 163 |
'status' => true, |
| 164 |
); |
| 165 |
|
| 166 |
// Plan card: product + qty render side by side; trial/signup fee as extra rows. |
| 167 |
$plan_product = isset( $rows['product'] ) ? $rows['product']['value'] : ''; |
| 168 |
$plan_qty = isset( $rows['quantity'] ) ? $rows['quantity']['value'] : ''; |
| 169 |
$plan_label = function_exists( 'subscrpt_get_subscription_plan_label' ) ? subscrpt_get_subscription_plan_label( $subscription_id ) : ''; |
| 170 |
$plan_extra = array(); |
| 171 |
foreach ( array( 'product', 'quantity', 'signup_fee', 'trial', 'trial_period' ) as $plan_key ) { |
| 172 |
$used_keys[ $plan_key ] = true; |
| 173 |
if ( in_array( $plan_key, array( 'signup_fee', 'trial', 'trial_period' ), true ) && isset( $rows[ $plan_key ] ) ) { |
| 174 |
$plan_extra[ $plan_key ] = $rows[ $plan_key ]; |
| 175 |
} |
| 176 |
} |
| 177 |
|
| 178 |
$build_sections = function ( array $map ) use ( $rows, &$used_keys ) { |
| 179 |
$out = array(); |
| 180 |
foreach ( $map as $section ) { |
| 181 |
$section_rows = array(); |
| 182 |
foreach ( $section['keys'] as $key ) { |
| 183 |
if ( isset( $rows[ $key ] ) ) { |
| 184 |
$section_rows[ $key ] = $rows[ $key ]; |
| 185 |
$used_keys[ $key ] = true; |
| 186 |
} |
| 187 |
} |
| 188 |
if ( ! empty( $section_rows ) ) { |
| 189 |
$out[] = array( |
| 190 |
'title' => $section['title'], |
| 191 |
'rows' => $section_rows, |
| 192 |
); |
| 193 |
} |
| 194 |
} |
| 195 |
return $out; |
| 196 |
}; |
| 197 |
|
| 198 |
// Payment Method renders next to the Plan card (main column). Shows the gateway |
| 199 |
// icon + title only — no key/value table. Extra rows (e.g. Stripe auto renewal) |
| 200 |
// keep their kv layout beneath. |
| 201 |
$used_keys['payment_method'] = true; |
| 202 |
$used_keys['stripe_auto_renewal'] = true; |
| 203 |
$payment_title = $order ? $order->get_payment_method_title() : ''; |
| 204 |
$payment_icon = ''; |
| 205 |
$payment_plugin = ''; |
| 206 |
if ( $order ) { |
| 207 |
$gateways = WC()->payment_gateways() ? WC()->payment_gateways()->payment_gateways() : array(); |
| 208 |
$gateway_id = $order->get_payment_method(); |
| 209 |
$gateway_obj = isset( $gateways[ $gateway_id ] ) ? $gateways[ $gateway_id ] : null; |
| 210 |
$payment_icon = $gateway_obj ? $gateway_obj->get_icon() : ''; |
| 211 |
|
| 212 |
// Resolve which plugin registered this gateway (e.g. several Stripe methods |
| 213 |
// all come from "WooCommerce Stripe Gateway"). Map the gateway class file to |
| 214 |
// its plugin folder, then read that plugin's header Name. |
| 215 |
if ( $gateway_obj ) { |
| 216 |
try { |
| 217 |
$gateway_file = wp_normalize_path( ( new ReflectionClass( $gateway_obj ) )->getFileName() ); |
| 218 |
$plugins_dir = wp_normalize_path( WP_PLUGIN_DIR ); |
| 219 |
if ( $gateway_file && 0 === strpos( $gateway_file, $plugins_dir ) ) { |
| 220 |
$gateway_slug = strtok( ltrim( substr( $gateway_file, strlen( $plugins_dir ) ), '/' ), '/' ); |
| 221 |
if ( ! function_exists( 'get_plugins' ) ) { |
| 222 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 223 |
} |
| 224 |
foreach ( get_plugins() as $plugin_path => $plugin_data ) { |
| 225 |
if ( strtok( $plugin_path, '/' ) === $gateway_slug ) { |
| 226 |
$payment_plugin = $plugin_data['Name']; |
| 227 |
break; |
| 228 |
} |
| 229 |
} |
| 230 |
} |
| 231 |
} catch ( \Exception $e ) { |
| 232 |
$payment_plugin = ''; |
| 233 |
} |
| 234 |
} |
| 235 |
} |
| 236 |
// Payment Method card is always shown; the title falls back to '-' when no |
| 237 |
// gateway is resolved. |
| 238 |
$has_payment = true; |
| 239 |
|
| 240 |
// Secondary info → sidebar. |
| 241 |
$side_sections = $build_sections( |
| 242 |
array( |
| 243 |
array( |
| 244 |
'title' => __( 'Billing & Shipping', 'subscription' ), |
| 245 |
'keys' => array( 'billing_address', 'shipping_address' ), |
| 246 |
), |
| 247 |
) |
| 248 |
); |
| 249 |
|
| 250 |
// Any leftover rows → Additional Details (main column). |
| 251 |
$main_sections = array(); |
| 252 |
$leftover_rows = array_diff_key( $rows, $used_keys ); |
| 253 |
if ( ! empty( $leftover_rows ) ) { |
| 254 |
$main_sections[] = array( |
| 255 |
'title' => __( 'Additional Details', 'subscription' ), |
| 256 |
'rows' => $leftover_rows, |
| 257 |
); |
| 258 |
} |
| 259 |
|
| 260 |
// Resolve related orders once. |
| 261 |
$related_rows = array(); |
| 262 |
foreach ( $order_histories as $history ) { |
| 263 |
$related_order = wc_get_order( $history->order_id ); |
| 264 |
if ( ! $related_order ) { |
| 265 |
continue; |
| 266 |
} |
| 267 |
$related_rows[] = array( |
| 268 |
'order' => $related_order, |
| 269 |
'label' => ucfirst( str_replace( '-', ' ', (string) $history->type ) ), |
| 270 |
); |
| 271 |
} |
| 272 |
|
| 273 |
/* |
| 274 |
* Sequence number within this subscription: 1 = first (parent) order, then each |
| 275 |
* renewal in order. Helper::get_related_orders() returns newest-first, so the |
| 276 |
* sequence counts down across the rendered rows. |
| 277 |
*/ |
| 278 |
$related_count = count( $related_rows ); |
| 279 |
foreach ( array_keys( $related_rows ) as $related_index ) { |
| 280 |
$related_rows[ $related_index ]['seq'] = $related_count - $related_index; |
| 281 |
} |
| 282 |
|
| 283 |
// Reusable per-page + date toolbar for the paginated tables. Uses the admin |
| 284 |
// advanced-select component; the date options are injected client-side from the |
| 285 |
// table's date column. |
| 286 |
$subscrpt_render_table_tools = function () { |
| 287 |
?> |
| 288 |
<div class="subscrpt-card__tools"> |
| 289 |
<?php |
| 290 |
wpsubs_render_adv_select( |
| 291 |
array( |
| 292 |
'name' => 'subscrpt_date_filter', |
| 293 |
'placeholder' => __( 'All Dates', 'subscription' ), |
| 294 |
'value' => '', |
| 295 |
'align' => 'right', |
| 296 |
'class' => 'subscrpt-filter-date', |
| 297 |
'options' => array( |
| 298 |
array( |
| 299 |
'value' => '', |
| 300 |
'label' => __( 'All Dates', 'subscription' ), |
| 301 |
), |
| 302 |
), |
| 303 |
) |
| 304 |
); |
| 305 |
wpsubs_render_per_page_select( |
| 306 |
array( |
| 307 |
'name' => 'subscrpt_per_page', |
| 308 |
'align' => 'right', |
| 309 |
'class' => 'subscrpt-filter-perpage', |
| 310 |
) |
| 311 |
); |
| 312 |
?> |
| 313 |
</div> |
| 314 |
<?php |
| 315 |
}; |
| 316 |
|
| 317 |
/** |
| 318 |
* Shared context passed to every subscription-details extension hook so that |
| 319 |
* third-party plugins can render extra sections without re-querying. |
| 320 |
* |
| 321 |
* @var array $subscrpt_details_ctx { |
| 322 |
* @type int $subscription_id Subscription post ID. |
| 323 |
* @type array $subscription_data Structured data from Helper::get_subscription_data(). |
| 324 |
* @type \WC_Order $order Related WooCommerce order (or null). |
| 325 |
* @type mixed $order_item Related order line item (or null). |
| 326 |
* @type string $status Subscription post status. |
| 327 |
* } |
| 328 |
*/ |
| 329 |
$subscrpt_details_ctx = array( |
| 330 |
'subscription_id' => $subscription_id, |
| 331 |
'subscription_data' => $subscription_data, |
| 332 |
'order' => $order, |
| 333 |
'order_item' => $order_item, |
| 334 |
'status' => $subscrpt_status, |
| 335 |
); |
| 336 |
?> |
| 337 |
<div class="wp-subscription-admin-content list-page subscrpt-subs-details"> |
| 338 |
|
| 339 |
<!-- Page header --> |
| 340 |
<div class="subscrpt-detail-head"> |
| 341 |
<h1 class="subscrpt-detail-head__title"><?php echo esc_html( $header_title ); ?></h1> |
| 342 |
<div class="subscrpt-detail-head__desc"><?php echo wp_kses_post( $header_desc_html ); ?></div> |
| 343 |
<div class="subscrpt-detail-head__divider"></div> |
| 344 |
</div> |
| 345 |
|
| 346 |
<div class="subscrpt-detail-grid"> |
| 347 |
|
| 348 |
<!-- Main column --> |
| 349 |
<div class="subscrpt-detail-main"> |
| 350 |
|
| 351 |
<?php |
| 352 |
/** |
| 353 |
* Fires at the top of the details main (left) column, before the summary strip. |
| 354 |
* |
| 355 |
* Echo a `.subscrpt-card` to add a full-width section here. |
| 356 |
* |
| 357 |
* @param array $subscrpt_details_ctx Subscription details context. |
| 358 |
*/ |
| 359 |
do_action( 'subscrpt_details_main_top', $subscrpt_details_ctx ); |
| 360 |
?> |
| 361 |
|
| 362 |
<!-- Summary strip --> |
| 363 |
<?php if ( ! empty( $summary_tiles ) ) : ?> |
| 364 |
<div class="subscrpt-summary"> |
| 365 |
<?php foreach ( $summary_tiles as $tile ) : ?> |
| 366 |
<div class="subscrpt-summary__tile"> |
| 367 |
<span class="subscrpt-summary__label"><?php echo esc_html( $tile['label'] ); ?></span> |
| 368 |
<span class="subscrpt-summary__value"><?php echo wp_kses_post( $tile['value'] ); ?></span> |
| 369 |
</div> |
| 370 |
<?php endforeach; ?> |
| 371 |
</div> |
| 372 |
<?php endif; ?> |
| 373 |
|
| 374 |
<?php if ( $is_grace_period ) : ?> |
| 375 |
<!-- Grace period card --> |
| 376 |
<div class="subscrpt-card"> |
| 377 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Grace Period', 'subscription' ); ?></div> |
| 378 |
<div class="subscrpt-card__body"> |
| 379 |
<table class="subscrpt-kv"> |
| 380 |
<tbody> |
| 381 |
<tr> |
| 382 |
<th><?php esc_html_e( 'Remaining', 'subscription' ); ?></th> |
| 383 |
<td><?php echo esc_html( sprintf( /* translators: %d: days */ _n( '%d day', '%d days', (int) $grace_remaining, 'subscription' ), (int) $grace_remaining ) ); ?></td> |
| 384 |
</tr> |
| 385 |
<?php if ( $grace_end_date ) : ?> |
| 386 |
<tr> |
| 387 |
<th><?php esc_html_e( 'End Date', 'subscription' ); ?></th> |
| 388 |
<td><?php echo esc_html( $grace_end_date ); ?></td> |
| 389 |
</tr> |
| 390 |
<?php endif; ?> |
| 391 |
</tbody> |
| 392 |
</table> |
| 393 |
</div> |
| 394 |
</div> |
| 395 |
<?php endif; ?> |
| 396 |
|
| 397 |
<!-- Plan + Payment Method side by side --> |
| 398 |
<?php |
| 399 |
$has_plan = $plan_product || $plan_qty || ! empty( $plan_extra ); |
| 400 |
if ( $has_plan || $has_payment ) : |
| 401 |
?> |
| 402 |
<div class="subscrpt-plan-pay"> |
| 403 |
<?php if ( $has_plan ) : ?> |
| 404 |
<!-- Plan card (product + qty side by side) --> |
| 405 |
<div class="subscrpt-card subscrpt-card--plan"> |
| 406 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Product', 'subscription' ); ?></div> |
| 407 |
<div class="subscrpt-card__body"> |
| 408 |
<div class="subscrpt-plan-row"> |
| 409 |
<div class="subscrpt-plan-thumb"> |
| 410 |
<?php if ( $product_image ) : ?> |
| 411 |
<?php echo wp_kses_post( $product_image ); ?> |
| 412 |
<?php else : ?> |
| 413 |
<span class="subscrpt-plan-thumb__ph dashicons dashicons-format-image"></span> |
| 414 |
<?php endif; ?> |
| 415 |
</div> |
| 416 |
<div class="subscrpt-plan-meta"> |
| 417 |
<span class="subscrpt-plan-name"><?php echo $plan_product ? wp_kses_post( $plan_product ) : '—'; ?></span> |
| 418 |
<?php if ( $plan_label ) : ?> |
| 419 |
<span class="subscrpt-plan-term"> |
| 420 |
<?php |
| 421 |
/* translators: %s: plan name */ |
| 422 |
printf( esc_html__( 'Plan: %s', 'subscription' ), esc_html( $plan_label ) ); |
| 423 |
?> |
| 424 |
</span> |
| 425 |
<?php endif; ?> |
| 426 |
<span class="subscrpt-plan-qty"> |
| 427 |
<?php |
| 428 |
/* translators: %s: quantity */ |
| 429 |
printf( esc_html__( 'Quantity: %s', 'subscription' ), $plan_qty ? wp_kses_post( $plan_qty ) : '1' ); |
| 430 |
?> |
| 431 |
</span> |
| 432 |
</div> |
| 433 |
</div> |
| 434 |
<?php if ( ! empty( $plan_extra ) ) : ?> |
| 435 |
<table class="subscrpt-kv subscrpt-plan-extra"> |
| 436 |
<tbody> |
| 437 |
<?php foreach ( $plan_extra as $row ) : ?> |
| 438 |
<tr> |
| 439 |
<th><?php echo esc_html( $row['label'] ); ?></th> |
| 440 |
<td><?php echo wp_kses_post( $row['value'] ); ?></td> |
| 441 |
</tr> |
| 442 |
<?php endforeach; ?> |
| 443 |
</tbody> |
| 444 |
</table> |
| 445 |
<?php endif; ?> |
| 446 |
</div> |
| 447 |
</div> |
| 448 |
<?php endif; ?> |
| 449 |
|
| 450 |
<?php if ( $has_payment ) : ?> |
| 451 |
<!-- Payment method card (gateway icon + title) --> |
| 452 |
<div class="subscrpt-card"> |
| 453 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Payment Method', 'subscription' ); ?></div> |
| 454 |
<div class="subscrpt-card__body"> |
| 455 |
<?php if ( $payment_title ) : ?> |
| 456 |
<div class="subscrpt-payment"> |
| 457 |
<span class="subscrpt-payment__icon"> |
| 458 |
<?php if ( $payment_icon ) : ?> |
| 459 |
<?php echo wp_kses_post( $payment_icon ); ?> |
| 460 |
<?php else : ?> |
| 461 |
<span class="subscrpt-payment__icon-ph dashicons dashicons-bank"></span> |
| 462 |
<?php endif; ?> |
| 463 |
</span> |
| 464 |
<span class="subscrpt-payment__meta"> |
| 465 |
<span class="subscrpt-payment__title"><?php echo esc_html( $payment_title ); ?></span> |
| 466 |
<?php if ( $payment_plugin ) : ?> |
| 467 |
<span class="subscrpt-payment__plugin"><?php echo esc_html( $payment_plugin ); ?></span> |
| 468 |
<?php endif; ?> |
| 469 |
</span> |
| 470 |
</div> |
| 471 |
<?php else : ?> |
| 472 |
<p class="subscrpt-muted"><?php esc_html_e( 'No payment method.', 'subscription' ); ?></p> |
| 473 |
<?php endif; ?> |
| 474 |
</div> |
| 475 |
</div> |
| 476 |
<?php endif; ?> |
| 477 |
</div> |
| 478 |
<?php endif; ?> |
| 479 |
|
| 480 |
<!-- Detail sections --> |
| 481 |
<?php foreach ( $main_sections as $section ) : ?> |
| 482 |
<div class="subscrpt-card"> |
| 483 |
<div class="subscrpt-card__head"><?php echo esc_html( $section['title'] ); ?></div> |
| 484 |
<div class="subscrpt-card__body"> |
| 485 |
<table class="subscrpt-kv"> |
| 486 |
<tbody> |
| 487 |
<?php foreach ( $section['rows'] as $row ) : ?> |
| 488 |
<tr> |
| 489 |
<th><?php echo esc_html( $row['label'] ); ?></th> |
| 490 |
<td><?php echo wp_kses_post( $row['value'] ); ?></td> |
| 491 |
</tr> |
| 492 |
<?php endforeach; ?> |
| 493 |
</tbody> |
| 494 |
</table> |
| 495 |
</div> |
| 496 |
</div> |
| 497 |
<?php endforeach; ?> |
| 498 |
|
| 499 |
<?php if ( empty( $summary_tiles ) && empty( $main_sections ) && empty( $side_sections ) && ! $has_payment && ! $plan_product && ! $plan_qty && empty( $plan_extra ) && ! $is_grace_period ) : ?> |
| 500 |
<div class="subscrpt-card"> |
| 501 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Subscription Details', 'subscription' ); ?></div> |
| 502 |
<div class="subscrpt-card__body"> |
| 503 |
<p class="subscrpt-muted"><?php esc_html_e( 'No subscription details available.', 'subscription' ); ?></p> |
| 504 |
</div> |
| 505 |
</div> |
| 506 |
<?php endif; ?> |
| 507 |
|
| 508 |
<!-- Related orders card --> |
| 509 |
<div class="subscrpt-card" data-subscrpt-paginate data-per-page="10" data-date-col="3"> |
| 510 |
<div class="subscrpt-card__head subscrpt-card__head--toolbar"> |
| 511 |
<span class="subscrpt-card__title"><?php esc_html_e( 'Related Orders', 'subscription' ); ?></span> |
| 512 |
<?php if ( ! empty( $related_rows ) ) : ?> |
| 513 |
<?php $subscrpt_render_table_tools(); ?> |
| 514 |
<?php endif; ?> |
| 515 |
</div> |
| 516 |
<?php if ( ! empty( $related_rows ) ) : ?> |
| 517 |
<table class="wpsubs-table subscrpt-table--compact"> |
| 518 |
<thead> |
| 519 |
<tr> |
| 520 |
<th><?php esc_html_e( '#', 'subscription' ); ?></th> |
| 521 |
<th><?php esc_html_e( 'Order', 'subscription' ); ?></th> |
| 522 |
<th><?php esc_html_e( 'Type', 'subscription' ); ?></th> |
| 523 |
<th><?php esc_html_e( 'Date', 'subscription' ); ?></th> |
| 524 |
<th><?php esc_html_e( 'Status', 'subscription' ); ?></th> |
| 525 |
<th><?php esc_html_e( 'Payment', 'subscription' ); ?></th> |
| 526 |
<th><?php esc_html_e( 'Total', 'subscription' ); ?></th> |
| 527 |
</tr> |
| 528 |
</thead> |
| 529 |
<tbody> |
| 530 |
<?php |
| 531 |
// Map WC order statuses onto the shared subscription badge |
| 532 |
// modifiers so they get the same bg/border/color treatment. |
| 533 |
$order_badge_map = array( |
| 534 |
'completed' => 'active', // green |
| 535 |
'pending' => 'pending', // blue |
| 536 |
'processing' => 'pending-cancel', // yellow |
| 537 |
'on-hold' => 'pending-cancel', // yellow |
| 538 |
'failed' => 'expired', // red |
| 539 |
'refunded' => 'expired', // red |
| 540 |
'trash' => 'trash', // red |
| 541 |
'cancelled' => 'cancelled', // gray |
| 542 |
); |
| 543 |
foreach ( $related_rows as $related ) : |
| 544 |
$related_order = $related['order']; |
| 545 |
$related_status = $related_order->get_status(); |
| 546 |
$badge_class = $order_badge_map[ $related_status ] ?? 'draft'; |
| 547 |
?> |
| 548 |
<tr> |
| 549 |
<td><span class="wpsubs-cell-title"><?php echo esc_html( number_format_i18n( $related['seq'] ) ); ?></span></td> |
| 550 |
<td> |
| 551 |
<?php |
| 552 |
// translators: %s: WooCommerce order number. |
| 553 |
$order_id_label = sprintf( __( 'Order ID: #%s', 'subscription' ), $related_order->get_order_number() ); |
| 554 |
?> |
| 555 |
<a href="<?php echo esc_url( $related_order->get_edit_order_url() ); ?>" target="_blank" class="wpsubs-cell-title" title="<?php echo esc_attr( $order_id_label ); ?>">#<?php echo esc_html( $related_order->get_order_number() ); ?></a> |
| 556 |
</td> |
| 557 |
<td><?php echo esc_html( $related['label'] ); ?></td> |
| 558 |
<td><?php echo esc_html( $related_order->get_date_created()->date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ) ) ); ?></td> |
| 559 |
<td><span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( wc_get_order_status_name( $related_status ) ); ?></span></td> |
| 560 |
<td><?php echo $related_order->get_payment_method_title() ? esc_html( $related_order->get_payment_method_title() ) : '—'; ?></td> |
| 561 |
<td><?php echo wp_kses_post( $related_order->get_formatted_order_total() ); ?></td> |
| 562 |
</tr> |
| 563 |
<?php endforeach; ?> |
| 564 |
</tbody> |
| 565 |
</table> |
| 566 |
<?php |
| 567 |
// Initial pager; WPSubsPager hydrates and re-derives totals on init. |
| 568 |
$related_total = count( $related_rows ); |
| 569 |
wpsubs_render_pager( |
| 570 |
array( |
| 571 |
'current' => 1, |
| 572 |
'total' => max( 1, (int) ceil( $related_total / 10 ) ), |
| 573 |
'info' => true, |
| 574 |
'per_page' => 10, |
| 575 |
'item_count' => $related_total, |
| 576 |
'link_mode' => 'cb', |
| 577 |
'class' => 'subscrpt-pager', |
| 578 |
// translators: %1$s: first item number, %2$s: last item number, %3$s: total items |
| 579 |
'info_format' => __( 'Showing %1$s–%2$s of %3$s related orders', 'subscription' ), |
| 580 |
'context' => 'subscription-details-related-orders', |
| 581 |
) |
| 582 |
); |
| 583 |
?> |
| 584 |
<?php else : ?> |
| 585 |
<div class="subscrpt-card__body"><p class="subscrpt-muted"><?php esc_html_e( 'No related orders found.', 'subscription' ); ?></p></div> |
| 586 |
<?php endif; ?> |
| 587 |
</div> |
| 588 |
|
| 589 |
<!-- Activities card --> |
| 590 |
<?php $subscrpt_pro_on = function_exists( 'subscrpt_pro_activated' ) && subscrpt_pro_activated(); ?> |
| 591 |
<div class="subscrpt-card" data-subscrpt-paginate data-per-page="10" data-date-col="2"> |
| 592 |
<div class="subscrpt-card__head subscrpt-card__head--toolbar"> |
| 593 |
<span class="subscrpt-card__title"><?php esc_html_e( 'Subscription Activities', 'subscription' ); ?></span> |
| 594 |
<?php if ( $subscrpt_pro_on ) : ?> |
| 595 |
<?php $subscrpt_render_table_tools(); ?> |
| 596 |
<?php endif; ?> |
| 597 |
</div> |
| 598 |
<?php if ( $subscrpt_pro_on ) : ?> |
| 599 |
<div class="subscrpt-activities"> |
| 600 |
<?php |
| 601 |
/** |
| 602 |
* Fires inside the subscription details activities card. |
| 603 |
* |
| 604 |
* The Pro plugin renders the activity table here. |
| 605 |
* |
| 606 |
* @param int $subscription_id Subscription post ID. |
| 607 |
*/ |
| 608 |
do_action( 'subscrpt_order_activities', $subscription_id ); |
| 609 |
?> |
| 610 |
</div> |
| 611 |
<?php |
| 612 |
// Activities pager. Total pages are unknown at render time (Pro |
| 613 |
// injects the rows); WPSubsPager recomputes on init via render(). |
| 614 |
wpsubs_render_pager( |
| 615 |
array( |
| 616 |
'current' => 1, |
| 617 |
'total' => 1, |
| 618 |
'info' => true, |
| 619 |
'per_page' => 10, |
| 620 |
'item_count' => 0, |
| 621 |
'link_mode' => 'cb', |
| 622 |
'class' => 'subscrpt-pager', |
| 623 |
// translators: %1$s: first item number, %2$s: last item number, %3$s: total items |
| 624 |
'info_format' => __( 'Showing %1$s–%2$s of %3$s activities', 'subscription' ), |
| 625 |
'context' => 'subscription-details-activities', |
| 626 |
) |
| 627 |
); |
| 628 |
?> |
| 629 |
<?php else : ?> |
| 630 |
<div class="subscrpt-card__body"> |
| 631 |
<div class="subscrpt-upgrade-banner"> |
| 632 |
<div> |
| 633 |
<strong><?php esc_html_e( 'Upgrade to WPSubscription Pro', 'subscription' ); ?></strong> |
| 634 |
<p><?php esc_html_e( 'Track subscription activity history, automation, and more.', 'subscription' ); ?></p> |
| 635 |
</div> |
| 636 |
<a href="https://wpsubscription.co/?utm_source=plugin&utm_medium=admin&utm_campaign=upgrade_pro" target="_blank" class="wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm" rel="noreferrer noopener"> |
| 637 |
<?php esc_html_e( 'Upgrade to Pro', 'subscription' ); ?> |
| 638 |
</a> |
| 639 |
</div> |
| 640 |
</div> |
| 641 |
<?php endif; ?> |
| 642 |
</div> |
| 643 |
|
| 644 |
<?php |
| 645 |
/** |
| 646 |
* Fires at the bottom of the details main (left) column, after the activities card. |
| 647 |
* |
| 648 |
* Echo a `.subscrpt-card` to add a full-width section here. |
| 649 |
* |
| 650 |
* @param array $subscrpt_details_ctx Subscription details context. |
| 651 |
*/ |
| 652 |
do_action( 'subscrpt_details_main_bottom', $subscrpt_details_ctx ); |
| 653 |
?> |
| 654 |
|
| 655 |
</div><!-- /.subscrpt-detail-main --> |
| 656 |
|
| 657 |
<!-- Sidebar column --> |
| 658 |
<div class="subscrpt-detail-side"> |
| 659 |
|
| 660 |
<?php |
| 661 |
/** |
| 662 |
* Fires at the top of the details sidebar (right) column, before the action card. |
| 663 |
* |
| 664 |
* Echo a `.subscrpt-card` to add a section here. |
| 665 |
* |
| 666 |
* @param array $subscrpt_details_ctx Subscription details context. |
| 667 |
*/ |
| 668 |
do_action( 'subscrpt_details_side_top', $subscrpt_details_ctx ); |
| 669 |
?> |
| 670 |
|
| 671 |
<!-- Status action card --> |
| 672 |
<div class="subscrpt-card subscrpt-card--overflow"> |
| 673 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Subscription Action', 'subscription' ); ?></div> |
| 674 |
<div class="subscrpt-card__body"> |
| 675 |
<?php if ( ! empty( $actions ) ) : ?> |
| 676 |
<form method="post" action="<?php echo esc_url( $form_action ); ?>" class="subscrpt-action-form"> |
| 677 |
<?php wp_nonce_field( 'subscrpt_order_action_nonce', 'subscrpt_order_action_nonce_field' ); ?> |
| 678 |
<?php |
| 679 |
$action_options = array(); |
| 680 |
foreach ( $actions as $action_slug ) { |
| 681 |
if ( isset( $actions_data[ $action_slug ] ) ) { |
| 682 |
$action_options[] = array( |
| 683 |
'value' => $actions_data[ $action_slug ]['value'], |
| 684 |
'label' => $actions_data[ $action_slug ]['label'], |
| 685 |
); |
| 686 |
} |
| 687 |
} |
| 688 |
wpsubs_render_adv_select( |
| 689 |
array( |
| 690 |
'name' => 'subscrpt_order_action', |
| 691 |
'placeholder' => __( 'Choose Action', 'subscription' ), |
| 692 |
'value' => '', |
| 693 |
'options' => $action_options, |
| 694 |
'class' => 'subscrpt-action-select', |
| 695 |
) |
| 696 |
); |
| 697 |
?> |
| 698 |
<button type="submit" class="wpsubs-btn wpsubs-btn--primary" style="width:100%;justify-content:center;margin-top:10px;"> |
| 699 |
<?php esc_html_e( 'Process', 'subscription' ); ?> |
| 700 |
</button> |
| 701 |
</form> |
| 702 |
<?php else : ?> |
| 703 |
<p class="subscrpt-muted"><?php esc_html_e( 'No actions available for this status.', 'subscription' ); ?></p> |
| 704 |
<?php endif; ?> |
| 705 |
</div> |
| 706 |
</div> |
| 707 |
|
| 708 |
<!-- Customer card --> |
| 709 |
<div class="subscrpt-card"> |
| 710 |
<div class="subscrpt-card__head"><?php esc_html_e( 'Customer Details', 'subscription' ); ?></div> |
| 711 |
<div class="subscrpt-card__body"> |
| 712 |
<?php if ( $order ) : ?> |
| 713 |
<div class="subscrpt-customer-name"> |
| 714 |
<?php if ( $customer_url ) : ?> |
| 715 |
<a href="<?php echo esc_url( $customer_url ); ?>" target="_blank"><?php echo esc_html( $customer_name ); ?></a> |
| 716 |
<?php else : ?> |
| 717 |
<?php echo esc_html( $customer_name ); ?> |
| 718 |
<?php endif; ?> |
| 719 |
</div> |
| 720 |
<?php if ( $customer_email ) : ?> |
| 721 |
<div class="subscrpt-customer-line"><a href="mailto:<?php echo esc_attr( $customer_email ); ?>"><?php echo esc_html( $customer_email ); ?></a></div> |
| 722 |
<?php endif; ?> |
| 723 |
<?php if ( $customer_phone ) : ?> |
| 724 |
<div class="subscrpt-customer-line"><a href="tel:<?php echo esc_attr( $customer_phone ); ?>"><?php echo esc_html( $customer_phone ); ?></a></div> |
| 725 |
<?php endif; ?> |
| 726 |
|
| 727 |
<?php else : ?> |
| 728 |
<p class="subscrpt-muted"><?php esc_html_e( 'Order not found.', 'subscription' ); ?></p> |
| 729 |
<?php endif; ?> |
| 730 |
</div> |
| 731 |
</div> |
| 732 |
|
| 733 |
<!-- Secondary info cards (payment method, addresses) --> |
| 734 |
<?php foreach ( $side_sections as $section ) : ?> |
| 735 |
<div class="subscrpt-card"> |
| 736 |
<div class="subscrpt-card__head"><?php echo esc_html( $section['title'] ); ?></div> |
| 737 |
<div class="subscrpt-card__body"> |
| 738 |
<table class="subscrpt-kv"> |
| 739 |
<tbody> |
| 740 |
<?php foreach ( $section['rows'] as $row ) : ?> |
| 741 |
<tr> |
| 742 |
<th><?php echo esc_html( $row['label'] ); ?></th> |
| 743 |
<td><?php echo wp_kses_post( $row['value'] ); ?></td> |
| 744 |
</tr> |
| 745 |
<?php endforeach; ?> |
| 746 |
</tbody> |
| 747 |
</table> |
| 748 |
</div> |
| 749 |
</div> |
| 750 |
<?php endforeach; ?> |
| 751 |
|
| 752 |
<?php |
| 753 |
/** |
| 754 |
* Fires at the bottom of the details sidebar (right) column, after the secondary info cards. |
| 755 |
* |
| 756 |
* Echo a `.subscrpt-card` to add a section here. |
| 757 |
* |
| 758 |
* @param array $subscrpt_details_ctx Subscription details context. |
| 759 |
*/ |
| 760 |
do_action( 'subscrpt_details_side_bottom', $subscrpt_details_ctx ); |
| 761 |
?> |
| 762 |
|
| 763 |
</div><!-- /.subscrpt-detail-side --> |
| 764 |
|
| 765 |
</div><!-- /.subscrpt-detail-grid --> |
| 766 |
|
| 767 |
</div> |
| 768 |
|
| 769 |
<style> |
| 770 |
/* Page sits on WP admin gray; the cards are the white surfaces (matches list/health pages). |
| 771 |
No `padding: 0` here — that overrode `.list-page`'s own `0 24px` and ran the |
| 772 |
whole page flush into the admin menu and the right edge of the window. */ |
| 773 |
.wp-subscription-admin-content.list-page.subscrpt-subs-details { |
| 774 |
background: transparent; |
| 775 |
border-radius: 0; |
| 776 |
margin-top: 24px; |
| 777 |
margin-bottom: 32px; |
| 778 |
} |
| 779 |
.subscrpt-subs-details .subscrpt-detail-head { margin-bottom: 20px; } |
| 780 |
.subscrpt-detail-head__title { |
| 781 |
font-size: 1.375rem; |
| 782 |
font-weight: 700; |
| 783 |
color: var(--wpsubs-text); |
| 784 |
line-height: 1.2; |
| 785 |
margin: 0 0 8px; |
| 786 |
padding: 0; |
| 787 |
} |
| 788 |
.subscrpt-detail-head__desc { |
| 789 |
display: flex; |
| 790 |
align-items: center; |
| 791 |
flex-wrap: wrap; |
| 792 |
gap: 8px; |
| 793 |
font-size: 13px; |
| 794 |
color: var(--wpsubs-text-muted); |
| 795 |
margin: 0 0 12px; |
| 796 |
line-height: 1.5; |
| 797 |
} |
| 798 |
.subscrpt-detail-head__sep { color: var(--wpsubs-text-subtle); } |
| 799 |
.subscrpt-detail-head__divider { border-top: 1px dashed #d0d3d7; } |
| 800 |
|
| 801 |
/* Summary metric strip */ |
| 802 |
.subscrpt-summary { |
| 803 |
display: grid; |
| 804 |
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); |
| 805 |
gap: 20px; |
| 806 |
} |
| 807 |
.subscrpt-summary__tile { |
| 808 |
background: var(--wpsubs-surface); |
| 809 |
border: 1px solid var(--wpsubs-border); |
| 810 |
border-radius: var(--wpsubs-radius); |
| 811 |
padding: 16px 18px; |
| 812 |
display: flex; |
| 813 |
flex-direction: column; |
| 814 |
gap: 6px; |
| 815 |
} |
| 816 |
.subscrpt-summary__label { |
| 817 |
font-size: 11px; |
| 818 |
font-weight: 600; |
| 819 |
text-transform: uppercase; |
| 820 |
letter-spacing: 0.06em; |
| 821 |
color: var(--wpsubs-text-muted); |
| 822 |
} |
| 823 |
.subscrpt-summary__value { |
| 824 |
font-size: 18px; |
| 825 |
font-weight: 700; |
| 826 |
color: #111827; |
| 827 |
line-height: 1.3; |
| 828 |
} |
| 829 |
.subscrpt-summary__value .subscrpt-summary__unit { |
| 830 |
font-size: 13px; |
| 831 |
font-weight: 500; |
| 832 |
color: var(--wpsubs-text-muted); |
| 833 |
} |
| 834 |
.subscrpt-summary__value .subscrpt-summary__was { |
| 835 |
font-size: 14px; |
| 836 |
font-weight: 500; |
| 837 |
color: var(--wpsubs-text-muted); |
| 838 |
margin-right: 4px; |
| 839 |
} |
| 840 |
|
| 841 |
.subscrpt-detail-grid { |
| 842 |
display: grid; |
| 843 |
grid-template-columns: minmax(0, 1fr) 340px; |
| 844 |
gap: 20px; |
| 845 |
align-items: start; |
| 846 |
} |
| 847 |
@media (max-width: 960px) { |
| 848 |
.subscrpt-detail-grid { grid-template-columns: 1fr; } |
| 849 |
} |
| 850 |
.subscrpt-detail-main, .subscrpt-detail-side { |
| 851 |
display: flex; |
| 852 |
flex-direction: column; |
| 853 |
gap: 20px; |
| 854 |
min-width: 0; |
| 855 |
} |
| 856 |
|
| 857 |
.subscrpt-card { |
| 858 |
background: var(--wpsubs-surface); |
| 859 |
border: 1px solid var(--wpsubs-border); |
| 860 |
border-radius: var(--wpsubs-radius); |
| 861 |
box-shadow: none; |
| 862 |
overflow: hidden; |
| 863 |
} |
| 864 |
.subscrpt-card__head { |
| 865 |
padding: 14px 18px; |
| 866 |
font-size: 14px; |
| 867 |
font-weight: 600; |
| 868 |
color: #111827; |
| 869 |
border-bottom: 1px solid var(--wpsubs-border); |
| 870 |
background: var(--wpsubs-surface); |
| 871 |
} |
| 872 |
.subscrpt-card__body { padding: 16px 18px; } |
| 873 |
/* Cards holding an adv-select dropdown must not clip the popover. Without |
| 874 |
overflow:hidden the head no longer inherits the card's rounded corners, so |
| 875 |
round the head (and body) explicitly. */ |
| 876 |
.subscrpt-card--overflow { overflow: visible; } |
| 877 |
.subscrpt-card--overflow .subscrpt-card__head { |
| 878 |
border-top-left-radius: var(--wpsubs-radius); |
| 879 |
border-top-right-radius: var(--wpsubs-radius); |
| 880 |
} |
| 881 |
.subscrpt-card--overflow .subscrpt-card__body:last-child { |
| 882 |
border-bottom-left-radius: var(--wpsubs-radius); |
| 883 |
border-bottom-right-radius: var(--wpsubs-radius); |
| 884 |
} |
| 885 |
|
| 886 |
/* Card head with right-aligned filter tools. */ |
| 887 |
.subscrpt-card__head--toolbar { |
| 888 |
display: flex; |
| 889 |
align-items: center; |
| 890 |
justify-content: space-between; |
| 891 |
gap: 12px; |
| 892 |
flex-wrap: wrap; |
| 893 |
font-weight: 400; |
| 894 |
} |
| 895 |
.subscrpt-card__title { font-size: 14px; font-weight: 600; color: #111827; } |
| 896 |
/* Filter dropdown items must not inherit the card-head bold weight. */ |
| 897 |
.subscrpt-card__tools .wpsubs-adv-select__label, |
| 898 |
.subscrpt-card__tools .wpsubs-adv-select__item { font-weight: 400; } |
| 899 |
.subscrpt-card__tools { |
| 900 |
display: flex; |
| 901 |
align-items: center; |
| 902 |
gap: 8px; |
| 903 |
flex-wrap: wrap; |
| 904 |
} |
| 905 |
.subscrpt-card__tools .wpsubs-adv-select__trigger { |
| 906 |
height: 32px; |
| 907 |
padding-top: 0; |
| 908 |
padding-bottom: 0; |
| 909 |
} |
| 910 |
|
| 911 |
/* Pager (uses .wpsubs-pagination layout; styles in admin-components.css). */ |
| 912 |
.subscrpt-pager { margin: 0; } |
| 913 |
.subscrpt-empty-row td { color: var(--wpsubs-text-muted); font-size: 13px; } |
| 914 |
|
| 915 |
/* Tables render flush to the card edges (the table thead is the column strip). */ |
| 916 |
.subscrpt-card .wpsubs-table { border: 0; } |
| 917 |
.subscrpt-card .wpsubs-table tbody tr:last-child td { border-bottom: 0; } |
| 918 |
/* Match the page's 13px body text (default wpsubs-table cells run larger). */ |
| 919 |
.subscrpt-card .wpsubs-table th, |
| 920 |
.subscrpt-card .wpsubs-table td, |
| 921 |
.subscrpt-activities .wpsubs-table th, |
| 922 |
.subscrpt-activities .wpsubs-table td { font-size: 13px; } |
| 923 |
/* Tighter rows for the data tables. */ |
| 924 |
.subscrpt-table--compact th, |
| 925 |
.subscrpt-table--compact td, |
| 926 |
.subscrpt-activities .wpsubs-table th, |
| 927 |
.subscrpt-activities .wpsubs-table td { padding-top: 12px; padding-bottom: 12px; } |
| 928 |
.subscrpt-table--compact .wpsubs-badge { font-size: 11px; } |
| 929 |
.subscrpt-activities .widefat, |
| 930 |
.subscrpt-activities .wpsubs-table { border: 0; margin: 0; } |
| 931 |
.subscrpt-card__actions { |
| 932 |
display: flex; |
| 933 |
flex-wrap: wrap; |
| 934 |
gap: 8px; |
| 935 |
margin-top: 14px; |
| 936 |
} |
| 937 |
|
| 938 |
.subscrpt-kv { width: 100%; border-collapse: collapse; } |
| 939 |
.subscrpt-kv th, .subscrpt-kv td { |
| 940 |
text-align: left; |
| 941 |
padding: 9px 0; |
| 942 |
font-size: 13px; |
| 943 |
vertical-align: top; |
| 944 |
border-bottom: 1px solid var(--wpsubs-border); |
| 945 |
} |
| 946 |
.subscrpt-kv tr:last-child th, .subscrpt-kv tr:last-child td { border-bottom: 0; } |
| 947 |
.subscrpt-kv th { |
| 948 |
width: 40%; |
| 949 |
font-weight: 500; |
| 950 |
color: var(--wpsubs-text-muted); |
| 951 |
padding-right: 12px; |
| 952 |
} |
| 953 |
.subscrpt-kv td { color: var(--wpsubs-text); } |
| 954 |
.subscrpt-kv a { color: var(--wpsubs-brand); text-decoration: none; } |
| 955 |
.subscrpt-kv a:hover { text-decoration: underline; } |
| 956 |
|
| 957 |
/* Plan + Payment Method row: Plan wider (2-col content), Payment narrower. */ |
| 958 |
.subscrpt-plan-pay { |
| 959 |
display: grid; |
| 960 |
grid-template-columns: repeat(3, minmax(0, 1fr)); |
| 961 |
gap: 20px; |
| 962 |
align-items: stretch; |
| 963 |
} |
| 964 |
.subscrpt-plan-pay > .subscrpt-card { margin: 0; } |
| 965 |
/* Plan spans 2 of the 3 columns so its right edge aligns with the summary's |
| 966 |
2nd/3rd column boundary; the gateway card fills the remaining column. */ |
| 967 |
.subscrpt-plan-pay > .subscrpt-card--plan { grid-column: span 2; } |
| 968 |
@media (max-width: 782px) { |
| 969 |
.subscrpt-plan-pay { grid-template-columns: 1fr; } |
| 970 |
} |
| 971 |
|
| 972 |
/* Plan card: product image (1:1) + name/qty stacked. */ |
| 973 |
.subscrpt-plan-row { |
| 974 |
display: grid; |
| 975 |
grid-template-columns: 64px 1fr; |
| 976 |
gap: 16px; |
| 977 |
align-items: center; |
| 978 |
} |
| 979 |
.subscrpt-plan-thumb { |
| 980 |
width: 64px; |
| 981 |
height: 64px; |
| 982 |
border: 1px solid var(--wpsubs-border); |
| 983 |
border-radius: var(--wpsubs-radius-sm); |
| 984 |
overflow: hidden; |
| 985 |
background: var(--wpsubs-surface-alt, #f6f7f7); |
| 986 |
display: flex; |
| 987 |
align-items: center; |
| 988 |
justify-content: center; |
| 989 |
} |
| 990 |
.subscrpt-plan-thumb img, |
| 991 |
.subscrpt-plan-thumb .subscrpt-plan-img { |
| 992 |
width: 100%; |
| 993 |
height: 100%; |
| 994 |
object-fit: cover; |
| 995 |
display: block; |
| 996 |
} |
| 997 |
.subscrpt-plan-thumb__ph { |
| 998 |
font-size: 28px; |
| 999 |
width: 28px; |
| 1000 |
height: 28px; |
| 1001 |
color: var(--wpsubs-text-subtle, #a7aaad); |
| 1002 |
} |
| 1003 |
.subscrpt-plan-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; } |
| 1004 |
.subscrpt-plan-name { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); line-height: 1.3; } |
| 1005 |
.subscrpt-plan-name a { color: var(--wpsubs-text); text-decoration: none; } |
| 1006 |
.subscrpt-plan-name a:hover { color: var(--wpsubs-brand); } |
| 1007 |
.subscrpt-plan-qty { font-size: 13px; color: var(--wpsubs-text-muted); } |
| 1008 |
.subscrpt-plan-term { font-size: 13px; color: var(--wpsubs-text-muted); } |
| 1009 |
.subscrpt-plan-col { display: flex; flex-direction: column; gap: 4px; min-width: 0; } |
| 1010 |
.subscrpt-plan-label { |
| 1011 |
font-size: 11px; |
| 1012 |
font-weight: 600; |
| 1013 |
text-transform: uppercase; |
| 1014 |
letter-spacing: 0.05em; |
| 1015 |
color: var(--wpsubs-text-muted); |
| 1016 |
} |
| 1017 |
.subscrpt-plan-value { font-size: 13px; color: var(--wpsubs-text); } |
| 1018 |
.subscrpt-plan-value a { color: var(--wpsubs-brand); text-decoration: none; } |
| 1019 |
.subscrpt-plan-value a:hover { text-decoration: underline; } |
| 1020 |
.subscrpt-plan-extra { margin-top: 14px; } |
| 1021 |
|
| 1022 |
/* Payment method: gateway icon + (gateway title / source plugin). Mirrors the |
| 1023 |
plan-row layout so the two cards line up. */ |
| 1024 |
.subscrpt-payment { display: grid; grid-template-columns: 40px 1fr; gap: 12px; align-items: center; } |
| 1025 |
.subscrpt-payment__icon { |
| 1026 |
width: 40px; |
| 1027 |
height: 40px; |
| 1028 |
display: flex; |
| 1029 |
align-items: center; |
| 1030 |
justify-content: center; |
| 1031 |
border: 1px solid var(--wpsubs-border); |
| 1032 |
border-radius: var(--wpsubs-radius-sm); |
| 1033 |
background: var(--wpsubs-surface-alt, #f6f7f7); |
| 1034 |
overflow: hidden; |
| 1035 |
} |
| 1036 |
.subscrpt-payment__icon img { width: 100%; height: 100%; object-fit: cover; display: block; } |
| 1037 |
.subscrpt-payment__icon-ph { font-size: 22px; width: 22px; height: 22px; color: var(--wpsubs-text-muted); } |
| 1038 |
.subscrpt-payment__meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; } |
| 1039 |
.subscrpt-payment__title { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); line-height: 1.3; } |
| 1040 |
.subscrpt-payment__plugin { font-size: 13px; color: var(--wpsubs-text-muted); } |
| 1041 |
|
| 1042 |
/* Plan + Payment cards stretch to equal height; bodies center their content. */ |
| 1043 |
.subscrpt-plan-pay > .subscrpt-card { display: flex; flex-direction: column; } |
| 1044 |
.subscrpt-plan-pay > .subscrpt-card .subscrpt-card__body { flex: 1; display: flex; flex-direction: column; justify-content: center; } |
| 1045 |
|
| 1046 |
.subscrpt-customer-name { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); } |
| 1047 |
.subscrpt-customer-name a { color: var(--wpsubs-text); text-decoration: none; } |
| 1048 |
.subscrpt-customer-name a:hover { color: var(--wpsubs-brand); } |
| 1049 |
.subscrpt-customer-line { font-size: 13px; margin-top: 3px; } |
| 1050 |
.subscrpt-customer-line a { color: var(--wpsubs-brand); text-decoration: none; } |
| 1051 |
.subscrpt-customer-line a:hover { text-decoration: underline; } |
| 1052 |
|
| 1053 |
.subscrpt-subs-details .subscrpt-muted { color: var(--wpsubs-text-muted); font-size: 13px; margin: 0; } |
| 1054 |
.subscrpt-action-form .wpsubs-adv-select { width: 100%; } |
| 1055 |
.subscrpt-action-form .wpsubs-adv-select__trigger { width: 100%; } |
| 1056 |
|
| 1057 |
.subscrpt-upgrade-banner { |
| 1058 |
display: flex; |
| 1059 |
align-items: center; |
| 1060 |
justify-content: space-between; |
| 1061 |
gap: 16px; |
| 1062 |
flex-wrap: wrap; |
| 1063 |
padding: 16px; |
| 1064 |
border: 1px solid var(--wpsubs-brand); |
| 1065 |
border-radius: var(--wpsubs-radius-sm); |
| 1066 |
background: var(--wpsubs-brand-light); |
| 1067 |
} |
| 1068 |
.subscrpt-upgrade-banner strong { color: var(--wpsubs-text); font-size: 14px; } |
| 1069 |
.subscrpt-upgrade-banner p { margin: 4px 0 0; font-size: 13px; color: var(--wpsubs-text-muted); } |
| 1070 |
</style> |
| 1071 |
|