Superpowered to do lists. No signup required.

✨ implement crud/pin lists, error toasts

+5 -5
src/routes/+layout.svelte
··· 7 7 import toast, { Toaster } from "svelte-french-toast"; 8 8 import { persisted, pinned_list } from "$lib/stores.svelte"; 9 9 10 - const theme = persisted<string>("theme", "light"); 10 + let theme = persisted<string>("theme", "dark"); 11 11 let is_menu_open = $state(false); 12 - let theme_style = $derived(theme.value === "light" 13 - ? "text-black absolute inset-0 -z-10 h-full w-full bg-white bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]" 14 - : "text-white absolute top-0 z-[-2] h-screen w-screen bg-[#000000] bg-[radial-gradient(#ffffff33_1px,#00091d_1px)] bg-[size:20px_20px]" 12 + let theme_style = $derived(theme.value === "dark" 13 + ? "text-white absolute top-0 z-[-2] h-screen w-screen bg-[#000000] bg-[radial-gradient(#ffffff33_1px,#00091d_1px)] bg-[size:20px_20px]" 14 + : "text-black absolute inset-0 -z-10 h-full w-full bg-white bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px]" 15 15 ); 16 16 17 17 function comingSoon() { ··· 41 41 onclick={comingSoon} 42 42 class="flex gap-2 text-start w-full h-full rounded-xl pl-2 pr-5 py-2 hover:bg-slate-500/10 transition-all duration-150 items-center" 43 43 > 44 - <img src="/shooting-star.svg" alt="Item 1" class="w-8 h-8" /> 44 + <img src="/shooting-star-line.svg" alt="Item 1" class="w-8 h-8" /> 45 45 Try a new list 46 46 </button> 47 47 <button
+1
src/routes/+page.svelte
··· 1 + <p>Test</p>
+97 -6
src/routes/[id]/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount } from "svelte"; 2 + import { onMount, tick } from "svelte"; 3 3 import { page } from "$app/stores"; 4 4 import { local_lists, pinned_list, generateId, type List } from "$lib/stores.svelte"; 5 + import { goto, pushState } from "$app/navigation"; 6 + import toast, { Toaster } from "svelte-french-toast"; 5 7 8 + let is_menu_open = $state(false); 6 9 let list : List | undefined = $state(); 7 10 let task_input = $state(""); 11 + let user_lists = $derived(local_lists.value) as List[]; 8 12 9 13 onMount(() => { 10 14 list = local_lists.value!.find((l) => l.id === $page.params.id); 11 15 }); 12 16 17 + // since list points to something inside local_lists, 18 + // it will run when list state changes 13 19 $effect(() => local_lists.update()); 14 20 15 21 function addTask() { 22 + if (task_input.length === 0) { 23 + toast.error("Enter a task to add"); 24 + return; 25 + } 26 + 16 27 list?.tasks.push({ 17 28 id: generateId(), 18 29 description: task_input, ··· 27 38 list.tasks = list.tasks.filter((t) => t.id !== id); 28 39 } 29 40 } 41 + 42 + function createList() { 43 + const new_list = { 44 + id: generateId(), 45 + title: "", 46 + tasks: [] 47 + }; 48 + 49 + local_lists.value!.push(new_list); 50 + list = local_lists.value!.find((l) => l.id === new_list.id); 51 + goto(`/${list!.id}`); 52 + } 53 + 54 + function switchToList(id: string) { 55 + list = local_lists.value!.find((l) => l.id === id); 56 + goto(`/${list!.id}`); 57 + } 58 + 59 + function pinList(id: string) { 60 + pinned_list.value = id; 61 + } 62 + 63 + function deleteList() { 64 + if (pinned_list.value === $page.params.id) { 65 + toast.error("Cannot delete pinned list"); 66 + return; 67 + } 68 + 69 + local_lists.value = local_lists.value!.filter((l) => l.id !== $page.params.id); 70 + list = local_lists.value.find((l) => l.id === pinned_list.value); 71 + goto(`/${list!.id}`); 72 + } 30 73 </script> 31 74 32 75 <main class="flex flex-col w-full px-2 pt-8 pb-12 lg:p-4 lg:pb-24 gap-8 text-xl lg:text-3xl"> 33 76 {#if list} 34 - <input 35 - type="text" 36 - bind:value={list.title} 37 - class="text-5xl font-bold bg-transparent" 38 - /> 77 + <section class="relative flex gap-4 w-full"> 78 + <div class="flex gap-4 border-black border w-fit h-fit p-2 bg-white rounded-xl"> 79 + <button onclick={() => is_menu_open = !is_menu_open}> 80 + <img 81 + src="/list-box-line.svg" 82 + alt="Lists button" 83 + class="w-12 h-12 hover:bg-slate-500/10 rounded-full" 84 + /> 85 + </button> 86 + <button onclick={() => pinList(list!.id)}> 87 + <img 88 + src={pinned_list.value === list.id ? "/pin.svg" : "/pin-line.svg"} 89 + alt="Pin list button" 90 + class="w-12 h-12 hover:bg-slate-500/10 rounded-full" 91 + /> 92 + </button> 93 + <button onclick={deleteList}> 94 + <img 95 + src="/trash-line.svg" 96 + alt="Delete list button" 97 + class="w-12 h-12 hover:bg-slate-500/10 rounded-full" 98 + /> 99 + </button> 100 + </div> 101 + 102 + {#if is_menu_open} 103 + <menu class="absolute flex flex-col gap-2 w-fit h-fit top-20 p-2 bg-white border border-black rounded-lg !text-black !text-lg"> 104 + {#each user_lists as user_list : List (user_list.id)} 105 + <button 106 + onclick={() => switchToList(user_list.id)} 107 + class="flex gap-2 justify-between text-start w-full h-full rounded-xl pl-2 pr-5 py-2 hover:bg-slate-500/10 transition-all duration-150 items-center" 108 + > 109 + {user_list.title.length > 0 ? user_list.title : "Untitled"} 110 + {#if user_list.id === list.id} 111 + <img src="/check-line.svg" alt="Item 1" class="w-8 h-8" /> 112 + {/if} 113 + </button> 114 + {/each} 115 + <button 116 + onclick={createList} 117 + class="flex gap-2 justify-between text-start w-full h-full rounded-xl pl-2 pr-5 py-2 hover:bg-slate-500/10 transition-all duration-150 items-center" 118 + > 119 + Create new list 120 + </button> 121 + </menu> 122 + {/if} 123 + </section> 124 + <input 125 + type="text" 126 + bind:value={list.title} 127 + placeholder="Untitled" 128 + class="text-5xl font-bold bg-transparent" 129 + /> 39 130 <ul class="flex flex-col gap-4"> 40 131 {#each list.tasks as task (task.id)} 41 132 <li class="group flex justify-between items-center gap-4">
+1
static/check-line.svg
··· 1 + <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><g fill="currentColor"><path d="M19.707 6.293a1 1 0 0 1 0 1.414l-10 10a1 1 0 0 1-1.414 0l-4-4a1 1 0 1 1 1.414-1.414L9 15.586l9.293-9.293a1 1 0 0 1 1.414 0z"/></g></g></svg></svg>
+1
static/list-box-line.svg
··· 1 + <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 8h5m0 4h-5m5 4h-5m-5 4h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2zM8 8h.001M8 12h.001M8 16h.001"/></g></svg></svg>
+1
static/pin-line.svg
··· 1 + <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m4 20l5-5m0 0l3.956 3.956a1 1 0 0 0 1.626-.314l2.255-5.261a1 1 0 0 1 .548-.535l3.207-1.283a1 1 0 0 0 .336-1.635l-6.856-6.856a1 1 0 0 0-1.635.336l-1.283 3.207a1 1 0 0 1-.535.548L5.358 9.418a1 1 0 0 0-.314 1.626L9 15z"/></g></svg></svg>
+1
static/shooting-star-line.svg
··· 1 + <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 8c-1.667.667-5.4 2.7-7 5.5m9.5-2.5C9.167 12.333 4 16.4 2 22m10.5-7.5c-1.167 1.167-3.8 4.1-5 6.5m7.174-14.55l.673-3.285l2.225 2.51l3.027-.294l-1.768 3.062l1.743 2.639l-3.286-.673l-2.51 2.225l.19-3.156l-3.062-1.768l2.768-1.26z"/></g></svg></svg>
-1
static/shooting-star.svg
··· 1 - <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M9 8c-1.667.667-5.4 2.7-7 5.5m9.5-2.5C9.167 12.333 4 16.4 2 22m10.5-7.5c-1.167 1.167-3.8 4.1-5 6.5"/><path fill="currentColor" d="m14.674 6.45l.673-3.285l2.225 2.51l3.027-.294l-1.768 3.062l1.743 2.639l-3.286-.673l-2.51 2.225l.19-3.156l-3.062-1.768l2.768-1.26z"/></g></g></svg></svg>
+1
static/trash-line.svg
··· 1 + <svg width="512" height="512" viewBox="0 0 512 512" style="color:currentColor" xmlns="http://www.w3.org/2000/svg" class="h-full w-full"><rect width="512" height="512" x="0" y="0" rx="30" fill="transparent" stroke="transparent" stroke-width="0" stroke-opacity="100%" paint-order="stroke"></rect><svg width="256px" height="256px" viewBox="0 0 24 24" fill="currentColor" x="128" y="128" role="img" style="display:inline-block;vertical-align:middle" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><g fill="currentColor"><path d="M4 7a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2v10a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V8a1 1 0 0 1-1-1zm3 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8H7z"/><path d="M11 5a1 1 0 0 0-1 1v1a1 1 0 0 1-2 0V6a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v1a1 1 0 1 1-2 0V6a1 1 0 0 0-1-1h-2zm-1 5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0v-5a1 1 0 0 1 1-1zm4 0a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0v-5a1 1 0 0 1 1-1z"/></g></g></svg></svg>