aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/lib
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-13 21:43:44 +0000
committerMohamed Bassem <me@mbassem.com>2024-03-14 16:40:45 +0000
commit04572a8e5081b1e4871e273cde9dbaaa44c52fe0 (patch)
tree8e993acb732a50d1306d4d6953df96c165c57f57 /apps/web/lib
parent2df08ed08c065e8b91bc8df0266bd4bcbb062be4 (diff)
downloadkarakeep-04572a8e5081b1e4871e273cde9dbaaa44c52fe0.tar.zst
structure: Create apps dir and copy tooling dir from t3-turbo repo
Diffstat (limited to 'apps/web/lib')
-rw-r--r--apps/web/lib/bookmarkUtils.tsx22
-rw-r--r--apps/web/lib/hooks/bookmark-search.ts73
-rw-r--r--apps/web/lib/providers.tsx75
-rw-r--r--apps/web/lib/trpc.tsx5
-rw-r--r--apps/web/lib/utils.ts6
5 files changed, 181 insertions, 0 deletions
diff --git a/apps/web/lib/bookmarkUtils.tsx b/apps/web/lib/bookmarkUtils.tsx
new file mode 100644
index 00000000..a2828c29
--- /dev/null
+++ b/apps/web/lib/bookmarkUtils.tsx
@@ -0,0 +1,22 @@
+import { ZBookmark } from "@hoarder/trpc/types/bookmarks";
+
+const MAX_LOADING_MSEC = 30 * 1000;
+
+export function isBookmarkStillCrawling(bookmark: ZBookmark) {
+ return (
+ bookmark.content.type == "link" &&
+ !bookmark.content.crawledAt &&
+ Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
+ );
+}
+
+export function isBookmarkStillTagging(bookmark: ZBookmark) {
+ return (
+ bookmark.taggingStatus == "pending" &&
+ Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC
+ );
+}
+
+export function isBookmarkStillLoading(bookmark: ZBookmark) {
+ return isBookmarkStillTagging(bookmark) || isBookmarkStillCrawling(bookmark);
+}
diff --git a/apps/web/lib/hooks/bookmark-search.ts b/apps/web/lib/hooks/bookmark-search.ts
new file mode 100644
index 00000000..738e1bd8
--- /dev/null
+++ b/apps/web/lib/hooks/bookmark-search.ts
@@ -0,0 +1,73 @@
+import { useEffect, useState } from "react";
+import { api } from "@/lib/trpc";
+import { useRouter, useSearchParams } from "next/navigation";
+import { keepPreviousData } from "@tanstack/react-query";
+
+function useSearchQuery() {
+ const searchParams = useSearchParams();
+ const searchQuery = searchParams.get("q") || "";
+ return { searchQuery };
+}
+
+export function useDoBookmarkSearch() {
+ const router = useRouter();
+ const { searchQuery } = useSearchQuery();
+ const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | undefined>();
+
+ useEffect(() => {
+ return () => {
+ if (!timeoutId) {
+ return;
+ }
+ clearTimeout(timeoutId);
+ };
+ }, [timeoutId]);
+
+ const doSearch = (val: string) => {
+ setTimeoutId(undefined);
+ router.replace(`/dashboard/search?q=${val}`);
+ };
+
+ const debounceSearch = (val: string) => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ const id = setTimeout(() => {
+ doSearch(val);
+ }, 200);
+ setTimeoutId(id);
+ };
+
+ return {
+ doSearch,
+ debounceSearch,
+ searchQuery,
+ };
+}
+
+export function useBookmarkSearch() {
+ const { searchQuery } = useSearchQuery();
+
+ const { data, isPending, isPlaceholderData, error } =
+ api.bookmarks.searchBookmarks.useQuery(
+ {
+ text: searchQuery,
+ },
+ {
+ placeholderData: keepPreviousData,
+ gcTime: 0,
+ },
+ );
+
+ if (error) {
+ throw error;
+ }
+
+ return {
+ searchQuery,
+ error,
+ data,
+ isPending,
+ isPlaceholderData,
+ };
+}
diff --git a/apps/web/lib/providers.tsx b/apps/web/lib/providers.tsx
new file mode 100644
index 00000000..5c4649b5
--- /dev/null
+++ b/apps/web/lib/providers.tsx
@@ -0,0 +1,75 @@
+"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";
+import superjson from "superjson";
+import { SessionProvider } from "next-auth/react";
+import { Session } from "next-auth";
+
+function makeQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ // With SSR, we usually want to set some default staleTime
+ // above 0 to avoid refetching immediately on the client
+ staleTime: 60 * 1000,
+ },
+ },
+ });
+}
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+function getQueryClient() {
+ if (typeof window === "undefined") {
+ // Server: always make a new query client
+ return makeQueryClient();
+ } else {
+ // Browser: make a new query client if we don't already have one
+ // This is very important so we don't re-make a new client if React
+ // supsends during the initial render. This may not be needed if we
+ // have a suspense boundary BELOW the creation of the query client
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
+ return browserQueryClient;
+ }
+}
+
+export default function Providers({
+ children,
+ session,
+}: {
+ children: React.ReactNode;
+ session: Session | null;
+}) {
+ const queryClient = getQueryClient();
+
+ 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`,
+ transformer: superjson,
+ }),
+ ],
+ }),
+ );
+
+ return (
+ <SessionProvider session={session}>
+ <api.Provider client={trpcClient} queryClient={queryClient}>
+ <QueryClientProvider client={queryClient}>
+ {children}
+ </QueryClientProvider>
+ </api.Provider>
+ </SessionProvider>
+ );
+}
diff --git a/apps/web/lib/trpc.tsx b/apps/web/lib/trpc.tsx
new file mode 100644
index 00000000..79a2a9fe
--- /dev/null
+++ b/apps/web/lib/trpc.tsx
@@ -0,0 +1,5 @@
+"use client";
+import type { AppRouter } from "@hoarder/trpc/routers/_app";
+import { createTRPCReact } from "@trpc/react-query";
+
+export const api = createTRPCReact<AppRouter>();
diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts
new file mode 100644
index 00000000..365058ce
--- /dev/null
+++ b/apps/web/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}