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

feat: add new bookmark page

finxol.io 85fb413c 546483e9

verified
+99 -7
+21 -3
src/routeTree.gen.ts
··· 9 9 // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 10 11 11 import { Route as rootRouteImport } from './routes/__root' 12 + import { Route as NewRouteImport } from './routes/new' 12 13 import { Route as AccountRouteImport } from './routes/account' 13 14 import { Route as AboutRouteImport } from './routes/about' 14 15 import { Route as IndexRouteImport } from './routes/index' 15 16 17 + const NewRoute = NewRouteImport.update({ 18 + id: '/new', 19 + path: '/new', 20 + getParentRoute: () => rootRouteImport, 21 + } as any) 16 22 const AccountRoute = AccountRouteImport.update({ 17 23 id: '/account', 18 24 path: '/account', ··· 33 39 '/': typeof IndexRoute 34 40 '/about': typeof AboutRoute 35 41 '/account': typeof AccountRoute 42 + '/new': typeof NewRoute 36 43 } 37 44 export interface FileRoutesByTo { 38 45 '/': typeof IndexRoute 39 46 '/about': typeof AboutRoute 40 47 '/account': typeof AccountRoute 48 + '/new': typeof NewRoute 41 49 } 42 50 export interface FileRoutesById { 43 51 __root__: typeof rootRouteImport 44 52 '/': typeof IndexRoute 45 53 '/about': typeof AboutRoute 46 54 '/account': typeof AccountRoute 55 + '/new': typeof NewRoute 47 56 } 48 57 export interface FileRouteTypes { 49 58 fileRoutesByFullPath: FileRoutesByFullPath 50 - fullPaths: '/' | '/about' | '/account' 59 + fullPaths: '/' | '/about' | '/account' | '/new' 51 60 fileRoutesByTo: FileRoutesByTo 52 - to: '/' | '/about' | '/account' 53 - id: '__root__' | '/' | '/about' | '/account' 61 + to: '/' | '/about' | '/account' | '/new' 62 + id: '__root__' | '/' | '/about' | '/account' | '/new' 54 63 fileRoutesById: FileRoutesById 55 64 } 56 65 export interface RootRouteChildren { 57 66 IndexRoute: typeof IndexRoute 58 67 AboutRoute: typeof AboutRoute 59 68 AccountRoute: typeof AccountRoute 69 + NewRoute: typeof NewRoute 60 70 } 61 71 62 72 declare module '@tanstack/solid-router' { 63 73 interface FileRoutesByPath { 74 + '/new': { 75 + id: '/new' 76 + path: '/new' 77 + fullPath: '/new' 78 + preLoaderRoute: typeof NewRouteImport 79 + parentRoute: typeof rootRouteImport 80 + } 64 81 '/account': { 65 82 id: '/account' 66 83 path: '/account' ··· 89 106 IndexRoute: IndexRoute, 90 107 AboutRoute: AboutRoute, 91 108 AccountRoute: AccountRoute, 109 + NewRoute: NewRoute, 92 110 } 93 111 export const routeTree = rootRouteImport 94 112 ._addFileChildren(rootRouteChildren)
+8 -3
src/routes/__root.tsx
··· 25 25 return ( 26 26 <main> 27 27 <header> 28 - <Link to="/"> 29 - Bookmarker 30 - </Link> 28 + <div> 29 + <Link to="/"> 30 + Bookmarker 31 + </Link> 32 + <Link to="/new"> 33 + New Bookmark 34 + </Link> 35 + </div> 31 36 {!query.isPending && query.data && 32 37 ( 33 38 <Link to="/account">
+1 -1
src/routes/index.tsx
··· 9 9 10 10 function Index() { 11 11 const query = useQuery(() => ({ 12 - queryKey: [client.api.v1.bookmarks.all.$url()], 12 + queryKey: [client.api.v1.bookmarks.all.$url().pathname], 13 13 queryFn: async () => { 14 14 const res = await client.api.v1.bookmarks.all.$get() 15 15 if (res.ok) {
+69
src/routes/new.tsx
··· 1 + import { createFileRoute, useNavigate } from "@tanstack/solid-router" 2 + import { useMutation, useQueryClient } from "@tanstack/solid-query" 3 + import { createSignal } from "solid-js" 4 + import { client } from "../apiclient.ts" 5 + 6 + export const Route = createFileRoute("/new")({ 7 + component: RouteComponent, 8 + }) 9 + 10 + function RouteComponent() { 11 + const queryClient = useQueryClient() 12 + const navigate = useNavigate() 13 + const [url, setUrl] = createSignal("") 14 + 15 + const addBookmark = useMutation(() => ({ 16 + mutationFn: async (url: string) => { 17 + const res = await client.api.v1.bookmarks.add.$post({ 18 + form: { url }, 19 + }) 20 + if (!res.ok) { 21 + const data = await res.json() 22 + throw new Error( 23 + "error" in data ? data.error : "Failed to add bookmark", 24 + ) 25 + } 26 + return await res.json() 27 + }, 28 + onSuccess: async () => { 29 + await queryClient.invalidateQueries({ 30 + queryKey: [client.api.v1.bookmarks.all.$url().pathname], 31 + }) 32 + navigate({ to: "/" }) 33 + }, 34 + })) 35 + 36 + const handleSubmit = (e: SubmitEvent) => { 37 + e.preventDefault() 38 + const value = url().trim() 39 + if (value) { 40 + addBookmark.mutate(value) 41 + } 42 + } 43 + 44 + 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"} 65 + </p> 66 + )} 67 + </div> 68 + ) 69 + }