Skip to content

Commit

Permalink
Create Feedback page
Browse files Browse the repository at this point in the history
  • Loading branch information
michho8 committed May 11, 2024
1 parent f167a29 commit d561af0
Show file tree
Hide file tree
Showing 14 changed files with 609 additions and 90 deletions.
8 changes: 7 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.0.5",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"camaro": "^6.2.2",
Expand All @@ -27,15 +29,19 @@
"react": "^18",
"react-dom": "^18",
"react-idle-timer": "^5.7.2",
"react-hook-form": "^7.50.1",
"react-router-dom": "^6.22.1",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"uniqid": "^5.4.0"
"uniqid": "^5.4.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@stylistic/eslint-plugin": "^1.6.1",
"@swc/core": "^1.3.106",
"@testing-library/jest-dom": "^6.3.0",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.11",
"@types/node": "^20",
"@types/react": "^18",
Expand Down
122 changes: 122 additions & 0 deletions ui/src/app/feedback/components/FeedbackForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client"
import React from 'react';
import {Input} from "@/components/ui/input";
import {Button} from "@/components/ui/button"
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form"
import {z} from "zod"
import {zodResolver} from "@hookform/resolvers/zod"
import {useForm} from "react-hook-form"
import {EmailResult, Feedback, sendFeedback} from "@/services/EmailService";

const FeedbackForm = ({ currUser, setShowSuccessAlert, setShowErrorAlert } : { currUser:string, setShowSuccessAlert:any, setShowErrorAlert:any }) => {

const formSchema = z.object({
type: z.string(),
name: z.optional(z.string()),
email: z.string().email(),
message: z.string().min(15),
})

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
type: "General",
email: currUser + "@hawaii.edu"
}
});

async function onSubmit(values: z.infer<typeof formSchema>) {
const input: Feedback = {
type: values.type,
name: values.name,
email: values.email,
message: values.message,
exceptionMessage: "test exception message"
};
const results = await sendFeedback(input);
const emailResult: EmailResult = {
resultCode: results.resultCode,
recipient: results.recipient,
from: results.from,
subject: results.subject,
text: results.text
};

(emailResult.resultCode == "SUCCESS") ? setShowSuccessAlert(true) : setShowErrorAlert(true);

if (emailResult.resultCode == "SUCCESS") {
setShowSuccessAlert(true);
} else {
setShowErrorAlert(true);
}

//will break without curly braces
form.reset({});
}

return (
<Form {...form}>
<>
<div className="col-span-7 pl-5 pt-5">
<form id="feedbackForm" onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField control={form.control} name="type"
render={({field}) => (
<FormItem>
<FormLabel htmlFor="type" className="text-base">Feedback Type:
<span className="text-red-500">*</span></FormLabel>
<FormControl>
<select id="type" className="w-full px-3 py-1 border border-gray-300 rounded-md focus:accent-blue-500 focus:border-blue-500" {...field}>
<option value="General" defaultValue="true">General</option>
<option value="Problem">Problem</option>
<option value="Feature">Feature</option>
<option value="Question">Question</option>
</select>
</FormControl>
</FormItem>
)}
/>
<FormField control={form.control} name="name"
render={({field}) => (
<FormItem>
<FormLabel htmlFor="name" className="text-base">Your Name (Optional):</FormLabel>
<FormControl>
<Input id="name" className="text-base" placeholder="John Doe" {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField control={form.control} name="email"
render={({field}) => (
<FormItem>
<FormLabel htmlFor="email" className="text-base">Email Address:
<span className="text-red-500">*</span></FormLabel>
<FormControl>
<Input id="email" className="text-base" placeholder="Enter your email here" {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField control={form.control} name="message"
render={({field}) => (
<FormItem>
<FormLabel htmlFor="message" className="text-base">Your Feedback:
<span className="text-red-500">*</span></FormLabel>
<FormControl>
<textarea id="message" className="w-full px-3 py-1 border border-gray-300 rounded-md" rows={6} {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<Button className="text-white bg-uh-teal hover:bg-green-blue focus:ring-4 focus:ring-blue-300 rounded-md text-base dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 shadow-2xl"
type="submit">Submit</Button>
</form>
</div>
</>
</Form>
)
}

export default FeedbackForm;
40 changes: 40 additions & 0 deletions ui/src/app/feedback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client"
import React, {useState} from 'react';
import ErrorAlert from "@/components/layout/alerts/ErrorAlert";
import SuccessAlert from "@/components/layout/alerts/SuccessAlert";
import FeedbackForm from "@/app/feedback/components/FeedbackForm";
import {getCurrentUser} from "@/access/AuthenticationService";

async function getUser() {
return await getCurrentUser();
}
const Feedback = () => {
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const [showErrorAlert, setShowErrorAlert] = useState(false);

const [userUid, setUserUid] = useState<string>("something first");

getUser().then(res => setUserUid(res.uid))

return (
<>
<div id="successAlert" hidden={!showSuccessAlert} className="container">
<SuccessAlert isActive={showSuccessAlert}></SuccessAlert>
</div>
<div id="errorAlert" hidden={!showErrorAlert} className="container">
<ErrorAlert isActive={showErrorAlert}></ErrorAlert>
</div>
<div className="container grid grid-cols-12 pt-5 pb-4">
<div className="col-span-5 pt-5">
<h1 className="text-md-left text-3xl font-bold">Feedback</h1>
<p className="text-md-left text-xl mt-3">Helps us to understand where improvements are needed. Please let us know.</p>
</div>
<div className="col-span-7 pl-5 pt-5">
<FeedbackForm currUser={userUid} setShowSuccessAlert={setShowSuccessAlert} setShowErrorAlert={setShowErrorAlert} ></FeedbackForm>
</div>
</div>
</>
)
}

export default Feedback;
24 changes: 24 additions & 0 deletions ui/src/components/layout/alerts/ErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/components/ui/alert"
import {XCircle} from "lucide-react";
import React from "react";

export function ErrorAlert({ isActive } : { isActive:any }) {
return (
<section className="errorAlert">
{isActive ? (
<Alert variant="destructive" className="bg-red-100">
<XCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription className="text-base text-red-500">
Email feedback was unsuccessful. Please try again.
</AlertDescription>
</Alert>
) : null }
</section>
)}

export default ErrorAlert;
26 changes: 26 additions & 0 deletions ui/src/components/layout/alerts/SuccessAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/components/ui/alert"
import {CheckCircle2} from "lucide-react";
import React from "react";

export function SuccessAlert({ isActive } : { isActive:any }) {
return (
<section className="successAlert">
{isActive ? (
<Alert variant="default" className="bg-green-100">
<CheckCircle2 className="h-4 w-4" />
<AlertTitle>Thank you!</AlertTitle>
<AlertDescription>
<div className="text-base alert-success-test-color">
Your feedback has successfully been submitted.
</div>
</AlertDescription>
</Alert>
) : null }
</section>
)}

export default SuccessAlert;
84 changes: 40 additions & 44 deletions ui/src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,59 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from '@/components/ui/utils'
import { cn } from "@/components/ui/utils"

const alertVariants = cva(

`relative w-full rounded-lg border border-slate-200 p-4
[&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute
[&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 dark:border-slate-800 dark:[&>svg]:text-slate-50`,
{
variants: {
variant: {
default: 'bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50',
destructive:
`border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50
dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900`,
},
},
defaultVariants: {
variant: 'default',
},
}
"relative w-full rounded-lg border border-slate-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 dark:border-slate-800 dark:[&>svg]:text-slate-50",
{
variants: {
variant: {
default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
destructive:
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
},
},
defaultVariants: {
variant: "default",
},
}
)

const Alert = React.forwardRef<
HTMLDivElement,
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = 'Alert'
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = 'AlertTitle'
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = 'AlertDescription'
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }
Loading

0 comments on commit d561af0

Please sign in to comment.