A very simple bookmarking webapp bookmarker.finxol.deno.net/

feat: improve new bookmark page

finxol.io 8a870c65 e50e5c63

verified
+209 -25
+1 -1
src/routes/index.tsx
··· 147 147 title="Delete bookmark" 148 148 onClick={() => 149 149 deleteBookmark.mutate( 150 - bookmark.id, 150 + String(bookmark.id), 151 151 )} 152 152 disabled={deleteBookmark.isPending} 153 153 >
+148
src/routes/new.css
··· 1 + .new-page { 2 + padding: calc(var(--spacing) * 2); 3 + max-width: 520px; 4 + margin: 0 auto; 5 + width: 100%; 6 + } 7 + 8 + .back-link { 9 + display: inline-flex; 10 + align-items: center; 11 + gap: calc(var(--spacing) * 0.5); 12 + font-size: 0.85rem; 13 + font-weight: 500; 14 + color: oklch(from var(--primary-text) l c h / 0.5); 15 + text-decoration: none; 16 + margin-bottom: calc(var(--spacing) * 3); 17 + transition: color 0.15s ease; 18 + 19 + .back-link:hover { 20 + color: var(--primary-text); 21 + } 22 + } 23 + 24 + .new-card { 25 + background: oklch(from var(--primary-text) l c h / 0.1); 26 + border: 1px solid oklch(from var(--primary-text) l c h / 0.1); 27 + border-radius: var(--radius); 28 + padding: calc(var(--spacing) * 3); 29 + 30 + h2 { 31 + font-size: 1.25rem; 32 + font-weight: 700; 33 + margin-bottom: calc(var(--spacing) * 0.5); 34 + } 35 + 36 + .new-subtitle { 37 + font-size: 0.875rem; 38 + color: oklch(from var(--primary-text) l c h / 0.7); 39 + margin-bottom: calc(var(--spacing) * 2.5); 40 + text-wrap: balance; 41 + text-wrap: pretty; 42 + } 43 + } 44 + 45 + .new-form { 46 + display: flex; 47 + flex-direction: column; 48 + gap: calc(var(--spacing) * 2); 49 + } 50 + 51 + .input-group { 52 + display: flex; 53 + flex-direction: column; 54 + gap: calc(var(--spacing) * 0.5); 55 + 56 + label { 57 + font-size: 0.8rem; 58 + font-weight: 600; 59 + color: oklch(from var(--primary-text) l c h / 0.7); 60 + } 61 + } 62 + 63 + .input-wrapper { 64 + position: relative; 65 + display: flex; 66 + align-items: center; 67 + 68 + .input-icon { 69 + position: absolute; 70 + left: calc(var(--spacing) * 1); 71 + color: oklch(from var(--primary-text) l c h / 0.3); 72 + pointer-events: none; 73 + } 74 + 75 + input { 76 + width: 100%; 77 + padding: calc(var(--spacing) * 1) calc(var(--spacing) * 1); 78 + padding-left: calc(var(--spacing) * 3); 79 + border: 1px solid oklch(from var(--primary-text) l c h / 0.15); 80 + border-radius: var(--radius); 81 + font-size: 0.9rem; 82 + color: var(--primary-text); 83 + background: transparent; 84 + outline: none; 85 + transition: 86 + border-color 0.15s ease, 87 + box-shadow 0.15s ease; 88 + 89 + &::placeholder { 90 + color: oklch(from var(--primary-text) l c h / 0.3); 91 + } 92 + 93 + &:focus { 94 + border-color: var(--primary); 95 + box-shadow: 0 0 0 3px oklch(from var(--primary) l c h / 0.12); 96 + } 97 + 98 + &:disabled { 99 + opacity: 0.6; 100 + cursor: not-allowed; 101 + } 102 + } 103 + } 104 + 105 + .submit-button { 106 + display: inline-flex; 107 + align-items: center; 108 + justify-content: center; 109 + gap: calc(var(--spacing) * 0.5); 110 + padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2); 111 + background-color: var(--primary); 112 + color: white; 113 + border: none; 114 + border-radius: var(--radius); 115 + font-weight: 600; 116 + font-size: 0.875rem; 117 + cursor: pointer; 118 + transition: opacity 0.15s ease; 119 + } 120 + 121 + .submit-button:hover:not(:disabled) { 122 + opacity: 0.85; 123 + } 124 + 125 + .submit-button:disabled { 126 + opacity: 0.5; 127 + cursor: not-allowed; 128 + } 129 + 130 + .error-message { 131 + margin-top: calc(var(--spacing) * 1.5); 132 + padding: calc(var(--spacing) * 1); 133 + background: oklch(0.65 0.25 25 / 0.08); 134 + border: 1px solid oklch(0.65 0.25 25 / 0.2); 135 + border-radius: var(--radius); 136 + color: oklch(0.5 0.2 25); 137 + font-size: 0.85rem; 138 + } 139 + 140 + .spinner { 141 + animation: spin 1s linear infinite; 142 + } 143 + 144 + @keyframes spin { 145 + to { 146 + transform: rotate(360deg); 147 + } 148 + }
+58 -22
src/routes/new.tsx
··· 1 1 import { createFileRoute, useNavigate } from "@tanstack/solid-router" 2 2 import { useMutation, useQueryClient } from "@tanstack/solid-query" 3 - import { createSignal } from "solid-js" 3 + import { createSignal, Show } from "solid-js" 4 4 import { client } from "../apiclient.ts" 5 + import { ArrowLeftIcon, LinkIcon, LoaderIcon } from "lucide-solid" 6 + import { Link } from "@tanstack/solid-router" 7 + import "./new.css" 5 8 6 9 export const Route = createFileRoute("/new")({ 7 10 component: RouteComponent, ··· 42 45 } 43 46 44 47 return ( 45 - <div> 46 - <h2>Add a new bookmark</h2> 47 - <form onSubmit={handleSubmit}> 48 - <label for="url">URL</label> 49 - <input 50 - id="url" 51 - type="url" 52 - required 53 - placeholder="https://example.com" 54 - value={url()} 55 - onInput={(e) => setUrl(e.currentTarget.value)} 56 - disabled={addBookmark.isPending} 57 - /> 58 - <button type="submit" disabled={addBookmark.isPending}> 59 - {addBookmark.isPending ? "Adding..." : "Add bookmark"} 60 - </button> 61 - </form> 62 - {addBookmark.isError && ( 63 - <p style="color: red;"> 64 - {addBookmark.error?.message ?? "Failed to add bookmark"} 48 + <div class="new-page"> 49 + <Link to="/" class="back-link"> 50 + <ArrowLeftIcon size={16} /> 51 + Back to bookmarks 52 + </Link> 53 + 54 + <div class="new-card"> 55 + <h2>Add a new bookmark</h2> 56 + <p class="new-subtitle"> 57 + Paste a URL and we'll grab the title, description, and image 58 + for you. 65 59 </p> 66 - )} 60 + 61 + <form onSubmit={handleSubmit} class="new-form"> 62 + <div class="input-group"> 63 + <label for="url">URL</label> 64 + <div class="input-wrapper"> 65 + <LinkIcon size={16} class="input-icon" /> 66 + <input 67 + id="url" 68 + type="url" 69 + required 70 + placeholder="https://example.com" 71 + value={url()} 72 + onInput={(e) => setUrl(e.currentTarget.value)} 73 + disabled={addBookmark.isPending} 74 + /> 75 + </div> 76 + </div> 77 + 78 + <button 79 + type="submit" 80 + class="submit-button" 81 + disabled={addBookmark.isPending || !url().trim()} 82 + > 83 + <Show 84 + when={!addBookmark.isPending} 85 + fallback={ 86 + <> 87 + <LoaderIcon size={16} class="spinner" /> 88 + Saving... 89 + </> 90 + } 91 + > 92 + Save bookmark 93 + </Show> 94 + </button> 95 + </form> 96 + 97 + <Show when={addBookmark.isError}> 98 + <div class="error-message"> 99 + {addBookmark.error?.message ?? "Failed to add bookmark"} 100 + </div> 101 + </Show> 102 + </div> 67 103 </div> 68 104 ) 69 105 }
+2 -2
src/routes/root.css
··· 20 20 --spacing: 0.75rem; 21 21 --radius: 0.5rem; 22 22 23 - @media screen and (prefers-color-scheme: dark) { 23 + @media screen and (prefers-color-scheme: light) { 24 24 --primary: var(--fuchsia-900); 25 - --primary-text: var(--fuchsia-300); 25 + --primary-text: var(--fuchsia-200); 26 26 --secondary: var(--fuchsia-900); 27 27 --secondary-text: var(--teal-300); 28 28 --accent: var(--orange-900);