Jelajahi Sumber

Create the page to display a recipe

Lee Morgan 2 minggu lalu
induk
melakukan
2c37e47dd1

+ 20 - 9
src/routes/recipe/[recipeId]/+page.server.js

@@ -1,12 +1,23 @@
+import auth from "$lib/server/auth.js";
+import Recipe from "$lib/server/models/Recipe.js";
+
 export async function load({cookies, params}){
-    const user = await userAuth(cookies);
-    const recipe = await Recipe.findOne({_id: params.recipeId}, {
-        name: 1,
-        ingredients: 1,
-        steps: 1,
-        notes: 1,
-        createdAt: 1,
-        updatedAt: 1
-    });
+    const user = await auth(cookies);
+
+    let recipe;
+    try{
+        recipe = await Recipe.findOne({_id: params.recipeId}, {"ingredients._id": 0}).lean();
+
+        if(recipe.private && user._id.toString() !== recipe.user.toString()){
+            recipe = null;
+        }else{
+            recipe.id = recipe._id.toString();
+            recipe._id = undefined;
+            recipe.user = undefined;
+        }
+    }catch{
+        recipe = null;
+    }
+
     return {recipe};
 }

+ 273 - 1
src/routes/recipe/[recipeId]/+page.svelte

@@ -1 +1,273 @@
-<h1>Some Recipe</h1>
+<script>
+    let {data} = $props();
+</script>
+
+<div class="container">
+    {#if data.recipe}
+        <article>
+            <h1>{data.recipe.name}</h1>
+
+            <ul>
+                {#each data.recipe.ingredients as i}
+                    <li>{i.quantity} {i.unit} {i.name}</li>
+                {/each}
+            </ul>
+
+            <ol>
+                {#each data.recipe.steps as s}
+                    <li>{s}</li>
+                {/each}
+            </ol>
+
+            <p>{data.recipe.notes}</p>
+        </article>
+    {:else}
+        <div class="missing">
+            <h1>Recipe doesn't exist or is private</h1>
+            <p>It may have been removed, or you don't have permission to view it.</p>
+            <a href="/dashboard" class="button">Back to Dashboard</a>
+        </div>
+    {/if}
+
+    <div class="buttonBox">
+        <a class="button" href="/recipe/{data.recipe.id}/edit">Edit</a>
+        <button>Printable PDF</button>
+    </div>
+</div>
+
+<style>
+    .container{
+        width: min(720px, 100%);
+        min-height: calc(100% - 75px);
+        min-height: calc(100dvh - 75px);
+        margin: 0 auto;
+        padding: 1rem max(0.75rem, env(safe-area-inset-right, 0px)) max(2rem, env(safe-area-inset-bottom, 0px)) max(0.75rem, env(safe-area-inset-left, 0px));
+        color: var(--color-text);
+        overflow-x: clip;
+    }
+
+    .container:has(article){
+        display: flex;
+        flex-direction: column;
+        gap: 1rem;
+    }
+
+    .container:not(:has(article)){
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 100%;
+    }
+
+    .buttonBox{
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: flex-end;
+        gap: 0.5rem;
+    }
+
+    .buttonBox button{
+        border: none;
+        outline: none;
+        cursor: pointer;
+        margin: 0;
+        background: var(--color-primary);
+        color: var(--color-text-on-primary);
+        border-radius: 6px;
+        padding: 0.6rem 1.2rem;
+        font-weight: 600;
+        font-size: 1rem;
+        line-height: 1.2;
+        white-space: nowrap;
+        transition: background 0.15s ease, transform 0.05s ease;
+    }
+
+    .buttonBox button:hover{
+        background: var(--color-primary-hover);
+    }
+
+    .buttonBox button:active{
+        transform: translateY(1px);
+    }
+
+    article{
+        display: flex;
+        flex-direction: column;
+        gap: 1.15rem;
+        min-width: 0;
+        padding: 1.15rem 0.9rem 1.35rem;
+        background: var(--color-surface);
+        border: 1px solid var(--color-border);
+        border-radius: 10px;
+    }
+
+    article h1{
+        margin: 0;
+        color: var(--color-text);
+        font-size: clamp(1.4rem, 1.1rem + 1.6vw, 1.85rem);
+        font-weight: 700;
+        line-height: 1.25;
+        text-align: center;
+        overflow-wrap: anywhere;
+    }
+
+    article ul,
+    article ol,
+    article > p{
+        display: flex;
+        flex-direction: column;
+        gap: 0.45rem;
+        min-width: 0;
+        max-width: 100%;
+        margin: 0;
+        padding: 0.75rem;
+        list-style: none;
+        background: var(--color-surface-alt);
+        border: 1px solid var(--color-border);
+        border-radius: 8px;
+    }
+
+    article ul::before,
+    article ol::before,
+    article > p::before{
+        color: var(--color-text);
+        font-size: clamp(1rem, 0.9rem + 0.5vw, 1.1rem);
+        font-weight: 700;
+        letter-spacing: 0.01em;
+        margin-bottom: 0.2rem;
+    }
+
+    article ul::before{
+        content: "Ingredients";
+    }
+
+    article ol::before{
+        content: "Steps";
+    }
+
+    article > p::before{
+        content: "Notes";
+    }
+
+    article li{
+        padding: 0.55rem 0.7rem;
+        background: var(--color-surface);
+        border: 1px solid var(--color-border);
+        border-left: 3px solid var(--color-primary);
+        border-radius: 6px;
+        font-size: 0.95rem;
+        line-height: 1.4;
+        color: var(--color-text);
+        overflow-wrap: anywhere;
+        word-break: break-word;
+        min-width: 0;
+        max-width: 100%;
+    }
+
+    article ol{
+        counter-reset: step;
+    }
+
+    article ol li{
+        display: grid;
+        grid-template-columns: 1.5rem minmax(0, 1fr);
+        align-items: start;
+        gap: 0.7rem;
+        counter-increment: step;
+    }
+
+    article ol li::before{
+        content: counter(step);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 1.5rem;
+        height: 1.5rem;
+        border-radius: 50%;
+        background: var(--color-primary);
+        color: var(--color-text-on-primary);
+        font-size: 0.75rem;
+        font-weight: 700;
+        line-height: 1;
+    }
+
+    article > p{
+        color: var(--color-text);
+        font-size: 0.95rem;
+        line-height: 1.5;
+        white-space: pre-wrap;
+        overflow-wrap: anywhere;
+    }
+
+    article > p:empty{
+        display: none;
+    }
+
+    .missing{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 0.75rem;
+        width: min(420px, 100%);
+        padding: 2rem 1.5rem;
+        text-align: center;
+        background: var(--color-surface);
+        border: 1px solid var(--color-border);
+        border-radius: 10px;
+    }
+
+    .missing h1{
+        margin: 0;
+        color: var(--color-text);
+        font-size: clamp(1.25rem, 1rem + 1.4vw, 1.5rem);
+        font-weight: 700;
+        line-height: 1.3;
+    }
+
+    .missing p{
+        margin: 0 0 0.35rem;
+        color: var(--color-text-muted);
+        font-size: 0.95rem;
+        line-height: 1.45;
+    }
+
+    .missing .button{
+        margin: 0;
+    }
+
+    @media (min-width: 640px){
+        .container{
+            padding: 2rem 1rem 3rem;
+        }
+
+        article{
+            padding: 2rem;
+        }
+
+        article ul,
+        article ol,
+        article > p{
+            padding: 1rem;
+        }
+    }
+
+    @media (max-width: 480px){
+        .buttonBox{
+            flex-direction: column;
+        }
+
+        .buttonBox button{
+            width: 100%;
+        }
+    }
+
+    @media (pointer: coarse){
+        .buttonBox button,
+        .missing .button{
+            min-height: 44px;
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+</style>