307 lines
12 KiB
HTML
307 lines
12 KiB
HTML
<!-- OnlyPrompt - Create Prompt page:
|
||
- Form to publish new AI prompts with title, description, category, content, example output, image upload, and pricing toggle -->
|
||
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>OnlyPrompt - Create New Prompt</title>
|
||
<link rel="stylesheet" href="../css/variables.css">
|
||
<link rel="stylesheet" href="../css/base.css">
|
||
<link rel="stylesheet" href="../css/sidebar.css">
|
||
<link rel="stylesheet" href="../css/login.css">
|
||
<link rel="stylesheet" href="../css/topbar.css">
|
||
<link rel="stylesheet" href="../css/create.css">
|
||
<script src="../js/profile-shared.js"></script>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||
</head>
|
||
<body>
|
||
<div class="layout">
|
||
|
||
<div id="sidebar-container"></div>
|
||
|
||
<div class="page-body">
|
||
|
||
<div id="topbar-container"></div>
|
||
|
||
<main class="create-main">
|
||
<div class="create-container">
|
||
<div class="create-header">
|
||
<h1 id="create-title">Create AI Prompt</h1>
|
||
<p id="create-subtitle">Design and save custom prompts for your AI workflows.</p>
|
||
</div>
|
||
|
||
<form id="createPromptForm" class="create-form" enctype="multipart/form-data">
|
||
<!-- Title -->
|
||
<div class="form-group">
|
||
<label for="title">Prompt Title *</label>
|
||
<input type="text" id="title" name="title" placeholder="e.g., Write an inspiring startup story about innovation" required>
|
||
</div>
|
||
|
||
<!-- Description -->
|
||
<div class="form-group">
|
||
<label for="description">Description *</label>
|
||
<textarea id="description" name="description" rows="2" placeholder="Draft a narrative about a small team overcoming challenges to launch a groundbreaking app" required></textarea>
|
||
</div>
|
||
|
||
<!-- Category -->
|
||
<div class="form-group">
|
||
<label for="category">Category *</label>
|
||
<select id="category" name="category" required>
|
||
<option value="creative-writing">Creative Writing</option>
|
||
<option value="coding">Coding</option>
|
||
<option value="art">Art</option>
|
||
<option value="marketing">Marketing</option>
|
||
<option value="video">Video</option>
|
||
<option value="data">Data</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Prompt Content -->
|
||
<div class="form-group">
|
||
<label for="promptContent">Prompt Content *</label>
|
||
<textarea id="promptContent" name="promptContent" rows="6" placeholder="Write your prompt instructions here..." required></textarea>
|
||
<small class="form-hint">Use clear, step-by-step instructions for the AI.</small>
|
||
</div>
|
||
|
||
<!-- Example Output (Text) -->
|
||
<div class="form-group">
|
||
<label for="exampleOutput">Example Output (optional)</label>
|
||
<textarea id="exampleOutput" name="exampleOutput" rows="4" placeholder="Show an example of what the AI might generate..."></textarea>
|
||
</div>
|
||
|
||
<!-- Example Image (optional) -->
|
||
<div class="form-group">
|
||
<label for="exampleImage">Example Image (optional)</label>
|
||
<input type="file" id="exampleImage" name="exampleImage" accept="image/png, image/jpeg, image/jpg">
|
||
<small class="form-hint">Upload a PNG or JPG – preview will appear below.</small>
|
||
<div id="imagePreview">
|
||
<img id="previewImg" src="#" alt="Preview">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Pricing (with toggle) -->
|
||
<div class="form-group pricing-group">
|
||
<label>Pricing</label>
|
||
<div class="pricing-toggle">
|
||
<button type="button" id="freeBtn" class="price-option active">Free</button>
|
||
<button type="button" id="paidBtn" class="price-option">Paid</button>
|
||
</div>
|
||
<div id="priceField">
|
||
<input type="number" id="price" name="price" step="0.01" min="0" placeholder="Price in USD (e.g., 19.99)">
|
||
</div>
|
||
<small class="form-hint">You can set a price later or keep it free.</small>
|
||
</div>
|
||
|
||
<!-- Submit Button -->
|
||
<div class="form-actions">
|
||
<button type="submit" class="submit-btn" id="submitPromptBtn">Publish Prompt</button>
|
||
<button type="button" class="cancel-btn">Cancel</button>
|
||
</div>
|
||
<p id="create-status"></p></p>
|
||
</form>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Toggle between free and paid
|
||
const freeBtn = document.getElementById('freeBtn');
|
||
const paidBtn = document.getElementById('paidBtn');
|
||
const priceField = document.getElementById('priceField');
|
||
const priceInput = document.getElementById('price');
|
||
const editPromptId = new URLSearchParams(location.search).get('id');
|
||
const submitPromptBtn = document.getElementById('submitPromptBtn');
|
||
|
||
freeBtn.addEventListener('click', () => {
|
||
freeBtn.classList.add('active');
|
||
paidBtn.classList.remove('active');
|
||
priceField.style.display = 'none';
|
||
priceInput.removeAttribute('required');
|
||
});
|
||
paidBtn.addEventListener('click', () => {
|
||
paidBtn.classList.add('active');
|
||
freeBtn.classList.remove('active');
|
||
priceField.style.display = 'block';
|
||
priceInput.setAttribute('required', 'required');
|
||
});
|
||
|
||
// Image preview for example image
|
||
const imageInput = document.getElementById('exampleImage');
|
||
const imagePreview = document.getElementById('imagePreview');
|
||
const previewImg = document.getElementById('previewImg');
|
||
let exampleImageUrl = '';
|
||
|
||
if (imageInput) {
|
||
imageInput.addEventListener('change', function(event) {
|
||
const file = event.target.files[0];
|
||
if (file && (file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg')) {
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
exampleImageUrl = e.target.result;
|
||
previewImg.src = exampleImageUrl;
|
||
imagePreview.style.display = 'block';
|
||
};
|
||
reader.readAsDataURL(file);
|
||
} else {
|
||
imagePreview.style.display = 'none';
|
||
previewImg.src = '#';
|
||
exampleImageUrl = '';
|
||
if (file) alert('Please upload a PNG or JPG image.');
|
||
}
|
||
});
|
||
}
|
||
|
||
async function loadCategories() {
|
||
const categorySelect = document.getElementById('category');
|
||
try {
|
||
const response = await fetch('/api/v1/categories/minimal');
|
||
if (!response.ok) return;
|
||
|
||
const categories = await response.json();
|
||
if (!categories.length) return;
|
||
|
||
categorySelect.innerHTML = categories
|
||
.map((category) => `<option value="${category.slug}">${category.name}</option>`)
|
||
.join('');
|
||
} catch {
|
||
// Keep the static fallback categories.
|
||
}
|
||
}
|
||
|
||
async function loadPromptForEdit() {
|
||
if (!editPromptId) return;
|
||
|
||
document.getElementById('create-title').textContent = 'Edit AI Prompt';
|
||
document.getElementById('create-subtitle').textContent = 'Update your published prompt.';
|
||
submitPromptBtn.textContent = 'Save Changes';
|
||
|
||
const status = document.getElementById('create-status');
|
||
status.textContent = 'Loading prompt...';
|
||
|
||
try {
|
||
const response = await fetch(`/api/v1/prompts/${editPromptId}`);
|
||
if (response.status === 401) {
|
||
location.href = '/login';
|
||
return;
|
||
}
|
||
if (!response.ok) throw new Error('Prompt could not be loaded.');
|
||
|
||
const prompt = await response.json();
|
||
document.getElementById('title').value = prompt.title || '';
|
||
document.getElementById('description').value = prompt.description || '';
|
||
document.getElementById('category').value = prompt.categorySlug || document.getElementById('category').value;
|
||
document.getElementById('promptContent').value = prompt.content || '';
|
||
document.getElementById('exampleOutput').value = prompt.exampleOutput || '';
|
||
exampleImageUrl = prompt.exampleImageUrl || '';
|
||
|
||
if (exampleImageUrl) {
|
||
previewImg.src = exampleImageUrl;
|
||
imagePreview.style.display = 'block';
|
||
}
|
||
|
||
if (prompt.price != null && Number(prompt.price) > 0) {
|
||
paidBtn.click();
|
||
priceInput.value = Number(prompt.price);
|
||
} else {
|
||
freeBtn.click();
|
||
}
|
||
|
||
status.textContent = '';
|
||
} catch (error) {
|
||
status.textContent = error.message;
|
||
}
|
||
}
|
||
|
||
// Handle form submission
|
||
document.getElementById('createPromptForm').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
const status = document.getElementById('create-status');
|
||
const submitBtn = document.querySelector('.submit-btn');
|
||
status.textContent = editPromptId ? 'Saving...' : 'Publishing...';
|
||
submitBtn.disabled = true;
|
||
|
||
try {
|
||
const isPaid = paidBtn.classList.contains('active');
|
||
const price = isPaid ? Number(priceInput.value || 0) : null;
|
||
const payload = {
|
||
title: document.getElementById('title').value.trim(),
|
||
description: document.getElementById('description').value.trim(),
|
||
category: document.getElementById('category').value,
|
||
content: document.getElementById('promptContent').value.trim(),
|
||
exampleOutput: document.getElementById('exampleOutput').value.trim() || null,
|
||
exampleImageUrl: exampleImageUrl || null,
|
||
price,
|
||
subscriptionTier: null,
|
||
slug: null
|
||
};
|
||
|
||
const response = await fetch(editPromptId ? `/api/v1/prompts/${editPromptId}` : '/api/v1/prompts', {
|
||
method: editPromptId ? 'PUT' : 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'same-origin',
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
if (response.status === 401) {
|
||
location.href = '/login';
|
||
return;
|
||
}
|
||
if (!response.ok) {
|
||
const error = await response.text();
|
||
throw new Error(getCreateErrorMessage(error, response.status));
|
||
}
|
||
|
||
const prompt = await response.json();
|
||
location.href = `/post-detail?id=${prompt.id}`;
|
||
} catch (error) {
|
||
status.textContent = error.message;
|
||
submitBtn.disabled = false;
|
||
}
|
||
});
|
||
|
||
function getCreateErrorMessage(errorText, status) {
|
||
if (!errorText) return `Server error ${status}`;
|
||
|
||
try {
|
||
const error = JSON.parse(errorText);
|
||
const messages = error.errors
|
||
? Object.values(error.errors).flat()
|
||
: [error.title || errorText];
|
||
|
||
return messages.join(' ');
|
||
} catch {
|
||
return errorText;
|
||
}
|
||
}
|
||
|
||
// Cancel button (go back)
|
||
document.querySelector('.cancel-btn').addEventListener('click', () => {
|
||
window.history.back();
|
||
});
|
||
|
||
// Fetch sidebar and topbar
|
||
fetch('/sidebar.html')
|
||
.then(r => r.text())
|
||
.then(data => {
|
||
document.getElementById('sidebar-container').innerHTML = data;
|
||
// Remove active class from all sidebar links
|
||
document.querySelectorAll('#sidebar-container .sidebar a').forEach(link => {
|
||
link.classList.remove('active');
|
||
});
|
||
// Optionally set active on "Create New" if it exists, otherwise keep none
|
||
const createLink = document.querySelector('#sidebar-container a[href="create.html"]');
|
||
if (createLink) createLink.classList.add('active');
|
||
});
|
||
|
||
fetch('/topbar.html')
|
||
.then(r => r.text())
|
||
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
||
|
||
loadCategories().then(loadPromptForEdit);
|
||
</script>
|
||
</body>
|
||
</html>
|