diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 3c9b389750..5a5472a976 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -1,8 +1,9 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { EnvelopeIcon, LightBulbIcon } from "@heroicons/react/24/solid"; +import { InformationCircleIcon } from "@heroicons/react/20/solid"; +import { EnvelopeIcon } from "@heroicons/react/24/solid"; import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react"; -import { type ReactNode, useState, useEffect } from "react"; +import { type ReactNode, useEffect, useState } from "react"; import { type FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback"; import { Button } from "./primitives/Buttons"; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog"; @@ -16,7 +17,6 @@ import { Label } from "./primitives/Label"; import { Paragraph } from "./primitives/Paragraph"; import { Select, SelectItem } from "./primitives/Select"; import { TextArea } from "./primitives/TextArea"; -import { InformationCircleIcon } from "@heroicons/react/20/solid"; import { TextLink } from "./primitives/TextLink"; type FeedbackProps = { diff --git a/apps/webapp/app/components/primitives/TextArea.tsx b/apps/webapp/app/components/primitives/TextArea.tsx index 7d543e2ec5..f5350a510b 100644 --- a/apps/webapp/app/components/primitives/TextArea.tsx +++ b/apps/webapp/app/components/primitives/TextArea.tsx @@ -8,7 +8,7 @@ export function TextArea({ className, rows, ...props }: TextAreaProps) { {...props} rows={rows ?? 6} className={cn( - "placeholder:text-muted-foreground w-full rounded-md border border-tertiary bg-tertiary px-3 text-sm text-text-bright transition focus-custom file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 focus:border-transparent focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50", + "placeholder:text-muted-foreground w-full rounded border border-charcoal-800 bg-charcoal-750 px-3 text-sm text-text-bright transition focus-custom focus-custom file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 hover:bg-charcoal-650 disabled:cursor-not-allowed disabled:opacity-50", className )} /> diff --git a/apps/webapp/app/routes/_app.orgs.new/route.tsx b/apps/webapp/app/routes/_app.orgs.new/route.tsx index d99286dd05..571c6163be 100644 --- a/apps/webapp/app/routes/_app.orgs.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.new/route.tsx @@ -4,6 +4,7 @@ import { RadioGroup } from "@radix-ui/react-radio-group"; import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; import { Form, useActionData, useNavigation } from "@remix-run/react"; +import { uiComponent } from "@team-plain/typescript-sdk"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { MainCenteredContainer } from "~/components/layout/AppLayout"; @@ -17,21 +18,25 @@ import { Input } from "~/components/primitives/Input"; import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; import { RadioGroupItem } from "~/components/primitives/RadioButton"; +import { TextArea } from "~/components/primitives/TextArea"; import { useFeatures } from "~/hooks/useFeatures"; import { createOrganization } from "~/models/organization.server"; import { NewOrganizationPresenter } from "~/presenters/NewOrganizationPresenter.server"; -import { requireUserId } from "~/services/session.server"; +import { logger } from "~/services/logger.server"; +import { requireUser, requireUserId } from "~/services/session.server"; import { organizationPath, rootPath } from "~/utils/pathBuilder"; +import { sendToPlain } from "~/utils/plain.server"; const schema = z.object({ orgName: z.string().min(3).max(50), companySize: z.string().optional(), + whyUseUs: z.string().optional(), }); export const loader = async ({ request }: LoaderFunctionArgs) => { const userId = await requireUserId(request); const presenter = new NewOrganizationPresenter(); - const { hasOrganizations } = await presenter.call({ userId }); + const { hasOrganizations } = await presenter.call({ userId: userId }); return typedjson({ hasOrganizations, @@ -39,8 +44,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { }; export const action: ActionFunction = async ({ request }) => { - const userId = await requireUserId(request); - + const user = await requireUser(request); const formData = await request.formData(); const submission = parse(formData, { schema }); @@ -51,10 +55,41 @@ export const action: ActionFunction = async ({ request }) => { try { const organization = await createOrganization({ title: submission.value.orgName, - userId, + userId: user.id, companySize: submission.value.companySize ?? null, }); + const whyUseUs = formData.get("whyUseUs"); + + if (whyUseUs) { + try { + await sendToPlain({ + userId: user.id, + email: user.email, + name: user.name ?? user.displayName ?? user.email, + title: "New org feedback", + components: [ + uiComponent.text({ + text: `${submission.value.orgName} just created a new organization.`, + }), + uiComponent.divider({ spacingSize: "M" }), + uiComponent.text({ + size: "L", + color: "NORMAL", + text: "What problem are you trying to solve?", + }), + uiComponent.text({ + size: "L", + color: "NORMAL", + text: whyUseUs.toString(), + }), + ], + }); + } catch (error) { + logger.error("Error sending data to Plain when creating an org:", { error }); + } + } + return redirect(organizationPath(organization)); } catch (error: any) { return json({ errors: { body: error.message } }, { status: 400 }); @@ -97,39 +132,48 @@ export default function NewOrganizationPage() { {orgName.error} {isManagedCloud && ( - - - - - - - - - + <> + + + + + + + + + + + +