fixed the mobile UI bugs
This commit is contained in:
parent
b41271cf29
commit
6ac8159ce2
3 changed files with 40 additions and 16 deletions
|
|
@ -3,14 +3,11 @@
|
||||||
import React, { useEffect, useState, ReactNode } from 'react';
|
import React, { useEffect, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
export default function ServiceButton({ url, checkUrl, className, icon, name, desc, access, disabled }: { url: string, checkUrl?: string, className: string, icon: ReactNode, name: string, desc: string, access?: 'public' | 'private' | 'on_request', disabled?: boolean }) {
|
export default function ServiceButton({ url, checkUrl, className, icon, name, desc, access, disabled }: { url: string, checkUrl?: string, className: string, icon: ReactNode, name: string, desc: string, access?: 'public' | 'private' | 'on_request', disabled?: boolean }) {
|
||||||
const [status, setStatus] = useState<'pending' | 'up' | 'down'>(disabled ? 'up' : 'pending');
|
const [status, setStatus] = useState<'pending' | 'up' | 'down'>('pending');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Disabled buttons are decorative: skip the health check and stay green.
|
// Disabled buttons are decorative: skip the health check.
|
||||||
if (disabled) {
|
if (disabled) return;
|
||||||
setStatus('up');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkStatus = async () => {
|
const checkStatus = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -25,9 +22,11 @@ export default function ServiceButton({ url, checkUrl, className, icon, name, de
|
||||||
checkStatus();
|
checkStatus();
|
||||||
}, [url, checkUrl, disabled]);
|
}, [url, checkUrl, disabled]);
|
||||||
|
|
||||||
const statusColor = status === 'up' ? '#4ade80' : status === 'down' ? '#ef4444' : '#9ca3af';
|
// Disabled buttons always read as green without a state update in the effect.
|
||||||
const statusGlow = status === 'up' ? 'rgba(74, 222, 128, 0.9)' : status === 'down' ? 'rgba(239, 68, 68, 0.9)' : 'transparent';
|
const effectiveStatus = disabled ? 'up' : status;
|
||||||
const statusLabel = status === 'up' ? 'Active' : status === 'down' ? 'Dead' : 'Checking...';
|
const statusColor = effectiveStatus === 'up' ? '#4ade80' : effectiveStatus === 'down' ? '#ef4444' : '#9ca3af';
|
||||||
|
const statusGlow = effectiveStatus === 'up' ? 'rgba(74, 222, 128, 0.9)' : effectiveStatus === 'down' ? 'rgba(239, 68, 68, 0.9)' : 'transparent';
|
||||||
|
const statusLabel = effectiveStatus === 'up' ? 'Active' : effectiveStatus === 'down' ? 'Dead' : 'Checking...';
|
||||||
const isFriend = className === 'friend';
|
const isFriend = className === 'friend';
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
|
|
|
||||||
30
src/app/components/ThemeScript.tsx
Normal file
30
src/app/components/ThemeScript.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { useServerInsertedHTML } from "next/navigation";
|
||||||
|
|
||||||
|
// Runs before paint so the saved theme and the phone/desktop split are applied
|
||||||
|
// without a flash. It is injected with useServerInsertedHTML so the <script>
|
||||||
|
// lives in the SSR HTML stream instead of inside the React tree — React 19
|
||||||
|
// logs an error for script tags rendered by components on the client.
|
||||||
|
const INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('theme');if(t==='terminal'||t==='skeuo'){document.documentElement.setAttribute('data-theme',t);}var ua=navigator.userAgent||'';var phone=/(iPhone|iPod|Android.+Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini|Mobile)/i.test(ua);document.documentElement.setAttribute('data-device',phone?'phone':'desktop');}catch(e){}})();`;
|
||||||
|
|
||||||
|
export default function ThemeScript() {
|
||||||
|
// useServerInsertedHTML can fire several times during a single render pass;
|
||||||
|
// guard so the script only lands in the document once.
|
||||||
|
const inserted = useRef(false);
|
||||||
|
|
||||||
|
useServerInsertedHTML(() => {
|
||||||
|
if (inserted.current) return null;
|
||||||
|
inserted.current = true;
|
||||||
|
return (
|
||||||
|
<script
|
||||||
|
id="theme-init"
|
||||||
|
suppressHydrationWarning
|
||||||
|
dangerouslySetInnerHTML={{ __html: INIT_SCRIPT }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
// Removed unused google fonts since we use local Custom Mono
|
// Removed unused google fonts since we use local Custom Mono
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import ThemeScript from "./components/ThemeScript";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "dvorakmortonxd.dev",
|
title: "dvorakmortonxd.dev",
|
||||||
|
|
@ -25,14 +26,8 @@ export default function RootLayout({
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" data-theme="skeuo" suppressHydrationWarning>
|
<html lang="en" data-theme="skeuo" suppressHydrationWarning>
|
||||||
<head>
|
|
||||||
<script
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `(function(){try{var t=localStorage.getItem('theme');if(t==='terminal'||t==='skeuo'){document.documentElement.setAttribute('data-theme',t);}var ua=navigator.userAgent||'';var phone=/(iPhone|iPod|Android.+Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini|Mobile)/i.test(ua);document.documentElement.setAttribute('data-device',phone?'phone':'desktop');}catch(e){}})();`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body className="antialiased">
|
<body className="antialiased">
|
||||||
|
<ThemeScript />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue