aboutsummaryrefslogtreecommitdiffstats
path: root/packages/web
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-02-22 17:33:12 +0000
committerMohamedBassem <me@mbassem.com>2024-02-22 17:34:37 +0000
commit2ac3c39a9c80305bb959d88561e78f65a1cd1be1 (patch)
treeabdf860a648d691377914702f4d5c804e02fd341 /packages/web
parent61a1b2f40cf69d8c2055becf9119881cafa9da81 (diff)
downloadkarakeep-2ac3c39a9c80305bb959d88561e78f65a1cd1be1.tar.zst
feature: Adding some loading card while the link is getting crawled
Diffstat (limited to '')
-rw-r--r--packages/web/app/dashboard/bookmarks/components/AddLink.tsx16
-rw-r--r--packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx7
-rw-r--r--packages/web/app/dashboard/bookmarks/components/LinkCard.tsx85
-rw-r--r--packages/web/app/signin/components/CredentialsForm.tsx4
-rw-r--r--packages/web/lib/hooks/use-loading-card.ts9
-rw-r--r--packages/web/lib/types/api/bookmarks.ts3
-rw-r--r--packages/web/server/api/routers/bookmarks.ts25
7 files changed, 100 insertions, 49 deletions
diff --git a/packages/web/app/dashboard/bookmarks/components/AddLink.tsx b/packages/web/app/dashboard/bookmarks/components/AddLink.tsx
index 7663543f..242a52a5 100644
--- a/packages/web/app/dashboard/bookmarks/components/AddLink.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/AddLink.tsx
@@ -9,32 +9,24 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
import { ActionButton } from "@/components/ui/action-button";
-import { useLoadingCard } from "@/lib/hooks/use-loading-card";
const formSchema = z.object({
url: z.string().url({ message: "The link must be a valid URL" }),
});
export default function AddLink() {
- const { setLoading } = useLoadingCard();
+ const form = useForm<z.infer<typeof formSchema>>({
+ resolver: zodResolver(formSchema),
+ });
+
const invalidateBookmarksCache = api.useUtils().bookmarks.invalidate;
const bookmarkLinkMutator = api.bookmarks.bookmarkLink.useMutation({
- onMutate: () => {
- setLoading(true);
- },
onSuccess: () => {
invalidateBookmarksCache();
},
onError: () => {
toast({ description: "Something went wrong", variant: "destructive" });
},
- onSettled: () => {
- setLoading(false);
- },
- });
-
- const form = useForm<z.infer<typeof formSchema>>({
- resolver: zodResolver(formSchema),
});
const onError: SubmitErrorHandler<z.infer<typeof formSchema>> = (errors) => {
diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
index c1c8f3e0..dc98472e 100644
--- a/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/BookmarksGrid.tsx
@@ -1,7 +1,5 @@
"use client";
-import { useLoadingCard } from "@/lib/hooks/use-loading-card";
-import BookmarkCardSkeleton from "./BookmarkCardSkeleton";
import LinkCard from "./LinkCard";
import { ZBookmark, ZGetBookmarksRequest } from "@/lib/types/api/bookmarks";
import { api } from "@/lib/trpc";
@@ -13,8 +11,6 @@ function renderBookmark(bookmark: ZBookmark) {
}
}
-export const dynamic = "force-dynamic";
-
export default function BookmarksGrid({
query,
bookmarks: initialBookmarks,
@@ -24,12 +20,9 @@ export default function BookmarksGrid({
}) {
const { data } = api.bookmarks.getBookmarks.useQuery(query, {
initialData: { bookmarks: initialBookmarks },
- staleTime: Infinity,
});
- const { loading } = useLoadingCard();
return (
<div className="container grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
- {loading && <BookmarkCardSkeleton />}
{data.bookmarks.map((b) => renderBookmark(b))}
</div>
);
diff --git a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
index 7413c2fe..35696134 100644
--- a/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
+++ b/packages/web/app/dashboard/bookmarks/components/LinkCard.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { Badge } from "@/components/ui/badge";
import {
ImageCard,
@@ -10,9 +12,71 @@ import {
import { ZBookmark } from "@/lib/types/api/bookmarks";
import Link from "next/link";
import BookmarkOptions from "./BookmarkOptions";
+import { api } from "@/lib/trpc";
+import { Skeleton } from "@/components/ui/skeleton";
+
+function isStillCrawling(bookmark: ZBookmark) {
+ return (
+ !bookmark.content.crawledAt && Date.now() - bookmark.createdAt < 30 * 1000
+ );
+}
-export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) {
+function TagList(bookmark: ZBookmark, stillCrawling: boolean) {
+ if (stillCrawling) {
+ return (
+ <ImageCardBody className="space-y-2">
+ <Skeleton className="h-4 w-full" />
+ <Skeleton className="h-4 w-full" />
+ <Skeleton className="h-4 w-full" />
+ </ImageCardBody>
+ );
+ }
+ return (
+ <ImageCardBody className="flex h-full flex-wrap space-x-1 overflow-hidden">
+ {bookmark.tags.map((t) => (
+ <Link
+ className="flex h-full flex-col justify-end"
+ key={t.id}
+ href={`/dashboard/tags/${t.name}`}
+ >
+ <Badge
+ variant="default"
+ className="text-nowrap bg-gray-300 text-gray-500 hover:text-white"
+ >
+ #{t.name}
+ </Badge>
+ </Link>
+ ))}
+ </ImageCardBody>
+ );
+}
+
+export default function LinkCard({
+ bookmark: initialData,
+}: {
+ bookmark: ZBookmark;
+}) {
+ const { data: bookmark } = api.bookmarks.getBookmark.useQuery(
+ {
+ id: initialData.id,
+ },
+ {
+ initialData,
+ refetchInterval: (query) => {
+ const data = query.state.data;
+ if (!data) {
+ return false;
+ }
+ // If the link is not crawled and
+ if (isStillCrawling(data)) {
+ return 1000;
+ }
+ return false;
+ },
+ },
+ );
const link = bookmark.content;
+ const isCrawling = isStillCrawling(bookmark);
const parsedUrl = new URL(link.url);
// A dummy white pixel for when there's no image.
@@ -28,7 +92,7 @@ export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) {
}
>
<Link href={link.url}>
- <ImageCardBanner src={image} />
+ <ImageCardBanner src={isCrawling ? "/blur.avif" : image} />
</Link>
<ImageCardContent>
<ImageCardTitle>
@@ -38,22 +102,7 @@ export default function LinkCard({ bookmark }: { bookmark: ZBookmark }) {
</ImageCardTitle>
{/* There's a hack here. Every tag has the full hight of the container itself. That why, when we enable flex-wrap,
the overflowed don't show up. */}
- <ImageCardBody className="flex h-full flex-wrap space-x-1 overflow-hidden">
- {bookmark.tags.map((t) => (
- <Link
- className="flex h-full flex-col justify-end"
- key={t.id}
- href={`/dashboard/tags/${t.name}`}
- >
- <Badge
- variant="default"
- className="text-nowrap bg-gray-300 text-gray-500 hover:text-white"
- >
- #{t.name}
- </Badge>
- </Link>
- ))}
- </ImageCardBody>
+ {TagList(bookmark, isCrawling)}
<ImageCardFooter>
<div className="flex justify-between text-gray-500">
<div className="my-auto">
diff --git a/packages/web/app/signin/components/CredentialsForm.tsx b/packages/web/app/signin/components/CredentialsForm.tsx
index 60b61156..f47708f6 100644
--- a/packages/web/app/signin/components/CredentialsForm.tsx
+++ b/packages/web/app/signin/components/CredentialsForm.tsx
@@ -84,7 +84,7 @@ function SignIn() {
);
}}
/>
- <ActionButton type="submit" loading={false}>
+ <ActionButton type="submit" loading={form.formState.isSubmitting}>
Sign In
</ActionButton>
</div>
@@ -195,7 +195,7 @@ function SignUp() {
);
}}
/>
- <ActionButton type="submit" loading={false}>
+ <ActionButton type="submit" loading={form.formState.isSubmitting}>
Sign Up
</ActionButton>
</div>
diff --git a/packages/web/lib/hooks/use-loading-card.ts b/packages/web/lib/hooks/use-loading-card.ts
deleted file mode 100644
index bb32a8f1..00000000
--- a/packages/web/lib/hooks/use-loading-card.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { create } from "zustand";
-
-export const useLoadingCard = create<{
- loading: boolean;
- setLoading: (val: boolean) => void;
-}>((set) => ({
- loading: false,
- setLoading: (val: boolean) => set({ loading: val }),
-}));
diff --git a/packages/web/lib/types/api/bookmarks.ts b/packages/web/lib/types/api/bookmarks.ts
index 772ef153..94f89e55 100644
--- a/packages/web/lib/types/api/bookmarks.ts
+++ b/packages/web/lib/types/api/bookmarks.ts
@@ -8,6 +8,7 @@ export const zBookmarkedLinkSchema = z.object({
description: z.string().nullish(),
imageUrl: z.string().url().nullish(),
favicon: z.string().url().nullish(),
+ crawledAt: z.date().nullish(),
});
export type ZBookmarkedLink = z.infer<typeof zBookmarkedLinkSchema>;
@@ -18,7 +19,7 @@ export type ZBookmarkContent = z.infer<typeof zBookmarkContentSchema>;
export const zBookmarkSchema = z.object({
id: z.string(),
- createdAt: z.coerce.date(),
+ createdAt: z.date(),
archived: z.boolean(),
favourited: z.boolean(),
tags: z.array(zBookmarkTagSchema),
diff --git a/packages/web/server/api/routers/bookmarks.ts b/packages/web/server/api/routers/bookmarks.ts
index 37d12eb9..65f20ef5 100644
--- a/packages/web/server/api/routers/bookmarks.ts
+++ b/packages/web/server/api/routers/bookmarks.ts
@@ -26,6 +26,7 @@ const defaultBookmarkFields = {
description: true,
imageUrl: true,
favicon: true,
+ crawledAt: true,
},
},
tags: {
@@ -153,6 +154,30 @@ export const bookmarksAppRouter = router({
bookmarkId: input.bookmarkId,
});
}),
+ getBookmark: authedProcedure
+ .input(
+ z.object({
+ id: z.string(),
+ }),
+ )
+ .output(zBookmarkSchema)
+ .query(async ({ input, ctx }) => {
+ const bookmark = await prisma.bookmark.findUnique({
+ where: {
+ userId: ctx.user.id,
+ id: input.id,
+ },
+ select: defaultBookmarkFields,
+ });
+ if (!bookmark) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Bookmark not found",
+ });
+ }
+
+ return toZodSchema(bookmark);
+ }),
getBookmarks: authedProcedure
.input(zGetBookmarksRequestSchema)
.output(zGetBookmarksResponseSchema)