untitled-4011Sidebar.tsx2 matches
2122export function Sidebar({ currentPath, navigate, isDarkMode, toggleDarkMode }: SidebarProps) {
23const { userProfile, isConnected, setApiKey } = useValTown();
24const [isCollapsed, setIsCollapsed] = useState(false);
2526const handleLogout = () => {
27setApiKey("");
28navigate("/");
29};
untitled-4011HomePage.tsx16 matches
1011export function HomePage({ navigate, isDarkMode, toggleDarkMode }: HomePageProps) {
12const { setApiKey, error, isLoading } = useValTown();
13const [tempApiKey, setTempApiKey] = useState("");
14const [showApiKey, setShowApiKey] = useState(false);
1516const handleSubmit = (e: React.FormEvent) => {
17e.preventDefault();
18if (tempApiKey.trim()) {
19setApiKey(tempApiKey.trim());
20}
21};
46</div>
4748{/* API Key Form */}
49<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 border dark:border-gray-700">
50<h2 className="text-xl font-semibold mb-4 dark:text-white">Get Started</h2>
53<div>
54<label className="block text-sm font-medium mb-2 dark:text-gray-200">
55Val Town API Key
56</label>
57<div className="flex gap-2">
58<input
59type={showApiKey ? "text" : "password"}
60value={tempApiKey}
61onChange={(e) => setTempApiKey(e.target.value)}
62placeholder="Enter your API key"
63className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
64required
66<button
67type="button"
68onClick={() => setShowApiKey(!showApiKey)}
69className="px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 dark:text-gray-200"
70>
71{showApiKey ? "Hide" : "Show"}
72</button>
73</div>
74<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
75Get your API key from{" "}
76<a
77href="https://val.town/settings/api"
78target="_blank"
79rel="noopener noreferrer"
93<button
94type="submit"
95disabled={!tempApiKey.trim() || isLoading}
96className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
97>
untitled-4011Router.tsx2 matches
18export function Router({ isDarkMode, toggleDarkMode }: RouterProps) {
19const [currentPath, setCurrentPath] = useState(window.location.pathname);
20const { isConnected, apiKey, isLoading } = useValTown();
2122useEffect(() => {
4748// Show home page if not authenticated
49if (!apiKey || !isConnected) {
50return <HomePage navigate={navigate} isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />;
51}
untitled-4011ValTownProvider.tsx17 matches
1011export function ValTownProvider({ children }: ValTownProviderProps) {
12const [apiKey, setApiKeyState] = useState<string | null>(null);
13const [isConnected, setIsConnected] = useState(false);
14const [isLoading, setIsLoading] = useState(true);
16const [error, setError] = useState<string | null>(null);
1718// Load API key from localStorage on mount
19useEffect(() => {
20const savedApiKey = localStorage.getItem("valtown_api_key");
21if (savedApiKey) {
22setApiKeyState(savedApiKey);
23}
24setIsLoading(false);
25}, []);
2627// Test connection and fetch user profile when API key changes
28useEffect(() => {
29if (!apiKey) {
30setIsConnected(false);
31setUserProfile(null);
39
40try {
41const response = await fetch("/api/valtown/v1/me", {
42headers: {
43"X-ValTown-API-Key": apiKey,
44"Content-Type": "application/json",
45},
6869testConnection();
70}, [apiKey]);
7172const setApiKey = (key: string) => {
73if (key.trim()) {
74localStorage.setItem("valtown_api_key", key.trim());
75setApiKeyState(key.trim());
76} else {
77localStorage.removeItem("valtown_api_key");
78setApiKeyState(null);
79}
80};
8182const value: ValTownContextType = {
83apiKey,
84setApiKey,
85isConnected,
86isLoading,
untitled-4011SettingsPage.tsx23 matches
45export function SettingsPage() {
6const { apiKey, setApiKey, isConnected, userProfile } = useValTown();
7const [tempApiKey, setTempApiKey] = useState(apiKey || "");
8const [showApiKey, setShowApiKey] = useState(false);
910const handleSaveApiKey = () => {
11setApiKey(tempApiKey);
12};
1314const handleClearApiKey = () => {
15setApiKey("");
16setTempApiKey("");
17};
182223<div className="grid gap-6">
24{/* API Key Configuration */}
25<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border dark:border-gray-700 p-6">
26<h2 className="text-lg font-semibold mb-4 dark:text-white">Val Town API Configuration</h2>
27<div className="space-y-4">
28<div>
29<label className="block text-sm font-medium mb-2 dark:text-gray-200">
30API Key
31</label>
32<div className="flex gap-2">
33<input
34type={showApiKey ? "text" : "password"}
35value={tempApiKey}
36onChange={(e) => setTempApiKey(e.target.value)}
37placeholder="Enter your Val Town API key"
38className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
39/>
40<button
41onClick={() => setShowApiKey(!showApiKey)}
42className="px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 dark:text-gray-200"
43>
44{showApiKey ? "Hide" : "Show"}
45</button>
46</div>
47<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
48Get your API key from{" "}
49<a
50href="https://val.town/settings/api"
51target="_blank"
52rel="noopener noreferrer"
60<div className="flex gap-2">
61<button
62onClick={handleSaveApiKey}
63disabled={!tempApiKey.trim()}
64className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
65>
66Save API Key
67</button>
68<button
69onClick={handleClearApiKey}
70className="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700"
71>
untitled-4011proxy.ts11 matches
6const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
78const VALTOWN_API_BASE_URL = `https://api.val.town`;
9const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
10const RATE_LIMIT_MAX_REQUESTS = 100; // requests per window
33}
3435// Proxy handler for all Val Town API requests
36app.all("/valtown/*", async (c) => {
37const path = c.req.path.replace("/api/valtown/", "");
38const searchParams = c.req.url.includes("?") ? c.req.url.split("?")[1] : "";
39const targetUrl = `${VALTOWN_API_BASE_URL}/${path}${searchParams ? `?${searchParams}` : ""}`;
40const userApiKey = c.req.header("X-ValTown-API-Key");
41const clientIp = c.req.header("CF-Connecting-IP") || c.req.header("X-Forwarded-For") || "unknown";
4243console.log(`[Proxy] Request IN: ${c.req.method} ${c.req.path} from IP: ${clientIp}`);
44
45if (!userApiKey) {
46console.error("[Proxy] X-ValTown-API-Key: MISSING!");
47return c.json({ error: "User API key not provided to proxy." }, 401);
48}
4956console.log(`[Proxy] Rate limit check PASSED for IP: ${clientIp}`);
5758// Prepare headers for Val Town API
59const requestHeaders = new Headers();
60const contentType = c.req.header("content-type");
67if (ifMatch) requestHeaders.set("if-match", ifMatch);
68if (ifNoneMatch) requestHeaders.set("if-none-match", ifNoneMatch);
69requestHeaders.set("Authorization", `Bearer ${userApiKey}`);
7071// Handle request body
9798try {
99await sleep(100); // Small delay to avoid overwhelming Val Town API
100
101console.log(`[Proxy] Making fetch call to ValTown: ${c.req.method} ${targetUrl}`);
untitled-4011README.md5 matches
9โ โโโ index.ts # Main Hono server entry point
10โ โโโ routes/
11โ โโโ proxy.ts # Val Town API proxy routes
12โโโ frontend/
13โ โโโ index.html # Main HTML template
42## Architecture
4344- **Backend**: Hono-based API server with proxy routes to Val Town API
45- **Frontend**: React SPA with client-side routing
46- **Authentication**: API key-based authentication for Val Town services
47- **Styling**: TailwindCSS for responsive design
4852Key changes made:
53- Removed Next.js routing in favor of client-side routing
54- Converted API routes to Hono endpoints
55- Replaced next-auth with API key authentication
56- Updated imports to use Val Town compatible modules
kevinbetVotingPanel.tsx3 matches
22const checkUserVoteStatus = async () => {
23try {
24const response = await fetch(`/api/votes/user/${encodeURIComponent(username)}`);
25const data = await response.json();
26
40
41try {
42const response = await fetch('/api/votes', {
43method: 'POST',
44headers: {
70const refreshVoteResults = async () => {
71try {
72const response = await fetch('/api/votes');
73const data = await response.json();
74
16await runMigrations();
1718// API routes
19app.route("/api/votes", votes);
20app.route("/api/chat", chat);
2122// Serve static files
kevinbetChatRoom.tsx2 matches
18useEffect(() => {
19// Set up Server-Sent Events for real-time chat
20const eventSource = new EventSource('/api/chat/stream');
21eventSourceRef.current = eventSource;
2266
67try {
68const response = await fetch('/api/chat/messages', {
69method: 'POST',
70headers: {