1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource https://esm.sh/react */
import React, { useState, useEffect } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
import DOMPurify from "https://esm.sh/dompurify";
import { marked } from "https://esm.sh/marked";
function App() {
const [vals, setVals] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const [debugInfo, setDebugInfo] = useState([]);
const [username, setUsername] = useState("");
const handleValClick = (valId) => {
window.open(`https://val.town/v/${valId}`, '_blank');
};
useEffect(() => {
const pathname = window.location.pathname;
const usernameFromPath = pathname.split('/')[1];
setUsername(usernameFromPath || 'ejfox'); // Default to 'ejfox' if no username provided
const fetchVals = async () => {
setLoading(true);
setVals([]);
setError(null);
setDebugInfo([]);
try {
setDebugInfo(prev => [...prev, `Fetching vals for ${usernameFromPath || 'ejfox'}...`]);
const response = await fetch(`/vals/${usernameFromPath || 'ejfox'}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (!Array.isArray(data.vals)) {
throw new Error('Invalid data format: vals is not an array');
}
setDebugInfo(prev => [...prev, `Fetched ${data.vals.length} vals`]);
// Fetch emojis for each val with a delay
for (const val of data.vals) {
setDebugInfo(prev => [...prev, `Fetching emoji for ${val.name}...`]);
const emoji = await fetchEmojiForName(val.name);
setVals(prevVals => [...prevVals, { ...val, emoji }]);
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay
}
setDebugInfo(prev => [...prev, 'All vals and emojis fetched']);
} catch (error) {
console.error('Error fetching vals:', error);
setError(error.toString());
setDebugInfo(prev => [...prev, `Error: ${error.message}`]);
} finally {
setLoading(false);
}
};
fetchVals();
}, []);
const renderMarkdown = (content) => {
const rawMarkup = marked(content);
const sanitizedMarkup = DOMPurify.sanitize(rawMarkup);
return { __html: sanitizedMarkup };
};
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6">Vals Grid for {username}</h1>
<div className="mb-8 p-4 bg-gray-100 rounded">
<h3 className="text-xl font-semibold mb-2 text-xs">Debug Information:</h3>
<ul className="list-disc pl-5">
{debugInfo.map((info, index) => <li key={index}>{info}</li>)}
</ul>
</div>
{loading && vals.length === 0 && (
<div className="text-center mt-8">Loading...</div>
)}
{error && (
<div className="text-center mt-8 text-red-500">Error: {error}</div>
)}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{vals.map(val => (
<div
key={val.id}
className="bg-white rounded-lg shadow-md p-4 flex flex-col items-center justify-center cursor-pointer transform transition duration-200 ease-in-out hover:scale-105 hover:shadow-lg active:scale-95"
onClick={() => handleValClick(val.id)}
>
<div className="text-4xl mb-2">{val.emoji}</div>
<div className="text-xs text-center font-bold mb-1">{val.name}</div>
{val.description && (
<div
className="text-xxs text-center text-gray-500 line-clamp-2"
dangerouslySetInnerHTML={renderMarkdown(val.description)}
/>
)}