Furthermore, not sure how we are going to handle the searching. Maybe a full-text search index? For now, it has been ignored, but the filters seem to be working properly.
354 lines
12 KiB
Plaintext
354 lines
12 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 mb-2">
|
|
Recipe Title
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Description
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Recipe Tags
|
|
</label>
|
|
<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="/v1/web/state/tags"
|
|
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 mb-2">
|
|
Prep Time
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Cook Time
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Serving Size
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Category
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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 mb-2">
|
|
Difficulty
|
|
<span class="text-red-500">*</span>
|
|
</label>
|
|
<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>
|
|
<div id="ingredient-list">
|
|
<div 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>
|
|
</div>
|
|
</div>
|
|
<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>
|
|
<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>
|
|
<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("div");
|
|
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)"
|
|
/>
|
|
`;
|
|
list.appendChild(item);
|
|
}
|
|
|
|
function addInstruction() {
|
|
const list = document.getElementById("instruction-list");
|
|
const itemNum = list.querySelectorAll("textarea").length + 1;
|
|
const item = document.createElement("textarea");
|
|
item.id = "instructions";
|
|
item.name = "instructions";
|
|
item.className = "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";
|
|
item.rows = "3";
|
|
item.placeholder = `Step ${itemNum}: Describe this step...`;
|
|
list.appendChild(item);
|
|
}
|
|
</script>
|
|
}
|