Skip to content

Commit

Permalink
Merge pull request #30 from appwrite/fix-upvotes
Browse files Browse the repository at this point in the history
fix: upvotes bug
  • Loading branch information
loks0n authored Jun 16, 2023
2 parents e6c65c8 + 9b225ff commit 7fb0d84
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 59 deletions.
19 changes: 13 additions & 6 deletions src/AppwriteService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProjectUpvote>("main", "projectUpvotes", [
Query.limit(100),
Query.equal("userId", userId),
Query.orderDesc("$createdAt"),
])
await databases.listDocuments<ProjectUpvote>(
"main",
"projectUpvotes",
queries
)
).documents;
},
uploadThumbnail: async (file: File) => {
Expand Down
45 changes: 19 additions & 26 deletions src/components/blocks/Upvote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -72,14 +65,14 @@ export default component$((props: Props) => {
<button
preventdefault:click
onClick$={upvoteProject}
class={`button upvote-button ${props.inline ? "" : "vertical-button"} ${
class={`button upvote-button ${
isUpvoted.value ? "is-primary" : "is-secondary"
}`}
aria-label="Upvote"
aria-pressed={isUpvoted.value}
>
<span class="icon-heart" aria-hidden="true"></span>
<span class="text">{votes.value}</span>
<span class="text">{count.value}</span>
</button>
);
});
34 changes: 34 additions & 0 deletions src/components/hooks/useUpvotes.ts
Original file line number Diff line number Diff line change
@@ -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<string[]>) {
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;
});
});
}
2 changes: 1 addition & 1 deletion src/components/layout/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default component$((props: { project: Project | null }) => {
{project.name}
</p>
</Link>
<Upvote projectId={project.$id} votes={project.upvotes} inline />
<Upvote projectId={project.$id} votes={project.upvotes} />
</div>
<Link href={`/projects/${project.$id}`}>
<p
Expand Down
6 changes: 1 addition & 5 deletions src/components/layout/ProjectFeatured.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ export default component$((props: { project: Project }) => {
>
{project.name}
</Link>
<Upvote
projectId={project.$id}
inline={true}
votes={project.upvotes}
/>
<Upvote projectId={project.$id} votes={project.upvotes} />
</div>

<div class="u-stretch">
Expand Down
32 changes: 28 additions & 4 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
component$,
useComputed$,
useContext,
useSignal,
useVisibleTask$,
Expand All @@ -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 [
Expand Down Expand Up @@ -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<Project[] | undefined>([]);
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)
),
]);
});
Expand Down
22 changes: 8 additions & 14 deletions src/routes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signal<ProjectUpvote[]>>(
"app.upvotes-context"
);

export const AccountContext = createContextId<Signal<null | Models.User<any>>>(
"app.account-context"
);

export const UpvotesContext = createContextId<Record<string, boolean>>(
"app.upvotes-context"
);

export const ThemeContext =
createContextId<Signal<"light" | "dark">>("app.theme-context");

Expand All @@ -51,6 +50,9 @@ export default component$(() => {
const account = useSignal<null | Models.User<any>>(null);
useContextProvider(AccountContext, account);

const upvotes = useStore<Record<string, boolean>>({}, { deep: true });
useContextProvider(UpvotesContext, upvotes);

const themeData = useThemeLoader();
const theme = useSignal(themeData.value ?? "dark");
useContextProvider(ThemeContext, theme);
Expand All @@ -71,12 +73,8 @@ export default component$(() => {
document.documentElement.style.overflow = "auto";
}),
});

useContextProvider(SearchModalContext, searchModal);

const upvotes = useSignal<ProjectUpvote[]>([]);
useContextProvider(UpvotesContext, upvotes);

const searchModalRef = useSignal<HTMLDialogElement>();
const onKeyDown = $((e: QwikKeyboardEvent) => {
if (e.key === "Escape") {
Expand All @@ -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<string | null>(null);
Expand Down
8 changes: 6 additions & 2 deletions src/routes/projects/[projectId]/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 {
Expand Down Expand Up @@ -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") ?? "[]"
Expand All @@ -117,7 +121,7 @@ export default component$(() => {

<div class="u-flex u-gap-16 u-cross-center">
<h2 class="heading-level-2">{project.name}</h2>
<Upvote projectId={project.$id} votes={project.upvotes} inline />
<Upvote projectId={project.$id} votes={project.upvotes} />
</div>

<p style="font-size: 1.2rem; margin-top: -1rem;">{project.tagline}</p>
Expand Down
16 changes: 15 additions & 1 deletion src/routes/search/index.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<string | null>(
Expand Down

1 comment on commit 7fb0d84

@vercel
Copy link

@vercel vercel bot commented on 7fb0d84 Jun 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.