A very simple bookmarking webapp bookmarker.finxol.deno.net/
at main 150 lines 5.5 kB view raw
1import { LoaderIcon, PencilIcon, XIcon } from "lucide-solid" 2import { useMutation, useQueryClient } from "@tanstack/solid-query" 3import { createSignal, Show } from "solid-js" 4import { client } from "../apiclient.ts" 5import { Bookmark } from "../../server/utils/bookmarks.ts" 6import "./BookmarkEditModal.css" 7import "./ui/button.css" 8import { useWebHaptics } from "../utils/haptics.ts" 9 10interface BookmarkEditModalProps { 11 bookmark: Bookmark & { id: string } 12} 13 14export function BookmarkEditModal(props: BookmarkEditModalProps) { 15 const haptics = useWebHaptics() 16 17 const queryClient = useQueryClient() 18 const [title, setTitle] = createSignal(props.bookmark.title) 19 const [description, setDescription] = createSignal( 20 props.bookmark.description, 21 ) 22 23 const editBookmark = useMutation(() => ({ 24 mutationFn: async () => { 25 const res = await client.api.v1.bookmarks[":id"].$put({ 26 param: { id: props.bookmark.id }, 27 json: { 28 title: title().trim(), 29 description: description().trim(), 30 }, 31 }) 32 if (!res.ok) { 33 const data = await res.json() 34 haptics.trigger([ 35 { duration: 40, intensity: 0.7 }, 36 { delay: 40, duration: 40, intensity: 0.7 }, 37 { delay: 40, duration: 40, intensity: 0.9 }, 38 { delay: 40, duration: 50, intensity: 0.6 }, 39 ]) 40 throw new Error( 41 "error" in data ? data.error : "Failed to update bookmark", 42 ) 43 } 44 return await res.json() 45 }, 46 onSuccess: async () => { 47 await queryClient.invalidateQueries({ 48 queryKey: [client.api.v1.bookmarks.all.$url().pathname], 49 }) 50 // Close the modal 51 const dialog = document.getElementById( 52 `edit-${props.bookmark.id}`, 53 ) as HTMLDialogElement | null 54 dialog?.requestClose() 55 }, 56 })) 57 58 const handleSubmit = (e: SubmitEvent) => { 59 e.preventDefault() 60 const titleValue = title().trim() 61 if (titleValue) { 62 editBookmark.mutate() 63 } 64 } 65 66 return ( 67 <> 68 <button 69 type="button" 70 command="show-modal" 71 commandfor={`edit-${props.bookmark.id}`} 72 class="bookmark-edit button-icon button-ghost" 73 onClick={() => haptics.trigger()} 74 > 75 <PencilIcon size={16} /> 76 </button> 77 <dialog 78 id={`edit-${props.bookmark.id}`} 79 class="bookmark-edit-modal" 80 > 81 <div class="title"> 82 <h2>Edit bookmark</h2> 83 84 <button 85 type="button" 86 commandfor={`edit-${props.bookmark.id}`} 87 command="close" 88 class="button-ghost button-icon" 89 > 90 <XIcon size={16} /> 91 </button> 92 </div> 93 94 <form onSubmit={handleSubmit} class="edit-form"> 95 <div class="input-group"> 96 <label for={`title-${props.bookmark.id}`}>Title</label> 97 <input 98 id={`title-${props.bookmark.id}`} 99 type="text" 100 required 101 value={title()} 102 onInput={(e) => setTitle(e.currentTarget.value)} 103 disabled={editBookmark.isPending} 104 maxlength="200" 105 /> 106 </div> 107 108 <div class="input-group"> 109 <label for={`description-${props.bookmark.id}`}> 110 Description 111 </label> 112 <textarea 113 id={`description-${props.bookmark.id}`} 114 value={description()} 115 onInput={(e) => 116 setDescription(e.currentTarget.value)} 117 disabled={editBookmark.isPending} 118 maxlength="500" 119 /> 120 </div> 121 122 <button 123 type="submit" 124 class="button" 125 disabled={editBookmark.isPending || !title()?.trim()} 126 > 127 <Show 128 when={!editBookmark.isPending} 129 fallback={ 130 <> 131 <LoaderIcon size={16} class="spinner" /> 132 Saving... 133 </> 134 } 135 > 136 Save changes 137 </Show> 138 </button> 139 </form> 140 141 <Show when={editBookmark.isError}> 142 <div class="error-message"> 143 {editBookmark.error?.message ?? 144 "Failed to update bookmark"} 145 </div> 146 </Show> 147 </dialog> 148 </> 149 ) 150}