Redesign help menu item (#2710)
* Seperate help menu item and contact menu item * Add menu items * Install react-popover * Render contact only if support keys are present * Adjust contact support links * Add translations * Add embed changes * Adjust menu if helped is pressed * Add items to help menu * Change button color on selection * Create endpoint * Create feedback table * Create migration file * Write feedback to db * Remove logs * Add response message * Send feedback email * Disable submit if no rating and after submit * Add translations * Fix padding * Clean up * Clean up * Add user feedback email to .env example * Lint fixes and styles * Changed onClick function to a named function and fix style * Fix ids order * Removed commented code and changed textarea id and name * Fix id orders * Change to AND operator Co-authored-by: Omar López <zomars@me.com> * Add user relation to feedback Co-authored-by: Omar López <zomars@me.com> * Add migration files * Change rating to strings * Change rating to strings * Fix type errors * WIP success & error messages * Change success and error to boolans * Style messages * Add await Co-authored-by: Omar López <zomars@me.com> * Remove duplicate string * Refactor import statement Co-authored-by: Omar López <zomars@me.com> * Change opacity of emojis * added support@cal.com email for feedback * Add success toast * Update .env.example Co-authored-by: Omar López <zomars@me.com> * Add tCRP route * tCRP send email * tCRP send email Co-authored-by: Alan <alannnc@gmail.com> Co-authored-by: Omar López <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>pull/2829/head^2
parent
c8d6c0dbdd
commit
323524b77c
|
@ -70,6 +70,10 @@ NEXT_PUBLIC_ZENDESK_KEY=
|
||||||
# Help Scout Config
|
# Help Scout Config
|
||||||
NEXT_PUBLIC_HELPSCOUT_KEY=
|
NEXT_PUBLIC_HELPSCOUT_KEY=
|
||||||
|
|
||||||
|
# Inbox to send user feedback
|
||||||
|
SEND_FEEDBACK_EMAIL=
|
||||||
|
|
||||||
|
|
||||||
# This is used so we can bypass emails in auth flows for E2E testing
|
# This is used so we can bypass emails in auth flows for E2E testing
|
||||||
# Set it to "1" if you need to run E2E tests locally
|
# Set it to "1" if you need to run E2E tests locally
|
||||||
NEXT_PUBLIC_IS_E2E=
|
NEXT_PUBLIC_IS_E2E=
|
||||||
|
|
|
@ -10,12 +10,13 @@ import {
|
||||||
MapIcon,
|
MapIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
ViewGridIcon,
|
ViewGridIcon,
|
||||||
|
QuestionMarkCircleIcon,
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
import { UserPlan } from "@prisma/client";
|
import { UserPlan } from "@prisma/client";
|
||||||
import { SessionContextValue, signOut, useSession } from "next-auth/react";
|
import { SessionContextValue, signOut, useSession } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { Fragment, ReactNode, useEffect } from "react";
|
import React, { Fragment, ReactNode, useEffect, useState } from "react";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
import { useIsEmbed } from "@calcom/embed-core";
|
import { useIsEmbed } from "@calcom/embed-core";
|
||||||
|
@ -461,8 +462,10 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
|
const [helpOpen, setHelpOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown>
|
<Dropdown onOpenChange={() => setHelpOpen(false)}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button className="group flex w-full cursor-pointer appearance-none items-center text-left">
|
<button className="group flex w-full cursor-pointer appearance-none items-center text-left">
|
||||||
<span
|
<span
|
||||||
|
@ -504,96 +507,115 @@ function UserDropdown({ small }: { small?: boolean }) {
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent portalled={true}>
|
<DropdownMenuContent portalled={true}>
|
||||||
<DropdownMenuItem>
|
{helpOpen ? (
|
||||||
<a
|
<HelpMenuItem />
|
||||||
onClick={() => {
|
) : (
|
||||||
mutation.mutate({ away: !user?.away });
|
<>
|
||||||
utils.invalidateQueries("viewer.me");
|
<DropdownMenuItem>
|
||||||
}}
|
<a
|
||||||
className="flex min-w-max cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
|
onClick={() => {
|
||||||
<MoonIcon
|
mutation.mutate({ away: !user?.away });
|
||||||
className={classNames(
|
utils.invalidateQueries("viewer.me");
|
||||||
user?.away
|
}}
|
||||||
? "text-purple-500 group-hover:text-purple-700"
|
className="flex min-w-max cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
|
||||||
: "text-gray-500 group-hover:text-gray-700",
|
<MoonIcon
|
||||||
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
|
className={classNames(
|
||||||
)}
|
user?.away
|
||||||
aria-hidden="true"
|
? "text-purple-500 group-hover:text-purple-700"
|
||||||
/>
|
: "text-gray-500 group-hover:text-gray-700",
|
||||||
{user?.away ? t("set_as_free") : t("set_as_away")}
|
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
|
||||||
</a>
|
)}
|
||||||
</DropdownMenuItem>
|
aria-hidden="true"
|
||||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
/>
|
||||||
{user?.username && (
|
{user?.away ? t("set_as_free") : t("set_as_away")}
|
||||||
<DropdownMenuItem>
|
</a>
|
||||||
<a
|
</DropdownMenuItem>
|
||||||
target="_blank"
|
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||||
rel="noopener noreferrer"
|
{user?.username && (
|
||||||
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`}
|
<DropdownMenuItem>
|
||||||
className="flex items-center px-4 py-2 text-sm text-gray-700">
|
<a
|
||||||
<ExternalLinkIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("view_public_page")}
|
target="_blank"
|
||||||
</a>
|
rel="noopener noreferrer"
|
||||||
</DropdownMenuItem>
|
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`}
|
||||||
|
className="flex items-center px-4 py-2 text-sm text-gray-700">
|
||||||
|
<ExternalLinkIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" />{" "}
|
||||||
|
{t("view_public_page")}
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<a
|
||||||
|
href="https://cal.com/slack"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 2447.6 2452.5"
|
||||||
|
className={classNames(
|
||||||
|
"text-gray-500 group-hover:text-gray-700",
|
||||||
|
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-4 rtl:ml-4"
|
||||||
|
)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipRule="evenodd" fillRule="evenodd">
|
||||||
|
<path
|
||||||
|
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{t("join_our_slack")}
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://cal.com/roadmap"
|
||||||
|
className="flex items-center px-4 py-2 text-sm text-gray-700">
|
||||||
|
<MapIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("visit_roadmap")}
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="flex w-full px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
onClick={() => setHelpOpen(true)}>
|
||||||
|
<QuestionMarkCircleIcon
|
||||||
|
className={classNames(
|
||||||
|
"text-gray-500 group-hover:text-neutral-500",
|
||||||
|
"h-5 w-5 flex-shrink-0 ltr:mr-3"
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{t("help")}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<a
|
||||||
|
onClick={() => signOut({ callbackUrl: "/auth/logout" })}
|
||||||
|
className="flex cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
|
||||||
|
<LogoutIcon
|
||||||
|
className={classNames(
|
||||||
|
"text-gray-500 group-hover:text-gray-700",
|
||||||
|
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{t("sign_out")}
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<a
|
|
||||||
href="https://cal.com/slack"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="flex px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900">
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 2447.6 2452.5"
|
|
||||||
className={classNames(
|
|
||||||
"text-gray-500 group-hover:text-gray-700",
|
|
||||||
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-4 rtl:ml-4"
|
|
||||||
)}
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clipRule="evenodd" fillRule="evenodd">
|
|
||||||
<path
|
|
||||||
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
<path
|
|
||||||
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
<path
|
|
||||||
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
<path
|
|
||||||
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
{t("join_our_slack")}
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href="https://cal.com/roadmap"
|
|
||||||
className="flex items-center px-4 py-2 text-sm text-gray-700">
|
|
||||||
<MapIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("visit_roadmap")}
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
<HelpMenuItem />
|
|
||||||
|
|
||||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<a
|
|
||||||
onClick={() => signOut({ callbackUrl: "/auth/logout" })}
|
|
||||||
className="flex cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
|
|
||||||
<LogoutIcon
|
|
||||||
className={classNames(
|
|
||||||
"text-gray-500 group-hover:text-gray-700",
|
|
||||||
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{t("sign_out")}
|
|
||||||
</a>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import HelpscoutMenuItem from "@ee/lib/helpscout/HelpscoutMenuItem";
|
||||||
|
import IntercomMenuItem from "@ee/lib/intercom/IntercomMenuItem";
|
||||||
|
import ZendeskMenuItem from "@ee/lib/zendesk/ZendeskMenuItem";
|
||||||
|
|
||||||
|
export default function HelpMenuItem() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IntercomMenuItem />
|
||||||
|
<ZendeskMenuItem />
|
||||||
|
<HelpscoutMenuItem />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,13 +1,191 @@
|
||||||
import HelpscoutMenuItem from "@ee/lib/helpscout/HelpscoutMenuItem";
|
import { ExternalLinkIcon, ExclamationIcon } from "@heroicons/react/solid";
|
||||||
import IntercomMenuItem from "@ee/lib/intercom/IntercomMenuItem";
|
import { useState } from "react";
|
||||||
import ZendeskMenuItem from "@ee/lib/zendesk/ZendeskMenuItem";
|
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import showToast from "@calcom/lib/notification";
|
||||||
|
import Button from "@calcom/ui/Button";
|
||||||
|
|
||||||
|
import classNames from "@lib/classNames";
|
||||||
|
import { trpc } from "@lib/trpc";
|
||||||
|
|
||||||
|
import ContactMenuItem from "./ContactMenuItem";
|
||||||
|
|
||||||
export default function HelpMenuItem() {
|
export default function HelpMenuItem() {
|
||||||
|
const [rating, setRating] = useState<null | string>(null);
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
// const [errorMessage, setErrorMessage] = useState(false);
|
||||||
|
const [disableSubmit, setDisableSubmit] = useState(true);
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
const mutation = trpc.useMutation("viewer.submitFeedback");
|
||||||
|
|
||||||
|
const onRatingClick = (value: string) => {
|
||||||
|
setRating(value);
|
||||||
|
setDisableSubmit(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendFeedback = async (rating: string, comment: string) => {
|
||||||
|
mutation.mutate({ rating: rating, comment: comment });
|
||||||
|
|
||||||
|
if (mutation.isSuccess) {
|
||||||
|
setDisableSubmit(true);
|
||||||
|
showToast("Thank you, feedback submitted", "success");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="w-full border-gray-300 bg-white shadow-sm md:w-[150%]">
|
||||||
<IntercomMenuItem />
|
<div className=" w-full p-5">
|
||||||
<ZendeskMenuItem />
|
<p className="mb-1 text-neutral-500">{t("resources").toUpperCase()}</p>
|
||||||
<HelpscoutMenuItem />
|
<a
|
||||||
</>
|
href="https://docs.cal.com/"
|
||||||
|
target="_blank"
|
||||||
|
className="flex w-full py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
rel="noreferrer">
|
||||||
|
{t("support_documentation")}
|
||||||
|
<ExternalLinkIcon
|
||||||
|
className={classNames(
|
||||||
|
"text-neutral-400 group-hover:text-neutral-500",
|
||||||
|
"ml-1 h-5 w-5 flex-shrink-0 ltr:mr-3"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://developer.cal.com/"
|
||||||
|
target="_blank"
|
||||||
|
className="flex w-full py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
rel="noreferrer">
|
||||||
|
{t("developer_documentation")}
|
||||||
|
<ExternalLinkIcon
|
||||||
|
className={classNames(
|
||||||
|
"text-neutral-400 group-hover:text-neutral-500",
|
||||||
|
"ml-1 h-5 w-5 flex-shrink-0 ltr:mr-3"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<ContactMenuItem />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className=" bg-gray-200" />
|
||||||
|
<div className="w-full p-5">
|
||||||
|
<p className="mb-1 text-neutral-500">{t("feedback").toUpperCase()}</p>
|
||||||
|
<p className="flex w-full py-2 text-sm font-medium text-gray-700">{t("comments")}</p>
|
||||||
|
<textarea
|
||||||
|
id="comment"
|
||||||
|
name="comment"
|
||||||
|
rows={3}
|
||||||
|
onChange={(event) => setComment(event.target.value)}
|
||||||
|
className="my-1 block w-full rounded-sm border-gray-300 py-2 pb-2 shadow-sm sm:text-sm"></textarea>
|
||||||
|
|
||||||
|
<div className="my-3 flex justify-end">
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"m-1 items-center justify-center p-1.5 grayscale hover:opacity-100 hover:grayscale-0",
|
||||||
|
rating === "Extremely unsatisfied" ? "grayscale-0" : "opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => onRatingClick("Extremely unsatisfied")}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="1.5em" height="1.5em">
|
||||||
|
<path
|
||||||
|
fill="#FFCC4D"
|
||||||
|
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#664500"
|
||||||
|
d="M22 27c0 2.763-1.791 3-4 3-2.21 0-4-.237-4-3 0-2.761 1.79-6 4-6 2.209 0 4 3.239 4 6zm8-12c-.124 0-.25-.023-.371-.072-5.229-2.091-7.372-5.241-7.461-5.374-.307-.46-.183-1.081.277-1.387.459-.306 1.077-.184 1.385.274.019.027 1.93 2.785 6.541 4.629.513.206.763.787.558 1.3-.157.392-.533.63-.929.63zM6 15c-.397 0-.772-.238-.929-.629-.205-.513.044-1.095.557-1.3 4.612-1.844 6.523-4.602 6.542-4.629.308-.456.929-.577 1.387-.27.457.308.581.925.275 1.383-.089.133-2.232 3.283-7.46 5.374C6.25 14.977 6.124 15 6 15z"
|
||||||
|
/>
|
||||||
|
<path fill="#5DADEC" d="M24 16h4v19l-4-.046V16zM8 35l4-.046V16H8v19z" />
|
||||||
|
<path
|
||||||
|
fill="#664500"
|
||||||
|
d="M14.999 18c-.15 0-.303-.034-.446-.105-3.512-1.756-7.07-.018-7.105 0-.495.249-1.095.046-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.498-2.197 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552zm14 0c-.15 0-.303-.034-.446-.105-3.513-1.756-7.07-.018-7.105 0-.494.248-1.094.047-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.501-2.196 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552z"
|
||||||
|
/>
|
||||||
|
<ellipse fill="#5DADEC" cx="18" cy="34" rx="18" ry="2" />
|
||||||
|
<ellipse fill="#E75A70" cx="18" cy="27" rx="3" ry="2" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"m-1 items-center justify-center p-1.5 grayscale hover:opacity-100 hover:grayscale-0",
|
||||||
|
rating === "Unsatisfied" ? "grayscale-0" : "opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => onRatingClick("Unsatisfied")}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="1.5em" height="1.5em">
|
||||||
|
<path
|
||||||
|
fill="#FFCC4D"
|
||||||
|
d="M36 18c0 9.941-8.059 18-18 18-9.94 0-18-8.059-18-18C0 8.06 8.06 0 18 0c9.941 0 18 8.06 18 18"
|
||||||
|
/>
|
||||||
|
<ellipse fill="#664500" cx="11.5" cy="14.5" rx="2.5" ry="3.5" />
|
||||||
|
<ellipse fill="#664500" cx="24.5" cy="14.5" rx="2.5" ry="3.5" />
|
||||||
|
<path
|
||||||
|
fill="#664500"
|
||||||
|
d="M8.665 27.871c.178.161.444.171.635.029.039-.029 3.922-2.9 8.7-2.9 4.766 0 8.662 2.871 8.7 2.9.191.142.457.13.635-.029.177-.16.217-.424.094-.628C27.3 27.029 24.212 22 18 22s-9.301 5.028-9.429 5.243c-.123.205-.084.468.094.628z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"m-1 items-center justify-center p-1.5 grayscale hover:opacity-100 hover:grayscale-0",
|
||||||
|
rating === "Satisfied" ? "grayscale-0" : "opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => onRatingClick("Satisfied")}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="1.5em" height="1.5em">
|
||||||
|
<path
|
||||||
|
fill="#FFCC4D"
|
||||||
|
d="M36 18c0 9.941-8.059 18-18 18-9.94 0-18-8.059-18-18C0 8.06 8.06 0 18 0c9.941 0 18 8.06 18 18"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#664500"
|
||||||
|
d="M28.457 17.797c-.06-.135-1.499-3.297-4.457-3.297-2.957 0-4.397 3.162-4.457 3.297-.092.207-.032.449.145.591.175.142.426.147.61.014.012-.009 1.262-.902 3.702-.902 2.426 0 3.674.881 3.702.901.088.066.194.099.298.099.11 0 .221-.037.312-.109.177-.142.238-.386.145-.594zm-12 0c-.06-.135-1.499-3.297-4.457-3.297-2.957 0-4.397 3.162-4.457 3.297-.092.207-.032.449.144.591.176.142.427.147.61.014.013-.009 1.262-.902 3.703-.902 2.426 0 3.674.881 3.702.901.088.066.194.099.298.099.11 0 .221-.037.312-.109.178-.142.237-.386.145-.594zM18 22c-3.623 0-6.027-.422-9-1-.679-.131-2 0-2 2 0 4 4.595 9 11 9 6.404 0 11-5 11-9 0-2-1.321-2.132-2-2-2.973.578-5.377 1-9 1z"
|
||||||
|
/>
|
||||||
|
<path fill="#FFF" d="M9 23s3 1 9 1 9-1 9-1-2 4-9 4-9-4-9-4z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
"m-1 items-center justify-center p-1.5 grayscale hover:opacity-100 hover:grayscale-0",
|
||||||
|
rating === "Extremely satisfied" ? "grayscale-0" : "opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => onRatingClick("Extremely satisfied")}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="1.5em" height="1.5em">
|
||||||
|
<path
|
||||||
|
fill="#FFCC4D"
|
||||||
|
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#664500"
|
||||||
|
d="M18 21c-3.623 0-6.027-.422-9-1-.679-.131-2 0-2 2 0 4 4.595 9 11 9 6.404 0 11-5 11-9 0-2-1.321-2.132-2-2-2.973.578-5.377 1-9 1z"
|
||||||
|
/>
|
||||||
|
<path fill="#FFF" d="M9 22s3 1 9 1 9-1 9-1-2 4-9 4-9-4-9-4z" />
|
||||||
|
<path
|
||||||
|
fill="#E95F28"
|
||||||
|
d="M15.682 4.413l-4.542.801L8.8.961C8.542.492 8.012.241 7.488.333c-.527.093-.937.511-1.019 1.039l-.745 4.797-4.542.801c-.535.094-.948.525-1.021 1.064s.211 1.063.703 1.297l4.07 1.932-.748 4.812c-.083.536.189 1.064.673 1.309.179.09.371.133.562.133.327 0 .65-.128.891-.372l3.512-3.561 4.518 2.145c.49.232 1.074.123 1.446-.272.372-.395.446-.984.185-1.459L13.625 9.73l3.165-3.208c.382-.387.469-.977.217-1.459-.254-.482-.793-.743-1.325-.65zm4.636 0l4.542.801L27.2.961c.258-.469.788-.72 1.312-.628.526.093.936.511 1.018 1.039l.745 4.797 4.542.801c.536.094.949.524 1.021 1.063s-.211 1.063-.703 1.297l-4.07 1.932.748 4.812c.083.536-.189 1.064-.673 1.309-.179.09-.371.133-.562.133-.327 0-.65-.128-.891-.372l-3.512-3.561-4.518 2.145c-.49.232-1.074.123-1.446-.272-.372-.395-.446-.984-.185-1.459l2.348-4.267-3.165-3.208c-.382-.387-.469-.977-.217-1.459.255-.482.794-.743 1.326-.65z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="my-2 flex justify-end">
|
||||||
|
<Button
|
||||||
|
disabled={disableSubmit}
|
||||||
|
loading={mutation.isLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
if (rating && comment) {
|
||||||
|
await sendFeedback(rating, comment);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{t("submit")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{mutation.isError && (
|
||||||
|
<div className="mb-4 flex bg-red-100 p-4 text-sm text-red-700">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<ExclamationIcon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 flex-grow">
|
||||||
|
<p className="font-medium">{t("feedback_error")}</p>
|
||||||
|
<p>{t("please_try_again")}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,20 +22,12 @@ export default function HelpscoutMenuItem() {
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItem>
|
<button
|
||||||
<button
|
onClick={handleClick}
|
||||||
onClick={handleClick}
|
className="flex w-full py-2 pr-4 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
||||||
className="flex w-full px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
{t("contact_support")}
|
||||||
<ChatAltIcon
|
</button>
|
||||||
className={classNames(
|
|
||||||
"text-neutral-400 group-hover:text-neutral-500",
|
|
||||||
"h-5 w-5 flex-shrink-0 ltr:mr-3"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{t("help")}
|
|
||||||
</button>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{active && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
|
{active && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,22 +13,13 @@ export default function IntercomMenuItem() {
|
||||||
if (!process.env.NEXT_PUBLIC_INTERCOM_APP_ID) return null;
|
if (!process.env.NEXT_PUBLIC_INTERCOM_APP_ID) return null;
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem>
|
<button
|
||||||
<button
|
onClick={() => {
|
||||||
onClick={() => {
|
boot();
|
||||||
boot();
|
show();
|
||||||
show();
|
}}
|
||||||
}}
|
className="flex w-full py-2 pr-4 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
||||||
className="flex w-full px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
{t("contact_support")}
|
||||||
<ChatAltIcon
|
</button>
|
||||||
className={classNames(
|
|
||||||
"text-neutral-400 group-hover:text-neutral-500",
|
|
||||||
"h-5 w-5 flex-shrink-0 ltr:mr-3"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{t("help")}
|
|
||||||
</button>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,20 +17,11 @@ export default function ZendeskMenuItem() {
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItem>
|
<button
|
||||||
<button
|
onClick={() => setActive(true)}
|
||||||
onClick={() => setActive(true)}
|
className="flex w-full py-2 pr-4 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
||||||
className="flex w-full px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900">
|
{t("contact_support")}
|
||||||
<ChatAltIcon
|
</button>
|
||||||
className={classNames(
|
|
||||||
"text-neutral-400 group-hover:text-neutral-500",
|
|
||||||
"h-5 w-5 flex-shrink-0 ltr:mr-3"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{t("help")}
|
|
||||||
</button>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{active && (
|
{active && (
|
||||||
<Script id="ze-snippet" src={"https://static.zdassets.com/ekr/snippet.js?key=" + ZENDESK_KEY} />
|
<Script id="ze-snippet" src={"https://static.zdassets.com/ekr/snippet.js?key=" + ZENDESK_KEY} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import AttendeeRequestEmail from "@lib/emails/templates/attendee-request-email";
|
||||||
import AttendeeRequestRescheduledEmail from "@lib/emails/templates/attendee-request-reschedule-email";
|
import AttendeeRequestRescheduledEmail from "@lib/emails/templates/attendee-request-reschedule-email";
|
||||||
import AttendeeRescheduledEmail from "@lib/emails/templates/attendee-rescheduled-email";
|
import AttendeeRescheduledEmail from "@lib/emails/templates/attendee-rescheduled-email";
|
||||||
import AttendeeScheduledEmail from "@lib/emails/templates/attendee-scheduled-email";
|
import AttendeeScheduledEmail from "@lib/emails/templates/attendee-scheduled-email";
|
||||||
|
import FeedbackEmail, { Feedback } from "@lib/emails/templates/feedback-email";
|
||||||
import ForgotPasswordEmail, { PasswordReset } from "@lib/emails/templates/forgot-password-email";
|
import ForgotPasswordEmail, { PasswordReset } from "@lib/emails/templates/forgot-password-email";
|
||||||
import OrganizerCancelledEmail from "@lib/emails/templates/organizer-cancelled-email";
|
import OrganizerCancelledEmail from "@lib/emails/templates/organizer-cancelled-email";
|
||||||
import OrganizerPaymentRefundFailedEmail from "@lib/emails/templates/organizer-payment-refund-failed-email";
|
import OrganizerPaymentRefundFailedEmail from "@lib/emails/templates/organizer-payment-refund-failed-email";
|
||||||
|
@ -266,3 +267,14 @@ export const sendRequestRescheduleEmail = async (
|
||||||
|
|
||||||
await Promise.all(emailsToSend);
|
await Promise.all(emailsToSend);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendFeedbackEmail = async (feedback: Feedback) => {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const feedbackEmail = new FeedbackEmail(feedback);
|
||||||
|
resolve(feedbackEmail.sendEmail());
|
||||||
|
} catch (e) {
|
||||||
|
reject(console.error("FeedbackEmail.sendEmail failed", e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
import BaseEmail from "@lib/emails/templates/_base-email";
|
||||||
|
|
||||||
|
import { emailHead, emailBodyLogo } from "./common";
|
||||||
|
|
||||||
|
export interface Feedback {
|
||||||
|
userId: number;
|
||||||
|
rating: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FeedbackEmail extends BaseEmail {
|
||||||
|
feedback: Feedback;
|
||||||
|
|
||||||
|
constructor(feedback: Feedback) {
|
||||||
|
super();
|
||||||
|
this.feedback = feedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getNodeMailerPayload(): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
from: `Cal.com <${this.getMailerOptions().from}>`,
|
||||||
|
to: process.env.SEND_FEEDBACK_EMAIL,
|
||||||
|
subject: `User Feedback`,
|
||||||
|
html: this.getHtmlBody(),
|
||||||
|
text: this.getTextBody(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTextBody(): string {
|
||||||
|
return `
|
||||||
|
userId: ${this.feedback.userId}
|
||||||
|
rating: ${this.feedback.rating}
|
||||||
|
comment: ${this.feedback.comment}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getHtmlBody(): string {
|
||||||
|
return `
|
||||||
|
<!doctype html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
${emailHead("Feedback")}
|
||||||
|
<body style="word-spacing:normal;background-color:#F5F5F5;">
|
||||||
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:24px;padding-bottom:0px;word-break:break-word;">
|
||||||
|
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:24px;font-weight:700;line-height:24px;text-align:center;color:#292929;">Feedback</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="background-color:#F5F5F5;">
|
||||||
|
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#FFFFFF" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||||
|
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border-left:1px solid #E1E1E1;border-right:1px solid #E1E1E1;direction:ltr;font-size:0px;padding:0px;text-align:center;">
|
||||||
|
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:598px;" ><![endif]-->
|
||||||
|
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
<div style="font-family:Roboto, Helvetica, sans-serif;font-size:16px;font-weight:500;line-height:1;text-align:left;color:#3E3E3E;">
|
||||||
|
<div style="line-height: 6px;">
|
||||||
|
<p style="color: #494949;">Used id</p>
|
||||||
|
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||||
|
this.feedback.userId
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
<div style="line-height: 6px;">
|
||||||
|
<p style="color: #494949;">Rating</p>
|
||||||
|
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||||
|
this.feedback.rating
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
<div style="line-height: 6px;">
|
||||||
|
<p style="color: #494949;">Comment</p>
|
||||||
|
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
|
||||||
|
this.feedback.comment
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
${emailBodyLogo()}
|
||||||
|
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -813,16 +813,16 @@
|
||||||
"former_time": "Former time",
|
"former_time": "Former time",
|
||||||
"confirmation_page_gif": "Gif for confirmation page",
|
"confirmation_page_gif": "Gif for confirmation page",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"impersonate":"Impersonate",
|
"impersonate": "Impersonate",
|
||||||
"impersonate_user_tip":"All uses of this feature is audited.",
|
"impersonate_user_tip": "All uses of this feature is audited.",
|
||||||
"impersonating_user_warning":"Impersonating username \"{{user}}\".",
|
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
|
||||||
"impersonating_stop_instructions": "<0>Click Here to stop</0>.",
|
"impersonating_stop_instructions": "<0>Click Here to stop</0>.",
|
||||||
"email_validation_error":"That doesn't look like an email address",
|
"email_validation_error": "That doesn't look like an email address",
|
||||||
"place_where_cal_widget_appear": "Place this code in your HTML where you want your Cal widget to appear.",
|
"place_where_cal_widget_appear": "Place this code in your HTML where you want your Cal widget to appear.",
|
||||||
"copy_code": "Copy Code",
|
"copy_code": "Copy Code",
|
||||||
"code_copied": "Code copied!",
|
"code_copied": "Code copied!",
|
||||||
"how_you_want_add_cal_site":"How do you want to add Cal to your site?",
|
"how_you_want_add_cal_site": "How do you want to add Cal to your site?",
|
||||||
"choose_ways_put_cal_site":"Choose one of the following ways to put Cal on your site.",
|
"choose_ways_put_cal_site": "Choose one of the following ways to put Cal on your site.",
|
||||||
"setting_up_zapier": "Setting up your Zapier integration",
|
"setting_up_zapier": "Setting up your Zapier integration",
|
||||||
"generate_api_key": "Generate Api Key",
|
"generate_api_key": "Generate Api Key",
|
||||||
"your_unique_api_key": "Your unique API key",
|
"your_unique_api_key": "Your unique API key",
|
||||||
|
@ -832,6 +832,16 @@
|
||||||
"go_to_app_store": "Go to App Store",
|
"go_to_app_store": "Go to App Store",
|
||||||
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
|
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
|
||||||
"calendar_no_busy_slots": "There are no busy slots",
|
"calendar_no_busy_slots": "There are no busy slots",
|
||||||
|
"share_feedback": "Share feedback",
|
||||||
|
"resources": "Resources",
|
||||||
|
"support_documentation": "Support documentation",
|
||||||
|
"developer_documentation": "Developer Documentation",
|
||||||
|
"get_in_touch": "Get in touch",
|
||||||
|
"contact_support": "Contact Support",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"submitted_feedback": "Thank you for your feedback!",
|
||||||
|
"feedback_error": "Error sending feedback",
|
||||||
|
"comments": "Comments",
|
||||||
"booking_details": "Booking details",
|
"booking_details": "Booking details",
|
||||||
"or_lowercase": "or",
|
"or_lowercase": "or",
|
||||||
"nevermind": "Nevermind",
|
"nevermind": "Nevermind",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { BookingStatus, MembershipRole, Prisma } from "@prisma/client";
|
import { BookingStatus, MembershipRole, Prisma } from "@prisma/client";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { JSONObject } from "superjson/dist/types";
|
import { JSONObject } from "superjson/dist/types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
@ -10,6 +11,7 @@ import { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||||
|
import { sendFeedbackEmail } from "@lib/emails/email-manager";
|
||||||
import jackson from "@lib/jackson";
|
import jackson from "@lib/jackson";
|
||||||
import {
|
import {
|
||||||
isSAMLLoginEnabled,
|
isSAMLLoginEnabled,
|
||||||
|
@ -891,6 +893,32 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
throw new TRPCError({ code: "BAD_REQUEST" });
|
throw new TRPCError({ code: "BAD_REQUEST" });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
.mutation("submitFeedback", {
|
||||||
|
input: z.object({
|
||||||
|
rating: z.string(),
|
||||||
|
comment: z.string(),
|
||||||
|
}),
|
||||||
|
async resolve({ input, ctx }) {
|
||||||
|
const { rating, comment } = input;
|
||||||
|
|
||||||
|
const feedback = {
|
||||||
|
userId: ctx.user.id,
|
||||||
|
rating: rating,
|
||||||
|
comment: comment,
|
||||||
|
};
|
||||||
|
|
||||||
|
await ctx.prisma.feedback.create({
|
||||||
|
data: {
|
||||||
|
date: dayjs().toISOString(),
|
||||||
|
userId: ctx.user.id,
|
||||||
|
rating: rating,
|
||||||
|
comment: comment,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.SEND_FEEDBACK_EMAIL && comment) sendFeedbackEmail(feedback);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const viewerRouter = createRouter()
|
export const viewerRouter = createRouter()
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Feedback" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"date" TIMESTAMP(3) NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"rating" TEXT NOT NULL,
|
||||||
|
"comment" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Feedback_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Feedback" ADD CONSTRAINT "Feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -173,6 +173,7 @@ model User {
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|
||||||
|
Feedback Feedback[]
|
||||||
@@map(name: "users")
|
@@map(name: "users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,3 +478,12 @@ model App {
|
||||||
Webhook Webhook[]
|
Webhook Webhook[]
|
||||||
ApiKey ApiKey[]
|
ApiKey ApiKey[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Feedback {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
date DateTime
|
||||||
|
userId Int
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
rating String
|
||||||
|
comment String?
|
||||||
|
}
|
||||||
|
|
|
@ -10366,6 +10366,11 @@ libphonenumber-js@^1.9.52, libphonenumber-js@^1.9.53:
|
||||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088"
|
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088"
|
||||||
integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw==
|
integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw==
|
||||||
|
|
||||||
|
libphonenumber-js@^1.9.53:
|
||||||
|
version "1.9.53"
|
||||||
|
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088"
|
||||||
|
integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw==
|
||||||
|
|
||||||
lie@3.1.1:
|
lie@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||||
|
|
Loading…
Reference in New Issue