Skip to content

Commit 3027f0b

Browse files
add components
1 parent 2b64efa commit 3027f0b

16 files changed

+2979
-17
lines changed

package-lock.json

Lines changed: 2038 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
},
1111
"dependencies": {
1212
"@radix-ui/react-label": "^2.1.2",
13+
"@radix-ui/react-slot": "^1.1.2",
1314
"@tabler/icons": "^3.31.0",
1415
"@tabler/icons-react": "^3.31.0",
16+
"@types/canvas-confetti": "^1.9.0",
17+
"@uiw/react-md-editor": "^4.0.5",
1518
"appwrite": "^17.0.1",
19+
"canvas-confetti": "^1.9.3",
1620
"class-variance-authority": "^0.7.1",
1721
"clsx": "^2.1.1",
1822
"framer-motion": "^12.5.0",

src/app/api/vote/route.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { answerCollection, db, questionCollection, voteCollection } from "@/models/name";
2+
import { databases, users } from "@/models/server/config";
3+
import { UserPrefs } from "@/store/Auth";
4+
import { NextRequest, NextResponse } from "next/server";
5+
import { ID, Query } from "node-appwrite";
6+
7+
export async function POST(request: NextRequest) {
8+
try {
9+
const { votedById, voteStatus, type, typeId } = await request.json();
10+
11+
const response = await databases.listDocuments(db, voteCollection, [
12+
Query.equal("type", type),
13+
Query.equal("typeId", typeId),
14+
Query.equal("votedById", votedById),
15+
]);
16+
17+
if (response.documents.length > 0) {
18+
await databases.deleteDocument(db, voteCollection, response.documents[0].$id);
19+
20+
// Decrease the reputation of the question/answer author
21+
const questionOrAnswer = await databases.getDocument(
22+
db,
23+
type === "question" ? questionCollection : answerCollection,
24+
typeId
25+
);
26+
27+
const authorPrefs = await users.getPrefs<UserPrefs>(questionOrAnswer.authorId);
28+
29+
await users.updatePrefs<UserPrefs>(questionOrAnswer.authorId, {
30+
reputation:
31+
response.documents[0].voteStatus === "upvoted"
32+
? Number(authorPrefs.reputation) - 1
33+
: Number(authorPrefs.reputation) + 1,
34+
});
35+
}
36+
37+
// that means prev vote does not exists or voteStatus changed
38+
if (response.documents[0]?.voteStatus !== voteStatus) {
39+
const doc = await databases.createDocument(db, voteCollection, ID.unique(), {
40+
type,
41+
typeId,
42+
voteStatus,
43+
votedById,
44+
});
45+
46+
// Increate/Decrease the reputation of the question/answer author accordingly
47+
const questionOrAnswer = await databases.getDocument(
48+
db,
49+
type === "question" ? questionCollection : answerCollection,
50+
typeId
51+
);
52+
53+
const authorPrefs = await users.getPrefs<UserPrefs>(questionOrAnswer.authorId);
54+
55+
// if vote was present
56+
if (response.documents[0]) {
57+
await users.updatePrefs<UserPrefs>(questionOrAnswer.authorId, {
58+
reputation:
59+
// that means prev vote was "upvoted" and new value is "downvoted" so we have to decrease the reputation
60+
response.documents[0].voteStatus === "upvoted"
61+
? Number(authorPrefs.reputation) - 1
62+
: Number(authorPrefs.reputation) + 1,
63+
});
64+
} else {
65+
await users.updatePrefs<UserPrefs>(questionOrAnswer.authorId, {
66+
reputation:
67+
// that means prev vote was "upvoted" and new value is "downvoted" so we have to decrease the reputation
68+
voteStatus === "upvoted"
69+
? Number(authorPrefs.reputation) + 1
70+
: Number(authorPrefs.reputation) - 1,
71+
});
72+
}
73+
74+
const [upvotes, downvotes] = await Promise.all([
75+
databases.listDocuments(db, voteCollection, [
76+
Query.equal("type", type),
77+
Query.equal("typeId", typeId),
78+
Query.equal("voteStatus", "upvoted"),
79+
Query.equal("votedById", votedById),
80+
Query.limit(1), // for optimization as we only need total
81+
]),
82+
databases.listDocuments(db, voteCollection, [
83+
Query.equal("type", type),
84+
Query.equal("typeId", typeId),
85+
Query.equal("voteStatus", "downvoted"),
86+
Query.equal("votedById", votedById),
87+
Query.limit(1), // for optimization as we only need total
88+
]),
89+
]);
90+
91+
return NextResponse.json(
92+
{
93+
data: { document: doc, voteResult: upvotes.total - downvotes.total },
94+
message: response.documents[0] ? "Vote Status Updated" : "Voted",
95+
},
96+
{
97+
status: 201,
98+
}
99+
);
100+
}
101+
102+
const [upvotes, downvotes] = await Promise.all([
103+
databases.listDocuments(db, voteCollection, [
104+
Query.equal("type", type),
105+
Query.equal("typeId", typeId),
106+
Query.equal("voteStatus", "upvoted"),
107+
Query.equal("votedById", votedById),
108+
Query.limit(1), // for optimization as we only need total
109+
]),
110+
databases.listDocuments(db, voteCollection, [
111+
Query.equal("type", type),
112+
Query.equal("typeId", typeId),
113+
Query.equal("voteStatus", "downvoted"),
114+
Query.equal("votedById", votedById),
115+
Query.limit(1), // for optimization as we only need total
116+
]),
117+
]);
118+
119+
return NextResponse.json(
120+
{
121+
data: {
122+
document: null, voteResult: upvotes.total - downvotes.total
123+
},
124+
message: "Vote Withdrawn",
125+
},
126+
{
127+
status: 200,
128+
}
129+
);
130+
} catch (error: any) {
131+
return NextResponse.json(
132+
{ message: error?.message || "Error deleting answer" },
133+
{ status: error?.status || error?.code || 500 }
134+
);
135+
}
136+
}

src/components/Answers.tsx

Whitespace-only changes.

src/components/Comments.tsx

Whitespace-only changes.

src/components/Pagination.tsx

Whitespace-only changes.

src/components/QuestionCard.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { BorderBeam } from "./magicui/border-beam";
5+
import Link from "next/link";
6+
import { Models } from "appwrite";
7+
import slugify from "@/utils/slugify";
8+
import { avatars } from "@/models/client/config";
9+
import convertDateToRelativeTime from "@/utils/relativeTime";
10+
11+
const QuestionCard = ({ ques }: { ques: Models.Document }) => {
12+
const [height, setHeight] = React.useState(0);
13+
const ref = React.useRef<HTMLDivElement>(null);
14+
15+
React.useEffect(() => {
16+
if (ref.current) {
17+
setHeight(ref.current.clientHeight);
18+
}
19+
}, [ref]);
20+
21+
return (
22+
<div
23+
ref={ref}
24+
className="relative flex flex-col gap-4 overflow-hidden rounded-xl border border-white/20 bg-white/5 p-4 duration-200 hover:bg-white/10 sm:flex-row"
25+
>
26+
<BorderBeam size={height} duration={12} delay={9} />
27+
<div className="relative shrink-0 text-sm sm:text-right">
28+
<p>{ques.totalVotes} votes</p>
29+
<p>{ques.totalAnswers} answers</p>
30+
</div>
31+
<div className="relative w-full">
32+
<Link
33+
href={`/questions/${ques.$id}/${slugify(ques.title)}`}
34+
className="text-orange-500 duration-200 hover:text-orange-600"
35+
>
36+
<h2 className="text-xl">{ques.title}</h2>
37+
</Link>
38+
<div className="mt-3 flex flex-wrap items-center gap-3 text-sm">
39+
{ques.tags.map((tag: string) => (
40+
<Link
41+
key={tag}
42+
href={`/questions?tag=${tag}`}
43+
className="inline-block rounded-lg bg-white/10 px-2 py-0.5 duration-200 hover:bg-white/20"
44+
>
45+
#{tag}
46+
</Link>
47+
))}
48+
<div className="ml-auto flex items-center gap-1">
49+
<picture>
50+
<img
51+
src={avatars.getInitials(ques.author.name, 24, 24)}
52+
alt={ques.author.name}
53+
className="rounded-lg"
54+
/>
55+
</picture>
56+
<Link
57+
href={`/users/${ques.author.$id}/${slugify(ques.author.name)}`}
58+
className="text-orange-500 hover:text-orange-600"
59+
>
60+
{ques.author.name}
61+
</Link>
62+
<strong>&quot;{ques.author.reputation}&quot;</strong>
63+
</div>
64+
<span>asked {convertDateToRelativeTime(new Date(ques.$createdAt))}</span>
65+
</div>
66+
</div>
67+
</div>
68+
);
69+
};
70+
71+
export default QuestionCard;

0 commit comments

Comments
 (0)