🐞 fix: Wire up filtering and count after switch to client-side pull for prompts
This commit is contained in:
108
app/public/scripts/fetch-prompts.js
Normal file
108
app/public/scripts/fetch-prompts.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
|
||||||
|
|
||||||
|
const env = document.getElementById('supabase-env');
|
||||||
|
const supabaseUrl = env.dataset.url;
|
||||||
|
const supabaseKey = env.dataset.key;
|
||||||
|
|
||||||
|
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||||
|
|
||||||
|
const promptContainer = document.getElementById('prompt-container');
|
||||||
|
const errorBox = document.getElementById('prompt-error');
|
||||||
|
|
||||||
|
function renderPromptCard(prompt) {
|
||||||
|
const tagSpans = (prompt.tags ?? []).map(tag =>
|
||||||
|
`<span class="text-sm bg-gray-200 text-gray-800 px-2 py-1 pt-0 rounded">${tag}</span>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="prompt-card border border-gray-400 rounded p-4 bg-gray-700 text-gray-200 shadow-sm flex flex-col gap-2 min-h-[12rem]"
|
||||||
|
data-type="${prompt.type}" data-tags="${(prompt.tags ?? []).join(',')}">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-xl font-semibold">${prompt.title}</h3>
|
||||||
|
<span class="text-sm font-medium px-2 py-1 rounded ${
|
||||||
|
prompt.type === 'System'
|
||||||
|
? 'bg-blue-100 text-blue-700'
|
||||||
|
: 'bg-green-100 text-green-700'
|
||||||
|
}">${prompt.type}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-md">${prompt.notes ?? ''}</p>
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">${tagSpans}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFiltersFromTags(prompts) {
|
||||||
|
const tagSet = new Set(prompts.flatMap(p => p.tags ?? []));
|
||||||
|
const tagContainer = document.querySelector('#filters fieldset > div');
|
||||||
|
if (!tagContainer) return;
|
||||||
|
|
||||||
|
tagContainer.innerHTML = '';
|
||||||
|
|
||||||
|
tagSet.forEach(tag => {
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.className = "relative inline-flex items-center cursor-pointer";
|
||||||
|
label.innerHTML = `
|
||||||
|
<input type="checkbox" name="tag" value="${tag}" class="sr-only peer">
|
||||||
|
<span class="px-3 py-1 pt-0 rounded-full border border-gray-300 peer-checked:bg-blue-600 peer-checked:text-white hover:bg-blue-600 hover:text-white transition-colors duration-300 text-sm">${tag}</span>
|
||||||
|
`;
|
||||||
|
tagContainer.appendChild(label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterCards() {
|
||||||
|
const form = document.getElementById('filter-form');
|
||||||
|
const cards = document.querySelectorAll('.prompt-card');
|
||||||
|
const params = new URLSearchParams(new FormData(form));
|
||||||
|
const type = params.get('type');
|
||||||
|
const query = params.get('q')?.toLowerCase() || '';
|
||||||
|
const tagParams = params.getAll('tag');
|
||||||
|
|
||||||
|
let visibleCount = 0;
|
||||||
|
|
||||||
|
cards.forEach(card => {
|
||||||
|
const cardType = card.dataset.type;
|
||||||
|
const cardTags = card.dataset.tags.split(',');
|
||||||
|
const cardText = card.innerText.toLowerCase();
|
||||||
|
|
||||||
|
const matchesType = !type || type === cardType;
|
||||||
|
const matchesTags = tagParams.length === 0 || tagParams.some(t => cardTags.includes(t));
|
||||||
|
const matchesSearch = !query || cardText.includes(query);
|
||||||
|
|
||||||
|
const show = matchesType && matchesTags && matchesSearch;
|
||||||
|
card.style.display = show ? 'block' : 'none';
|
||||||
|
if (show) visibleCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const countLabel = document.getElementById('prompt-count');
|
||||||
|
if (countLabel) {
|
||||||
|
countLabel.textContent = visibleCount === 1
|
||||||
|
? '1 prompt shown'
|
||||||
|
: `${visibleCount} prompts shown`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPrompts() {
|
||||||
|
const { data: prompts, error } = await supabase.from('prompts').select('*').order('title');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
promptContainer.innerHTML = '';
|
||||||
|
errorBox.textContent = error.message;
|
||||||
|
errorBox.classList.remove('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promptContainer.innerHTML = prompts.map(renderPromptCard).join('');
|
||||||
|
updateFiltersFromTags(prompts);
|
||||||
|
filterCards(); // initial run
|
||||||
|
|
||||||
|
const form = document.getElementById('filter-form');
|
||||||
|
form?.addEventListener('input', filterCards);
|
||||||
|
|
||||||
|
const clearBtn = document.getElementById('clear-filters');
|
||||||
|
clearBtn?.addEventListener('click', () => {
|
||||||
|
form.reset();
|
||||||
|
filterCards();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadPrompts);
|
||||||
@@ -1,35 +1,37 @@
|
|||||||
---
|
---
|
||||||
|
// index.astro
|
||||||
import MainLayout from '../layouts/MainLayout.astro';
|
import MainLayout from '../layouts/MainLayout.astro';
|
||||||
import SearchBar from '../components/SearchBar.astro';
|
import SearchBar from '../components/SearchBar.astro';
|
||||||
import FilteredPromptList from '../components/FilteredPromptList.astro';
|
|
||||||
import { supabase } from '../lib/supabase';
|
|
||||||
|
|
||||||
const { data: prompts, error } = await supabase
|
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
|
||||||
.from('prompts')
|
const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
|
||||||
.select('*')
|
|
||||||
.order('title', { ascending: true });
|
|
||||||
|
|
||||||
const allTags = Array.from(
|
|
||||||
new Set(
|
|
||||||
prompts?.flatMap((p) => p.tags ?? [])
|
|
||||||
)
|
|
||||||
).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout page="Home">
|
<MainLayout page="Home">
|
||||||
|
<div
|
||||||
|
id="supabase-env"
|
||||||
|
data-url={supabaseUrl}
|
||||||
|
data-key={supabaseKey}
|
||||||
|
hidden
|
||||||
|
></div>
|
||||||
|
|
||||||
<header class="border-b p-4">
|
<header class="border-b p-4">
|
||||||
<h1 class="text-2xl font-bold"><a href="/">Prompt Catalog</a></h1>
|
<h1 class="text-2xl font-bold"><a href="/">Prompt Catalog</a></h1>
|
||||||
<p class="text-sm mt-1">Save and explore AI prompts</p>
|
<p class="text-sm mt-1">Save and explore AI prompts</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex flex-col lg:flex-row">
|
<main class="flex flex-col lg:flex-row">
|
||||||
<SearchBar allTags={allTags} />
|
<SearchBar allTags={[]} /> <!-- leave empty, we'll hydrate it in JS -->
|
||||||
|
|
||||||
<div class="border-b lg:border-l flex-1 flex flex-col overflow-hidden">
|
<div class="border-b lg:border-b-0 lg:border-l flex-1 flex flex-col overflow-hidden">
|
||||||
<div class="flex-1 overflow-y-auto px-4 lg:px-6 pt-2 pb-4">
|
<div class="flex-1 overflow-y-auto px-4 lg:px-6 pt-2 pb-4">
|
||||||
{error
|
<div id="prompt-error" class="text-red-500 hidden"></div>
|
||||||
? <p class="text-red-500">Supabase error: {error.message}</p>
|
<div class="border-b mb-4 flex justify-between items-center">
|
||||||
: <FilteredPromptList prompts={prompts} />}
|
<h2 id="prompt-count" class="text-xl font-semibold text-gray-300 mb-2"></h2>
|
||||||
|
<a href="#filters" class="block lg:hidden bg-blue-600 text-white px-2 py-0 pb-1 mb-2 rounded cursor-pointer hover:bg-blue-700 transition-colors duration-300">Filters ↓</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="prompt-container" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">Loading prompts…</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -37,4 +39,6 @@ const allTags = Array.from(
|
|||||||
<footer class="border-t p-4 text-center text-sm">
|
<footer class="border-t p-4 text-center text-sm">
|
||||||
© {new Date().getFullYear()} Prompt Catalog
|
© {new Date().getFullYear()} Prompt Catalog
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script type="module" src="/scripts/fetch-prompts.js"></script>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
// app/src/scripts/fetch-prompts.js
|
|
||||||
import { createClient } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
const supabase = createClient(import.meta.env.PUBLIC_SUPABASE_URL, import.meta.env.PUBLIC_SUPABASE_ANON_KEY);
|
|
||||||
|
|
||||||
async function loadPrompts() {
|
|
||||||
const { data, error } = await supabase.from('prompts').select('*');
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now render them in the DOM
|
|
||||||
const container = document.getElementById('prompt-list');
|
|
||||||
container.innerHTML = data.map(prompt => `<li>${prompt.title}</li>`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', loadPrompts);
|
|
||||||
Reference in New Issue
Block a user