diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml
new file mode 100644
index 0000000..f06f991
--- /dev/null
+++ b/.github/workflows/check-formatting.yml
@@ -0,0 +1,13 @@
+name: Pre-commit Checks
+
+on:
+ pull_request:
+ paths:
+ - '**/*.{js,jsx,ts,tsx,json,css,scss,md}'
+
+jobs:
+ pre-commit-checks:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Run Prettier
+ run: yarn format-check
diff --git a/.gitignore b/.gitignore
index 6381d42..7f40deb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,4 +36,5 @@ yarn-error.log*
next-env.d.ts
userbase.csv
-pscale_dump_apunts-dades_main_20240310_112108
\ No newline at end of file
+pscale_dump_apunts-dades_main_20240310_112108
+prisma/migrations
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..900f52e
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "singleQuote": false,
+ "semi": false,
+ "tabWidth": 2,
+ "printWidth": 80
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d067910..fae8e3d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
-}
\ No newline at end of file
+}
diff --git a/.vscode/typescriptreact.json.code-snippets b/.vscode/typescriptreact.json.code-snippets
index 3dcc777..d629595 100644
--- a/.vscode/typescriptreact.json.code-snippets
+++ b/.vscode/typescriptreact.json.code-snippets
@@ -1,4 +1,5 @@
-"Typescript React Function Component": {
+{
+ "Typescript React Function Component": {
"prefix": "fc",
"body": [
"import { FC } from 'react'",
@@ -11,7 +12,8 @@
" return
$TM_FILENAME_BASE
",
"}",
"",
- "export default $TM_FILENAME_BASE"
+ "export default $TM_FILENAME_BASE",
],
- "description": "Typescript React Function Component"
- },
\ No newline at end of file
+ "description": "Typescript React Function Component",
+ },
+}
diff --git a/README.md b/README.md
index 28b3c1c..2f1ca0a 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
# Benvinguts al repo d'Apunts Dades
+
## M'agradaria contribuir:
+
Obre un [Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue) explicant una mica què t'agradaria que s'afegís, preferiblement [etiqueta l'issue segons el tipus](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels).
Si t'animes a fer la implementació de qualsevol issue no assignat a ningú afegeix un comentari avisant que estàs en ello i quan estigui resolt envia una [PR enllaçada a l'Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) explicant una mica com ho has solucionat.
No fa falta que estigui tot perfecte ni que tinguis molta experiència en software. Des de l'AED animem a tothom qui en tingui ganes a contribuir i intentarem donar suport en la mesura del que ens sigui possible amb els recursos dels que disposem.
-Si necessites credencials de dev per alguna api escriu-nos.
\ No newline at end of file
+Si necessites credencials de dev per alguna api escriu-nos.
diff --git a/components.json b/components.json
index c19f2eb..1e8d33a 100644
--- a/components.json
+++ b/components.json
@@ -14,4 +14,4 @@
"components": "@/components",
"utils": "@/lib/utils"
}
-}
\ No newline at end of file
+}
diff --git a/next.config.js b/next.config.js
index 39656ff..569946b 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,11 +1,11 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
- domains: ['uploadthing.com', 'lh3.googleusercontent.com'],
+ domains: ["uploadthing.com", "lh3.googleusercontent.com"],
},
experimental: {
- appDir: true
- }
+ appDir: true,
+ },
}
module.exports = nextConfig
diff --git a/package.json b/package.json
index 97bdf36..5a31876 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,24 @@
"build": "prisma db push && prisma db seed && next build",
"start": "next start",
"lint": "next lint",
- "postinstall": "prisma generate"
+ "postinstall": "prisma generate",
+ "format": "prettier --write .",
+ "format-check": "prettier --check ."
},
"prisma": {
"seed": "node prisma/seed.js"
},
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "*.{js,jsx,ts,tsx,json,css,scss,md}": [
+ "prettier --write",
+ "git add"
+ ]
+ },
"dependencies": {
"@editorjs/attaches": "^1.3.0",
"@editorjs/code": "^2.8.0",
@@ -86,6 +99,9 @@
"devDependencies": {
"@types/editorjs__header": "^2.6.0",
"@types/lodash.debounce": "^4.0.7",
+ "husky": "^9.0.11",
+ "lint-staged": "^15.2.2",
+ "prettier": "^3.2.5",
"prisma": "^5.9.1"
}
}
diff --git a/prisma/accountVerify.js b/prisma/accountVerify.js
index 3be883d..07bf185 100644
--- a/prisma/accountVerify.js
+++ b/prisma/accountVerify.js
@@ -1,29 +1,29 @@
-const { PrismaClient } = require("@prisma/client");
-const Papa = require("papaparse");
-const fs = require("fs");
+const { PrismaClient } = require("@prisma/client")
+const Papa = require("papaparse")
+const fs = require("fs")
-const prisma = new PrismaClient();
+const prisma = new PrismaClient()
async function processUserRequests() {
- const userbasePath = "userbase.csv";
- const userBaseFileData = fs.readFileSync(userbasePath, "utf8");
- const verifiedValid = ["sí", "si"];
+ const userbasePath = "userbase.csv"
+ const userBaseFileData = fs.readFileSync(userbasePath, "utf8")
+ const verifiedValid = ["sí", "si"]
const parsedUserBase = Papa.parse(userBaseFileData, {
header: true, // Assuming the first row contains the headers
dynamicTyping: true, // Automatically convert strings to their appropriate data type
skipEmptyLines: true, // Skip empty lines in the CSV
complete: function async(result) {
result.data.forEach(async (row) => {
- const email = row.email;
- const generacio = row.generacio;
- const verificat = row.verificat;
+ const email = row.email
+ const generacio = row.generacio
+ const verificat = row.verificat
if (verificat && verifiedValid.includes(verificat.toLowerCase())) {
const existingUser = await prisma.authorizedUsers.findUnique({
where: {
email: email,
},
- });
+ })
if (!existingUser) {
await prisma.authorizedUsers.create({
data: {
@@ -31,13 +31,13 @@ async function processUserRequests() {
generacio: generacio,
createdAt: new Date(),
},
- });
+ })
}
}
- });
+ })
},
- });
- await prisma.$disconnect();
+ })
+ await prisma.$disconnect()
}
-processUserRequests();
\ No newline at end of file
+processUserRequests()
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 29e23af..71d06f2 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -57,7 +57,7 @@ model User {
}
model AuthorizedUsers {
- email String @id
+ email String @id
generacio Int
createdAt DateTime @default(now())
}
@@ -86,26 +86,27 @@ model Question {
updatedAt DateTime @updatedAt
subjectId String
authorId String
- subject Subject @relation(fields: [subjectId], references: [id])
+ subject Subject @relation(fields: [subjectId], references: [id])
author User @relation(fields: [authorId], references: [id])
answers Answer[]
votes QuestionVote[]
}
model Post {
- id String @id @default(cuid())
- title String
- content String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- subjectId String
- authorId String
- tipus TipusType
- year Int
- subject Subject @relation(fields: [subjectId], references: [id])
- author User @relation(fields: [authorId], references: [id])
- comments Comment[]
- votes PostVote[]
+ id String @id @default(cuid())
+ title String
+ content String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ subjectId String
+ authorId String
+ tipus TipusType
+ year Int
+ isAnonymous Boolean @default(false)
+ subject Subject @relation(fields: [subjectId], references: [id])
+ author User @relation(fields: [authorId], references: [id])
+ comments Comment[]
+ votes PostVote[]
}
model Answer {
diff --git a/prisma/seed.js b/prisma/seed.js
index 2145ee0..cc30397 100644
--- a/prisma/seed.js
+++ b/prisma/seed.js
@@ -1,59 +1,204 @@
-const { PrismaClient } = require('@prisma/client')
+const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
async function main() {
const aed = await prisma.user.upsert({
- where: { email: "info@aed.cat" },
- update: {},
- create: {
- email: "info@aed.cat",
- name: "Associació d'Estudiants de Dades",
- username: "AED",
- generacio: 2017,
- },
- });
- const createManySubjects = await prisma.subject.createMany({
- data: [
- { name: "Àlgebra", acronym: "ALG", semester: "Q1", creatorId: aed.id },
- { name: "Algorísmia i Programació I", acronym: "AP1", semester: "Q1", creatorId: aed.id },
- { name: "Càlcul", acronym: "CAL", semester: "Q1", creatorId: aed.id },
- { name: "Lògica i Matemàtica Discreta", acronym: "LMD", semester: "Q1", creatorId: aed.id },
- { name: "Àlgebra i Càlcul Avançats", acronym: "AC2", semester: "Q2", creatorId: aed.id },
- { name: "Algorísmia i Programació II", acronym: "AP2", semester: "Q2", creatorId: aed.id },
- { name: "Computadors", acronym: "COM", semester: "Q2", creatorId: aed.id },
- { name: "Probabilitat i Estadística I", acronym: "PIE1", semester: "Q2", creatorId: aed.id },
- { name: "Algorísmia i Programació III", acronym: "AP3", semester: "Q3", creatorId: aed.id },
- { name: "Bases de Dades", acronym: "BD", semester: "Q3", creatorId: aed.id },
- { name: "Probabilitat i Estadística II", acronym: "PIE2", semester: "Q3", creatorId: aed.id },
- { name: "Senyals i Sistemes", acronym: "SIS", semester: "Q3", creatorId: aed.id },
- { name: "Teoria de la Informació", acronym: "TEOI", semester: "Q3", creatorId: aed.id },
- { name: "Aprenentatge Automàtic I", acronym: "AA1", semester: "Q4", creatorId: aed.id },
- { name: "Anàlisi de Dades", acronym: "AD", semester: "Q4", creatorId: aed.id },
- { name: "Introducció al Processament Audiovisual", acronym: "IPA", semester: "Q4", creatorId: aed.id },
- { name: "Optimització Matemàtica", acronym: "OM", semester: "Q4", creatorId: aed.id },
- { name: "Paral·lelisme i Sistemes Distribuïts", acronym: "PSD", semester: "Q4", creatorId: aed.id },
- { name: "Aprenentatge Automàtic II", acronym: "AA2", semester: "Q5", creatorId: aed.id },
- { name: "Bases de Dades Avançades", acronym: "BDA", semester: "Q5", creatorId: aed.id },
- { name: "Cerca i Anàlisi de la Informació", acronym: "CAI", semester: "Q5", creatorId: aed.id },
- { name: "Emprenedoria i Innovació", acronym: "EI", semester: "Q5", creatorId: aed.id },
- { name: "Visualització de la Informació", acronym: "VI", semester: "Q5", creatorId: aed.id },
- { name: "Projectes d'Enginyeria", acronym: "PE", semester: "Q6", creatorId: aed.id },
- { name: "Processament d'Imatge i Visió Artificial", acronym: "PIVA", semester: "Q6", creatorId: aed.id },
- { name: "Processament del Llenguatge Oral i Escrit", acronym: "POE", semester: "Q6", creatorId: aed.id },
- { name: "Temes Avançats d'Enginyeria de Dades I", acronym: "TAED1", semester: "Q6", creatorId: aed.id },
- { name: "Temes Avançats d'Enginyeria de Dades II", acronym: "TAED2", semester: "Q7", creatorId: aed.id },
- { name: "Altres", acronym: "Altres", semester: "Q8", creatorId: aed.id },
- ],
- skipDuplicates: true,
- });
- const AuthorizedUsers = await prisma.authorizedUsers.createMany({
- data: [
- { email: "pau.matas@estudiantat.upc.edu", generacio: 2019 },
- { email: "aina.luis@estudiantat.upc.edu", generacio: 2020 },
- { email: "pol.puigdemont@estudiantat.upc.edu", generacio: 2020 },
- ],
- skipDuplicates: true,
- });
+ where: { email: "info@aed.cat" },
+ update: {},
+ create: {
+ email: "info@aed.cat",
+ name: "Associació d'Estudiants de Dades",
+ username: "AED",
+ generacio: 2017,
+ },
+ })
+ const createManySubjects = await prisma.subject.createMany({
+ data: [
+ {
+ name: "Àlgebra",
+ acronym: "ALG",
+ semester: "Q1",
+ creatorId: aed.id,
+ },
+ {
+ name: "Algorísmia i Programació I",
+ acronym: "AP1",
+ semester: "Q1",
+ creatorId: aed.id,
+ },
+ {
+ name: "Càlcul",
+ acronym: "CAL",
+ semester: "Q1",
+ creatorId: aed.id,
+ },
+ {
+ name: "Lògica i Matemàtica Discreta",
+ acronym: "LMD",
+ semester: "Q1",
+ creatorId: aed.id,
+ },
+ {
+ name: "Àlgebra i Càlcul Avançats",
+ acronym: "AC2",
+ semester: "Q2",
+ creatorId: aed.id,
+ },
+ {
+ name: "Algorísmia i Programació II",
+ acronym: "AP2",
+ semester: "Q2",
+ creatorId: aed.id,
+ },
+ {
+ name: "Computadors",
+ acronym: "COM",
+ semester: "Q2",
+ creatorId: aed.id,
+ },
+ {
+ name: "Probabilitat i Estadística I",
+ acronym: "PIE1",
+ semester: "Q2",
+ creatorId: aed.id,
+ },
+ {
+ name: "Algorísmia i Programació III",
+ acronym: "AP3",
+ semester: "Q3",
+ creatorId: aed.id,
+ },
+ {
+ name: "Bases de Dades",
+ acronym: "BD",
+ semester: "Q3",
+ creatorId: aed.id,
+ },
+ {
+ name: "Probabilitat i Estadística II",
+ acronym: "PIE2",
+ semester: "Q3",
+ creatorId: aed.id,
+ },
+ {
+ name: "Senyals i Sistemes",
+ acronym: "SIS",
+ semester: "Q3",
+ creatorId: aed.id,
+ },
+ {
+ name: "Teoria de la Informació",
+ acronym: "TEOI",
+ semester: "Q3",
+ creatorId: aed.id,
+ },
+ {
+ name: "Aprenentatge Automàtic I",
+ acronym: "AA1",
+ semester: "Q4",
+ creatorId: aed.id,
+ },
+ {
+ name: "Anàlisi de Dades",
+ acronym: "AD",
+ semester: "Q4",
+ creatorId: aed.id,
+ },
+ {
+ name: "Introducció al Processament Audiovisual",
+ acronym: "IPA",
+ semester: "Q4",
+ creatorId: aed.id,
+ },
+ {
+ name: "Optimització Matemàtica",
+ acronym: "OM",
+ semester: "Q4",
+ creatorId: aed.id,
+ },
+ {
+ name: "Paral·lelisme i Sistemes Distribuïts",
+ acronym: "PSD",
+ semester: "Q4",
+ creatorId: aed.id,
+ },
+ {
+ name: "Aprenentatge Automàtic II",
+ acronym: "AA2",
+ semester: "Q5",
+ creatorId: aed.id,
+ },
+ {
+ name: "Bases de Dades Avançades",
+ acronym: "BDA",
+ semester: "Q5",
+ creatorId: aed.id,
+ },
+ {
+ name: "Cerca i Anàlisi de la Informació",
+ acronym: "CAI",
+ semester: "Q5",
+ creatorId: aed.id,
+ },
+ {
+ name: "Emprenedoria i Innovació",
+ acronym: "EI",
+ semester: "Q5",
+ creatorId: aed.id,
+ },
+ {
+ name: "Visualització de la Informació",
+ acronym: "VI",
+ semester: "Q5",
+ creatorId: aed.id,
+ },
+ {
+ name: "Projectes d'Enginyeria",
+ acronym: "PE",
+ semester: "Q6",
+ creatorId: aed.id,
+ },
+ {
+ name: "Processament d'Imatge i Visió Artificial",
+ acronym: "PIVA",
+ semester: "Q6",
+ creatorId: aed.id,
+ },
+ {
+ name: "Processament del Llenguatge Oral i Escrit",
+ acronym: "POE",
+ semester: "Q6",
+ creatorId: aed.id,
+ },
+ {
+ name: "Temes Avançats d'Enginyeria de Dades I",
+ acronym: "TAED1",
+ semester: "Q6",
+ creatorId: aed.id,
+ },
+ {
+ name: "Temes Avançats d'Enginyeria de Dades II",
+ acronym: "TAED2",
+ semester: "Q7",
+ creatorId: aed.id,
+ },
+ {
+ name: "Altres",
+ acronym: "Altres",
+ semester: "Q8",
+ creatorId: aed.id,
+ },
+ ],
+ skipDuplicates: true,
+ })
+ const AuthorizedUsers = await prisma.authorizedUsers.createMany({
+ data: [
+ { email: "pau.matas@estudiantat.upc.edu", generacio: 2019 },
+ { email: "aina.luis@estudiantat.upc.edu", generacio: 2020 },
+ { email: "pol.puigdemont@estudiantat.upc.edu", generacio: 2020 },
+ ],
+ skipDuplicates: true,
+ })
}
main()
.then(async () => {
@@ -64,4 +209,3 @@ main()
await prisma.$disconnect()
process.exit(1)
})
-
\ No newline at end of file
diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx
index b46a783..5f69528 100644
--- a/src/app/(auth)/sign-in/page.tsx
+++ b/src/app/(auth)/sign-in/page.tsx
@@ -1,24 +1,28 @@
-import SignIn from "@/components/SignIn";
-import { buttonVariants } from "@/components/ui/Button";
-import { cn } from "@/lib/utils";
-import { ChevronLeft } from "lucide-react";
-import Link from "next/link";
+import SignIn from "@/components/SignIn"
+import { buttonVariants } from "@/components/ui/Button"
+import { cn } from "@/lib/utils"
+import { ChevronLeft } from "lucide-react"
+import Link from "next/link"
const page = () => {
- return (
-
-
-
-
- Home
-
+ return (
+
- );
-};
+
+
+
+ )
+}
-export default page;
+export default page
diff --git a/src/app/@authModal/(.)sign-in/page.tsx b/src/app/@authModal/(.)sign-in/page.tsx
index d632d5c..3b1dfbf 100644
--- a/src/app/@authModal/(.)sign-in/page.tsx
+++ b/src/app/@authModal/(.)sign-in/page.tsx
@@ -1,23 +1,23 @@
-import CloseModal from "@/components/CloseModal";
-import SignIn from "@/components/SignIn";
-import { FC } from "react";
+import CloseModal from "@/components/CloseModal"
+import SignIn from "@/components/SignIn"
+import { FC } from "react"
interface pageProps {}
const page: FC = ({}) => {
- return (
-
-
-
-
-
-
+ return (
+
- );
-};
+
+
+
+
+ )
+}
-export default page;
+export default page
diff --git a/src/app/@authModal/default.tsx b/src/app/@authModal/default.tsx
index 3e4bcd3..86b9e9a 100644
--- a/src/app/@authModal/default.tsx
+++ b/src/app/@authModal/default.tsx
@@ -1,3 +1,3 @@
export default function Default() {
- return null;
+ return null
}
diff --git a/src/app/[slug]/layout.tsx b/src/app/[slug]/layout.tsx
index cf9100a..19c7362 100644
--- a/src/app/[slug]/layout.tsx
+++ b/src/app/[slug]/layout.tsx
@@ -1,194 +1,213 @@
-import SubscribeLeaveToggle from "@/components/SubscribeLeaveToggle";
-import { buttonVariants } from "@/components/ui/Button";
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { HomeIcon, FileQuestionIcon, FileTextIcon, InfoIcon } from "lucide-react";
-import Link from "next/link";
-import { notFound } from "next/navigation";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
-
-const Layout = async ({ children, params: { slug } }: { children: React.ReactNode; params: { slug: string } }) => {
- const session = await getAuthSession();
-
- const subject = await db.subject.findFirst({
- where: { acronym: slug },
- include: {
- posts: {
- include: {
- author: true,
- votes: true,
- },
- },
- questions: {
- include: {
- answers: true,
- },
- },
- },
- });
-
- const subscription = !session?.user
- ? undefined
- : await db.subscription.findFirst({
- where: {
- subject: {
- acronym: slug,
- },
- user: {
- id: session.user.id,
- },
- },
- });
-
- const isSubscribed = !!subscription;
-
- if (!subject) return notFound();
-
- const memberCount = await db.subscription.count({
- where: {
- subject: {
- acronym: slug,
- },
- },
- });
-
- const mostRecentPostYear = subject.posts.reduce((acc, post) => {
- if (post.year > acc) return post.year;
- return acc;
- }, 0);
-
- const questionCount = await db.question.count({
- where: {
- subject: {
- acronym: slug,
- },
- },
- });
-
- const unAnsweredQuestionCount = await db.question.count({
- where: {
- subject: {
- acronym: slug,
- },
- answers: {
- none: {},
- },
- },
- });
-
- const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i;
- const subjectNameArticle = subject.name.match(startsWithVowel) ? "d'" : "de ";
-
- return (
-
-
-
-
- Inici
-
-
-
-
- Apunts
-
-
-
-
- Preguntes
-
-
-
-
-
-
-
- Més sobre els apunts {subjectNameArticle}
- {subject.name}
-
-
-
-
-
-
- Apunts {subjectNameArticle}
- {subject.name}
-
-
-
-
-
-
Membres
- {memberCount}
-
-
-
Subscripció
- {isSubscribed ? "Subscrit" : "No subscrit"}
-
-
-
Quadrimestre
- {subject.semester}
-
-
-
Apunts més recents
- {mostRecentPostYear}
-
-
-
Preguntes
- {questionCount}
-
-
-
Preguntes sense respondre
- {unAnsweredQuestionCount}
-
- {subject.creatorId === session?.user?.id ? (
-
-
Pots editar aquesta assignatura
-
- ) : null}
-
- {subject.creatorId !== session?.user?.id ? (
-
- ) : null}
-
-
- Comparteix Apunts
-
-
-
- Llança una pregunta
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default Layout;
+import SubscribeLeaveToggle from "@/components/SubscribeLeaveToggle"
+import { buttonVariants } from "@/components/ui/Button"
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import {
+ HomeIcon,
+ FileQuestionIcon,
+ FileTextIcon,
+ InfoIcon,
+} from "lucide-react"
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const Layout = async ({
+ children,
+ params: { slug },
+}: {
+ children: React.ReactNode
+ params: { slug: string }
+}) => {
+ const session = await getAuthSession()
+
+ const subject = await db.subject.findFirst({
+ where: { acronym: slug },
+ include: {
+ posts: {
+ include: {
+ author: true,
+ votes: true,
+ },
+ },
+ questions: {
+ include: {
+ answers: true,
+ },
+ },
+ },
+ })
+
+ const subscription = !session?.user
+ ? undefined
+ : await db.subscription.findFirst({
+ where: {
+ subject: {
+ acronym: slug,
+ },
+ user: {
+ id: session.user.id,
+ },
+ },
+ })
+
+ const isSubscribed = !!subscription
+
+ if (!subject) return notFound()
+
+ const memberCount = await db.subscription.count({
+ where: {
+ subject: {
+ acronym: slug,
+ },
+ },
+ })
+
+ const mostRecentPostYear = subject.posts.reduce((acc, post) => {
+ if (post.year > acc) return post.year
+ return acc
+ }, 0)
+
+ const questionCount = await db.question.count({
+ where: {
+ subject: {
+ acronym: slug,
+ },
+ },
+ })
+
+ const unAnsweredQuestionCount = await db.question.count({
+ where: {
+ subject: {
+ acronym: slug,
+ },
+ answers: {
+ none: {},
+ },
+ },
+ })
+
+ const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i
+ const subjectNameArticle = subject.name.match(startsWithVowel) ? "d'" : "de "
+
+ return (
+
+
+
+
+ Inici
+
+
+
+
+ Apunts
+
+
+
+
+ Preguntes
+
+
+
+
+
+
+
+ Més sobre els apunts {subjectNameArticle}
+ {subject.name}
+
+
+
+
+
+
+ Apunts {subjectNameArticle}
+ {subject.name}
+
+
+
+
+
+
Membres
+ {memberCount}
+
+
+
Subscripció
+
+ {isSubscribed ? "Subscrit" : "No subscrit"}
+
+
+
+
Quadrimestre
+ {subject.semester}
+
+
+
Apunts més recents
+ {mostRecentPostYear}
+
+
+
Preguntes
+ {questionCount}
+
+
+
Preguntes sense respondre
+ {unAnsweredQuestionCount}
+
+ {subject.creatorId === session?.user?.id ? (
+
+
+ Pots editar aquesta assignatura
+
+
+ ) : null}
+
+ {subject.creatorId !== session?.user?.id ? (
+
+ ) : null}
+
+
+ Comparteix Apunts
+
+
+
+ Llança una pregunta
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Layout
diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx
index 88a7402..e1d174a 100644
--- a/src/app/[slug]/page.tsx
+++ b/src/app/[slug]/page.tsx
@@ -1,57 +1,54 @@
-import MiniCreatePost from "@/components/MiniCreatePost";
-import PostFeed from "@/components/PostFeed";
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { notFound } from "next/navigation";
+import MiniCreatePost from "@/components/MiniCreatePost"
+import PostFeed from "@/components/PostFeed"
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { notFound } from "next/navigation"
interface PageProps {
- params: {
- slug: string;
- };
+ params: {
+ slug: string
+ }
}
const page = async ({ params }: PageProps) => {
- const { slug } = params;
-
- const session = await getAuthSession();
-
- const subject = await db.subject.findFirst({
- where: { acronym: slug },
- include: {
- posts: {
- include: {
- author: true,
- votes: true,
- comments: true,
- subject: true,
- },
- orderBy: {
- createdAt: "desc",
- },
-
- take: INFINITE_SCROLL_PAGINATION_RESULTS,
- },
- },
- });
-
- if (!subject) return notFound();
-
- return (
- <>
-
- {subject.acronym}/
- {subject.name}
-
-
-
-
-
- >
- );
-};
-
-export default page;
+ const { slug } = params
+
+ const session = await getAuthSession()
+
+ const subject = await db.subject.findFirst({
+ where: { acronym: slug },
+ include: {
+ posts: {
+ include: {
+ author: true,
+ votes: true,
+ comments: true,
+ subject: true,
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+
+ take: INFINITE_SCROLL_PAGINATION_RESULTS,
+ },
+ },
+ })
+
+ if (!subject) return notFound()
+
+ return (
+ <>
+
+ {subject.acronym}/
+ {subject.name}
+
+
+
+
+
+ >
+ )
+}
+
+export default page
diff --git a/src/app/[slug]/post/[postId]/page.tsx b/src/app/[slug]/post/[postId]/page.tsx
index c05becf..a5b5c7d 100644
--- a/src/app/[slug]/post/[postId]/page.tsx
+++ b/src/app/[slug]/post/[postId]/page.tsx
@@ -1,54 +1,54 @@
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { db } from "@/lib/db";
-import { notFound } from "next/navigation";
-import { PostView } from "@/components/PostView";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { db } from "@/lib/db"
+import { notFound } from "next/navigation"
+import { PostView } from "@/components/PostView"
interface PageProps {
- params: {
- slug: string;
- postId: string;
- };
+ params: {
+ slug: string
+ postId: string
+ }
}
const page = async ({ params }: PageProps) => {
- const { slug, postId } = params;
- const post = await db.post.findFirst({
- where: { id: postId, subject: { acronym: slug } },
- include: {
- author: true,
- votes: true,
- subject: true,
- comments: {
- include: {
- post: true,
- votes: true,
- author: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- take: INFINITE_SCROLL_PAGINATION_RESULTS,
- },
- },
- });
- const comments = await db.comment.findMany({
- where: {
- postId: postId,
- },
- include: {
- author: true,
- votes: true,
- },
- orderBy: {
- createdAt: "asc",
- },
- });
- if (!post) return notFound();
- return (
-
- );
-};
+ const { slug, postId } = params
+ const post = await db.post.findFirst({
+ where: { id: postId, subject: { acronym: slug } },
+ include: {
+ author: true,
+ votes: true,
+ subject: true,
+ comments: {
+ include: {
+ post: true,
+ votes: true,
+ author: true,
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ take: INFINITE_SCROLL_PAGINATION_RESULTS,
+ },
+ },
+ })
+ const comments = await db.comment.findMany({
+ where: {
+ postId: postId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ orderBy: {
+ createdAt: "asc",
+ },
+ })
+ if (!post) return notFound()
+ return (
+
+ )
+}
-export default page;
+export default page
diff --git a/src/app/[slug]/q/[questionId]/page.tsx b/src/app/[slug]/q/[questionId]/page.tsx
index 14e2af7..df56f25 100644
--- a/src/app/[slug]/q/[questionId]/page.tsx
+++ b/src/app/[slug]/q/[questionId]/page.tsx
@@ -1,63 +1,60 @@
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { db } from "@/lib/db";
-import { notFound } from "next/navigation";
-import { AnswersView } from "@/components/QuestionView";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { db } from "@/lib/db"
+import { notFound } from "next/navigation"
+import { AnswersView } from "@/components/QuestionView"
interface PageProps {
- params: {
- slug: string;
- questionId: string;
- };
+ params: {
+ slug: string
+ questionId: string
+ }
}
const page = async ({ params }: PageProps) => {
- const { slug, questionId } = params;
- const question = await db.question.findFirst({
- where: { id: questionId, subject: { acronym: slug } },
- include: {
- subject: true,
- votes: true,
- author: true,
- answers: {
- include: {
- question: true,
- votes: true,
- author: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- take: INFINITE_SCROLL_PAGINATION_RESULTS,
- },
- },
- });
- if (!question || question.subject === null) return notFound();
- const answers = await db.answer.findMany({
- where: {
- questionId: questionId,
- },
- include: {
- question: true,
- votes: true,
- author: true,
- },
- orderBy: [
- {
- accepted: "desc",
- },
- {
- createdAt: "asc",
- },
- ],
- });
- return (
-
- );
-};
+ const { slug, questionId } = params
+ const question = await db.question.findFirst({
+ where: { id: questionId, subject: { acronym: slug } },
+ include: {
+ subject: true,
+ votes: true,
+ author: true,
+ answers: {
+ include: {
+ question: true,
+ votes: true,
+ author: true,
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ take: INFINITE_SCROLL_PAGINATION_RESULTS,
+ },
+ },
+ })
+ if (!question || question.subject === null) return notFound()
+ const answers = await db.answer.findMany({
+ where: {
+ questionId: questionId,
+ },
+ include: {
+ question: true,
+ votes: true,
+ author: true,
+ },
+ orderBy: [
+ {
+ accepted: "desc",
+ },
+ {
+ createdAt: "asc",
+ },
+ ],
+ })
+ return (
+
+ )
+}
-export default page;
+export default page
diff --git a/src/app/[slug]/q/page.tsx b/src/app/[slug]/q/page.tsx
index 493defd..07dc4cb 100644
--- a/src/app/[slug]/q/page.tsx
+++ b/src/app/[slug]/q/page.tsx
@@ -1,56 +1,53 @@
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import MiniCreateQuestion from "@/components/MiniCreateQuestion";
-import QuestionFeed from "@/components/QuestionFeed";
-import { notFound } from "next/navigation";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import MiniCreateQuestion from "@/components/MiniCreateQuestion"
+import QuestionFeed from "@/components/QuestionFeed"
+import { notFound } from "next/navigation"
interface PageProps {
- params: {
- slug: string;
- };
+ params: {
+ slug: string
+ }
}
const page = async ({ params }: PageProps) => {
- const { slug } = params;
- const session = await getAuthSession();
- const subject = await db.subject.findFirst({
- where: { acronym: slug },
- include: {
- questions: {
- include: {
- author: true,
- votes: true,
- subject: true,
- answers: true,
- },
- orderBy: {
- createdAt: "desc",
- },
- take: INFINITE_SCROLL_PAGINATION_RESULTS,
- },
- },
- });
+ const { slug } = params
+ const session = await getAuthSession()
+ const subject = await db.subject.findFirst({
+ where: { acronym: slug },
+ include: {
+ questions: {
+ include: {
+ author: true,
+ votes: true,
+ subject: true,
+ answers: true,
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ take: INFINITE_SCROLL_PAGINATION_RESULTS,
+ },
+ },
+ })
- if (!subject) return notFound();
+ if (!subject) return notFound()
- return (
- <>
-
- {subject.acronym} Questions:
-
+ return (
+ <>
+
+ {subject.acronym} Questions:
+
-
+
-
- >
- );
-};
+
+ >
+ )
+}
-export default page;
+export default page
diff --git a/src/app/[slug]/submit/page.tsx b/src/app/[slug]/submit/page.tsx
index 2003ee5..2d0d22b 100644
--- a/src/app/[slug]/submit/page.tsx
+++ b/src/app/[slug]/submit/page.tsx
@@ -1,34 +1,34 @@
-import { SmallProfileForm } from "@/components/Form";
-import { db } from "@/lib/db";
-import { notFound } from "next/navigation";
+import { SmallProfileForm } from "@/components/Form"
+import { db } from "@/lib/db"
+import { notFound } from "next/navigation"
interface PageProps {
- params: {
- slug: string;
- };
+ params: {
+ slug: string
+ }
}
const page = async ({ params }: PageProps) => {
- const { slug } = params;
+ const { slug } = params
- const subject = await db.subject.findFirst({
- where: { acronym: slug },
- });
+ const subject = await db.subject.findFirst({
+ where: { acronym: slug },
+ })
- if (!subject) return notFound();
+ if (!subject) return notFound()
- const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i;
- const subjectNameArticle = subject.name.match(startsWithVowel) ? "d'" : "de ";
+ const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i
+ const subjectNameArticle = subject.name.match(startsWithVowel) ? "d'" : "de "
- return (
- <>
-
- Penja apunts {subjectNameArticle}
- {subject.name}
-
-
- >
- );
-};
+ return (
+ <>
+
+ Penja apunts {subjectNameArticle}
+ {subject.name}
+
+
+ >
+ )
+}
-export default page;
+export default page
diff --git a/src/app/api/a/route.ts b/src/app/api/a/route.ts
index e79b0bb..ceee2bd 100644
--- a/src/app/api/a/route.ts
+++ b/src/app/api/a/route.ts
@@ -1,87 +1,87 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { z } from "zod"
export async function GET(req: Request) {
- const url = new URL(req.url);
+ const url = new URL(req.url)
- const session = await getAuthSession();
+ const session = await getAuthSession()
- let followedCommunitiesIds: string[] = [];
+ let followedCommunitiesIds: string[] = []
- if (session && session.user) {
- const followedCommunities = await db.subscription.findMany({
- where: {
- userId: session.user.id,
- },
- include: {
- subject: true,
- },
- });
+ if (session && session.user) {
+ const followedCommunities = await db.subscription.findMany({
+ where: {
+ userId: session.user.id,
+ },
+ include: {
+ subject: true,
+ },
+ })
- followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id);
- }
+ followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id)
+ }
- try {
- const { limit, page, subjectAcronym, questionId } = z
- .object({
- limit: z.string(),
- page: z.string(),
- subjectAcronym: z.string().nullish().optional(),
- questionId: z.string().nullish().optional(),
- })
- .parse({
- subjectAcronym: url.searchParams.get("subjectAcronym"),
- limit: url.searchParams.get("limit"),
- page: url.searchParams.get("page"),
- questionId: url.searchParams.get("questionId"),
- });
+ try {
+ const { limit, page, subjectAcronym, questionId } = z
+ .object({
+ limit: z.string(),
+ page: z.string(),
+ subjectAcronym: z.string().nullish().optional(),
+ questionId: z.string().nullish().optional(),
+ })
+ .parse({
+ subjectAcronym: url.searchParams.get("subjectAcronym"),
+ limit: url.searchParams.get("limit"),
+ page: url.searchParams.get("page"),
+ questionId: url.searchParams.get("questionId"),
+ })
- let whereClause = {};
+ let whereClause = {}
- if (subjectAcronym) {
- whereClause = {
- subject: {
- acronym: subjectAcronym,
- },
- };
- } else if (session) {
- whereClause = {
- subject: {
- id: {
- in: followedCommunitiesIds,
- },
- },
- };
- }
- if (questionId) {
- whereClause = {
- question: {
- id: questionId,
- },
- };
- }
+ if (subjectAcronym) {
+ whereClause = {
+ subject: {
+ acronym: subjectAcronym,
+ },
+ }
+ } else if (session) {
+ whereClause = {
+ subject: {
+ id: {
+ in: followedCommunitiesIds,
+ },
+ },
+ }
+ }
+ if (questionId) {
+ whereClause = {
+ question: {
+ id: questionId,
+ },
+ }
+ }
- const answers = await db.answer.findMany({
- take: parseInt(limit),
- skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
- orderBy: [
- {
- accepted: "desc",
- },
- {
- createdAt: "asc",
- },
- ],
- include: {
- votes: true,
- author: true,
- question: true,
- },
- where: whereClause,
- });
- return new Response(JSON.stringify(answers));
- } catch (error) {
- return new Response("Could not fetch answers", { status: 500 });
- }
+ const answers = await db.answer.findMany({
+ take: parseInt(limit),
+ skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
+ orderBy: [
+ {
+ accepted: "desc",
+ },
+ {
+ createdAt: "asc",
+ },
+ ],
+ include: {
+ votes: true,
+ author: true,
+ question: true,
+ },
+ where: whereClause,
+ })
+ return new Response(JSON.stringify(answers))
+ } catch (error) {
+ return new Response("Could not fetch answers", { status: 500 })
+ }
}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
index 347f967..68933da 100644
--- a/src/app/api/auth/[...nextauth]/route.ts
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -3,4 +3,4 @@ import NextAuth from "next-auth/next"
const handler = NextAuth(authOptions)
-export {handler as GET, handler as POST}
\ No newline at end of file
+export { handler as GET, handler as POST }
diff --git a/src/app/api/comments/route.ts b/src/app/api/comments/route.ts
index 61874a2..2ecb808 100644
--- a/src/app/api/comments/route.ts
+++ b/src/app/api/comments/route.ts
@@ -1,82 +1,82 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { z } from "zod"
export async function GET(req: Request) {
- const url = new URL(req.url);
+ const url = new URL(req.url)
- const session = await getAuthSession();
+ const session = await getAuthSession()
- let followedCommunitiesIds: string[] = [];
+ let followedCommunitiesIds: string[] = []
- if (session && session.user) {
- const followedCommunities = await db.subscription.findMany({
- where: {
- userId: session.user.id,
- },
- include: {
- subject: true,
- },
- });
+ if (session && session.user) {
+ const followedCommunities = await db.subscription.findMany({
+ where: {
+ userId: session.user.id,
+ },
+ include: {
+ subject: true,
+ },
+ })
- followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id);
- }
+ followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id)
+ }
- try {
- const { limit, page, subjectAcronym, postId } = z
- .object({
- limit: z.string(),
- page: z.string(),
- subjectAcronym: z.string().nullish().optional(),
- postId: z.string().nullish().optional(),
- })
- .parse({
- subjectAcronym: url.searchParams.get("subjectAcronym"),
- limit: url.searchParams.get("limit"),
- page: url.searchParams.get("page"),
- postId: url.searchParams.get("postId"),
- });
+ try {
+ const { limit, page, subjectAcronym, postId } = z
+ .object({
+ limit: z.string(),
+ page: z.string(),
+ subjectAcronym: z.string().nullish().optional(),
+ postId: z.string().nullish().optional(),
+ })
+ .parse({
+ subjectAcronym: url.searchParams.get("subjectAcronym"),
+ limit: url.searchParams.get("limit"),
+ page: url.searchParams.get("page"),
+ postId: url.searchParams.get("postId"),
+ })
- let whereClause = {};
+ let whereClause = {}
- if (subjectAcronym) {
- whereClause = {
- subject: {
- acronym: subjectAcronym,
- },
- };
- } else if (session) {
- whereClause = {
- subject: {
- id: {
- in: followedCommunitiesIds,
- },
- },
- };
- }
- if (postId) {
- whereClause = {
- post: {
- id: postId,
- },
- };
- }
+ if (subjectAcronym) {
+ whereClause = {
+ subject: {
+ acronym: subjectAcronym,
+ },
+ }
+ } else if (session) {
+ whereClause = {
+ subject: {
+ id: {
+ in: followedCommunitiesIds,
+ },
+ },
+ }
+ }
+ if (postId) {
+ whereClause = {
+ post: {
+ id: postId,
+ },
+ }
+ }
- const comments = await db.comment.findMany({
- take: parseInt(limit),
- skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
- orderBy: {
- createdAt: "desc",
- },
- include: {
- votes: true,
- author: true,
- post: true,
- },
- where: whereClause,
- });
- return new Response(JSON.stringify(comments));
- } catch (error) {
- return new Response("Could not fetch comments", { status: 500 });
- }
+ const comments = await db.comment.findMany({
+ take: parseInt(limit),
+ skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
+ orderBy: {
+ createdAt: "desc",
+ },
+ include: {
+ votes: true,
+ author: true,
+ post: true,
+ },
+ where: whereClause,
+ })
+ return new Response(JSON.stringify(comments))
+ } catch (error) {
+ return new Response("Could not fetch comments", { status: 500 })
+ }
}
diff --git a/src/app/api/link/route.ts b/src/app/api/link/route.ts
index 2894dba..ee4daa5 100644
--- a/src/app/api/link/route.ts
+++ b/src/app/api/link/route.ts
@@ -1,36 +1,38 @@
-import axios from "axios";
+import axios from "axios"
export async function GET(req: Request) {
- const url = new URL(req.url);
- const href = url.searchParams.get("url");
+ const url = new URL(req.url)
+ const href = url.searchParams.get("url")
- if (!href) {
- return new Response("Invalid href", { status: 400 });
- }
+ if (!href) {
+ return new Response("Invalid href", { status: 400 })
+ }
- const res = await axios.get(href);
+ const res = await axios.get(href)
- // Parse the HTML using regular expressions
- const titleMatch = res.data.match(/(.*?)<\/title>/);
- const title = titleMatch ? titleMatch[1] : "";
+ // Parse the HTML using regular expressions
+ const titleMatch = res.data.match(/(.*?)<\/title>/)
+ const title = titleMatch ? titleMatch[1] : ""
- const descriptionMatch = res.data.match(/ sub.subject.id);
- }
+ followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id)
+ }
- try {
- const { limit, page, subjectAcronym } = z
- .object({
- limit: z.string(),
- page: z.string(),
- subjectAcronym: z.string().nullish().optional(),
- })
- .parse({
- subjectAcronym: url.searchParams.get("subjectAcronym"),
- limit: url.searchParams.get("limit"),
- page: url.searchParams.get("page"),
- });
+ try {
+ const { limit, page, subjectAcronym } = z
+ .object({
+ limit: z.string(),
+ page: z.string(),
+ subjectAcronym: z.string().nullish().optional(),
+ })
+ .parse({
+ subjectAcronym: url.searchParams.get("subjectAcronym"),
+ limit: url.searchParams.get("limit"),
+ page: url.searchParams.get("page"),
+ })
- let whereClause = {};
+ let whereClause = {}
- if (subjectAcronym) {
- whereClause = {
- subject: {
- acronym: subjectAcronym,
- },
- };
- } else if (session) {
- whereClause = {
- subject: {
- id: {
- in: followedCommunitiesIds,
- },
- },
- };
- }
+ if (subjectAcronym) {
+ whereClause = {
+ subject: {
+ acronym: subjectAcronym,
+ },
+ }
+ } else if (session) {
+ whereClause = {
+ subject: {
+ id: {
+ in: followedCommunitiesIds,
+ },
+ },
+ }
+ }
- const posts = await db.post.findMany({
- take: parseInt(limit),
- skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
- orderBy: {
- createdAt: "desc",
- },
- include: {
- subject: true,
- votes: true,
- author: true,
- comments: true,
- },
- where: whereClause,
- });
+ const posts = await db.post.findMany({
+ take: parseInt(limit),
+ skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
+ orderBy: {
+ createdAt: "desc",
+ },
+ include: {
+ subject: true,
+ votes: true,
+ author: true,
+ comments: true,
+ },
+ where: whereClause,
+ })
- return new Response(JSON.stringify(posts));
- } catch (error) {
- return new Response("Could not fetch posts", { status: 500 });
- }
+ return new Response(JSON.stringify(posts))
+ } catch (error) {
+ return new Response("Could not fetch posts", { status: 500 })
+ }
}
diff --git a/src/app/api/q/route.ts b/src/app/api/q/route.ts
index 3f1cfb9..171846f 100644
--- a/src/app/api/q/route.ts
+++ b/src/app/api/q/route.ts
@@ -1,75 +1,75 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { z } from "zod"
export async function GET(req: Request) {
- const url = new URL(req.url);
+ const url = new URL(req.url)
- const session = await getAuthSession();
+ const session = await getAuthSession()
- let followedCommunitiesIds: string[] = [];
+ let followedCommunitiesIds: string[] = []
- if (session && session.user) {
- const followedCommunities = await db.subscription.findMany({
- where: {
- userId: session.user.id,
- },
- include: {
- subject: true,
- },
- });
+ if (session && session.user) {
+ const followedCommunities = await db.subscription.findMany({
+ where: {
+ userId: session.user.id,
+ },
+ include: {
+ subject: true,
+ },
+ })
- followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id);
- }
+ followedCommunitiesIds = followedCommunities.map((sub) => sub.subject.id)
+ }
- try {
- const { limit, page, subjectAcronym } = z
- .object({
- limit: z.string(),
- page: z.string(),
- subjectAcronym: z.string().nullish().optional(),
- })
- .parse({
- subjectAcronym: url.searchParams.get("subjectAcronym"),
- limit: url.searchParams.get("limit"),
- page: url.searchParams.get("page"),
- });
+ try {
+ const { limit, page, subjectAcronym } = z
+ .object({
+ limit: z.string(),
+ page: z.string(),
+ subjectAcronym: z.string().nullish().optional(),
+ })
+ .parse({
+ subjectAcronym: url.searchParams.get("subjectAcronym"),
+ limit: url.searchParams.get("limit"),
+ page: url.searchParams.get("page"),
+ })
- let whereClause = {};
+ let whereClause = {}
- if (subjectAcronym) {
- whereClause = {
- subject: {
- acronym: subjectAcronym,
- },
- };
- } else if (session) {
- whereClause = {
- subject: {
- id: {
- in: followedCommunitiesIds,
- },
- },
- };
- }
+ if (subjectAcronym) {
+ whereClause = {
+ subject: {
+ acronym: subjectAcronym,
+ },
+ }
+ } else if (session) {
+ whereClause = {
+ subject: {
+ id: {
+ in: followedCommunitiesIds,
+ },
+ },
+ }
+ }
- const questions = await db.question.findMany({
- take: parseInt(limit),
- skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
- orderBy: {
- createdAt: "desc",
- },
- include: {
- subject: true,
- votes: true,
- author: true,
- answers: true,
- },
- where: whereClause,
- });
+ const questions = await db.question.findMany({
+ take: parseInt(limit),
+ skip: (parseInt(page) - 1) * parseInt(limit), // skip should start from 0 for page 1
+ orderBy: {
+ createdAt: "desc",
+ },
+ include: {
+ subject: true,
+ votes: true,
+ author: true,
+ answers: true,
+ },
+ where: whereClause,
+ })
- return new Response(JSON.stringify(questions));
- } catch (error) {
- return new Response("Could not fetch posts", { status: 500 });
- }
+ return new Response(JSON.stringify(questions))
+ } catch (error) {
+ return new Response("Could not fetch posts", { status: 500 })
+ }
}
diff --git a/src/app/api/subject/answer/accept/route.ts b/src/app/api/subject/answer/accept/route.ts
index a34dbb8..e67b305 100644
--- a/src/app/api/subject/answer/accept/route.ts
+++ b/src/app/api/subject/answer/accept/route.ts
@@ -1,88 +1,91 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { AnswerAcceptedValidator } from "@/lib/validators/vote";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { AnswerAcceptedValidator } from "@/lib/validators/vote"
+import { z } from "zod"
export async function PATCH(req: Request) {
- try {
- const body = await req.json();
+ try {
+ const body = await req.json()
- const { answerId, accepted } = AnswerAcceptedValidator.parse(body);
+ const { answerId, accepted } = AnswerAcceptedValidator.parse(body)
- const session = await getAuthSession();
+ const session = await getAuthSession()
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
- // // check if user has already voted on this answer
- // const existingAccepted = await db.answerVote.findFirst({
- // where: {
- // userId: session.user.id,
- // answerId,
- // },
- // });
+ // // check if user has already voted on this answer
+ // const existingAccepted = await db.answerVote.findFirst({
+ // where: {
+ // userId: session.user.id,
+ // answerId,
+ // },
+ // });
- const answer = await db.answer.findUnique({
- where: {
- id: answerId,
- },
- include: {
- author: true,
- votes: true,
- },
- });
+ const answer = await db.answer.findUnique({
+ where: {
+ id: answerId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ })
- if (!answer) {
- return new Response("Answer not found", { status: 404 });
- }
+ if (!answer) {
+ return new Response("Answer not found", { status: 404 })
+ }
- const question = await db.question.findFirst({
- where: {
- id: answer.questionId,
- },
- select: {
- author: true,
- },
- });
+ const question = await db.question.findFirst({
+ where: {
+ id: answer.questionId,
+ },
+ select: {
+ author: true,
+ },
+ })
- if (question?.author.id !== session.user.id) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (question?.author.id !== session.user.id) {
+ return new Response("Unauthorized", { status: 401 })
+ }
- if (answer.accepted) {
- // if vote type is the same as existing vote, delete the vote
- if (accepted) {
- await db.answer.update({
- where: {
- id: answerId,
- },
- data: {
- accepted: false,
- },
- });
+ if (answer.accepted) {
+ // if vote type is the same as existing vote, delete the vote
+ if (accepted) {
+ await db.answer.update({
+ where: {
+ id: answerId,
+ },
+ data: {
+ accepted: false,
+ },
+ })
- return new Response("OK");
- }
- }
+ return new Response("OK")
+ }
+ }
- // if no existing vote, create a new vote
- await db.answer.update({
- where: {
- id: answerId,
- },
- data: {
- accepted: true,
- },
- });
+ // if no existing vote, create a new vote
+ await db.answer.update({
+ where: {
+ id: answerId,
+ },
+ data: {
+ accepted: true,
+ },
+ })
- return new Response("OK");
- } catch (error) {
- error;
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 400 });
- }
+ return new Response("OK")
+ } catch (error) {
+ error
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 400 })
+ }
- return new Response("Could not accept the answer at this time. Please try later", { status: 500 });
- }
+ return new Response(
+ "Could not accept the answer at this time. Please try later",
+ { status: 500 },
+ )
+ }
}
diff --git a/src/app/api/subject/answer/create/route.ts b/src/app/api/subject/answer/create/route.ts
index 6b7ee08..c58ba95 100644
--- a/src/app/api/subject/answer/create/route.ts
+++ b/src/app/api/subject/answer/create/route.ts
@@ -1,33 +1,35 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { AnswerValidator } from "@/lib/validators/question";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { AnswerValidator } from "@/lib/validators/question"
+import { z } from "zod"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
+ try {
+ const session = await getAuthSession()
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
- const body = await req.json();
+ const body = await req.json()
- const { title, content, questionId } = AnswerValidator.parse(body);
+ const { title, content, questionId } = AnswerValidator.parse(body)
- await db.answer.create({
- data: {
- title: title,
- content: content,
- questionId: questionId,
- authorId: session.user.id,
- },
- });
- return new Response("Answer created", { status: 201 });
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
- return new Response("Could not create post, please try again", { status: 500 });
- }
+ await db.answer.create({
+ data: {
+ title: title,
+ content: content,
+ questionId: questionId,
+ authorId: session.user.id,
+ },
+ })
+ return new Response("Answer created", { status: 201 })
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+ return new Response("Could not create post, please try again", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/answer/vote/route.ts b/src/app/api/subject/answer/vote/route.ts
index dd56418..92af5c7 100644
--- a/src/app/api/subject/answer/vote/route.ts
+++ b/src/app/api/subject/answer/vote/route.ts
@@ -1,153 +1,155 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { redis } from "@/lib/redis";
-import { AnswerVoteValidator } from "@/lib/validators/vote";
-import { CachedAnswer } from "@/types/redis";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { redis } from "@/lib/redis"
+import { AnswerVoteValidator } from "@/lib/validators/vote"
+import { CachedAnswer } from "@/types/redis"
+import { z } from "zod"
-const CACHE_AFTER_UPVOTES = 1;
+const CACHE_AFTER_UPVOTES = 1
export async function PATCH(req: Request) {
- try {
- const body = await req.json();
-
- const { answerId, voteType } = AnswerVoteValidator.parse(body);
-
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- // check if user has already voted on this answer
- const existingVote = await db.answerVote.findFirst({
- where: {
- userId: session.user.id,
- answerId,
- },
- });
-
- const answer = await db.answer.findUnique({
- where: {
- id: answerId,
- },
- include: {
- author: true,
- votes: true,
- },
- });
-
- if (!answer) {
- return new Response("Answer not found", { status: 404 });
- }
-
- if (existingVote) {
- // if vote type is the same as existing vote, delete the vote
- if (existingVote.type === voteType) {
- await db.answerVote.delete({
- where: {
- userId_answerId: {
- answerId,
- userId: session.user.id,
- },
- },
- });
-
- // Recount the votes
- const votesAmt = answer.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedAnswer = {
- authorName: answer.author.name ?? "",
- content: JSON.stringify(answer.content),
- id: answer.id,
- title: answer.title,
- currentVote: null,
- createdAt: answer.createdAt,
- };
-
- await redis.hset(`answer:${answerId}`, cachePayload); // Store the answer data as a hash
- }
-
- return new Response("OK");
- }
-
- // if vote type is different, update the vote
- await db.answerVote.update({
- where: {
- userId_answerId: {
- answerId,
- userId: session.user.id,
- },
- },
- data: {
- type: voteType,
- },
- });
-
- // Recount the votes
- const votesAmt = answer.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedAnswer = {
- authorName: answer.author.name ?? "",
- content: JSON.stringify(answer.content),
- id: answer.id,
- title: answer.title,
- currentVote: voteType,
- createdAt: answer.createdAt,
- };
-
- await redis.hset(`answer:${answerId}`, cachePayload); // Store the answer data as a hash
- }
-
- return new Response("OK");
- }
-
- // if no existing vote, create a new vote
- await db.answerVote.create({
- data: {
- type: voteType,
- userId: session.user.id,
- answerId,
- },
- });
-
- // Recount the votes
- const votesAmt = answer.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedAnswer = {
- authorName: answer.author.name ?? "",
- content: JSON.stringify(answer.content),
- id: answer.id,
- title: answer.title,
- currentVote: voteType,
- createdAt: answer.createdAt,
- };
-
- await redis.hset(`answer:${answerId}`, cachePayload); // Store the answer data as a hash
- }
-
- return new Response("OK");
- } catch (error) {
- error;
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 400 });
- }
-
- return new Response("Could not vote at this time. Please try later", { status: 500 });
- }
+ try {
+ const body = await req.json()
+
+ const { answerId, voteType } = AnswerVoteValidator.parse(body)
+
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ // check if user has already voted on this answer
+ const existingVote = await db.answerVote.findFirst({
+ where: {
+ userId: session.user.id,
+ answerId,
+ },
+ })
+
+ const answer = await db.answer.findUnique({
+ where: {
+ id: answerId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ })
+
+ if (!answer) {
+ return new Response("Answer not found", { status: 404 })
+ }
+
+ if (existingVote) {
+ // if vote type is the same as existing vote, delete the vote
+ if (existingVote.type === voteType) {
+ await db.answerVote.delete({
+ where: {
+ userId_answerId: {
+ answerId,
+ userId: session.user.id,
+ },
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = answer.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedAnswer = {
+ authorName: answer.author.name ?? "",
+ content: JSON.stringify(answer.content),
+ id: answer.id,
+ title: answer.title,
+ currentVote: null,
+ createdAt: answer.createdAt,
+ }
+
+ await redis.hset(`answer:${answerId}`, cachePayload) // Store the answer data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if vote type is different, update the vote
+ await db.answerVote.update({
+ where: {
+ userId_answerId: {
+ answerId,
+ userId: session.user.id,
+ },
+ },
+ data: {
+ type: voteType,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = answer.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedAnswer = {
+ authorName: answer.author.name ?? "",
+ content: JSON.stringify(answer.content),
+ id: answer.id,
+ title: answer.title,
+ currentVote: voteType,
+ createdAt: answer.createdAt,
+ }
+
+ await redis.hset(`answer:${answerId}`, cachePayload) // Store the answer data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if no existing vote, create a new vote
+ await db.answerVote.create({
+ data: {
+ type: voteType,
+ userId: session.user.id,
+ answerId,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = answer.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedAnswer = {
+ authorName: answer.author.name ?? "",
+ content: JSON.stringify(answer.content),
+ id: answer.id,
+ title: answer.title,
+ currentVote: voteType,
+ createdAt: answer.createdAt,
+ }
+
+ await redis.hset(`answer:${answerId}`, cachePayload) // Store the answer data as a hash
+ }
+
+ return new Response("OK")
+ } catch (error) {
+ error
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 400 })
+ }
+
+ return new Response("Could not vote at this time. Please try later", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/comment/create/route.ts b/src/app/api/subject/comment/create/route.ts
index 6d32f5e..e110373 100644
--- a/src/app/api/subject/comment/create/route.ts
+++ b/src/app/api/subject/comment/create/route.ts
@@ -1,34 +1,36 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { CommentValidator } from "@/lib/validators/post";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { CommentValidator } from "@/lib/validators/post"
+import { z } from "zod"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
+ try {
+ const session = await getAuthSession()
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
- const body = await req.json();
+ const body = await req.json()
- const { content, postId } = CommentValidator.parse(body);
+ const { content, postId } = CommentValidator.parse(body)
- await db.comment.create({
- data: {
- content: content,
- postId: postId,
- authorId: session.user.id,
- },
- });
+ await db.comment.create({
+ data: {
+ content: content,
+ postId: postId,
+ authorId: session.user.id,
+ },
+ })
- return new Response("OK");
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
+ return new Response("OK")
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
- return new Response("Could not create post, please try again", { status: 500 });
- }
+ return new Response("Could not create post, please try again", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/comment/vote/route.ts b/src/app/api/subject/comment/vote/route.ts
index 8f0bc3e..18cf994 100644
--- a/src/app/api/subject/comment/vote/route.ts
+++ b/src/app/api/subject/comment/vote/route.ts
@@ -1,150 +1,152 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { redis } from "@/lib/redis";
-import { CommentVoteValidator } from "@/lib/validators/vote";
-import { CachedComment } from "@/types/redis";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { redis } from "@/lib/redis"
+import { CommentVoteValidator } from "@/lib/validators/vote"
+import { CachedComment } from "@/types/redis"
+import { z } from "zod"
-const CACHE_AFTER_UPVOTES = 1;
+const CACHE_AFTER_UPVOTES = 1
export async function PATCH(req: Request) {
- try {
- const body = await req.json();
-
- const { commentId, voteType } = CommentVoteValidator.parse(body);
-
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- // check if user has already voted on this comment
- const existingVote = await db.commentVote.findFirst({
- where: {
- userId: session.user.id,
- commentId,
- },
- });
-
- const comment = await db.comment.findUnique({
- where: {
- id: commentId,
- },
- include: {
- author: true,
- votes: true,
- },
- });
-
- if (!comment) {
- return new Response("Comment not found", { status: 404 });
- }
-
- if (existingVote) {
- // if vote type is the same as existing vote, delete the vote
- if (existingVote.type === voteType) {
- await db.commentVote.delete({
- where: {
- userId_commentId: {
- commentId,
- userId: session.user.id,
- },
- },
- });
-
- // Recount the votes
- const votesAmt = comment.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedComment = {
- authorName: comment.author.name ?? "",
- content: JSON.stringify(comment.content),
- id: comment.id,
- currentVote: null,
- createdAt: comment.createdAt,
- };
-
- await redis.hset(`comment:${commentId}`, cachePayload); // Store the comment data as a hash
- }
-
- return new Response("OK");
- }
-
- // if vote type is different, update the vote
- await db.commentVote.update({
- where: {
- userId_commentId: {
- commentId,
- userId: session.user.id,
- },
- },
- data: {
- type: voteType,
- },
- });
-
- // Recount the votes
- const votesAmt = comment.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedComment = {
- authorName: comment.author.name ?? "",
- content: JSON.stringify(comment.content),
- id: comment.id,
- currentVote: voteType,
- createdAt: comment.createdAt,
- };
-
- await redis.hset(`comment:${commentId}`, cachePayload); // Store the comment data as a hash
- }
-
- return new Response("OK");
- }
-
- // if no existing vote, create a new vote
- await db.commentVote.create({
- data: {
- type: voteType,
- userId: session.user.id,
- commentId,
- },
- });
-
- // Recount the votes
- const votesAmt = comment.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedComment = {
- authorName: comment.author.name ?? "",
- content: JSON.stringify(comment.content),
- id: comment.id,
- currentVote: voteType,
- createdAt: comment.createdAt,
- };
-
- await redis.hset(`comment:${commentId}`, cachePayload); // Store the comment data as a hash
- }
-
- return new Response("OK");
- } catch (error) {
- error;
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 400 });
- }
-
- return new Response("Could not vote at this time. Please try later", { status: 500 });
- }
+ try {
+ const body = await req.json()
+
+ const { commentId, voteType } = CommentVoteValidator.parse(body)
+
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ // check if user has already voted on this comment
+ const existingVote = await db.commentVote.findFirst({
+ where: {
+ userId: session.user.id,
+ commentId,
+ },
+ })
+
+ const comment = await db.comment.findUnique({
+ where: {
+ id: commentId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ })
+
+ if (!comment) {
+ return new Response("Comment not found", { status: 404 })
+ }
+
+ if (existingVote) {
+ // if vote type is the same as existing vote, delete the vote
+ if (existingVote.type === voteType) {
+ await db.commentVote.delete({
+ where: {
+ userId_commentId: {
+ commentId,
+ userId: session.user.id,
+ },
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = comment.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedComment = {
+ authorName: comment.author.name ?? "",
+ content: JSON.stringify(comment.content),
+ id: comment.id,
+ currentVote: null,
+ createdAt: comment.createdAt,
+ }
+
+ await redis.hset(`comment:${commentId}`, cachePayload) // Store the comment data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if vote type is different, update the vote
+ await db.commentVote.update({
+ where: {
+ userId_commentId: {
+ commentId,
+ userId: session.user.id,
+ },
+ },
+ data: {
+ type: voteType,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = comment.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedComment = {
+ authorName: comment.author.name ?? "",
+ content: JSON.stringify(comment.content),
+ id: comment.id,
+ currentVote: voteType,
+ createdAt: comment.createdAt,
+ }
+
+ await redis.hset(`comment:${commentId}`, cachePayload) // Store the comment data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if no existing vote, create a new vote
+ await db.commentVote.create({
+ data: {
+ type: voteType,
+ userId: session.user.id,
+ commentId,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = comment.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedComment = {
+ authorName: comment.author.name ?? "",
+ content: JSON.stringify(comment.content),
+ id: comment.id,
+ currentVote: voteType,
+ createdAt: comment.createdAt,
+ }
+
+ await redis.hset(`comment:${commentId}`, cachePayload) // Store the comment data as a hash
+ }
+
+ return new Response("OK")
+ } catch (error) {
+ error
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 400 })
+ }
+
+ return new Response("Could not vote at this time. Please try later", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/post/create/route.ts b/src/app/api/subject/post/create/route.ts
index e699bc8..76a0a2b 100644
--- a/src/app/api/subject/post/create/route.ts
+++ b/src/app/api/subject/post/create/route.ts
@@ -1,57 +1,63 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { ApuntsPostValidator } from "@/lib/validators/post";
-import { z } from "zod";
-import { TipusType } from "@prisma/client";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { ApuntsPostValidator } from "@/lib/validators/post"
+import { z } from "zod"
+import { TipusType } from "@prisma/client"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const body = await req.json();
-
- const { pdf, title, assignatura, tipus } = ApuntsPostValidator.parse(body);
-
- const subject = await db.subject.findFirst({
- where: {
- acronym: assignatura.toUpperCase(),
- },
- });
-
- if (!subject) {
- return new Response("Subject not found", { status: 404 });
- }
-
- const semester = subject.semester;
- const semesterNumber = semester[0] === "Q" ? parseInt(semester[1]) : 8;
- if (typeof session.user.generacio !== "number") {
- return new Response("Invalid generacio", { status: 409 });
- }
- const year: number = session.user.generacio + Math.floor((semesterNumber - 1) / 2);
-
- if (!["apunts", "examens", "exercicis", "diapositives", "altres"].includes(tipus)) {
- return new Response("Invalid tipus", { status: 422 });
- }
-
- await db.post.create({
- data: {
- title: title,
- content: pdf,
- subjectId: subject.id,
- authorId: session.user.id,
- tipus: tipus as TipusType,
- year: year,
- },
- });
- return new Response(JSON.stringify(subject.acronym), { status: 201 });
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
- return new Response("Something went wrong", { status: 500 });
- }
+ try {
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ const body = await req.json()
+
+ const { pdf, title, assignatura, tipus, anonim } =
+ ApuntsPostValidator.parse(body)
+
+ const subject = await db.subject.findFirst({
+ where: {
+ acronym: assignatura.toUpperCase(),
+ },
+ })
+
+ if (!subject) {
+ return new Response("Subject not found", { status: 404 })
+ }
+
+ const semester = subject.semester
+ const semesterNumber = semester[0] === "Q" ? parseInt(semester[1]) : 8
+ if (typeof session.user.generacio !== "number") {
+ return new Response("Invalid generacio", { status: 409 })
+ }
+ const year: number =
+ session.user.generacio + Math.floor((semesterNumber - 1) / 2)
+
+ if (
+ !["apunts", "examens", "exercicis", "diapositives", "altres"].includes(
+ tipus,
+ )
+ ) {
+ return new Response("Invalid tipus", { status: 422 })
+ }
+ await db.post.create({
+ data: {
+ title: title,
+ content: pdf,
+ subjectId: subject.id,
+ authorId: session.user.id,
+ tipus: tipus as TipusType,
+ year: year,
+ isAnonymous: anonim,
+ },
+ })
+ return new Response(JSON.stringify(subject.acronym), { status: 201 })
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+ return new Response("Something went wrong", { status: 500 })
+ }
}
diff --git a/src/app/api/subject/post/vote/route.ts b/src/app/api/subject/post/vote/route.ts
index 8206509..e600d42 100644
--- a/src/app/api/subject/post/vote/route.ts
+++ b/src/app/api/subject/post/vote/route.ts
@@ -1,153 +1,155 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { redis } from "@/lib/redis";
-import { PostVoteValidator } from "@/lib/validators/vote";
-import { CachedPost } from "@/types/redis";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { redis } from "@/lib/redis"
+import { PostVoteValidator } from "@/lib/validators/vote"
+import { CachedPost } from "@/types/redis"
+import { z } from "zod"
-const CACHE_AFTER_UPVOTES = 1;
+const CACHE_AFTER_UPVOTES = 1
export async function PATCH(req: Request) {
- try {
- const body = await req.json();
-
- const { postId, voteType } = PostVoteValidator.parse(body);
-
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- // check if user has already voted on this post
- const existingVote = await db.postVote.findFirst({
- where: {
- userId: session.user.id,
- postId,
- },
- });
-
- const post = await db.post.findUnique({
- where: {
- id: postId,
- },
- include: {
- author: true,
- votes: true,
- },
- });
-
- if (!post) {
- return new Response("Post not found", { status: 404 });
- }
-
- if (existingVote) {
- // if vote type is the same as existing vote, delete the vote
- if (existingVote.type === voteType) {
- await db.postVote.delete({
- where: {
- userId_postId: {
- postId,
- userId: session.user.id,
- },
- },
- });
-
- // Recount the votes
- const votesAmt = post.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedPost = {
- authorName: post.author.name ?? "",
- content: post.content ?? "",
- id: post.id,
- title: post.title,
- currentVote: null,
- createdAt: post.createdAt,
- };
-
- await redis.hset(`post:${postId}`, cachePayload); // Store the post data as a hash
- }
-
- return new Response("OK");
- }
-
- // if vote type is different, update the vote
- await db.postVote.update({
- where: {
- userId_postId: {
- postId,
- userId: session.user.id,
- },
- },
- data: {
- type: voteType,
- },
- });
-
- // Recount the votes
- const votesAmt = post.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedPost = {
- authorName: post.author.name ?? "",
- content: post.content ?? "",
- id: post.id,
- title: post.title,
- currentVote: voteType,
- createdAt: post.createdAt,
- };
-
- await redis.hset(`post:${postId}`, cachePayload); // Store the post data as a hash
- }
-
- return new Response("OK");
- }
-
- // if no existing vote, create a new vote
- await db.postVote.create({
- data: {
- type: voteType,
- userId: session.user.id,
- postId,
- },
- });
-
- // Recount the votes
- const votesAmt = post.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedPost = {
- authorName: post.author.name ?? "",
- content: post.content ?? "",
- id: post.id,
- title: post.title,
- currentVote: voteType,
- createdAt: post.createdAt,
- };
-
- await redis.hset(`post:${postId}`, cachePayload); // Store the post data as a hash
- }
-
- return new Response("OK");
- } catch (error) {
- error;
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 400 });
- }
-
- return new Response("Could not vote at this time. Please try later", { status: 500 });
- }
+ try {
+ const body = await req.json()
+
+ const { postId, voteType } = PostVoteValidator.parse(body)
+
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ // check if user has already voted on this post
+ const existingVote = await db.postVote.findFirst({
+ where: {
+ userId: session.user.id,
+ postId,
+ },
+ })
+
+ const post = await db.post.findUnique({
+ where: {
+ id: postId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ })
+
+ if (!post) {
+ return new Response("Post not found", { status: 404 })
+ }
+
+ if (existingVote) {
+ // if vote type is the same as existing vote, delete the vote
+ if (existingVote.type === voteType) {
+ await db.postVote.delete({
+ where: {
+ userId_postId: {
+ postId,
+ userId: session.user.id,
+ },
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = post.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedPost = {
+ authorName: post.author.name ?? "",
+ content: post.content ?? "",
+ id: post.id,
+ title: post.title,
+ currentVote: null,
+ createdAt: post.createdAt,
+ }
+
+ await redis.hset(`post:${postId}`, cachePayload) // Store the post data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if vote type is different, update the vote
+ await db.postVote.update({
+ where: {
+ userId_postId: {
+ postId,
+ userId: session.user.id,
+ },
+ },
+ data: {
+ type: voteType,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = post.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedPost = {
+ authorName: post.author.name ?? "",
+ content: post.content ?? "",
+ id: post.id,
+ title: post.title,
+ currentVote: voteType,
+ createdAt: post.createdAt,
+ }
+
+ await redis.hset(`post:${postId}`, cachePayload) // Store the post data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if no existing vote, create a new vote
+ await db.postVote.create({
+ data: {
+ type: voteType,
+ userId: session.user.id,
+ postId,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = post.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedPost = {
+ authorName: post.author.name ?? "",
+ content: post.content ?? "",
+ id: post.id,
+ title: post.title,
+ currentVote: voteType,
+ createdAt: post.createdAt,
+ }
+
+ await redis.hset(`post:${postId}`, cachePayload) // Store the post data as a hash
+ }
+
+ return new Response("OK")
+ } catch (error) {
+ error
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 400 })
+ }
+
+ return new Response("Could not vote at this time. Please try later", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/question/create/route.ts b/src/app/api/subject/question/create/route.ts
index 246ed3c..bdf32f5 100644
--- a/src/app/api/subject/question/create/route.ts
+++ b/src/app/api/subject/question/create/route.ts
@@ -1,36 +1,38 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { QuestionValidator } from "@/lib/validators/question";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { QuestionValidator } from "@/lib/validators/question"
+import { z } from "zod"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
+ try {
+ const session = await getAuthSession()
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
- const body = await req.json();
+ const body = await req.json()
- const { title, subjectId, content } = QuestionValidator.parse(body);
+ const { title, subjectId, content } = QuestionValidator.parse(body)
- const createdQuestion = await db.question.create({
- data: {
- title: title,
- content: content,
- authorId: session.user.id,
- subjectId: subjectId,
- },
- });
+ const createdQuestion = await db.question.create({
+ data: {
+ title: title,
+ content: content,
+ authorId: session.user.id,
+ subjectId: subjectId,
+ },
+ })
- const createdQuestionId = createdQuestion.id; // Get the ID of the created question
+ const createdQuestionId = createdQuestion.id // Get the ID of the created question
- return new Response(JSON.stringify(createdQuestionId), { status: 201 });
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
- return new Response("Could not create post, please try again", { status: 500 });
- }
+ return new Response(JSON.stringify(createdQuestionId), { status: 201 })
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+ return new Response("Could not create post, please try again", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/question/vote/route.ts b/src/app/api/subject/question/vote/route.ts
index ebb777c..d53756e 100644
--- a/src/app/api/subject/question/vote/route.ts
+++ b/src/app/api/subject/question/vote/route.ts
@@ -1,153 +1,153 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { redis } from "@/lib/redis";
-import { QuestionVoteValidator } from "@/lib/validators/vote";
-import { CachedQuestion } from "@/types/redis";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { redis } from "@/lib/redis"
+import { QuestionVoteValidator } from "@/lib/validators/vote"
+import { CachedQuestion } from "@/types/redis"
+import { z } from "zod"
-const CACHE_AFTER_UPVOTES = 1;
+const CACHE_AFTER_UPVOTES = 1
export async function PATCH(req: Request) {
- try {
- const body = await req.json();
-
- const { questionId, voteType } = QuestionVoteValidator.parse(body);
-
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- // check if user has already voted on this question
- const existingVote = await db.questionVote.findFirst({
- where: {
- userId: session.user.id,
- questionId,
- },
- });
-
- const question = await db.question.findUnique({
- where: {
- id: questionId,
- },
- include: {
- author: true,
- votes: true,
- },
- });
-
- if (!question) {
- return new Response("Question not found", { status: 404 });
- }
-
- if (existingVote) {
- // if vote type is the same as existing vote, delete the vote
- if (existingVote.type === voteType) {
- await db.questionVote.delete({
- where: {
- userId_questionId: {
- questionId,
- userId: session.user.id,
- },
- },
- });
-
- // Recount the votes
- const votesAmt = question.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedQuestion = {
- authorName: question.author.name ?? "",
- content: JSON.stringify(question.content),
- id: question.id,
- title: question.title,
- currentVote: null,
- createdAt: question.createdAt,
- };
-
- await redis.hset(`question:${questionId}`, cachePayload); // Store the question data as a hash
- }
-
- return new Response("OK");
- }
-
- // if vote type is different, update the vote
- await db.questionVote.update({
- where: {
- userId_questionId: {
- questionId,
- userId: session.user.id,
- },
- },
- data: {
- type: voteType,
- },
- });
-
- // Recount the votes
- const votesAmt = question.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedQuestion = {
- authorName: question.author.name ?? "",
- content: JSON.stringify(question.content),
- id: question.id,
- title: question.title,
- currentVote: voteType,
- createdAt: question.createdAt,
- };
-
- await redis.hset(`question:${questionId}`, cachePayload); // Store the question data as a hash
- }
-
- return new Response("OK");
- }
-
- // if no existing vote, create a new vote
- await db.questionVote.create({
- data: {
- type: voteType,
- userId: session.user.id,
- questionId,
- },
- });
-
- // Recount the votes
- const votesAmt = question.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- if (votesAmt >= CACHE_AFTER_UPVOTES) {
- const cachePayload: CachedQuestion = {
- authorName: question.author.name ?? "",
- content: JSON.stringify(question.content),
- id: question.id,
- title: question.title,
- currentVote: voteType,
- createdAt: question.createdAt,
- };
-
- await redis.hset(`question:${questionId}`, cachePayload); // Store the question data as a hash
- }
-
- return new Response("OK");
- } catch (error) {
- error;
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 400 });
- }
-
- return new Response("Internal Server Error", { status: 500 });
- }
+ try {
+ const body = await req.json()
+
+ const { questionId, voteType } = QuestionVoteValidator.parse(body)
+
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ // check if user has already voted on this question
+ const existingVote = await db.questionVote.findFirst({
+ where: {
+ userId: session.user.id,
+ questionId,
+ },
+ })
+
+ const question = await db.question.findUnique({
+ where: {
+ id: questionId,
+ },
+ include: {
+ author: true,
+ votes: true,
+ },
+ })
+
+ if (!question) {
+ return new Response("Question not found", { status: 404 })
+ }
+
+ if (existingVote) {
+ // if vote type is the same as existing vote, delete the vote
+ if (existingVote.type === voteType) {
+ await db.questionVote.delete({
+ where: {
+ userId_questionId: {
+ questionId,
+ userId: session.user.id,
+ },
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = question.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedQuestion = {
+ authorName: question.author.name ?? "",
+ content: JSON.stringify(question.content),
+ id: question.id,
+ title: question.title,
+ currentVote: null,
+ createdAt: question.createdAt,
+ }
+
+ await redis.hset(`question:${questionId}`, cachePayload) // Store the question data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if vote type is different, update the vote
+ await db.questionVote.update({
+ where: {
+ userId_questionId: {
+ questionId,
+ userId: session.user.id,
+ },
+ },
+ data: {
+ type: voteType,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = question.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedQuestion = {
+ authorName: question.author.name ?? "",
+ content: JSON.stringify(question.content),
+ id: question.id,
+ title: question.title,
+ currentVote: voteType,
+ createdAt: question.createdAt,
+ }
+
+ await redis.hset(`question:${questionId}`, cachePayload) // Store the question data as a hash
+ }
+
+ return new Response("OK")
+ }
+
+ // if no existing vote, create a new vote
+ await db.questionVote.create({
+ data: {
+ type: voteType,
+ userId: session.user.id,
+ questionId,
+ },
+ })
+
+ // Recount the votes
+ const votesAmt = question.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ if (votesAmt >= CACHE_AFTER_UPVOTES) {
+ const cachePayload: CachedQuestion = {
+ authorName: question.author.name ?? "",
+ content: JSON.stringify(question.content),
+ id: question.id,
+ title: question.title,
+ currentVote: voteType,
+ createdAt: question.createdAt,
+ }
+
+ await redis.hset(`question:${questionId}`, cachePayload) // Store the question data as a hash
+ }
+
+ return new Response("OK")
+ } catch (error) {
+ error
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 400 })
+ }
+
+ return new Response("Internal Server Error", { status: 500 })
+ }
}
diff --git a/src/app/api/subject/route.ts b/src/app/api/subject/route.ts
index a966e0c..40a23cd 100644
--- a/src/app/api/subject/route.ts
+++ b/src/app/api/subject/route.ts
@@ -1,52 +1,52 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { SubjectValidator } from "@/lib/validators/subject";
-import { z } from "zod";
-import { SemesterType } from "@prisma/client";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { SubjectValidator } from "@/lib/validators/subject"
+import { z } from "zod"
+import { SemesterType } from "@prisma/client"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
-
- if (!session) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const body = await req.json();
- const { name, acronym, semester } = SubjectValidator.parse(body);
-
- const subjectExists = await db.subject.findFirst({
- where: {
- OR: [{ name }, { acronym }],
- },
- });
-
- if (subjectExists) {
- return new Response("Subject already exists", { status: 409 });
- }
-
- const subject = await db.subject.create({
- data: {
- name,
- acronym,
- creatorId: session.user.id,
- semester: semester as SemesterType,
- },
- });
-
- await db.subscription.create({
- data: {
- userId: session.user.id,
- subjectId: subject.id,
- },
- });
-
- return new Response(subject.acronym);
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
-
- return new Response("Could not create subject", { status: 500 });
- }
+ try {
+ const session = await getAuthSession()
+
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ const body = await req.json()
+ const { name, acronym, semester } = SubjectValidator.parse(body)
+
+ const subjectExists = await db.subject.findFirst({
+ where: {
+ OR: [{ name }, { acronym }],
+ },
+ })
+
+ if (subjectExists) {
+ return new Response("Subject already exists", { status: 409 })
+ }
+
+ const subject = await db.subject.create({
+ data: {
+ name,
+ acronym,
+ creatorId: session.user.id,
+ semester: semester as SemesterType,
+ },
+ })
+
+ await db.subscription.create({
+ data: {
+ userId: session.user.id,
+ subjectId: subject.id,
+ },
+ })
+
+ return new Response(subject.acronym)
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+
+ return new Response("Could not create subject", { status: 500 })
+ }
}
diff --git a/src/app/api/subject/subscribe/route.ts b/src/app/api/subject/subscribe/route.ts
index 2e72310..dad6c96 100644
--- a/src/app/api/subject/subscribe/route.ts
+++ b/src/app/api/subject/subscribe/route.ts
@@ -1,44 +1,46 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { SubjectSubscriptionValidator } from "@/lib/validators/subject";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { SubjectSubscriptionValidator } from "@/lib/validators/subject"
+import { z } from "zod"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const body = await req.json();
-
- const { subjectId } = SubjectSubscriptionValidator.parse(body);
-
- const subscriptionExists = await db.subscription.findFirst({
- where: {
- subjectId,
- userId: session.user.id,
- },
- });
-
- if (subscriptionExists) {
- return new Response("Already subscribed", { status: 400 });
- }
-
- await db.subscription.create({
- data: {
- subjectId,
- userId: session.user.id,
- },
- });
-
- return new Response(subjectId);
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
-
- return new Response("Could not subscribe, please try again", { status: 500 });
- }
+ try {
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ const body = await req.json()
+
+ const { subjectId } = SubjectSubscriptionValidator.parse(body)
+
+ const subscriptionExists = await db.subscription.findFirst({
+ where: {
+ subjectId,
+ userId: session.user.id,
+ },
+ })
+
+ if (subscriptionExists) {
+ return new Response("Already subscribed", { status: 400 })
+ }
+
+ await db.subscription.create({
+ data: {
+ subjectId,
+ userId: session.user.id,
+ },
+ })
+
+ return new Response(subjectId)
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+
+ return new Response("Could not subscribe, please try again", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/subject/unsubscribe/route.ts b/src/app/api/subject/unsubscribe/route.ts
index c8a99a4..86a6bae 100644
--- a/src/app/api/subject/unsubscribe/route.ts
+++ b/src/app/api/subject/unsubscribe/route.ts
@@ -1,57 +1,61 @@
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { SubjectSubscriptionValidator } from "@/lib/validators/subject";
-import { z } from "zod";
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import { SubjectSubscriptionValidator } from "@/lib/validators/subject"
+import { z } from "zod"
export async function POST(req: Request) {
- try {
- const session = await getAuthSession();
-
- if (!session?.user) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- const body = await req.json();
-
- const { subjectId } = SubjectSubscriptionValidator.parse(body);
-
- const subscriptionExists = await db.subscription.findFirst({
- where: {
- subjectId,
- userId: session.user.id,
- },
- });
-
- if (!subscriptionExists) {
- return new Response("Not subscribed yet", { status: 400 });
- }
-
- const subject = await db.subject.findFirst({
- where: {
- id: subjectId,
- creatorId: session.user.id,
- },
- });
-
- if (subject) {
- return new Response("Cannot unsubscribe from your own subject", { status: 400 });
- }
-
- await db.subscription.delete({
- where: {
- userId_subjectId: {
- subjectId,
- userId: session.user.id,
- },
- },
- });
-
- return new Response(subjectId);
- } catch (error) {
- if (error instanceof z.ZodError) {
- return new Response(error.message, { status: 422 });
- }
-
- return new Response("Could not unsubscribe, please try again", { status: 500 });
- }
+ try {
+ const session = await getAuthSession()
+
+ if (!session?.user) {
+ return new Response("Unauthorized", { status: 401 })
+ }
+
+ const body = await req.json()
+
+ const { subjectId } = SubjectSubscriptionValidator.parse(body)
+
+ const subscriptionExists = await db.subscription.findFirst({
+ where: {
+ subjectId,
+ userId: session.user.id,
+ },
+ })
+
+ if (!subscriptionExists) {
+ return new Response("Not subscribed yet", { status: 400 })
+ }
+
+ const subject = await db.subject.findFirst({
+ where: {
+ id: subjectId,
+ creatorId: session.user.id,
+ },
+ })
+
+ if (subject) {
+ return new Response("Cannot unsubscribe from your own subject", {
+ status: 400,
+ })
+ }
+
+ await db.subscription.delete({
+ where: {
+ userId_subjectId: {
+ subjectId,
+ userId: session.user.id,
+ },
+ },
+ })
+
+ return new Response(subjectId)
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ return new Response(error.message, { status: 422 })
+ }
+
+ return new Response("Could not unsubscribe, please try again", {
+ status: 500,
+ })
+ }
}
diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts
index aff8475..d317f1e 100644
--- a/src/app/api/uploadthing/core.ts
+++ b/src/app/api/uploadthing/core.ts
@@ -1,37 +1,41 @@
-import { createUploadthing, type FileRouter } from "uploadthing/next";
-import { UploadThingError } from "@uploadthing/shared";
-import { getToken } from "next-auth/jwt";
+import { createUploadthing, type FileRouter } from "uploadthing/next"
+import { UploadThingError } from "@uploadthing/shared"
+import { getToken } from "next-auth/jwt"
-const f = createUploadthing();
+const f = createUploadthing()
// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
- // Define as many FileRoutes as you like, each with a unique routeSlug
- imageUploader: f({ image: { maxFileSize: "4MB" } })
- // Set permissions and file types for this FileRoute
- .middleware(async (req) => {
- // This code runs on your server before upload
- const user = await getToken({ req });
+ // Define as many FileRoutes as you like, each with a unique routeSlug
+ imageUploader: f({ image: { maxFileSize: "4MB" } })
+ // Set permissions and file types for this FileRoute
+ .middleware(async (req) => {
+ // This code runs on your server before upload
+ const user = await getToken({ req })
- // If you throw, the user will not be able to upload
- if (!user) throw new UploadThingError({ code: "FORBIDDEN", message: "Unauthorized" });
+ // If you throw, the user will not be able to upload
+ if (!user)
+ throw new UploadThingError({
+ code: "FORBIDDEN",
+ message: "Unauthorized",
+ })
- // Whatever is returned here is accessible in onUploadComplete as `metadata`
- return { userId: user.id };
- })
- .onUploadComplete(async ({}) => {}),
+ // Whatever is returned here is accessible in onUploadComplete as `metadata`
+ return { userId: user.id }
+ })
+ .onUploadComplete(async ({}) => {}),
- // Another FileRoute (made by myself, not by the library)
- fileUploader: f({
- pdf: { maxFileCount: 1, maxFileSize: "128MB" },
- text: { maxFileCount: 5 },
- })
- .middleware(async (req) => {
- const user = await getToken({ req });
- if (!user) throw new UploadThingError({ code: "FORBIDDEN" });
- return { userId: user.id };
- })
- .onUploadComplete(async ({}) => {}),
-} satisfies FileRouter;
+ // Another FileRoute (made by myself, not by the library)
+ fileUploader: f({
+ pdf: { maxFileCount: 1, maxFileSize: "128MB" },
+ text: { maxFileCount: 5 },
+ })
+ .middleware(async (req) => {
+ const user = await getToken({ req })
+ if (!user) throw new UploadThingError({ code: "FORBIDDEN" })
+ return { userId: user.id }
+ })
+ .onUploadComplete(async ({}) => {}),
+} satisfies FileRouter
-export type OurFileRouter = typeof ourFileRouter;
+export type OurFileRouter = typeof ourFileRouter
diff --git a/src/app/api/uploadthing/route.ts b/src/app/api/uploadthing/route.ts
index b41db7d..b4310d6 100644
--- a/src/app/api/uploadthing/route.ts
+++ b/src/app/api/uploadthing/route.ts
@@ -1,8 +1,8 @@
-import { createNextRouteHandler } from "uploadthing/next";
+import { createNextRouteHandler } from "uploadthing/next"
-import { ourFileRouter } from "./core";
+import { ourFileRouter } from "./core"
// Export routes for Next App Router
export const { GET, POST } = createNextRouteHandler({
- router: ourFileRouter,
-});
+ router: ourFileRouter,
+})
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 01b55dc..a5bdfcb 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,37 +1,49 @@
-import { cn } from "@/lib/utils";
-import "@/styles/globals.css";
-import { Inter } from "next/font/google";
-import Navbar from "@/components/Navbar";
-import { Toaster } from "@/components/ui/Toaster";
-import Providers from "@/components/Providers";
+import { cn } from "@/lib/utils"
+import "@/styles/globals.css"
+import { Inter } from "next/font/google"
+import Navbar from "@/components/Navbar"
+import { Toaster } from "@/components/ui/Toaster"
+import Providers from "@/components/Providers"
export const metadata = {
- title: "Apunts Dades",
- description:
- "Un forum per a compartir, recomanar i discutir sobre apunts del Grau en Ciència i Enginyeria de Dades (GCED) de la UPC",
-};
+ title: "Apunts Dades",
+ description:
+ "Un forum per a compartir, recomanar i discutir sobre apunts del Grau en Ciència i Enginyeria de Dades (GCED) de la UPC",
+}
-const inter = Inter({ subsets: ["latin"] }); // TODO: Fer servir la font de l'AED
+const inter = Inter({ subsets: ["latin"] }) // TODO: Fer servir la font de l'AED
-function RootLayout({ children, authModal }: { children: React.ReactNode; authModal: React.ReactNode }) {
- return (
-
-
-
- {/* @ts-expect-error server component */}
-
+function RootLayout({
+ children,
+ authModal,
+}: {
+ children: React.ReactNode
+ authModal: React.ReactNode
+}) {
+ return (
+
+
+
+ {/* @ts-expect-error server component */}
+
- {authModal}
+ {authModal}
- {children}
+
+ {children}
+
-
-
-
-
- );
+
+
+
+
+ )
}
-export default RootLayout;
\ No newline at end of file
+export default RootLayout
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 965c956..a660de4 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,67 +1,67 @@
// import CustomFeed from "@/components/CustomFeed";
-import { buttonVariants } from "@/components/ui/Button";
+import { buttonVariants } from "@/components/ui/Button"
// import { getAuthSession } from "@/lib/auth";
-import { HomeIcon } from "lucide-react";
-import Link from "next/link";
+import { HomeIcon } from "lucide-react"
+import Link from "next/link"
-import { db } from "@/lib/db";
-import { BookIcon } from "lucide-react";
+import { db } from "@/lib/db"
+import { BookIcon } from "lucide-react"
// import { HeartIcon, HeartPulseIcon } from "lucide-react";
-import { Badge } from "@/components/ui/Badge";
-import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/Badge"
+import { cn } from "@/lib/utils"
export default async function Home() {
- // const session = await getAuthSession();
+ // const session = await getAuthSession();
- const subjects = await db.subject.findMany({
- select: {
- id: true,
- acronym: true,
- name: true,
- semester: true,
- },
- });
+ const subjects = await db.subject.findMany({
+ select: {
+ id: true,
+ acronym: true,
+ name: true,
+ semester: true,
+ },
+ })
- // const subscription = !session?.user
- // ? undefined
- // : await db.subscription.findFirst({
- // where: {
- // userId: session.user.id,
- // subjectId: subjects.id,
- // },
- // });
+ // const subscription = !session?.user
+ // ? undefined
+ // : await db.subscription.findFirst({
+ // where: {
+ // userId: session.user.id,
+ // subjectId: subjects.id,
+ // },
+ // });
- // const isSubscribed = !!subscription;
- // const ColorClass = isSubscribed ? "text-red-500" : "text-black";
+ // const isSubscribed = !!subscription;
+ // const ColorClass = isSubscribed ? "text-red-500" : "text-black";
- function semesterColor(semester: string) {
- switch (semester) {
- case "Q1":
- return "bg-emerald-100";
- case "Q2":
- return "bg-rose-100";
- case "Q3":
- return "bg-cyan-100";
- case "Q4":
- return "bg-amber-100";
- case "Q5":
- return "bg-violet-100";
- case "Q6":
- return "bg-blue-100";
- default:
- return "bg-gray-100";
- }
- }
+ function semesterColor(semester: string) {
+ switch (semester) {
+ case "Q1":
+ return "bg-emerald-100"
+ case "Q2":
+ return "bg-rose-100"
+ case "Q3":
+ return "bg-cyan-100"
+ case "Q4":
+ return "bg-amber-100"
+ case "Q5":
+ return "bg-violet-100"
+ case "Q6":
+ return "bg-blue-100"
+ default:
+ return "bg-gray-100"
+ }
+ }
- return (
- <>
- El teu espai
-
- {/* Feed
+ return (
+ <>
+
El teu espai
+
+ {/* Feed
{session ?
: null} */}
- {/* subjects info */}
- {/*
+ {/* subjects info */}
+ {/*
@@ -86,51 +86,55 @@ export default async function Home() {
*/}
-
-
+
+
-
-
- Penja Apunts
-
-
-
-
+
+
+ Penja Apunts
+
+
+
+
-
- {subjects.map((subject, index) => {
- return (
-
-
-
-
-
- {subject.name}
- {/* */}
-
-
+
+ {subjects.map((subject, index) => {
+ return (
+
+
+
+
+
+ {subject.name}
+ {/* */}
+
+
-
- {subject.semester}
- {subject.acronym}
-
-
-
- );
- })}
-
- >
- );
+
+ {subject.semester}
+ {subject.acronym}
+
+
+
+ )
+ })}
+
+ >
+ )
}
diff --git a/src/app/privacyandterms/page.tsx b/src/app/privacyandterms/page.tsx
index c090c19..0ce4ccf 100644
--- a/src/app/privacyandterms/page.tsx
+++ b/src/app/privacyandterms/page.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from "react";
+import React, { FC } from "react"
interface PageProps {}
@@ -148,7 +148,7 @@ const Page: FC
= ({}) => {
acceptance of the updated terms.
- );
-};
+ )
+}
-export default Page;
+export default Page
diff --git a/src/app/submit/page.tsx b/src/app/submit/page.tsx
index 8d19989..3f3818f 100644
--- a/src/app/submit/page.tsx
+++ b/src/app/submit/page.tsx
@@ -1,10 +1,10 @@
-import { FC } from "react";
-import { ProfileForm } from "@/components/Form";
+import { FC } from "react"
+import { ProfileForm } from "@/components/Form"
interface pageProps {}
const page: FC = ({}) => {
- return ;
-};
+ return
+}
-export default page;
+export default page
diff --git a/src/components/AnswerComponent.tsx b/src/components/AnswerComponent.tsx
index 0c970a3..2b5014c 100644
--- a/src/components/AnswerComponent.tsx
+++ b/src/components/AnswerComponent.tsx
@@ -1,86 +1,91 @@
-"use client";
+"use client"
-import { formatTimeToNow } from "@/lib/utils";
-import { Answer, User, AnswerVote } from "@prisma/client";
-import { FC, useRef } from "react";
-import EditorOutput from "./EditorOutput";
-import AnswerVoteClient from "./votes/AnswerVoteClient";
-import AnswerAcceptClient from "./votes/AnswerAcceptClient";
+import { formatTimeToNow } from "@/lib/utils"
+import { Answer, User, AnswerVote } from "@prisma/client"
+import { FC, useRef } from "react"
+import EditorOutput from "./EditorOutput"
+import AnswerVoteClient from "./votes/AnswerVoteClient"
+import AnswerAcceptClient from "./votes/AnswerAcceptClient"
-type PartialVote = Pick;
+type PartialVote = Pick
interface AnswerProps {
- answer: Answer & {
- author: User;
- votes: AnswerVote[];
- };
- votesAmt: number;
- subjectName: string;
- currentVote?: PartialVote;
- subjectAcronym: string;
- questionId: string;
- questionAuthorId: string;
+ answer: Answer & {
+ author: User
+ votes: AnswerVote[]
+ }
+ votesAmt: number
+ subjectName: string
+ currentVote?: PartialVote
+ subjectAcronym: string
+ questionId: string
+ questionAuthorId: string
}
const AnswerComponent: FC = ({
- answer,
- votesAmt: _votesAmt,
- currentVote: _currentVote,
- subjectName,
- subjectAcronym,
- questionId,
- questionAuthorId,
+ answer,
+ votesAmt: _votesAmt,
+ currentVote: _currentVote,
+ subjectName,
+ subjectAcronym,
+ questionId,
+ questionAuthorId,
}) => {
- const pRef = useRef(null);
+ const pRef = useRef(null)
- return (
-
-
-
-
+ return (
+
+
-
-
- {subjectName ? (
- <>
-
- {subjectAcronym}
-
-
•
- >
- ) : null}
-
Compartit per {answer.author.name} {formatTimeToNow(new Date(answer.createdAt))}
-
-
- {answer.title}
-
+
+
+ {subjectName ? (
+ <>
+
+ {subjectAcronym}
+
+
•
+ >
+ ) : null}
+
Compartit per {answer.author.name} {" "}
+ {formatTimeToNow(new Date(answer.createdAt))}
+
+
+
+ {answer.title}
+
+
-
-
- {pRef.current?.clientHeight === 160 ? (
- // blur bottom if content is too long
-
- ) : null}
-
-
-
-
- );
-};
-export default AnswerComponent;
+
+
+ {pRef.current?.clientHeight === 160 ? (
+ // blur bottom if content is too long
+
+ ) : null}
+
+
+
+
+ )
+}
+export default AnswerComponent
diff --git a/src/components/AnswerFeed.tsx b/src/components/AnswerFeed.tsx
index 5b8423f..161cd50 100644
--- a/src/components/AnswerFeed.tsx
+++ b/src/components/AnswerFeed.tsx
@@ -1,108 +1,113 @@
-"use client";
+"use client"
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { ExtendedAnswer } from "@/types/db";
-import { useIntersection } from "@mantine/hooks";
-import { useInfiniteQuery } from "@tanstack/react-query";
-import axios from "axios";
-import { Loader2 } from "lucide-react";
-import { FC, useEffect, useRef } from "react";
-import { useSession } from "next-auth/react";
-import AnswerComponent from "@/components/AnswerComponent";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { ExtendedAnswer } from "@/types/db"
+import { useIntersection } from "@mantine/hooks"
+import { useInfiniteQuery } from "@tanstack/react-query"
+import axios from "axios"
+import { Loader2 } from "lucide-react"
+import { FC, useEffect, useRef } from "react"
+import { useSession } from "next-auth/react"
+import AnswerComponent from "@/components/AnswerComponent"
interface AnswerFeedProps {
- initialAnswers: ExtendedAnswer[];
- subjectName: string;
- subjectAcronym: string;
- questionId: string;
+ initialAnswers: ExtendedAnswer[]
+ subjectName: string
+ subjectAcronym: string
+ questionId: string
}
-const AnswerFeed: FC = ({ initialAnswers, subjectName, subjectAcronym, questionId }) => {
- const lastAnswerRef = useRef(null);
- const { ref, entry } = useIntersection({
- root: lastAnswerRef.current,
- threshold: 1,
- });
- const { data: session } = useSession();
+const AnswerFeed: FC = ({
+ initialAnswers,
+ subjectName,
+ subjectAcronym,
+ questionId,
+}) => {
+ const lastAnswerRef = useRef(null)
+ const { ref, entry } = useIntersection({
+ root: lastAnswerRef.current,
+ threshold: 1,
+ })
+ const { data: session } = useSession()
- const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
- ["infinite-query"],
- async ({ pageParam = 1 }) => {
- const query =
- `/api/a?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
- (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "") +
- (!!questionId ? `&questionId=${questionId}` : "");
- const { data } = await axios.get(query);
- return data as ExtendedAnswer[];
- },
+ const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
+ ["infinite-query"],
+ async ({ pageParam = 1 }) => {
+ const query =
+ `/api/a?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
+ (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "") +
+ (!!questionId ? `&questionId=${questionId}` : "")
+ const { data } = await axios.get(query)
+ return data as ExtendedAnswer[]
+ },
- {
- getNextPageParam: (_, pages) => {
- return pages.length + 1;
- },
- initialData: { pages: [initialAnswers], pageParams: [1] },
- }
- );
+ {
+ getNextPageParam: (_, pages) => {
+ return pages.length + 1
+ },
+ initialData: { pages: [initialAnswers], pageParams: [1] },
+ },
+ )
- useEffect(() => {
- if (entry?.isIntersecting) {
- fetchNextPage(); // Load more answers when the last answer comes into view
- }
- }, [entry, fetchNextPage]);
+ useEffect(() => {
+ if (entry?.isIntersecting) {
+ fetchNextPage() // Load more answers when the last answer comes into view
+ }
+ }, [entry, fetchNextPage])
- const answers = data?.pages.flatMap((page) => page) ?? initialAnswers;
+ const answers = data?.pages.flatMap((page) => page) ?? initialAnswers
- return (
-
- {answers.map((answer, index) => {
- const votesAmt = answer.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
+ return (
+
+ {answers.map((answer, index) => {
+ const votesAmt = answer.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
- const currentVote = answer.votes.find((vote) => vote.userId === session?.user.id);
+ const currentVote = answer.votes.find(
+ (vote) => vote.userId === session?.user.id,
+ )
- if (index === answers.length - 1) {
- // Add a ref to the last answer in the list
- return (
-
-
-
- );
- } else {
- return (
-
- );
- }
- })}
+ if (index === answers.length - 1) {
+ // Add a ref to the last answer in the list
+ return (
+
+
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
- {isFetchingNextPage && (
-
-
-
- )}
-
- );
-};
+ {isFetchingNextPage && (
+
+
+
+ )}
+
+ )
+}
-export default AnswerFeed;
+export default AnswerFeed
diff --git a/src/components/CloseModal.tsx b/src/components/CloseModal.tsx
index fa04d0b..2bf12e8 100644
--- a/src/components/CloseModal.tsx
+++ b/src/components/CloseModal.tsx
@@ -1,21 +1,22 @@
-"use client";
+"use client"
-import { X } from "lucide-react";
-import { Button } from "./ui/Button";
-import { useRouter } from "next/navigation";
+import { X } from "lucide-react"
+import { Button } from "./ui/Button"
+import { useRouter } from "next/navigation"
const CloseModal = ({}) => {
- const router = useRouter();
+ const router = useRouter()
- return (
- router.back()}
- aria-label="Tancar Modal">
-
-
- );
-};
+ return (
+ router.back()}
+ aria-label="Tancar Modal"
+ >
+
+
+ )
+}
-export default CloseModal;
+export default CloseModal
diff --git a/src/components/Combobox.tsx b/src/components/Combobox.tsx
index aeeed26..b7258ed 100644
--- a/src/components/Combobox.tsx
+++ b/src/components/Combobox.tsx
@@ -17,54 +17,62 @@ import {
} from "@/components/ui/Popover"
export interface Option {
- value: string;
- label: string;
+ value: string
+ label: string
}
-export function Combobox({ options, value, setValue }: { options: Option[], value: string, setValue: Function}) {
- const [open, setOpen] = React.useState(false);
+export function Combobox({
+ options,
+ value,
+ setValue,
+}: {
+ options: Option[]
+ value: string
+ setValue: Function
+}) {
+ const [open, setOpen] = React.useState(false)
return (
-
-
-
- {value
- ? options.find((option) => option.value === value)?.label
- : "Select..."}
-
-
-
-
-
-
- No option found.
-
- {options.map((option) => (
- {
- setValue(currentValue === value ? "" : currentValue);
- setOpen(false);
- }}
- >
-
- {option.label}
-
- ))}
-
-
-
-
+
+
+
+ {value
+ ? options.find((option) => option.value === value)?.label
+ : "Select..."}
+
+
+
+
+
+
+ No option found.
+
+ {options.map((option) => (
+ {
+ setValue(currentValue === value ? "" : currentValue)
+ setOpen(false)
+ }}
+ >
+
+ {option.label}
+
+ ))}
+
+
+
+
- );
-}
\ No newline at end of file
+ )
+}
diff --git a/src/components/CommentComponent.tsx b/src/components/CommentComponent.tsx
index d60c4c8..e8f83ea 100644
--- a/src/components/CommentComponent.tsx
+++ b/src/components/CommentComponent.tsx
@@ -1,69 +1,72 @@
-"use client";
+"use client"
-import { formatTimeToNow } from "@/lib/utils";
-import { Comment, User, CommentVote } from "@prisma/client";
-import { FC, useRef } from "react";
+import { formatTimeToNow } from "@/lib/utils"
+import { Comment, User, CommentVote } from "@prisma/client"
+import { FC, useRef } from "react"
// import EditorOutput from "./EditorOutput";
-import CommentVoteClient from "./votes/CommentVoteClient";
+import CommentVoteClient from "./votes/CommentVoteClient"
-type PartialVote = Pick;
+type PartialVote = Pick
interface CommentProps {
- comment: Comment & {
- author: User;
- votes: CommentVote[];
- };
- votesAmt: number;
- subjectName: string;
- currentVote?: PartialVote;
- subjectAcronym: string;
- postId: string;
+ comment: Comment & {
+ author: User
+ votes: CommentVote[]
+ }
+ votesAmt: number
+ subjectName: string
+ currentVote?: PartialVote
+ subjectAcronym: string
+ postId: string
}
const CommentComponent: FC = ({
- comment,
- votesAmt: _votesAmt,
- currentVote: _currentVote,
- subjectName,
- subjectAcronym,
+ comment,
+ votesAmt: _votesAmt,
+ currentVote: _currentVote,
+ subjectName,
+ subjectAcronym,
}) => {
- const pRef = useRef(null);
+ const pRef = useRef(null)
- return (
-
-
-
+ return (
+
+
+
-
-
- {subjectName ? (
- <>
-
- {subjectAcronym}
-
-
•
- >
- ) : null}
-
Compartit per {comment.author.name} {formatTimeToNow(new Date(comment.createdAt))}
-
-
-
{comment.content}
- {pRef.current?.clientHeight === 160 ? (
- // blur bottom if content is too long
-
- ) : null}
-
-
-
-
- );
-};
-export default CommentComponent;
+
+
+ {subjectName ? (
+ <>
+
+ {subjectAcronym}
+
+
•
+ >
+ ) : null}
+
Compartit per {comment.author.name} {" "}
+ {formatTimeToNow(new Date(comment.createdAt))}
+
+
+
{comment.content}
+ {pRef.current?.clientHeight === 160 ? (
+ // blur bottom if content is too long
+
+ ) : null}
+
+
+
+
+ )
+}
+export default CommentComponent
diff --git a/src/components/CommentFeed.tsx b/src/components/CommentFeed.tsx
index 741eb9e..110c9a6 100644
--- a/src/components/CommentFeed.tsx
+++ b/src/components/CommentFeed.tsx
@@ -1,106 +1,111 @@
-"use client";
+"use client"
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { ExtendedComment } from "@/types/db";
-import { useIntersection } from "@mantine/hooks";
-import { useInfiniteQuery } from "@tanstack/react-query";
-import axios from "axios";
-import { Loader2 } from "lucide-react";
-import { FC, useEffect, useRef } from "react";
-import { useSession } from "next-auth/react";
-import CommentComponent from "@/components/CommentComponent";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { ExtendedComment } from "@/types/db"
+import { useIntersection } from "@mantine/hooks"
+import { useInfiniteQuery } from "@tanstack/react-query"
+import axios from "axios"
+import { Loader2 } from "lucide-react"
+import { FC, useEffect, useRef } from "react"
+import { useSession } from "next-auth/react"
+import CommentComponent from "@/components/CommentComponent"
interface CommentFeedProps {
- initialComments: ExtendedComment[];
- subjectName: string;
- subjectAcronym: string;
- postId: string;
+ initialComments: ExtendedComment[]
+ subjectName: string
+ subjectAcronym: string
+ postId: string
}
-const CommentFeed: FC = ({ initialComments, subjectName, subjectAcronym, postId }) => {
- const lastCommentRef = useRef(null);
- const { ref, entry } = useIntersection({
- root: lastCommentRef.current,
- threshold: 1,
- });
- const { data: session } = useSession();
+const CommentFeed: FC = ({
+ initialComments,
+ subjectName,
+ subjectAcronym,
+ postId,
+}) => {
+ const lastCommentRef = useRef(null)
+ const { ref, entry } = useIntersection({
+ root: lastCommentRef.current,
+ threshold: 1,
+ })
+ const { data: session } = useSession()
- const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
- ["infinite-query"],
- async ({ pageParam = 1 }) => {
- const query =
- `/api/comments?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
- (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "") +
- (!!postId ? `&postId=${postId}` : "");
- const { data } = await axios.get(query);
- return data as ExtendedComment[];
- },
+ const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
+ ["infinite-query"],
+ async ({ pageParam = 1 }) => {
+ const query =
+ `/api/comments?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
+ (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "") +
+ (!!postId ? `&postId=${postId}` : "")
+ const { data } = await axios.get(query)
+ return data as ExtendedComment[]
+ },
- {
- getNextPageParam: (_, pages) => {
- return pages.length + 1;
- },
- initialData: { pages: [initialComments], pageParams: [1] },
- }
- );
+ {
+ getNextPageParam: (_, pages) => {
+ return pages.length + 1
+ },
+ initialData: { pages: [initialComments], pageParams: [1] },
+ },
+ )
- useEffect(() => {
- if (entry?.isIntersecting) {
- fetchNextPage(); // Load more comments when the last comment comes into view
- }
- }, [entry, fetchNextPage]);
+ useEffect(() => {
+ if (entry?.isIntersecting) {
+ fetchNextPage() // Load more comments when the last comment comes into view
+ }
+ }, [entry, fetchNextPage])
- const comments = data?.pages.flatMap((page) => page) ?? initialComments;
+ const comments = data?.pages.flatMap((page) => page) ?? initialComments
- return (
-
- {comments.map((comment, index) => {
- const votesAmt = comment.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
+ return (
+
+ {comments.map((comment, index) => {
+ const votesAmt = comment.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
- const currentVote = comment.votes.find((vote) => vote.userId === session?.user.id);
+ const currentVote = comment.votes.find(
+ (vote) => vote.userId === session?.user.id,
+ )
- if (index === comments.length - 1) {
- // Add a ref to the last comment in the list
- return (
-
-
-
- );
- } else {
- return (
-
- );
- }
- })}
+ if (index === comments.length - 1) {
+ // Add a ref to the last comment in the list
+ return (
+
+
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
- {isFetchingNextPage && (
-
-
-
- )}
-
- );
-};
+ {isFetchingNextPage && (
+
+
+
+ )}
+
+ )
+}
-export default CommentFeed;
+export default CommentFeed
diff --git a/src/components/CustomFeed.tsx b/src/components/CustomFeed.tsx
index 02a6ea3..08ff8a0 100644
--- a/src/components/CustomFeed.tsx
+++ b/src/components/CustomFeed.tsx
@@ -1,45 +1,45 @@
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { getAuthSession } from "@/lib/auth";
-import { db } from "@/lib/db";
-import PostFeed from "./PostFeed";
-import { notFound } from "next/navigation";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { getAuthSession } from "@/lib/auth"
+import { db } from "@/lib/db"
+import PostFeed from "./PostFeed"
+import { notFound } from "next/navigation"
const CustomFeed = async () => {
- const session = await getAuthSession();
+ const session = await getAuthSession()
- // only rendered if session exists, so this will not happen
- if (!session) return notFound();
+ // only rendered if session exists, so this will not happen
+ if (!session) return notFound()
- const followedCommunities = await db.subscription.findMany({
- where: {
- userId: session.user.id,
- },
- include: {
- subject: true,
- },
- });
+ const followedCommunities = await db.subscription.findMany({
+ where: {
+ userId: session.user.id,
+ },
+ include: {
+ subject: true,
+ },
+ })
- const posts = await db.post.findMany({
- where: {
- subject: {
- name: {
- in: followedCommunities.map((sub) => sub.subject.name),
- },
- },
- },
- orderBy: {
- createdAt: "desc",
- },
- include: {
- votes: true,
- author: true,
- comments: true,
- subject: true,
- },
- take: INFINITE_SCROLL_PAGINATION_RESULTS,
- });
+ const posts = await db.post.findMany({
+ where: {
+ subject: {
+ name: {
+ in: followedCommunities.map((sub) => sub.subject.name),
+ },
+ },
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ include: {
+ votes: true,
+ author: true,
+ comments: true,
+ subject: true,
+ },
+ take: INFINITE_SCROLL_PAGINATION_RESULTS,
+ })
- return ;
-};
+ return
+}
-export default CustomFeed;
+export default CustomFeed
diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx
index 1ef2d96..c5a0a89 100644
--- a/src/components/Editor.tsx
+++ b/src/components/Editor.tsx
@@ -1,25 +1,25 @@
-"use client";
+"use client"
-import { FC, useCallback, useEffect, useRef, useState } from "react";
-import TextareaAutosize from "react-textarea-autosize";
-import { useForm } from "react-hook-form";
+import { FC, useCallback, useEffect, useRef, useState } from "react"
+import TextareaAutosize from "react-textarea-autosize"
+import { useForm } from "react-hook-form"
import {
QuestionCreationRequest,
QuestionValidator,
AnswerCreationRequest,
AnswerValidator,
-} from "@/lib/validators/question";
-import { zodResolver } from "@hookform/resolvers/zod";
-import type EditorJS from "@editorjs/editorjs";
-import { toast } from "@/hooks/use-toast";
-import { useMutation } from "@tanstack/react-query";
-import axios from "axios";
-import { usePathname, useRouter } from "next/navigation";
+} from "@/lib/validators/question"
+import { zodResolver } from "@hookform/resolvers/zod"
+import type EditorJS from "@editorjs/editorjs"
+import { toast } from "@/hooks/use-toast"
+import { useMutation } from "@tanstack/react-query"
+import axios from "axios"
+import { usePathname, useRouter } from "next/navigation"
interface EditorProps {
- subjectId: string;
- contentType: "question" | "answer";
- questionId?: string;
+ subjectId: string
+ contentType: "question" | "answer"
+ questionId?: string
}
const Editor: FC = ({ subjectId, contentType, questionId }) => {
@@ -28,7 +28,9 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
handleSubmit,
formState: { errors },
} = useForm({
- resolver: zodResolver(contentType === "question" ? QuestionValidator : AnswerValidator),
+ resolver: zodResolver(
+ contentType === "question" ? QuestionValidator : AnswerValidator,
+ ),
defaultValues: {
subjectId,
title: `${
@@ -37,30 +39,30 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
content: null,
questionId: questionId || "", // Add questionId as a value in the form
},
- });
+ })
- const ref = useRef();
- const [isMounted, setIsMounted] = useState(false);
- const _titleRef = useRef(null);
- const pathname = usePathname();
- const router = useRouter();
+ const ref = useRef()
+ const [isMounted, setIsMounted] = useState(false)
+ const _titleRef = useRef(null)
+ const pathname = usePathname()
+ const router = useRouter()
const initializeEditor = useCallback(async () => {
- const EditorJS = (await import("@editorjs/editorjs")).default;
- const Header = (await import("@editorjs/header")).default;
- const Embed = (await import("@editorjs/embed")).default;
- const Table = (await import("@editorjs/table")).default;
- const List = (await import("@editorjs/list")).default;
- const Code = (await import("@editorjs/code")).default;
- const LinkTool = (await import("@editorjs/link")).default;
- const InlineCode = (await import("@editorjs/inline-code")).default;
+ const EditorJS = (await import("@editorjs/editorjs")).default
+ const Header = (await import("@editorjs/header")).default
+ const Embed = (await import("@editorjs/embed")).default
+ const Table = (await import("@editorjs/table")).default
+ const List = (await import("@editorjs/list")).default
+ const Code = (await import("@editorjs/code")).default
+ const LinkTool = (await import("@editorjs/link")).default
+ const InlineCode = (await import("@editorjs/inline-code")).default
// const ImageTool = (await import("@editorjs/image")).default; TODO: do this later
if (!ref.current) {
const editor = new EditorJS({
holder: "editor",
onReady() {
- ref.current = editor;
+ ref.current = editor
},
placeholder: `Esciu aquí la teva ${
contentType === "question" ? "pregunta" : "resposta"
@@ -81,15 +83,15 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
table: Table,
embed: Embed,
},
- });
+ })
}
- }, []);
+ }, [])
useEffect(() => {
if (typeof window !== "undefined") {
- setIsMounted(true);
+ setIsMounted(true)
}
- }, []);
+ }, [])
useEffect(() => {
if (Object.keys(errors).length) {
@@ -98,29 +100,29 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
title: "Alguna cosa no ha anat bé...",
description: (value as { message: string }).message,
variant: "destructive",
- });
+ })
}
}
- }, [errors]);
+ }, [errors])
useEffect(() => {
const init = async () => {
- await initializeEditor();
+ await initializeEditor()
setTimeout(() => {
- _titleRef.current?.focus();
- }, 0);
- };
+ _titleRef.current?.focus()
+ }, 0)
+ }
if (isMounted) {
- init();
+ init()
return () => {
- ref.current?.destroy();
- ref.current = undefined;
- };
+ ref.current?.destroy()
+ ref.current = undefined
+ }
}
- }, [isMounted, initializeEditor]);
+ }, [isMounted, initializeEditor])
const { mutate: createContent } = useMutation({
mutationFn: async ({
@@ -133,13 +135,13 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
content,
subjectId,
...(contentType === "answer" && { questionId: questionId }),
- };
- const apiPath = contentType === "question" ? "/api/subject/question/create" : "/api/subject/answer/create";
- const { data } = await axios.post(
- apiPath,
- payload
- );
- return data;
+ }
+ const apiPath =
+ contentType === "question"
+ ? "/api/subject/question/create"
+ : "/api/subject/answer/create"
+ const { data } = await axios.post(apiPath, payload)
+ return data
},
onError: () => {
toast({
@@ -148,42 +150,42 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => {
contentType === "question" ? "pregunta" : "reposta"
}. Torna-ho a provar més tard.`,
variant: "destructive",
- });
+ })
},
onSuccess: (data) => {
// return new Response(JSON.stringify(createdQuestionId), { status: 201 });
if (contentType === "question") {
- const questionId = data as string;
- const newPathname = pathname.replace("/q", `/q/${questionId}`);
- router.push(newPathname);
+ const questionId = data as string
+ const newPathname = pathname.replace("/q", `/q/${questionId}`)
+ router.push(newPathname)
}
- router.refresh();
+ router.refresh()
return toast({
description: `La teva ${
contentType === "question" ? "pregunta" : "reposta"
} s'ha creat correctament`,
- });
+ })
},
- });
+ })
async function onSubmit() {
- const blocks = await ref.current?.save();
- const title = (await _titleRef.current?.value) as string;
+ const blocks = await ref.current?.save()
+ const title = (await _titleRef.current?.value) as string
const payload: QuestionCreationRequest | AnswerCreationRequest = {
title: title,
content: blocks,
subjectId: subjectId,
...(contentType === "answer" && { questionId: questionId }),
- };
- createContent(payload);
+ }
+ createContent(payload)
}
if (!isMounted) {
- return null;
+ return null
}
- const { ref: titleRef, ...rest } = register("title");
+ const { ref: titleRef, ...rest } = register("title")
return (
- );
-};
+ )
+}
-export default Editor;
+export default Editor
diff --git a/src/components/EditorOutput.tsx b/src/components/EditorOutput.tsx
index 6a6936d..5d33768 100644
--- a/src/components/EditorOutput.tsx
+++ b/src/components/EditorOutput.tsx
@@ -1,11 +1,11 @@
-import CustomCodeRenderer from '@/components/renderers/CustomCodeRenderer'
-import CustomImageRenderer from '@/components/renderers/CustomImageRenderer'
-import { FC } from 'react'
-import dynamic from 'next/dynamic'
+import CustomCodeRenderer from "@/components/renderers/CustomCodeRenderer"
+import CustomImageRenderer from "@/components/renderers/CustomImageRenderer"
+import { FC } from "react"
+import dynamic from "next/dynamic"
const Output = dynamic(
- async () => (await import('editorjs-react-renderer')).default,
- { ssr: false }
+ async () => (await import("editorjs-react-renderer")).default,
+ { ssr: false },
) as FC
interface EditorOutputProps {
@@ -19,8 +19,8 @@ const renderers = {
const style = {
paragraph: {
- fontSize: '0.875rem',
- lineHeight: '1.25rem',
+ fontSize: "0.875rem",
+ lineHeight: "1.25rem",
},
}
@@ -28,7 +28,7 @@ const EditorOutput: FC = ({ content }) => {
return (
diff --git a/src/components/Form.tsx b/src/components/Form.tsx
index 1613ab0..cbb7d7f 100644
--- a/src/components/Form.tsx
+++ b/src/components/Form.tsx
@@ -1,41 +1,52 @@
-"use client";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { FieldValues, SubmitHandler, useForm } from "react-hook-form";
-import { z } from "zod";
-import { useMutation } from "@tanstack/react-query";
-import axios from "axios";
-import { useRouter } from "next/navigation";
-import { toast } from "@/hooks/use-toast";
+"use client"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { FieldValues, SubmitHandler, useForm } from "react-hook-form"
+import { z } from "zod"
+import { useMutation } from "@tanstack/react-query"
+import axios from "axios"
+import { useRouter } from "next/navigation"
+import { toast } from "@/hooks/use-toast"
-import { Button } from "@/components/ui/Button";
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/Form";
-import { Input } from "@/components/ui/Input";
-import { Combobox } from "@/components/Combobox";
-import { ApuntsPostCreationRequest } from "@/lib/validators/post";
-import { uploadFiles } from "@/lib/uploadthing";
+import { Button } from "@/components/ui/Button"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/Form"
+import { Input } from "@/components/ui/Input"
+import { Combobox } from "@/components/Combobox"
+import { Checkbox } from "@/components/ui/checkbox"
+import { ApuntsPostCreationRequest } from "@/lib/validators/post"
+import { uploadFiles } from "@/lib/uploadthing"
const formSchema = z.object({
- pdf: z.any(),
- title: z.string({
- required_error: "Selecciona un usuari",
- }),
- assignatura: z.string({
- required_error: "Selecciona una assignatura.",
- }),
- tipus: z.string({
- required_error: "Selecciona un tipus.",
- }),
-});
+ pdf: z.any(),
+ title: z.string({
+ required_error: "Selecciona un usuari",
+ }),
+ assignatura: z.string({
+ required_error: "Selecciona una assignatura.",
+ }),
+ tipus: z.string({
+ required_error: "Selecciona un tipus.",
+ }),
+ anonim: z.boolean().default(false).optional(),
+})
const smallFormSchema = z.object({
- pdf: z.any(),
- title: z.string({
- required_error: "Selecciona un usuari",
- }),
- tipus: z.string({
- required_error: "Selecciona un tipus.",
- }),
-});
+ pdf: z.any(),
+ title: z.string({
+ required_error: "Selecciona un usuari",
+ }),
+ tipus: z.string({
+ required_error: "Selecciona un tipus.",
+ }),
+ anonim: z.boolean().default(false).optional(),
+})
// export function ComboboxForm() {
// const form = useForm>({
@@ -43,219 +54,232 @@ const smallFormSchema = z.object({
// })
export function ProfileForm() {
- const router = useRouter();
+ const router = useRouter()
- const { mutate: createApuntsPost } = useMutation({
- mutationFn: async ({ pdf, title, assignatura, tipus }: ApuntsPostCreationRequest) => {
- const payload: ApuntsPostCreationRequest = {
- pdf,
- title,
- assignatura,
- tipus,
- };
- const { data } = await axios.post("/api/subject/post/create", payload);
- return data;
- },
- onError: () => {
- toast({
- title: "Alguna cosa no ha anat bé",
- description: "No s'ha pogut crear el post. Torna-ho a provar més tard.",
- variant: "destructive",
- });
- },
- onSuccess: (subjectAcronym) => {
- router.push(`/${subjectAcronym}`);
- router.refresh();
+ const { mutate: createApuntsPost } = useMutation({
+ mutationFn: async ({
+ pdf,
+ title,
+ assignatura,
+ tipus,
+ anonim,
+ }: ApuntsPostCreationRequest) => {
+ const payload: ApuntsPostCreationRequest = {
+ pdf,
+ title,
+ assignatura,
+ tipus,
+ anonim,
+ }
+ const { data } = await axios.post("/api/subject/post/create", payload)
+ return data
+ },
+ onError: () => {
+ toast({
+ title: "Alguna cosa no ha anat bé",
+ description: "No s'ha pogut crear el post. Torna-ho a provar més tard.",
+ variant: "destructive",
+ })
+ },
+ onSuccess: (subjectAcronym) => {
+ router.push(`/${subjectAcronym}`)
+ router.refresh()
- return toast({
- description: "El teu post s'ha creat correctament",
- });
- },
- });
- const form = useForm({ resolver: zodResolver(formSchema) });
- async function onSubmit(data: ApuntsPostCreationRequest) {
- const [res] = await uploadFiles([data.pdf], "fileUploader");
- const payload: ApuntsPostCreationRequest = {
- pdf: res.fileUrl,
- title: data.title,
- assignatura: data.assignatura,
- tipus: data.tipus,
- };
+ return toast({
+ description: "El teu post s'ha creat correctament",
+ })
+ },
+ })
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ })
+ async function onSubmit(data: ApuntsPostCreationRequest) {
+ const [res] = await uploadFiles([data.pdf], "fileUploader")
+ const payload: ApuntsPostCreationRequest = {
+ pdf: res.fileUrl,
+ title: data.title,
+ assignatura: data.assignatura,
+ tipus: data.tipus,
+ anonim: data.anonim,
+ }
- createApuntsPost(payload);
- }
- // ------------------------------
- const assignatures = [
- {
- value: "alg",
- label: "ALG",
- },
- {
- value: "cal",
- label: "CAL",
- },
- {
- value: "lmd",
- label: "LMD",
- },
- {
- value: "ap1",
- label: "AP1",
- },
- {
- value: "ap2",
- label: "AP2",
- },
- {
- value: "ac2",
- label: "AC2",
- },
- {
- value: "pie1",
- label: "PIE1",
- },
- {
- value: "com",
- label: "COM",
- },
- {
- value: "sis",
- label: "SIS",
- },
- {
- value: "ap3",
- label: "AP3",
- },
- {
- value: "teoi",
- label: "TEOI",
- },
- {
- value: "pie2",
- label: "PIE2",
- },
- {
- value: "bd",
- label: "BD",
- },
- {
- value: "psd",
- label: "PSD",
- },
- {
- value: "ipa",
- label: "IPA",
- },
- {
- value: "om",
- label: "OM",
- },
- {
- value: "ad",
- label: "AD",
- },
- {
- value: "aa1",
- label: "AA1",
- },
- {
- value: "vi",
- label: "VI",
- },
- {
- value: "cai",
- label: "CAI",
- },
- {
- value: "bda",
- label: "BDA",
- },
- {
- value: "aa2",
- label: "AA2",
- },
- {
- value: "ei",
- label: "EI",
- },
- {
- value: "taed1",
- label: "TAED1",
- },
- {
- value: "poe",
- label: "POE",
- },
- {
- value: "piva",
- label: "PIVA",
- },
- {
- value: "pe",
- label: "PE",
- },
- {
- value: "taed2",
- label: "TAED2",
- },
- {
- value: "altres",
- label: "Altres",
- },
- ];
- const tipus = [
- {
- value: "apunts",
- label: "Apunts",
- },
- {
- value: "examens",
- label: "Exàmens",
- },
- {
- value: "exercicis",
- label: "Exercicis",
- },
- {
- value: "diapositives",
- label: "Diapositives",
- },
- {
- value: "altres",
- label: "Altres",
- },
- ];
- // ------------------------------
- return (
-
- );
+ (
+
+ Tipus
+
+
+
+ Tria el tipus de document.
+
+ )}
+ />
+ (
+
+
+
+
+
+ Penjar com a anònim
+
+ L'AED guarda sempre l'autor dels apunts.
+ L'opció d'anònim permet que no es mostrin als altres
+ usuaris.
+
+
+
+ )}
+ />
+
+ Submit
+
+
+
+ )
}
-export default ProfileForm;
+export default ProfileForm
-export const SmallProfileForm = ({ subjectAcronym }: { subjectAcronym: string }) => {
- const router = useRouter();
+export const SmallProfileForm = ({
+ subjectAcronym,
+}: {
+ subjectAcronym: string
+}) => {
+ const router = useRouter()
- const { mutate: createApuntsPost } = useMutation({
- mutationFn: async ({ pdf, title, assignatura, tipus }: ApuntsPostCreationRequest) => {
- const payload: ApuntsPostCreationRequest = {
- pdf,
- title,
- assignatura,
- tipus,
- };
- const { data } = await axios.post("/api/subject/post/create", payload);
- return data;
- },
- onError: () => {
- toast({
- title: "Alguna cosa no ha anat bé",
- description: "No s'ha pogut crear el post. Torna-ho a provar més tard.",
- variant: "destructive",
- });
- },
- onSuccess: (subjectAcronym) => {
- router.push(`/${subjectAcronym}`);
- router.refresh();
+ const { mutate: createApuntsPost } = useMutation({
+ mutationFn: async ({
+ pdf,
+ title,
+ assignatura,
+ tipus,
+ anonim,
+ }: ApuntsPostCreationRequest) => {
+ const payload: ApuntsPostCreationRequest = {
+ pdf,
+ title,
+ assignatura,
+ tipus,
+ anonim,
+ }
+ const { data } = await axios.post("/api/subject/post/create", payload)
+ return data
+ },
+ onError: () => {
+ toast({
+ title: "Alguna cosa no ha anat bé",
+ description: "No s'ha pogut crear el post. Torna-ho a provar més tard.",
+ variant: "destructive",
+ })
+ },
+ onSuccess: (subjectAcronym) => {
+ router.push(`/${subjectAcronym}`)
+ router.refresh()
- return toast({
- description: "El teu post s'ha creat correctament",
- });
- },
- });
- const form = useForm({ resolver: zodResolver(smallFormSchema) });
- async function onSubmit(data: ApuntsPostCreationRequest) {
- const [res] = await uploadFiles([data.pdf], "fileUploader");
- const payload: ApuntsPostCreationRequest = {
- pdf: res.fileUrl,
- title: data.title,
- assignatura: subjectAcronym,
- tipus: data.tipus,
- };
-
- createApuntsPost(payload);
- }
- // ------------------------------
- const tipus = [
- {
- value: "apunts",
- label: "Apunts",
- },
- {
- value: "examens",
- label: "Exàmens",
- },
- {
- value: "exercicis",
- label: "Exercicis",
- },
- {
- value: "diapositives",
- label: "Diapositives",
- },
- {
- value: "altres",
- label: "Altres",
- },
- ];
- // ------------------------------
- return (
-
- )}
- className="space-y-8">
- (
-
- Fitxers PDF
-
-
- {
- if (e.target.files) {
- field.onChange(e.target.files[0]);
- }
- }}
- />
-
-
- Penja els teus apunts en format PDF.
-
-
- )}
- />
- {/* TODO: Nomes admins
+ return toast({
+ description: "El teu post s'ha creat correctament",
+ })
+ },
+ })
+ const form = useForm({
+ resolver: zodResolver(smallFormSchema),
+ })
+ async function onSubmit(data: ApuntsPostCreationRequest) {
+ const [res] = await uploadFiles([data.pdf], "fileUploader")
+ const payload: ApuntsPostCreationRequest = {
+ pdf: res.fileUrl,
+ title: data.title,
+ assignatura: subjectAcronym,
+ tipus: data.tipus,
+ anonim: data.anonim,
+ }
+ createApuntsPost(payload)
+ }
+ // ------------------------------
+ const tipus = [
+ {
+ value: "apunts",
+ label: "Apunts",
+ },
+ {
+ value: "examens",
+ label: "Exàmens",
+ },
+ {
+ value: "exercicis",
+ label: "Exercicis",
+ },
+ {
+ value: "diapositives",
+ label: "Diapositives",
+ },
+ {
+ value: "altres",
+ label: "Altres",
+ },
+ ]
+ // ------------------------------
+ return (
+
+ )}
+ className="space-y-8"
+ >
+ (
+
+ Fitxers PDF
+
+
+ {
+ if (e.target.files) {
+ field.onChange(e.target.files[0])
+ }
+ }}
+ />
+
+
+
+ Penja els teus apunts en format PDF.
+
+
+
+ )}
+ />
+ {/* TODO: Nomes admins
)}
/> */}
- (
-
- Nom dels Apunts
-
-
-
- El nom dels teus apunts.
-
-
- )}
- />
+ (
+
+ Nom dels Apunts
+
+
+
+ El nom dels teus apunts.
+
+
+ )}
+ />
- (
-
- Tipus
-
-
-
- Tria el tipus de document.
-
- )}
- />
-
- Submit
-
-
-
- );
-};
+ (
+
+ Tipus
+
+
+
+ Tria el tipus de document.
+
+ )}
+ />
+ (
+
+
+
+
+
+ Penjar com a anònim
+
+ L'AED guarda sempre l'autor dels apunts.
+ L'opció d'anònim permet que no es mostrin als altres
+ usuaris.
+
+
+
+ )}
+ />
+
+ Submit
+
+
+
+ )
+}
diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx
index ab2da91..6af2742 100644
--- a/src/components/Icons.tsx
+++ b/src/components/Icons.tsx
@@ -1,22 +1,23 @@
-import { LucideProps, MessageSquare, User } from "lucide-react";
+import { LucideProps, MessageSquare, User } from "lucide-react"
export const Icons = {
- user: User,
- logo: (props: LucideProps) => (
-
-
+
-
- ),
- google: (props: LucideProps) => (
-
-
-
-
-
-
-
- ),
- commentReply: MessageSquare,
-};
+ />
+
+ ),
+ google: (props: LucideProps) => (
+
+
+
+
+
+
+
+ ),
+ commentReply: MessageSquare,
+}
diff --git a/src/components/MiniCreateAnswer.tsx b/src/components/MiniCreateAnswer.tsx
index 421c6d1..190e25b 100644
--- a/src/components/MiniCreateAnswer.tsx
+++ b/src/components/MiniCreateAnswer.tsx
@@ -1,15 +1,15 @@
-"use client";
+"use client"
-import { Session } from "next-auth";
-import { Button } from "@/components/ui/Button";
-import { FC } from "react";
-import UserAvatar from "./UserAvatar";
-import Editor from "@/components/Editor";
+import { Session } from "next-auth"
+import { Button } from "@/components/ui/Button"
+import { FC } from "react"
+import UserAvatar from "./UserAvatar"
+import Editor from "@/components/Editor"
interface MiniCreateAnswer {
- session: Session | null;
- subjectId: string;
- questionId: string;
+ session: Session | null
+ subjectId: string
+ questionId: string
}
const MiniCreateAnswer: FC = ({
session,
@@ -32,19 +32,27 @@ const MiniCreateAnswer: FC = ({
{/* form */}
-
+
Compartir
- );
-};
+ )
+}
-export default MiniCreateAnswer;
+export default MiniCreateAnswer
diff --git a/src/components/MiniCreateComment.tsx b/src/components/MiniCreateComment.tsx
index c044aa8..524a1a3 100644
--- a/src/components/MiniCreateComment.tsx
+++ b/src/components/MiniCreateComment.tsx
@@ -1,23 +1,20 @@
-"use client";
+"use client"
-import { Session } from "next-auth";
-import { Button } from "@/components/ui/Button";
-import { FC, useState } from "react";
-import UserAvatar from "./UserAvatar";
-import axios from "axios";
-import { useMutation } from "@tanstack/react-query";
-import { toast } from "@/hooks/use-toast";
+import { Session } from "next-auth"
+import { Button } from "@/components/ui/Button"
+import { FC, useState } from "react"
+import UserAvatar from "./UserAvatar"
+import axios from "axios"
+import { useMutation } from "@tanstack/react-query"
+import { toast } from "@/hooks/use-toast"
interface MiniCreateComment {
- session: Session | null;
- postId: string;
+ session: Session | null
+ postId: string
}
-const MiniCreateComment: FC = ({
- session,
- postId,
-}) => {
- const [content, setContent] = useState("");
+const MiniCreateComment: FC = ({ session, postId }) => {
+ const [content, setContent] = useState("")
// Define the mutation function using useMutation hook
const { mutate: createComment } = useMutation({
@@ -25,25 +22,25 @@ const MiniCreateComment: FC = ({
const { data } = await axios.post("/api/subject/comment/create", {
content: content,
postId,
- });
- return data;
+ })
+ return data
},
onSuccess: ({}) => {
// Handle success and show toast
toast({
description: `Comment created successfully`,
- });
+ })
// You can add any additional handling specific to your needs here
},
onError: ({}) => {
// Handle error if needed
},
- });
+ })
const handleSubmit = async () => {
// Call the mutate function to initiate the mutation
- createComment();
- };
+ createComment()
+ }
return (
@@ -83,7 +80,7 @@ const MiniCreateComment: FC = ({
- );
-};
+ )
+}
-export default MiniCreateComment;
+export default MiniCreateComment
diff --git a/src/components/MiniCreatePost.tsx b/src/components/MiniCreatePost.tsx
index feb1955..e96c06d 100644
--- a/src/components/MiniCreatePost.tsx
+++ b/src/components/MiniCreatePost.tsx
@@ -1,40 +1,40 @@
-"use client";
+"use client"
-import { Session } from "next-auth";
-import { usePathname, useRouter } from "next/navigation";
-import { FC } from "react";
-import UserAvatar from "./UserAvatar";
-import { Input } from "./ui/Input";
+import { Session } from "next-auth"
+import { usePathname, useRouter } from "next/navigation"
+import { FC } from "react"
+import UserAvatar from "./UserAvatar"
+import { Input } from "./ui/Input"
interface MiniCreatePostProps {
- session: Session | null;
+ session: Session | null
}
const MiniCreatePost: FC = ({ session }) => {
- const router = useRouter();
- const pathname = usePathname();
-
- return (
-
-
-
-
-
-
-
-
-
router.push(pathname + "/submit")}
- placeholder="Comparteix els teus apunts"
- />
-
- {/*
+
+
+
+
+
+
+
+
router.push(pathname + "/submit")}
+ placeholder="Comparteix els teus apunts"
+ />
+
+ {/*
router.push(parentPathname + "/submit")}
variant="ghost">
@@ -51,9 +51,9 @@ const MiniCreatePost: FC = ({ session }) => {
variant="ghost">
*/}
-
-
- );
-};
+
+
+ )
+}
-export default MiniCreatePost;
+export default MiniCreatePost
diff --git a/src/components/MiniCreateQuestion.tsx b/src/components/MiniCreateQuestion.tsx
index f53f7b3..16a82a9 100644
--- a/src/components/MiniCreateQuestion.tsx
+++ b/src/components/MiniCreateQuestion.tsx
@@ -1,14 +1,14 @@
-"use client";
+"use client"
-import { Session } from "next-auth";
-import { Button } from "@/components/ui/Button";
-import { FC } from "react";
-import UserAvatar from "./UserAvatar";
-import Editor from "@/components/Editor";
+import { Session } from "next-auth"
+import { Button } from "@/components/ui/Button"
+import { FC } from "react"
+import UserAvatar from "./UserAvatar"
+import Editor from "@/components/Editor"
interface MiniCreateQuestionProps {
- session: Session | null;
- subjectId: string;
+ session: Session | null
+ subjectId: string
}
const MiniCreateQuestion: FC = ({
session,
@@ -30,19 +30,23 @@ const MiniCreateQuestion: FC = ({
{/* form */}
-
+
Compartir
- );
-};
+ )
+}
-export default MiniCreateQuestion;
+export default MiniCreateQuestion
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 74d27d3..cda49b5 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -1,36 +1,36 @@
-import Link from "next/link";
-import { Icons } from "@/components/Icons";
-import { buttonVariants } from "./ui/Button";
-import { getAuthSession } from "@/lib/auth";
-import UserAccountNav from "./UserAccountNav";
+import Link from "next/link"
+import { Icons } from "@/components/Icons"
+import { buttonVariants } from "./ui/Button"
+import { getAuthSession } from "@/lib/auth"
+import UserAccountNav from "./UserAccountNav"
const Navbar = async () => {
- const session = await getAuthSession();
+ const session = await getAuthSession()
- return (
-
-
-
-
-
Apunts Dades
-
+ return (
+
+
+
+
+
+ Apunts Dades
+
+
- {/* search bar */}
+ {/* search bar */}
- {session && session.user ? (
-
- ) : (
-
-
- Sign In
-
-
- )}
-
-
- );
-};
+ {session && session.user ? (
+
+ ) : (
+
+
+ Sign In
+
+
+ )}
+
+
+ )
+}
-export default Navbar;
+export default Navbar
diff --git a/src/components/Post.tsx b/src/components/Post.tsx
index 9cbb74e..15fa30e 100644
--- a/src/components/Post.tsx
+++ b/src/components/Post.tsx
@@ -1,89 +1,104 @@
-"use client";
+"use client"
-import { formatTimeToNow } from "@/lib/utils";
-import { Post, User, PostVote } from "@prisma/client";
-import { MessageSquare } from "lucide-react";
-import Link from "next/link";
-import { buttonVariants } from "@/components/ui/Button";
-import { FC, useRef } from "react";
-import PostVoteClient from "./votes/PostVoteClient";
-import { Badge } from "@/components/ui/Badge";
+import { formatTimeToNow } from "@/lib/utils"
+import { Post, User, PostVote } from "@prisma/client"
+import { MessageSquare } from "lucide-react"
+import Link from "next/link"
+import { buttonVariants } from "@/components/ui/Button"
+import { FC, useRef } from "react"
+import PostVoteClient from "./votes/PostVoteClient"
+import { Badge } from "@/components/ui/Badge"
-type PartialVote = Pick;
+type PartialVote = Pick
interface PostProps {
- post: Post & {
- author: User;
- votes: PostVote[];
- };
- votesAmt: number;
- subjectAcronym: string;
- currentVote?: PartialVote;
- commentAmt: number;
+ post: Post & {
+ author: User
+ votes: PostVote[]
+ }
+ votesAmt: number
+ subjectAcronym: string
+ currentVote?: PartialVote
+ commentAmt: number
}
-const Post: FC = ({ post, votesAmt: _votesAmt, currentVote: _currentVote, subjectAcronym, commentAmt }) => {
- const pRef = useRef(null);
+const Post: FC = ({
+ post,
+ votesAmt: _votesAmt,
+ currentVote: _currentVote,
+ subjectAcronym,
+ commentAmt,
+}) => {
+ const pRef = useRef(null)
- return (
-
-
-
+ return (
+
+
+
-
-
- {subjectAcronym ? (
- <>
-
- {subjectAcronym}
-
-
•
- >
- ) : null}
-
Compartit per {post.author.name} {formatTimeToNow(new Date(post.createdAt))}
-
-
- {post.title}
-
-
- {post.tipus}
- {post.year}
-
+
+
+ {subjectAcronym ? (
+ <>
+
+ {subjectAcronym}
+
+
•
+ >
+ ) : null}
+
+ Compartit per {post.isAnonymous ? "Anònim" : post.author.name}
+ {" "}
+ {formatTimeToNow(new Date(post.createdAt))}
+
+
+
+ {post.title}
+
+
+
+ {post.tipus}
+ {post.year}
+
-
- {post.content && post.content.endsWith(".pdf") ? (
-
- Visualitza els Apunts
-
- ) : null}
- {pRef.current?.clientHeight === 160 ? (
- // blur bottom if content is too long
-
- ) : null}
-
-
-
+
+ {post.content && post.content.endsWith(".pdf") ? (
+
+ Visualitza els Apunts
+
+ ) : null}
+ {pRef.current?.clientHeight === 160 ? (
+ // blur bottom if content is too long
+
+ ) : null}
+
+
+
-
-
- {commentAmt} comments
-
-
-
- );
-};
-export default Post;
+
+
+ {commentAmt} comments
+
+
+
+ )
+}
+export default Post
diff --git a/src/components/PostFeed.tsx b/src/components/PostFeed.tsx
index f34de05..3dcf25c 100644
--- a/src/components/PostFeed.tsx
+++ b/src/components/PostFeed.tsx
@@ -1,102 +1,102 @@
-"use client";
+"use client"
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { ExtendedPost } from "@/types/db";
-import { useIntersection } from "@mantine/hooks";
-import { useInfiniteQuery } from "@tanstack/react-query";
-import axios from "axios";
-import { Loader2 } from "lucide-react";
-import { FC, useEffect, useRef } from "react";
-import Post from "./Post";
-import { useSession } from "next-auth/react";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { ExtendedPost } from "@/types/db"
+import { useIntersection } from "@mantine/hooks"
+import { useInfiniteQuery } from "@tanstack/react-query"
+import axios from "axios"
+import { Loader2 } from "lucide-react"
+import { FC, useEffect, useRef } from "react"
+import Post from "./Post"
+import { useSession } from "next-auth/react"
interface PostFeedProps {
- initialPosts: ExtendedPost[];
- subjectAcronym?: string;
+ initialPosts: ExtendedPost[]
+ subjectAcronym?: string
}
const PostFeed: FC = ({ initialPosts, subjectAcronym }) => {
- const lastPostRef = useRef(null);
- const { ref, entry } = useIntersection({
- root: lastPostRef.current,
- threshold: 1,
- });
- const { data: session } = useSession();
+ const lastPostRef = useRef(null)
+ const { ref, entry } = useIntersection({
+ root: lastPostRef.current,
+ threshold: 1,
+ })
+ const { data: session } = useSession()
- const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
- ["infinite-query"],
- async ({ pageParam = 1 }) => {
- const query =
- `/api/posts?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
- (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "");
+ const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
+ ["infinite-query"],
+ async ({ pageParam = 1 }) => {
+ const query =
+ `/api/posts?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
+ (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "")
- const { data } = await axios.get(query);
- return data as ExtendedPost[];
- },
+ const { data } = await axios.get(query)
+ return data as ExtendedPost[]
+ },
- {
- getNextPageParam: (_, pages) => {
- return pages.length + 1;
- },
- initialData: { pages: [initialPosts], pageParams: [1] },
- }
- );
+ {
+ getNextPageParam: (_, pages) => {
+ return pages.length + 1
+ },
+ initialData: { pages: [initialPosts], pageParams: [1] },
+ },
+ )
- useEffect(() => {
- if (entry?.isIntersecting) {
- fetchNextPage(); // Load more posts when the last post comes into view
- }
- }, [entry, fetchNextPage]);
+ useEffect(() => {
+ if (entry?.isIntersecting) {
+ fetchNextPage() // Load more posts when the last post comes into view
+ }
+ }, [entry, fetchNextPage])
- const posts = data?.pages.flatMap((page) => page) ?? initialPosts;
+ const posts = data?.pages.flatMap((page) => page) ?? initialPosts
- return (
-
- {posts.map((post, index) => {
- const votesAmt = post.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
+ return (
+
+ {posts.map((post, index) => {
+ const votesAmt = post.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
- const currentVote = post.votes.find((vote) => vote.userId === session?.user.id);
+ const currentVote = post.votes.find(
+ (vote) => vote.userId === session?.user.id,
+ )
- if (index === posts.length - 1) {
- // Add a ref to the last post in the list
- return (
-
-
-
- );
- } else {
- return (
-
- );
- }
- })}
+ if (index === posts.length - 1) {
+ // Add a ref to the last post in the list
+ return (
+
+
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
- {isFetchingNextPage && (
-
-
-
- )}
-
- );
-};
+ {isFetchingNextPage && (
+
+
+
+ )}
+
+ )
+}
-export default PostFeed;
+export default PostFeed
diff --git a/src/components/PostView.tsx b/src/components/PostView.tsx
index 4ab1b79..0755cad 100644
--- a/src/components/PostView.tsx
+++ b/src/components/PostView.tsx
@@ -1,22 +1,22 @@
-"use client";
-import { FC } from "react";
-import { ExtendedPost, ExtendedComment } from "@/types/db";
-import { useSession } from "next-auth/react";
-import MiniCreateComment from "@/components/MiniCreateComment";
-import CommentFeed from "@/components/CommentFeed";
-import Post from "@/components/Post";
+"use client"
+import { FC } from "react"
+import { ExtendedPost, ExtendedComment } from "@/types/db"
+import { useSession } from "next-auth/react"
+import MiniCreateComment from "@/components/MiniCreateComment"
+import CommentFeed from "@/components/CommentFeed"
+import Post from "@/components/Post"
interface PostViewProps {
- post: ExtendedPost;
- comments: ExtendedComment[];
+ post: ExtendedPost
+ comments: ExtendedComment[]
}
export const PostView: FC = ({ post, comments }) => {
- const { data: session } = useSession();
- const votesAmt = post.votes.length;
+ const { data: session } = useSession()
+ const votesAmt = post.votes.length
const currentVote = post.votes.find(
- (vote) => vote.userId === session?.user?.id
- );
+ (vote) => vote.userId === session?.user?.id,
+ )
return (
@@ -40,7 +40,7 @@ export const PostView: FC
= ({ post, comments }) => {
/>
- );
-};
+ )
+}
-export default PostView;
+export default PostView
diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
index 92b1b35..78c574d 100644
--- a/src/components/Providers.tsx
+++ b/src/components/Providers.tsx
@@ -1,16 +1,16 @@
-"use client";
+"use client"
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { SessionProvider } from "next-auth/react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
+import { SessionProvider } from "next-auth/react"
const Providers = ({ children }: { children: React.ReactNode }) => {
- const queryClient = new QueryClient();
+ const queryClient = new QueryClient()
- return (
-
- {children}
-
- );
-};
+ return (
+
+ {children}
+
+ )
+}
-export default Providers;
+export default Providers
diff --git a/src/components/QuestionComponent.tsx b/src/components/QuestionComponent.tsx
index 6e290b9..a57d469 100644
--- a/src/components/QuestionComponent.tsx
+++ b/src/components/QuestionComponent.tsx
@@ -1,84 +1,90 @@
-"use client";
+"use client"
-import { formatTimeToNow } from "@/lib/utils";
-import { Question, User, QuestionVote } from "@prisma/client";
-import { MessageSquare } from "lucide-react";
-import Link from "next/link";
-import { FC, useRef } from "react";
-import EditorOutput from "./EditorOutput";
-import QuestionVoteClient from "./votes/QuestionVoteClient";
+import { formatTimeToNow } from "@/lib/utils"
+import { Question, User, QuestionVote } from "@prisma/client"
+import { MessageSquare } from "lucide-react"
+import Link from "next/link"
+import { FC, useRef } from "react"
+import EditorOutput from "./EditorOutput"
+import QuestionVoteClient from "./votes/QuestionVoteClient"
-type PartialVote = Pick;
+type PartialVote = Pick
interface QuestionProps {
- question: Question & {
- author: User;
- votes: QuestionVote[];
- };
- votesAmt: number;
- subjectName: string;
- currentVote?: PartialVote;
- answerAmt: number;
- subjectAcronym: string;
+ question: Question & {
+ author: User
+ votes: QuestionVote[]
+ }
+ votesAmt: number
+ subjectName: string
+ currentVote?: PartialVote
+ answerAmt: number
+ subjectAcronym: string
}
const QuestionComponent: FC = ({
- question,
- votesAmt: _votesAmt,
- currentVote: _currentVote,
- subjectName,
- answerAmt,
- subjectAcronym,
+ question,
+ votesAmt: _votesAmt,
+ currentVote: _currentVote,
+ subjectName,
+ answerAmt,
+ subjectAcronym,
}) => {
- const pRef = useRef(null);
+ const pRef = useRef(null)
- return (
-
-
-
+ return (
+
+
+
-
-
- {subjectName ? (
- <>
-
- {subjectAcronym}
-
-
•
- >
- ) : null}
-
Compartit per {question.author.name} {formatTimeToNow(new Date(question.createdAt))}
-
-
- {question.title}
-
+
+
+ {subjectName ? (
+ <>
+
+ {subjectAcronym}
+
+
•
+ >
+ ) : null}
+
Compartit per {question.author.name} {" "}
+ {formatTimeToNow(new Date(question.createdAt))}
+
+
+
+ {question.title}
+
+
-
-
- {pRef.current?.clientHeight === 160 ? (
- // blur bottom if content is too long
-
- ) : null}
-
-
-
+
+
+ {pRef.current?.clientHeight === 160 ? (
+ // blur bottom if content is too long
+
+ ) : null}
+
+
+
-
-
- {answerAmt} answers
-
-
-
- );
-};
-export default QuestionComponent;
+
+
+ {answerAmt} answers
+
+
+
+ )
+}
+export default QuestionComponent
diff --git a/src/components/QuestionFeed.tsx b/src/components/QuestionFeed.tsx
index fbda66c..c32ead4 100644
--- a/src/components/QuestionFeed.tsx
+++ b/src/components/QuestionFeed.tsx
@@ -1,104 +1,107 @@
-"use client";
+"use client"
-import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config";
-import { ExtendedQuestion } from "@/types/db";
-import { useIntersection } from "@mantine/hooks";
-import { useInfiniteQuery } from "@tanstack/react-query";
-import axios from "axios";
-import { Loader2 } from "lucide-react";
-import { FC, useEffect, useRef } from "react";
-import QuestionComponent from "./QuestionComponent";
-import { useSession } from "next-auth/react";
+import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config"
+import { ExtendedQuestion } from "@/types/db"
+import { useIntersection } from "@mantine/hooks"
+import { useInfiniteQuery } from "@tanstack/react-query"
+import axios from "axios"
+import { Loader2 } from "lucide-react"
+import { FC, useEffect, useRef } from "react"
+import QuestionComponent from "./QuestionComponent"
+import { useSession } from "next-auth/react"
interface QuestionFeedProps {
- initialQuestions: ExtendedQuestion[];
- subjectAcronym?: string;
+ initialQuestions: ExtendedQuestion[]
+ subjectAcronym?: string
}
-const QuestionFeed: FC = ({ initialQuestions, subjectAcronym }) => {
- const lastQuestionRef = useRef(null);
- const { ref, entry } = useIntersection({
- root: lastQuestionRef.current,
- threshold: 1,
- });
- const { data: session } = useSession();
+const QuestionFeed: FC = ({
+ initialQuestions,
+ subjectAcronym,
+}) => {
+ const lastQuestionRef = useRef(null)
+ const { ref, entry } = useIntersection({
+ root: lastQuestionRef.current,
+ threshold: 1,
+ })
+ const { data: session } = useSession()
- const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
- ["infinite-query"],
- async ({ pageParam = 1 }) => {
- const query =
- `/api/q?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
- (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "");
+ const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery(
+ ["infinite-query"],
+ async ({ pageParam = 1 }) => {
+ const query =
+ `/api/q?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` +
+ (!!subjectAcronym ? `&subjectAcronym=${subjectAcronym}` : "")
- const { data } = await axios.get(query);
- return data as ExtendedQuestion[];
- },
+ const { data } = await axios.get(query)
+ return data as ExtendedQuestion[]
+ },
- {
- getNextPageParam: (_, pages) => {
- return pages.length + 1;
- },
- initialData: { pages: [initialQuestions], pageParams: [1] },
- }
- );
+ {
+ getNextPageParam: (_, pages) => {
+ return pages.length + 1
+ },
+ initialData: { pages: [initialQuestions], pageParams: [1] },
+ },
+ )
- useEffect(() => {
- if (entry?.isIntersecting) {
- fetchNextPage(); // Load more questions when the last question comes into view
- }
- }, [entry, fetchNextPage]);
+ useEffect(() => {
+ if (entry?.isIntersecting) {
+ fetchNextPage() // Load more questions when the last question comes into view
+ }
+ }, [entry, fetchNextPage])
- const questions = data?.pages.flatMap((page) => page) ?? initialQuestions;
+ const questions = data?.pages.flatMap((page) => page) ?? initialQuestions
- return (
-
- {questions.map((question, index) => {
- const votesAmt = question.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
+ return (
+
+ {questions.map((question, index) => {
+ const votesAmt = question.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
- const currentVote = question.votes.find((vote) => vote.userId === session?.user.id);
+ const currentVote = question.votes.find(
+ (vote) => vote.userId === session?.user.id,
+ )
- if (index === questions.length - 1) {
- // Add a ref to the last question in the list
- return (
-
-
-
- );
- } else {
- return (
-
- );
- }
- })}
+ if (index === questions.length - 1) {
+ // Add a ref to the last question in the list
+ return (
+
+
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
- {isFetchingNextPage && (
-
-
-
- )}
-
- );
-};
+ {isFetchingNextPage && (
+
+
+
+ )}
+
+ )
+}
-export default QuestionFeed;
+export default QuestionFeed
diff --git a/src/components/QuestionView.tsx b/src/components/QuestionView.tsx
index e5dac4e..19137f0 100644
--- a/src/components/QuestionView.tsx
+++ b/src/components/QuestionView.tsx
@@ -1,25 +1,25 @@
-"use client";
-import { FC } from "react";
-import QuestionComponent from "@/components/QuestionComponent";
-import { ExtendedQuestion, ExtendedAnswer } from "@/types/db";
-import { useSession } from "next-auth/react";
-import MiniCreateAnswer from "@/components/MiniCreateAnswer";
-import AnswerFeed from "@/components/AnswerFeed";
+"use client"
+import { FC } from "react"
+import QuestionComponent from "@/components/QuestionComponent"
+import { ExtendedQuestion, ExtendedAnswer } from "@/types/db"
+import { useSession } from "next-auth/react"
+import MiniCreateAnswer from "@/components/MiniCreateAnswer"
+import AnswerFeed from "@/components/AnswerFeed"
interface AnswersViewProps {
- question: ExtendedQuestion;
- answers: ExtendedAnswer[];
+ question: ExtendedQuestion
+ answers: ExtendedAnswer[]
}
export const AnswersView: FC = ({ question, answers }) => {
- const { data: session } = useSession();
- const votesAmt = question.votes.length;
- const answerAmt = question.answers.length;
- const subjectName = question.subject.name;
- const subjectAcronym = question.subject.acronym;
+ const { data: session } = useSession()
+ const votesAmt = question.votes.length
+ const answerAmt = question.answers.length
+ const subjectName = question.subject.name
+ const subjectAcronym = question.subject.acronym
const currentVote = question.votes.find(
- (vote) => vote.userId === session?.user?.id
- );
+ (vote) => vote.userId === session?.user?.id,
+ )
return (
@@ -33,13 +33,22 @@ export const AnswersView: FC
= ({ question, answers }) => {
/>
-
+
- );
-};
+ )
+}
-export default AnswersView;
+export default AnswersView
diff --git a/src/components/SignIn.tsx b/src/components/SignIn.tsx
index d131f9b..24883fe 100644
--- a/src/components/SignIn.tsx
+++ b/src/components/SignIn.tsx
@@ -1,35 +1,35 @@
-import { Icons } from "@/components/Icons";
-import Link from "next/link";
-import UserAuthForm from "@/components/UserAuthForm";
+import { Icons } from "@/components/Icons"
+import Link from "next/link"
+import UserAuthForm from "@/components/UserAuthForm"
const SignIn = () => {
- return (
-
-
-
-
Ben tornat!
-
- Continuant, entraràs al teu un compte d'Apunts Dades sota les nostres Condicions d'Ús i Polítiques de
- Privacitat.
-
+ return (
+
+
+
+
Ben tornat!
+
+ Continuant, entraràs al teu un compte d'Apunts Dades sota les
+ nostres Condicions d'Ús i Polítiques de Privacitat.
+
- {/* sign in form */}
-
+ {/* sign in form */}
+
-
- No tens un compte?{" "}
-
- Registra't
-
-
-
-
- );
-};
+
+ No tens un compte?{" "}
+
+ Registra't
+
+
+
+
+ )
+}
-export default SignIn;
+export default SignIn
diff --git a/src/components/SubscribeLeaveToggle.tsx b/src/components/SubscribeLeaveToggle.tsx
index b272e9f..7c5a76d 100644
--- a/src/components/SubscribeLeaveToggle.tsx
+++ b/src/components/SubscribeLeaveToggle.tsx
@@ -1,112 +1,119 @@
-"use client";
+"use client"
-import { FC, startTransition } from "react";
-import { Button } from "./ui/Button";
-import { SubscribeToSubjectPayload } from "@/lib/validators/subject";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { toast } from "@/hooks/use-toast";
-import { useRouter } from "next/navigation";
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
+import { FC, startTransition } from "react"
+import { Button } from "./ui/Button"
+import { SubscribeToSubjectPayload } from "@/lib/validators/subject"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { toast } from "@/hooks/use-toast"
+import { useRouter } from "next/navigation"
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
interface SubscribeLeaveToggleProps {
- subjectId: string;
- subjectName: string;
- isSubscribed: boolean;
+ subjectId: string
+ subjectName: string
+ isSubscribed: boolean
}
-const SubscribeLeaveToggle: FC = ({ subjectId, subjectName, isSubscribed }) => {
- const { loginToast } = useCustomToasts();
- const router = useRouter();
+const SubscribeLeaveToggle: FC = ({
+ subjectId,
+ subjectName,
+ isSubscribed,
+}) => {
+ const { loginToast } = useCustomToasts()
+ const router = useRouter()
- const { mutate: subscribe, isLoading: isSubLoading } = useMutation({
- mutationFn: async () => {
- const payload: SubscribeToSubjectPayload = {
- subjectId,
- };
- const { data } = await axios.post("/api/subject/subscribe", payload);
- return data as string;
- },
- onError: (err) => {
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ const { mutate: subscribe, isLoading: isSubLoading } = useMutation({
+ mutationFn: async () => {
+ const payload: SubscribeToSubjectPayload = {
+ subjectId,
+ }
+ const { data } = await axios.post("/api/subject/subscribe", payload)
+ return data as string
+ },
+ onError: (err) => {
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "S'ha produït un error desconegut.",
- description: "No s'ha pogut subscriure a l'assignatura. Siusplau, torna a intentar-ho més tard.",
- variant: "destructive",
- });
- },
- onSuccess: ({}) => {
- startTransition(() => {
- router.refresh();
- });
+ return toast({
+ title: "S'ha produït un error desconegut.",
+ description:
+ "No s'ha pogut subscriure a l'assignatura. Siusplau, torna a intentar-ho més tard.",
+ variant: "destructive",
+ })
+ },
+ onSuccess: ({}) => {
+ startTransition(() => {
+ router.refresh()
+ })
- const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i;
- const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de ";
+ const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i
+ const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de "
- return toast({
- title: `T'has subscrit als apunts ${subjectArticle}${subjectName}!`,
- description: "",
- });
- },
- });
+ return toast({
+ title: `T'has subscrit als apunts ${subjectArticle}${subjectName}!`,
+ description: "",
+ })
+ },
+ })
- const { mutate: unsubscribe, isLoading: isUnsubLoading } = useMutation({
- mutationFn: async () => {
- const payload: SubscribeToSubjectPayload = {
- subjectId,
- };
- const { data } = await axios.post("/api/subject/unsubscribe", payload);
- return data as string;
- },
- onError: (err) => {
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ const { mutate: unsubscribe, isLoading: isUnsubLoading } = useMutation({
+ mutationFn: async () => {
+ const payload: SubscribeToSubjectPayload = {
+ subjectId,
+ }
+ const { data } = await axios.post("/api/subject/unsubscribe", payload)
+ return data as string
+ },
+ onError: (err) => {
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "S'ha produït un error desconegut.",
- description:
- "No s'ha pogut donar de baixa la subscripció a l'assignatura. Siusplau, torna a intentar-ho més tard.",
- variant: "destructive",
- });
- },
- onSuccess: ({}) => {
- startTransition(() => {
- router.refresh();
- });
+ return toast({
+ title: "S'ha produït un error desconegut.",
+ description:
+ "No s'ha pogut donar de baixa la subscripció a l'assignatura. Siusplau, torna a intentar-ho més tard.",
+ variant: "destructive",
+ })
+ },
+ onSuccess: ({}) => {
+ startTransition(() => {
+ router.refresh()
+ })
- const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i;
- const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de ";
+ const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i
+ const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de "
- return toast({
- title: `Has donat de baixa la teva subscripció als apunts ${subjectArticle}${subjectName}!`,
- description: "",
- });
- },
- });
+ return toast({
+ title: `Has donat de baixa la teva subscripció als apunts ${subjectArticle}${subjectName}!`,
+ description: "",
+ })
+ },
+ })
- return isSubscribed ? (
- unsubscribe()}
- className="w-full mt-1 mb-4">
- Deixar de seguir
-
- ) : (
- subscribe()}
- className="w-full mt-1 mb-4">
- Seguir
-
- );
-};
+ return isSubscribed ? (
+ unsubscribe()}
+ className="w-full mt-1 mb-4"
+ >
+ Deixar de seguir
+
+ ) : (
+ subscribe()}
+ className="w-full mt-1 mb-4"
+ >
+ Seguir
+
+ )
+}
-export default SubscribeLeaveToggle;
+export default SubscribeLeaveToggle
diff --git a/src/components/UserAccountNav.tsx b/src/components/UserAccountNav.tsx
index 42e327c..13ac2c3 100644
--- a/src/components/UserAccountNav.tsx
+++ b/src/components/UserAccountNav.tsx
@@ -1,67 +1,72 @@
-"use client";
+"use client"
-import { User } from "next-auth";
-import { FC } from "react";
+import { User } from "next-auth"
+import { FC } from "react"
import {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
-} from "@/components/ui/DropdownMenu";
-import UserAvatar from "@/components/UserAvatar";
-import Link from "next/link";
-import { signOut } from "next-auth/react";
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+} from "@/components/ui/DropdownMenu"
+import UserAvatar from "@/components/UserAvatar"
+import Link from "next/link"
+import { signOut } from "next-auth/react"
interface UserAccountNavProps {
- user: Pick;
+ user: Pick
}
const UserAccountNav: FC = ({ user }) => {
- return (
-
-
-
-
-
-
-
- {user.name &&
{user.name}
}
- {user.email &&
{user.email}
}
-
-
+ return (
+
+
+
+
+
+
+
+ {user.name &&
{user.name}
}
+ {user.email && (
+
+ {user.email}
+
+ )}
+
+
-
+
-
- Inici
-
+
+ Inici
+
-
- Privacitat i Termes de Servei
-
+
+ Privacitat i Termes de Servei
+
-
+
- {
- event.preventDefault();
- signOut({ callbackUrl: `${window.location.origin}/sign-in` });
- }}
- className="cursor-pointer">
- Tancar Sessió
-
-
-
- );
-};
+ {
+ event.preventDefault()
+ signOut({
+ callbackUrl: `${window.location.origin}/sign-in`,
+ })
+ }}
+ className="cursor-pointer"
+ >
+ Tancar Sessió
+
+
+
+ )
+}
-export default UserAccountNav;
+export default UserAccountNav
diff --git a/src/components/UserAuthForm.tsx b/src/components/UserAuthForm.tsx
index 7121697..c5486e9 100644
--- a/src/components/UserAuthForm.tsx
+++ b/src/components/UserAuthForm.tsx
@@ -1,46 +1,48 @@
-"use client";
+"use client"
-import { Button } from "./ui/Button";
-import { FC, useState } from "react";
-import { signIn } from "next-auth/react";
-import { cn } from "@/lib/utils";
-import { Icons } from "./Icons";
-import { useToast } from "@/hooks/use-toast";
+import { Button } from "./ui/Button"
+import { FC, useState } from "react"
+import { signIn } from "next-auth/react"
+import { cn } from "@/lib/utils"
+import { Icons } from "./Icons"
+import { useToast } from "@/hooks/use-toast"
interface UserAuthFormProps extends React.HTMLAttributes {}
const UserAuthForm: FC = ({ className }) => {
- const [isLoading, setIsLoading] = useState(false);
- const { toast } = useToast();
+ const [isLoading, setIsLoading] = useState(false)
+ const { toast } = useToast()
- const loginWithGoogle = async () => {
- setIsLoading(true);
+ const loginWithGoogle = async () => {
+ setIsLoading(true)
- try {
- await signIn("google", { callbackUrl: "/" });
- } catch (error) {
- toast({
- title: "Hi ha hagut un problema.",
- description: "Hi ha hagut un error al iniciar sessió amb Google. Si us plau, torna a intentar-ho.",
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
+ try {
+ await signIn("google", { callbackUrl: "/" })
+ } catch (error) {
+ toast({
+ title: "Hi ha hagut un problema.",
+ description:
+ "Hi ha hagut un error al iniciar sessió amb Google. Si us plau, torna a intentar-ho.",
+ variant: "destructive",
+ })
+ } finally {
+ setIsLoading(false)
+ }
+ }
- return (
-
-
- {isLoading ? null : }
- Google
-
-
- );
-};
+ return (
+
+
+ {isLoading ? null : }
+ Google
+
+
+ )
+}
-export default UserAuthForm;
+export default UserAuthForm
diff --git a/src/components/UserAvatar.tsx b/src/components/UserAvatar.tsx
index 5c9c332..e1f93ba 100644
--- a/src/components/UserAvatar.tsx
+++ b/src/components/UserAvatar.tsx
@@ -1,35 +1,35 @@
-import { User } from "next-auth";
-import { FC } from "react";
-import { Avatar, AvatarFallback } from "./ui/Avatar";
-import Image from "next/image";
-import { Icons } from "./Icons";
-import { AvatarProps } from "@radix-ui/react-avatar";
+import { User } from "next-auth"
+import { FC } from "react"
+import { Avatar, AvatarFallback } from "./ui/Avatar"
+import Image from "next/image"
+import { Icons } from "./Icons"
+import { AvatarProps } from "@radix-ui/react-avatar"
interface UserAvatarProps extends AvatarProps {
- user: Pick;
+ user: Pick
}
const UserAvatar: FC = ({ user, ...props }) => {
- return (
-
- {user.image ? (
-
-
-
- ) : (
-
- {user?.name}
-
-
- )}
-
- );
-};
+ return (
+
+ {user.image ? (
+
+
+
+ ) : (
+
+ {user?.name}
+
+
+ )}
+
+ )
+}
-export default UserAvatar;
+export default UserAvatar
diff --git a/src/components/renderers/CustomCodeRenderer.tsx b/src/components/renderers/CustomCodeRenderer.tsx
index 4a6349f..67404d8 100644
--- a/src/components/renderers/CustomCodeRenderer.tsx
+++ b/src/components/renderers/CustomCodeRenderer.tsx
@@ -1,13 +1,13 @@
-"use client";
+"use client"
function CustomCodeRenderer({ data }: any) {
- data;
+ data
- return (
-
- {data.code}
-
- );
+ return (
+
+ {data.code}
+
+ )
}
-export default CustomCodeRenderer;
+export default CustomCodeRenderer
diff --git a/src/components/renderers/CustomImageRenderer.tsx b/src/components/renderers/CustomImageRenderer.tsx
index be6d13f..84d4a2b 100644
--- a/src/components/renderers/CustomImageRenderer.tsx
+++ b/src/components/renderers/CustomImageRenderer.tsx
@@ -1,20 +1,15 @@
-"use client";
+"use client"
-import Image from "next/image";
+import Image from "next/image"
function CustomImageRenderer({ data }: any) {
- const src = data.file.url;
+ const src = data.file.url
- return (
-
-
-
- );
+ return (
+
+
+
+ )
}
-export default CustomImageRenderer;
+export default CustomImageRenderer
diff --git a/src/components/ui/Avatar.tsx b/src/components/ui/Avatar.tsx
index 51e507b..5955cbc 100644
--- a/src/components/ui/Avatar.tsx
+++ b/src/components/ui/Avatar.tsx
@@ -13,7 +13,7 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
/>
@@ -40,7 +40,7 @@ const AvatarFallback = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx
index f000e3e..265875e 100644
--- a/src/components/ui/Badge.tsx
+++ b/src/components/ui/Badge.tsx
@@ -20,7 +20,7 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
)
export interface BadgeProps
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
index c86da0d..d97895f 100644
--- a/src/components/ui/Button.tsx
+++ b/src/components/ui/Button.tsx
@@ -1,36 +1,34 @@
-import { cn } from '@/lib/utils'
-import { cva, VariantProps } from 'class-variance-authority'
-import { Loader2 } from 'lucide-react'
-import * as React from 'react'
+import { cn } from "@/lib/utils"
+import { cva, VariantProps } from "class-variance-authority"
+import { Loader2 } from "lucide-react"
+import * as React from "react"
const buttonVariants = cva(
- 'active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900',
+ "active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900",
{
variants: {
variant: {
- default:
- 'bg-zinc-900 text-zinc-100 hover:bg-zinc-800',
- destructive: 'text-white hover:bg-red-600 dark:hover:bg-red-600',
+ default: "bg-zinc-900 text-zinc-100 hover:bg-zinc-800",
+ destructive: "text-white hover:bg-red-600 dark:hover:bg-red-600",
outline:
- 'bg-zinc-100 text-zinc-900 hover:bg-zinc-200 outline outline-1 outline-zinc-300',
- subtle:
- 'hover:bg-zinc-200 bg-zinc-100 text-zinc-900',
+ "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 outline outline-1 outline-zinc-300",
+ subtle: "hover:bg-zinc-200 bg-zinc-100 text-zinc-900",
ghost:
- 'bg-transparent hover:bg-zinc-100 text-zinc-800 data-[state=open]:bg-transparent data-[state=open]:bg-transparent',
- link: 'bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent',
+ "bg-transparent hover:bg-zinc-100 text-zinc-800 data-[state=open]:bg-transparent data-[state=open]:bg-transparent",
+ link: "bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
},
size: {
- default: 'h-10 py-2 px-4',
- sm: 'h-9 px-2 rounded-md',
- xs: 'h-8 px-1.5 rounded-sm',
- lg: 'h-11 px-8 rounded-md',
+ default: "h-10 py-2 px-4",
+ sm: "h-9 px-2 rounded-md",
+ xs: "h-8 px-1.5 rounded-sm",
+ lg: "h-11 px-8 rounded-md",
},
},
defaultVariants: {
- variant: 'default',
- size: 'default',
+ variant: "default",
+ size: "default",
},
- }
+ },
)
export interface ButtonProps
@@ -46,13 +44,14 @@ const Button = React.forwardRef(
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={isLoading}
- {...props}>
- {isLoading ? : null}
+ {...props}
+ >
+ {isLoading ? : null}
{children}
)
- }
+ },
)
-Button.displayName = 'Button'
+Button.displayName = "Button"
-export { Button, buttonVariants }
\ No newline at end of file
+export { Button, buttonVariants }
diff --git a/src/components/ui/Command.tsx b/src/components/ui/Command.tsx
index 32e0007..28c6a45 100644
--- a/src/components/ui/Command.tsx
+++ b/src/components/ui/Command.tsx
@@ -16,7 +16,7 @@ const Command = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
- className
+ className,
)}
{...props}
/>
@@ -47,7 +47,7 @@ const CommandInput = React.forwardRef<
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
{...props}
/>
@@ -90,7 +90,7 @@ const CommandGroup = React.forwardRef<
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
- className
+ className,
)}
{...props}
/>
@@ -118,7 +118,7 @@ const CommandItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
{...props}
/>
@@ -134,7 +134,7 @@ const CommandShortcut = ({
diff --git a/src/components/ui/Dialog.tsx b/src/components/ui/Dialog.tsx
index 01ff19c..0b5735c 100644
--- a/src/components/ui/Dialog.tsx
+++ b/src/components/ui/Dialog.tsx
@@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
- className
+ className,
)}
{...props}
/>
@@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
- className
+ className,
)}
{...props}
>
@@ -60,7 +60,7 @@ const DialogHeader = ({
@@ -74,7 +74,7 @@ const DialogFooter = ({
@@ -89,7 +89,7 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx
index f69a0d6..576b13a 100644
--- a/src/components/ui/DropdownMenu.tsx
+++ b/src/components/ui/DropdownMenu.tsx
@@ -29,7 +29,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
- className
+ className,
)}
{...props}
>
@@ -48,7 +48,7 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
@@ -66,7 +66,7 @@ const DropdownMenuContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
@@ -85,7 +85,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -100,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
checked={checked}
{...props}
@@ -124,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
+ className,
)}
{...props}
>
@@ -149,7 +149,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/Form.tsx b/src/components/ui/Form.tsx
index d540b14..037b24d 100644
--- a/src/components/ui/Form.tsx
+++ b/src/components/ui/Form.tsx
@@ -17,18 +17,18 @@ const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
> = {
name: TName
}
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
+ {} as FormFieldContextValue,
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
>({
...props
}: ControllerProps) => {
@@ -67,7 +67,7 @@ type FormItemContextValue = {
}
const FormItemContext = React.createContext(
- {} as FormItemContextValue
+ {} as FormItemContextValue,
)
const FormItem = React.forwardRef<
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx
index 677d05f..9900814 100644
--- a/src/components/ui/Input.tsx
+++ b/src/components/ui/Input.tsx
@@ -12,13 +12,13 @@ const Input = React.forwardRef(
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
ref={ref}
{...props}
/>
)
- }
+ },
)
Input.displayName = "Input"
diff --git a/src/components/ui/Label.tsx b/src/components/ui/Label.tsx
index 5341821..afde563 100644
--- a/src/components/ui/Label.tsx
+++ b/src/components/ui/Label.tsx
@@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
)
const Label = React.forwardRef<
diff --git a/src/components/ui/Popover.tsx b/src/components/ui/Popover.tsx
index a0ec48b..cb5c141 100644
--- a/src/components/ui/Popover.tsx
+++ b/src/components/ui/Popover.tsx
@@ -20,7 +20,7 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/ui/Toast.tsx b/src/components/ui/Toast.tsx
index a822477..d61b0e0 100644
--- a/src/components/ui/Toast.tsx
+++ b/src/components/ui/Toast.tsx
@@ -15,7 +15,7 @@ const ToastViewport = React.forwardRef<
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
- className
+ className,
)}
{...props}
/>
@@ -35,7 +35,7 @@ const toastVariants = cva(
defaultVariants: {
variant: "default",
},
- }
+ },
)
const Toast = React.forwardRef<
@@ -61,7 +61,7 @@ const ToastAction = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
- className
+ className,
)}
{...props}
/>
@@ -76,7 +76,7 @@ const ToastClose = React.forwardRef<
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
- className
+ className,
)}
toast-close=""
{...props}
diff --git a/src/components/ui/Toaster.tsx b/src/components/ui/Toaster.tsx
index 37c92fd..9461ca3 100644
--- a/src/components/ui/Toaster.tsx
+++ b/src/components/ui/Toaster.tsx
@@ -1,28 +1,35 @@
-"use client";
+"use client"
-import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/Toast";
-import { useToast } from "@/hooks/use-toast";
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/Toast"
+import { useToast } from "@/hooks/use-toast"
export function Toaster() {
- const { toasts } = useToast();
+ const { toasts } = useToast()
- return (
-
- {toasts.map(function ({ id, title, description, action, ...props }) {
- return (
-
-
- {title && {title} }
- {description && {description} }
-
- {action}
-
-
- );
- })}
-
-
- );
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title} }
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
}
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
index df61a13..b8e7c62 100644
--- a/src/components/ui/checkbox.tsx
+++ b/src/components/ui/checkbox.tsx
@@ -14,7 +14,7 @@ const Checkbox = React.forwardRef<
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
- className
+ className,
)}
{...props}
>
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
index 30fc44d..2373b49 100644
--- a/src/components/ui/tooltip.tsx
+++ b/src/components/ui/tooltip.tsx
@@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
diff --git a/src/components/votes/AnswerAcceptClient.tsx b/src/components/votes/AnswerAcceptClient.tsx
index 0985ce3..082476c 100644
--- a/src/components/votes/AnswerAcceptClient.tsx
+++ b/src/components/votes/AnswerAcceptClient.tsx
@@ -1,104 +1,113 @@
-"use client";
+"use client"
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
-import { AnswerAcceptedRequest } from "@/lib/validators/vote";
-import { usePrevious } from "@mantine/hooks";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { useEffect, useState } from "react";
-import { toast } from "../../hooks/use-toast";
-import { Button } from "../ui/Button";
-import { Check } from "lucide-react";
-import { cn } from "@/lib/utils";
-import debounce from "lodash.debounce";
-import { Answer, AnswerVote, User } from "@prisma/client";
-import { useSession } from "next-auth/react";
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
+import { AnswerAcceptedRequest } from "@/lib/validators/vote"
+import { usePrevious } from "@mantine/hooks"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { useEffect, useState } from "react"
+import { toast } from "../../hooks/use-toast"
+import { Button } from "../ui/Button"
+import { Check } from "lucide-react"
+import { cn } from "@/lib/utils"
+import debounce from "lodash.debounce"
+import { Answer, AnswerVote, User } from "@prisma/client"
+import { useSession } from "next-auth/react"
interface AnswerAcceptClientProps {
- questionAuthorId: string;
- answerId: string;
- initialAccepted: boolean | undefined;
- answer: Answer & {
- author: User;
- votes: AnswerVote[];
- };
+ questionAuthorId: string
+ answerId: string
+ initialAccepted: boolean | undefined
+ answer: Answer & {
+ author: User
+ votes: AnswerVote[]
+ }
}
-const AnswerAcceptClient = ({ questionAuthorId, answerId, initialAccepted, answer }: AnswerAcceptClientProps) => {
- const { loginToast } = useCustomToasts();
- const [currentAccepted, setCurrentAccepted] = useState(initialAccepted);
- const prevAccepted = usePrevious(currentAccepted);
+const AnswerAcceptClient = ({
+ questionAuthorId,
+ answerId,
+ initialAccepted,
+ answer,
+}: AnswerAcceptClientProps) => {
+ const { loginToast } = useCustomToasts()
+ const [currentAccepted, setCurrentAccepted] = useState(initialAccepted)
+ const prevAccepted = usePrevious(currentAccepted)
- // ensure sync with server
- useEffect(() => {
- setCurrentAccepted(initialAccepted);
- }, [initialAccepted]);
+ // ensure sync with server
+ useEffect(() => {
+ setCurrentAccepted(initialAccepted)
+ }, [initialAccepted])
- const { mutate: accept } = useMutation({
- mutationFn: async (accepted: boolean) => {
- const payload: AnswerAcceptedRequest = {
- accepted: accepted,
- answerId: answerId,
- };
+ const { mutate: accept } = useMutation({
+ mutationFn: async (accepted: boolean) => {
+ const payload: AnswerAcceptedRequest = {
+ accepted: accepted,
+ answerId: answerId,
+ }
- await axios.patch("/api/subject/answer/accept", payload);
- },
- onError: (err, accept) => {
- // reset current acceptation
- setCurrentAccepted(prevAccepted);
+ await axios.patch("/api/subject/answer/accept", payload)
+ },
+ onError: (err, accept) => {
+ // reset current acceptation
+ setCurrentAccepted(prevAccepted)
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "Something went wrong.",
- description: "Your acceptation was not registered. Please try again.",
- variant: "destructive",
- });
- },
- onMutate: (accepted: boolean) => {
- if (currentAccepted === accepted) {
- // User is voting the same way again, so remove their acceptation
- setCurrentAccepted(undefined);
- } else {
- // User is voting in the opposite direction, so subtract 2
- setCurrentAccepted(accepted);
- }
- },
- });
+ return toast({
+ title: "Something went wrong.",
+ description: "Your acceptation was not registered. Please try again.",
+ variant: "destructive",
+ })
+ },
+ onMutate: (accepted: boolean) => {
+ if (currentAccepted === accepted) {
+ // User is voting the same way again, so remove their acceptation
+ setCurrentAccepted(undefined)
+ } else {
+ // User is voting in the opposite direction, so subtract 2
+ setCurrentAccepted(accepted)
+ }
+ },
+ })
- const debouncedAccepted = debounce(accept, 1000, { leading: true, trailing: false });
+ const debouncedAccepted = debounce(accept, 1000, {
+ leading: true,
+ trailing: false,
+ })
- const { data: session } = useSession();
+ const { data: session } = useSession()
- return (
-
- {questionAuthorId === session?.user.id ? (
- debouncedAccepted(true)}
- size="sm"
- variant="ghost"
- aria-label="accept">
-
-
- ) : (
-
- )}
-
- );
-};
+ return (
+
+ {questionAuthorId === session?.user.id ? (
+ debouncedAccepted(true)}
+ size="sm"
+ variant="ghost"
+ aria-label="accept"
+ >
+
+
+ ) : (
+
+ )}
+
+ )
+}
-export default AnswerAcceptClient;
+export default AnswerAcceptClient
diff --git a/src/components/votes/AnswerVoteClient.tsx b/src/components/votes/AnswerVoteClient.tsx
index 987a7a5..e928832 100644
--- a/src/components/votes/AnswerVoteClient.tsx
+++ b/src/components/votes/AnswerVoteClient.tsx
@@ -1,115 +1,127 @@
-"use client";
+"use client"
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
-import { AnswerVoteRequest } from "@/lib/validators/vote";
-import { usePrevious } from "@mantine/hooks";
-import { VoteType } from "@prisma/client";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { useEffect, useState } from "react";
-import { toast } from "../../hooks/use-toast";
-import { Button } from "../ui/Button";
-import { ArrowBigDown, ArrowBigUp } from "lucide-react";
-import { cn } from "@/lib/utils";
-import debounce from "lodash.debounce";
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
+import { AnswerVoteRequest } from "@/lib/validators/vote"
+import { usePrevious } from "@mantine/hooks"
+import { VoteType } from "@prisma/client"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { useEffect, useState } from "react"
+import { toast } from "../../hooks/use-toast"
+import { Button } from "../ui/Button"
+import { ArrowBigDown, ArrowBigUp } from "lucide-react"
+import { cn } from "@/lib/utils"
+import debounce from "lodash.debounce"
interface AnswerVoteClientProps {
- answerId: string;
- initialVotesAmt: number;
- initialVote?: VoteType | null;
+ answerId: string
+ initialVotesAmt: number
+ initialVote?: VoteType | null
}
-const AnswerVoteClient = ({ answerId, initialVotesAmt, initialVote }: AnswerVoteClientProps) => {
- const { loginToast } = useCustomToasts();
- const [votesAmt, setVotesAmt] = useState(initialVotesAmt);
- const [currentVote, setCurrentVote] = useState(initialVote);
- const prevVote = usePrevious(currentVote);
+const AnswerVoteClient = ({
+ answerId,
+ initialVotesAmt,
+ initialVote,
+}: AnswerVoteClientProps) => {
+ const { loginToast } = useCustomToasts()
+ const [votesAmt, setVotesAmt] = useState(initialVotesAmt)
+ const [currentVote, setCurrentVote] = useState(initialVote)
+ const prevVote = usePrevious(currentVote)
- // ensure sync with server
- useEffect(() => {
- setCurrentVote(initialVote);
- }, [initialVote]);
+ // ensure sync with server
+ useEffect(() => {
+ setCurrentVote(initialVote)
+ }, [initialVote])
- const { mutate: vote } = useMutation({
- mutationFn: async (type: VoteType) => {
- const payload: AnswerVoteRequest = {
- voteType: type,
- answerId: answerId,
- };
+ const { mutate: vote } = useMutation({
+ mutationFn: async (type: VoteType) => {
+ const payload: AnswerVoteRequest = {
+ voteType: type,
+ answerId: answerId,
+ }
- await axios.patch("/api/subject/answer/vote", payload);
- },
- onError: (err, voteType) => {
- if (voteType === "UP") setVotesAmt((prev) => prev - 1);
- else setVotesAmt((prev) => prev + 1);
+ await axios.patch("/api/subject/answer/vote", payload)
+ },
+ onError: (err, voteType) => {
+ if (voteType === "UP") setVotesAmt((prev) => prev - 1)
+ else setVotesAmt((prev) => prev + 1)
- // reset current vote
- setCurrentVote(prevVote);
+ // reset current vote
+ setCurrentVote(prevVote)
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "Something went wrong.",
- description: "Your vote was not registered. Please try again.",
- variant: "destructive",
- });
- },
- onMutate: (type: VoteType) => {
- if (currentVote === type) {
- // User is voting the same way again, so remove their vote
- setCurrentVote(undefined);
- if (type === "UP") setVotesAmt((prev) => prev - 1);
- else if (type === "DOWN") setVotesAmt((prev) => prev + 1);
- } else {
- // User is voting in the opposite direction, so subtract 2
- setCurrentVote(type);
- if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1));
- else if (type === "DOWN") setVotesAmt((prev) => prev - (currentVote ? 2 : 1));
- }
- },
- });
+ return toast({
+ title: "Something went wrong.",
+ description: "Your vote was not registered. Please try again.",
+ variant: "destructive",
+ })
+ },
+ onMutate: (type: VoteType) => {
+ if (currentVote === type) {
+ // User is voting the same way again, so remove their vote
+ setCurrentVote(undefined)
+ if (type === "UP") setVotesAmt((prev) => prev - 1)
+ else if (type === "DOWN") setVotesAmt((prev) => prev + 1)
+ } else {
+ // User is voting in the opposite direction, so subtract 2
+ setCurrentVote(type)
+ if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1))
+ else if (type === "DOWN")
+ setVotesAmt((prev) => prev - (currentVote ? 2 : 1))
+ }
+ },
+ })
- const debouncedVote = debounce(vote, 1000, { leading: true, trailing: false });
+ const debouncedVote = debounce(vote, 1000, {
+ leading: true,
+ trailing: false,
+ })
- return (
-
- {/* upvote */}
-
debouncedVote("UP")}
- size="sm"
- variant="ghost"
- aria-label="upvote">
-
-
+ return (
+
+ {/* upvote */}
+
debouncedVote("UP")}
+ size="sm"
+ variant="ghost"
+ aria-label="upvote"
+ >
+
+
- {/* score */}
-
{votesAmt}
+ {/* score */}
+
+ {votesAmt}
+
- {/* downvote */}
-
debouncedVote("DOWN")}
- size="sm"
- className={cn({
- "text-emerald-500": currentVote === "DOWN",
- })}
- variant="ghost"
- aria-label="downvote">
-
-
-
- );
-};
+ {/* downvote */}
+
debouncedVote("DOWN")}
+ size="sm"
+ className={cn({
+ "text-emerald-500": currentVote === "DOWN",
+ })}
+ variant="ghost"
+ aria-label="downvote"
+ >
+
+
+
+ )
+}
-export default AnswerVoteClient;
+export default AnswerVoteClient
diff --git a/src/components/votes/CommentVoteClient.tsx b/src/components/votes/CommentVoteClient.tsx
index 11c9b9a..0524154 100644
--- a/src/components/votes/CommentVoteClient.tsx
+++ b/src/components/votes/CommentVoteClient.tsx
@@ -1,115 +1,127 @@
-"use client";
+"use client"
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
-import { CommentVoteRequest } from "@/lib/validators/vote";
-import { usePrevious } from "@mantine/hooks";
-import { VoteType } from "@prisma/client";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { useEffect, useState } from "react";
-import { toast } from "../../hooks/use-toast";
-import { Button } from "../ui/Button";
-import { ArrowBigDown, ArrowBigUp } from "lucide-react";
-import { cn } from "@/lib/utils";
-import debounce from "lodash.debounce";
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
+import { CommentVoteRequest } from "@/lib/validators/vote"
+import { usePrevious } from "@mantine/hooks"
+import { VoteType } from "@prisma/client"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { useEffect, useState } from "react"
+import { toast } from "../../hooks/use-toast"
+import { Button } from "../ui/Button"
+import { ArrowBigDown, ArrowBigUp } from "lucide-react"
+import { cn } from "@/lib/utils"
+import debounce from "lodash.debounce"
interface CommentVoteClientProps {
- commentId: string;
- initialVotesAmt: number;
- initialVote?: VoteType | null;
+ commentId: string
+ initialVotesAmt: number
+ initialVote?: VoteType | null
}
-const CommentVoteClient = ({ commentId, initialVotesAmt, initialVote }: CommentVoteClientProps) => {
- const { loginToast } = useCustomToasts();
- const [votesAmt, setVotesAmt] = useState(initialVotesAmt);
- const [currentVote, setCurrentVote] = useState(initialVote);
- const prevVote = usePrevious(currentVote);
+const CommentVoteClient = ({
+ commentId,
+ initialVotesAmt,
+ initialVote,
+}: CommentVoteClientProps) => {
+ const { loginToast } = useCustomToasts()
+ const [votesAmt, setVotesAmt] = useState(initialVotesAmt)
+ const [currentVote, setCurrentVote] = useState(initialVote)
+ const prevVote = usePrevious(currentVote)
- // ensure sync with server
- useEffect(() => {
- setCurrentVote(initialVote);
- }, [initialVote]);
+ // ensure sync with server
+ useEffect(() => {
+ setCurrentVote(initialVote)
+ }, [initialVote])
- const { mutate: vote } = useMutation({
- mutationFn: async (type: VoteType) => {
- const payload: CommentVoteRequest = {
- voteType: type,
- commentId: commentId,
- };
+ const { mutate: vote } = useMutation({
+ mutationFn: async (type: VoteType) => {
+ const payload: CommentVoteRequest = {
+ voteType: type,
+ commentId: commentId,
+ }
- await axios.patch("/api/subject/comment/vote", payload);
- },
- onError: (err, voteType) => {
- if (voteType === "UP") setVotesAmt((prev) => prev - 1);
- else setVotesAmt((prev) => prev + 1);
+ await axios.patch("/api/subject/comment/vote", payload)
+ },
+ onError: (err, voteType) => {
+ if (voteType === "UP") setVotesAmt((prev) => prev - 1)
+ else setVotesAmt((prev) => prev + 1)
- // reset current vote
- setCurrentVote(prevVote);
+ // reset current vote
+ setCurrentVote(prevVote)
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "Something went wrong.",
- description: "Your vote was not registered. Please try again.",
- variant: "destructive",
- });
- },
- onMutate: (type: VoteType) => {
- if (currentVote === type) {
- // User is voting the same way again, so remove their vote
- setCurrentVote(undefined);
- if (type === "UP") setVotesAmt((prev) => prev - 1);
- else if (type === "DOWN") setVotesAmt((prev) => prev + 1);
- } else {
- // User is voting in the opposite direction, so subtract 2
- setCurrentVote(type);
- if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1));
- else if (type === "DOWN") setVotesAmt((prev) => prev - (currentVote ? 2 : 1));
- }
- },
- });
+ return toast({
+ title: "Something went wrong.",
+ description: "Your vote was not registered. Please try again.",
+ variant: "destructive",
+ })
+ },
+ onMutate: (type: VoteType) => {
+ if (currentVote === type) {
+ // User is voting the same way again, so remove their vote
+ setCurrentVote(undefined)
+ if (type === "UP") setVotesAmt((prev) => prev - 1)
+ else if (type === "DOWN") setVotesAmt((prev) => prev + 1)
+ } else {
+ // User is voting in the opposite direction, so subtract 2
+ setCurrentVote(type)
+ if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1))
+ else if (type === "DOWN")
+ setVotesAmt((prev) => prev - (currentVote ? 2 : 1))
+ }
+ },
+ })
- const debouncedVote = debounce(vote, 1000, { leading: true, trailing: false });
+ const debouncedVote = debounce(vote, 1000, {
+ leading: true,
+ trailing: false,
+ })
- return (
-
- {/* upvote */}
-
debouncedVote("UP")}
- size="sm"
- variant="ghost"
- aria-label="upvote">
-
-
+ return (
+
+ {/* upvote */}
+
debouncedVote("UP")}
+ size="sm"
+ variant="ghost"
+ aria-label="upvote"
+ >
+
+
- {/* score */}
-
{votesAmt}
+ {/* score */}
+
+ {votesAmt}
+
- {/* downvote */}
-
debouncedVote("DOWN")}
- size="sm"
- className={cn({
- "text-emerald-500": currentVote === "DOWN",
- })}
- variant="ghost"
- aria-label="downvote">
-
-
-
- );
-};
+ {/* downvote */}
+
debouncedVote("DOWN")}
+ size="sm"
+ className={cn({
+ "text-emerald-500": currentVote === "DOWN",
+ })}
+ variant="ghost"
+ aria-label="downvote"
+ >
+
+
+
+ )
+}
-export default CommentVoteClient;
+export default CommentVoteClient
diff --git a/src/components/votes/PostVoteClient.tsx b/src/components/votes/PostVoteClient.tsx
index 8873ec6..42730ff 100644
--- a/src/components/votes/PostVoteClient.tsx
+++ b/src/components/votes/PostVoteClient.tsx
@@ -1,115 +1,127 @@
-"use client";
+"use client"
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
-import { PostVoteRequest } from "@/lib/validators/vote";
-import { usePrevious } from "@mantine/hooks";
-import { VoteType } from "@prisma/client";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { useEffect, useState } from "react";
-import { toast } from "../../hooks/use-toast";
-import { Button } from "../ui/Button";
-import { ArrowBigDown, ArrowBigUp } from "lucide-react";
-import { cn } from "@/lib/utils";
-import debounce from "lodash.debounce";
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
+import { PostVoteRequest } from "@/lib/validators/vote"
+import { usePrevious } from "@mantine/hooks"
+import { VoteType } from "@prisma/client"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { useEffect, useState } from "react"
+import { toast } from "../../hooks/use-toast"
+import { Button } from "../ui/Button"
+import { ArrowBigDown, ArrowBigUp } from "lucide-react"
+import { cn } from "@/lib/utils"
+import debounce from "lodash.debounce"
interface PostVoteClientProps {
- postId: string;
- initialVotesAmt: number;
- initialVote?: VoteType | null;
+ postId: string
+ initialVotesAmt: number
+ initialVote?: VoteType | null
}
-const PostVoteClient = ({ postId, initialVotesAmt, initialVote }: PostVoteClientProps) => {
- const { loginToast } = useCustomToasts();
- const [votesAmt, setVotesAmt] = useState(initialVotesAmt);
- const [currentVote, setCurrentVote] = useState(initialVote);
- const prevVote = usePrevious(currentVote);
+const PostVoteClient = ({
+ postId,
+ initialVotesAmt,
+ initialVote,
+}: PostVoteClientProps) => {
+ const { loginToast } = useCustomToasts()
+ const [votesAmt, setVotesAmt] = useState(initialVotesAmt)
+ const [currentVote, setCurrentVote] = useState(initialVote)
+ const prevVote = usePrevious(currentVote)
- // ensure sync with server
- useEffect(() => {
- setCurrentVote(initialVote);
- }, [initialVote]);
+ // ensure sync with server
+ useEffect(() => {
+ setCurrentVote(initialVote)
+ }, [initialVote])
- const { mutate: vote } = useMutation({
- mutationFn: async (type: VoteType) => {
- const payload: PostVoteRequest = {
- voteType: type,
- postId: postId,
- };
+ const { mutate: vote } = useMutation({
+ mutationFn: async (type: VoteType) => {
+ const payload: PostVoteRequest = {
+ voteType: type,
+ postId: postId,
+ }
- await axios.patch("/api/subject/post/vote", payload);
- },
- onError: (err, voteType) => {
- if (voteType === "UP") setVotesAmt((prev) => prev - 1);
- else setVotesAmt((prev) => prev + 1);
+ await axios.patch("/api/subject/post/vote", payload)
+ },
+ onError: (err, voteType) => {
+ if (voteType === "UP") setVotesAmt((prev) => prev - 1)
+ else setVotesAmt((prev) => prev + 1)
- // reset current vote
- setCurrentVote(prevVote);
+ // reset current vote
+ setCurrentVote(prevVote)
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "Something went wrong.",
- description: "Your vote was not registered. Please try again.",
- variant: "destructive",
- });
- },
- onMutate: (type: VoteType) => {
- if (currentVote === type) {
- // User is voting the same way again, so remove their vote
- setCurrentVote(undefined);
- if (type === "UP") setVotesAmt((prev) => prev - 1);
- else if (type === "DOWN") setVotesAmt((prev) => prev + 1);
- } else {
- // User is voting in the opposite direction, so subtract 2
- setCurrentVote(type);
- if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1));
- else if (type === "DOWN") setVotesAmt((prev) => prev - (currentVote ? 2 : 1));
- }
- },
- });
+ return toast({
+ title: "Something went wrong.",
+ description: "Your vote was not registered. Please try again.",
+ variant: "destructive",
+ })
+ },
+ onMutate: (type: VoteType) => {
+ if (currentVote === type) {
+ // User is voting the same way again, so remove their vote
+ setCurrentVote(undefined)
+ if (type === "UP") setVotesAmt((prev) => prev - 1)
+ else if (type === "DOWN") setVotesAmt((prev) => prev + 1)
+ } else {
+ // User is voting in the opposite direction, so subtract 2
+ setCurrentVote(type)
+ if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1))
+ else if (type === "DOWN")
+ setVotesAmt((prev) => prev - (currentVote ? 2 : 1))
+ }
+ },
+ })
- const debouncedVote = debounce(vote, 1000, { leading: true, trailing: false });
+ const debouncedVote = debounce(vote, 1000, {
+ leading: true,
+ trailing: false,
+ })
- return (
-
- {/* upvote */}
-
debouncedVote("UP")}
- size="sm"
- variant="ghost"
- aria-label="upvote">
-
-
+ return (
+
+ {/* upvote */}
+
debouncedVote("UP")}
+ size="sm"
+ variant="ghost"
+ aria-label="upvote"
+ >
+
+
- {/* score */}
-
{votesAmt}
+ {/* score */}
+
+ {votesAmt}
+
- {/* downvote */}
-
debouncedVote("DOWN")}
- size="sm"
- className={cn({
- "text-emerald-500": currentVote === "DOWN",
- })}
- variant="ghost"
- aria-label="downvote">
-
-
-
- );
-};
+ {/* downvote */}
+
debouncedVote("DOWN")}
+ size="sm"
+ className={cn({
+ "text-emerald-500": currentVote === "DOWN",
+ })}
+ variant="ghost"
+ aria-label="downvote"
+ >
+
+
+
+ )
+}
-export default PostVoteClient;
+export default PostVoteClient
diff --git a/src/components/votes/PostVoteServer.tsx b/src/components/votes/PostVoteServer.tsx
index 340c4f6..fb64f9b 100644
--- a/src/components/votes/PostVoteServer.tsx
+++ b/src/components/votes/PostVoteServer.tsx
@@ -1,13 +1,13 @@
-import { getAuthSession } from "@/lib/auth";
-import type { Post, PostVote } from "@prisma/client";
-import { notFound } from "next/navigation";
-import PostVoteClient from "./PostVoteClient";
+import { getAuthSession } from "@/lib/auth"
+import type { Post, PostVote } from "@prisma/client"
+import { notFound } from "next/navigation"
+import PostVoteClient from "./PostVoteClient"
interface PostVoteServerProps {
- postId: string;
- initialVotesAmt?: number;
- initialVote?: PostVote["type"] | null;
- getData?: () => Promise<(Post & { votes: PostVote[] }) | null>;
+ postId: string
+ initialVotesAmt?: number
+ initialVote?: PostVote["type"] | null
+ getData?: () => Promise<(Post & { votes: PostVote[] }) | null>
}
/**
@@ -17,37 +17,44 @@ interface PostVoteServerProps {
*
*/
-const PostVoteServer = async ({ postId, initialVotesAmt, initialVote, getData }: PostVoteServerProps) => {
- const session = await getAuthSession();
-
- let _votesAmt: number = 0;
- let _currentVote: PostVote["type"] | null | undefined = undefined;
-
- if (getData) {
- // fetch data in component
- const post = await getData();
- if (!post) return notFound();
-
- _votesAmt = post.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- _currentVote = post.votes.find((vote) => vote.userId === session?.user?.id)?.type;
- } else {
- // passed as props
- _votesAmt = initialVotesAmt!;
- _currentVote = initialVote;
- }
-
- return (
-
- );
-};
-
-export default PostVoteServer;
+const PostVoteServer = async ({
+ postId,
+ initialVotesAmt,
+ initialVote,
+ getData,
+}: PostVoteServerProps) => {
+ const session = await getAuthSession()
+
+ let _votesAmt: number = 0
+ let _currentVote: PostVote["type"] | null | undefined = undefined
+
+ if (getData) {
+ // fetch data in component
+ const post = await getData()
+ if (!post) return notFound()
+
+ _votesAmt = post.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ _currentVote = post.votes.find(
+ (vote) => vote.userId === session?.user?.id,
+ )?.type
+ } else {
+ // passed as props
+ _votesAmt = initialVotesAmt!
+ _currentVote = initialVote
+ }
+
+ return (
+
+ )
+}
+
+export default PostVoteServer
diff --git a/src/components/votes/QuestionVoteClient.tsx b/src/components/votes/QuestionVoteClient.tsx
index 8610045..b46f61a 100644
--- a/src/components/votes/QuestionVoteClient.tsx
+++ b/src/components/votes/QuestionVoteClient.tsx
@@ -1,115 +1,127 @@
-"use client";
+"use client"
-import { useCustomToasts } from "@/hooks/use-custom-toasts";
-import { QuestionVoteRequest } from "@/lib/validators/vote";
-import { usePrevious } from "@mantine/hooks";
-import { VoteType } from "@prisma/client";
-import { useMutation } from "@tanstack/react-query";
-import axios, { AxiosError } from "axios";
-import { useEffect, useState } from "react";
-import { toast } from "../../hooks/use-toast";
-import { Button } from "../ui/Button";
-import { ArrowBigDown, ArrowBigUp } from "lucide-react";
-import { cn } from "@/lib/utils";
-import debounce from "lodash.debounce";
+import { useCustomToasts } from "@/hooks/use-custom-toasts"
+import { QuestionVoteRequest } from "@/lib/validators/vote"
+import { usePrevious } from "@mantine/hooks"
+import { VoteType } from "@prisma/client"
+import { useMutation } from "@tanstack/react-query"
+import axios, { AxiosError } from "axios"
+import { useEffect, useState } from "react"
+import { toast } from "../../hooks/use-toast"
+import { Button } from "../ui/Button"
+import { ArrowBigDown, ArrowBigUp } from "lucide-react"
+import { cn } from "@/lib/utils"
+import debounce from "lodash.debounce"
interface QuestionVoteClientProps {
- questionId: string;
- initialVotesAmt: number;
- initialVote?: VoteType | null;
+ questionId: string
+ initialVotesAmt: number
+ initialVote?: VoteType | null
}
-const QuestionVoteClient = ({ questionId, initialVotesAmt, initialVote }: QuestionVoteClientProps) => {
- const { loginToast } = useCustomToasts();
- const [votesAmt, setVotesAmt] = useState(initialVotesAmt);
- const [currentVote, setCurrentVote] = useState(initialVote);
- const prevVote = usePrevious(currentVote);
+const QuestionVoteClient = ({
+ questionId,
+ initialVotesAmt,
+ initialVote,
+}: QuestionVoteClientProps) => {
+ const { loginToast } = useCustomToasts()
+ const [votesAmt, setVotesAmt] = useState(initialVotesAmt)
+ const [currentVote, setCurrentVote] = useState(initialVote)
+ const prevVote = usePrevious(currentVote)
- // ensure sync with server
- useEffect(() => {
- setCurrentVote(initialVote);
- }, [initialVote]);
+ // ensure sync with server
+ useEffect(() => {
+ setCurrentVote(initialVote)
+ }, [initialVote])
- const { mutate: vote } = useMutation({
- mutationFn: async (type: VoteType) => {
- const payload: QuestionVoteRequest = {
- voteType: type,
- questionId: questionId,
- };
+ const { mutate: vote } = useMutation({
+ mutationFn: async (type: VoteType) => {
+ const payload: QuestionVoteRequest = {
+ voteType: type,
+ questionId: questionId,
+ }
- await axios.patch("/api/subject/question/vote", payload);
- },
- onError: (err, voteType) => {
- if (voteType === "UP") setVotesAmt((prev) => prev - 1);
- else setVotesAmt((prev) => prev + 1);
+ await axios.patch("/api/subject/question/vote", payload)
+ },
+ onError: (err, voteType) => {
+ if (voteType === "UP") setVotesAmt((prev) => prev - 1)
+ else setVotesAmt((prev) => prev + 1)
- // reset current vote
- setCurrentVote(prevVote);
+ // reset current vote
+ setCurrentVote(prevVote)
- if (err instanceof AxiosError) {
- if (err.response?.status === 401) {
- return loginToast();
- }
- }
+ if (err instanceof AxiosError) {
+ if (err.response?.status === 401) {
+ return loginToast()
+ }
+ }
- return toast({
- title: "Something went wrong.",
- description: "Your vote was not registered. Please try again.",
- variant: "destructive",
- });
- },
- onMutate: (type: VoteType) => {
- if (currentVote === type) {
- // User is voting the same way again, so remove their vote
- setCurrentVote(undefined);
- if (type === "UP") setVotesAmt((prev) => prev - 1);
- else if (type === "DOWN") setVotesAmt((prev) => prev + 1);
- } else {
- // User is voting in the opposite direction, so subtract 2
- setCurrentVote(type);
- if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1));
- else if (type === "DOWN") setVotesAmt((prev) => prev - (currentVote ? 2 : 1));
- }
- },
- });
+ return toast({
+ title: "Something went wrong.",
+ description: "Your vote was not registered. Please try again.",
+ variant: "destructive",
+ })
+ },
+ onMutate: (type: VoteType) => {
+ if (currentVote === type) {
+ // User is voting the same way again, so remove their vote
+ setCurrentVote(undefined)
+ if (type === "UP") setVotesAmt((prev) => prev - 1)
+ else if (type === "DOWN") setVotesAmt((prev) => prev + 1)
+ } else {
+ // User is voting in the opposite direction, so subtract 2
+ setCurrentVote(type)
+ if (type === "UP") setVotesAmt((prev) => prev + (currentVote ? 2 : 1))
+ else if (type === "DOWN")
+ setVotesAmt((prev) => prev - (currentVote ? 2 : 1))
+ }
+ },
+ })
- const debouncedVote = debounce(vote, 1000, { leading: true, trailing: false });
+ const debouncedVote = debounce(vote, 1000, {
+ leading: true,
+ trailing: false,
+ })
- return (
-
- {/* upvote */}
-
debouncedVote("UP")}
- size="sm"
- variant="ghost"
- aria-label="upvote">
-
-
+ return (
+
+ {/* upvote */}
+
debouncedVote("UP")}
+ size="sm"
+ variant="ghost"
+ aria-label="upvote"
+ >
+
+
- {/* score */}
-
{votesAmt}
+ {/* score */}
+
+ {votesAmt}
+
- {/* downvote */}
-
debouncedVote("DOWN")}
- size="sm"
- className={cn({
- "text-emerald-500": currentVote === "DOWN",
- })}
- variant="ghost"
- aria-label="downvote">
-
-
-
- );
-};
+ {/* downvote */}
+
debouncedVote("DOWN")}
+ size="sm"
+ className={cn({
+ "text-emerald-500": currentVote === "DOWN",
+ })}
+ variant="ghost"
+ aria-label="downvote"
+ >
+
+
+
+ )
+}
-export default QuestionVoteClient;
+export default QuestionVoteClient
diff --git a/src/components/votes/QuestionVoteServer.tsx b/src/components/votes/QuestionVoteServer.tsx
index 87b9d8f..ca85de0 100644
--- a/src/components/votes/QuestionVoteServer.tsx
+++ b/src/components/votes/QuestionVoteServer.tsx
@@ -1,13 +1,13 @@
-import { getAuthSession } from "@/lib/auth";
-import type { Question, QuestionVote } from "@prisma/client";
-import { notFound } from "next/navigation";
-import QuestionVoteClient from "./QuestionVoteClient";
+import { getAuthSession } from "@/lib/auth"
+import type { Question, QuestionVote } from "@prisma/client"
+import { notFound } from "next/navigation"
+import QuestionVoteClient from "./QuestionVoteClient"
interface QuestionVoteServerProps {
- questionId: string;
- initialVotesAmt?: number;
- initialVote?: QuestionVote["type"] | null;
- getData?: () => Promise<(Question & { votes: QuestionVote[] }) | null>;
+ questionId: string
+ initialVotesAmt?: number
+ initialVote?: QuestionVote["type"] | null
+ getData?: () => Promise<(Question & { votes: QuestionVote[] }) | null>
}
/**
@@ -17,37 +17,44 @@ interface QuestionVoteServerProps {
*
*/
-const QuestionVoteServer = async ({ questionId, initialVotesAmt, initialVote, getData }: QuestionVoteServerProps) => {
- const session = await getAuthSession();
-
- let _votesAmt: number = 0;
- let _currentVote: QuestionVote["type"] | null | undefined = undefined;
-
- if (getData) {
- // fetch data in component
- const question = await getData();
- if (!question) return notFound();
-
- _votesAmt = question.votes.reduce((acc, vote) => {
- if (vote.type === "UP") return acc + 1;
- if (vote.type === "DOWN") return acc - 1;
- return acc;
- }, 0);
-
- _currentVote = question.votes.find((vote) => vote.userId === session?.user?.id)?.type;
- } else {
- // passed as props
- _votesAmt = initialVotesAmt!;
- _currentVote = initialVote;
- }
-
- return (
-
- );
-};
-
-export default QuestionVoteServer;
+const QuestionVoteServer = async ({
+ questionId,
+ initialVotesAmt,
+ initialVote,
+ getData,
+}: QuestionVoteServerProps) => {
+ const session = await getAuthSession()
+
+ let _votesAmt: number = 0
+ let _currentVote: QuestionVote["type"] | null | undefined = undefined
+
+ if (getData) {
+ // fetch data in component
+ const question = await getData()
+ if (!question) return notFound()
+
+ _votesAmt = question.votes.reduce((acc, vote) => {
+ if (vote.type === "UP") return acc + 1
+ if (vote.type === "DOWN") return acc - 1
+ return acc
+ }, 0)
+
+ _currentVote = question.votes.find(
+ (vote) => vote.userId === session?.user?.id,
+ )?.type
+ } else {
+ // passed as props
+ _votesAmt = initialVotesAmt!
+ _currentVote = initialVote
+ }
+
+ return (
+
+ )
+}
+
+export default QuestionVoteServer
diff --git a/src/config.ts b/src/config.ts
index def9267..53cbb5a 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1 +1 @@
-export const INFINITE_SCROLL_PAGINATION_RESULTS = 4;
+export const INFINITE_SCROLL_PAGINATION_RESULTS = 4
diff --git a/src/hooks/use-custom-toasts.tsx b/src/hooks/use-custom-toasts.tsx
index 04ffdc0..f10f486 100644
--- a/src/hooks/use-custom-toasts.tsx
+++ b/src/hooks/use-custom-toasts.tsx
@@ -1,23 +1,24 @@
-import Link from "next/link";
-import { toast } from "./use-toast";
-import { buttonVariants } from "@/components/ui/Button";
+import Link from "next/link"
+import { toast } from "./use-toast"
+import { buttonVariants } from "@/components/ui/Button"
export const useCustomToasts = () => {
- const loginToast = () => {
- const { dismiss } = toast({
- title: "Inici de sessió necessari.",
- description: "Necessites iniciar sessió per a accedir a aquesta pàgina.",
- variant: "destructive",
- action: (
- dismiss()}
- className={buttonVariants({ variant: "outline" })}>
- Inicia sessió
-
- ),
- });
- };
+ const loginToast = () => {
+ const { dismiss } = toast({
+ title: "Inici de sessió necessari.",
+ description: "Necessites iniciar sessió per a accedir a aquesta pàgina.",
+ variant: "destructive",
+ action: (
+ dismiss()}
+ className={buttonVariants({ variant: "outline" })}
+ >
+ Inicia sessió
+
+ ),
+ })
+ }
- return { loginToast };
-};
+ return { loginToast }
+}
diff --git a/src/hooks/use-on-click-outside.ts b/src/hooks/use-on-click-outside.ts
index 3364023..e0aa32d 100644
--- a/src/hooks/use-on-click-outside.ts
+++ b/src/hooks/use-on-click-outside.ts
@@ -1,10 +1,10 @@
-import { RefObject, useEffect } from 'react'
+import { RefObject, useEffect } from "react"
type Event = MouseEvent | TouchEvent
export const useOnClickOutside = (
ref: RefObject,
- handler: (event: Event) => void
+ handler: (event: Event) => void,
) => {
useEffect(() => {
const listener = (event: Event) => {
@@ -16,12 +16,12 @@ export const useOnClickOutside = (
handler(event) // Call the handler only if the click is outside of the element passed.
}
- document.addEventListener('mousedown', listener)
- document.addEventListener('touchstart', listener)
+ document.addEventListener("mousedown", listener)
+ document.addEventListener("touchstart", listener)
return () => {
- document.removeEventListener('mousedown', listener)
- document.removeEventListener('touchstart', listener)
+ document.removeEventListener("mousedown", listener)
+ document.removeEventListener("touchstart", listener)
}
}, [ref, handler]) // Reload only if ref or handler changes
}
diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts
index 4274db7..7ccf21b 100644
--- a/src/hooks/use-toast.ts
+++ b/src/hooks/use-toast.ts
@@ -1,10 +1,7 @@
// Inspired by react-hot-toast library
import * as React from "react"
-import type {
- ToastActionElement,
- ToastProps,
-} from "@/components/ui/Toast"
+import type { ToastActionElement, ToastProps } from "@/components/ui/Toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
@@ -84,7 +81,7 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
toasts: state.toasts.map((t) =>
- t.id === action.toast.id ? { ...t, ...action.toast } : t
+ t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
}
@@ -109,7 +106,7 @@ export const reducer = (state: State, action: Action): State => {
...t,
open: false,
}
- : t
+ : t,
),
}
}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index bee4f75..f6e1b1d 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,86 +1,86 @@
-import { db } from "@/lib/db";
-import { PrismaAdapter } from "@next-auth/prisma-adapter";
-import { nanoid } from "nanoid";
-import { NextAuthOptions, getServerSession } from "next-auth";
-import GoogleProvider from "next-auth/providers/google";
+import { db } from "@/lib/db"
+import { PrismaAdapter } from "@next-auth/prisma-adapter"
+import { nanoid } from "nanoid"
+import { NextAuthOptions, getServerSession } from "next-auth"
+import GoogleProvider from "next-auth/providers/google"
export const authOptions: NextAuthOptions = {
- adapter: PrismaAdapter(db),
- session: {
- strategy: "jwt",
- },
- pages: {
- signIn: "/sign-in",
- },
- providers: [
- GoogleProvider({
- clientId: process.env.GOOGLE_CLIENT_ID!,
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
- }),
- ],
- callbacks: {
- async signIn(params) {
- const { profile } = params;
- const allowedEmail = await db.authorizedUsers.findFirst({
- where: {
- email: profile?.email,
- },
- });
- if (!allowedEmail) {
- return false;
- }
- return true;
- },
+ adapter: PrismaAdapter(db),
+ session: {
+ strategy: "jwt",
+ },
+ pages: {
+ signIn: "/sign-in",
+ },
+ providers: [
+ GoogleProvider({
+ clientId: process.env.GOOGLE_CLIENT_ID!,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
+ }),
+ ],
+ callbacks: {
+ async signIn(params) {
+ const { profile } = params
+ const allowedEmail = await db.authorizedUsers.findFirst({
+ where: {
+ email: profile?.email,
+ },
+ })
+ if (!allowedEmail) {
+ return false
+ }
+ return true
+ },
- async session({ token, session }) {
- if (token) {
- session.user.id = token.id;
- session.user.name = token.name;
- session.user.email = token.email;
- session.user.image = token.picture;
- session.user.username = token.username;
- session.user.generacio = token.generacio as string;
- }
+ async session({ token, session }) {
+ if (token) {
+ session.user.id = token.id
+ session.user.name = token.name
+ session.user.email = token.email
+ session.user.image = token.picture
+ session.user.username = token.username
+ session.user.generacio = token.generacio as string
+ }
- return session;
- },
+ return session
+ },
- async jwt({ token, user }) {
- const dbUser = await db.user.findFirst({
- where: {
- email: token.email,
- },
- });
+ async jwt({ token, user }) {
+ const dbUser = await db.user.findFirst({
+ where: {
+ email: token.email,
+ },
+ })
- if (!dbUser) {
- token.id = user!.id;
- return token;
- }
+ if (!dbUser) {
+ token.id = user!.id
+ return token
+ }
- if (!dbUser.username) {
- await db.user.update({
- where: {
- id: dbUser.id,
- },
- data: {
- username: nanoid(10),
- },
- });
- }
+ if (!dbUser.username) {
+ await db.user.update({
+ where: {
+ id: dbUser.id,
+ },
+ data: {
+ username: nanoid(10),
+ },
+ })
+ }
- return {
- id: dbUser.id,
- name: dbUser.name,
- email: dbUser.email,
- picture: dbUser.image,
- username: dbUser.username,
- generacio: dbUser.generacio,
- };
- },
- redirect() {
- return "/";
- },
- },
-};
+ return {
+ id: dbUser.id,
+ name: dbUser.name,
+ email: dbUser.email,
+ picture: dbUser.image,
+ username: dbUser.username,
+ generacio: dbUser.generacio,
+ }
+ },
+ redirect() {
+ return "/"
+ },
+ },
+}
-export const getAuthSession = () => getServerSession(authOptions);
+export const getAuthSession = () => getServerSession(authOptions)
diff --git a/src/lib/db.ts b/src/lib/db.ts
index 3dbeec3..2f2cae5 100644
--- a/src/lib/db.ts
+++ b/src/lib/db.ts
@@ -1,4 +1,4 @@
-import { PrismaClient } from '@prisma/client'
+import { PrismaClient } from "@prisma/client"
import "server-only"
declare global {
@@ -7,7 +7,7 @@ declare global {
}
let prisma: PrismaClient
-if (process.env.NODE_ENV === 'production') {
+if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient()
} else {
if (!global.cachedPrisma) {
@@ -16,4 +16,4 @@ if (process.env.NODE_ENV === 'production') {
prisma = global.cachedPrisma
}
-export const db = prisma
\ No newline at end of file
+export const db = prisma
diff --git a/src/lib/redis.ts b/src/lib/redis.ts
index 52582d1..1f4c9f7 100644
--- a/src/lib/redis.ts
+++ b/src/lib/redis.ts
@@ -1,3 +1,6 @@
-import { Redis } from "@upstash/redis";
+import { Redis } from "@upstash/redis"
-export const redis = new Redis({ url: process.env.REDIS_URL!, token: process.env.REDIS_SECRET! });
+export const redis = new Redis({
+ url: process.env.REDIS_URL!,
+ token: process.env.REDIS_SECRET!,
+})
diff --git a/src/lib/uploadthing.ts b/src/lib/uploadthing.ts
index 2604b14..e2a1d98 100644
--- a/src/lib/uploadthing.ts
+++ b/src/lib/uploadthing.ts
@@ -1,5 +1,5 @@
-import { generateReactHelpers } from "@uploadthing/react/hooks";
+import { generateReactHelpers } from "@uploadthing/react/hooks"
-import type { OurFileRouter } from "@/app//api/uploadthing/core";
+import type { OurFileRouter } from "@/app//api/uploadthing/core"
-export const { uploadFiles } = generateReactHelpers();
+export const { uploadFiles } = generateReactHelpers()
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index ff12c14..a199a3f 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,57 +1,56 @@
-import { ClassValue, clsx } from "clsx";
-import { twMerge } from "tailwind-merge";
-import { formatDistanceToNowStrict } from "date-fns";
-import locale from "date-fns/locale/en-US";
+import { ClassValue, clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+import { formatDistanceToNowStrict } from "date-fns"
+import locale from "date-fns/locale/en-US"
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
+ return twMerge(clsx(inputs))
}
const formatDistanceLocale = {
- lessThanXSeconds: "just now",
- xSeconds: "just now",
- halfAMinute: "just now",
- lessThanXMinutes: "{{count}}m",
- xMinutes: "{{count}}m",
- aboutXHours: "{{count}}h",
- xHours: "{{count}}h",
- xDays: "{{count}}d",
- aboutXWeeks: "{{count}}w",
- xWeeks: "{{count}}w",
- aboutXMonths: "{{count}}m",
- xMonths: "{{count}}m",
- aboutXYears: "{{count}}y",
- xYears: "{{count}}y",
- overXYears: "{{count}}y",
- almostXYears: "{{count}}y",
-};
+ lessThanXSeconds: "just now",
+ xSeconds: "just now",
+ halfAMinute: "just now",
+ lessThanXMinutes: "{{count}}m",
+ xMinutes: "{{count}}m",
+ aboutXHours: "{{count}}h",
+ xHours: "{{count}}h",
+ xDays: "{{count}}d",
+ aboutXWeeks: "{{count}}w",
+ xWeeks: "{{count}}w",
+ aboutXMonths: "{{count}}m",
+ xMonths: "{{count}}m",
+ aboutXYears: "{{count}}y",
+ xYears: "{{count}}y",
+ overXYears: "{{count}}y",
+ almostXYears: "{{count}}y",
+}
function formatDistance(token: string, count: number, options?: any): string {
- options = options || {};
+ options = options || {}
- const result = formatDistanceLocale[token as keyof typeof formatDistanceLocale].replace(
- "{{count}}",
- count.toString()
- );
+ const result = formatDistanceLocale[
+ token as keyof typeof formatDistanceLocale
+ ].replace("{{count}}", count.toString())
- if (options.addSuffix) {
- if (options.comparison > 0) {
- return "in " + result;
- } else {
- if (result === "just now") return result;
- return result + " ago";
- }
- }
+ if (options.addSuffix) {
+ if (options.comparison > 0) {
+ return "in " + result
+ } else {
+ if (result === "just now") return result
+ return result + " ago"
+ }
+ }
- return result;
+ return result
}
export function formatTimeToNow(date: Date): string {
- return formatDistanceToNowStrict(date, {
- addSuffix: true,
- locale: {
- ...locale,
- formatDistance,
- },
- });
+ return formatDistanceToNowStrict(date, {
+ addSuffix: true,
+ locale: {
+ ...locale,
+ formatDistance,
+ },
+ })
}
diff --git a/src/lib/validators/post.ts b/src/lib/validators/post.ts
index 34e0ae5..c995ee8 100644
--- a/src/lib/validators/post.ts
+++ b/src/lib/validators/post.ts
@@ -1,27 +1,28 @@
-import { z } from "zod";
+import { z } from "zod"
export const PostValidator = z.object({
- title: z
- .string()
- .min(3, { message: "Title must be at least 3 characters long" })
- .max(128, { message: "Title must be at most 128 characters long" }),
- subjectId: z.string(),
- content: z.any(),
- tipus: z.string(),
- year: z.number(),
-});
+ title: z
+ .string()
+ .min(3, { message: "Title must be at least 3 characters long" })
+ .max(128, { message: "Title must be at most 128 characters long" }),
+ subjectId: z.string(),
+ content: z.any(),
+ tipus: z.string(),
+ year: z.number(),
+})
export const CommentValidator = z.object({
- content: z.string().min(1).max(2048),
- postId: z.string(),
-});
+ content: z.string().min(1).max(2048),
+ postId: z.string(),
+})
export const ApuntsPostValidator = z.object({
- pdf: z.any(),
- title: z.string(),
- assignatura: z.string().min(2).max(5),
- tipus: z.string(),
-});
+ pdf: z.any(),
+ title: z.string(),
+ assignatura: z.string().min(2).max(5),
+ tipus: z.string(),
+ anonim: z.boolean(),
+})
-export type PostCreationRequest = z.infer;
-export type ApuntsPostCreationRequest = z.infer;
+export type PostCreationRequest = z.infer
+export type ApuntsPostCreationRequest = z.infer
diff --git a/src/lib/validators/question.ts b/src/lib/validators/question.ts
index 284694d..835df5b 100644
--- a/src/lib/validators/question.ts
+++ b/src/lib/validators/question.ts
@@ -1,20 +1,21 @@
-import { z } from "zod";
+import { z } from "zod"
export const QuestionValidator = z.object({
- title: z
- .string()
- .min(3, { message: "Title must be at least 3 characters long" })
- .max(128, { message: "Title must be at most 128 characters long" }),
- subjectId: z.string(),
- content: z.any(),
-});
+ title: z
+ .string()
+ .min(3, { message: "Title must be at least 3 characters long" })
+ .max(128, { message: "Title must be at most 128 characters long" }),
+ subjectId: z.string(),
+ content: z.any(),
+})
export const AnswerValidator = z.object({
- title : z.string()
- .min(3, { message: "Content must be at least 3 characters long" })
- .max(128, { message: "Content must be at most 2048 characters long" }),
- subjectId: z.string(),
- content: z.any(),
- questionId: z.string(),
-});
-export type QuestionCreationRequest = z.infer;
-export type AnswerCreationRequest = z.infer;
\ No newline at end of file
+ title: z
+ .string()
+ .min(3, { message: "Content must be at least 3 characters long" })
+ .max(128, { message: "Content must be at most 2048 characters long" }),
+ subjectId: z.string(),
+ content: z.any(),
+ questionId: z.string(),
+})
+export type QuestionCreationRequest = z.infer
+export type AnswerCreationRequest = z.infer
diff --git a/src/lib/validators/subject.ts b/src/lib/validators/subject.ts
index a38e21a..7b81512 100644
--- a/src/lib/validators/subject.ts
+++ b/src/lib/validators/subject.ts
@@ -1,14 +1,16 @@
-import { z } from "zod";
+import { z } from "zod"
export const SubjectValidator = z.object({
- name: z.string().min(3).max(128),
- acronym: z.string().min(2).max(5),
- semester: z.string().min(1).max(2),
-});
+ name: z.string().min(3).max(128),
+ acronym: z.string().min(2).max(5),
+ semester: z.string().min(1).max(2),
+})
export const SubjectSubscriptionValidator = z.object({
- subjectId: z.string(),
-});
+ subjectId: z.string(),
+})
-export type CreateSubjectPayload = z.infer;
-export type SubscribeToSubjectPayload = z.infer;
+export type CreateSubjectPayload = z.infer
+export type SubscribeToSubjectPayload = z.infer<
+ typeof SubjectSubscriptionValidator
+>
diff --git a/src/lib/validators/vote.ts b/src/lib/validators/vote.ts
index 330710f..0fc7adb 100644
--- a/src/lib/validators/vote.ts
+++ b/src/lib/validators/vote.ts
@@ -1,36 +1,36 @@
-import { z } from "zod";
+import { z } from "zod"
export const PostVoteValidator = z.object({
- postId: z.string(),
- voteType: z.enum(["UP", "DOWN"]),
-});
+ postId: z.string(),
+ voteType: z.enum(["UP", "DOWN"]),
+})
-export type PostVoteRequest = z.infer;
+export type PostVoteRequest = z.infer
export const CommentVoteValidator = z.object({
- commentId: z.string(),
- voteType: z.enum(["UP", "DOWN"]),
-});
+ commentId: z.string(),
+ voteType: z.enum(["UP", "DOWN"]),
+})
-export type CommentVoteRequest = z.infer;
+export type CommentVoteRequest = z.infer
export const QuestionVoteValidator = z.object({
- questionId: z.string(),
- voteType: z.enum(["UP", "DOWN"]),
-});
+ questionId: z.string(),
+ voteType: z.enum(["UP", "DOWN"]),
+})
-export type QuestionVoteRequest = z.infer;
+export type QuestionVoteRequest = z.infer
export const AnswerVoteValidator = z.object({
- answerId: z.string(),
- voteType: z.enum(["UP", "DOWN"]),
-});
+ answerId: z.string(),
+ voteType: z.enum(["UP", "DOWN"]),
+})
-export type AnswerVoteRequest = z.infer;
+export type AnswerVoteRequest = z.infer
export const AnswerAcceptedValidator = z.object({
- answerId: z.string(),
- accepted: z.boolean(),
-});
+ answerId: z.string(),
+ accepted: z.boolean(),
+})
-export type AnswerAcceptedRequest = z.infer;
+export type AnswerAcceptedRequest = z.infer
diff --git a/src/middleware.ts b/src/middleware.ts
index ba6ebeb..dc1aeff 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -1,21 +1,17 @@
import { withAuth } from "next-auth/middleware"
-export default withAuth(
- function middleware (req) {
- },
- {
- callbacks: {
- authorized: ({ req, token }) => {
- if (
- req.nextUrl.pathname &&
- req.nextUrl.pathname.startsWith('/') &&
- !req.nextUrl.pathname.startsWith('/sign-in') &&
- token === null
- ) {
- return false
- }
- return true
+export default withAuth(function middleware(req) {}, {
+ callbacks: {
+ authorized: ({ req, token }) => {
+ if (
+ req.nextUrl.pathname &&
+ req.nextUrl.pathname.startsWith("/") &&
+ !req.nextUrl.pathname.startsWith("/sign-in") &&
+ token === null
+ ) {
+ return false
}
- }
- }
-)
\ No newline at end of file
+ return true
+ },
+ },
+})
diff --git a/src/styles/editor.css b/src/styles/editor.css
index cf6c4b0..d7e0bd3 100644
--- a/src/styles/editor.css
+++ b/src/styles/editor.css
@@ -8,9 +8,9 @@
.dark .cdx-button,
.dark .ce-popover,
.dark .ce-toolbar__plus:hover {
- background: theme('colors.popover.DEFAULT');
+ background: theme("colors.popover.DEFAULT");
color: inherit;
- border-color: theme('colors.border');
+ border-color: theme("colors.border");
}
.dark .ce-inline-tool,
@@ -23,18 +23,18 @@
.dark .ce-popover__item-icon,
.dark .ce-conversion-tool__icon {
- background-color: theme('colors.muted.DEFAULT');
+ background-color: theme("colors.muted.DEFAULT");
box-shadow: none;
}
.dark .cdx-search-field {
- border-color: theme('colors.border');
- background: theme('colors.input');
+ border-color: theme("colors.border");
+ background: theme("colors.input");
color: inherit;
}
.dark ::selection {
- background: theme('colors.accent.DEFAULT');
+ background: theme("colors.accent.DEFAULT");
}
.dark .cdx-settings-button:hover,
@@ -47,12 +47,12 @@
.dark .ce-popover__item:hover,
.dark .ce-conversion-tool:hover,
.dark .ce-toolbar__settings-btn:hover {
- background-color: theme('colors.accent.DEFAULT');
- color: theme('colors.accent.foreground');
+ background-color: theme("colors.accent.DEFAULT");
+ color: theme("colors.accent.foreground");
}
.dark .cdx-notify--error {
- background: theme('colors.destructive.DEFAULT') !important;
+ background: theme("colors.destructive.DEFAULT") !important;
}
.dark .cdx-notify__cross::after,
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 0b46ea1..0f0ff2a 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -1,7 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-
+
@layer base {
:root {
--background: 0 0% 100%;
@@ -9,63 +9,63 @@
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
-
+
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
-
+
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
-
+
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
-
+
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
-
+
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
-
+
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
-
+
--radius: 0.5rem;
}
-
+
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
-
+
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
-
+
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
-
+
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
-
+
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
-
+
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
-
+
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
-
+
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
-
+
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
-
+
@layer base {
* {
@apply border-border;
@@ -73,4 +73,4 @@
body {
@apply bg-background text-foreground;
}
-}
\ No newline at end of file
+}
diff --git a/src/types/db.d.ts b/src/types/db.d.ts
index a4c8467..85fa0d9 100644
--- a/src/types/db.d.ts
+++ b/src/types/db.d.ts
@@ -1,26 +1,35 @@
-import { Post, Subject, PostVote, QuestionVote, User, Comment, Question, Answer } from "@prisma/client";
+import {
+ Post,
+ Subject,
+ PostVote,
+ QuestionVote,
+ User,
+ Comment,
+ Question,
+ Answer,
+} from "@prisma/client"
export type ExtendedPost = Post & {
- subject: Subject;
- votes: PostVote[];
- author: User;
- comments: Comment[];
-};
+ subject: Subject
+ votes: PostVote[]
+ author: User
+ comments: Comment[]
+}
export type ExtendedQuestion = Question & {
- subject: Subject;
- votes: QuestionVote[];
- author: User;
- answers: Answer[];
-};
+ subject: Subject
+ votes: QuestionVote[]
+ author: User
+ answers: Answer[]
+}
export type ExtendedAnswer = Answer & {
- question: Question;
- votes: Vote[];
- author: User;
-};
+ question: Question
+ votes: Vote[]
+ author: User
+}
export type ExtendedComment = Comment & {
- votes: Vote[];
- author: User;
-};
\ No newline at end of file
+ votes: Vote[]
+ author: User
+}
diff --git a/src/types/editor.d.ts b/src/types/editor.d.ts
index 45abd7d..f9a7293 100644
--- a/src/types/editor.d.ts
+++ b/src/types/editor.d.ts
@@ -4,4 +4,4 @@ declare module "@editorjs/list"
declare module "@editorjs/code"
declare module "@editorjs/link"
declare module "@editorjs/inline-code"
-declare module "@editorjs/image"
\ No newline at end of file
+declare module "@editorjs/image"
diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts
index 8396547..7a9bdf8 100644
--- a/src/types/next-auth.d.ts
+++ b/src/types/next-auth.d.ts
@@ -1,21 +1,21 @@
-import type { Session, User } from "next-auth";
-import type { JWT } from "next-auth/jwt";
+import type { Session, User } from "next-auth"
+import type { JWT } from "next-auth/jwt"
-type UserId = string;
+type UserId = string
declare module "next-auth/jwt" {
- interface JWT {
- id: UserId;
- username?: string | null;
- }
+ interface JWT {
+ id: UserId
+ username?: string | null
+ }
}
declare module "next-auth" {
- interface Session {
- user: User & {
- id: UserId;
- username?: string | null;
- generacio?: string | null;
- };
- }
+ interface Session {
+ user: User & {
+ id: UserId
+ username?: string | null
+ generacio?: string | null
+ }
+ }
}
diff --git a/src/types/redis.d.ts b/src/types/redis.d.ts
index eab1dea..0afe0f4 100644
--- a/src/types/redis.d.ts
+++ b/src/types/redis.d.ts
@@ -1,36 +1,36 @@
-import { PostVote, QuestionVote, AnswerVote, CommentVote } from "@prisma/client";
+import { PostVote, QuestionVote, AnswerVote, CommentVote } from "@prisma/client"
export type CachedPost = {
- id: string;
- title: string;
- authorName: string;
- content: string;
- currentVote: PostVote["type"] | null;
- createdAt: Date;
-};
+ id: string
+ title: string
+ authorName: string
+ content: string
+ currentVote: PostVote["type"] | null
+ createdAt: Date
+}
export type CachedComment = {
- id: string;
- authorName: string;
- content: string;
- currentVote: CommentVote["type"] | null;
- createdAt: Date;
-};
+ id: string
+ authorName: string
+ content: string
+ currentVote: CommentVote["type"] | null
+ createdAt: Date
+}
export type CachedQuestion = {
- id: string;
- title: string;
- authorName: string;
- content: string;
- currentVote: QuestionVote["type"] | null;
- createdAt: Date;
-};
+ id: string
+ title: string
+ authorName: string
+ content: string
+ currentVote: QuestionVote["type"] | null
+ createdAt: Date
+}
export type CachedAnswer = {
- id: string;
- title: string;
- authorName: string;
- content: string;
- currentVote: AnswerVote["type"] | null;
- createdAt: Date;
-};
+ id: string
+ title: string
+ authorName: string
+ content: string
+ currentVote: AnswerVote["type"] | null
+ createdAt: Date
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 9e800bd..20df21d 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,81 +1,81 @@
-const { fontFamily } = require('tailwindcss/defaultTheme')
+const { fontFamily } = require("tailwindcss/defaultTheme")
/** @type {import('tailwindcss').Config} */
module.exports = {
- darkMode: ['class'],
- content: ['./src/app/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'],
+ darkMode: ["class"],
+ content: ["./src/app/**/*.{ts,tsx}", "./src/components/**/*.{ts,tsx}"],
theme: {
container: {
center: true,
- padding: '2rem',
+ padding: "2rem",
screens: {
- '2xl': '1400px',
+ "2xl": "1400px",
},
},
extend: {
backgroundImage: {
- 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
- 'gradient-conic':
- 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
+ "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
+ "gradient-conic":
+ "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
colors: {
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))',
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
},
secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))',
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))',
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
},
muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))',
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
},
accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))',
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
},
popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))',
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
},
card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))',
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`,
- sm: 'calc(var(--radius) - 4px)',
+ sm: "calc(var(--radius) - 4px)",
},
fontFamily: {
- sans: ['var(--font-sans)', ...fontFamily.sans],
+ sans: ["var(--font-sans)", ...fontFamily.sans],
},
keyframes: {
- 'accordion-down': {
+ "accordion-down": {
from: { height: 0 },
- to: { height: 'var(--radix-accordion-content-height)' },
+ to: { height: "var(--radix-accordion-content-height)" },
},
- 'accordion-up': {
- from: { height: 'var(--radix-accordion-content-height)' },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
- 'accordion-down': 'accordion-down 0.2s ease-out',
- 'accordion-up': 'accordion-up 0.2s ease-out',
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
},
},
},
- plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
+ plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 84287e8..1ce0389 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -3,11 +3,11 @@ import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
- './pages/**/*.{ts,tsx}',
- './components/**/*.{ts,tsx}',
- './app/**/*.{ts,tsx}',
- './src/**/*.{ts,tsx}',
- ],
+ "./pages/**/*.{ts,tsx}",
+ "./components/**/*.{ts,tsx}",
+ "./app/**/*.{ts,tsx}",
+ "./src/**/*.{ts,tsx}",
+ ],
prefix: "",
theme: {
container: {
@@ -77,4 +77,4 @@ const config = {
plugins: [require("tailwindcss-animate")],
} satisfies Config
-export default config
\ No newline at end of file
+export default config
diff --git a/tsconfig.json b/tsconfig.json
index d53a6ed..e128fc0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,6 +23,12 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "prisma/accountVerify.js"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "prisma/accountVerify.js"
+ ],
"exclude": ["node_modules"]
}
diff --git a/yarn.lock b/yarn.lock
index 12743c1..ef89138 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1274,6 +1274,13 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ansi-escapes@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.0.tgz#8a13ce75286f417f1963487d86ba9f90dccf9947"
+ integrity sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==
+ dependencies:
+ type-fest "^3.0.0"
+
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
@@ -1291,7 +1298,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
-ansi-styles@^6.1.0:
+ansi-styles@^6.0.0, ansi-styles@^6.1.0, ansi-styles@^6.2.1:
version "6.2.1"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
@@ -1658,6 +1665,11 @@ canvas@^2.11.2:
nan "^2.17.0"
simple-get "^3.0.3"
+chalk@5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
+ integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
+
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
@@ -1698,6 +1710,21 @@ class-variance-authority@^0.6.0:
dependencies:
clsx "1.2.1"
+cli-cursor@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea"
+ integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==
+ dependencies:
+ restore-cursor "^4.0.0"
+
+cli-truncate@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a"
+ integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==
+ dependencies:
+ slice-ansi "^5.0.0"
+ string-width "^7.0.0"
+
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
@@ -1758,6 +1785,11 @@ color@^4.2.3:
color-convert "^2.0.1"
color-string "^1.9.0"
+colorette@^2.0.20:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
@@ -1765,6 +1797,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
+commander@11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
+ integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
+
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@@ -1833,7 +1870,7 @@ date-fns@^2.30.0:
dependencies:
"@babel/runtime" "^7.21.0"
-debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
+debug@4, debug@4.3.4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -2388,11 +2425,31 @@ esutils@^2.0.2:
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+eventemitter3@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+ integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
events@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+execa@8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
+ integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^8.0.1"
+ human-signals "^5.0.0"
+ is-stream "^3.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^5.1.0"
+ onetime "^6.0.0"
+ signal-exit "^4.1.0"
+ strip-final-newline "^3.0.0"
+
execa@^5.0.0:
version "5.1.1"
resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
@@ -2614,6 +2671,11 @@ gauge@^3.0.0:
strip-ansi "^6.0.1"
wide-align "^1.1.2"
+get-east-asian-width@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e"
+ integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==
+
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz"
@@ -2635,6 +2697,11 @@ get-stream@^6.0.0, get-stream@^6.0.1:
resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+get-stream@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2"
+ integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==
+
get-symbol-description@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz"
@@ -2863,6 +2930,16 @@ human-signals@^4.3.0:
resolved "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz"
integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==
+human-signals@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
+ integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
+
+husky@^9.0.11:
+ version "9.0.11"
+ resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9"
+ integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==
+
hyphen@^1.6.4:
version "1.10.4"
resolved "https://registry.npmjs.org/hyphen/-/hyphen-1.10.4.tgz"
@@ -3018,6 +3095,18 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+is-fullwidth-code-point@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
+ integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
+
+is-fullwidth-code-point@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704"
+ integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==
+ dependencies:
+ get-east-asian-width "^1.0.0"
+
is-generator-function@^1.0.10:
version "1.0.10"
resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz"
@@ -3262,6 +3351,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
+lilconfig@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc"
+ integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==
+
lilconfig@^2.0.5, lilconfig@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"
@@ -3272,6 +3366,34 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+lint-staged@^15.2.2:
+ version "15.2.2"
+ resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.2.tgz#ad7cbb5b3ab70e043fa05bff82a09ed286bc4c5f"
+ integrity sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==
+ dependencies:
+ chalk "5.3.0"
+ commander "11.1.0"
+ debug "4.3.4"
+ execa "8.0.1"
+ lilconfig "3.0.0"
+ listr2 "8.0.1"
+ micromatch "4.0.5"
+ pidtree "0.6.0"
+ string-argv "0.3.2"
+ yaml "2.3.4"
+
+listr2@8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.0.1.tgz#4d3f50ae6cec3c62bdf0e94f5c2c9edebd4b9c34"
+ integrity sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==
+ dependencies:
+ cli-truncate "^4.0.0"
+ colorette "^2.0.20"
+ eventemitter3 "^5.0.1"
+ log-update "^6.0.0"
+ rfdc "^1.3.0"
+ wrap-ansi "^9.0.0"
+
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
@@ -3299,6 +3421,17 @@ lodash.merge@^4.6.2:
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+log-update@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59"
+ integrity sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==
+ dependencies:
+ ansi-escapes "^6.2.0"
+ cli-cursor "^4.0.0"
+ slice-ansi "^7.0.0"
+ strip-ansi "^7.1.0"
+ wrap-ansi "^9.0.0"
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
@@ -3365,7 +3498,7 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-micromatch@^4.0.4, micromatch@^4.0.5:
+micromatch@4.0.5, micromatch@^4.0.4, micromatch@^4.0.5:
version "4.0.5"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
@@ -3711,7 +3844,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
-onetime@^5.1.2:
+onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
@@ -3859,6 +3992,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+pidtree@0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"
+ integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==
+
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
@@ -3974,6 +4112,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+prettier@^3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
+ integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
+
pretty-format@^3.8.0:
version "3.8.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
@@ -4240,6 +4383,14 @@ resolve@^2.0.0-next.4:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+restore-cursor@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9"
+ integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==
+ dependencies:
+ onetime "^5.1.0"
+ signal-exit "^3.0.2"
+
restructure@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/restructure/-/restructure-3.0.0.tgz"
@@ -4250,6 +4401,11 @@ reusify@^1.0.4:
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+rfdc@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.1.tgz#2b6d4df52dffe8bb346992a10ea9451f24373a8f"
+ integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==
+
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
@@ -4388,12 +4544,12 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7:
+signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
-signal-exit@^4.0.1:
+signal-exit@^4.0.1, signal-exit@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
@@ -4438,6 +4594,22 @@ slash@^4.0.0:
resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz"
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+slice-ansi@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
+ integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
+ dependencies:
+ ansi-styles "^6.0.0"
+ is-fullwidth-code-point "^4.0.0"
+
+slice-ansi@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"
+ integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==
+ dependencies:
+ ansi-styles "^6.2.1"
+ is-fullwidth-code-point "^5.0.0"
+
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
@@ -4469,6 +4641,11 @@ streamx@^2.15.0:
fast-fifo "^1.1.0"
queue-tick "^1.0.1"
+string-argv@0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
+ integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
+
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3:
name string-width-cjs
version "4.2.3"
@@ -4488,6 +4665,15 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
+string-width@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a"
+ integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==
+ dependencies:
+ emoji-regex "^10.3.0"
+ get-east-asian-width "^1.0.0"
+ strip-ansi "^7.1.0"
+
string.prototype.matchall@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz"
@@ -4543,7 +4729,7 @@ string_decoder@^1.1.1:
dependencies:
ansi-regex "^5.0.1"
-strip-ansi@^7.0.1:
+strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
@@ -4831,6 +5017,11 @@ type-fest@^0.20.2:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+type-fest@^3.0.0:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
+ integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==
+
typed-array-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz"
@@ -5087,6 +5278,15 @@ wrap-ansi@^8.1.0:
string-width "^5.0.1"
strip-ansi "^7.0.1"
+wrap-ansi@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
+ integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
+ dependencies:
+ ansi-styles "^6.2.1"
+ string-width "^7.0.0"
+ strip-ansi "^7.1.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
@@ -5097,6 +5297,11 @@ yallist@^4.0.0:
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+yaml@2.3.4:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
+ integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
+
yaml@^2.1.1:
version "2.3.1"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz"