📓 Add changelog widget (#798)

This commit is contained in:
Luke Vella 2023-08-02 18:30:10 +01:00 committed by GitHub
parent bc53e97f93
commit d08ed6d0f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 158 additions and 100 deletions

3
.gitignore vendored
View file

@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
# Turbo # Turbo
.turbo .turbo
# Jetbrains IDE
.idea

View file

@ -33,10 +33,6 @@ declare global {
* Crisp website ID * Crisp website ID
*/ */
NEXT_PUBLIC_CRISP_WEBSITE_ID?: string; NEXT_PUBLIC_CRISP_WEBSITE_ID?: string;
/**
* When defined users will be able to send feedback to this email address
*/
NEXT_PUBLIC_FEEDBACK_EMAIL?: string;
/** /**
* Users of your instance will see this as their support email * Users of your instance will see this as their support email
*/ */

View file

@ -34,9 +34,9 @@ declare global {
*/ */
NEXT_PUBLIC_CRISP_WEBSITE_ID?: string; NEXT_PUBLIC_CRISP_WEBSITE_ID?: string;
/** /**
* When defined users will be able to send feedback to this email address * When `true` it will show the feedback button and pull the changelog from featurebase
*/ */
NEXT_PUBLIC_FEEDBACK_EMAIL?: string; NEXT_PUBLIC_FEEDBACK_ENABLED?: string;
/** /**
* Users of your instance will see this as their support email * Users of your instance will see this as their support email
*/ */

View file

@ -121,7 +121,6 @@
"inviteLink": "Invite Link", "inviteLink": "Invite Link",
"inviteParticipantLinkInfo": "Anyone with this link will be able to vote on your poll.", "inviteParticipantLinkInfo": "Anyone with this link will be able to vote on your poll.",
"support": "Support", "support": "Support",
"changelog": "Change log",
"billing": "Billing", "billing": "Billing",
"guestPollAlertDescription": "<0>Create an account</0> or <1>login</1> to claim this poll.", "guestPollAlertDescription": "<0>Create an account</0> or <1>login</1> to claim this poll.",
"guestPollAlertTitle": "Your administrator rights can be lost if you clear your cookies", "guestPollAlertTitle": "Your administrator rights can be lost if you clear your cookies",
@ -209,5 +208,6 @@
"clockPreferencesDescription": "Set your preferred time zone and time format.", "clockPreferencesDescription": "Set your preferred time zone and time format.",
"featureRequest": "Request a Feature", "featureRequest": "Request a Feature",
"bugReport": "Report an Issue", "bugReport": "Report an Issue",
"getSupport": "Get Support" "getSupport": "Get Support",
"feedback": "Feedback"
} }

View file

@ -1,22 +1,74 @@
import { trpc } from "@rallly/backend";
import { HelpCircleIcon } from "@rallly/icons";
import { cn } from "@rallly/ui";
import { Button } from "@rallly/ui/button";
import Script from "next/script"; import Script from "next/script";
import React from "react"; import React from "react";
import { useUser } from "@/components/user-provider"; import { Trans } from "@/components/trans";
import { isFeedbackEnabled } from "@/utils/constants";
export const FeaturebaseProvider = ({ children }: React.PropsWithChildren) => { const FeaturebaseScript = () => (
const { user } = useUser(); <Script src="https://do.featurebase.app/js/sdk.js" id="featurebase-sdk" />
);
export const Changelog = ({ className }: { className?: string }) => {
React.useEffect(() => { React.useEffect(() => {
if (user.isGuest) return;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any; const win = window as any;
if (typeof win.Featurebase !== "function") {
win.Featurebase = function (...args: unknown[]) {
(win.Featurebase.q = win.Featurebase.q || []).push(args);
};
}
win.Featurebase("initialize_changelog_widget", {
organization: "rallly", // Replace this with your featurebase organization name
placement: "bottom", // Choose between right, left, top, bottom placement
theme: "light", // Choose between dark or light theme
});
}, []);
if (!isFeedbackEnabled) return null;
return (
<>
<FeaturebaseScript />
<Button
className={cn("[&>*]:pointer-events-none", className)}
size="sm"
variant="ghost"
data-featurebase-changelog
>
<HelpCircleIcon className="h-4 w-4" />
<span className="hidden sm:block">
<Trans key="whatsNew" defaults="What's new?" />
</span>
<span
id="fb-update-badge"
className="bg-primary rounded-md px-1 py-px text-xs text-gray-100 empty:hidden"
/>
</Button>
</>
);
};
export const FeaturebaseIdentify = () => {
const { data: user } = trpc.whoami.get.useQuery();
React.useEffect(() => {
if (user?.isGuest !== false || !isFeedbackEnabled) return;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any;
if (typeof win.Featurebase !== "function") { if (typeof win.Featurebase !== "function") {
win.Featurebase = function () { win.Featurebase = function () {
// eslint-disable-next-line prefer-rest-params // eslint-disable-next-line prefer-rest-params
(win.Featurebase.q = win.Featurebase.q || []).push(arguments); (win.Featurebase.q = win.Featurebase.q || []).push(arguments);
}; };
} }
win.Featurebase( win.Featurebase(
"identify", "identify",
{ {
@ -35,10 +87,6 @@ export const FeaturebaseProvider = ({ children }: React.PropsWithChildren) => {
}, },
); );
}, [user]); }, [user]);
return (
<> return <FeaturebaseScript />;
<Script src="https://do.featurebase.app/js/sdk.js" id="featurebase-sdk" />
{children}
</>
);
}; };

View file

@ -16,60 +16,53 @@ import {
import Link from "next/link"; import Link from "next/link";
import { Trans } from "next-i18next"; import { Trans } from "next-i18next";
import { FeaturebaseProvider } from "@/components/featurebase";
const FeedbackButton = () => { const FeedbackButton = () => {
return ( return (
<FeaturebaseProvider> <DropdownMenu modal={false}>
<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">
<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" />
<MegaphoneIcon className="h-5 text-white" /> </DropdownMenuTrigger>
</DropdownMenuTrigger> <DropdownMenuContent sideOffset={10} align="end">
<DropdownMenuContent sideOffset={10} align="end"> <DropdownMenuLabel>
<DropdownMenuLabel> <Trans i18nKey="menu" />
<Trans i18nKey="menu" /> </DropdownMenuLabel>
</DropdownMenuLabel> <DropdownMenuSeparator />
<DropdownMenuSeparator /> <DropdownMenuItem asChild>
<DropdownMenuItem asChild> <Link
<Link href={`https://rallly.featurebase.app/?b=feedback`}
href={`https://rallly.featurebase.app/?b=feedback`} target={"_blank"}
target={"_blank"} >
> <SmileIcon className="mr-2 h-4 w-4" />
<SmileIcon className="mr-2 h-4 w-4" /> <Trans i18nKey="sendFeedback" defaults="Send Feedback" />
<Trans i18nKey="sendFeedback" defaults="Send Feedback" /> </Link>
</Link> </DropdownMenuItem>
</DropdownMenuItem> <DropdownMenuItem asChild>
<DropdownMenuItem asChild> <Link
<Link href={`https://rallly.featurebase.app/?b=feature-request`}
href={`https://rallly.featurebase.app/?b=feature-request`} target={"_blank"}
target={"_blank"} >
> <LightbulbIcon className="mr-2 h-4 w-4" />
<LightbulbIcon className="mr-2 h-4 w-4" /> <Trans i18nKey={"featureRequest"} defaults={"Request a Feature"} />
<Trans </Link>
i18nKey={"featureRequest"} </DropdownMenuItem>
defaults={"Request a Feature"} <DropdownMenuItem asChild>
/> <Link
</Link> href={`https://rallly.featurebase.app/?b=bug-reports`}
</DropdownMenuItem> target={"_blank"}
<DropdownMenuItem asChild> >
<Link <BugIcon className="mr-2 h-4 w-4" />
href={`https://rallly.featurebase.app/?b=bug-reports`} <Trans i18nKey={"bugReport"} defaults={"Report an Issue"} />
target={"_blank"} </Link>
> </DropdownMenuItem>
<BugIcon className="mr-2 h-4 w-4" /> <DropdownMenuSeparator />
<Trans i18nKey={"bugReport"} defaults={"Report an Issue"} /> <DropdownMenuItem asChild>
</Link> <Link href={`https://support.rallly.co`} target={"_blank"}>
</DropdownMenuItem> <LifeBuoyIcon className="mr-2 h-4 w-4" />
<DropdownMenuSeparator /> <Trans i18nKey={"getSupport"} defaults={"Get Support"} />
<DropdownMenuItem asChild> </Link>
<Link href={`https://support.rallly.co`} target={"_blank"}> </DropdownMenuItem>
<LifeBuoyIcon className="mr-2 h-4 w-4" /> </DropdownMenuContent>
<Trans i18nKey={"getSupport"} defaults={"Get Support"} /> </DropdownMenu>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</FeaturebaseProvider>
); );
}; };

View file

@ -1,5 +1,3 @@
import "react-big-calendar/lib/css/react-big-calendar.css";
import { XIcon } from "@rallly/icons"; import { XIcon } from "@rallly/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import React from "react"; import React from "react";

View file

@ -11,14 +11,16 @@ import { Toaster } from "react-hot-toast";
import { Clock, ClockPreferences } from "@/components/clock"; import { Clock, ClockPreferences } from "@/components/clock";
import { Container } from "@/components/container"; import { Container } from "@/components/container";
import { Changelog, FeaturebaseIdentify } from "@/components/featurebase";
import FeedbackButton from "@/components/feedback"; import FeedbackButton from "@/components/feedback";
import { Spinner } from "@/components/spinner"; import { Spinner } from "@/components/spinner";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { UserDropdown } from "@/components/user-dropdown"; import { UserDropdown } from "@/components/user-dropdown";
import { isFeedbackEnabled } from "@/utils/constants";
import { IconComponent, NextPageWithLayout } from "../../types"; import { IconComponent, NextPageWithLayout } from "../../types";
import ModalProvider from "../modal/modal-provider"; import ModalProvider from "../modal/modal-provider";
import { IfGuest, UserProvider } from "../user-provider"; import { IfGuest } from "../user-provider";
const NavMenuItem = ({ const NavMenuItem = ({
href, href,
@ -35,12 +37,11 @@ const NavMenuItem = ({
}) => { }) => {
const router = useRouter(); const router = useRouter();
return ( return (
<Button variant="ghost" asChild> <Button variant="ghost" size="sm" asChild>
<Link <Link
target={target} target={target}
href={href} href={href}
className={cn( className={cn(
"flex items-center gap-2.5 px-2.5 py-1.5 text-sm font-medium",
router.asPath === href router.asPath === href
? "text-foreground" ? "text-foreground"
: "text-muted-foreground hover:text-foreground active:bg-gray-200/50", : "text-muted-foreground hover:text-foreground active:bg-gray-200/50",
@ -119,17 +120,23 @@ const MainNav = () => {
</nav> </nav>
</div> </div>
<div className="flex items-center gap-x-4"> <div className="flex items-center gap-x-4">
<nav className="flex gap-x-2"> <nav className="flex items-center gap-x-1 sm:gap-x-2">
<IfGuest> <IfGuest>
<NavMenuItem <Button
size="sm"
variant="ghost"
asChild
className="hidden sm:flex" className="hidden sm:flex"
icon={LogInIcon} >
href="/login" <Link href="/login">
label={<Trans i18nKey="login" defaults="Login" />} <LogInIcon className="h-4 w-4" />
/> <Trans i18nKey="login" defaults="Login" />
</Link>
</Button>
</IfGuest> </IfGuest>
<Changelog />
<ClockPreferences> <ClockPreferences>
<Button className="text-muted-foreground" variant="ghost"> <Button size="sm" variant="ghost">
<Clock /> <Clock />
</Button> </Button>
</ClockPreferences> </ClockPreferences>
@ -146,8 +153,9 @@ export const StandardLayout: React.FunctionComponent<{
hideNav?: boolean; hideNav?: boolean;
}> = ({ children, hideNav, ...rest }) => { }> = ({ children, hideNav, ...rest }) => {
const key = hideNav ? "no-nav" : "nav"; const key = hideNav ? "no-nav" : "nav";
return ( return (
<UserProvider> <>
<Toaster /> <Toaster />
<ModalProvider> <ModalProvider>
<div className="flex min-h-screen flex-col" {...rest}> <div className="flex min-h-screen flex-col" {...rest}>
@ -169,9 +177,14 @@ export const StandardLayout: React.FunctionComponent<{
</m.div> </m.div>
</AnimatePresence> </AnimatePresence>
</div> </div>
{process.env.NEXT_PUBLIC_FEEDBACK_EMAIL ? <FeedbackButton /> : null} {isFeedbackEnabled ? (
<>
<FeaturebaseIdentify />
<FeedbackButton />
</>
) : null}
</ModalProvider> </ModalProvider>
</UserProvider> </>
); );
}; };

View file

@ -6,8 +6,8 @@ import {
ListIcon, ListIcon,
LogInIcon, LogInIcon,
LogOutIcon, LogOutIcon,
MegaphoneIcon,
RefreshCcwIcon, RefreshCcwIcon,
ScrollTextIcon,
Settings2Icon, Settings2Icon,
UserIcon, UserIcon,
UserPlusIcon, UserPlusIcon,
@ -26,6 +26,7 @@ import Link from "next/link";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { CurrentUserAvatar } from "@/components/user"; import { CurrentUserAvatar } from "@/components/user";
import { isFeedbackEnabled } from "@/utils/constants";
import { IfAuthenticated, IfGuest, useUser } from "./user-provider"; import { IfAuthenticated, IfGuest, useUser } from "./user-provider";
@ -121,16 +122,18 @@ export const UserDropdown = () => {
<Trans i18nKey="support" defaults="Support" /> <Trans i18nKey="support" defaults="Support" />
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild={true}> {isFeedbackEnabled ? (
<Link <DropdownMenuItem asChild={true}>
target="_blank" <Link
href="https://rallly.co/blog" target="_blank"
className="flex items-center gap-x-2" href="https://rallly.featurebase.app"
> className="flex items-center gap-x-2"
<ScrollTextIcon className="h-4 w-4" /> >
<Trans i18nKey="changelog" defaults="Change log" /> <MegaphoneIcon className="h-4 w-4" />
</Link> <Trans i18nKey="feedback" defaults="Feedback" />
</DropdownMenuItem> </Link>
</DropdownMenuItem>
) : null}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<IfGuest> <IfGuest>
<DropdownMenuItem asChild={true}> <DropdownMenuItem asChild={true}>

View file

@ -158,6 +158,7 @@
} }
} }
.rbc-time-view { .rbc-time-view {
@apply border-0 border-b border-t border-gray-100; @apply border-0 border-b border-t border-gray-100;
} }

View file

@ -3,3 +3,6 @@ export const plusPlanIdMonthly = process.env
export const plusPlanIdYearly = process.env export const plusPlanIdYearly = process.env
.NEXT_PUBLIC_PRO_PLAN_ID_MONTHLY as string; .NEXT_PUBLIC_PRO_PLAN_ID_MONTHLY as string;
export const isFeedbackEnabled =
process.env.NEXT_PUBLIC_FEEDBACK_ENABLED === "true";

View file

@ -36,7 +36,7 @@ declare global {
/** /**
* When defined users will be able to send feedback to this email address * When defined users will be able to send feedback to this email address
*/ */
NEXT_PUBLIC_FEEDBACK_EMAIL?: string; NEXT_PUBLIC_FEEDBACK_ENABLED?: string;
/** /**
* Users of your instance will see this as their support email * Users of your instance will see this as their support email
*/ */

View file

@ -23,7 +23,7 @@ const buttonVariants = cva(
}, },
size: { size: {
default: "h-9 px-2.5 text-sm", default: "h-9 px-2.5 text-sm",
sm: "h-7 text-xs px-2 rounded-md", sm: "h-7 text-xs px-1.5 rounded-md",
lg: "h-11 text-base px-4 rounded-md", lg: "h-11 text-base px-4 rounded-md",
}, },
}, },

View file

@ -66,7 +66,7 @@
"NEXT_PUBLIC_CRISP_WEBSITE_ID", "NEXT_PUBLIC_CRISP_WEBSITE_ID",
"NEXT_PUBLIC_ENABLE_ANALYTICS", "NEXT_PUBLIC_ENABLE_ANALYTICS",
"NEXT_PUBLIC_ENABLE_FINALIZATION", "NEXT_PUBLIC_ENABLE_FINALIZATION",
"NEXT_PUBLIC_FEEDBACK_EMAIL", "NEXT_PUBLIC_FEEDBACK_ENABLED",
"NEXT_PUBLIC_LANDING_PAGE_URL", "NEXT_PUBLIC_LANDING_PAGE_URL",
"NEXT_PUBLIC_MAINTENANCE_MODE", "NEXT_PUBLIC_MAINTENANCE_MODE",
"NEXT_PUBLIC_PADDLE_SANDBOX", "NEXT_PUBLIC_PADDLE_SANDBOX",