Skip to content

Commit 57e1798

Browse files
committed
feat(ajustes): comprobar actualizaciones en la zona admin (AdminBar del skill)
1 parent 02ebc36 commit 57e1798

4 files changed

Lines changed: 103 additions & 2 deletions

File tree

‎app/src/i18n/locales/en/translation.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,12 @@
328328
"changes": "Changes",
329329
"kofi": "Support the project on Ko-fi",
330330
"privacy": "100% private · 0% cloud",
331+
"checkUpdates": "Check for updates",
332+
"checking": "Checking…",
333+
"upToDate": "You're up to date — v{{version}} is the latest version",
334+
"updateAvailable": "New version available: v{{version}}",
335+
"viewRelease": "What's new",
336+
"updateError": "Couldn't check for updates. Try again.",
331337
"made": "made at home"
332338
},
333339
"connection": {

‎app/src/i18n/locales/es/translation.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,12 @@
328328
"changes": "Cambios",
329329
"kofi": "Apoya el proyecto en Ko-fi",
330330
"privacy": "100% privado · 0% nube",
331+
"checkUpdates": "Comprobar actualizaciones",
332+
"checking": "Comprobando…",
333+
"upToDate": "Estás al día — v{{version}} es la última versión",
334+
"updateAvailable": "Nueva versión disponible: v{{version}}",
335+
"viewRelease": "Ver novedades",
336+
"updateError": "No se pudo comprobar la actualización. Inténtalo de nuevo.",
331337
"made": "hecho en casa"
332338
},
333339
"connection": {

‎app/src/i18n/locales/zh-CN/translation.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@
326326
"changes": "更新",
327327
"kofi": "在 Ko-fi 支持项目",
328328
"privacy": "100% 私密 · 0% 云",
329+
"checkUpdates": "检查更新",
330+
"checking": "检查中…",
331+
"upToDate": "已是最新版本 — v{{version}} 是最新版",
332+
"updateAvailable": "有新版本可用:v{{version}}",
333+
"viewRelease": "查看更新",
334+
"updateError": "无法检查更新。请重试。",
329335
"made": "在家制作"
330336
},
331337
"connection": {

‎app/src/pages/Ajustes.tsx‎

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,50 @@ function Section({
9999
}
100100

101101
/** Zona de administración (patrón AdminBar del skill): tarjeta horizontal con
102-
* borde de acento, título "Administración" y secciones desplegables a la derecha. */
102+
* borde de acento, título "Administración", secciones desplegables (Usuarios)
103+
* y widgets inline. El comprobador de actualizaciones vive AQUÍ, no en Acerca de. */
103104
function AdminZone() {
104105
const { t } = useTranslation();
105106
const [open, setOpen] = useState(false);
107+
const [checking, setChecking] = useState(false);
108+
const [updateStatus, setUpdateStatus] = useState<'idle' | 'uptodate' | 'available' | 'error'>('idle');
109+
const [latestVersion, setLatestVersion] = useState('');
110+
111+
const checkUpdates = async () => {
112+
setChecking(true);
113+
setUpdateStatus('idle');
114+
try {
115+
const repo = REPO_URL.match(/github\.com\/([^/]+\/[^/.]+)/)?.[1];
116+
if (!repo) throw new Error('no repo');
117+
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
118+
headers: { Accept: 'application/vnd.github+json' },
119+
});
120+
let version = '';
121+
if (res.ok) {
122+
const data = await res.json();
123+
version = data.tag_name || '';
124+
} else if (res.status === 404) {
125+
const tagRes = await fetch(`https://api.github.com/repos/${repo}/tags?per_page=1`, {
126+
headers: { Accept: 'application/vnd.github+json' },
127+
});
128+
if (tagRes.ok) {
129+
const tags = await tagRes.json();
130+
version = tags[0]?.name || '';
131+
}
132+
}
133+
if (!version || compareSemver(version, pkg.version) <= 0) {
134+
setUpdateStatus('uptodate');
135+
} else {
136+
setLatestVersion(version);
137+
setUpdateStatus('available');
138+
}
139+
} catch {
140+
setUpdateStatus('error');
141+
} finally {
142+
setChecking(false);
143+
}
144+
};
145+
106146
return (
107147
<motion.section
108148
initial={{ opacity: 0, y: 24 }}
@@ -133,8 +173,44 @@ function AdminZone() {
133173
<ChevronDown size={14} className={cn('transition-transform', open && 'rotate-180')} />
134174
</button>
135175
</div>
176+
177+
{/* Widget inline: Comprobar actualizaciones (en la AdminBar según el skill) */}
178+
<div className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 border-t border-app pt-3">
179+
<button
180+
type="button"
181+
onClick={checkUpdates}
182+
disabled={checking}
183+
className="inline-flex h-8 items-center gap-2 rounded-lg border border-brand/40 bg-brand/10 px-3 text-xs font-semibold text-brand transition-colors hover:bg-brand/20 disabled:opacity-60"
184+
>
185+
{checking && <RefreshCw size={13} className="animate-spin" />}
186+
{checking ? t('ajustes.about.checking') : t('ajustes.about.checkUpdates')}
187+
</button>
188+
{updateStatus === 'uptodate' && (
189+
<span className="inline-flex items-center gap-1.5 text-xs text-emerald-500">
190+
<Check size={13} />
191+
{t('ajustes.about.upToDate', { version: pkg.version })}
192+
</span>
193+
)}
194+
{updateStatus === 'available' && (
195+
<span className="inline-flex items-center gap-2 text-xs text-brand">
196+
{t('ajustes.about.updateAvailable', { version: latestVersion })}
197+
<a
198+
href={`${REPO_URL}/releases`}
199+
target="_blank"
200+
rel="noreferrer"
201+
className="inline-flex h-6 items-center rounded-full border border-brand/40 bg-brand/10 px-2.5 text-[11px] font-medium text-brand hover:bg-brand/20"
202+
>
203+
{t('ajustes.about.viewRelease')}
204+
</a>
205+
</span>
206+
)}
207+
{updateStatus === 'error' && (
208+
<span className="text-xs text-destructive">{t('ajustes.about.updateError')}</span>
209+
)}
210+
</div>
211+
136212
{open && (
137-
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-4 border-t border-app pt-4">
213+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="mt-3">
138214
<UsersSection />
139215
</motion.div>
140216
)}
@@ -1479,6 +1555,13 @@ function InstallSection({ state, install }: { state: InstallState; install: () =
14791555

14801556
const REPO_URL = 'https://github.com/gnacho/panelsolar';
14811557

1558+
function compareSemver(a: string, b: string): number {
1559+
const pa = a.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
1560+
const pb = b.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
1561+
for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
1562+
return 0;
1563+
}
1564+
14821565
function AboutSection() {
14831566
const { t } = useTranslation();
14841567
const [systemInfo, setSystemInfo] = useState<{

0 commit comments

Comments
 (0)