Skip to content
17 changes: 14 additions & 3 deletions apps/webapp/app/components/onboarding/TechnologyPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,22 @@ export function TechnologyPicker({

const addCustomValue = useCallback(() => {
const trimmed = otherInputValue.trim();
if (trimmed && !customValues.includes(trimmed) && !value.includes(trimmed)) {
if (!trimmed) return;

const matchedOption = TECHNOLOGY_OPTIONS.find(
(opt) => opt.toLowerCase() === trimmed.toLowerCase()
);

if (matchedOption) {
if (!value.includes(matchedOption)) {
onChange([...value, matchedOption]);
}
} else if (!customValues.includes(trimmed) && !value.includes(trimmed)) {
onCustomValuesChange([...customValues, trimmed]);
setOtherInputValue("");
}
}, [otherInputValue, customValues, onCustomValuesChange, value]);

setOtherInputValue("");
}, [otherInputValue, customValues, onCustomValuesChange, value, onChange]);

const handleOtherKeyDown = useCallback(
(e: React.KeyboardEvent) => {
Expand Down
133 changes: 82 additions & 51 deletions apps/webapp/app/components/primitives/Buttons.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { Link, type LinkProps, NavLink, type NavLinkProps } from "@remix-run/react";
import React, { forwardRef, type ReactNode, useImperativeHandle, useRef } from "react";
import React, {
forwardRef,
type ReactNode,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import { type ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
import { ShortcutKey } from "./ShortcutKey";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./Tooltip";
import { Icon, type RenderIcon } from "./Icon";
import { Spinner } from "./Spinner";

const sizes = {
small: {
Expand Down Expand Up @@ -180,6 +188,7 @@ export type ButtonContentPropsType = {
tooltip?: ReactNode;
iconSpacing?: string;
hideShortcutKey?: boolean;
isLoading?: boolean;
};

export function ButtonContent(props: ButtonContentPropsType) {
Expand All @@ -196,7 +205,19 @@ export function ButtonContent(props: ButtonContentPropsType) {
tooltip,
iconSpacing,
hideShortcutKey,
isLoading,
} = props;

const [showSpinner, setShowSpinner] = useState(false);
useEffect(() => {
if (!isLoading) {
setShowSpinner(false);
return;
}
const timer = setTimeout(() => setShowSpinner(true), 200);
return () => clearTimeout(timer);
}, [isLoading]);

const variation = allVariants.variant[props.variant];

const btnClassName = cn(allVariants.$all, variation.button);
Expand All @@ -217,56 +238,64 @@ export function ButtonContent(props: ButtonContentPropsType) {

const buttonContent = (
<div className={cn("flex", fullWidth ? "" : "w-fit text-xxs", btnClassName, className)}>
<div
className={cn(
textAlignLeft ? "text-left" : "justify-center",
"flex w-full items-center",
iconSpacingClassName,
iconSpacing
)}
>
{LeadingIcon && (
<Icon
icon={LeadingIcon}
className={cn(
iconClassName,
variation.icon,
leadingIconClassName,
"shrink-0 justify-start"
)}
/>
)}
<div className={cn("relative", "flex w-full items-center")}>
<div
className={cn(
textAlignLeft ? "text-left" : "justify-center",
"flex w-full items-center",
iconSpacingClassName,
iconSpacing,
showSpinner && "invisible"
)}
>
{LeadingIcon && (
<Icon
icon={LeadingIcon}
className={cn(
iconClassName,
variation.icon,
leadingIconClassName,
"shrink-0 justify-start"
)}
/>
)}

{text &&
(typeof text === "string" ? (
<span className={cn("mx-auto grow self-center truncate", textColorClassName)}>
{text}
</span>
) : (
<>{text}</>
))}

{shortcut &&
!tooltip &&
props.shortcutPosition === "before-trailing-icon" &&
renderShortcutKey()}

{TrailingIcon && (
<Icon
icon={TrailingIcon}
className={cn(
iconClassName,
variation.icon,
trailingIconClassName,
"shrink-0 justify-end"
)}
/>
)}
{text &&
(typeof text === "string" ? (
<span className={cn("mx-auto grow self-center truncate", textColorClassName)}>
{text}
</span>
) : (
<>{text}</>
))}

{shortcut &&
!tooltip &&
props.shortcutPosition === "before-trailing-icon" &&
renderShortcutKey()}

{shortcut &&
!tooltip &&
(!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") &&
renderShortcutKey()}
{TrailingIcon && (
<Icon
icon={TrailingIcon}
className={cn(
iconClassName,
variation.icon,
trailingIconClassName,
"shrink-0 justify-end"
)}
/>
)}

{shortcut &&
!tooltip &&
(!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") &&
renderShortcutKey()}
</div>
{showSpinner && (
<span className="absolute inset-0 flex items-center justify-center">
<Spinner className="size-3.5" color="white" />
</span>
)}
</div>
</div>
);
Expand Down Expand Up @@ -298,6 +327,8 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
const innerRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement);

const isDisabled = disabled || props.isLoading;

useShortcutKeys({
shortcut: props.shortcut,
action: (e) => {
Expand All @@ -307,14 +338,14 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
e.stopPropagation();
}
},
disabled: disabled || !props.shortcut,
disabled: isDisabled || !props.shortcut,
});

return (
<button
className={cn("group/button outline-none focus-custom", props.fullWidth ? "w-full" : "")}
type={type}
disabled={disabled}
disabled={isDisabled}
onClick={onClick}
name={props.name}
value={props.value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { Select, SelectItem } from "~/components/primitives/Select";
import { ButtonSpinner } from "~/components/primitives/Spinner";

import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
Expand Down Expand Up @@ -169,6 +169,8 @@ const schema = z.object({
technologiesOther: z.string().optional(),
goals: z.string().optional(),
goalsOther: z.string().optional(),
workingOnPositions: z.string().optional(),
goalsPositions: z.string().optional(),
});

export const action: ActionFunction = async ({ request, params }) => {
Expand Down Expand Up @@ -200,10 +202,25 @@ export const action: ActionFunction = async ({ request, params }) => {
}
}

const numberArraySchema = z.array(z.number());
function safeParseNumberArray(value: string | undefined): number[] | undefined {
if (!value) return undefined;
try {
const result = numberArraySchema.safeParse(JSON.parse(value));
return result.success && result.data.length > 0 ? result.data : undefined;
} catch {
return undefined;
}
}

const onboardingData: Record<string, Prisma.InputJsonValue> = {};

const workingOn = safeParseStringArray(submission.value.workingOn);
if (workingOn) onboardingData.workingOn = workingOn;
if (workingOn) {
onboardingData.workingOn = workingOn;
const workingOnPositions = safeParseNumberArray(submission.value.workingOnPositions);
if (workingOnPositions) onboardingData.workingOnPositions = workingOnPositions;
}

if (submission.value.workingOnOther) {
onboardingData.workingOnOther = submission.value.workingOnOther;
Expand All @@ -216,7 +233,11 @@ export const action: ActionFunction = async ({ request, params }) => {
if (technologiesOther) onboardingData.technologiesOther = technologiesOther;

const goals = safeParseStringArray(submission.value.goals);
if (goals) onboardingData.goals = goals;
if (goals) {
onboardingData.goals = goals;
const goalsPositions = safeParseNumberArray(submission.value.goalsPositions);
if (goalsPositions) onboardingData.goalsPositions = goalsPositions;
}

if (submission.value.goalsOther) {
onboardingData.goalsOther = submission.value.goalsOther;
Expand Down Expand Up @@ -376,6 +397,13 @@ export default function Page() {
<InputGroup>
<Label>What are you working on?</Label>
<input type="hidden" name="workingOn" value={JSON.stringify(selectedWorkingOn)} />
<input
type="hidden"
name="workingOnPositions"
value={JSON.stringify(
selectedWorkingOn.map((v) => shuffledWorkingOn.indexOf(v) + 1)
)}
/>
<MultiSelectField
value={selectedWorkingOn}
setValue={setSelectedWorkingOn}
Expand Down Expand Up @@ -421,6 +449,13 @@ export default function Page() {
<InputGroup>
<Label>What are you trying to do with Trigger.dev?</Label>
<input type="hidden" name="goals" value={JSON.stringify(selectedGoals)} />
<input
type="hidden"
name="goalsPositions"
value={JSON.stringify(
selectedGoals.map((v) => shuffledGoals.indexOf(v) + 1)
)}
/>
<MultiSelectField
value={selectedGoals}
setValue={setSelectedGoals}
Expand All @@ -445,13 +480,8 @@ export default function Page() {

<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
disabled={isLoading}
TrailingIcon={isLoading ? ButtonSpinner : undefined}
>
{isLoading ? "Creating…" : "Create"}
<Button type="submit" variant={"primary/small"} isLoading={isLoading}>
Create
</Button>
}
cancelButton={
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/routes/_app.orgs.new/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export default function NewOrganizationPage() {

<FormButtons
confirmButton={
<Button type="submit" variant={"primary/small"} disabled={isLoading}>
<Button type="submit" variant={"primary/small"} isLoading={isLoading}>
Create
</Button>
}
Expand Down
Loading