Impossible to maintain loading and Extension checking state
Current Issue:
When GD/Imagick is installed, the "Display Settings Unavailable" warning appears briefly and then disappears
When clicking the "Refresh QR Status" button:
Button shows loading state ("Checking...")
After check completes, the warning screen pops right back even though GD/Imagick is installed
What We Need to Achieve:
Initial Page Load:
If GD/Imagick is installed: Show full display settings tab
If not installed: Show warning message with refresh button
When Refresh Button is Clicked:
Show loading state
Check GD/Imagick status on server
If newly installed: Switch to display settings view
If still not installed: Keep showing warning
Either way, loading state should end
State Persistence:
Once GD/Imagick is detected as installed, it should stay that way
No flickering between states
No reappearing warnings if extension is actually installed
WalletUpAdmin.jsx in React/Vite
```
//update the QR status check:
// QR State Management
const [qrState, setQrState] = useState({
canUseQR: window.walletUpData?.qrExtensionsAvailable === true,
isChecking: false,
gdAvailable: window.walletUpData?.gdAvailable === true,
imagickAvailable: window.walletUpData?.imagickAvailable === true
});
useEffect(() => {
console.log('QR State changed:', qrState);
}, [qrState]);
const checkQRStatus = useCallback(async () => {
try {
setQrState(prev => ({ ...prev, isChecking: true }));
console.log('Starting QR status check');
const response = await fetch(window.walletUpData.ajaxUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'wallet_up_check_qr_status',
nonce: window.walletUpData.nonce
})
});
console.log('Response received:', response);
const data = await response.json();
console.log('Response data:', data);
if (data.success) {
const newState = {
canUseQR: data.data.qr_extensions_available === true,
isChecking: false,
gdAvailable: data.data.gd_available,
imagickAvailable: data.data.imagick_available
};
console.log('Setting new QR state:', newState);
setQrState(newState);
// Update window data
window.walletUpData = {
...window.walletUpData,
qrExtensionsAvailable: newState.canUseQR,
gdAvailable: newState.gdAvailable,
imagickAvailable: newState.imagickAvailable
};
console.log('Updated window data:', window.walletUpData);
}
} catch (error) {
console.error('QR check error:', error);
setError('Failed to check QR status. Please try again.');
setQrState(prev => ({ ...prev, isChecking: false }));
}
}, []);
```
```
{/* Display Settings Tab */}
<TabsContent value="display">{qrState.canUseQR ? (
<Card> <CardHeader><CardTitle>Display Settings</CardTitle>
<CardDescription>Customize how payment options appear</CardDescription>
</CardHeader> <CardContent> <div className="space-y-4"> <div className="space-y-2"> <div className="flex items-center space-x-2"><Checkbox
id="hideMessage"
checked={displaySettings.hideMessage}
onCheckedChange={(checked) => handleDisplayChange('hideMessage', checked)}
disabled={displaySettings.showDefaultMessage}
/>
<Label htmlFor="hideMessage" className={displaySettings.showDefaultMessage ? "text-gray-400" : ""}>
Hide all payment messages
</Label> </div> <p className="text-sm text-gray-500">When enabled, no messages will be shown above payment buttons or QR codes
</p> </div> <div className="space-y-2"> <div className="flex items-center space-x-2"><Checkbox
id="showDefaultMessage"
checked={displaySettings.showDefaultMessage}
onCheckedChange={(checked) => handleDisplayChange('showDefaultMessage', checked)}
disabled={displaySettings.hideMessage}
/>
<Label htmlFor="showDefaultMessage" className={displaySettings.hideMessage ? "text-gray-400" : ""}>
Show default payment message
</Label> </div> <p className="text-sm text-gray-500">Example: "Please Scan or Click to Pay $XX with [Payment Method]"
</p> </div> <div className="space-y-2"><Label htmlFor="customMessage" className={displaySettings.showDefaultMessage || displaySettings.hideMessage ? "text-gray-400" : ""}>
Custom Payment Message
</Label><Input
id="customMessage"
placeholder="Enter your custom payment message"
value={displaySettings.customMessage || ''}
onChange={(e) => handleDisplayChange('customMessage', e.target.value)}
disabled={displaySettings.showDefaultMessage || displaySettings.hideMessage}
/>
<p className="text-sm text-gray-500">Use $amount and $method as placeholders for dynamic values
</p> </div> <div><Label htmlFor="defaultMode">Default Display Mode</Label>
<Select
value={displaySettings.defaultMode}
onValueChange={(value) => handleDisplayChange('defaultMode', value)}
>
<SelectTrigger id="defaultMode"> <SelectValue placeholder="Select mode" /> </SelectTrigger> <SelectContent><SelectItem value="qr">QR Code</SelectItem>
<SelectItem value="button">Button</SelectItem>
<SelectItem value="link">Link</SelectItem>
</SelectContent> </Select> </div> <div><Label htmlFor="qrSize">QR Code Size</Label>
<Select
value={displaySettings.qrSize}
onValueChange={(value) => handleDisplayChange('qrSize', value)}
>
<SelectTrigger id="qrSize"> <SelectValue placeholder="Select size" /> </SelectTrigger> <SelectContent><SelectItem value="small">Small (150x150)</SelectItem>
<SelectItem value="medium">Medium (180x180)</SelectItem>
<SelectItem value="large">Large (250x250)</SelectItem>
</SelectContent> </Select> </div> <div><Label htmlFor="qrColor">QR Code Color</Label>
<Input
id="qrColor"
type="color"
value={displaySettings.qrColor}
onChange={(e) => handleDisplayChange('qrColor', e.target.value)}
/>
</div> <div><Label htmlFor="qrBackground">Background Color</Label>
<Input
id="qrBackground"
type="color"
value={displaySettings.qrBackground}
onChange={(e) => handleDisplayChange('qrBackground', e.target.value)}
/>
</div> <div className="flex items-center space-x-2"><Checkbox
id="qrBorder"
checked={displaySettings.qrBorder}
onCheckedChange={(checked) => handleDisplayChange('qrBorder', checked)}
/>
<Label htmlFor="qrBorder">Show Border</Label>
</div> <div><Label htmlFor="qrStyle">QR Style</Label>
<Select
value={displaySettings.qrStyle}
onValueChange={(value) => handleDisplayChange('qrStyle', value)}
>
<SelectTrigger id="qrStyle"> <SelectValue placeholder="Select style" /> </SelectTrigger> <SelectContent><SelectItem value="square">Square</SelectItem>
<SelectItem value="rounded">Rounded</SelectItem>
<SelectItem value="dot">Dot</SelectItem>
</SelectContent> </Select> </div> <div className="mt-8"><Button
onClick={saveSettings}
disabled={isSaving}
className="w-full"
>
{isSaving ? 'Saving...' : 'Save Display Settings'}
</Button>{error && (
<Alert variant="destructive" className="mt-4"><AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>)}
{saveStatus === 'success' && (
<Alert className="mt-4"><AlertTitle>Success</AlertTitle>
<AlertDescription>Display Settings saved successfully!</AlertDescription>
</Alert>)}
</div> </div> </CardContent> </Card>) : (
<Card> <CardHeader><CardTitle>Display Settings Unavailable</CardTitle>
<CardDescription>QR Code Customization requires GD or Imagick PHP extension</CardDescription>
</CardHeader> <CardContent> <Alert variant="destructive"><AlertTitle>Extension Required</AlertTitle>
<AlertDescription>Please contact your hosting provider to enable either GD or Imagick PHP extension to use QR code features.
</AlertDescription> </Alert> <div className="mt-4"><Button
onClick={checkQRStatus}
disabled={qrState.isChecking}
className="w-full"
>
{qrState.isChecking ? 'Checking...' : 'Refresh QR Status'}
</Button> </div> </CardContent> </Card>)}
</TabsContent>```
enqueue-walletup.php
```
public function check_qr_extensions() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Permission denied'], 403);
exit;
}
// Check extensions
$has_gd = extension_loaded('gd');
$has_imagick = extension_loaded('imagick');
$qr_available = $has_gd || $has_imagick;
// Debug logs
error_log('QR Check - GD: ' . ($has_gd ? 'Yes' : 'No'));
error_log('QR Check - Imagick: ' . ($has_imagick ? 'Yes' : 'No'));
error_log('QR Check - Available: ' . ($qr_available ? 'Yes' : 'No'));
$current_status = array(
'qr_extensions_available' => $qr_available,
'gd_available' => $has_gd,
'imagick_available' => $has_imagick
);
// Store the status
update_option('wallet_up_qr_status', $current_status);
// Send response
wp_send_json_success($current_status);
exit;
}
public function handle_qr_status_check() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Permission denied'], 403);
exit;
}
if (!check_ajax_referer('wallet_up_nonce', 'nonce', false)) {
wp_send_json_error(['message' => 'Invalid security token'], 403);
exit;
}
$status = get_option('wallet_up_qr_status');
if (!$status) {
$status = wallet_up_check_qr_extensions();
}
wp_send_json_success($status);
}
/**
* Enqueue admin assets
*/
public function wallet_up_admin_assets($hook) {
$wallet_up_screen = get_current_screen();
if (!$wallet_up_screen || $wallet_up_screen->id !== 'toplevel_page_wallet-up') {
return;
}
if ($this->is_debug()) {
error_log('Wallet Up: Loading admin assets');
}
$manifest_path = $this->get_manifest_path();
if (!$manifest_path) {
$this->handle_build_error('Manifest file not found');
return;
}
try {
$manifest = $this->load_manifest($manifest_path);
if (!isset($manifest['src/main.jsx'])) {
throw new Exception('Main entry point not found in manifest');
}
// Get the main entry and CSS files
$main_entry = $manifest['src/main.jsx'];
$main_js = $main_entry['file'];
$main_css = isset($main_entry['css']) ? $main_entry['css'][0] : null;
$dist_path = WALLET_UP_BASE_DIR . 'walup-react/dist/';
$dist_url = WALLET_UP_BASE_URL . 'walup-react/dist/';
if ($this->is_debug()) {
$this->log_debug_info($dist_path, $main_js);
}
// Enqueue WordPress dependencies first
wp_enqueue_script('wp-element');
wp_enqueue_script('wp-i18n');
// Enqueue React application
wp_enqueue_script(
'wallet-up-react',
$dist_url . $main_js,
['wp-element', 'wp-i18n'],
$this->asset_version,
true
);
// Enqueue CSS if it exists
if ($main_css) {
wp_enqueue_style(
'wallet-up-styles',
$dist_url . $main_css,
array(),
$this->asset_version
);
}
// Get stored QR status
$qr_status = get_option('wallet_up_qr_status', array(
'gd_available' => false,
'imagick_available' => false,
'qr_available' => false
));
// If no stored status exists, check and store it
if (empty($qr_status)) {
$has_gd = extension_loaded('gd');
$has_imagick = extension_loaded('imagick');
$qr_available = $has_gd || $has_imagick;
$qr_status = array(
'gd_available' => $has_gd,
'imagick_available' => $has_imagick,
'qr_available' => $qr_available
);
update_option('wallet_up_qr_status', $qr_status);
}
// Single localization of walletUpData
wp_localize_script(
'wallet-up-react',
'walletUpData',
array(
'debug' => $this->is_debug(),
'nonce' => wp_create_nonce('wallet_up_nonce'),
'ajaxUrl' => admin_url('admin-ajax.php'),
'settings' => $this->get_sanitized_settings(),
'displaySettings' => $this->get_display_settings(),
'paths' => [
'assets' => esc_url_raw(WALLET_UP_BASE_URL . 'assets/'),
],
'i18n' => $this->get_i18n_strings(),
'qrExtensionsAvailable' => $qr_status['qr_available'],
'gdAvailable' => $qr_status['gd_available'],
'imagickAvailable' => $qr_status['imagick_available']
)
);
// Separate localization for AJAX data
wp_localize_script(
'wallet-up-react',
'walletUpAjax',
array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wallet_up_nonce')
)
);
} catch (Exception $e) {
if ($this->is_debug()) {
error_log('Wallet Up: Asset loading error - ' . $e->getMessage());
}
$this->handle_build_error($e->getMessage());
}
}
```
wallet-up.php (main plugin)
```
function wallet_up_check_qr_extensions() {
// Check extensions
$has_gd = extension_loaded('gd');
$has_imagick = extension_loaded('imagick');
$qr_available = $has_gd || $has_imagick;
// Store status
$current_status = array(
'qr_extensions_available' => $qr_available,
'gd_available' => $has_gd,
'imagick_available' => $has_imagick,
'qr_available' => $qr_available
);
update_option('wallet_up_qr_status', $current_status);
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('WalletUp: QR status - GD: ' . ($has_gd ? 'Yes' : 'No') .
', Imagick: ' . ($has_imagick ? 'Yes' : 'No'));
}
return $current_status;
}
// Register activation and initialization hooks
register_activation_hook(__FILE__, function() {
wallet_up_check_qr_extensions();
wallet_up_install_analytics();
});
// Run check on key WordPress events
add_action('plugins_loaded', 'wallet_up_check_qr_extensions', 10);
add_action('admin_init', 'wallet_up_check_qr_extensions', 10);
function wallet_up_admin_notice() {
echo '<div class="notice notice-error"><p>' . esc_html__('Wallet Up QR Code generation requires either GD or Imagick PHP extension. Please contact your hosting provider to enable one of these extensions.', 'walletup') . '</p></div>';
}
// Include necessary WordPress core files
include_once(ABSPATH . 'wp-admin/includes/plugin.php');
include_once(ABSPATH . 'wp-includes/pluggable.php');
include_once(ABSPATH . 'wp-includes/option.php');
// Add filters for shortcode support in widgets
add_filter('widget_text', 'shortcode_unautop');
add_filter('widget_text', 'do_shortcode');
// Register activation hook
register_activation_hook(__FILE__, 'wallet_up_install_analytics');
```
Posted Jan 4, 2025, 9:08 PM UTC