📢 Add Featurebase (#796)

This commit is contained in:
Luke Vella 2023-08-01 21:27:20 +01:00 committed by GitHub
parent 5532723d23
commit 222947292b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 151 additions and 157 deletions

View file

@ -89,13 +89,14 @@ Thank you to our sponsors for making this project possible.
[Become a sponsor →](https://github.com/sponsors/lukevella)
This project is also supported by the following companies through their open-source sponsorships.
And thank you to these companies for sponsoring and showing support for this project.
<a href="https://appwrite.io"><img src="./assets/images/appwrite.svg" alt="appwrite" height="30" /></a>
<a href="https://appwrite.io?utm_source=rallly"><img src="./assets/images/appwrite.svg" alt="appwrite" height="24" /></a>
&nbsp;&nbsp;&nbsp;
<a href="https://vercel.com/?utm_source=rallly&utm_campaign=oss"><img src="./apps/landing/public/vercel-logotype-dark.svg" alt="Powered by Vercel" height="30" /></a>
<a href="https://vercel.com/?utm_source=rallly&utm_campaign=oss"><img src="./apps/landing/public/vercel-logotype-dark.svg" alt="Powered by Vercel" height="24" /></a>
&nbsp;&nbsp;&nbsp;
<a href="https://m.do.co/c/f91efc9c9e50"><img src="./apps/landing/public/digitalocean.svg" alt="Digital Ocean" height="30" /></a>
<a href="https://m.do.co/c/f91efc9c9e50"><img src="./apps/landing/public/digitalocean.svg" alt="Digital Ocean" height="24" /></a>
&nbsp;&nbsp;&nbsp;
<a href="https://sentry.io"><img src="./apps/landing/public/sentry.svg" alt="Sentry" height="30" /></a>&nbsp;&nbsp;&nbsp;
<a href="https://cloudron.io"><img src="./assets/images/cloudron-logo.svg" alt="Cloudron" height="36"></a>
<a href="https://sentry.io?utm_source=rallly"><img src="./apps/landing/public/sentry.svg" alt="Sentry" height="24" /></a>&nbsp;&nbsp;&nbsp;
<a href="https://cloudron.io?utm_source=rallly"><img src="./assets/images/cloudron-logo.svg" alt="Cloudron" height="32"></a>&nbsp;&nbsp;&nbsp;
<a href="https://featurebase.app?utm_source=rallly"><img src="./assets/images/featurebase.svg" alt="Featurebase" height="30"></a>

View file

@ -9,7 +9,7 @@ const PrivacyPolicy = () => {
<NextSeo title="Privacy Policy" />
<div className="prose my-16 mx-auto max-w-3xl rounded-lg bg-white p-8 shadow-md">
<h1>Privacy Policy</h1>
<p>Last updated: 19 April 2023</p>
<p>Last updated: 1 August 2023</p>
<p>
At rallly.co, we take your privacy seriously. This privacy policy
explains how we collect, use, and disclose your personal data, and
@ -66,10 +66,18 @@ const PrivacyPolicy = () => {
<h2>Sharing of personal data</h2>
<p>
We do not share your personal data with third parties, except for our
data processor, Posthog, which is based in the United States. We have
implemented appropriate safeguards to protect your personal data when
it is transferred outside of the European Economic Area.
We do not share your personal data with any third parties for
marketing or commercial purposes. We may share your personal data with
third parties to provide you with our services, to comply with
applicable laws and regulations, to respond to a subpoena, search
warrant or other lawful request for information we receive, or to
otherwise protect our rights.
</p>
<p>
For example, we use Featurebase to make it easy for users to submit
feedback. Your name and email may be shared with Featurbase to provide
a seamless transition between the two services.
</p>
<h2>Your rights</h2>

View file

@ -10,7 +10,6 @@
"settings": "Settings",
"changeNameDescription": "Enter a new name for this participant.",
"changeNameInfo": "This will not affect any votes you have already made.",
"close": "Close",
"comments": "Comments",
"continue": "Continue",
"copied": "Copied",
@ -36,11 +35,6 @@
"emailPlaceholder": "jessie.smith@example.com",
"expiredOrInvalidLink": "This link is expired or invalid. Please request a new link.",
"exportToCsv": "Export to CSV",
"feedbackFormFooter": "Need help? Visit the <a>support page</a>.",
"feedbackFormLabel": "How can we improve <appname />?",
"feedbackFormPlaceholder": "Share your thoughts…",
"feedbackFormTitle": "Feedback Form",
"feedbackSent": "Thank you! Your feedback has been sent.",
"forgetMe": "Forget me",
"guest": "Guest",
"ifNeedBe": "If need be",
@ -76,7 +70,6 @@
"response": "Response",
"save": "Save",
"saveInstruction": "Select your availability and click <b>{action}</b>",
"send": "Send",
"sendFeedback": "Send Feedback",
"specifyTimes": "Specify times",
"specifyTimesDescription": "Include start and end times for each option",
@ -213,5 +206,8 @@
"disableCommentsDescription": "Remove the option to leave a comment on the poll",
"planCustomizablePollSettings": "Customizable poll settings",
"clockPreferences": "Clock Preferences",
"clockPreferencesDescription": "Set your preferred time zone and time format."
"clockPreferencesDescription": "Set your preferred time zone and time format.",
"featureRequest": "Request a Feature",
"bugReport": "Report an Issue",
"getSupport": "Get Support"
}

View file

@ -0,0 +1,44 @@
import Script from "next/script";
import React from "react";
import { useUser } from "@/components/user-provider";
export const FeaturebaseProvider = ({ children }: React.PropsWithChildren) => {
const { user } = useUser();
React.useEffect(() => {
if (user.isGuest) return;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any;
if (typeof win.Featurebase !== "function") {
win.Featurebase = function () {
// eslint-disable-next-line prefer-rest-params
(win.Featurebase.q = win.Featurebase.q || []).push(arguments);
};
}
win.Featurebase(
"identify",
{
organization: "rallly",
// Required. Replace with your customers data.
email: user.email,
name: user.name,
id: user.id,
},
(err: Error) => {
// Callback function. Called when identify completed.
if (err) {
console.error(err);
}
},
);
}, [user]);
return (
<>
<Script src="https://do.featurebase.app/js/sdk.js" id="featurebase-sdk" />
{children}
</>
);
};

View file

@ -1,107 +1,75 @@
import { trpc } from "@rallly/backend";
import { CheckCircleIcon, MegaphoneIcon } from "@rallly/icons";
import { Button } from "@rallly/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip";
import {
BugIcon,
LifeBuoyIcon,
LightbulbIcon,
MegaphoneIcon,
SmileIcon,
} from "@rallly/icons";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@rallly/ui/dropdown-menu";
import Link from "next/link";
import { Trans, useTranslation } from "next-i18next";
import { useForm } from "react-hook-form";
import { Trans } from "next-i18next";
import { Logo } from "@/components/logo";
import { useModalState } from "@/components/modal/use-modal";
const FeedbackForm = (props: { onClose: () => void }) => {
const { t } = useTranslation();
const sendFeedback = trpc.feedback.send.useMutation();
const { handleSubmit, register, formState } = useForm<{ content: string }>();
return (
<form
onSubmit={handleSubmit(async (data) => {
await sendFeedback.mutateAsync(data);
})}
className="shadow-huge animate-popIn fixed bottom-8 right-8 z-20 w-[460px] max-w-full origin-bottom-right space-y-2 overflow-hidden rounded-md border bg-white p-3"
>
{formState.isSubmitted ? (
<div className="absolute inset-0 flex h-full w-full items-center justify-center bg-white">
<div className="space-y-3 text-center">
<CheckCircleIcon className="inline-block h-14 text-green-500" />
<div>{t("feedbackSent")}</div>
<div>
<button onClick={props.onClose} className="text-link">
{t("close")}
</button>
</div>
</div>
</div>
) : null}
<div className="font-semibold text-gray-800">
{t("feedbackFormTitle")}
</div>
<fieldset>
<label className="mb-2" htmlFor="feedback">
<Trans
t={t}
i18nKey="feedbackFormLabel"
components={{ appname: <Logo /> }}
/>
</label>
<textarea
id="feedback"
autoFocus={true}
placeholder={t("feedbackFormPlaceholder")}
rows={4}
className="w-full border bg-gray-50 p-2 text-gray-800"
{...register("content", { required: true })}
/>
</fieldset>
<div className="flex items-center justify-between gap-4">
<div className="flex gap-2">
<Button onClick={props.onClose}>{t("cancel")}</Button>
<Button
loading={formState.isSubmitting}
type="submit"
variant="primary"
>
{t("send")}
</Button>
</div>
<div className="text-sm">
<Trans
t={t}
i18nKey="feedbackFormFooter"
components={{
a: (
<Link href="https://support.rallly.co" className="text-link" />
),
}}
/>
</div>
</div>
</form>
);
};
import { FeaturebaseProvider } from "@/components/featurebase";
const FeedbackButton = () => {
const { t } = useTranslation();
const [isVisible, show, close] = useModalState();
if (isVisible) {
return <FeedbackForm onClose={close} />;
}
return (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={show}
className="shadow-huge fixed bottom-8 right-8 hidden h-14 w-14 items-center justify-center rounded-full bg-gray-800 sm:inline-flex"
>
<MegaphoneIcon className="h-7 text-white" />
</button>
</TooltipTrigger>
<TooltipContent side="left">
<p>{t("sendFeedback")}</p>
</TooltipContent>
</Tooltip>
<FeaturebaseProvider>
<DropdownMenu modal={false}>
<DropdownMenuTrigger className="shadow-huge fixed bottom-8 right-6 hidden h-12 w-12 items-center justify-center rounded-full bg-gray-800 hover:bg-gray-700 active:shadow-none sm:inline-flex">
<MegaphoneIcon className="h-5 text-white" />
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={10} align="end">
<DropdownMenuLabel>
<Trans i18nKey="menu" />
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
href={`https://rallly.featurebase.app/?b=feedback`}
target={"_blank"}
>
<SmileIcon className="mr-2 h-4 w-4" />
<Trans i18nKey="sendFeedback" defaults="Send Feedback" />
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`https://rallly.featurebase.app/?b=feature-request`}
target={"_blank"}
>
<LightbulbIcon className="mr-2 h-4 w-4" />
<Trans
i18nKey={"featureRequest"}
defaults={"Request a Feature"}
/>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`https://rallly.featurebase.app/?b=bug-reports`}
target={"_blank"}
>
<BugIcon className="mr-2 h-4 w-4" />
<Trans i18nKey={"bugReport"} defaults={"Report an Issue"} />
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href={`https://support.rallly.co`} target={"_blank"}>
<LifeBuoyIcon className="mr-2 h-4 w-4" />
<Trans i18nKey={"getSupport"} defaults={"Get Support"} />
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</FeaturebaseProvider>
);
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -1,37 +0,0 @@
import { prisma } from "@rallly/database";
import { sendRawEmail } from "@rallly/emails";
import { z } from "zod";
import { publicProcedure, router } from "../trpc";
export const feedback = router({
send: publicProcedure
.input(z.object({ content: z.string() }))
.mutation(async ({ input, ctx }) => {
let replyTo: string | undefined;
let name = "Guest";
if (!ctx.user.isGuest) {
const user = await prisma.user.findUnique({
where: { id: ctx.user.id },
select: { email: true, name: true },
});
if (user) {
replyTo = user.email;
name = user.name;
}
}
await sendRawEmail({
to: process.env.NEXT_PUBLIC_FEEDBACK_EMAIL,
from: {
name: "Rallly Feedback Form",
address: process.env.SUPPORT_EMAIL ?? "",
},
subject: "Feedback",
replyTo,
text: `${name} says:\n\n${input.content}`,
});
}),
});

View file

@ -1,6 +1,5 @@
import { mergeRouters, router } from "../trpc";
import { auth } from "./auth";
import { feedback } from "./feedback";
import { polls } from "./polls";
import { user } from "./user";
import { userPreferences } from "./user-preferences";
@ -12,7 +11,6 @@ export const appRouter = mergeRouters(
auth,
polls,
user,
feedback,
userPreferences,
}),
);