Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Help & Feedback menu and tab-selected styles #1374

Merged
merged 22 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9e7b07d
Added a new dropdown help and feedback menu to the side menu
samejr Sep 29, 2024
f54ae15
Added a shortcut to the popover menu
samejr Sep 29, 2024
7a3f948
Removed dev cli connected button for now
samejr Sep 29, 2024
a0f92db
Contact us form uses original Feedback component to prevent broken links
samejr Sep 29, 2024
af6185a
Improved the messaging when selecting different options in the email …
samejr Sep 29, 2024
01ab899
buttons style tweak
samejr Sep 29, 2024
596ceb8
SideMenuItem supports the trailingIconClassName
samejr Sep 30, 2024
e77805f
Adding a consistent focus-visible states
samejr Sep 30, 2024
7e71d86
Removing tooltips for now
samejr Sep 30, 2024
1ece140
Squashed commit of the following:
samejr Oct 1, 2024
67107d0
More support for custom-focus
samejr Oct 1, 2024
691ef3e
More custom focus styles added
samejr Oct 1, 2024
10070a4
Support for focus-visible style for the Segmented control
samejr Oct 2, 2024
2d53f6d
Fixed table triple dot menu z-index issue
samejr Oct 2, 2024
3a63585
Improved help menu wording
samejr Oct 2, 2024
cc41085
When you submit the help form, close the modal
samejr Oct 2, 2024
fa5a8f8
focus-visible style for radio buttons
samejr Oct 2, 2024
fab1e0d
Merge remote-tracking branch 'origin/main' into new-help-menu
samejr Oct 2, 2024
b95fb8a
button prop is now optional in the SideMenu component
samejr Oct 2, 2024
a066e04
focus styling for a text link
samejr Oct 10, 2024
d4ee186
Merge remote-tracking branch 'origin/main' into new-help-menu
samejr Oct 10, 2024
dfce7d5
Deleted unused sequin files
samejr Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 100 additions & 158 deletions apps/webapp/app/components/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { BookOpenIcon } from "@heroicons/react/20/solid";
import {
CalendarDaysIcon,
ChevronRightIcon,
EnvelopeIcon,
LifebuoyIcon,
LightBulbIcon,
} from "@heroicons/react/24/solid";
import { EnvelopeIcon, LightBulbIcon } from "@heroicons/react/24/solid";
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
import { DiscordIcon } from "@trigger.dev/companyicons";
import { ActivityIcon } from "lucide-react";
import { type ReactNode, useState } from "react";
import { type ReactNode, useState, useEffect } from "react";
import { type FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback";
import { cn } from "~/utils/cn";
import { docsTroubleshootingPath } from "~/utils/pathBuilder";
import { Button, LinkButton } from "./primitives/Buttons";
import { Button } from "./primitives/Buttons";
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog";
import { Fieldset } from "./primitives/Fieldset";
import { FormButtons } from "./primitives/FormButtons";
import { FormError } from "./primitives/FormError";
import { Header1 } from "./primitives/Headers";
import { Icon } from "./primitives/Icon";
import { InfoPanel } from "./primitives/InfoPanel";
import { InputGroup } from "./primitives/InputGroup";
import { Label } from "./primitives/Label";
import { Paragraph } from "./primitives/Paragraph";
import { Select, SelectItem } from "./primitives/Select";
import { Sheet, SheetBody, SheetContent, SheetTrigger } from "./primitives/Sheet";
import { TextArea } from "./primitives/TextArea";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { TextLink } from "./primitives/TextLink";

type FeedbackProps = {
button: ReactNode;
Expand All @@ -37,161 +29,111 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
const location = useLocation();
const lastSubmission = useActionData();
const navigation = useNavigation();
const [type, setType] = useState<FeedbackType>(defaultValue);

const [form, { path, feedbackType, message }] = useForm({
id: "accept-invite",
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
shouldRevalidate: "onInput",
});

if (
open &&
navigation.formAction === "/resources/feedback" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
useEffect(() => {
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
}, [navigation, form]);
Comment on lines +43 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure the useEffect dependencies are complete

In your useEffect hook, you're relying on navigation.formAction, navigation.state, form.error, and form.errors.length, but only navigation and form are specified in the dependency array. To prevent potential stale closures or missed updates, consider including all specific dependencies used within the effect.

Update the dependency array as follows:

}, [
-  navigation, form
+  navigation.formAction,
+  navigation.state,
+  form.error,
+  form.errors.length
]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
}, [navigation, form]);
useEffect(() => {
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
}, [
navigation.formAction,
navigation.state,
form.error,
form.errors.length
]);

🛠️ Refactor suggestion

Consider improving dialog close logic after form submission

Currently, the useEffect hook closes the dialog when the form is successfully submitted by checking if navigation.state === "loading". This might close the dialog prematurely or not at the intended time. Consider refining the condition to ensure the dialog closes only after the submission is successfully completed.

You could modify the useEffect hook as follows:

useEffect(() => {
  if (
-   navigation.formAction === "/resources/feedback" &&
-   navigation.state === "loading" &&
-   form.error === undefined &&
-   form.errors.length === 0
+   navigation.state === "idle" &&
+   lastSubmission &&
+   lastSubmission.type === "actionReload"
  ) {
    setOpen(false);
  }
- }, [navigation, form]);
+ }, [navigation.state, lastSubmission]);

This ensures that the dialog closes only when the navigation state is idle and there is a last submission, indicating the form was submitted successfully.

Committable suggestion was skipped due to low confidence.


return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild={true}>{button}</SheetTrigger>
<SheetContent className="@container">
<SheetBody className="flex h-full flex-col justify-between">
<LinkBanner
title="Join our Discord community"
icon={<DiscordIcon className="size-9" />}
to="https://trigger.dev/discord"
className="hover:border-text-link"
>
<Paragraph>The quickest way to get answers from the Trigger.dev community.</Paragraph>
</LinkBanner>
<LinkBanner
title="Book a 15 min chat with the founders"
icon={<CalendarDaysIcon className="size-9 text-green-500" />}
to="https://cal.com/team/triggerdotdev/founders-call"
className="hover:border-green-500"
>
<Paragraph>Have a question or want to chat? Book a time to talk with us.</Paragraph>
</LinkBanner>
<LinkBanner
title="Suggest a feature"
icon={<LightBulbIcon className="size-9 text-sun-500" />}
to="https://feedback.trigger.dev/"
className="hover:border-sun-400"
>
<Paragraph>Have an idea for a new feature or improvement? Let us know!</Paragraph>
</LinkBanner>
<LinkBanner
title="Troubleshooting"
icon={<LifebuoyIcon className="size-9 text-rose-500" />}
>
<Paragraph>
If you're having trouble, check out our troubleshooting guide or the Trigger.dev
Status page.
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{button}</DialogTrigger>
<DialogContent>
<DialogHeader>Contact us</DialogHeader>
<div className="mt-2 flex flex-col gap-4">
<div className="flex items-center gap-4">
<Icon icon={EnvelopeIcon} className="size-10 min-w-[2.5rem] text-blue-500" />
<Paragraph variant="base/bright">
How can we help? We read every message and will respond as quickly as we can.
</Paragraph>
<div className="flex flex-wrap gap-2">
<LinkButton
to={docsTroubleshootingPath("")}
variant="tertiary/medium"
LeadingIcon={BookOpenIcon}
>
Troubleshooting Docs
</LinkButton>
<LinkButton
to={"https://status.trigger.dev/"}
variant="tertiary/medium"
LeadingIcon={ActivityIcon}
>
Trigger.dev Status
</LinkButton>
</div>
</LinkBanner>
<LinkBanner
title="Send us an email"
icon={<EnvelopeIcon className="size-9 text-blue-500" />}
>
<Paragraph>We read every message and respond quickly.</Paragraph>
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<InputGroup className="max-w-full">
<Select
{...conform.select(feedbackType)}
variant="tertiary/medium"
defaultValue={defaultValue}
placeholder="Select type"
text={(value) => feedbackTypeLabel[value]}
dropdownIcon
</div>
<hr className="border-charcoal-800" />
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<InputGroup className="max-w-full">
{type === "feature" && (
<InfoPanel
icon={InformationCircleIcon}
iconClassName="text-blue-500"
panelClassName="w-full mb-2"
>
{Object.entries(feedbackTypeLabel).map(([name, title]) => (
<SelectItem key={name} value={name}>
{title}
</SelectItem>
))}
</Select>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
</InputGroup>
<InputGroup className="max-w-full">
<Label>Message</Label>
<TextArea {...conform.textarea(message)} />
<FormError id={message.errorId}>{message.error}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<div className="flex w-full justify-end">
<FormButtons
className="m-0 w-max"
confirmButton={
<Button type="submit" variant="tertiary/medium">
Send message
</Button>
}
/>
</div>
</Fieldset>
</Form>
</LinkBanner>
</SheetBody>
</SheetContent>
</Sheet>
);
}

function LinkBanner({
className,
icon,
title,
children,
to,
}: {
className?: string;
icon?: ReactNode;
title?: string;
children?: ReactNode;
to?: string;
}) {
return (
<a
href={to}
target="_blank"
className={cn(
"group/banner mb-4 flex w-full items-center justify-between rounded-md border border-grid-bright bg-charcoal-750 p-4 transition",
className
)}
>
<div className="flex w-full items-start gap-4">
<span>{icon}</span>
<div className="flex w-full flex-col gap-2">
<Header1 className="text-2xl font-semibold text-text-bright">{title}</Header1>
{children}
<Paragraph variant="small">
All our feature requests are public and voted on by the community. The best
way to submit your feature request is to{" "}
<TextLink to="https://feedback.trigger.dev">
post it to our feedback forum
</TextLink>
.
</Paragraph>
</InfoPanel>
)}
Comment on lines +71 to +86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor InfoPanel rendering to avoid code duplication

The conditional rendering blocks for type === "feature" and type === "help" are very similar, differing mainly in the content. Consider refactoring to reduce duplication by mapping type to the corresponding content.

Here's how you could refactor:

const infoPanelContent = {
  feature: {
    message: (
      <Paragraph variant="small">
        All our feature requests are public and voted on by the community. The best way to submit your feature request is to{" "}
        <TextLink to="https://feedback.trigger.dev">post it to our feedback forum</TextLink>.
      </Paragraph>
    ),
  },
  help: {
    message: (
      <Paragraph variant="small">
        The quickest way to get answers from the Trigger.dev team and community is to{" "}
        <TextLink to="https://trigger.dev/discord">ask in our Discord</TextLink>.
      </Paragraph>
    ),
  },
};

{["feature", "help"].includes(type) && (
  <InfoPanel
    icon={InformationCircleIcon}
    iconClassName="text-blue-500"
    panelClassName="w-full mb-2"
  >
    {infoPanelContent[type].message}
  </InfoPanel>
)}

This approach makes it cleaner and easier to add more types in the future.

Also applies to: 87-98

{type === "help" && (
<InfoPanel
icon={InformationCircleIcon}
iconClassName="text-blue-500"
panelClassName="w-full mb-2"
>
<Paragraph variant="small">
The quickest way to get answers from the Trigger.dev team and community is to{" "}
<TextLink to="https://trigger.dev/discord">ask in our Discord</TextLink>.
</Paragraph>
</InfoPanel>
)}
<Select
{...conform.select(feedbackType)}
variant="tertiary/medium"
value={type}
defaultValue={type}
setValue={(v) => setType(v as FeedbackType)}
placeholder="Select type"
text={(value) => feedbackTypeLabel[value as FeedbackType]}
dropdownIcon
>
{Object.entries(feedbackTypeLabel).map(([name, title]) => (
<SelectItem key={name} value={name}>
{title}
</SelectItem>
))}
</Select>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
</InputGroup>
<InputGroup className="max-w-full">
<Label>Message</Label>
<TextArea {...conform.textarea(message)} />
<FormError id={message.errorId}>{message.error}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<div className="flex w-full justify-end">
<FormButtons
className="m-0 w-max"
confirmButton={
<Button type="submit" variant="tertiary/medium">
Send message
</Button>
}
/>
</div>
</Fieldset>
</Form>
</div>
</div>
{to && (
<ChevronRightIcon className="size-5 text-charcoal-500 transition group-hover:translate-x-1 group-hover/banner:text-text-bright" />
)}
</a>
</DialogContent>
</Dialog>
);
}
2 changes: 1 addition & 1 deletion apps/webapp/app/components/billing/v2/FreePlanUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb
<ArrowUpCircleIcon className="h-5 w-5 text-text-dimmed" />
<Paragraph className="text-2sm text-text-bright">Free Plan</Paragraph>
</div>
<Link to={to} className="text-2sm text-text-link">
<Link to={to} className="text-2sm text-text-link focus-custom">
Upgrade
</Link>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/code/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
onMouseEnter={() => setMouseOver(true)}
onMouseLeave={() => setMouseOver(false)}
className={cn(
"absolute right-3 z-50 transition-colors duration-100 hover:cursor-pointer",
"absolute right-3 z-50 transition-colors duration-100 focus-custom hover:cursor-pointer",
showChrome ? "top-10" : "top-2.5",
copied ? "text-emerald-500" : "text-charcoal-500 hover:text-charcoal-300"
)}
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/navigation/AccountSideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function AccountSideMenu({ user }: { user: User }) {
<SideMenuItem
name="Your profile"
icon="account"
iconColor="text-indigo-500"
activeIconColor="text-indigo-500"
to={accountPath()}
data-action="account"
/>
Expand All @@ -49,7 +49,7 @@ export function AccountSideMenu({ user }: { user: User }) {
<SideMenuItem
name="Personal Access Tokens"
icon={ShieldCheckIcon}
iconColor="text-emerald-500"
activeIconColor="text-emerald-500"
to={personalAccessTokensPath()}
data-action="tokens"
/>
Expand Down
Loading
Loading