diff --git a/apps/webapp/app/components/onboarding/TechnologyPicker.tsx b/apps/webapp/app/components/onboarding/TechnologyPicker.tsx
index 3d822e4b69..9c9f9a6b24 100644
--- a/apps/webapp/app/components/onboarding/TechnologyPicker.tsx
+++ b/apps/webapp/app/components/onboarding/TechnologyPicker.tsx
@@ -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) => {
diff --git a/apps/webapp/app/components/primitives/Buttons.tsx b/apps/webapp/app/components/primitives/Buttons.tsx
index 5c52a5d95d..8ba196b5dc 100644
--- a/apps/webapp/app/components/primitives/Buttons.tsx
+++ b/apps/webapp/app/components/primitives/Buttons.tsx
@@ -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: {
@@ -180,6 +188,7 @@ export type ButtonContentPropsType = {
tooltip?: ReactNode;
iconSpacing?: string;
hideShortcutKey?: boolean;
+ isLoading?: boolean;
};
export function ButtonContent(props: ButtonContentPropsType) {
@@ -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);
@@ -217,56 +238,64 @@ export function ButtonContent(props: ButtonContentPropsType) {
const buttonContent = (
-
- {LeadingIcon && (
-
- )}
+
+
+ {LeadingIcon && (
+
+ )}
- {text &&
- (typeof text === "string" ? (
-
- {text}
-
- ) : (
- <>{text}>
- ))}
-
- {shortcut &&
- !tooltip &&
- props.shortcutPosition === "before-trailing-icon" &&
- renderShortcutKey()}
-
- {TrailingIcon && (
-
- )}
+ {text &&
+ (typeof text === "string" ? (
+
+ {text}
+
+ ) : (
+ <>{text}>
+ ))}
+
+ {shortcut &&
+ !tooltip &&
+ props.shortcutPosition === "before-trailing-icon" &&
+ renderShortcutKey()}
- {shortcut &&
- !tooltip &&
- (!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") &&
- renderShortcutKey()}
+ {TrailingIcon && (
+
+ )}
+
+ {shortcut &&
+ !tooltip &&
+ (!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") &&
+ renderShortcutKey()}
+
+ {showSpinner && (
+
+
+
+ )}
);
@@ -298,6 +327,8 @@ export const Button = forwardRef
(
const innerRef = useRef(null);
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement);
+ const isDisabled = disabled || props.isLoading;
+
useShortcutKeys({
shortcut: props.shortcut,
action: (e) => {
@@ -307,14 +338,14 @@ export const Button = forwardRef(
e.stopPropagation();
}
},
- disabled: disabled || !props.shortcut,
+ disabled: isDisabled || !props.shortcut,
});
return (