BATCH 3 - Frontend & UI: wrapper.tsx (Console Widget): - FIXED: API URL from .../ext/modpackchecker/check to .../check - Added 429 rate limit handling with user-friendly message UpdateBadge.tsx (Dashboard Badge): - Added 60-second TTL to global cache (was infinite) - Prevents stale data during client-side navigation admin/view.blade.php: - Disabled Discord webhook field (PRO TIER badge) - Disabled Check Interval field (PRO TIER badge) - Added support callout linking to Discord BATCH 4 - Documentation: README.md: - Fixed architecture diagram (server_uuid, status string) - Added app/Services/ModpackApiService.php to file structure - Fixed API endpoint URLs throughout - Updated installation for BuiltByBit (.blueprint package) - Updated 'Adding New Platform' instructions for Service pattern - Added Support section with Discord link - Changed license to explicit commercial terms NEW: CHANGELOG.md - Version history for future updates - Documents v1.0.0 features Reviewed by: Gemini AI (Architecture Consultant) Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
168 lines
6.0 KiB
TypeScript
168 lines
6.0 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { ServerContext } from '@/state/server';
|
|
import http from '@/api/http';
|
|
|
|
interface VersionData {
|
|
success: boolean;
|
|
platform?: string;
|
|
modpack_id?: string;
|
|
modpack_name?: string;
|
|
current_version?: string;
|
|
latest_version?: string;
|
|
status?: string;
|
|
message?: string;
|
|
error?: string;
|
|
}
|
|
|
|
const ModpackVersionCard: React.FC = () => {
|
|
const uuid = ServerContext.useStoreState((state) => state.server.data?.uuid);
|
|
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
|
const [data, setData] = useState<VersionData | null>(null);
|
|
|
|
const checkForUpdates = async () => {
|
|
if (!uuid) return;
|
|
|
|
setStatus('loading');
|
|
try {
|
|
// Updated to match Batch 1 route optimization
|
|
const response = await http.post(`/api/client/extensions/modpackchecker/servers/${uuid}/check`);
|
|
setData(response.data);
|
|
setStatus(response.data.success ? 'success' : 'error');
|
|
} catch (error: any) {
|
|
// Handle 429 Rate Limit responses with user-friendly message
|
|
if (error.response?.status === 429) {
|
|
setData({
|
|
success: false,
|
|
error: 'Rate limit reached. Please wait 60 seconds.',
|
|
});
|
|
} else {
|
|
setData({
|
|
success: false,
|
|
error: error.response?.data?.message || 'Failed to check for updates',
|
|
});
|
|
}
|
|
setStatus('error');
|
|
}
|
|
};
|
|
|
|
const getPlatformIcon = (platform?: string) => {
|
|
switch (platform) {
|
|
case 'curseforge':
|
|
return '🔥';
|
|
case 'modrinth':
|
|
return '🌿';
|
|
case 'technic':
|
|
return '⚙️';
|
|
case 'ftb':
|
|
return '📦';
|
|
default:
|
|
return '❓';
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status?: string) => {
|
|
switch (status) {
|
|
case 'up_to_date':
|
|
return '#4ECDC4'; // Frost
|
|
case 'update_available':
|
|
return '#FF6B35'; // Fire
|
|
case 'error':
|
|
return '#ef4444';
|
|
default:
|
|
return '#888';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
background: 'rgba(30, 30, 46, 0.8)',
|
|
borderRadius: '8px',
|
|
padding: '16px',
|
|
marginBottom: '16px',
|
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }}>
|
|
<h3 style={{ margin: 0, fontSize: '14px', fontWeight: 600, color: '#fff' }}>
|
|
Modpack Version
|
|
</h3>
|
|
{status === 'idle' && (
|
|
<button
|
|
onClick={checkForUpdates}
|
|
style={{
|
|
background: 'linear-gradient(135deg, #FF6B35, #4ECDC4)',
|
|
border: 'none',
|
|
borderRadius: '6px',
|
|
padding: '6px 12px',
|
|
color: '#fff',
|
|
fontSize: '12px',
|
|
fontWeight: 500,
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
Check for Updates
|
|
</button>
|
|
)}
|
|
{status === 'success' && (
|
|
<button
|
|
onClick={checkForUpdates}
|
|
style={{
|
|
background: 'rgba(255, 255, 255, 0.1)',
|
|
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
borderRadius: '6px',
|
|
padding: '6px 12px',
|
|
color: '#888',
|
|
fontSize: '12px',
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
Refresh
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{status === 'loading' && (
|
|
<div style={{ color: '#888', fontSize: '13px' }}>
|
|
<span style={{ display: 'inline-block', animation: 'pulse 1.5s infinite' }}>
|
|
Checking version...
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{status === 'success' && data?.success && (
|
|
<div style={{ fontSize: '13px' }}>
|
|
<div style={{ marginBottom: '8px', color: '#ccc' }}>
|
|
<span style={{ marginRight: '8px' }}>{getPlatformIcon(data.platform)}</span>
|
|
<strong style={{ color: '#fff' }}>{data.modpack_name}</strong>
|
|
<span style={{ color: '#666', marginLeft: '8px', textTransform: 'capitalize' }}>
|
|
({data.platform})
|
|
</span>
|
|
</div>
|
|
<div style={{ display: 'flex', gap: '16px', color: '#888' }}>
|
|
<div>
|
|
<span style={{ color: '#666' }}>Latest: </span>
|
|
<span style={{ color: getStatusColor(data.status) }}>{data.latest_version}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{(status === 'error' || (status === 'success' && !data?.success)) && (
|
|
<div style={{ color: '#ef4444', fontSize: '13px' }}>
|
|
{data?.error || data?.message || 'Unknown error'}
|
|
</div>
|
|
)}
|
|
|
|
<style>{`
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ModpackVersionCard;
|