Browse Source

Add public/private options for recipes

Lee Morgan 2 weeks ago
parent
commit
26b40ce5c1

+ 5 - 1
src/lib/server/models/Recipe.js

@@ -32,9 +32,13 @@ const recipeSchema = new mongoose.Schema({
         type: Date,
         type: Date,
         required: true
         required: true
     },
     },
-    updateAt: {
+    updatedAt: {
         type: Date,
         type: Date,
         required: true
         required: true
+    },
+    private: {
+        type: Boolean,
+        required: true
     }
     }
 });
 });
 
 

+ 2 - 2
src/routes/dashboard/+page.server.js

@@ -2,9 +2,9 @@ import Recipe from "$lib/server/models/Recipe.js";
 import userAuth from "$lib/server/auth.js";
 import userAuth from "$lib/server/auth.js";
 
 
 export async function load({cookies}){
 export async function load({cookies}){
-    const userCookie = await userAuth(cookies);
+    const user = await userAuth(cookies);
     const recipes = await Recipe.aggregate([
     const recipes = await Recipe.aggregate([
-        {$match: {user: userCookie._id}},
+        {$match: {user: user._id}},
         {$project: {
         {$project: {
             _id: 0,
             _id: 0,
             id: {$toString: "$_id"},
             id: {$toString: "$_id"},

+ 4 - 2
src/routes/dashboard/+page.svelte

@@ -10,10 +10,10 @@
 
 
     <div class="recipes">
     <div class="recipes">
         {#each data.recipes as r}
         {#each data.recipes as r}
-            <article class="recipe">
+            <a href="/recipe/{r.id}" class="recipe">
                 <p class="name">{r.name}</p>
                 <p class="name">{r.name}</p>
                 <p class="date">{r.updatedAt ? new Date(r.updatedAt).toLocaleDateString() : ""}</p>
                 <p class="date">{r.updatedAt ? new Date(r.updatedAt).toLocaleDateString() : ""}</p>
-            </article>
+            </a>
         {:else}
         {:else}
             <p class="empty">No recipes yet. Create your first one.</p>
             <p class="empty">No recipes yet. Create your first one.</p>
         {/each}
         {/each}
@@ -70,6 +70,8 @@
         border: 1px solid var(--color-border);
         border: 1px solid var(--color-border);
         border-left: 3px solid var(--color-primary);
         border-left: 3px solid var(--color-primary);
         border-radius: 10px;
         border-radius: 10px;
+        text-decoration: none;
+        color: var(--color-text);
     }
     }
 
 
     .name{
     .name{

+ 12 - 0
src/routes/recipe/[recipeId]/+page.server.js

@@ -0,0 +1,12 @@
+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
+    });
+    return {recipe};
+}

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

@@ -0,0 +1 @@
+<h1>Some Recipe</h1>

+ 139 - 2
src/routes/recipe/new/+page.svelte

@@ -11,6 +11,7 @@
     let stepString = $state("");
     let stepString = $state("");
     let steps = $state([]);
     let steps = $state([]);
     let notes = $state("");
     let notes = $state("");
+    let visibility = $state("private");
 
 
     const submit = async (event)=>{
     const submit = async (event)=>{
         event.preventDefault();
         event.preventDefault();
@@ -21,7 +22,10 @@
             return;
             return;
         }
         }
 
 
-        const data = {name, ingredients, steps, notes};
+        const data = {
+            name, ingredients, steps, notes,
+            private: visibility === "private" ? true : false
+        };
 
 
         const response = await fetch("/recipe/new", {
         const response = await fetch("/recipe/new", {
             method: "POST",
             method: "POST",
@@ -114,6 +118,30 @@
 
 
         <textarea bind:value={notes} rows="4" placeholder="Notes"></textarea>
         <textarea bind:value={notes} rows="4" placeholder="Notes"></textarea>
 
 
+        <fieldset class="visibility">
+            <legend>Visibility</legend>
+            <div class="visibilityOptions">
+                <label>
+                    <input
+                        type="radio"
+                        name="visibility"
+                        value="private"
+                        bind:group={visibility}
+                    >
+                    <span>Private</span>
+                </label>
+                <label>
+                    <input
+                        type="radio"
+                        name="visibility"
+                        value="public"
+                        bind:group={visibility}
+                    >
+                    <span>Public</span>
+                </label>
+            </div>
+        </fieldset>
+
         <button>Create Recipe</button>
         <button>Create Recipe</button>
     </form>
     </form>
 </div>
 </div>
@@ -263,6 +291,114 @@
         min-height: 5rem;
         min-height: 5rem;
     }
     }
 
 
+    .visibility{
+        border: 0;
+        margin: 0;
+        padding: 0;
+        min-width: 0;
+        display: flex;
+        flex-direction: column;
+        gap: 0.4rem;
+    }
+
+    .visibility legend{
+        float: left;
+        width: 100%;
+        padding: 0;
+        margin: 0;
+        color: var(--color-text);
+        font-size: 0.9rem;
+        font-weight: 600;
+    }
+
+    .visibilityOptions{
+        position: relative;
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 0;
+        width: 100%;
+        clear: both;
+        padding: 0.2rem;
+        background: var(--color-surface-alt);
+        border: 1px solid var(--color-border);
+        border-radius: 999px;
+        isolation: isolate;
+    }
+
+    .visibilityOptions::before{
+        content: "";
+        position: absolute;
+        top: 0.2rem;
+        left: 0.2rem;
+        width: calc(50% - 0.2rem);
+        height: calc(100% - 0.4rem);
+        background: var(--color-primary);
+        border-radius: 999px;
+        transition: transform 0.2s ease;
+        pointer-events: none;
+        z-index: 0;
+    }
+
+    .visibilityOptions:has(input[value="public"]:checked)::before{
+        transform: translateX(100%);
+    }
+
+    .visibilityOptions label{
+        position: relative;
+        z-index: 1;
+        flex-direction: row;
+        align-items: stretch;
+        justify-content: stretch;
+        gap: 0;
+        margin: 0;
+        cursor: pointer;
+    }
+
+    .visibilityOptions input{
+        position: absolute;
+        inset: 0;
+        width: 100%;
+        height: 100%;
+        margin: 0;
+        padding: 0;
+        opacity: 0;
+        border: 0;
+        background: none;
+        appearance: none;
+        cursor: pointer;
+    }
+
+    .visibilityOptions span{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        min-height: 2.5rem;
+        padding: 0.45rem 0.8rem;
+        background: transparent;
+        border: 0;
+        border-radius: 999px;
+        color: var(--color-text-muted);
+        font-size: 0.95rem;
+        font-weight: 600;
+        line-height: 1.2;
+        text-align: center;
+        transition: color 0.15s ease;
+    }
+
+    .visibilityOptions input:hover + span{
+        color: var(--color-text);
+    }
+
+    .visibilityOptions input:checked + span{
+        color: var(--color-text-on-primary);
+    }
+
+    .visibilityOptions input:focus-visible + span{
+        outline: 2px solid var(--color-primary);
+        outline-offset: 2px;
+    }
+
     @container recipe-form (min-width: 260px){
     @container recipe-form (min-width: 260px){
         .ingredients{
         .ingredients{
             grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
             grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
@@ -332,7 +468,8 @@
 
 
     @media (pointer: coarse){
     @media (pointer: coarse){
         .button,
         .button,
-        .standardForm button{
+        .standardForm button,
+        .visibilityOptions span{
             min-height: 44px;
             min-height: 44px;
         }
         }
     }
     }

+ 2 - 1
src/routes/recipe/new/+server.js

@@ -22,7 +22,8 @@ export async function POST({request, cookies}){
         steps: body.steps,
         steps: body.steps,
         notes: body.notes,
         notes: body.notes,
         createdAt: new Date(),
         createdAt: new Date(),
-        updatedAt: new Date()
+        updatedAt: new Date(),
+        private: body.private
     });
     });
 
 
     await recipe.save();
     await recipe.save();