Skip to content

Commit

Permalink
adding the ability for users to leave feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
webdevcody committed Mar 15, 2024
1 parent 22e8204 commit 721c34a
Show file tree
Hide file tree
Showing 10 changed files with 539 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
NEXT_PUBLIC_PLANNER_AI_FEEDBACK_URL: ${{ secrets.NEXT_PUBLIC_PLANNER_AI_FEEDBACK_URL }}
NEXT_PUBLIC_PLANNER_AI_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_PLANNER_AI_PROJECT_ID }}
run: |
npx sst deploy --stage prod
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@clerk/nextjs": "^4.29.3",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-progress": "^1.0.3",
Expand Down Expand Up @@ -46,6 +47,7 @@
"svix": "^1.15.0",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.0",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
182 changes: 182 additions & 0 deletions src/app/feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React from "react";

import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";

import { useToast } from "@/components/ui/use-toast";
import useMediaQuery from "@/hooks/use-media-query";

type FeedbackFormProps = {
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
};

const feedbackSchema = z.object({
name: z.string().min(2, {
message: "Name is required",
}),
feedback: z.string().min(1, { message: "Feedback is required" }),
});

type FeedbackProps = {
triggerContent: React.ReactNode;
};

export default function Feedback({ triggerContent }: FeedbackProps) {
const [open, setOpen] = React.useState(false);

const { isMobile } = useMediaQuery();

const description =
"We value your feedback. How can we improve your experience?";

if (isMobile) {
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>{triggerContent}</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="text-left">
<DrawerTitle>Feedback</DrawerTitle>
<DrawerDescription>{description}</DrawerDescription>
</DrawerHeader>
<FeedbackForm setOpen={setOpen} />
<DrawerFooter className="pt-2">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{triggerContent}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Feedback</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<FeedbackForm setOpen={setOpen} />
</DialogContent>
</Dialog>
);
}

export function FeedbackForm({ setOpen }: FeedbackFormProps) {
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({
resolver: zodResolver(feedbackSchema),
defaultValues: {
name: "",
feedback: "",
},
});
const { toast } = useToast();
const onSubmit = async (values: z.infer<typeof feedbackSchema>) => {
try {
await fetch(
`${process.env.NEXT_PUBLIC_PLANNER_AI_FEEDBACK_URL}/api/v1/feedback`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: values.name,
feedback: values.feedback,
projectId: process.env.NEXT_PUBLIC_PLANNER_AI_PROJECT_ID,
}),
}
);
setOpen(false);
toast({
title: "Feedback submitted",
description: "Thank you for your feedback",
});
} catch (error) {
console.error("Failed to send feedback:", error);
}
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className={cn("grid items-start gap-4 px-4 sm:px-0")}
>
<div className="grid gap-2">
<Label htmlFor="name">
Your name <span className="text-red-600">*</span>
</Label>
<Controller
name="name"
control={control}
render={({ field }) => (
<Input {...field} type="name" id="name" placeholder="Jane Doe" />
)}
/>
{errors.name && typeof errors.name.message === "string" && (
<p className="text-red-600">{errors.name.message}</p>
)}
</div>
<div className="grid gap-2">
<Label htmlFor="feedback">
Your feedback <span className="text-red-600">*</span>
</Label>
<Controller
name="feedback"
control={control}
render={({ field }) => (
<textarea
{...field}
className="w-full h-32 text-sm border rounded-lg flex border-input bg-background px-3 py-2 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
placeholder="Tell us how we can improve our product"
/>
)}
/>
{errors.feedback && typeof errors.feedback.message === "string" && (
<p className="text-red-600">{errors.feedback.message}</p>
)}
</div>
<div className="flex justify-end">
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending feedback...
</>
) : (
"Send feedback"
)}
</Button>
</div>
</form>
);
}
15 changes: 13 additions & 2 deletions src/app/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import Link from "next/link";
import { useSession } from "@/lib/utils";
import MobileNav, { MenuToggle, useMobileNavState } from "./mobile-nav";
import Image from "next/image";
import { BellIcon } from "lucide-react";
import { BellIcon, MessageCircleHeart } from "lucide-react";
import { useQuery } from "convex/react";
import { api } from "../../convex/_generated/api";
import Feedback from "./feedback";

const NotificationIcon = () => {
const hasUnread = useQuery(api.notification.hasUnread);
Expand Down Expand Up @@ -44,7 +45,9 @@ export function Header() {
width="40"
height="40"
/>
<span className="text-xs md:text-base">ThumbnailCritique</span>
<span className="text-xs md:text-base hidden sm:block">
ThumbnailCritique
</span>
</Link>

<div className="gap-4 hidden md:flex md:gap-8 text-xs md:text-base">
Expand Down Expand Up @@ -74,6 +77,14 @@ export function Header() {
<>
{isAuthenticated && (
<>
<Feedback
triggerContent={
<button>
<MessageCircleHeart />
</button>
}
/>

<NotificationIcon />

<UserButton />
Expand Down
Loading

0 comments on commit 721c34a

Please sign in to comment.