mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-28 09:46:39 +02:00
📢 Add Featurebase (#796)
This commit is contained in:
parent
5532723d23
commit
222947292b
8 changed files with 151 additions and 157 deletions
13
README.md
13
README.md
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<a href="https://sentry.io"><img src="./apps/landing/public/sentry.svg" alt="Sentry" height="30" /></a>
|
||||
<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>
|
||||
<a href="https://cloudron.io?utm_source=rallly"><img src="./assets/images/cloudron-logo.svg" alt="Cloudron" height="32"></a>
|
||||
<a href="https://featurebase.app?utm_source=rallly"><img src="./assets/images/featurebase.svg" alt="Featurebase" height="30"></a>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
44
apps/web/src/components/featurebase.tsx
Normal file
44
apps/web/src/components/featurebase.tsx
Normal 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}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
16
assets/images/featurebase.svg
Normal file
16
assets/images/featurebase.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.5 KiB |
|
@ -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}`,
|
||||
});
|
||||
}),
|
||||
});
|
|
@ -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,
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue