-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/Algoture/plura
- Loading branch information
Showing
14 changed files
with
448 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { zValidator } from "@hono/zod-validator"; | ||
import { feedbackSchema } from "@repo/types"; | ||
import { Hono } from "hono"; | ||
import { auth } from "@plura/auth"; | ||
import { prisma } from "@plura/db"; | ||
import { checkLogin } from "@/app/actions/checkLogin"; | ||
import { checkAdmin } from "@/app/actions/checkAdmin"; | ||
|
||
const app = new Hono() | ||
.post("/", zValidator("json", feedbackSchema), async (c) => { | ||
const session = await auth.api.getSession({ | ||
headers: c.req.raw.headers, | ||
}); | ||
if (!session?.user.id) { | ||
return c.json({ message: "Unauthorized", status: 401 }, 401); | ||
} | ||
const body = c.req.valid("json"); | ||
try { | ||
await prisma.feedback.create({ | ||
data: { | ||
userId: session.user.id, | ||
desc: body.desc, | ||
emotion: body.emotion, | ||
}, | ||
}); | ||
return c.json( | ||
{ | ||
message: "Successfully submitted!", | ||
}, | ||
200, | ||
); | ||
} catch (error) { | ||
console.error("Error creating feedback:", error); | ||
return c.json( | ||
{ | ||
message: "Error submitting feedback", | ||
status: 500, | ||
}, | ||
500, | ||
); | ||
} | ||
}) | ||
.use(checkLogin, checkAdmin) | ||
.get("/all", async (c) => { | ||
try { | ||
const feedbacks = await prisma.feedback.findMany(); | ||
return c.json( | ||
{ | ||
message: "All feedback retrieved successfully", | ||
data: feedbacks, | ||
}, | ||
200, | ||
); | ||
} catch (error) { | ||
console.error("Error fetching all feedback:", error); | ||
return c.json( | ||
{ | ||
message: "Error fetching feedback", | ||
status: 500, | ||
}, | ||
500, | ||
); | ||
} | ||
}) | ||
.get("/:id", async (c) => { | ||
const userId = c.req.param("id"); | ||
if (!userId) { | ||
return c.json( | ||
{ | ||
message: "User ID is required", | ||
status: 400, | ||
}, | ||
400, | ||
); | ||
} | ||
try { | ||
const feedbacks = await prisma.feedback.findMany({ | ||
where: { | ||
userId: userId, | ||
}, | ||
}); | ||
if (feedbacks.length === 0) { | ||
return c.json( | ||
{ | ||
message: "No feedback found for this user", | ||
status: 404, | ||
}, | ||
404, | ||
); | ||
} | ||
return c.json( | ||
{ | ||
message: "Feedback retrieved successfully", | ||
status: 200, | ||
data: feedbacks, | ||
}, | ||
200, | ||
); | ||
} catch (error) { | ||
console.error("Error fetching feedback:", error); | ||
return c.json( | ||
{ | ||
message: "Error fetching feedback", | ||
status: 500, | ||
}, | ||
500, | ||
); | ||
} | ||
}); | ||
|
||
export default app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"use server"; | ||
|
||
import { betterFetch } from "@better-fetch/fetch"; | ||
import { getSession } from "./session"; | ||
import { headers } from "next/headers"; | ||
|
||
const API_ENDPOINT = | ||
process.env.NODE_ENV === "production" | ||
? "https://api.plura.pro/" | ||
: "http://localhost:3001"; | ||
|
||
export const createFeedback = async ({ | ||
desc, | ||
emotion, | ||
}: { | ||
desc: string; | ||
emotion: string; | ||
}) => { | ||
const user = await getSession(); | ||
if (!user) { | ||
return; | ||
} | ||
const feedback = await betterFetch(`${API_ENDPOINT}/v1/feedback`, { | ||
method: "POST", | ||
body: { | ||
desc: desc, | ||
emotion: emotion, | ||
}, | ||
headers: { | ||
cookie: (await headers()).get("cookie") || "", | ||
}, | ||
}); | ||
|
||
return feedback; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* eslint-disable react/no-unescaped-entities */ | ||
"use client"; | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogDescription, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger, | ||
} from "@/components/ui/dialog"; | ||
import { toast } from "sonner"; | ||
import { Profanity } from "profanity-validator"; | ||
import { Frown, Meh, MessageSquare, Smile } from "lucide-react"; | ||
import { Textarea } from "../ui/textarea"; | ||
import { SubmitHandler, useForm } from "react-hook-form"; | ||
import { z } from "zod"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { useState } from "react"; | ||
import { createFeedback } from "@/actions/feedback"; | ||
|
||
const profanity = new Profanity({ | ||
customWords: [""], | ||
heat: 0.9, | ||
}); | ||
|
||
const profanityCheck = async (value: string) => { | ||
const result = await profanity.validateField(value); | ||
return result.isValid; | ||
}; | ||
|
||
const postSchema = z.object({ | ||
description: z | ||
.string() | ||
.min(10, "Description must be at least 10 characters") | ||
.refine(async (val) => await profanityCheck(val), { | ||
message: "Inappropriate content detected in description", | ||
}), | ||
}); | ||
type PostSchema = z.infer<typeof postSchema>; | ||
|
||
export function FeedbackModal() { | ||
const [selectedEmo, setSelectedEmo] = useState<"happy" | "idle" | "sad">( | ||
"happy", | ||
); | ||
|
||
const form = useForm<PostSchema>({ | ||
resolver: zodResolver(postSchema), | ||
}); | ||
|
||
const onSubmit: SubmitHandler<PostSchema> = async (data) => { | ||
try { | ||
const validatedData = await postSchema.parseAsync({ ...data }); | ||
const res = await createFeedback({ | ||
desc: validatedData.description, | ||
emotion: selectedEmo, | ||
}); | ||
return res; | ||
} catch (error) { | ||
toast.error(`Error in submiting feeback !Please try again `); | ||
} finally { | ||
toast.success(`Thanks for your feedback!`); | ||
} | ||
}; | ||
|
||
return ( | ||
<Dialog> | ||
<DialogTrigger asChild> | ||
<Button variant="default"> | ||
{" "} | ||
<MessageSquare /> Feedback{" "} | ||
</Button> | ||
</DialogTrigger> | ||
<DialogContent className="max-w-[425px] md:min-w-[480px] border"> | ||
<DialogHeader className="!text-center w-full pt-5 pb-2"> | ||
<DialogTitle className="text-2xl text-primary"> | ||
Leave feedback | ||
</DialogTitle> | ||
<DialogDescription> | ||
We'd love to hear what went well or how we can improve the product | ||
experience. | ||
</DialogDescription> | ||
</DialogHeader> | ||
<Form {...form}> | ||
<form | ||
onSubmit={form.handleSubmit(onSubmit)} | ||
className="space-y-6 w-full max-w-md mx-auto" | ||
> | ||
<FormField | ||
control={form.control} | ||
name="description" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormControl> | ||
<Textarea | ||
{...field} | ||
placeholder="Can you ..." | ||
className="border min-h-[100px]" | ||
/> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<div className="grid gap-4 py-4"> | ||
<div className="flex gap-2"> | ||
<Button | ||
size="icon" | ||
variant="secondary" | ||
className={`hover:bg-secondary transition-all duration-200 ${selectedEmo === "happy" ? "border-primary border-[1px]" : ""}`} | ||
onClick={() => setSelectedEmo("happy")} | ||
type="button" | ||
> | ||
<Smile /> | ||
</Button> | ||
<Button | ||
size="icon" | ||
variant="secondary" | ||
className={`hover:bg-secondary transition-all duration-200 ${selectedEmo === "idle" ? "border-primary border-[1px]" : ""}`} | ||
onClick={() => setSelectedEmo("idle")} | ||
type="button" | ||
> | ||
<Meh /> | ||
</Button> | ||
<Button | ||
size="icon" | ||
variant="secondary" | ||
className={`hover:bg-secondary transition-all duration-200 ${selectedEmo === "sad" ? "border-primary border-[1px]" : ""}`} | ||
onClick={() => setSelectedEmo("sad")} | ||
type="button" | ||
> | ||
<Frown /> | ||
</Button> | ||
</div> | ||
</div> | ||
<Button type="submit" className="w-full"> | ||
{form.formState.isSubmitting ? "Checking..." : "Submit"} | ||
</Button> | ||
</form> | ||
</Form> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.