Merge pull request #113 from learnhouse/swve/eng-106-add-feedback-button

Add feedback button
This commit is contained in:
Badr B 2023-09-11 21:16:06 +02:00 committed by GitHub
commit a01a0afea7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 15 deletions

View file

@ -2,7 +2,7 @@ import "@styles/globals.css";
import { Menu } from "@components/Objects/Menu/Menu"; import { Menu } from "@components/Objects/Menu/Menu";
import AuthProvider from "@components/Security/AuthProvider"; import AuthProvider from "@components/Security/AuthProvider";
export default async function RootLayout({ children, params }: { children: React.ReactNode , params :any}) { export default function RootLayout({ children, params }: { children: React.ReactNode , params :any}) {
return ( return (
<> <>
<AuthProvider> <AuthProvider>

View file

@ -1,16 +1,30 @@
'use client'; 'use client';
import React from "react"; import React, { use, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import { getUriWithOrg } from "@services/config/config"; import { getAPIUrl, getUriWithOrg } from "@services/config/config";
import { getOrganizationContextInfo, getOrganizationContextInfoWithoutCredentials } from "@services/organizations/orgs"; import { getOrganizationContextInfo, getOrganizationContextInfoWithoutCredentials } from "@services/organizations/orgs";
import ClientComponentSkeleton from "@components/Utils/ClientComp"; import ClientComponentSkeleton from "@components/Utils/ClientComp";
import { HeaderProfileBox } from "@components/Security/HeaderProfileBox"; import { HeaderProfileBox } from "@components/Security/HeaderProfileBox";
import MenuLinks from "./MenuLinks"; import MenuLinks from "./MenuLinks";
import { getOrgLogoMediaDirectory } from "@services/media/media"; import { getOrgLogoMediaDirectory } from "@services/media/media";
import { MessageSquareIcon } from "lucide-react";
import { Tooltip } from "@radix-ui/react-tooltip";
import ToolTip from "@components/StyledElements/Tooltip/Tooltip";
import Modal from "@components/StyledElements/Modal/Modal";
import FeedbackModal from "../Modals/Feedback/Feedback";
import useSWR from "swr";
import { swrFetcher } from "@services/utils/ts/requests";
export const Menu = async (props: any) => { export const Menu = (props: any) => {
const orgslug = props.orgslug; const orgslug = props.orgslug;
const org = await getOrganizationContextInfoWithoutCredentials(orgslug, { revalidate: 0, tags: ['organizations'] }); const [feedbackModal, setFeedbackModal] = React.useState(false);
const { data: org, error: error, isLoading } = useSWR(`${getAPIUrl()}orgs/slug/${orgslug}`, swrFetcher);
function closeFeedbackModal() {
setFeedbackModal(false);
}
return ( return (
<> <>
<div className="backdrop-blur-lg h-[60px] blur-3xl z-10" style={{ <div className="backdrop-blur-lg h-[60px] blur-3xl z-10" style={{
@ -25,7 +39,7 @@ export const Menu = async (props: any) => {
<div className="logo flex "> <div className="logo flex ">
<Link href={getUriWithOrg(orgslug, "/")}> <Link href={getUriWithOrg(orgslug, "/")}>
<div className="flex w-auto h-9 rounded-md items-center m-auto py-1 justify-center" > <div className="flex w-auto h-9 rounded-md items-center m-auto py-1 justify-center" >
{org.logo ? ( {org?.logo ? (
<img <img
src={`${getOrgLogoMediaDirectory(org.org_id, org?.logo)}`} src={`${getOrgLogoMediaDirectory(org.org_id, org?.logo)}`}
alt="Learnhouse" alt="Learnhouse"
@ -39,15 +53,24 @@ export const Menu = async (props: any) => {
</Link> </Link>
</div> </div>
<div className="links flex grow"> <div className="links flex grow">
<ClientComponentSkeleton>
<MenuLinks orgslug={orgslug} /> <MenuLinks orgslug={orgslug} />
</ClientComponentSkeleton>
</div> </div>
<div className="profile flex"> <div className="profile flex items-center space-x-2">
<ClientComponentSkeleton>
<Modal
isDialogOpen={feedbackModal}
onOpenChange={setFeedbackModal}
minHeight="sm"
dialogContent={<FeedbackModal></FeedbackModal>}
dialogTitle="Feedback"
dialogDescription="An issue? A suggestion? a bug ? Let us know!"
dialogTrigger={
<div className="feedback cursor-pointer block items-center h-fit p-2 rounded-2xl bg-orange-800 hover:bg-orange-900 text-orange-300 shadow">
<MessageSquareIcon size={12} />
</div>
}
/>
<HeaderProfileBox /> <HeaderProfileBox />
</ClientComponentSkeleton>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,88 @@
import FormLayout, { ButtonBlack, Flex, FormField, FormLabel, FormMessage, Input, Textarea } from "@components/StyledElements/Form/Form"
import { BarLoader } from "react-spinners"
import * as Form from '@radix-ui/react-form'
import React, { useState } from "react";
import * as Sentry from '@sentry/browser';
import { CheckCircleIcon } from "lucide-react";
import { AuthContext } from "@components/Security/AuthProvider";
import { randomUUID } from "crypto";
export const FeedbackModal = (user: any) => {
const auth: any = React.useContext(AuthContext);
const [isSubmitting, setIsSubmitting] = useState(false);
const [view, setView] = useState<"feedbackForm" | "success">("feedbackForm")
const [feedbackMessage, setFeedbackMessage] = useState("");
const handleSubmit = async (e: any) => {
e.preventDefault();
setIsSubmitting(true);
const user = auth.userInfo.user_object ? auth.userInfo.user_object : null;
const eventId = Sentry.captureMessage(`Feedback from ${user ? user.email : 'Anonymous'} - ${feedbackMessage}`);
const userFeedback = {
event_id: eventId,
name: user ? user.full_name : 'Anonymous',
email: user ? user.email : 'Anonymous',
comments: feedbackMessage,
}
Sentry.captureUserFeedback(userFeedback);
setIsSubmitting(false);
setView("success");
};
const handleFeedbackMessage = (event: React.ChangeEvent<any>) => {
setFeedbackMessage(event.target.value)
};
if (view == "feedbackForm") {
return (
<FormLayout onSubmit={handleSubmit}>
<FormField name="feedback-message">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Feedback message</FormLabel>
<FormMessage match="valueMissing">Please provide learning elements, separated by comma (,)</FormMessage>
</Flex>
<Form.Control asChild>
<Textarea style={{ height: 150, }} onChange={handleFeedbackMessage} required />
</Form.Control>
</FormField>
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
<Form.Submit asChild>
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
{isSubmitting ? <BarLoader cssOverride={{ borderRadius: 60, }} width={60} color="#ffffff" />
: "Submit Feedback"}
</ButtonBlack>
</Form.Submit>
</Flex>
</FormLayout>
)
} else {
return (
<div className="flex flex-col items-center space-y-5">
<div className="flex flex-col items-center space-y-5 pt-10">
<div className="flex items-center space-x-2">
<div className="text-9xl text-green-500">
<CheckCircleIcon></CheckCircleIcon>
</div>
<div className="text-3xl text-green-500">
<div>Thank you for your feedback!</div>
</div>
</div>
<div className="text-xl text-gray-500">
<div>We will take it into account.</div>
</div>
</div>
<div className="flex items-center space-x-2">
<ButtonBlack onClick={() => setView("feedbackForm")}>Send another feedback</ButtonBlack>
</div>
</div>
)
}
}
export default FeedbackModal

View file

@ -28,7 +28,7 @@ const Modal = (params: ModalParams) => (
<Dialog.Portal> <Dialog.Portal>
<DialogOverlay /> <DialogOverlay />
<DialogContent minHeight={params.minHeight}> <DialogContent minHeight={params.minHeight}>
<DialogTopBar> <DialogTopBar className='-space-y-1'>
<DialogTitle>{params.dialogTitle}</DialogTitle> <DialogTitle>{params.dialogTitle}</DialogTitle>
<DialogDescription> <DialogDescription>
{params.dialogDescription} {params.dialogDescription}