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 onboarding question #1404

Merged
merged 20 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
2 changes: 1 addition & 1 deletion apps/webapp/app/components/primitives/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
)}
/>
Expand Down
144 changes: 109 additions & 35 deletions apps/webapp/app/routes/_app.orgs.new/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ 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 { requireUser, requireUserId } from "~/services/session.server";
import { organizationPath, rootPath } from "~/utils/pathBuilder";
import { PlainClient, uiComponent } from "@team-plain/typescript-sdk";
import { env } from "~/env.server";

const schema = z.object({
orgName: z.string().min(3).max(50),
companySize: z.string().optional(),
whyUseUs: z.string().optional(),
matt-aitken marked this conversation as resolved.
Show resolved Hide resolved
});

export const loader = async ({ request }: LoaderFunctionArgs) => {
Expand All @@ -40,7 +44,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {

export const action: ActionFunction = async ({ request }) => {
const userId = await requireUserId(request);
Copy link
Member

Choose a reason for hiding this comment

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

You don't need this if you have the requireUser


const user = await requireUser(request);
const formData = await request.formData();
const submission = parse(formData, { schema });

Expand All @@ -55,6 +59,67 @@ export const action: ActionFunction = async ({ request }) => {
companySize: submission.value.companySize ?? null,
});

if (env.PLAIN_API_KEY) {
const client = new PlainClient({
Copy link
Member

Choose a reason for hiding this comment

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

Create a new plain.server.ts file, maybe in the webapp utilities folder.

It would be:

export async function sendToPlain({ userId, email, orgName, title, components}: Input) {
  const client = new ......
}

Then in this particular case you would wrap this in try/catch so it doesn't stop people creating orgs if Plain is down.

apiKey: env.PLAIN_API_KEY,
});

const whyUseUs = formData.get("whyUseUs");

matt-aitken marked this conversation as resolved.
Show resolved Hide resolved
const upsertCustomerRes = await client.upsertCustomer({
identifier: {
emailAddress: user.email,
},
onCreate: {
externalId: userId,
fullName: submission.value.orgName,
email: {
email: user.email,
isVerified: true,
},
},
onUpdate: {
externalId: { value: userId },
fullName: { value: submission.value.orgName },
email: {
email: user.email,
isVerified: true,
},
},
});

if (upsertCustomerRes.error) {
console.error("Failed to upsert customer in Plain", upsertCustomerRes.error);
} else if (whyUseUs) {
const createThreadRes = await client.createThread({
customerIdentifier: {
customerId: upsertCustomerRes.data.customer.id,
},
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(),
}),
],
});

if (createThreadRes.error) {
console.error("Failed to create thread in Plain", createThreadRes.error);
}
}
}

return redirect(organizationPath(organization));
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
Expand Down Expand Up @@ -97,39 +162,48 @@ export default function NewOrganizationPage() {
<FormError id={orgName.errorId}>{orgName.error}</FormError>
</InputGroup>
{isManagedCloud && (
<InputGroup>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
label="1-5"
value={"1-5"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-6-49"
label="6-49"
value={"6-49"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-50-99"
label="50-99"
value={"50-99"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-100+"
label="100+"
value={"100+"}
variant="button/small"
className="grow"
/>
</RadioGroup>
</InputGroup>
<>
<InputGroup>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
label="1-5"
value={"1-5"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-6-49"
label="6-49"
value={"6-49"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-50-99"
label="50-99"
value={"50-99"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-100+"
label="100+"
value={"100+"}
variant="button/small"
className="grow"
/>
</RadioGroup>
</InputGroup>
matt-aitken marked this conversation as resolved.
Show resolved Hide resolved
matt-aitken marked this conversation as resolved.
Show resolved Hide resolved
<InputGroup>
<Label htmlFor={"whyUseUs"}>What problem are you trying to solve?</Label>
<TextArea name="whyUseUs" rows={4} spellCheck={false} />
<Hint>
Your answer will help us understand your use case and provide better support.
</Hint>
</InputGroup>
matt-aitken marked this conversation as resolved.
Show resolved Hide resolved
</>
Comment on lines +135 to +176
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

LGTM with suggestion: Form updates for new feedback field

The changes to the form look good:

  • The new TextArea component for the whyUseUs field is correctly implemented.
  • The layout structure using a fragment to group the companySize and whyUseUs inputs is appropriate.
  • The hint text provides clear guidance to the user.

However, there's one improvement that can be made:

Add error handling for the whyUseUs field to be consistent with other form fields. Apply this change:

  <InputGroup>
    <Label htmlFor={"whyUseUs"}>What problem are you trying to solve?</Label>
-   <TextArea name="whyUseUs" rows={4} spellCheck={false} />
+   <TextArea {...conform.textarea(whyUseUs)} rows={4} spellCheck={false} />
    <Hint>
      Your answer will help us understand your use case and provide better support.
    </Hint>
+   <FormError id={whyUseUs.errorId}>{whyUseUs.error}</FormError>
  </InputGroup>

Also, update the form hook to include whyUseUs:

- const [form, { orgName }] = useForm({
+ const [form, { orgName, whyUseUs }] = useForm({
  // ... (rest of the code remains the same)
});

These changes will ensure that any validation errors for the whyUseUs field are properly displayed to the user, maintaining consistency with other form fields.

📝 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
<>
<InputGroup>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
label="1-5"
value={"1-5"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-6-49"
label="6-49"
value={"6-49"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-50-99"
label="50-99"
value={"50-99"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-100+"
label="100+"
value={"100+"}
variant="button/small"
className="grow"
/>
</RadioGroup>
</InputGroup>
<InputGroup>
<Label htmlFor={"whyUseUs"}>What problem are you trying to solve?</Label>
<TextArea name="whyUseUs" rows={4} spellCheck={false} />
<Hint>
Your answer will help us understand your use case and provide better support.
</Hint>
</InputGroup>
</>
<>
<InputGroup>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
label="1-5"
value={"1-5"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-6-49"
label="6-49"
value={"6-49"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-50-99"
label="50-99"
value={"50-99"}
variant="button/small"
className="grow"
/>
<RadioGroupItem
id="employees-100+"
label="100+"
value={"100+"}
variant="button/small"
className="grow"
/>
</RadioGroup>
</InputGroup>
<InputGroup>
<Label htmlFor={"whyUseUs"}>What problem are you trying to solve?</Label>
<TextArea {...conform.textarea(whyUseUs)} rows={4} spellCheck={false} />
<Hint>
Your answer will help us understand your use case and provide better support.
</Hint>
<FormError id={whyUseUs.errorId}>{whyUseUs.error}</FormError>
</InputGroup>
</>

)}

<FormButtons
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/routes/confirm-basic-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export default function Page() {
<Label htmlFor={confirmEmail.id}>How did you hear about us?</Label>
<Input
{...conform.input(referralSource, { type: "text" })}
placeholder="Google, Twitter…?"
placeholder="Google, X (Twitter)…?"
icon="heart"
spellCheck={false}
/>
Expand Down
17 changes: 17 additions & 0 deletions apps/webapp/app/routes/storybook.textarea/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Input } from "~/components/primitives/Input";
import { TextArea } from "~/components/primitives/TextArea";

export default function Story() {
return (
<div className="flex gap-16">
<div>
<div className="m-8 flex w-64 flex-col gap-4">
<TextArea placeholder="6 rows (default)" autoFocus />
<Input placeholder="Input" />
<TextArea placeholder="3 rows" rows={3} />
<TextArea disabled placeholder="Disabled" />
</div>
</div>
</div>
);
}
12 changes: 8 additions & 4 deletions apps/webapp/app/routes/storybook/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,20 @@ const stories: Story[] = [
slug: "date-fields",
},
{
name: "Simple form",
slug: "simple-form",
name: "Input fields",
slug: "input-fields",
},
{
name: "Search fields",
slug: "search-fields",
},
{
name: "Input fields",
slug: "input-fields",
name: "Simple form",
slug: "simple-form",
},
{
name: "Textarea",
slug: "textarea",
},
{
sectionTitle: "Menus",
Expand Down