Files
firefrost-services/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx
Claude (Chronicler #63) 5a607c8c8b refactor(modpackchecker): Batch 3+4 fixes - frontend, admin, docs
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>
2026-04-06 11:47:20 +00:00

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;