Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 1 addition & 5 deletions apps/webapp/app/components/primitives/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const CheckboxWithLabel = React.forwardRef<HTMLInputElement, CheckboxProp
ref
) => {
const [isChecked, setIsChecked] = useState<boolean>(defaultChecked ?? false);
const [isDisabled, setIsDisabled] = useState<boolean>(disabled ?? false);
const isDisabled = disabled ?? false;
const onChangeRef = React.useRef(onChange);
const generatedId = React.useId();
const inputId = id ?? generatedId;
Expand All @@ -102,10 +102,6 @@ export const CheckboxWithLabel = React.forwardRef<HTMLInputElement, CheckboxProp
const isDisabledClassName = variants[variant].isDisabled;
const inputPositionClasses = variants[variant].inputPosition;

useEffect(() => {
setIsDisabled(disabled ?? false);
}, [disabled]);

useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
Expand Down
11 changes: 3 additions & 8 deletions apps/webapp/app/components/primitives/ClientTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,13 @@ const ClientTabs = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ onValueChange, value: valueProp, defaultValue, ...props }, ref) => {
const [value, setValue] = React.useState<string | undefined>(valueProp ?? defaultValue);

React.useEffect(() => {
if (valueProp !== undefined) {
setValue(valueProp);
}
}, [valueProp]);
const [internalValue, setInternalValue] = React.useState<string | undefined>(defaultValue);
const value = valueProp ?? internalValue;

const handleValueChange = React.useCallback(
(nextValue: string) => {
if (valueProp === undefined) {
setValue(nextValue);
setInternalValue(nextValue);
}
onValueChange?.(nextValue);
},
Expand Down
23 changes: 7 additions & 16 deletions apps/webapp/app/components/runs/v3/RunTagInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState, useEffect, type KeyboardEvent } from "react";
import { useCallback, useState, type KeyboardEvent } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Input } from "~/components/primitives/Input";
import { RunTag } from "./RunTag";
Expand Down Expand Up @@ -26,39 +26,30 @@ export function RunTagInput({
maxTagLength = 128,
onTagsChange,
}: TagInputProps) {
// Use controlled tags if provided, otherwise use default
const initialTags = controlledTags ?? defaultTags;

const [tags, setTags] = useState<string[]>(initialTags);
const [internalTags, setInternalTags] = useState<string[]>(defaultTags);
const tags = controlledTags ?? internalTags;
const [inputValue, setInputValue] = useState("");

// Sync internal state with external tag changes
useEffect(() => {
if (controlledTags !== undefined) {
setTags(controlledTags);
}
}, [controlledTags]);

const addTag = useCallback(
(tagText: string) => {
const trimmedTag = tagText.trim();
if (trimmedTag && !tags.includes(trimmedTag) && tags.length < maxTags) {
const newTags = [...tags, trimmedTag];
setTags(newTags);
if (controlledTags === undefined) setInternalTags(newTags);
onTagsChange?.(newTags);
}
setInputValue("");
},
[tags, onTagsChange, maxTags]
[tags, controlledTags, onTagsChange, maxTags]
);

const removeTag = useCallback(
(tagToRemove: string) => {
const newTags = tags.filter((tag) => tag !== tagToRemove);
setTags(newTags);
if (controlledTags === undefined) setInternalTags(newTags);
onTagsChange?.(newTags);
},
[tags, onTagsChange]
[tags, controlledTags, onTagsChange]
);

const handleKeyDown = useCallback(
Expand Down
16 changes: 10 additions & 6 deletions apps/webapp/app/components/schedules/PurchaseSchedulesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,16 @@ export function PurchaseSchedulesModal({
const isLoading = fetcher.state !== "idle";

const [open, setOpen] = useState(false);
// Reset the bundle stepper to the user's current extra-schedules count on
// each open. Earlier this only re-synced when `extraSchedules`/`stepSize`
// props changed, so if the user opened the modal, typed a value, cancelled,
// and reopened without purchasing, the stale draft persisted.
// Reset the bundle stepper to the user's current extra-schedules count on each open.
const handleOpenChange = (nextOpen: boolean) => {
if (nextOpen) setBundles(Math.round(extraSchedules / stepSize));
setOpen(nextOpen);
};
Comment thread
carderne marked this conversation as resolved.

useEffect(() => {
if (open) setBundles(Math.round(extraSchedules / stepSize));
if (!open) return;
// oxlint-disable-next-line react/react-compiler -- Keep the open draft aligned with authoritative billing values.
setBundles(Math.round(extraSchedules / stepSize));
}, [open, extraSchedules, stepSize]);

useEffect(() => {
Expand Down Expand Up @@ -113,7 +117,7 @@ export function PurchaseSchedulesModal({
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
{triggerButton ?? (
<Button variant="primary/small" onClick={() => setOpen(true)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
};

export default function Page() {
const [isOpen, setIsOpen] = useState(false);
const { slack, option, emailAlertsEnabled } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const navigation = useNavigation();
Expand Down Expand Up @@ -274,10 +273,6 @@ export default function Page() {
shouldRevalidate: "onSubmit",
});

useEffect(() => {
setIsOpen(true);
}, []);

useEffect(() => {
if (navigation.state !== "idle") return;
if (lastSubmission !== undefined) return;
Expand All @@ -287,7 +282,7 @@ export default function Page() {

return (
<Dialog
open={isOpen}
open
onOpenChange={(o) => {
if (!o) {
navigate(v3ProjectAlertsPath(organization, project, environment));
Expand Down