64 });
65 } catch (error) {
66 console.error("Error scraping Zillow:", error);
67 return new Response(JSON.stringify({ error: "Error scraping Zillow listing" }), {
68 status: 500,
69 headers: { "Content-Type": "application/json" },
1// This val creates a form to input a Zillow or Craigslist link, determines the link type,
2// calls the appropriate scraping API, and renders the results in a table.
3// It uses React for the UI, fetch for API calls, and basic string manipulation for link validation.
4
5/** @jsxImportSource https://esm.sh/react */
88 if (request.method === "POST" && new URL(request.url).pathname === "/scrape") {
89 const { link } = await request.json();
90 let scrapingEndpoint;
91
92 if (link.includes("zillow.com")) {
93 scrapingEndpoint = "https://shapedlines-scrapezillowapi.web.val.run?url=";
94 } else if (link.includes("craigslist.org")) {
95 scrapingEndpoint = "https://shapedlines-scrapecraigslistapi.web.val.run?url=";
96 } else {
97 return new Response(JSON.stringify({ error: "Invalid link. Please provide a Zillow or Craigslist link." }), {
102
103 try {
104 const scrapeResponse = await fetch(`${scrapingEndpoint}${encodeURIComponent(link)}`);
105 if (!scrapeResponse.ok) {
106 throw new Error("Failed to scrape data");
110 // Calculate transit time
111 const transitResponse = await fetch(
112 `https://shapedlines-calculatetransitapi.web.val.run?address=${encodeURIComponent(scrapeResult.address)}`,
113 );
114 if (!transitResponse.ok) {
200 /* Anthropic SDK instance – memoised so we don't recreate each render */
201 const anthropic = React.useMemo(() => {
202 if (!config.anthropicApiKey) return null;
203 return new Anthropic({
204 dangerouslyAllowBrowser: true,
205 apiKey: config.anthropicApiKey,
206 baseURL: "https://api.anthropic.com",
207 defaultHeaders: {
208 "anthropic-version": "2023-06-01",
210 },
211 });
212 }, [config.anthropicApiKey]);
213
214 /* Abort helper */
244 const streamMessage = React.useCallback(
245 async (messages: any[]): Promise<{ message: AssistantMsg; stopReason: string }> => {
246 if (!anthropic) throw new Error("API key missing");
247
248 // Use the existing buildBody helper but override messages
268 })) as AsyncIterable<MessageStreamEvent>;
269 } catch (err: any) {
270 console.error("Failed to call Anthropic API", err);
271 throw err;
272 }
338 const send = React.useCallback(
339 async (history: Message[], userText: string): Promise<Message[]> => {
340 if (!anthropic) throw new Error("API key missing");
341 if (status !== "idle") throw new Error("Stream already in progress");
342
954 const url = new URL(req.url);
955
956 // API endpoints
957 if (url.pathname === '/api/results') {
958 try {
959 const limit = parseInt(url.searchParams.get('limit') || '50');
986 }
987
988 if (url.pathname === '/api/stats') {
989 try {
990 const stats = await getQuizStats();
128 model: string;
129 max_tokens: number;
130 anthropic_api_key: string;
131 mcp_servers?: Array<{
132 type: string;
4
5 const response = await fetch(
6 `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true`,
7 );
8
104});
105
106// API endpoint for JSON data
107app.get("/api/data", async c => {
108 try {
109 const submissionsResult = await sqlite.execute(`
133
134// Export individual submission data
135app.get("/api/export", async c => {
136 try {
137 const submissionsResult = await sqlite.execute(`
159
160// Delete individual submission
161app.delete("/api/submissions/:id", async c => {
162 try {
163 const id = c.req.param('id');
195
196// Refresh data endpoint (for real-time updates)
197app.get("/api/refresh", async c => {
198 try {
199 // Force refresh by redirecting back to main page
205
206// Debug endpoint to check database contents
207app.get("/api/debug", async c => {
208 try {
209 // Get all tables
527 <p>Total Submissions: <strong>${total}</strong></p>
528 <p style="font-size: 0.9rem; opacity: 0.8;">
529 <a href="/api/debug" style="color: rgba(255,255,255,0.8); text-decoration: none;">🔍 Debug Database</a>
530 </p>
531 </div>
546 <div class="table-actions">
547 <button onclick="refreshData()" class="refresh-btn">🔄 Refresh</button>
548 <a href="/api/export" class="export-btn">📥 Export CSV</a>
549 </div>
550 </div>
629
630 try {
631 const response = await fetch(\`/api/submissions/\${id}\`, {
632 method: 'DELETE',
633 headers: {
93- SQLite database with optimized schema and indexing
94- Enhanced email system with HTML/text templates
95- API endpoints for quiz submission and analytics
96- Static file serving for all frontend assets
97- Mobile-responsive design
106
107### Backend (Hono)
108- RESTful API with proper error handling
109- SQLite database with structured schema and indexes
110- Email automation with professional HTML templates
238
239### Environment Variables
240- `VAL_TOWN_API_KEY`: Automatically provided by Val Town
241- Optional overrides:
242 - `ADMIN_EMAIL`: Admin notification email
269- **Advanced Analytics**: User behavior tracking
270- **Social Features**: Community building around archetypes
271- **API Integration**: Connect with event platforms
272
273### Technical Improvements
60});
61
62// API Routes
63app.post("/api/submit-quiz", async c => {
64 try {
65 const body = await c.req.json() as QuizSubmission;
159
160// Test email endpoint for debugging
161app.get("/api/test-email", async c => {
162 try {
163 console.log("Testing email functionality...");
182
183// Debug endpoint for troubleshooting
184app.get("/api/debug-config", async c => {
185 try {
186 return c.json({
198
199// Test insert endpoint for debugging
200app.get("/api/test-insert", async c => {
201 try {
202 console.log("Testing database insert...");
242
243// Enhanced debug endpoint to check table structure and data
244app.get("/api/debug-table", async c => {
245 try {
246 // Get table schema
286
287// Get analytics endpoint with proper error handling
288app.get("/api/analytics", async c => {
289 try {
290 const stats = await sqlite.execute(`
11
12 <!-- Fonts -->
13 <link rel="preconnect" href="https://fonts.googleapis.com">
14 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15 <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Playfair+Display:wght@700;800&display=swap" rel="stylesheet">
16
17 <!-- External Libraries -->