Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,20 @@ import {
SelectValue,
} from "@agentset/ui/select";

// Email validation helper
const isValidEmail = (email: string): boolean => {
if (!email) return false;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
Comment on lines +33 to +38
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use Zod for validation and move static content to end of file.

This implementation violates two coding guidelines:

  1. Form validation: The guidelines specify "Use Zod for form validation" for files matching apps/web/**/*.{ts,tsx}. Using regex for email validation is fragile and doesn't provide the type safety and composability that Zod offers.

  2. Code organization: The guidelines require "Place static content (consts) and TypeScript interfaces at the end of the file" for files in apps/web/**/*.{ts,tsx}.

As per coding guidelines

Consider refactoring to use Zod with react-hook-form for better validation and error handling:

+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+
+const inviteSchema = z.object({
+  email: z.string().email("Please enter a valid email address"),
+  role: z.enum(["admin", "member"]),
+});
+
+type InviteFormData = z.infer<typeof inviteSchema>;

Then move any remaining static helpers to the end of the file, after the component definition.

Committable suggestion skipped: line range outside the PR's diff.


function InviteMemberDialog() {
const [open, setOpen] = useState(false);
const { id, isAdmin } = useOrganization();

const [email, setEmail] = useState("");
const [role, setRole] = useState("member");
const [emailError, setEmailError] = useState("");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Manual error state increases complexity.

This manual error state contradicts the guideline to "Minimize client-side state." Using Zod with react-hook-form would eliminate the need for this separate state variable, as the form library manages validation state internally.

As per coding guidelines

🤖 Prompt for AI Agents
In apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/team/invite-dialog.tsx
around line 46, the component uses a manual emailError useState which duplicates
validation state; replace this with react-hook-form + Zod validation: define a
Zod schema for the email field, pass zodResolver(schema) into useForm, remove
the emailError state and any manual setEmailError calls, and rely on
formState.errors.email to show validation messages and control submit flow so
validation is centralized and client state minimized.


const trpc = useTRPC();
const queryClient = useQueryClient();
Expand Down Expand Up @@ -69,6 +77,12 @@ function InviteMemberDialog() {
});

const handleInvite = () => {

if (!email || !isValidEmail(email)) {
setEmailError("Please enter a valid email address");
return;
}

logEvent("team_invite_member_clicked", {
organizationId: id,
email,
Expand All @@ -82,8 +96,18 @@ function InviteMemberDialog() {
});
};

const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen);

if (!isOpen) {
setEmail("");
setEmailError("");
setRole("member");
}
};

return (
<Dialog open={open} onOpenChange={setOpen}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild disabled={!isAdmin}>
<Button>
<PlusIcon className="size-4" />
Expand All @@ -102,10 +126,24 @@ function InviteMemberDialog() {
<div className="flex flex-col gap-2">
<Label>Email</Label>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => {
const newEmail = e.target.value;
setEmail(newEmail);

if (newEmail && !isValidEmail(newEmail)) {
setEmailError("Please enter a valid email address");
} else {
setEmailError("");
}
}}
className={emailError ? "border-red-500" : ""}
/>
{emailError && (
<p className="text-sm text-red-500">{emailError}</p>
)}
</div>

<div className="flex flex-col gap-2">
Expand All @@ -122,7 +160,11 @@ function InviteMemberDialog() {
</div>
</div>
<DialogFooter>
<Button isLoading={isPending} onClick={handleInvite}>
<Button
isLoading={isPending}
onClick={handleInvite}
disabled={!email || !!emailError || isPending}
>
Invite
</Button>
</DialogFooter>
Expand Down