diff --git a/src/AppwriteService.ts b/src/AppwriteService.ts
index d39a8b5..e049a9d 100644
--- a/src/AppwriteService.ts
+++ b/src/AppwriteService.ts
@@ -187,13 +187,20 @@ export const AppwriteService = {
)
).documents;
},
- listUserUpvotes: async (userId: string) => {
+ listUserUpvotes: async (userId: string, queries: string[] = []) => {
+ const defaultQueries = [
+ Query.equal("userId", userId),
+ Query.orderDesc("$createdAt"),
+ ];
+
+ queries = [...queries, ...defaultQueries];
+
return (
- await databases.listDocuments("main", "projectUpvotes", [
- Query.limit(100),
- Query.equal("userId", userId),
- Query.orderDesc("$createdAt"),
- ])
+ await databases.listDocuments(
+ "main",
+ "projectUpvotes",
+ queries
+ )
).documents;
},
uploadThumbnail: async (file: File) => {
diff --git a/src/components/blocks/Upvote.tsx b/src/components/blocks/Upvote.tsx
index 1728a7f..120c530 100644
--- a/src/components/blocks/Upvote.tsx
+++ b/src/components/blocks/Upvote.tsx
@@ -6,62 +6,55 @@ import {
useContext,
useSignal,
} from "@builder.io/qwik";
-import { AppwriteException } from "appwrite";
import { AppwriteService } from "~/AppwriteService";
import { AccountContext, UpvotesContext } from "~/routes/layout";
type Props = {
projectId: string;
votes: number;
- inline?: boolean;
};
export default component$((props: Props) => {
- const upvoteContext = useContext(UpvotesContext);
- const accountContext = useContext(AccountContext);
+ const upvotes = useContext(UpvotesContext);
+ const account = useContext(AccountContext);
const isLoading = useSignal(false);
const isUpvotedServer = useComputed$(() => {
- return !!upvoteContext.value.find(
- (upvote) => upvote.projectId === props.projectId
- );
+ return upvotes[props.projectId];
});
const isUpvotedClient = useSignal(isUpvotedServer.value);
const isUpvoted = useComputed$(() => {
return isLoading.value ? isUpvotedClient.value : isUpvotedServer.value;
});
- const votesServer = useSignal(props.votes);
- const votes = useComputed$(() => {
+ const countServer = useSignal(props.votes);
+ const count = useComputed$(() => {
if (isLoading.value) {
return isUpvotedClient.value
- ? votesServer.value + 1
- : votesServer.value - 1;
+ ? countServer.value + 1
+ : countServer.value - 1;
}
- return votesServer.value;
+ return countServer.value;
});
const upvoteProject = $(async (e: QwikMouseEvent) => {
e.stopPropagation();
+ if (!account.value) {
+ alert("Please sign in first.");
+ return;
+ }
+
isUpvotedClient.value = !isUpvotedServer.value;
isLoading.value = true;
try {
- const res = await AppwriteService.upvoteProject(props.projectId);
- if (accountContext.value) {
- upvoteContext.value = await AppwriteService.listUserUpvotes(
- accountContext.value.$id
- );
- }
- votesServer.value = res.votes;
+ const response = await AppwriteService.upvoteProject(props.projectId);
+ upvotes[props.projectId] = response.isUpvoted;
+ countServer.value = response.votes;
} catch (error: unknown) {
- if (error instanceof AppwriteException && error.code === 401) {
- alert("Please sign in first.");
- } else {
- alert("An unexpected error occurred.");
- }
+ alert("An unexpected error occurred.");
} finally {
isUpvotedClient.value = isUpvotedServer.value;
isLoading.value = false;
@@ -72,14 +65,14 @@ export default component$((props: Props) => {
);
});
diff --git a/src/components/hooks/useUpvotes.ts b/src/components/hooks/useUpvotes.ts
new file mode 100644
index 0000000..61fbf5c
--- /dev/null
+++ b/src/components/hooks/useUpvotes.ts
@@ -0,0 +1,34 @@
+import type { Signal } from "@builder.io/qwik";
+import { useContext, useVisibleTask$ } from "@builder.io/qwik";
+import { Query } from "appwrite";
+import { AppwriteService } from "~/AppwriteService";
+import { AccountContext, UpvotesContext } from "~/routes/layout";
+
+export function useUpvotes(projectIds: Signal) {
+ const upvotes = useContext(UpvotesContext);
+ const account = useContext(AccountContext);
+
+ useVisibleTask$(async ({ track }) => {
+ track(() => [account.value, projectIds.value]);
+ if (!account.value) {
+ return;
+ }
+
+ const newProjectIds = projectIds.value.filter(
+ (projectId) => upvotes[projectId] === undefined
+ );
+
+ if (newProjectIds.length === 0) {
+ return;
+ }
+
+ const projectUpvotes = await AppwriteService.listUserUpvotes(
+ account.value.$id,
+ [Query.equal("projectId", newProjectIds)]
+ );
+
+ projectUpvotes.forEach((upvote) => {
+ upvotes[upvote.projectId] = true;
+ });
+ });
+}
diff --git a/src/components/layout/Project.tsx b/src/components/layout/Project.tsx
index ecfe49f..d0c2ac1 100644
--- a/src/components/layout/Project.tsx
+++ b/src/components/layout/Project.tsx
@@ -49,7 +49,7 @@ export default component$((props: { project: Project | null }) => {
{project.name}
-
+
{
>
{project.name}
-
+
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 7bb989f..b3859bf 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -1,5 +1,6 @@
import {
component$,
+ useComputed$,
useContext,
useSignal,
useVisibleTask$,
@@ -15,7 +16,7 @@ import type { Project } from "~/AppwriteService";
import { AppwriteService } from "~/AppwriteService";
import { Query } from "appwrite";
import { AccountContext } from "./layout";
-import { UpvotesContext } from "./layout";
+import { useUpvotes } from "~/components/hooks/useUpvotes";
export const useHomeData = routeLoader$(async () => {
const [
@@ -91,17 +92,40 @@ export const head: DocumentHead = () => ({
export default component$(() => {
const account = useContext(AccountContext);
- const upvotes = useContext(UpvotesContext);
const homeDataSignal = useHomeData();
const homeData = homeDataSignal.value;
+ const allProjects = [
+ ...(homeData.featured ? [homeData.featured] : []),
+ ...(homeData.newAndShiny ?? []),
+ ...(homeData.trendZone ?? []),
+ ...(homeData.madeWithTailwind ?? []),
+ ];
+
+ const projectIds = useComputed$(() =>
+ allProjects.map((project) => project.$id)
+ );
+ useUpvotes(projectIds);
+
+ useVisibleTask$(async () => {
+ account.value = await AppwriteService.getAccount();
+ });
+
const yourPicks = useSignal
([]);
useVisibleTask$(async () => {
- if (!account.value || upvotes.value.length === 0) return;
+ if (!account.value) return;
+
+ const lastUpvotes = await AppwriteService.listUserUpvotes(
+ account.value.$id,
+ [Query.limit(3)]
+ );
+
+ if (lastUpvotes.length === 0) return;
+
yourPicks.value = await AppwriteService.listProjects([
Query.equal(
"$id",
- upvotes.value.slice(0, 3).map((upvote) => upvote.projectId)
+ lastUpvotes.map((upvote) => upvote.projectId)
),
]);
});
diff --git a/src/routes/layout.tsx b/src/routes/layout.tsx
index 879f68f..dfe24a0 100644
--- a/src/routes/layout.tsx
+++ b/src/routes/layout.tsx
@@ -13,22 +13,21 @@ import {
useTask$,
} from "@builder.io/qwik";
import { routeLoader$, useLocation, useNavigate } from "@builder.io/qwik-city";
-import type { Models } from "appwrite";
-import type { ProjectUpvote } from "~/AppwriteService";
+import { type Models } from "appwrite";
import { AppwriteService } from "~/AppwriteService";
import { Config } from "~/Config";
import Search from "~/components/blocks/Search";
import Footer from "~/components/layout/Footer";
import Header from "~/components/layout/Header";
-export const UpvotesContext = createContextId>(
- "app.upvotes-context"
-);
-
export const AccountContext = createContextId>>(
"app.account-context"
);
+export const UpvotesContext = createContextId>(
+ "app.upvotes-context"
+);
+
export const ThemeContext =
createContextId>("app.theme-context");
@@ -51,6 +50,9 @@ export default component$(() => {
const account = useSignal>(null);
useContextProvider(AccountContext, account);
+ const upvotes = useStore>({}, { deep: true });
+ useContextProvider(UpvotesContext, upvotes);
+
const themeData = useThemeLoader();
const theme = useSignal(themeData.value ?? "dark");
useContextProvider(ThemeContext, theme);
@@ -71,12 +73,8 @@ export default component$(() => {
document.documentElement.style.overflow = "auto";
}),
});
-
useContextProvider(SearchModalContext, searchModal);
- const upvotes = useSignal([]);
- useContextProvider(UpvotesContext, upvotes);
-
const searchModalRef = useSignal();
const onKeyDown = $((e: QwikKeyboardEvent) => {
if (e.key === "Escape") {
@@ -94,10 +92,6 @@ export default component$(() => {
useVisibleTask$(async () => {
account.value = await AppwriteService.getAccount();
-
- if (account.value) {
- upvotes.value = await AppwriteService.listUserUpvotes(account.value.$id);
- }
});
const openedFilter = useSignal(null);
diff --git a/src/routes/projects/[projectId]/index.tsx b/src/routes/projects/[projectId]/index.tsx
index 564b5bb..cae7020 100644
--- a/src/routes/projects/[projectId]/index.tsx
+++ b/src/routes/projects/[projectId]/index.tsx
@@ -1,4 +1,4 @@
-import { component$, useVisibleTask$ } from "@builder.io/qwik";
+import { component$, useComputed$, useVisibleTask$ } from "@builder.io/qwik";
import { AppwriteService } from "~/AppwriteService";
import type { DocumentHead } from "@builder.io/qwik-city";
import { routeLoader$, Link } from "@builder.io/qwik-city";
@@ -7,6 +7,7 @@ import ProjectTags from "~/components/layout/ProjectTags";
import Upvote from "~/components/blocks/Upvote";
import Socials from "~/components/blocks/Socials";
import { AppwriteException } from "appwrite";
+import { useUpvotes } from "~/components/hooks/useUpvotes";
export const useProjectData = routeLoader$(async ({ params, status }) => {
try {
@@ -91,6 +92,9 @@ export default component$(() => {
headerIds: false,
});
+ const projectIds = useComputed$(() => [project.$id]);
+ useUpvotes(projectIds);
+
useVisibleTask$(async () => {
const visitedProjects = JSON.parse(
localStorage.getItem("visitedProjects") ?? "[]"
@@ -117,7 +121,7 @@ export default component$(() => {
{project.name}
-
+
{project.tagline}
diff --git a/src/routes/search/index.tsx b/src/routes/search/index.tsx
index 0ee4443..da9c94c 100644
--- a/src/routes/search/index.tsx
+++ b/src/routes/search/index.tsx
@@ -1,9 +1,16 @@
-import { $, component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
+import {
+ $,
+ component$,
+ useComputed$,
+ useSignal,
+ useVisibleTask$,
+} from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
import { Link } from "@builder.io/qwik-city";
import { routeLoader$, useLocation } from "@builder.io/qwik-city";
import { Query } from "appwrite";
import { AppwriteService } from "~/AppwriteService";
+import { useUpvotes } from "~/components/hooks/useUpvotes";
import Group from "~/components/layout/Group";
import ProjectFeatured from "~/components/layout/ProjectFeatured";
@@ -126,6 +133,13 @@ export default component$(() => {
const searchData = useSearchData();
const projects = useSignal(searchData.value.projects);
+
+ const projectIds = useComputed$(() =>
+ projects.value.map((project) => project.$id)
+ );
+
+ useUpvotes(projectIds);
+
const location = useLocation();
const lastId = useSignal(