Skip to content

Commit 231b176

Browse files
committed
Added title and button to toasts. Use it for new project error
1 parent af557b7 commit 231b176

File tree

4 files changed

+66
-35
lines changed

4 files changed

+66
-35
lines changed

apps/webapp/app/components/Feedback.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { conform, useForm } from "@conform-to/react";
22
import { parse } from "@conform-to/zod";
33
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
44
import { EnvelopeIcon } from "@heroicons/react/24/solid";
5-
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
5+
import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react";
66
import { type ReactNode, useEffect, useState } from "react";
77
import { type FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback";
88
import { Button } from "./primitives/Buttons";
@@ -23,10 +23,12 @@ import { DialogClose } from "@radix-ui/react-dialog";
2323
type FeedbackProps = {
2424
button: ReactNode;
2525
defaultValue?: FeedbackType;
26+
onOpenChange?: (open: boolean) => void;
2627
};
2728

28-
export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
29+
export function Feedback({ button, defaultValue = "bug", onOpenChange }: FeedbackProps) {
2930
const [open, setOpen] = useState(false);
31+
const [searchParams, setSearchParams] = useSearchParams();
3032
const location = useLocation();
3133
const lastSubmission = useActionData();
3234
const navigation = useNavigation();
@@ -52,8 +54,26 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
5254
}
5355
}, [navigation, form]);
5456

57+
// Handle URL param functionality
58+
useEffect(() => {
59+
const open = searchParams.get("feedbackPanel");
60+
if (open) {
61+
setType(open as FeedbackType);
62+
setOpen(true);
63+
// Clone instead of mutating in place
64+
const next = new URLSearchParams(searchParams);
65+
next.delete("feedbackPanel");
66+
setSearchParams(next);
67+
}
68+
}, [searchParams]);
69+
70+
const handleOpenChange = (value: boolean) => {
71+
setOpen(value);
72+
onOpenChange?.(value);
73+
};
74+
5575
return (
56-
<Dialog open={open} onOpenChange={setOpen}>
76+
<Dialog open={open} onOpenChange={handleOpenChange}>
5777
<DialogTrigger asChild>{button}</DialogTrigger>
5878
<DialogContent>
5979
<DialogHeader>Contact us</DialogHeader>

apps/webapp/app/components/primitives/Toast.tsx

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { EnvelopeIcon, ExclamationCircleIcon, XMarkIcon } from "@heroicons/react/20/solid";
22
import { CheckCircleIcon } from "@heroicons/react/24/solid";
3-
import { Toaster, toast } from "sonner";
3+
import { useSearchParams } from "@remix-run/react";
4+
import { useEffect } from "react";
45
import { useTypedLoaderData } from "remix-typedjson";
6+
import { Toaster, toast } from "sonner";
7+
import { type ToastMessageAction } from "~/models/message.server";
58
import { type loader } from "~/root";
6-
import { useEffect } from "react";
7-
import { Paragraph } from "./Paragraph";
89
import { cn } from "~/utils/cn";
9-
import { type ToastMessageAction } from "~/models/message.server";
10-
import { Header2, Header3 } from "./Headers";
1110
import { Button, LinkButton } from "./Buttons";
12-
import { Feedback } from "../Feedback";
13-
import assertNever from "assert-never";
14-
import { assertExhaustive } from "@trigger.dev/core";
11+
import { Header2 } from "./Headers";
12+
import { Paragraph } from "./Paragraph";
1513

1614
const defaultToastDuration = 5000;
1715
const permanentToastDuration = 60 * 60 * 24 * 1000;
@@ -78,14 +76,14 @@ export function ToastUI({
7876
<ExclamationCircleIcon className="mt-1 size-4 min-w-4 text-error" />
7977
)}
8078
<div className="flex flex-col">
81-
{title && <Header2 className="pt-1">{title}</Header2>}
82-
<Paragraph variant="small/dimmed" className="py-1">
79+
{title && <Header2 className="pt-0">{title}</Header2>}
80+
<Paragraph variant="small/dimmed" className="pb-1 pt-0.5">
8381
{message}
8482
</Paragraph>
85-
<Action action={action} toastId={t} />
83+
<Action action={action} toastId={t} className="my-2" />
8684
</div>
8785
<button
88-
className="hover:bg-midnight-800 ms-auto rounded p-2 text-text-dimmed transition hover:text-text-bright"
86+
className="hover:bg-midnight-800 -mr-1 -mt-1 ms-auto rounded p-2 text-text-dimmed transition hover:text-text-bright"
8987
onClick={() => toast.dismiss(t)}
9088
>
9189
<XMarkIcon className="size-4" />
@@ -95,37 +93,48 @@ export function ToastUI({
9593
);
9694
}
9795

98-
function Action({ action, toastId }: { action?: ToastMessageAction; toastId: string }) {
96+
function Action({
97+
action,
98+
toastId,
99+
className,
100+
}: {
101+
action?: ToastMessageAction;
102+
toastId: string;
103+
className?: string;
104+
}) {
105+
const [_, setSearchParams] = useSearchParams();
106+
99107
if (!action) return null;
100108

101109
switch (action.action.type) {
102110
case "link": {
103111
return (
104-
<LinkButton variant={action.variant ?? "secondary/small"} to={action.action.path}>
112+
<LinkButton
113+
className={className}
114+
variant={action.variant ?? "secondary/small"}
115+
to={action.action.path}
116+
>
105117
{action.label}
106118
</LinkButton>
107119
);
108120
}
109121
case "help": {
122+
const feedbackType = action.action.feedbackType;
110123
return (
111-
<Feedback
112-
button={
113-
<Button
114-
variant={action.variant ?? "secondary/small"}
115-
LeadingIcon={EnvelopeIcon}
116-
onClick={(e) => {
117-
e.preventDefault();
118-
toast.dismiss(toastId);
119-
}}
120-
>
121-
{action.label}
122-
</Button>
123-
}
124-
/>
124+
<Button
125+
className={className}
126+
variant={action.variant ?? "secondary/small"}
127+
LeadingIcon={EnvelopeIcon}
128+
onClick={() => {
129+
setSearchParams({
130+
feedbackPanel: feedbackType,
131+
});
132+
toast.dismiss(toastId);
133+
}}
134+
>
135+
{action.label}
136+
</Button>
125137
);
126138
}
127-
default: {
128-
return null;
129-
}
130139
}
131140
}

apps/webapp/app/models/project.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export async function createProject(
6363

6464
if (projectCount >= organization.maximumProjectCount) {
6565
throw new ExceededProjectLimitError(
66-
`Organization ${organization.slug} has reached the maximum number of projects (${organization.maximumProjectCount}). You can request more by contacting help in the bottom-left.`
66+
`This organization has reached the maximum number of projects (${organization.maximumProjectCount}).`
6767
);
6868
}
6969

apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
88
import invariant from "tiny-invariant";
99
import { z } from "zod";
1010
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
11+
import { Feedback } from "~/components/Feedback";
1112
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
1213
import { Button, LinkButton } from "~/components/primitives/Buttons";
1314
import { Callout } from "~/components/primitives/Callout";
@@ -213,6 +214,7 @@ export default function Page() {
213214
</Fieldset>
214215
</Form>
215216
</div>
217+
<Feedback button={<></>} />
216218
</MainCenteredContainer>
217219
</BackgroundWrapper>
218220
</AppContainer>

0 commit comments

Comments
 (0)