diff options
Diffstat (limited to '')
| -rw-r--r-- | .eslintrc.js | 3 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | packages/web/app/dashboard/bookmarks/components/AddLink.tsx | 26 | ||||
| -rw-r--r-- | packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx | 92 | ||||
| -rw-r--r-- | packages/web/app/dashboard/settings/components/AddApiKey.tsx | 18 | ||||
| -rw-r--r-- | packages/web/app/dashboard/settings/components/DeleteApiKey.tsx | 24 | ||||
| -rw-r--r-- | packages/web/app/layout.tsx | 7 | ||||
| -rw-r--r-- | packages/web/lib/providers.tsx | 33 | ||||
| -rw-r--r-- | packages/web/lib/trpc.tsx | 19 | ||||
| -rw-r--r-- | packages/web/package.json | 3 | ||||
| -rw-r--r-- | yarn.lock | 67 |
11 files changed, 197 insertions, 96 deletions
diff --git a/.eslintrc.js b/.eslintrc.js index a78fa42f..eccbab28 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,8 +13,9 @@ module.exports = { "plugin:@typescript-eslint/recommended", "next", "prettier", + "plugin:@tanstack/eslint-plugin-query/recommended", ], - plugins: ["tailwindcss", "@typescript-eslint"], + plugins: ["tailwindcss", "@typescript-eslint", "@tanstack/query"], parserOptions: { ecmaVersion: "latest", sourceType: "module", diff --git a/package.json b/package.json index ef92e2f1..c9b15e86 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "eslint-plugin-react-hooks": "^4.6.0" }, "devDependencies": { + "@tanstack/eslint-plugin-query": "^5.20.1", "@types/node": "^20", "es-errors": "^1.3.0", "eslint": "^8.56.0", diff --git a/packages/web/app/dashboard/bookmarks/components/AddLink.tsx b/packages/web/app/dashboard/bookmarks/components/AddLink.tsx index e8ecec35..34f043e7 100644 --- a/packages/web/app/dashboard/bookmarks/components/AddLink.tsx +++ b/packages/web/app/dashboard/bookmarks/components/AddLink.tsx @@ -17,21 +17,19 @@ const formSchema = z.object({ export default function AddLink() { const router = useRouter(); + const bookmarkLinkMutator = api.bookmarks.bookmarkLink.useMutation({ + onSuccess: () => { + router.refresh(); + }, + onError: () => { + toast({ description: "Something went wrong", variant: "destructive" }); + }, + }); const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), }); - async function onSubmit(value: z.infer<typeof formSchema>) { - try { - await api.bookmarks.bookmarkLink.mutate({ url: value.url, type: "link" }); - } catch (e) { - toast({ description: "Something went wrong", variant: "destructive" }); - return; - } - router.refresh(); - } - const onError: SubmitErrorHandler<z.infer<typeof formSchema>> = (errors) => { toast({ description: Object.values(errors) @@ -43,7 +41,13 @@ export default function AddLink() { return ( <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit, onError)}> + <form + onSubmit={form.handleSubmit( + (value) => + bookmarkLinkMutator.mutate({ url: value.url, type: "link" }), + onError, + )} + > <div className="container flex w-full items-center space-x-2 py-4"> <FormField control={form.control} diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx index 4123dc36..1360f966 100644 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx +++ b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx @@ -18,61 +18,45 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { const router = useRouter(); const linkId = bookmark.id; - const unbookmarkLink = async () => { - try { - await api.bookmarks.deleteBookmark.mutate({ - bookmarkId: linkId, - }); - + const onError = () => { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + }; + const onSettled = () => { + router.refresh(); + }; + const deleteBookmarkMutator = api.bookmarks.deleteBookmark.useMutation({ + onSuccess: () => { toast({ description: "The bookmark has been deleted!", }); - } catch (e) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - } - - router.refresh(); - }; + }, + onError, + onSettled, + }); - const updateBookmark = async (req: ZUpdateBookmarksRequest) => { - try { - await api.bookmarks.updateBookmark.mutate(req); + const updateBookmarkMutator = api.bookmarks.updateBookmark.useMutation({ + onSuccess: () => { toast({ description: "The bookmark has been updated!", }); - } catch (e) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - } + }, + onError, + onSettled, + }); - router.refresh(); - }; - - const crawlBookmark = async () => { - try { - await api.bookmarks.recrawlBookmark.mutate({ - bookmarkId: linkId, - }); + const crawlBookmarkMutator = api.bookmarks.recrawlBookmark.useMutation({ + onSuccess: () => { toast({ description: "Re-fetch has been enqueued!", }); - } catch (e) { - toast({ - variant: "destructive", - title: "Something went wrong", - description: "There was a problem with your request.", - }); - } - - router.refresh(); - }; + }, + onError, + onSettled, + }); return ( <DropdownMenu> @@ -84,7 +68,7 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { <DropdownMenuContent className="w-fit"> <DropdownMenuItem onClick={() => - updateBookmark({ + updateBookmarkMutator.mutate({ bookmarkId: linkId, favourited: !bookmark.favourited, }) @@ -95,17 +79,29 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { </DropdownMenuItem> <DropdownMenuItem onClick={() => - updateBookmark({ bookmarkId: linkId, archived: !bookmark.archived }) + updateBookmarkMutator.mutate({ + bookmarkId: linkId, + archived: !bookmark.archived, + }) } > <Archive className="mr-2 size-4" /> <span>{bookmark.archived ? "Un-archive" : "Archive"}</span> </DropdownMenuItem> - <DropdownMenuItem onClick={crawlBookmark}> + <DropdownMenuItem + onClick={() => + crawlBookmarkMutator.mutate({ bookmarkId: bookmark.id }) + } + > <RotateCw className="mr-2 size-4" /> <span>Refresh</span> </DropdownMenuItem> - <DropdownMenuItem className="text-destructive" onClick={unbookmarkLink}> + <DropdownMenuItem + className="text-destructive" + onClick={() => + deleteBookmarkMutator.mutate({ bookmarkId: bookmark.id }) + } + > <Trash2 className="mr-2 size-4" /> <span>Delete</span> </DropdownMenuItem> diff --git a/packages/web/app/dashboard/settings/components/AddApiKey.tsx b/packages/web/app/dashboard/settings/components/AddApiKey.tsx index f4f2894c..c438f4b1 100644 --- a/packages/web/app/dashboard/settings/components/AddApiKey.tsx +++ b/packages/web/app/dashboard/settings/components/AddApiKey.tsx @@ -53,20 +53,22 @@ function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { name: z.string(), }); const router = useRouter(); + const mutator = api.apiKeys.create.useMutation({ + onSuccess: (resp) => { + onSuccess(resp.key); + router.refresh(); + }, + onError: () => { + toast({ description: "Something went wrong", variant: "destructive" }); + }, + }); const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), }); async function onSubmit(value: z.infer<typeof formSchema>) { - try { - const resp = await api.apiKeys.create.mutate({ name: value.name }); - onSuccess(resp.key); - } catch (e) { - toast({ description: "Something went wrong", variant: "destructive" }); - return; - } - router.refresh(); + mutator.mutate({ name: value.name }); } const onError: SubmitErrorHandler<z.infer<typeof formSchema>> = (errors) => { diff --git a/packages/web/app/dashboard/settings/components/DeleteApiKey.tsx b/packages/web/app/dashboard/settings/components/DeleteApiKey.tsx index 715b7a2c..bc3e3c92 100644 --- a/packages/web/app/dashboard/settings/components/DeleteApiKey.tsx +++ b/packages/web/app/dashboard/settings/components/DeleteApiKey.tsx @@ -13,9 +13,9 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; -import { api } from "@/lib/trpc"; import { useRouter } from "next/navigation"; import { toast } from "@/components/ui/use-toast"; +import { api } from "@/lib/trpc"; export default function DeleteApiKey({ name, @@ -25,13 +25,15 @@ export default function DeleteApiKey({ id: string; }) { const router = useRouter(); - const deleteKey = async () => { - await api.apiKeys.revoke.mutate({ id }); - toast({ - description: "Key was successfully deleted", - }); - router.refresh(); - }; + const mutator = api.apiKeys.revoke.useMutation({ + onSuccess: () => { + toast({ + description: "Key was successfully deleted", + }); + router.refresh(); + }, + }); + return ( <Dialog> <DialogTrigger asChild> @@ -54,7 +56,11 @@ export default function DeleteApiKey({ </Button> </DialogClose> <DialogClose asChild> - <Button type="button" variant="destructive" onClick={deleteKey}> + <Button + type="button" + variant="destructive" + onClick={() => mutator.mutate({ id })} + > Delete </Button> </DialogClose> diff --git a/packages/web/app/layout.tsx b/packages/web/app/layout.tsx index a2d34046..f1971b20 100644 --- a/packages/web/app/layout.tsx +++ b/packages/web/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "./globals.css"; import React from "react"; import { Toaster } from "@/components/ui/toaster"; +import Providers from "@/lib/providers"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; const inter = Inter({ subsets: ["latin"] }); @@ -19,7 +21,10 @@ export default function RootLayout({ return ( <html lang="en"> <body className={inter.className}> - {children} + <Providers> + {children} + <ReactQueryDevtools initialIsOpen={false} /> + </Providers> <Toaster /> </body> </html> diff --git a/packages/web/lib/providers.tsx b/packages/web/lib/providers.tsx new file mode 100644 index 00000000..e81645dd --- /dev/null +++ b/packages/web/lib/providers.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { useState } from "react"; +import { api } from "./trpc"; +import { loggerLink } from "@trpc/client"; +import { httpBatchLink } from "@trpc/client"; + +export default function Providers({ children }: { children: React.ReactNode }) { + const [queryClient] = React.useState(() => new QueryClient()); + + const [trpcClient] = useState(() => + api.createClient({ + links: [ + loggerLink({ + enabled: (op) => + process.env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + httpBatchLink({ + // TODO: Change this to be a full URL exposed as a client side setting + url: `/api/trpc`, + }), + ], + }), + ); + + return ( + <api.Provider client={trpcClient} queryClient={queryClient}> + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> + </api.Provider> + ); +} diff --git a/packages/web/lib/trpc.tsx b/packages/web/lib/trpc.tsx index 540c6ab5..aa246047 100644 --- a/packages/web/lib/trpc.tsx +++ b/packages/web/lib/trpc.tsx @@ -1,20 +1,5 @@ "use client"; -import { httpBatchLink } from "@trpc/client"; import type { AppRouter } from "@/server/api/routers/_app"; +import { createTRPCReact } from "@trpc/react-query"; -import { loggerLink } from "@trpc/client"; -import { createTRPCClient } from "@trpc/client"; - -export const api = createTRPCClient<AppRouter>({ - links: [ - loggerLink({ - enabled: (op) => - process.env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), - }), - httpBatchLink({ - // TODO: Change this to be a full URL exposed as a client side setting - url: `/api/trpc`, - }), - ], -}); +export const api = createTRPCReact<AppRouter>(); diff --git a/packages/web/package.json b/packages/web/package.json index d66a3fe7..997e4042 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -20,8 +20,11 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@remember/db": "0.1.0", + "@tanstack/react-query": "^5.20.5", + "@tanstack/react-query-devtools": "^5.21.0", "@trpc/client": "11.0.0-next-beta.274", "@trpc/next": "11.0.0-next-beta.274", + "@trpc/react-query": "^11.0.0-next-beta.289", "@trpc/server": "11.0.0-next-beta.274", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", @@ -1649,8 +1649,11 @@ __metadata: "@radix-ui/react-slot": "npm:^1.0.2" "@radix-ui/react-toast": "npm:^1.1.5" "@remember/db": "npm:0.1.0" + "@tanstack/react-query": "npm:^5.20.5" + "@tanstack/react-query-devtools": "npm:^5.21.0" "@trpc/client": "npm:11.0.0-next-beta.274" "@trpc/next": "npm:11.0.0-next-beta.274" + "@trpc/react-query": "npm:^11.0.0-next-beta.289" "@trpc/server": "npm:11.0.0-next-beta.274" "@types/bcrypt": "npm:^5.0.2" "@types/react": "npm:^18" @@ -1973,6 +1976,54 @@ __metadata: languageName: node linkType: hard +"@tanstack/eslint-plugin-query@npm:^5.20.1": + version: 5.20.1 + resolution: "@tanstack/eslint-plugin-query@npm:5.20.1" + dependencies: + "@typescript-eslint/utils": "npm:^6.20.0" + peerDependencies: + eslint: ^8.0.0 + checksum: 10c0/318bdc734497a126873ac92f6f61b0e2f771b4d339edbc6d9c28c00852a100aba7b424213fb639bbbc6f9768cf1b75392d5c802afa9c1e82567d65fe8d217aeb + languageName: node + linkType: hard + +"@tanstack/query-core@npm:5.20.5": + version: 5.20.5 + resolution: "@tanstack/query-core@npm:5.20.5" + checksum: 10c0/e41b4961a3a2518536c72c1522d8512561645af2c2fe8ae49583820b3da6c4fe87112480d617202873ade9f6aa43aa78f986b5b7bbb3f0c5714fe536183de56e + languageName: node + linkType: hard + +"@tanstack/query-devtools@npm:5.21.0": + version: 5.21.0 + resolution: "@tanstack/query-devtools@npm:5.21.0" + checksum: 10c0/27f035660d2e537a28880b6a54c6602ec417c861d0f94a9dd090f7aa161dd890eb9e4edd4d41d9ea7a5821d5e02e4f9796258eced511dc5a767340536a7be366 + languageName: node + linkType: hard + +"@tanstack/react-query-devtools@npm:^5.21.0": + version: 5.21.0 + resolution: "@tanstack/react-query-devtools@npm:5.21.0" + dependencies: + "@tanstack/query-devtools": "npm:5.21.0" + peerDependencies: + "@tanstack/react-query": ^5.20.5 + react: ^18.0.0 + checksum: 10c0/8247ab372d9d115b3058b36e8124f8ca9c81872334a13995b946f0511d2d16a0e7174d6c8fda1a3a2cb535cf55acc9d75b40f15c6f8ecdea135bb5aac5988953 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.20.5": + version: 5.20.5 + resolution: "@tanstack/react-query@npm:5.20.5" + dependencies: + "@tanstack/query-core": "npm:5.20.5" + peerDependencies: + react: ^18.0.0 + checksum: 10c0/ca6cf3ee9702c15ed69246b5be04390910bfe8f04c1ebbab19a84102f610cdc7be6b8cc5ad8bc76ac59bc321c8d6d65cf96329c4e600e5e5938d6859162371d0 + languageName: node + linkType: hard + "@tootallnate/quickjs-emscripten@npm:^0.23.0": version: 0.23.0 resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" @@ -2009,6 +2060,19 @@ __metadata: languageName: node linkType: hard +"@trpc/react-query@npm:^11.0.0-next-beta.289": + version: 11.0.0-next-beta.289 + resolution: "@trpc/react-query@npm:11.0.0-next-beta.289" + peerDependencies: + "@tanstack/react-query": ^5.0.0 + "@trpc/client": 11.0.0-next-beta.289+27a9183d8 + "@trpc/server": 11.0.0-next-beta.289+27a9183d8 + react: ">=18.2.0" + react-dom: ">=18.2.0" + checksum: 10c0/9fd35444e22a379ec7f03f896ac25ff7a5f82e303948195ed491eade616cbfdb007e578bf84c4f73f8df787cfdbb937cf8a6dbbd35306591b6e3ac99e5066b7a + languageName: node + linkType: hard + "@trpc/server@npm:11.0.0-next-beta.274": version: 11.0.0-next-beta.274 resolution: "@trpc/server@npm:11.0.0-next-beta.274" @@ -2396,7 +2460,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:6.21.0": +"@typescript-eslint/utils@npm:6.21.0, @typescript-eslint/utils@npm:^6.20.0": version: 6.21.0 resolution: "@typescript-eslint/utils@npm:6.21.0" dependencies: @@ -7998,6 +8062,7 @@ __metadata: resolution: "remember@workspace:." dependencies: "@next/eslint-plugin-next": "npm:^14.1.0" + "@tanstack/eslint-plugin-query": "npm:^5.20.1" "@types/node": "npm:^20" "@typescript-eslint/eslint-plugin": "npm:^6.21.0" "@typescript-eslint/parser": "npm:^6.21.0" |
