Initial work on feedback form
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Button } from "./primitives/Buttons";
|
||||
import {
|
||||
Sheet,
|
||||
SheetBody,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTrigger,
|
||||
} from "./primitives/Sheet";
|
||||
import { Header1 } from "./primitives/Headers";
|
||||
import { NamedIconInBox } from "./primitives/NamedIcon";
|
||||
import { Paragraph } from "./primitives/Paragraph";
|
||||
import {
|
||||
Form,
|
||||
useActionData,
|
||||
useLocation,
|
||||
useNavigation,
|
||||
} from "@remix-run/react";
|
||||
import { Fieldset } from "./primitives/Fieldset";
|
||||
import { InputGroup } from "./primitives/InputGroup";
|
||||
import { Label } from "./primitives/Label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./primitives/Select";
|
||||
import { FormButtons } from "./primitives/FormButtons";
|
||||
import { conform, useForm } from "@conform-to/react";
|
||||
import { parse } from "@conform-to/zod";
|
||||
import { schema } from "~/routes/resources.feedback";
|
||||
import { FormError } from "./primitives/FormError";
|
||||
import { Input } from "./primitives/Input";
|
||||
import { TextArea } from "./primitives/TextArea";
|
||||
import { useState } from "react";
|
||||
import { set } from "jsonpointer";
|
||||
|
||||
export function Feedback() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
const lastSubmission = useActionData();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [form, { redirectPath, feedbackType, message }] = useForm({
|
||||
id: "accept-invite",
|
||||
lastSubmission,
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, { schema });
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
open &&
|
||||
navigation.formAction === "/resources/feedback" &&
|
||||
form.error === undefined &&
|
||||
form.errors.length === 0
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild={true}>
|
||||
<Button
|
||||
variant="secondary/small"
|
||||
LeadingIcon={ChatBubbleLeftRightIcon}
|
||||
shortcut={{ key: "f" }}
|
||||
onClick={() => console.log("feedback")}
|
||||
>
|
||||
Send us feedback
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader className="justify-between">Give us feedback</SheetHeader>
|
||||
<SheetBody>
|
||||
<Paragraph variant="small" className="mb-4">
|
||||
We'd love to hear your feedback, good, bad or ugly.
|
||||
</Paragraph>
|
||||
<Form method="post" action="/resources/feedback" {...form.props}>
|
||||
<Fieldset>
|
||||
<input
|
||||
value={location.pathname}
|
||||
{...conform.input(redirectPath, { type: "hidden" })}
|
||||
/>
|
||||
<InputGroup>
|
||||
<Label>What kind of feedback do you have?</Label>
|
||||
<SelectGroup>
|
||||
<Select {...conform.input(feedbackType)} defaultValue="bug">
|
||||
<SelectTrigger size="medium" width="full">
|
||||
<SelectValue placeholder="Type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="bug">Bug report</SelectItem>
|
||||
<SelectItem value="feature">Feature request</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SelectGroup>
|
||||
<FormError id={feedbackType.errorId}>
|
||||
{feedbackType.error}
|
||||
</FormError>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<Label>Message</Label>
|
||||
<TextArea {...conform.textarea(message)} />
|
||||
<FormError id={message.errorId}>{message.error}</FormError>
|
||||
</InputGroup>
|
||||
<FormError>{form.error}</FormError>
|
||||
<FormButtons
|
||||
confirmButton={
|
||||
<Button type="submit" variant="primary/medium">
|
||||
Send
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Fieldset>
|
||||
</Form>
|
||||
</SheetBody>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { BookOpenIcon } from "@heroicons/react/20/solid";
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { Fragment } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { LogoIcon } from "../LogoIcon";
|
||||
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
|
||||
import { Button, LinkButton } from "../primitives/Buttons";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
import { Breadcrumb } from "./Breadcrumb";
|
||||
|
||||
export function NavBar() {
|
||||
@@ -27,15 +26,7 @@ export function NavBar() {
|
||||
>
|
||||
Documentation
|
||||
</LinkButton>
|
||||
<Button
|
||||
variant="secondary/small"
|
||||
data-attr="posthog-feedback-button"
|
||||
LeadingIcon={ChatBubbleLeftRightIcon}
|
||||
shortcut={{ key: "f" }}
|
||||
onClick={() => console.log("feedback")}
|
||||
>
|
||||
Send us feedback
|
||||
</Button>
|
||||
<Feedback />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {};
|
||||
|
||||
export function TextArea({ className, rows, ...props }: TextAreaProps) {
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
rows={rows ?? 6}
|
||||
className={cn(
|
||||
"w-full rounded-md border border-slate-800 bg-slate-850 px-3 text-sm text-bright ring-offset-background transition file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground hover:border-slate-750 hover:bg-slate-800 focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ const EnvironmentSchema = z.object({
|
||||
REPLY_TO_EMAIL: z.string(),
|
||||
RESEND_API_KEY: z.string(),
|
||||
SESSION_SECRET: z.string(),
|
||||
PLAIN_API_KEY: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Environment = z.infer<typeof EnvironmentSchema>;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { parse } from "@conform-to/zod";
|
||||
import { ActionArgs, json } from "@remix-run/server-runtime";
|
||||
import { PlainClient } from "@team-plain/typescript-sdk";
|
||||
import { z } from "zod";
|
||||
import { env } from "~/env.server";
|
||||
import { redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import { CreateEndpointError } from "~/services/endpoints/createEndpoint.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
|
||||
let client: PlainClient | undefined;
|
||||
|
||||
const feedbackType = z.union([z.literal("bug"), z.literal("feature")], {
|
||||
required_error: "Must be either 'bug' or 'feature'",
|
||||
invalid_type_error: "Must be either 'bug' or 'feature'",
|
||||
});
|
||||
|
||||
export const schema = z.object({
|
||||
redirectPath: z.string(),
|
||||
feedbackType,
|
||||
message: z.string().min(1, "Must be at least 1 character"),
|
||||
});
|
||||
|
||||
export async function action({ request }: ActionArgs) {
|
||||
const userId = await requireUserId(request);
|
||||
|
||||
const formData = await request.formData();
|
||||
const submission = parse(formData, { schema });
|
||||
|
||||
if (!submission.value || submission.intent !== "submit") {
|
||||
return json(submission);
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.PLAIN_API_KEY) {
|
||||
client = new PlainClient({
|
||||
apiKey: env.PLAIN_API_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
submission.value.redirectPath,
|
||||
request,
|
||||
"Feedback submitted"
|
||||
);
|
||||
} catch (e) {
|
||||
return json(e, { status: 400 });
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@
|
||||
"@remix-run/server-runtime": "1.16.1",
|
||||
"@sentry/remix": "^7.53.1",
|
||||
"@tanstack/react-table": "^8.0.0-alpha.87",
|
||||
"@team-plain/typescript-sdk": "^2.2.0",
|
||||
"@trigger.dev/companyicons": "^1.5.6",
|
||||
"@trigger.dev/database": "workspace:*",
|
||||
"@trigger.dev/internal": "workspace:*",
|
||||
|
||||
Generated
+35
-1
@@ -99,6 +99,7 @@ importers:
|
||||
'@tailwindcss/forms': ^0.5.3
|
||||
'@tailwindcss/typography': ^0.5.9
|
||||
'@tanstack/react-table': ^8.0.0-alpha.87
|
||||
'@team-plain/typescript-sdk': ^2.2.0
|
||||
'@testing-library/cypress': ^8.0.3
|
||||
'@testing-library/dom': ^8.18.1
|
||||
'@testing-library/jest-dom': ^5.16.5
|
||||
@@ -272,6 +273,7 @@ importers:
|
||||
'@remix-run/server-runtime': 1.16.1
|
||||
'@sentry/remix': 7.53.1_535ee7qxxe65mhmlpnplklpcuu
|
||||
'@tanstack/react-table': 8.7.6_biqbaboplfbrettd7655fr4n2y
|
||||
'@team-plain/typescript-sdk': 2.2.0
|
||||
'@trigger.dev/companyicons': 1.5.6_biqbaboplfbrettd7655fr4n2y
|
||||
'@trigger.dev/database': link:../../packages/database
|
||||
'@trigger.dev/internal': link:../../packages/internal
|
||||
@@ -5613,6 +5615,14 @@ packages:
|
||||
resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==}
|
||||
dev: false
|
||||
|
||||
/@graphql-typed-document-node/core/3.2.0_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
dependencies:
|
||||
graphql: 16.6.0
|
||||
dev: false
|
||||
|
||||
/@hapi/boom/10.0.1:
|
||||
resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==}
|
||||
dependencies:
|
||||
@@ -9593,6 +9603,17 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/@team-plain/typescript-sdk/2.2.0:
|
||||
resolution: {integrity: sha512-ZNRZJ1uhQYHqaAKNAMdcSvULxutDQldocJWEDxJ5CJX4QYyzD8adK7gB9szKQDsxHFA3sZgTjtSruFBOf+C2fw==}
|
||||
dependencies:
|
||||
'@graphql-typed-document-node/core': 3.2.0_graphql@16.6.0
|
||||
axios: 1.4.0
|
||||
graphql: 16.6.0
|
||||
zod: 3.21.4
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/@testing-library/cypress/8.0.7_cypress@10.11.0:
|
||||
resolution: {integrity: sha512-3HTV725rOS+YHve/gD9coZp/UcPK5xhr4H0GMnq/ni6USdtzVtSOG9WBFtd8rYnrXk8rrGD+0toRFYouJNIG0Q==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
@@ -11384,6 +11405,16 @@ packages:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/axios/1.4.0:
|
||||
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
|
||||
dependencies:
|
||||
follow-redirects: 1.15.2
|
||||
form-data: 4.0.0
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/axobject-query/3.1.1:
|
||||
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
|
||||
dependencies:
|
||||
@@ -15706,7 +15737,6 @@ packages:
|
||||
/graphql/16.6.0:
|
||||
resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
|
||||
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
|
||||
dev: true
|
||||
|
||||
/gunzip-maybe/1.4.2:
|
||||
resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
|
||||
@@ -24254,6 +24284,10 @@ packages:
|
||||
resolution: {integrity: sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==}
|
||||
dev: false
|
||||
|
||||
/zod/3.21.4:
|
||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||
dev: false
|
||||
|
||||
/zwitch/2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
dev: true
|
||||
|
||||
Reference in New Issue
Block a user