🐞 fix: Update templates to use MainLayout and set page title via parameter

This commit is contained in:
Keith Solomon
2025-07-27 11:07:16 -05:00
parent 3ad0762e17
commit a28ac0b16a
4 changed files with 355 additions and 366 deletions

View File

@@ -2,14 +2,14 @@
// MainLayout.astro // MainLayout.astro
import "../styles/global.css"; import "../styles/global.css";
const { children } = Astro.props; const page = Astro.props.page;
--- ---
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Prompt Catalog</title> <title>Prompt Catalog - {page}</title>
</head> </head>
<body class="font-sans antialiased bg-gray-800 text-gray-100"> <body class="font-sans antialiased bg-gray-800 text-gray-100">

View File

@@ -1,279 +1,274 @@
--- ---
import "../styles/global.css"; import MainLayout from '../layouts/MainLayout.astro';
import Sidebar from "../components/SidebarAdd.astro"; import Sidebar from "../components/SidebarAdd.astro";
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL; const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY; const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
--- ---
<html> <MainLayout page="Add Prompt">
<head> <div
<title>Prompt Catalog - Add New Prompt</title> id="supabase-env"
</head> data-url={supabaseUrl}
data-key={supabaseKey}
hidden
></div>
<body class="font-sans antialiased bg-gray-800 text-gray-100"> <header class="border-b p-4">
<div <h1 class="text-2xl font-bold"><a href="/">Prompt Catalog - Add New Prompt</a></h1>
id="supabase-env" <p class="text-sm mt-1">Add or import new AI prompts to the catalog</p>
data-url={supabaseUrl} </header>
data-key={supabaseKey}
hidden
></div>
<header class="border-b p-4"> <main class="flex">
<h1 class="text-2xl font-bold">Prompt Catalog - Add New Prompt</h1> <Sidebar />
<p class="text-sm mt-1">Add or import new AI prompts to the catalog</p>
</header>
<main class="flex"> <div class="border-l flex-1 flex flex-col overflow-hidden">
<Sidebar /> <div class="flex-1 overflow-y-auto p-4">
<div id="success" class="bg-green-100 text-green-700 p-4 rounded mb-4 hidden">
<div class="border-l flex-1 flex flex-col overflow-hidden"> Prompt added successfully!
<div class="flex-1 overflow-y-auto p-4">
<div id="success" class="bg-green-100 text-green-700 p-4 rounded mb-4 hidden">
Prompt added successfully!
</div>
<div id="error" class="bg-red-100 text-red-700 p-4 rounded mb-4 hidden"></div>
<form id="add-form" class="space-y-4">
<div>
<label for="title" class="block text-md font-semibold mb-1">Title<span class="text-red-600">*</span></label>
<input name="title" id="title" required class="border p-2 w-full rounded" />
<input type="hidden" name="slug" id="slug" />
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<label for="type" class="block text-md font-semibold mb-1">Type<span class="text-red-600">*</span></label>
<select name="type" id="type" required class="border border-gray-100 p-2 rounded w-full bg-gray-800">
<option value="System">System</option>
<option value="Task">Task</option>
</select>
</div>
<div>
<label for="tags" class="block text-md font-semibold mb-1">Tags (comma-separated)</label>
<input name="tags" id="tags" class="border p-2 pt-1 w-full rounded" />
</div>
</div>
<div>
<label for="description" class="block text-md font-semibold mb-1">Prompt<span class="text-red-600">*</span></label>
<textarea name="description" id="description" rows="6" required class="border p-2 w-full rounded"></textarea>
</div>
<div>
<label for="notes" class="block text-md font-semibold mb-1">Description<span class="text-red-600">*</span></label>
<textarea name="notes" id="notes" rows="3" required class="border p-2 w-full rounded"></textarea>
</div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors duration-300 cursor-pointer">
Add Prompt
</button>
<hr class="my-6 border-gray-600" />
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<h2 class="text-lg font-semibold mb-2">Import Prompts</h2>
<p class="text-sm text-gray-400 mb-2">Upload a JSON file to import prompts.</p>
<label for="importFile" class="block text-md font-semibold mb-1">Import Prompts from JSON</label>
<input type="file" id="importFile" accept="application/json" class="border p-2 w-full rounded bg-gray-800 text-white" />
<button type="button" id="importBtn" class="mt-2 bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition-colors duration-300 cursor-pointer">
Import Prompts
</button>
</div>
<div id="export-section">
<h2 class="text-lg font-semibold mb-2">Export Prompts</h2>
<p class="text-sm text-gray-400 mb-2">Select prompts to export, or leave all unchecked to export everything.</p>
<label class="block text-md font-semibold mb-1">Export Prompts to JSON</label>
<div id="prompt-list" class="max-h-64 overflow-y-auto mb-4 border py-1 pb-2 px-3 rounded flex flex-wrap gap-x-4 gap-y-1"></div>
<button type="button" id="exportBtn" class="bg-yellow-600 text-white px-4 py-2 rounded hover:bg-yellow-700 transition-colors duration-300 cursor-pointer">
Export All Prompts
</button>
</div>
</div>
</form>
</div> </div>
<div id="error" class="bg-red-100 text-red-700 p-4 rounded mb-4 hidden"></div>
<form id="add-form" class="space-y-4">
<div>
<label for="title" class="block text-md font-semibold mb-1">Title<span class="text-red-600">*</span></label>
<input name="title" id="title" required class="border p-2 w-full rounded" />
<input type="hidden" name="slug" id="slug" />
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<label for="type" class="block text-md font-semibold mb-1">Type<span class="text-red-600">*</span></label>
<select name="type" id="type" required class="border border-gray-100 p-2 rounded w-full bg-gray-800">
<option value="System">System</option>
<option value="Task">Task</option>
</select>
</div>
<div>
<label for="tags" class="block text-md font-semibold mb-1">Tags (comma-separated)</label>
<input name="tags" id="tags" class="border p-2 pt-1 w-full rounded" />
</div>
</div>
<div>
<label for="description" class="block text-md font-semibold mb-1">Prompt<span class="text-red-600">*</span></label>
<textarea name="description" id="description" rows="6" required class="border p-2 w-full rounded"></textarea>
</div>
<div>
<label for="notes" class="block text-md font-semibold mb-1">Description<span class="text-red-600">*</span></label>
<textarea name="notes" id="notes" rows="3" required class="border p-2 w-full rounded"></textarea>
</div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors duration-300 cursor-pointer">
Add Prompt
</button>
<hr class="my-6 border-gray-600" />
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<h2 class="text-lg font-semibold mb-2">Import Prompts</h2>
<p class="text-sm text-gray-400 mb-2">Upload a JSON file to import prompts.</p>
<label for="importFile" class="block text-md font-semibold mb-1">Import Prompts from JSON</label>
<input type="file" id="importFile" accept="application/json" class="border p-2 w-full rounded bg-gray-800 text-white" />
<button type="button" id="importBtn" class="mt-2 bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 transition-colors duration-300 cursor-pointer">
Import Prompts
</button>
</div>
<div id="export-section">
<h2 class="text-lg font-semibold mb-2">Export Prompts</h2>
<p class="text-sm text-gray-400 mb-2">Select prompts to export, or leave all unchecked to export everything.</p>
<label class="block text-md font-semibold mb-1">Export Prompts to JSON</label>
<div id="prompt-list" class="max-h-64 overflow-y-auto mb-4 border py-1 pb-2 px-3 rounded flex flex-wrap gap-x-4 gap-y-1"></div>
<button type="button" id="exportBtn" class="bg-yellow-600 text-white px-4 py-2 rounded hover:bg-yellow-700 transition-colors duration-300 cursor-pointer">
Export All Prompts
</button>
</div>
</div>
</form>
</div> </div>
</main> </div>
</main>
<footer class="border-t p-4 text-center text-sm"> <footer class="border-t p-4 text-center text-sm">
&copy; {new Date().getFullYear()} Prompt Catalog &copy; {new Date().getFullYear()} Prompt Catalog
</footer> </footer>
<script type="module"> <script type="module">
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm'; import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
const SUPABASE_URL = document.getElementById('supabase-env').dataset.url; const SUPABASE_URL = document.getElementById('supabase-env').dataset.url;
const SUPABASE_ANON_KEY = document.getElementById('supabase-env').dataset.key; const SUPABASE_ANON_KEY = document.getElementById('supabase-env').dataset.key;
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const form = document.getElementById('add-form'); const form = document.getElementById('add-form');
const importBtn = document.getElementById('importBtn'); const importBtn = document.getElementById('importBtn');
const exportBtn = document.getElementById('exportBtn'); const exportBtn = document.getElementById('exportBtn');
const successBox = document.getElementById('success'); const successBox = document.getElementById('success');
const errorBox = document.getElementById('error'); const errorBox = document.getElementById('error');
async function loadPromptCheckboxes() { async function loadPromptCheckboxes() {
const promptList = document.getElementById('prompt-list'); const promptList = document.getElementById('prompt-list');
const { data, error } = await supabase.from('prompts').select('id, title'); const { data, error } = await supabase.from('prompts').select('id, title');
if (error) { if (error) {
promptList.innerHTML = `<p class="text-red-400">Failed to load prompts: ${error.message}</p>`; promptList.innerHTML = `<p class="text-red-400">Failed to load prompts: ${error.message}</p>`;
return; return;
}
promptList.innerHTML = data
.map(
prompt => `
<label class="block mb-0">
<input type="checkbox" value="${prompt.id}" class="mr-0" />
${prompt.title}
</label>`
)
.join('');
// After rendering, attach change listeners to checkboxes
const checkboxes = Array.from(document.querySelectorAll('#prompt-list input[type="checkbox"]'));
checkboxes.forEach(cb => cb.addEventListener('change', updateExportBtnText));
updateExportBtnText();
} }
// Load checkboxes on DOM load promptList.innerHTML = data
loadPromptCheckboxes(); .map(
prompt => `
<label class="block mb-0">
<input type="checkbox" value="${prompt.id}" class="mr-0" />
${prompt.title}
</label>`
)
.join('');
function updateExportBtnText() { // After rendering, attach change listeners to checkboxes
const checkboxes = Array.from(document.querySelectorAll('#prompt-list input[type="checkbox"]')); const checkboxes = Array.from(document.querySelectorAll('#prompt-list input[type="checkbox"]'));
const checkedCount = checkboxes.filter(cb => cb.checked).length; checkboxes.forEach(cb => cb.addEventListener('change', updateExportBtnText));
updateExportBtnText();
}
exportBtn.textContent = checkedCount > 0 ? 'Export Selected Prompts' : 'Export All Prompts'; // Load checkboxes on DOM load
} loadPromptCheckboxes();
document.addEventListener('DOMContentLoaded', () => { function updateExportBtnText() {
document.getElementById('title').addEventListener('input', (e) => { const checkboxes = Array.from(document.querySelectorAll('#prompt-list input[type="checkbox"]'));
const value = e.target.value const checkedCount = checkboxes.filter(cb => cb.checked).length;
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.trim();
document.getElementById('slug').value = value;
});
form.addEventListener('submit', async (e) => { exportBtn.textContent = checkedCount > 0 ? 'Export Selected Prompts' : 'Export All Prompts';
e.preventDefault(); }
successBox.style.display = 'none'; document.addEventListener('DOMContentLoaded', () => {
errorBox.style.display = 'none'; document.getElementById('title').addEventListener('input', (e) => {
const value = e.target.value
const formData = new FormData(form); .toLowerCase()
const payload = { .replace(/[^\w\s-]/g, '')
title: formData.get('title'), .replace(/\s+/g, '-')
slug: formData.get('slug'), .trim();
type: formData.get('type'), document.getElementById('slug').value = value;
description: formData.get('description'),
tags: formData.get('tags')?.split(',').map(t => t.trim()).filter(Boolean),
notes: formData.get('notes')
};
const { error } = await supabase.from('prompts').insert([payload]);
if (error) {
errorBox.innerText = error.message;
errorBox.style.display = 'block';
} else {
successBox.style.display = 'block';
form.reset();
}
});
}); });
importBtn.addEventListener('click', async () => { form.addEventListener('submit', async (e) => {
const fileInput = document.getElementById('importFile'); e.preventDefault();
const file = fileInput.files[0];
if (!file) {
alert('Please select a JSON file.');
return;
}
const successBox = document.getElementById('success');
const errorBox = document.getElementById('error');
try {
const text = await file.text();
const prompts = JSON.parse(text);
const formatted = prompts.map(p => ({
type: p.type.charAt(0).toUpperCase() + p.type.slice(1), // normalize to "System"/"Task"
title: p.title,
description: p.description,
tags: p.tags.replace(/[{}"]/g, '').split(',').map(t => t.trim()), // parse tag string
notes: p.notes,
slug: p.title
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.trim()
}));
const { error } = await supabase.from('prompts').insert(formatted);
if (error) {
errorBox.innerText = `Import failed: ${error.message}`;
errorBox.style.display = 'block';
} else {
successBox.innerText = 'Prompts imported successfully!';
successBox.style.display = 'block';
fileInput.value = '';
}
} catch (err) {
errorBox.innerText = `Error: ${err.message}`;
errorBox.style.display = 'block';
}
});
exportBtn.addEventListener('click', async () => {
const selectedIds = checkboxes.filter(cb => cb.checked).map(cb => cb.value);
const { data, error } = selectedIds.length > 0
? await supabase.from('prompts').select('*').in('id', selectedIds)
: await supabase.from('prompts').select('*');
successBox.style.display = 'none'; successBox.style.display = 'none';
errorBox.style.display = 'none'; errorBox.style.display = 'none';
if (error) { const formData = new FormData(form);
errorBox.innerText = `Export failed: ${error.message}`; const payload = {
errorBox.style.display = 'block'; title: formData.get('title'),
return; slug: formData.get('slug'),
} type: formData.get('type'),
description: formData.get('description'),
tags: formData.get('tags')?.split(',').map(t => t.trim()).filter(Boolean),
notes: formData.get('notes')
};
const output = data.map(p => ({ const { error } = await supabase.from('prompts').insert([payload]);
id: p.id,
type: p.type.toLowerCase(), if (error) {
errorBox.innerText = error.message;
errorBox.style.display = 'block';
} else {
successBox.style.display = 'block';
form.reset();
}
});
});
importBtn.addEventListener('click', async () => {
const fileInput = document.getElementById('importFile');
const file = fileInput.files[0];
if (!file) {
alert('Please select a JSON file.');
return;
}
const successBox = document.getElementById('success');
const errorBox = document.getElementById('error');
try {
const text = await file.text();
const prompts = JSON.parse(text);
const formatted = prompts.map(p => ({
type: p.type.charAt(0).toUpperCase() + p.type.slice(1), // normalize to "System"/"Task"
title: p.title, title: p.title,
description: p.description, description: p.description,
tags: `{${(p.tags || []).join(',')}}`, tags: p.tags.replace(/[{}"]/g, '').split(',').map(t => t.trim()), // parse tag string
createdat: p.createdat, notes: p.notes,
notes: p.notes || '' slug: p.title
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.trim()
})); }));
const blob = new Blob([JSON.stringify(output, null, 2)], { const { error } = await supabase.from('prompts').insert(formatted);
type: 'application/json'
});
const url = URL.createObjectURL(blob); if (error) {
const link = document.createElement('a'); errorBox.innerText = `Import failed: ${error.message}`;
link.href = url; errorBox.style.display = 'block';
link.download = selectedIds.length > 0 ? 'selected-prompts.json' : 'all-prompts.json'; } else {
link.click(); successBox.innerText = 'Prompts imported successfully!';
URL.revokeObjectURL(url); successBox.style.display = 'block';
fileInput.value = '';
}
} catch (err) {
errorBox.innerText = `Error: ${err.message}`;
errorBox.style.display = 'block';
}
});
exportBtn.addEventListener('click', async () => {
const checkboxes = Array.from(document.querySelectorAll('#prompt-list input[type="checkbox"]'));
const selectedIds = checkboxes.filter(cb => cb.checked).map(cb => cb.value);
const { data, error } = selectedIds.length > 0
? await supabase.from('prompts').select('*').in('id', selectedIds)
: await supabase.from('prompts').select('*');
successBox.style.display = 'none';
errorBox.style.display = 'none';
if (error) {
errorBox.innerText = `Export failed: ${error.message}`;
errorBox.style.display = 'block';
return;
}
const output = data.map(p => ({
id: p.id,
type: p.type.toLowerCase(),
title: p.title,
description: p.description,
tags: `{${(p.tags || []).join(',')}}`,
createdat: p.createdat,
notes: p.notes || ''
}));
const blob = new Blob([JSON.stringify(output, null, 2)], {
type: 'application/json'
}); });
</script>
</body> const url = URL.createObjectURL(blob);
</html> const link = document.createElement('a');
link.href = url;
link.download = selectedIds.length > 0 ? 'selected-prompts.json' : 'all-prompts.json';
link.click();
URL.revokeObjectURL(url);
});
</script>
</MainLayout>

View File

@@ -1,160 +1,154 @@
--- ---
import "../styles/global.css"; import MainLayout from '../layouts/MainLayout.astro';
import Sidebar from "../components/SidebarEdit.astro"; import Sidebar from "../components/SidebarEdit.astro";
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL; const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY; const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
--- ---
<html lang="en"> <MainLayout page="Edit Prompt">
<head> <div
<title>Prompt Catalog - Edit Prompt</title>
</head>
<body class="font-sans antialiased bg-gray-800 text-gray-100">
<div
id="supabase-env" id="supabase-env"
data-url={supabaseUrl} data-url={supabaseUrl}
data-key={supabaseKey} data-key={supabaseKey}
hidden hidden
></div> ></div>
<header class="border-b p-4"> <header class="border-b p-4">
<h1 class="text-2xl font-bold">Prompt Catalog - Add New Prompt</h1> <h1 class="text-2xl font-bold"><a href="/">Prompt Catalog - Edit Prompt</a></h1>
<p class="text-sm mt-1">Add a new AI prompt to the catalog</p> <p class="text-sm mt-1">Edit an existing AI prompt in the catalog</p>
</header> </header>
<main class="flex"> <main class="flex">
<Sidebar /> <Sidebar />
<div class="border-l flex-1 flex flex-col overflow-hidden"> <div class="border-l flex-1 flex flex-col overflow-hidden">
<main class="flex-1 overflow-y-auto p-4"> <main class="flex-1 overflow-y-auto p-4">
<div id="edit-root">Loading...</div> <div id="edit-root">Loading...</div>
</main> </main>
</div> </div>
</main> </main>
<footer class="border-t p-4 text-center text-sm"> <footer class="border-t p-4 text-center text-sm">
&copy; {new Date().getFullYear()} Prompt Catalog &copy; {new Date().getFullYear()} Prompt Catalog
</footer> </footer>
<script type="module"> <script type="module">
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm'; import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm';
const env = document.getElementById('supabase-env').dataset; const env = document.getElementById('supabase-env').dataset;
const supabase = createClient(env.url, env.key); const supabase = createClient(env.url, env.key);
const slug = new URLSearchParams(window.location.search).get('slug'); const slug = new URLSearchParams(window.location.search).get('slug');
const root = document.getElementById('edit-root'); const root = document.getElementById('edit-root');
if (!slug) { if (!slug) {
root.innerHTML = `<p class="text-red-600 font-medium">❌ No slug provided.</p>`; root.innerHTML = `<p class="text-red-600 font-medium">❌ No slug provided.</p>`;
} else {
const { data, error } = await supabase
.from('prompts')
.select('*')
.eq('slug', slug)
.limit(1);
const prompt = data?.[0];
if (error || !prompt) {
root.innerHTML = `<p class="text-red-600 font-medium">❌ Prompt not found or error loading it.</p>`;
} else { } else {
const { data, error } = await supabase root.innerHTML = `
.from('prompts') <form id="edit-form" class="space-y-4">
.select('*') <input type="hidden" name="slug" value="${prompt.slug}" />
.eq('slug', slug)
.limit(1);
const prompt = data?.[0]; <div>
<label for="title" class="block text-md font-semibold mb-1">Title</label>
if (error || !prompt) { <input name="title" id="title" value="${prompt.title}" required class="border p-2 w-full rounded" />
root.innerHTML = `<p class="text-red-600 font-medium">❌ Prompt not found or error loading it.</p>`; </div>
} else {
root.innerHTML = `
<form id="edit-form" class="space-y-4">
<input type="hidden" name="slug" value="${prompt.slug}" />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label for="title" class="block text-md font-semibold mb-1">Title</label> <label for="type" class="block text-md font-semibold mb-1">Type</label>
<input name="title" id="title" value="${prompt.title}" required class="border p-2 w-full rounded" /> <select name="type" id="type" required class="border p-2 w-full rounded">
</div> <option value="System" ${prompt.type === 'System' ? 'selected' : ''}>System</option>
<option value="Task" ${prompt.type === 'Task' ? 'selected' : ''}>Task</option>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> </select>
<div>
<label for="type" class="block text-md font-semibold mb-1">Type</label>
<select name="type" id="type" required class="border p-2 w-full rounded">
<option value="System" ${prompt.type === 'System' ? 'selected' : ''}>System</option>
<option value="Task" ${prompt.type === 'Task' ? 'selected' : ''}>Task</option>
</select>
</div>
<div>
<label for="tags" class="block text-md font-semibold mb-1">Tags (comma-separated)</label>
<input name="tags" id="tags" value="${(prompt.tags ?? []).join(', ')}" class="border p-2 pt-1 w-full rounded" />
</div>
</div> </div>
<div> <div>
<label for="description" class="block text-md font-semibold mb-1">Prompt</label> <label for="tags" class="block text-md font-semibold mb-1">Tags (comma-separated)</label>
<textarea name="description" id="description" rows="6" required class="border p-2 w-full rounded">${prompt.description}</textarea> <input name="tags" id="tags" value="${(prompt.tags ?? []).join(', ')}" class="border p-2 pt-1 w-full rounded" />
</div> </div>
</div>
<div> <div>
<label for="notes" class="block text-md font-semibold mb-1">Description</label> <label for="description" class="block text-md font-semibold mb-1">Prompt</label>
<textarea name="notes" id="notes" rows="3" class="border p-2 w-full rounded">${prompt.notes ?? ''}</textarea> <textarea name="description" id="description" rows="6" required class="border p-2 w-full rounded">${prompt.description}</textarea>
</div> </div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors duration-300 cursor-pointer"> <div>
Save Changes <label for="notes" class="block text-md font-semibold mb-1">Description</label>
<textarea name="notes" id="notes" rows="3" class="border p-2 w-full rounded">${prompt.notes ?? ''}</textarea>
</div>
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition-colors duration-300 cursor-pointer">
Save Changes
</button>
<div class="flex justify-between items-center pt-4 border-t mt-6">
<span class="text-lg text-red-400 font-bold">Be careful! Deletion is permanent.</span>
<button
id="delete-btn"
type="button"
class="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition-colors duration-300 cursor-pointer"
>
Delete Prompt
</button> </button>
</div>
</form>
<div class="flex justify-between items-center pt-4 border-t mt-6"> <div id="success" class="text-green-600 mt-4 hidden">Prompt Updated.</div>
<span class="text-lg text-red-400 font-bold">Be careful! Deletion is permanent.</span> <div id="error" class="text-red-600 mt-4 hidden"></div>
<button `;
id="delete-btn"
type="button"
class="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition-colors duration-300 cursor-pointer"
>
Delete Prompt
</button>
</div>
</form>
<div id="success" class="text-green-600 mt-4 hidden">Prompt Updated.</div> document.getElementById('edit-form').addEventListener('submit', async (e) => {
<div id="error" class="text-red-600 mt-4 hidden"></div> e.preventDefault();
`; const formData = new FormData(e.target);
const payload = {
title: formData.get('title'),
type: formData.get('type'),
description: formData.get('description'),
tags: formData.get('tags')?.split(',').map(t => t.trim()).filter(Boolean),
notes: formData.get('notes')
};
document.getElementById('edit-form').addEventListener('submit', async (e) => { const { error: updateError } = await supabase
e.preventDefault(); .from('prompts')
const formData = new FormData(e.target); .update(payload)
const payload = { .eq('slug', slug);
title: formData.get('title'),
type: formData.get('type'),
description: formData.get('description'),
tags: formData.get('tags')?.split(',').map(t => t.trim()).filter(Boolean),
notes: formData.get('notes')
};
const { error: updateError } = await supabase document.getElementById('success').style.display = updateError ? 'none' : 'block';
.from('prompts') document.getElementById('error').innerText = updateError?.message || '';
.update(payload) document.getElementById('error').style.display = updateError ? 'block' : 'none';
.eq('slug', slug); });
document.getElementById('success').style.display = updateError ? 'none' : 'block'; document.getElementById('delete-btn').addEventListener('click', async () => {
document.getElementById('error').innerText = updateError?.message || ''; const confirmed = window.confirm("Are you sure you want to delete this prompt? This cannot be undone.");
document.getElementById('error').style.display = updateError ? 'block' : 'none';
});
document.getElementById('delete-btn').addEventListener('click', async () => { if (!confirmed) return;
const confirmed = window.confirm("Are you sure you want to delete this prompt? This cannot be undone.");
if (!confirmed) return; const { error: deleteError } = await supabase
.from('prompts')
.delete()
.eq('slug', slug);
const { error: deleteError } = await supabase if (deleteError) {
.from('prompts') document.getElementById('error').innerText = deleteError.message;
.delete() document.getElementById('error').style.display = 'block';
.eq('slug', slug); } else {
window.location.href = '/';
if (deleteError) { }
document.getElementById('error').innerText = deleteError.message; });
document.getElementById('error').style.display = 'block';
} else {
window.location.href = '/';
}
});
}
} }
</script> }
</body> </script>
</html> </MainLayout>

View File

@@ -16,10 +16,10 @@ const allTags = Array.from(
).sort(); ).sort();
--- ---
<MainLayout> <MainLayout page="Home">
<header class="border-b p-4"> <header class="border-b p-4">
<h1 class="text-2xl font-bold">Prompt Catalog</h1> <h1 class="text-2xl font-bold"><a href="/">Prompt Catalog</a></h1>
<p class="text-sm mt-1">Explore and filter AI prompts</p> <p class="text-sm mt-1">Save and explore AI prompts</p>
</header> </header>
<main class="flex"> <main class="flex">