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 (
{
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 = () =>
Loading Profile...
@{userData()?.user} • ID: {userData()?.id}
Analyzing: {getBasename(projectSkills().project)}