91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { useFetcher, useLocation, useParams } from "react-router";
|
|
import { Fragment, useEffect, useState } from "react";
|
|
import toast from "react-hot-toast";
|
|
import { useTranslation } from "react-i18next";
|
|
import ButtonPrimary from "~/components/ui/buttons/ButtonPrimary";
|
|
import StarsIconFilled from "~/components/ui/icons/StarsIconFilled";
|
|
import InputText from "~/components/ui/input/InputText";
|
|
import Modal from "~/components/ui/modals/Modal";
|
|
import { appPath } from "~/utils/app/CanonicalUrlUtils";
|
|
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
|
|
import { useRootData } from "~/utils/data/useRootData";
|
|
|
|
export default function AddFeedbackButton() {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const { t } = useTranslation();
|
|
return (
|
|
<Fragment>
|
|
<div className="relative hidden sm:inline-flex">
|
|
<div className="relative">
|
|
<div className="inline-flex divide-x divide-gray-300 rounded-sm shadow-none">
|
|
<div className="relative z-0 inline-flex rounded-full text-sm shadow-none">
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
type="button"
|
|
className="text-muted-foreground bg-secondary hover:bg-secondary/90 border-border relative inline-flex items-center rounded-full border p-2 font-medium shadow-inner focus:z-10 focus:outline-hidden focus:ring-2 focus:ring-offset-2"
|
|
aria-haspopup="listbox"
|
|
aria-expanded="true"
|
|
aria-label={t("feedback.title")}
|
|
>
|
|
<span className="sr-only">{t("feedback.title")}</span>
|
|
|
|
<StarsIconFilled className="h-5 w-5 p-0.5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Modal open={isOpen} setOpen={setIsOpen} size="sm">
|
|
<FeedbackForm onClose={() => setIsOpen(false)} />
|
|
</Modal>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
function FeedbackForm({ onClose }: { onClose: () => void }) {
|
|
const { t } = useTranslation();
|
|
const params = useParams();
|
|
const appOrAdminData = useAppOrAdminData();
|
|
const { csrf } = useRootData();
|
|
const fetcher = useFetcher<{ success?: string; error?: string }>();
|
|
const location = useLocation();
|
|
|
|
useEffect(() => {
|
|
if (fetcher.data?.success) {
|
|
toast.success(fetcher.data.success);
|
|
onClose();
|
|
} else if (fetcher.data?.error) {
|
|
toast.error(fetcher.data.error);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [fetcher.data]);
|
|
|
|
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
fetcher.submit(e.currentTarget, {
|
|
method: "post",
|
|
action: appPath({ tenantSlug: params.tenant ?? "", mode: "host-aware" }),
|
|
});
|
|
// onClose();
|
|
};
|
|
return (
|
|
<fetcher.Form onSubmit={onSubmit} method="post">
|
|
<input type="hidden" name="action" value="add-feedback" readOnly hidden />
|
|
<input type="hidden" name="csrf" value={csrf ?? ""} readOnly hidden />
|
|
<input type="hidden" name="tenantId" value={appOrAdminData.currentTenant?.id} readOnly hidden />
|
|
<input type="hidden" name="fromUrl" value={location.pathname} readOnly hidden />
|
|
<div className="space-y-3">
|
|
<h3 className="text-lg font-medium leading-6">{t("feedback.title")}</h3>
|
|
<p className="text-muted-foreground text-sm">{t("feedback.description")}</p>
|
|
<InputText name="message" title="feedback.message" withLabel={false} defaultValue="" required rows={5} placeholder={t("feedback.placeholder")} />
|
|
<div className="flex justify-between">
|
|
<button type="button" onClick={onClose} className="text-muted-foreground text-sm hover:underline">
|
|
{t("shared.cancel")}
|
|
</button>
|
|
<ButtonPrimary type="submit">{t("feedback.send")}</ButtonPrimary>
|
|
</div>
|
|
</div>
|
|
</fetcher.Form>
|
|
);
|
|
}
|