import { render } from 'solid-js/web'; import { createSignal, onMount, Show, For } from 'solid-js'; const IconPencil = () => ; const IconAdd = () => ; const IconCode = () => ; const getBasename = (filePath) => { if (!filePath) return ''; const parts = filePath.split(/[\\/]/); return parts[parts.length - 1] || ''; }; const getSkillIcon = (extension, displayName) => { const iconPath = `/private/server/exocore/web/public/icons/${extension.toLowerCase()}.svg`; return ( {`${displayName} { e.currentTarget.style.display = 'none'; e.currentTarget.parentNode.querySelector('.fallback-icon').style.display = 'block'; }} /> ); }; const planStylesConfig = { "Core Access": { textGrad1: '#D0A9F5', textGrad2: '#E8D4F7', cardGrad1: '#6A0DAD', cardGrad2: '#A74AC7', cardBorder: 'rgba(75, 0, 130, 0.8)', textColor: '#FFFFFF', iconFill: '#E8D4F7', glowColor: 'rgba(224, 187, 228, 0.3)' }, "Prime Core": { textGrad1: '#FFEB3B', textGrad2: '#FFF59D', cardGrad1: '#FBC02D', cardGrad2: '#FFD700', cardBorder: 'rgba(185, 139, 0, 0.8)', textColor: '#1A1A1A', iconFill: '#424242', glowColor: 'rgba(255, 250, 205, 0.4)' }, "Alpha Core": { textGrad1: '#00BCD4', textGrad2: '#80DEEA', cardGrad1: '#03A9F4', cardGrad2: '#4FC3F7', cardBorder: 'rgba(2, 119, 189, 0.8)', textColor: '#FFFFFF', iconFill: '#B2EBF2', glowColor: 'rgba(128, 222, 234, 0.3)' }, "EXO Elite": { textGrad1: '#F44336', textGrad2: '#FF8A80', cardGrad1: '#D32F2F', cardGrad2: '#E57373', cardBorder: 'rgba(154, 0, 7, 0.8)', textColor: '#FFFFFF', iconFill: '#FFCDD2', glowColor: 'rgba(255, 138, 128, 0.3)' }, "Hacker Core": { textGrad1: '#4CAF50', textGrad2: '#A5D6A7', cardGrad1: '#000000', cardGrad2: '#388E3C', cardBorder: 'rgba(27, 94, 32, 0.8)', textColor: '#00FF00', iconFill: '#C8E6C9', glowColor: 'rgba(165, 214, 167, 0.3)' } }; const defaultPlanStyle = { textGrad1: '#B0BEC5', textGrad2: '#ECEFF1', cardGrad1: '#455A64', cardGrad2: '#607D8B', cardBorder: 'rgba(38, 50, 56, 0.8)', textColor: '#FFFFFF', iconFill: '#ECEFF1', glowColor: 'rgba(144, 164, 174, 0.2)' }; function App() { const [loading, setLoading] = createSignal(true); const [status, setStatus] = createSignal({ type: '', message: '' }); const [userData, setUserData] = createSignal(null); const [editingBio, setEditingBio] = createSignal(false); const [editingNickname, setEditingNickname] = createSignal(false); const [nickname, setNickname] = createSignal(''); const [bio, setBio] = createSignal(''); const [modalOpen, setModalOpen] = createSignal(false); const [modalAction, setModalAction] = createSignal(() => {}); const [projectSkills, setProjectSkills] = createSignal(null); const avatarDimensions = { mobile: { headerH: 170, avatarH: 110, overlap: 55 }, tablet: { headerH: 240, avatarH: 140, overlap: 70 }, desktop: { headerH: 280, avatarH: 160, overlap: 80 }, }; const getToken = () => localStorage.getItem('exocore-token') || ''; const getCookies = () => localStorage.getItem('exocore-cookies') || ''; async function fetchUserInfo() { // Don't set loading to true here if it's a re-fetch, // it will be handled by the calling function. // setLoading(true); const token = getToken(); const cookies = getCookies(); if (!token || !cookies) { window.location.href = '/private/server/exocore/web/public/login'; return; } try { const res = await fetch('/private/server/exocore/web/userinfo', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, cookies }), }); if (!res.ok) { let errorMsg = `Server error: ${res.status}`; try { const errorData = await res.json(); errorMsg = errorData.message || errorMsg; } catch (parseError) {} throw new Error(errorMsg); } const data = await res.json(); if (data.data?.user && data.data.user.verified === 'success') { setUserData(data.data.user); // fetch skills only if user data is successfully fetched fetchSkills(); } else { setUserData(null); setStatus({ type: 'error', message: data.message || 'User verification failed. Redirecting...' }); setTimeout(() => { window.location.href = '/private/server/exocore/web/public/login'; }, 2500); } } catch (err) { setUserData(null); setStatus({ type: 'error', message: 'Failed to fetch user info: ' + err.message + '. Redirecting...' }); setTimeout(() => { window.location.href = '/private/server/exocore/web/public/login'; }, 2500); } finally { setLoading(false); } } async function fetchSkills() { try { const skillsRes = await fetch('/private/server/exocore/web/skills', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); if (skillsRes.ok) { const skillsData = await skillsRes.json(); setProjectSkills(skillsData && skillsData.length > 0 ? skillsData[0] : null); } else { console.error("Failed to fetch skills:", skillsRes.status); setProjectSkills(null); } } catch (skillsErr) { console.error("Error fetching skills:", skillsErr); setProjectSkills(null); } } async function handleUpdate(field, value, endEditStateFn) { setLoading(true); setModalOpen(false); // Close modal if open const token = getToken(); const cookies = getCookies(); try { const res = await fetch('/private/server/exocore/web/userinfoEdit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, cookies, field, edit: value }), }); const data = await res.json(); // Check if the response is NOT OK or if there's an explicit error message if (!res.ok || data.success === false) { throw new Error(data.message || 'An unknown error occurred.'); } // SUCCESS! setStatus({ type: 'success', message: data.message || 'Update successful!' }); setTimeout(() => setStatus({ type: '', message: '' }), 3000); // Re-fetch the single source of truth to ensure UI is in sync. await fetchUserInfo(); if (endEditStateFn) { endEditStateFn(false); } } catch (err) { setStatus({ type: 'error', message: 'Update failed: ' + err.message }); setTimeout(() => setStatus({ type: '', message: '' }), 3000); } finally { // Loading is set to false inside fetchUserInfo's finally block // setLoading(false); } } function openImageModal(field) { setModalAction(() => () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*,.heic,.heif'; input.onchange = (e) => { const file = e.target.files[0]; if (file) { if (file.size > 10 * 1024 * 1024) { // 10MB limit setStatus({ type: 'error', message: 'Max file size: 10MB.' }); setTimeout(() => setStatus({ type: '', message: '' }), 3000); return; } const reader = new FileReader(); reader.onload = () => handleUpdate(field, reader.result); reader.onerror = () => { setStatus({ type: 'error', message: 'Error reading file.' }); setTimeout(() => setStatus({ type: '', message: '' }), 3000); } reader.readAsDataURL(file); } }; input.click(); }); setModalOpen(true); } onMount(() => { setLoading(true); fetchUserInfo(); }); const Spinner = () =>
; const activePlanStyles = () => { const planName = userData()?.activePlan?.plan; return planStylesConfig[planName] || defaultPlanStyle; }; return ( <>
{status().message}
{status().message || 'Could not load profile.'}

}>

Loading Profile...

} >
Cover photo openImageModal('cover_photo')} />
User avatar openImageModal('avatar')} />

{userData()?.nickname || userData()?.user}

}>
setNickname(e.currentTarget.value)} />

@{userData()?.user} • ID: {userData()?.id}

{userData()?.activePlan?.plan}

About Me

{userData()?.bio || 'No bio yet. Click edit to add one!'}

}>
0}>

Project Skills

Analyzing: {getBasename(projectSkills().project)}

{(skill) => (
{getSkillIcon(skill.extension, skill.name)}
{skill.name}
{skill.skill}
)}
); } render(() => , document.getElementById('app'));