- Fix EventSource is not defined error in queue dashboard - Add browser guards for all EventSource usage - Replace static constants (EventSource.OPEN/CLOSED) with numeric values - Fix setInterval SSR violation in LLM health indicator - Replace $effect anti-pattern with onMount in share page - Add comprehensive SvelteKit SSR best practices documentation - Add SSR audit and testing verification All changes follow SvelteKit best practices and are verified against official documentation. Production build succeeds with no SSR errors. Closes: FixEventSourceSSR See: docs/outcomes/FixEventSourceSSR.md
139 lines
4.1 KiB
Svelte
139 lines
4.1 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/stores';
|
|
import { goto } from '$app/navigation';
|
|
import { onMount } from 'svelte';
|
|
import UrlInputSection from './components/UrlInputSection.svelte';
|
|
|
|
let status = $state('idle');
|
|
let logs = $state<string[]>([]);
|
|
|
|
// URL param parsing for Share Target
|
|
// Instagram typically shares text that contains the URL, so we might need to parse it out
|
|
let sharedText = $derived($page.url.searchParams.get('text') || '');
|
|
let sharedUrl = $derived($page.url.searchParams.get('url') || '');
|
|
|
|
function extractUrl(text: string) {
|
|
const match = text.match(/(https?:\/\/[^\s]+)/);
|
|
return match ? match[0] : null;
|
|
}
|
|
|
|
let targetUrl = $derived(sharedUrl || extractUrl(sharedText));
|
|
|
|
// Track if we've already auto-processed to prevent duplicate processing
|
|
let hasAutoProcessed = $state(false);
|
|
|
|
// Auto-process URL if provided via share target
|
|
// Use onMount instead of $effect for side effects (SvelteKit best practice)
|
|
onMount(() => {
|
|
if (targetUrl && status === 'idle' && !hasAutoProcessed) {
|
|
hasAutoProcessed = true;
|
|
process();
|
|
}
|
|
});
|
|
|
|
async function process(url?: string) {
|
|
const urlToProcess = url || targetUrl;
|
|
if (!urlToProcess) return;
|
|
|
|
status = 'enqueuing';
|
|
logs = [...logs, '🚀 Enqueuing extraction from: ' + urlToProcess];
|
|
|
|
try {
|
|
// Enqueue URL for background processing
|
|
const response = await fetch('/api/queue', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ url: urlToProcess }),
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.message || 'Failed to enqueue URL');
|
|
}
|
|
|
|
const queueItem = await response.json();
|
|
logs = [...logs, `✅ URL enqueued successfully with ID: ${queueItem.id}`];
|
|
logs = [...logs, '🔄 Redirecting to queue dashboard...'];
|
|
|
|
// Small delay to show the success message
|
|
setTimeout(() => {
|
|
// Redirect to homepage (queue dashboard) with the queue item ID highlighted
|
|
goto(`/?highlight=${queueItem.id}`);
|
|
}, 1500);
|
|
|
|
} catch (e) {
|
|
status = 'error';
|
|
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
|
|
logs = [...logs, `❌ Error: ${errorMessage}`];
|
|
}
|
|
}
|
|
|
|
function retry() {
|
|
status = 'idle';
|
|
logs = [...logs, 'Retrying...'];
|
|
process();
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Share to InstaRecipe</title>
|
|
<meta name="description" content="Share Instagram recipes for extraction" />
|
|
</svelte:head>
|
|
|
|
<div class="mx-auto p-6 max-w-4xl">
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold mb-2 text-center">Share to InstaRecipe</h1>
|
|
<p class="text-gray-600 text-center">
|
|
{#if targetUrl}
|
|
Processing your shared recipe...
|
|
{:else}
|
|
Paste an Instagram recipe URL to extract it
|
|
{/if}
|
|
</p>
|
|
</div>
|
|
|
|
{#if !targetUrl}
|
|
<UrlInputSection onProcess={process} />
|
|
{:else}
|
|
<!-- Status indicator for shared URLs -->
|
|
<div class="max-w-2xl mx-auto mb-8">
|
|
<div class="bg-white p-6 rounded-lg shadow-md border">
|
|
<h3 class="font-semibold mb-2">Processing URL:</h3>
|
|
<p class="text-sm text-gray-600 mb-4 break-all">{targetUrl}</p>
|
|
|
|
{#if status === 'enqueuing'}
|
|
<div class="flex items-center space-x-2">
|
|
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
|
|
<span class="text-blue-600">Enqueuing for processing...</span>
|
|
</div>
|
|
{:else if status === 'error'}
|
|
<div class="flex items-center space-x-2 mb-4">
|
|
<span class="text-red-600">❌ Error occurred</span>
|
|
</div>
|
|
<button
|
|
onclick={retry}
|
|
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
|
>
|
|
Retry
|
|
</button>
|
|
{:else}
|
|
<div class="text-green-600">✅ Ready to process</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Log viewer for feedback -->
|
|
{#if logs.length > 0}
|
|
<div class="max-w-2xl mx-auto mt-8">
|
|
<div class="bg-gray-50 p-4 rounded-lg border">
|
|
<h3 class="font-semibold mb-2">Process Log:</h3>
|
|
<div class="space-y-1 text-sm">
|
|
{#each logs as log}
|
|
<div class="text-gray-700">{log}</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div> |