It was brought to my attention that the menu was hard to understand. Now there are some simple directions.
454 lines
18 KiB
Plaintext
454 lines
18 KiB
Plaintext
package templates
|
|
|
|
import "github.com/haydenhargreaves/Potion/internal/templates/components"
|
|
import "github.com/haydenhargreaves/Potion/internal/domain/server"
|
|
|
|
templ CreatePage() {
|
|
@components.Navbar("create")
|
|
<div class="w-full h-fit flex justify-center">
|
|
<div class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 h-full border-l border-r border-gray-300 bg-white">
|
|
@Page()
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
templ Page() {
|
|
@components.BannerText("Create Your Masterpiece")
|
|
<div class="mx-4 md:mx-16 my-8">
|
|
<p class="mb-8">
|
|
Welcome to the Recipe Creation Wizard! Simply fill in the details about your culinary creation,
|
|
including the recipe's name, a description, and other specifics like its category, duration,
|
|
and difficulty. Don't forget to dynamically add all your ingredients and instructions using
|
|
the dedicated buttons, and feel free to upload an appealing image. All required fields are
|
|
marked with an <span class="text-red-500">*</span>. Once everything looks perfect, just hit the "Create Recipe"
|
|
button to
|
|
share your masterpiece!
|
|
</p>
|
|
<form
|
|
hx-post={ domain.API_CREATE_RECIPE }
|
|
hx-swap="outerHTML"
|
|
hx-target="#response"
|
|
hx-trigger="submit"
|
|
hx-encoding="multipart/form-data"
|
|
>
|
|
<div class="flex flex-col">
|
|
<label for="title" class="text-sm">
|
|
Recipe Title
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide a unique title for your recipe. This is the most important part!
|
|
</p>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all shadow-sm invalid:border-red-500"
|
|
type="text"
|
|
id="title"
|
|
name="title"
|
|
required
|
|
maxlength="128"
|
|
minlength="1"
|
|
placeholder="e.g., Classic Chicken Curry"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">Please enter a title. Between 1-128 characters.</p>
|
|
</div>
|
|
<div class="flex flex-col my-4">
|
|
<label for="description" class="text-sm">
|
|
Description
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide a description for your recipe. This can be short and sweet or long and detailed!
|
|
</p>
|
|
<textarea
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all resize-none shadow-sm invalid:border-red-500"
|
|
id="description"
|
|
name="description"
|
|
rows="4"
|
|
required
|
|
maxlength="1024"
|
|
minlength="1"
|
|
placeholder="A brief description of your delicious recipe..."
|
|
></textarea>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter a description. Between 1-1000 characters.
|
|
</p>
|
|
</div>
|
|
<div class="my-4 flex flex-col gap-x-2">
|
|
<div class="flex flex-col flex-grow">
|
|
<label for="tags" class="text-sm">
|
|
Recipe Tags
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide a list of tags. <span class="italic">e.g., easy, dairy-free, gluten-free, high protein.</span>
|
|
</p>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm"
|
|
hx-post={ domain.STATE_TAGS_CREATE }
|
|
maxlength="32"
|
|
hx-trigger="keyup[keyCode==13]"
|
|
hx-on::after-request="this.value=''"
|
|
hx-swap="innerHTML"
|
|
hx-target="#tag-list"
|
|
enterkeyhint="done"
|
|
type="text"
|
|
id="tag"
|
|
name="tag"
|
|
placeholder="e.g., Healthy"
|
|
/>
|
|
<input type="hidden" name="tags" id="tags" value=""/>
|
|
</div>
|
|
<ul id="tag-list" class="my-2 flex gap-1 flex-wrap"></ul>
|
|
</div>
|
|
<div class="my-4 flex gap-x-2">
|
|
<div class="flex flex-col flex-grow w-1/3">
|
|
<label for="preparation-time" class="text-sm">
|
|
Prep Time
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide the estimated prep time (minutes).
|
|
</p>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all shadow-sm invalid:border-red-500"
|
|
type="number"
|
|
id="preparation-time"
|
|
name="preparation-time"
|
|
required
|
|
min="0"
|
|
max="120"
|
|
placeholder="e.g., 20"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter a time (minutes).
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col flex-grow w-1/3">
|
|
<label for="cook-time" class="text-sm">
|
|
Cook Time
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide the estimated cook time (minutes).
|
|
</p>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all shadow-sm invalid:border-red-500"
|
|
type="number"
|
|
id="cook-time"
|
|
name="cook-time"
|
|
required
|
|
min="0"
|
|
max="120"
|
|
placeholder="e.g., 45"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter a time (minutes).
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col flex-grow w-1/3">
|
|
<label for="serving-size" class="text-sm">
|
|
Serving Size
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide the estimated serving size.
|
|
</p>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all shadow-sm invalid:border-red-500"
|
|
type="number"
|
|
max="16"
|
|
min="1"
|
|
required
|
|
id="serving-size"
|
|
name="serving-size"
|
|
placeholder="e.g., 4"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter a serving size.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="my-4 flex gap-x-2">
|
|
<div class="flex flex-col flex-grow w-1/3">
|
|
<label for="category" class="text-sm">
|
|
Category
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide the meal category.
|
|
</p>
|
|
<select
|
|
id="category"
|
|
name="category"
|
|
required
|
|
class="peer border border-gray-300 bg-gray-200 px-4 py-2 rounded-lg focus:outline-none
|
|
focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm
|
|
invalid:border-red-500"
|
|
>
|
|
<option value="">Select a category</option>
|
|
<option value="breakfast">Breakfast</option>
|
|
<option value="lunch">Lunch</option>
|
|
<option value="dinner">Dinner</option>
|
|
<option value="dessert">Dessert</option>
|
|
<option value="snack">Snack</option>
|
|
<option value="side">Side</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please select a category.
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-col flex-grow w-1/3">
|
|
<label for="difficulty" class="text-sm">
|
|
Difficulty
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide a baseline difficulty.
|
|
</p>
|
|
<select
|
|
id="difficulty"
|
|
name="difficulty"
|
|
required
|
|
class="peer border border-gray-300 bg-gray-200 px-4 py-2 rounded-lg focus:outline-none
|
|
focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm
|
|
invalid:border-red-500"
|
|
>
|
|
<option value="">Select a difficulty</option>
|
|
<option value="1">Beginner</option>
|
|
<option value="2">Easy</option>
|
|
<option value="3">Intermediate</option>
|
|
<option value="4">Challenging</option>
|
|
<option value="5">Extreme</option>
|
|
</select>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please select a difficulty.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col my-4">
|
|
<label for="ingredients" class="text-sm">
|
|
Ingredients
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs py-1 text-gray-700">Please provide a list of ingredients and their quantities.</p>
|
|
<ul id="ingredient-list">
|
|
<li class="w-full flex gap-x-2 py-2">
|
|
<div class="flex-grow">
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer w-full border border-gray-300 px-4 py-2 rounded-lg focus:outline-none
|
|
focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm
|
|
invalid:border-red-500"
|
|
type="text"
|
|
id="ingredients"
|
|
name="ingredients"
|
|
required
|
|
minlength="1"
|
|
placeholder="Ingredient name (e.g., Chicken Breast)"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter at least one ingredient.
|
|
</p>
|
|
</div>
|
|
<div class="w-1/3">
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="peer w-full border border-gray-300 px-4 py-2 rounded-lg focus:outline-none
|
|
focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm
|
|
invalid:border-red-500"
|
|
type="text"
|
|
id="quantity"
|
|
name="quantity"
|
|
required
|
|
minlength="1"
|
|
placeholder="Quantity (e.g., 1lb)"
|
|
/>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please provide a quantity.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
<button
|
|
type="button"
|
|
onClick="addIngredient();"
|
|
class="text-base md:text-lg text-white bg-blue-500 w-fit px-5 py-2 rounded-lg cursor-pointer"
|
|
>
|
|
Add Ingredient
|
|
</button>
|
|
</div>
|
|
<div class="flex flex-col my-4">
|
|
<label for="instructions" class="text-sm">
|
|
Instructions
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<p class="text-xs py-1 text-gray-700">
|
|
Please provide a list of instructions. You do not need to include step number, they will be added automatically!
|
|
</p>
|
|
<div id="instruction-list" class="flex flex-col">
|
|
<textarea
|
|
class="peer border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500
|
|
focus:ring-2 duration-200 ease-in-out transition-all resize-none shadow-sm invalid:border-red-500
|
|
valid:my-2 invalid:mt-2"
|
|
id="instructions"
|
|
name="instructions"
|
|
rows="3"
|
|
required
|
|
minlength="1"
|
|
placeholder="Step 1: Describe this step..."
|
|
></textarea>
|
|
<p class="hidden peer-invalid:block text-xs text-red-500 my-1">
|
|
Please enter at least one step.
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick="addInstruction();"
|
|
class="text-base md:text-lg text-white bg-blue-500 w-fit px-5 py-2 rounded-lg cursor-pointer"
|
|
>
|
|
Add Instruction Step
|
|
</button>
|
|
</div>
|
|
<div class="flex flex-col my-4">
|
|
<label for="image" class="text-sm">
|
|
Recipe Image
|
|
</label>
|
|
<p class="text-xs pt-1 pb-2 text-gray-700">
|
|
Please provide an image of your creation. This is optional but is a nice touch!
|
|
</p>
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
name="image"
|
|
id="image"
|
|
class="my-2 block w-full text-sm text-placeholder file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:bg-blue-100 file:text-blue-700 cursor-pointer"
|
|
/>
|
|
</div>
|
|
<p id="response" class="hidden"></p>
|
|
<button
|
|
type="submit"
|
|
class="w-full mt-8 bg-gradient-to-r from-blue-200 to-purple-200 py-2 rounded-lg text-lg cursor-pointer shadow-md"
|
|
>
|
|
Create Recipe
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<script>
|
|
function addIngredient() {
|
|
const list = document.getElementById("ingredient-list");
|
|
const item = document.createElement("li");
|
|
|
|
// New item index
|
|
const index = list.querySelectorAll("li").length;
|
|
item.id = `ingredient-${index}`;
|
|
|
|
item.classList.add("w-full", "flex", "gap-x-2", "py-2");
|
|
item.innerHTML = `
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="flex-grow border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm"
|
|
type="text"
|
|
id="ingredients"
|
|
name="ingredients"
|
|
placeholder="Ingredient name (e.g., Chicken Breast)"
|
|
/>
|
|
<input
|
|
onkeydown="return event.key != 'Enter';"
|
|
class="w-1/3 border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm"
|
|
type="text"
|
|
id="quantity"
|
|
name="quantity"
|
|
placeholder="Quantity (e.g., 1lb)"
|
|
/>
|
|
<button type="button" class="cursor-pointer" onClick="removeIngredient(${index});">
|
|
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M10.0303 8.96965C9.73741 8.67676 9.26253 8.67676 8.96964 8.96965C8.67675 9.26255 8.67675 9.73742 8.96964 10.0303L10.9393 12L8.96966 13.9697C8.67677 14.2625 8.67677 14.7374 8.96966 15.0303C9.26255 15.3232 9.73743 15.3232 10.0303 15.0303L12 13.0607L13.9696 15.0303C14.2625 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2625 15.0303 13.9696L13.0606 12L15.0303 10.0303C15.3232 9.73744 15.3232 9.26257 15.0303 8.96968C14.7374 8.67678 14.2625 8.67678 13.9696 8.96968L12 10.9393L10.0303 8.96965Z" fill="currentColor"/>
|
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12Z" fill="currentColor"/>
|
|
</svg>
|
|
</button>
|
|
`;
|
|
list.appendChild(item);
|
|
}
|
|
|
|
function removeIngredient(index) {
|
|
const list = document.getElementById("ingredient-list");
|
|
|
|
// List contents, ensure valid items
|
|
const listElement = list.querySelector(`#ingredient-${index}`);
|
|
if (listElement) listElement.remove();
|
|
}
|
|
|
|
function addInstruction() {
|
|
const list = document.getElementById("instruction-list");
|
|
const itemNum = list.querySelectorAll("textarea").length + 1;
|
|
const div = document.createElement("div");
|
|
div.id = `instruction-${itemNum}`;
|
|
div.className = "flex";
|
|
|
|
div.innerHTML = `
|
|
<textarea
|
|
rows="3"
|
|
name="instructions"
|
|
placeholder="Step ${itemNum}: Describe this step..."
|
|
class="flex-grow border border-gray-300 my-2 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all resize-none shadow-sm"
|
|
></textarea>
|
|
|
|
<button type="button" class="p-2 cursor-pointer" onClick="removeInstruction(${itemNum});">
|
|
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M10.0303 8.96965C9.73741 8.67676 9.26253 8.67676 8.96964 8.96965C8.67675 9.26255 8.67675 9.73742 8.96964 10.0303L10.9393 12L8.96966 13.9697C8.67677 14.2625 8.67677 14.7374 8.96966 15.0303C9.26255 15.3232 9.73743 15.3232 10.0303 15.0303L12 13.0607L13.9696 15.0303C14.2625 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2625 15.0303 13.9696L13.0606 12L15.0303 10.0303C15.3232 9.73744 15.3232 9.26257 15.0303 8.96968C14.7374 8.67678 14.2625 8.67678 13.9696 8.96968L12 10.9393L10.0303 8.96965Z" fill="currentColor"/>
|
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12Z" fill="currentColor"/>
|
|
</svg>
|
|
</button>
|
|
`;
|
|
|
|
list.appendChild(div);
|
|
}
|
|
|
|
function removeInstruction(num) {
|
|
const list = document.getElementById("instruction-list");
|
|
const item = list.querySelector(`#instruction-${num}`);
|
|
if (item) item.remove();
|
|
|
|
// This list will start at 2, since the first element is not included
|
|
const remainingItems = list.querySelectorAll("div");
|
|
|
|
for (let i = 2; i < remainingItems.length + 2; i++) {
|
|
// Get the old content
|
|
const textContent = remainingItems[i - 2].querySelector("textarea").value;
|
|
|
|
// Create a new element
|
|
const div = document.createElement("div");
|
|
div.id = `instruction-${i}`;
|
|
div.className = "flex";
|
|
div.innerHTML = `
|
|
<textarea
|
|
rows="3"
|
|
name="instructions"
|
|
placeholder="Step ${i}: Describe this step..."
|
|
class="flex-grow border border-gray-300 my-2 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all resize-none shadow-sm"
|
|
>${textContent}</textarea>
|
|
|
|
<button type="button" class="p-2 cursor-pointer" onClick="removeInstruction(${i});">
|
|
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M10.0303 8.96965C9.73741 8.67676 9.26253 8.67676 8.96964 8.96965C8.67675 9.26255 8.67675 9.73742 8.96964 10.0303L10.9393 12L8.96966 13.9697C8.67677 14.2625 8.67677 14.7374 8.96966 15.0303C9.26255 15.3232 9.73743 15.3232 10.0303 15.0303L12 13.0607L13.9696 15.0303C14.2625 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2625 15.0303 13.9696L13.0606 12L15.0303 10.0303C15.3232 9.73744 15.3232 9.26257 15.0303 8.96968C14.7374 8.67678 14.2625 8.67678 13.9696 8.96968L12 10.9393L10.0303 8.96965Z" fill="currentColor"/>
|
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12Z" fill="currentColor"/>
|
|
</svg>
|
|
</button>
|
|
`;
|
|
|
|
remainingItems[i - 2].replaceWith(div);
|
|
}
|
|
}
|
|
</script>
|
|
}
|