Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Algoture/plura
Browse files Browse the repository at this point in the history
  • Loading branch information
Algoture committed Jan 12, 2025
2 parents 3d2003f + b32636a commit 141fe57
Show file tree
Hide file tree
Showing 14 changed files with 448 additions and 60 deletions.
111 changes: 111 additions & 0 deletions apps/api/app/v1/[[...route]]/feedback.ts
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;
4 changes: 3 additions & 1 deletion apps/api/app/v1/[[...route]]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import health from "./health";
import user from "./user";
import project from "./project";
import contributors from "./contributors";
import feedback from "./feedback";
import { cors } from "hono/cors";
import workspace from "./workspace";
import { Ratelimit } from "@upstash/ratelimit";
Expand Down Expand Up @@ -75,7 +76,8 @@ app.route("/auth", auth);
app.route("/user", user);
app.route("/contributors", contributors);
app.route("/workspace", workspace);
app.route("project", project);
app.route("/project", project);
app.route("/feedback", feedback);
app.route("/workflow", workflow);

const GET = handle(app);
Expand Down
35 changes: 35 additions & 0 deletions apps/app/actions/feedback.ts
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;
};
151 changes: 151 additions & 0 deletions apps/app/components/custom/feedback-modal.tsx
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>
);
}
25 changes: 19 additions & 6 deletions apps/app/components/custom/infobar/infobar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import {
Expand All @@ -26,6 +25,8 @@ import { Check, ChevronsUpDown, Slash } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { FeedbackModal } from "../feedback-modal";
import { usePathname } from "next/navigation";

const frameworks = [
{
Expand Down Expand Up @@ -55,19 +56,21 @@ export default function Infobar() {
const [openPopover1, setOpenPopover1] = useState(false);
const [openPopover2, setOpenPopover2] = useState(false);
const [value, setValue] = useState("");
const pathname = usePathname()
.replace(/^\/|\/$/g, "")
.split("/");

useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 0);
};

window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);

return (
<nav
className={`flex flex-col w-full items-start sticky top-0 right-0 bg-background transition-all duration-200 ${
className={`flex w-full items-center sticky top-0 right-0 bg-background transition-all duration-200 ${
isScrolled ? "shadow-sm z-10" : ""
}`}
>
Expand Down Expand Up @@ -190,12 +193,22 @@ export default function Infobar() {
<BreadcrumbSeparator>
<Slash className="h-4 w-4" />
</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
{pathname[0] && (
<BreadcrumbItem>
<Button
variant={"ghost"}
className="text-muted-foreground hover:text-primary selection-none p-2 h-6"
>
{pathname[0].charAt(0).toUpperCase() + pathname[0].slice(1)}
</Button>
</BreadcrumbItem>
)}
</BreadcrumbList>
</Breadcrumb>
</div>
<div>
<FeedbackModal />
</div>
</nav>
);
}
Loading

0 comments on commit 141fe57

Please sign in to comment.