PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.0
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
← All changes | includes/Admin/views/integrations.php +409 -183 1.10.42.0.0 View file →
@@ -11,10 +11,8 @@
11 11
12 12 // Exit if accessed directly.
13 13 defined( 'ABSPATH' ) || exit;
14 14
15 -wp_enqueue_style( 'wp-subscription-admin-components', SUBSCRPT_ASSETS . '/css/admin-components.css', [], SUBSCRPT_VERSION );
16 -
17 15 // Split by type.
18 16 $payment_gateways = array_filter(
19 17 $integrations,
20 18 function ( $i ) {
@@ -73,218 +71,446 @@
73 71 $cat_key = isset( $category_config[ $cat_key ] ) ? $cat_key : 'other';
74 72 $third_party_grouped[ $cat_key ][] = $integration;
75 73 }
76 74 $third_party_grouped = array_filter( $third_party_grouped );
75 +
76 +/**
77 + * Facet counts for the filter bar.
78 + *
79 + * Derived from the same array the cards render from, so a count can never
80 + * disagree with what is on screen — including when filter_integration_actions()
81 + * has removed something, or when a future integration is added and nobody
82 + * remembers to update a hardcoded number.
83 + */
84 +$subscrpt_status_of = static function ( array $integration ): string {
85 + if ( ! empty( $integration['is_active'] ) ) {
86 + return 'active';
87 + }
88 + return ! empty( $integration['is_installed'] ) ? 'inactive' : 'not-installed';
89 +};
90 +
91 +$subscrpt_facets = [
92 + 'category' => [],
93 + 'status' => [
94 + 'active' => 0,
95 + 'inactive' => 0,
96 + 'not-installed' => 0,
97 + ],
98 + // No `recurring` facet: every integration that supports automatic
99 + // recurring is a payment gateway, so the chip selected exactly the same
100 + // six cards as the Payment Gateways category. The card badge still shows
101 + // it, where it says something about that one integration.
102 + 'tag' => [
103 + 'free' => 0,
104 + 'pro' => 0,
105 + 'beta' => 0,
106 + ],
107 +];
108 +
109 +foreach ( $integrations as $integration ) {
110 + $cat_key = ( ( $integration['type'] ?? '' ) === 'payment_gateway' )
111 + ? 'payment_gateway'
112 + : ( isset( $category_config[ $integration['category'] ?? '' ] ) ? $integration['category'] : 'other' );
113 +
114 + $subscrpt_facets['category'][ $cat_key ] = ( $subscrpt_facets['category'][ $cat_key ] ?? 0 ) + 1;
115 + ++$subscrpt_facets['status'][ $subscrpt_status_of( $integration ) ];
116 +
117 + if ( ! empty( $integration['is_pro'] ) ) {
118 + ++$subscrpt_facets['tag']['pro'];
119 + } else {
120 + ++$subscrpt_facets['tag']['free'];
121 + }
122 + if ( ! empty( $integration['is_beta'] ) ) {
123 + ++$subscrpt_facets['tag']['beta'];
124 + }
125 +}
126 +
127 +$subscrpt_total = count( $integrations );
128 +
129 +// Labels for the category chips. Payment gateways are a `type`, not a
130 +// `category`, but on this page they read as one more group.
131 +//
132 +// The short `label` rather than the full `section`: in a 232px rail
133 +// "Customer Relationship Management" wraps to two lines and the group of
134 +// chips ends up taller than the viewport. The section headings above the
135 +// cards still spell it out in full.
136 +$subscrpt_category_labels = [ 'payment_gateway' => __( 'Payment Gateways', 'subscription' ) ];
137 +foreach ( $category_config as $key => $cfg ) {
138 + $subscrpt_category_labels[ $key ] = $cfg['label'] ?? $cfg['section'];
139 +}
140 +$subscrpt_category_labels['other'] = __( 'Other', 'subscription' );
141 +
142 +$subscrpt_status_labels = [
143 + 'active' => __( 'Active', 'subscription' ),
144 + 'inactive' => __( 'Inactive', 'subscription' ),
145 + 'not-installed' => __( 'Not installed', 'subscription' ),
146 +];
147 +
148 +$subscrpt_tag_labels = [
149 + 'free' => __( 'Free', 'subscription' ),
150 + 'pro' => __( 'Pro', 'subscription' ),
151 + 'beta' => __( 'Beta', 'subscription' ),
152 +];
77 153 ?>
78 154
79 -<div class="wp-subscription-admin-content list-page subscrpt-subs-list">
155 +<div class="wp-subscription-admin-content list-page">
80 156
81 - <!-- Page header -->
82 - <div style="margin-bottom:20px;">
83 - <h1 style="font-size:1.375rem;font-weight:700;color:var(--wpsubs-text);margin:0 0 6px;line-height:1.2;"><?php esc_html_e( 'Integrations', 'subscription' ); ?></h1>
84 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0 0 12px;line-height:1.5;"><?php esc_html_e( 'Connect your subscriptions with payment gateways and third-party plugins.', 'subscription' ); ?></p>
85 - <div style="border-top:1px dashed #d0d3d7;"></div>
86 - </div>
157 + <?php
158 + wpsubs_render_page_header(
159 + array(
160 + 'title' => __( 'Integrations', 'subscription' ),
161 + 'description' => __( 'Connect your subscriptions with payment gateways and third-party plugins.', 'subscription' ),
162 + )
163 + );
164 + ?>
87 165
88 - <?php if ( ! empty( $_GET['subscrpt_installed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
89 - <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"><p><?php esc_html_e( 'Plugin installed and activated successfully.', 'subscription' ); ?></p></div>
90 - <?php endif; ?>
166 + <?php
167 + /**
168 + * Filter bar.
169 + *
170 + * Filtering is done in the browser against cards that are all already in the
171 + * DOM — there are fifteen of them, so a round trip per keystroke would be
172 + * slower and would lose the page's scroll position for nothing.
173 + *
174 + * Every control degrades to "everything visible" without JavaScript, which
175 + * is the state the page is in today.
176 + */
177 + ?>
178 + <?php
179 + /*
180 + * Two columns: filters in a sticky rail, results beside them. There are
181 + * three facet groups and a dozen chips — as a horizontal bar above the
182 + * grid that pushed the first card most of the way down the viewport,
183 + * and scrolled out of reach exactly when a long list needed narrowing.
184 + */
185 + ?>
186 + <div class="subscrpt-int-layout">
91 187
92 - <!-- Payment Gateways -->
93 - <div style="margin-bottom:32px;">
94 - <div style="margin-bottom:12px;">
95 - <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
96 - </div>
97 - <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
98 - <?php foreach ( $payment_gateways as $integration ) : ?>
99 - <?php
100 - $is_installed = ! empty( $integration['is_installed'] );
101 - $is_active = ! empty( $integration['is_active'] );
102 - $is_beta = ! empty( $integration['is_beta'] );
103 - $is_pro = ! empty( $integration['is_pro'] );
104 - $icon_url = $integration['icon_url'] ?? '';
105 - $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
106 - $icon_color = $integration['icon_color'] ?? '#64748b';
188 + <aside class="subscrpt-int-sidebar">
189 + <div class="subscrpt-int-filters" data-subscrpt-integration-filters hidden>
107 190
108 - if ( $is_active ) {
109 - $status_dot = '#16a34a';
110 - $status_text = __( 'Active', 'subscription' );
111 - } elseif ( $is_installed ) {
112 - $status_dot = '#d97706';
113 - $status_text = __( 'Inactive', 'subscription' );
114 - } else {
115 - $status_dot = '#9ca3af';
116 - $status_text = __( 'Not Installed', 'subscription' );
191 + <?php
192 + $subscrpt_chip_groups = [
193 + [
194 + 'facet' => 'category',
195 + 'legend' => __( 'Category', 'subscription' ),
196 + 'counts' => $subscrpt_facets['category'],
197 + 'labels' => $subscrpt_category_labels,
198 + ],
199 + [
200 + 'facet' => 'status',
201 + 'legend' => __( 'Status', 'subscription' ),
202 + 'counts' => $subscrpt_facets['status'],
203 + 'labels' => $subscrpt_status_labels,
204 + ],
205 + [
206 + 'facet' => 'tag',
207 + 'legend' => __( 'Tags', 'subscription' ),
208 + 'counts' => $subscrpt_facets['tag'],
209 + 'labels' => $subscrpt_tag_labels,
210 + ],
211 + ];
212 +
213 + foreach ( $subscrpt_chip_groups as $subscrpt_group ) :
214 + // A facet nothing has is noise, not information.
215 + $subscrpt_shown = array_filter( $subscrpt_group['counts'] );
216 + if ( empty( $subscrpt_shown ) ) {
217 + continue;
117 218 }
219 + $subscrpt_is_single = in_array( $subscrpt_group['facet'], array( 'category', 'status' ), true );
118 220 ?>
119 - <div class="wpsubs-table-card" style="padding:16px;display:flex;flex-direction:column;gap:12px;">
221 + <nav class="wpsubs-vnav" aria-label="<?php echo esc_attr( $subscrpt_group['legend'] ); ?>">
222 + <p class="wpsubs-vnav__title"><?php echo esc_html( $subscrpt_group['legend'] ); ?></p>
223 + <?php
224 + /*
225 + * Category and status are single-select — a card has exactly
226 + * one of each, so picking two could only ever return nothing.
227 + * Both therefore need an explicit "All" to come back to.
228 + */
229 + if ( $subscrpt_is_single ) :
230 + ?>
231 + <button type="button" class="wpsubs-vnav__item is-active" data-subscrpt-int-chip data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>" data-value="">
232 + <span class="wpsubs-vnav__label"><?php esc_html_e( 'All', 'subscription' ); ?></span>
233 + <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_total ) ); ?></span>
234 + </button>
235 + <?php
236 + endif;
237 + ?>
238 + <?php foreach ( $subscrpt_shown as $subscrpt_key => $subscrpt_count ) : ?>
239 + <button
240 + type="button"
241 + class="wpsubs-vnav__item"
242 + data-subscrpt-int-chip
243 + data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>"
244 + data-value="<?php echo esc_attr( $subscrpt_key ); ?>"
245 + aria-pressed="false"
246 + >
247 + <span class="wpsubs-vnav__label"><?php echo esc_html( $subscrpt_group['labels'][ $subscrpt_key ] ?? $subscrpt_key ); ?></span>
248 + <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_count ) ); ?></span>
249 + </button>
250 + <?php endforeach; ?>
251 + </nav>
252 + <?php
253 + endforeach;
254 + ?>
255 + <?php // Last in the rail: it only appears once a filter is on, and it undoes what is above it. ?>
256 + <button type="button" class="wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm" data-subscrpt-int-reset hidden>
257 + <?php esc_html_e( 'Reset', 'subscription' ); ?>
258 + </button>
259 + </div>
260 + </aside>
120 261
121 - <!-- Header: icon + name + status -->
122 - <div style="display:flex;gap:10px;align-items:flex-start;">
123 - <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
124 - <?php if ( $icon_url ) : ?>
125 - <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
126 - <?php else : ?>
127 - <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
128 - <?php endif; ?>
129 - </div>
130 - <div style="flex:1;min-width:0;">
131 - <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
132 - <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
133 - <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
134 - <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
135 - <?php echo esc_html( $status_text ); ?>
136 - </span>
137 - </div>
138 - <div style="display:flex;flex-wrap:wrap;gap:4px;">
139 - <?php if ( $is_pro ) : ?>
140 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:#f5f3ff;color:#7c3aed;line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
141 - <?php endif; ?>
142 - <?php if ( $is_beta ) : ?>
143 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff7ed;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Beta', 'subscription' ); ?></span>
144 - <?php endif; ?>
145 - <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
146 - <span style="display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#dcfce7;color:#166534;line-height:1.6;">
147 - <svg width="10" height="10" viewBox="0 0 24 24" fill="none"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#166534"/></svg>
148 - <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
149 - </span>
150 - <?php else : ?>
151 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff3e0;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Manual renewals only', 'subscription' ); ?></span>
152 - <?php endif; ?>
153 - </div>
154 - </div>
155 - </div>
262 + <div class="subscrpt-int-results">
263 + <p class="subscrpt-int-empty" data-subscrpt-int-empty hidden>
264 + <?php esc_html_e( 'No integrations match these filters.', 'subscription' ); ?>
265 + </p>
156 266
157 - <!-- Description -->
158 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
267 + <?php if ( ! empty( $_GET['subscrpt_installed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
268 + <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"><p><?php esc_html_e( 'Plugin installed and activated successfully.', 'subscription' ); ?></p></div>
269 + <?php endif; ?>
159 270
160 - <!-- Separator -->
161 - <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
271 + <!-- Payment Gateways -->
272 + <div style="margin-bottom:32px;" data-subscrpt-int-section>
273 + <div style="margin-bottom:12px;">
274 + <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
275 + </div>
276 + <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
277 + <?php foreach ( $payment_gateways as $integration ) : ?>
278 + <?php
279 + $is_installed = ! empty( $integration['is_installed'] );
280 + $is_active = ! empty( $integration['is_active'] );
281 + $is_beta = ! empty( $integration['is_beta'] );
282 + $is_pro = ! empty( $integration['is_pro'] );
283 + $icon_url = $integration['icon_url'] ?? '';
284 + $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
285 + $icon_color = $integration['icon_color'] ?? '#64748b';
162 286
163 - <!-- Actions -->
164 - <div style="display:flex;gap:6px;flex-wrap:wrap;">
165 - <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
166 - <div style="width:100%;display:flex;align-items:center;gap:6px;background:#f5f3ff;border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:#7c3aed;line-height:1.4;">
167 - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
168 - <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
169 - </div>
170 - <?php else : ?>
171 - <?php
172 - foreach ( $integration['actions'] as $action ) :
173 - $is_primary = ( 'function' === $action['type'] );
174 - $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
175 - ?>
176 - <?php if ( 'link' === $action['type'] ) : ?>
177 - <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
178 - <?php elseif ( 'external_link' === $action['type'] ) : ?>
179 - <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
180 - <?php echo esc_html( $action['label'] ); ?>
181 - <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
182 - </a>
183 - <?php elseif ( 'function' === $action['type'] ) : ?>
184 - <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
185 - <?php endif; ?>
186 - <?php endforeach; ?>
187 - <?php endif; ?>
188 - </div>
287 + if ( $is_active ) {
288 + $status_dot = '#16a34a';
289 + $status_text = __( 'Active', 'subscription' );
290 + } elseif ( $is_installed ) {
291 + $status_dot = '#d97706';
292 + $status_text = __( 'Inactive', 'subscription' );
293 + } else {
294 + $status_dot = '#9ca3af';
295 + $status_text = __( 'Not Installed', 'subscription' );
296 + }
189 297
190 - </div>
191 - <?php endforeach; ?>
192 - </div>
193 - </div>
298 + // Filtering reads these rather than scraping the rendered markup,
299 + // which would break the moment a label is translated.
300 + $subscrpt_card_status = $subscrpt_status_of( $integration );
301 + $subscrpt_card_tags = array_keys(
302 + array_filter(
303 + [
304 + 'free' => ! $is_pro,
305 + 'pro' => $is_pro,
306 + 'beta' => $is_beta,
307 + 'recurring' => ! empty( $integration['supports_recurring'] ),
308 + ]
309 + )
310 + );
311 + ?>
312 + <div
313 + class="wpsubs-table-card subscrpt-int-card"
314 + style="padding:16px;display:flex;flex-direction:column;gap:12px;"
315 + data-subscrpt-int-card
316 + data-name="<?php echo esc_attr( $integration['title'] ); ?>"
317 + data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
318 + data-category="payment_gateway"
319 + data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
320 + data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
321 + >
194 322
195 - <!-- 3rd Party Integrations -->
196 - <?php foreach ( $third_party_grouped as $cat_key => $cat_integrations ) : ?>
197 - <div style="margin-bottom:32px;">
198 - <div style="margin-bottom:12px;">
199 - <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php echo esc_html( $category_config[ $cat_key ]['section'] ?? __( 'Other', 'subscription' ) ); ?></h2>
200 - </div>
201 - <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
202 - <?php foreach ( $cat_integrations as $integration ) : ?>
203 - <?php
204 - $is_active = ! empty( $integration['is_active'] );
205 - $is_pro = ! empty( $integration['is_pro'] );
206 - $icon_url = $integration['icon_url'] ?? '';
207 - $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
208 - $icon_color = $integration['icon_color'] ?? '#64748b';
209 - $category = $integration['category'] ?? '';
210 - $cat = $category_config[ $category ] ?? [
211 - 'label' => $category,
212 - 'bg' => '#f1f5f9',
213 - 'color' => '#475569',
214 - ];
215 -
216 - $status_dot = $is_active ? '#16a34a' : '#9ca3af';
217 - $status_text = $is_active ? __( 'Active', 'subscription' ) : __( 'Not Installed', 'subscription' );
218 - ?>
219 - <div class="wpsubs-table-card" style="padding:16px;display:flex;flex-direction:column;gap:12px;">
220 -
221 - <!-- Header: icon + name + status -->
222 - <div style="display:flex;gap:10px;align-items:flex-start;">
223 - <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
224 - <?php if ( $icon_url ) : ?>
225 - <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
226 - <?php else : ?>
227 - <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
228 - <?php endif; ?>
229 - </div>
230 - <div style="flex:1;min-width:0;">
231 - <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
232 - <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
233 - <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
234 - <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
235 - <?php echo esc_html( $status_text ); ?>
236 - </span>
323 + <!-- Header: icon + name + status -->
324 + <div style="display:flex;gap:10px;align-items:flex-start;">
325 + <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
326 + <?php if ( $icon_url ) : ?>
327 + <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
328 + <?php else : ?>
329 + <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
330 + <?php endif; ?>
237 331 </div>
238 - <?php if ( $cat['label'] || $is_pro ) : ?>
332 + <div style="flex:1;min-width:0;">
333 + <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
334 + <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
335 + <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
336 + <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
337 + <?php echo esc_html( $status_text ); ?>
338 + </span>
339 + </div>
239 340 <div style="display:flex;flex-wrap:wrap;gap:4px;">
240 341 <?php if ( $is_pro ) : ?>
241 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:#f5f3ff;color:#7c3aed;line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
342 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
242 343 <?php endif; ?>
243 - <?php if ( $cat['label'] ) : ?>
244 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:<?php echo esc_attr( $cat['bg'] ); ?>;color:<?php echo esc_attr( $cat['color'] ); ?>;line-height:1.6;"><?php echo esc_html( $cat['label'] ); ?></span>
344 + <?php if ( $is_beta ) : ?>
345 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff7ed;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Beta', 'subscription' ); ?></span>
245 346 <?php endif; ?>
347 + <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
348 + <span style="display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#dcfce7;color:#166534;line-height:1.6;">
349 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#166534"/></svg>
350 + <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
351 + </span>
352 + <?php else : ?>
353 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff3e0;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Manual renewals only', 'subscription' ); ?></span>
354 + <?php endif; ?>
246 355 </div>
356 + </div>
357 + </div>
358 +
359 + <!-- Description -->
360 + <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
361 +
362 + <!-- Separator -->
363 + <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
364 +
365 + <!-- Actions -->
366 + <div style="display:flex;gap:6px;flex-wrap:wrap;">
367 + <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
368 + <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
369 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
370 + <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
371 + </div>
372 + <?php else : ?>
373 + <?php
374 + foreach ( $integration['actions'] as $action ) :
375 + $is_primary = ( 'function' === $action['type'] );
376 + $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
377 + ?>
378 + <?php if ( 'link' === $action['type'] ) : ?>
379 + <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
380 + <?php elseif ( 'external_link' === $action['type'] ) : ?>
381 + <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
382 + <?php echo esc_html( $action['label'] ); ?>
383 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
384 + </a>
385 + <?php elseif ( 'function' === $action['type'] ) : ?>
386 + <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
387 + <?php endif; ?>
388 + <?php endforeach; ?>
247 389 <?php endif; ?>
248 390 </div>
391 +
249 392 </div>
393 + <?php endforeach; ?>
394 + </div>
395 + </div>
250 396
251 - <!-- Description -->
252 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
397 + <!-- 3rd Party Integrations -->
398 + <?php foreach ( $third_party_grouped as $cat_key => $cat_integrations ) : ?>
399 + <div style="margin-bottom:32px;" data-subscrpt-int-section>
400 + <div style="margin-bottom:12px;">
401 + <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php echo esc_html( $category_config[ $cat_key ]['section'] ?? __( 'Other', 'subscription' ) ); ?></h2>
402 + </div>
403 + <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
404 + <?php foreach ( $cat_integrations as $integration ) : ?>
405 + <?php
406 + $is_active = ! empty( $integration['is_active'] );
407 + $is_pro = ! empty( $integration['is_pro'] );
408 + $icon_url = $integration['icon_url'] ?? '';
409 + $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
410 + $icon_color = $integration['icon_color'] ?? '#64748b';
411 + $category = $integration['category'] ?? '';
412 + $cat = $category_config[ $category ] ?? [
413 + 'label' => $category,
414 + 'bg' => '#f1f5f9',
415 + 'color' => '#475569',
416 + ];
253 417
254 - <!-- Separator -->
255 - <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
418 + $status_dot = $is_active ? '#16a34a' : '#9ca3af';
419 + $status_text = $is_active ? __( 'Active', 'subscription' ) : __( 'Not Installed', 'subscription' );
256 420
257 - <!-- Actions -->
258 - <div style="display:flex;gap:6px;flex-wrap:wrap;">
259 - <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
260 - <div style="width:100%;display:flex;align-items:center;gap:6px;background:#f5f3ff;border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:#7c3aed;line-height:1.4;">
261 - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
262 - <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
421 + $subscrpt_card_status = $subscrpt_status_of( $integration );
422 + $subscrpt_card_tags = array_keys(
423 + array_filter(
424 + [
425 + 'free' => ! $is_pro,
426 + 'pro' => $is_pro,
427 + 'beta' => ! empty( $integration['is_beta'] ),
428 + 'recurring' => ! empty( $integration['supports_recurring'] ),
429 + ]
430 + )
431 + );
432 + ?>
433 + <div
434 + class="wpsubs-table-card subscrpt-int-card"
435 + style="padding:16px;display:flex;flex-direction:column;gap:12px;"
436 + data-subscrpt-int-card
437 + data-name="<?php echo esc_attr( $integration['title'] ); ?>"
438 + data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
439 + data-category="<?php echo esc_attr( isset( $category_config[ $category ] ) ? $category : 'other' ); ?>"
440 + data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
441 + data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
442 + >
443 +
444 + <!-- Header: icon + name + status -->
445 + <div style="display:flex;gap:10px;align-items:flex-start;">
446 + <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
447 + <?php if ( $icon_url ) : ?>
448 + <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
449 + <?php else : ?>
450 + <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
451 + <?php endif; ?>
263 452 </div>
264 - <?php else : ?>
265 - <?php
266 - foreach ( $integration['actions'] as $action ) :
267 - $is_primary = ( 'function' === $action['type'] );
268 - $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
269 - ?>
270 - <?php if ( 'link' === $action['type'] ) : ?>
271 - <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
272 - <?php elseif ( 'external_link' === $action['type'] ) : ?>
273 - <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
274 - <?php echo esc_html( $action['label'] ); ?>
275 - <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
276 - </a>
277 - <?php elseif ( 'function' === $action['type'] ) : ?>
278 - <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
453 + <div style="flex:1;min-width:0;">
454 + <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
455 + <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
456 + <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
457 + <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
458 + <?php echo esc_html( $status_text ); ?>
459 + </span>
460 + </div>
461 + <?php if ( $cat['label'] || $is_pro ) : ?>
462 + <div style="display:flex;flex-wrap:wrap;gap:4px;">
463 + <?php if ( $is_pro ) : ?>
464 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
465 + <?php endif; ?>
466 + <?php if ( $cat['label'] ) : ?>
467 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:<?php echo esc_attr( $cat['bg'] ); ?>;color:<?php echo esc_attr( $cat['color'] ); ?>;line-height:1.6;"><?php echo esc_html( $cat['label'] ); ?></span>
468 + <?php endif; ?>
469 + </div>
279 470 <?php endif; ?>
280 - <?php endforeach; ?>
281 - <?php endif; ?>
471 + </div>
472 + </div>
473 +
474 + <!-- Description -->
475 + <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
476 +
477 + <!-- Separator -->
478 + <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
479 +
480 + <!-- Actions -->
481 + <div style="display:flex;gap:6px;flex-wrap:wrap;">
482 + <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
483 + <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
484 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
485 + <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
486 + </div>
487 + <?php else : ?>
488 + <?php
489 + foreach ( $integration['actions'] as $action ) :
490 + $is_primary = ( 'function' === $action['type'] );
491 + $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
492 + ?>
493 + <?php if ( 'link' === $action['type'] ) : ?>
494 + <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
495 + <?php elseif ( 'external_link' === $action['type'] ) : ?>
496 + <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
497 + <?php echo esc_html( $action['label'] ); ?>
498 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
499 + </a>
500 + <?php elseif ( 'function' === $action['type'] ) : ?>
501 + <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
502 + <?php endif; ?>
503 + <?php endforeach; ?>
504 + <?php endif; ?>
505 + </div>
506 +
282 507 </div>
508 + <?php endforeach; ?>
509 + </div>
510 + </div>
511 + <?php endforeach; ?>
283 512
284 - </div>
285 - <?php endforeach; ?>
286 513 </div>
514 +
287 515 </div>
288 - <?php endforeach; ?>
289 -
290 516 </div>