feat: recipe type (dish/drink) for better cocktail handling (v0.31.0)
Add recipes.recipeType enum (dish|drink, default dish). Recipe form now shows a Type selector and hides food-only fields for drinks (prep/cook time, difficulty, batch-cook toggle) instead of forcing every cocktail through meal-oriented UI. Detail page skips the meal/drink pairing suggestion buttons on drink recipes (pairing a drink with a drink doesn't make sense). Recipes list gains a Type filter facet alongside difficulty/batch-cook. Scoped deliberately narrow per investigation: no ABV/glass-type/ garnish structured fields, no meal-plan schema changes, and the existing AI drink-pairing-suggestion feature (ephemeral, never saved as a recipe) is left as-is rather than wired into recipe creation — those suggestions are wine/beer/spirit recommendations without ingredients/steps, not really recipes to save. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ type SearchParams = Promise<{
|
||||
tag?: string;
|
||||
page?: string;
|
||||
batchCook?: string;
|
||||
recipeType?: string;
|
||||
}>;
|
||||
|
||||
const SORT_MAP = {
|
||||
@@ -40,7 +41,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
if (!session) return null;
|
||||
const m = getMessages((session.user as { locale?: string }).locale);
|
||||
|
||||
const { q, sort, visibility, difficulty, tag, page: pageParam, batchCook } = await searchParams;
|
||||
const { q, sort, visibility, difficulty, tag, page: pageParam, batchCook, recipeType } = await searchParams;
|
||||
const query = (q ?? "").trim().slice(0, 200);
|
||||
const sortKey: SortKey = (sort && sort in SORT_MAP ? sort : "updated_desc") as SortKey;
|
||||
const tagFilter = tag?.trim().slice(0, 50) || undefined;
|
||||
@@ -54,6 +55,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
? (difficulty as "easy" | "medium" | "hard")
|
||||
: undefined;
|
||||
const batchCookFilter = batchCook === "1" || batchCook === "0" ? batchCook : undefined;
|
||||
const recipeTypeFilter = recipeType === "dish" || recipeType === "drink" ? recipeType : undefined;
|
||||
|
||||
// Escape ilike wildcard chars (% and _) so a search like "100%" matches literally.
|
||||
const escapedQuery = query.replace(/[\\%_]/g, (c) => `\\${c}`);
|
||||
@@ -78,6 +80,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
visibilityFilter ? eq(recipes.visibility, visibilityFilter) : undefined,
|
||||
difficultyFilter ? eq(recipes.difficulty, difficultyFilter) : undefined,
|
||||
tagFilter ? sql`${recipes.tags} @> ARRAY[${tagFilter}]::text[]` : undefined,
|
||||
recipeTypeFilter ? eq(recipes.recipeType, recipeTypeFilter) : undefined,
|
||||
);
|
||||
|
||||
const [userRecipes, totalRow] = await Promise.all([
|
||||
@@ -112,6 +115,7 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
if (difficultyFilter) params.set("difficulty", difficultyFilter);
|
||||
if (tagFilter) params.set("tag", tagFilter);
|
||||
if (batchCookFilter) params.set("batchCook", batchCookFilter);
|
||||
if (recipeTypeFilter) params.set("recipeType", recipeTypeFilter);
|
||||
if (p > 1) params.set("page", String(p));
|
||||
const qs = params.toString();
|
||||
return qs ? `/recipes?${qs}` : "/recipes";
|
||||
@@ -133,9 +137,10 @@ export default async function RecipesPage({ searchParams }: { searchParams: Sear
|
||||
initialDifficulty={difficultyFilter ?? ""}
|
||||
initialTag={tagFilter ?? ""}
|
||||
initialBatchCook={batchCookFilter ?? ""}
|
||||
initialRecipeType={recipeTypeFilter ?? ""}
|
||||
/>
|
||||
<RecipesEmptyState query={query} count={total} />
|
||||
<RecipesGrid key={`${query}-${sortKey}-${visibilityFilter}-${difficultyFilter}-${tagFilter}-${batchCookFilter}-${page}`} recipes={recipesWithFavorites} />
|
||||
<RecipesGrid key={`${query}-${sortKey}-${visibilityFilter}-${difficultyFilter}-${tagFilter}-${batchCookFilter}-${recipeTypeFilter}-${page}`} recipes={recipesWithFavorites} />
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-2 pt-2">
|
||||
|
||||
Reference in New Issue
Block a user