This commit is contained in:
2025-12-27 19:44:34 +00:00
parent 854a3c097f
commit 5f04b5e2da

View File

@@ -2,8 +2,8 @@
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Telegram Mini App - Perfil de Usuario</title> <title>Telegram Mini App - Perfil Extendido</title>
<!-- Script Oficial de Telegram WebApp --> <!-- Script Oficial de Telegram WebApp -->
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script>
@@ -23,9 +23,21 @@
<style> <style>
body { body {
background-color: var(--tg-theme-bg-color, #17212b); background-color: var(--tg-theme-bg-color, #0f172a);
color: var(--tg-theme-text-color, #ffffff); color: var(--tg-theme-text-color, #ffffff);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-webkit-tap-highlight-color: transparent;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
} }
</style> </style>
</head> </head>
@@ -35,165 +47,242 @@
<script type="text/babel"> <script type="text/babel">
const { useState, useEffect } = React; const { useState, useEffect } = React;
// Componente de Tarjeta de Información // --- Componentes UI ---
const InfoCard = ({ icon, label, value, isLink }) => {
const SectionTitle = ({ title }) => (
<h3 className="text-slate-400 text-xs uppercase font-bold tracking-wider mb-3 mt-6 ml-1 flex items-center gap-2">
{title}
</h3>
);
const InfoCard = ({ icon, label, value, subValue, highlight }) => {
return ( return (
<div className="bg-white/10 p-4 rounded-xl flex items-center justify-between mb-3 backdrop-blur-sm border border-white/5 shadow-sm"> <div className="bg-slate-800/50 p-3.5 rounded-xl flex items-center justify-between mb-2 backdrop-blur-sm border border-slate-700/50 hover:bg-slate-800 transition-colors">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 overflow-hidden">
<div className="text-blue-400"> <div className={`p-2 rounded-lg ${highlight ? 'bg-blue-500/10 text-blue-400' : 'bg-slate-700/50 text-slate-400'}`}>
{icon} {icon}
</div> </div>
<span className="text-gray-300 text-sm font-medium">{label}</span> <div className="flex flex-col overflow-hidden">
<span className="text-slate-300 text-sm font-medium truncate">{label}</span>
{subValue && <span className="text-slate-500 text-xs truncate">{subValue}</span>}
</div> </div>
<div className="text-white font-semibold text-right truncate max-w-[150px]"> </div>
{isLink && value ? ( <div className="text-white font-semibold text-sm text-right pl-2">
<span className="text-blue-400">@{value}</span> {value}
) : (
value || "N/A"
)}
</div> </div>
</div> </div>
); );
}; };
const Badge = ({ children, color = "blue" }) => {
const colors = {
blue: "bg-blue-500/20 text-blue-300 border-blue-500/30",
green: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30",
yellow: "bg-amber-500/20 text-amber-300 border-amber-500/30",
purple: "bg-purple-500/20 text-purple-300 border-purple-500/30",
};
return (
<span className={`px-2 py-0.5 rounded text-xs font-medium border ${colors[color] || colors.blue}`}>
{children}
</span>
);
};
// --- App Principal ---
const App = () => { const App = () => {
const [user, setUser] = useState(null); const [data, setData] = useState(null);
const [isMock, setIsMock] = useState(false); const [isMock, setIsMock] = useState(false);
const [themeParams, setThemeParams] = useState({});
useEffect(() => { useEffect(() => {
const tg = window.Telegram.WebApp; const tg = window.Telegram.WebApp;
// 1. Inicializar la WebApp
tg.ready(); tg.ready();
tg.expand(); // Ocupar toda la altura disponible tg.expand();
// Guardar parámetros de tema para usar colores nativos si se desea // Intentar obtener datos reales
setThemeParams(tg.themeParams); const initData = tg.initDataUnsafe;
// 2. Intentar obtener el usuario if (initData?.user) {
const telegramUser = tg.initDataUnsafe?.user; // MODO TELEGRAM REAL
tg.setHeaderColor('#0f172a');
if (telegramUser) { setData({
// Estamos dentro de Telegram con un usuario real user: initData.user,
setUser(telegramUser); device: {
platform: tg.platform, // 'ios', 'android', 'tdesktop', 'weba'
version: tg.version,
colorScheme: tg.colorScheme, // 'light' or 'dark'
expanded: tg.isExpanded
},
session: {
auth_date: initData.auth_date,
start_param: initData.start_param || "Ninguno",
hash: initData.hash ? "Seguro (Verificado)" : "No verificado"
}
});
setIsMock(false); setIsMock(false);
// Configurar el color del header de Telegram para que coincida con nuestra app
tg.setHeaderColor('#1e293b'); // Un color oscuro tipo slate-800
} else { } else {
// NO estamos en Telegram (ej: navegador web de desarrollo) // MODO VISTA PREVIA (NAVEGADOR)
// Usamos datos falsos para visualizar el diseño
setIsMock(true); setIsMock(true);
setUser({ setData({
id: 123456789, user: {
id: 987654321,
first_name: "Usuario", first_name: "Usuario",
last_name: "De Prueba", last_name: "Demo",
username: "ejemplo_dev", username: "usuario_demo",
language_code: "es", language_code: "es",
is_premium: true, is_premium: true,
allows_write_to_pm: true allows_write_to_pm: true,
photo_url: null
},
device: {
platform: "tdesktop", // Simulando PC
version: "7.0",
colorScheme: "dark",
expanded: true
},
session: {
auth_date: Math.floor(Date.now() / 1000),
start_param: "ref_12345",
hash: "mock_hash_123"
}
}); });
} }
// Renderizar iconos
setTimeout(() => {
if (window.lucide) window.lucide.createIcons();
}, 100);
}, []); }, []);
// Función para renderizar iconos de Lucide (ya que estamos en CDN) if (!data) return <div className="flex h-screen items-center justify-center text-slate-500 animate-pulse">Cargando datos...</div>;
const renderIcon = (name) => {
if (!window.lucide) return null; // Helpers para formatear
// Mapeo simple de iconos necesarios const formatPlatform = (p) => {
const icons = { const map = {
user: <i data-lucide="user" className="w-5 h-5"></i>, 'ios': 'iPhone / iPad',
id: <i data-lucide="hash" className="w-5 h-5"></i>, 'android': 'Android',
globe: <i data-lucide="globe" className="w-5 h-5"></i>, 'tdesktop': 'PC / Mac (Escritorio)',
crown: <i data-lucide="crown" className="w-5 h-5"></i> 'weba': 'Web Browser',
'macos': 'macOS'
}; };
return icons[name]; return map[p] || p;
}; };
// Efecto para activar los iconos de Lucide después del renderizado const formatDate = (timestamp) => {
useEffect(() => { if (!timestamp) return "N/A";
if (window.lucide) { return new Date(timestamp * 1000).toLocaleDateString() + ' ' + new Date(timestamp * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
window.lucide.createIcons(); };
}
});
if (!user) return <div className="flex justify-center items-center h-screen text-white">Cargando...</div>;
return ( return (
<div className="min-h-screen bg-slate-900 p-4 flex flex-col items-center pt-8"> <div className="min-h-screen bg-slate-950 p-4 pb-12 font-sans select-none">
{/* Aviso si estamos en modo prueba */} {/* Header: Foto y Nombre */}
{isMock && ( <div className="flex flex-col items-center pt-6 pb-2">
<div className="w-full max-w-md bg-yellow-500/20 border border-yellow-500/50 text-yellow-200 px-4 py-2 rounded-lg mb-6 text-xs text-center"> <div className="relative group cursor-pointer transition-transform active:scale-95">
Modo Vista Previa (Fuera de Telegram) <div className="w-24 h-24 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 p-[3px] shadow-2xl shadow-indigo-500/20">
</div> <div className="w-full h-full rounded-full bg-slate-900 flex items-center justify-center overflow-hidden">
)} {data.user.photo_url ? (
<img src={data.user.photo_url} className="w-full h-full object-cover" />
{/* Header con Avatar */}
<div className="flex flex-col items-center mb-8 animate-fade-in-down">
<div className="relative">
<div className="w-24 h-24 bg-gradient-to-tr from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-4xl font-bold text-white shadow-xl mb-4 border-4 border-slate-800">
{user.photo_url ? (
<img src={user.photo_url} alt="Profile" className="w-full h-full rounded-full object-cover" />
) : ( ) : (
<span>{user.first_name?.charAt(0) || "U"}</span> <span className="text-3xl font-bold text-white">{data.user.first_name[0]}</span>
)} )}
</div> </div>
{user.is_premium && ( </div>
<div className="absolute bottom-3 right-0 bg-yellow-500 text-slate-900 rounded-full p-1 border-2 border-slate-800"> {data.user.is_premium && (
<i data-lucide="star" className="w-4 h-4 fill-current"></i> <div className="absolute -bottom-1 -right-1 bg-gradient-to-r from-amber-400 to-orange-500 text-white p-1.5 rounded-full border-4 border-slate-950 shadow-sm" title="Usuario Premium">
<i data-lucide="star" className="w-3.5 h-3.5 fill-white"></i>
</div> </div>
)} )}
</div> </div>
<h1 className="text-2xl font-bold text-white"> <div className="mt-4 text-center">
{user.first_name} {user.last_name} <h1 className="text-xl font-bold text-white flex items-center justify-center gap-2">
{data.user.first_name} {data.user.last_name}
</h1> </h1>
{user.username && ( {data.user.username && (
<p className="text-blue-400 font-medium">@{user.username}</p> <p className="text-indigo-400 font-medium text-sm">@{data.user.username}</p>
)} )}
</div> </div>
{/* Lista de Datos */} {/* Badges Rápidos */}
<div className="w-full max-w-md space-y-2"> <div className="flex gap-2 mt-3">
<h2 className="text-slate-400 text-xs uppercase font-bold tracking-wider mb-2 ml-1">Información de Cuenta</h2> {data.user.language_code && (
<Badge color="blue">{data.user.language_code.toUpperCase()}</Badge>
)}
<Badge color={data.user.allows_write_to_pm ? "green" : "yellow"}>
{data.user.allows_write_to_pm ? "Chat Activo" : "Chat Bloqueado"}
</Badge>
</div>
</div>
{isMock && (
<div className="mt-4 bg-orange-500/10 border border-orange-500/20 rounded-lg p-3 flex gap-3 items-start">
<i data-lucide="alert-triangle" className="w-5 h-5 text-orange-400 shrink-0 mt-0.5"></i>
<div className="text-xs text-orange-200/80">
<strong className="text-orange-400 block mb-1">Modo Simulación</strong>
Estás viendo datos de ejemplo porque no estás dentro de Telegram.
</div>
</div>
)}
{/* SECCIÓN 1: Identificación */}
<SectionTitle title="Identificación" />
<InfoCard <InfoCard
icon={<i data-lucide="hash" className="w-5 h-5"></i>} icon={<i data-lucide="fingerprint" className="w-5 h-5"></i>}
label="ID de Telegram" label="User ID"
value={user.id} value={<span className="font-mono text-slate-200">{data.user.id}</span>}
highlight
/>
<InfoCard
icon={<i data-lucide="link" className="w-5 h-5"></i>}
label="Parámetro de Inicio"
subValue="Usado para referencias/invitaciones"
value={data.session.start_param}
/> />
{/* SECCIÓN 2: Entorno Técnico */}
<SectionTitle title="Dispositivo y App" />
<InfoCard <InfoCard
icon={<i data-lucide="globe" className="w-5 h-5"></i>} icon={<i data-lucide="smartphone" className="w-5 h-5"></i>}
label="Idioma" label="Plataforma"
value={user.language_code === 'es' ? 'Español' : user.language_code?.toUpperCase()} value={formatPlatform(data.device.platform)}
/>
<InfoCard
icon={<i data-lucide="git-branch" className="w-5 h-5"></i>}
label="Versión Telegram"
value={`v${data.device.version}`}
/>
<InfoCard
icon={<i data-lucide="palette" className="w-5 h-5"></i>}
label="Tema del Sistema"
value={data.device.colorScheme === 'dark' ? '🌙 Oscuro' : '☀️ Claro'}
/> />
{/* SECCIÓN 3: Seguridad de Sesión */}
<SectionTitle title="Sesión" />
<InfoCard <InfoCard
icon={<i data-lucide="zap" className="w-5 h-5"></i>} icon={<i data-lucide="clock" className="w-5 h-5"></i>}
label="Estado Premium" label="Fecha de Autenticación"
value={user.is_premium ? "Activo" : "No Activo"} value={<span className="text-xs">{formatDate(data.session.auth_date)}</span>}
/> />
{/* Botón de acción */} {/* Botón de acción */}
<div className="mt-8 mb-4">
<button <button
onClick={() => window.Telegram.WebApp.close()} onClick={() => window.Telegram.WebApp.close()}
className="mt-8 w-full bg-blue-600 hover:bg-blue-500 active:scale-95 transition-all text-white font-bold py-3 px-4 rounded-xl shadow-lg flex items-center justify-center gap-2" className="w-full bg-slate-800 hover:bg-slate-700 text-slate-300 font-medium py-3.5 px-4 rounded-xl transition-all border border-slate-700 flex items-center justify-center gap-2 active:scale-95"
> >
<span>Cerrar Mini App</span> <i data-lucide="x" className="w-4 h-4"></i>
Cerrar Mini App
</button> </button>
</div> </div>
{/* Raw Data (Opcional, útil para desarrollo) */} <div className="text-center">
<div className="mt-10 w-full max-w-md"> <p className="text-[10px] text-slate-600">
<details className="text-xs text-slate-500 cursor-pointer"> Mini App v1.2 React + Tailwind
<summary className="hover:text-slate-300 transition-colors">Ver datos crudos (JSON)</summary> </p>
<pre className="mt-2 bg-slate-950 p-3 rounded-lg overflow-x-auto text-green-400 border border-slate-800">
{JSON.stringify(user, null, 2)}
</pre>
</details>
</div> </div>
</div> </div>
); );
}; };