diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ce6dab5..43e2e1e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,8 @@ "Bash(git add:*)", "Bash(npm run lint:*)", "Bash(mv:*)", - "mcp__ide__getDiagnostics" + "mcp__ide__getDiagnostics", + "Bash(git checkout:*)" ], "deny": [] } diff --git a/CLAUDE.md b/CLAUDE.md index de2bb38..026c692 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,6 +109,7 @@ src/ - **Function Style**: Always use function declarations (`function name() {}`) instead of arrow function expressions (`const name = () => {}`) for components and named functions - **Dialog Components**: Always include both `DialogTitle` AND `DialogDescription` in `DialogHeader` to prevent accessibility warnings - **React Router**: Use future flags `v7_startTransition` and `v7_relativeSplatPath` in BrowserRouter to prepare for v7 upgrade +- **Component Extraction**: When a section of JSX + logic becomes substantial (>30 lines) or reusable, extract it into a separate component. Place in appropriate directory: page-specific components in `components/PageName/`, reusable ones in `components/` ### Important Notes diff --git a/src/components/Groups/AddMemberForm.tsx b/src/components/Groups/AddMemberForm.tsx new file mode 100644 index 0000000..2db4d89 --- /dev/null +++ b/src/components/Groups/AddMemberForm.tsx @@ -0,0 +1,96 @@ +import { useForm } from "react-hook-form"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { UserPlus } from "lucide-react"; +import { useInviteToGroupMutation } from "@/hooks/queries/useGroupsQuery"; + +interface AddMemberFormProps { + groupId: string; +} + +interface FormData { + usernameOrEmail: string; +} + +export function AddMemberForm({ groupId }: AddMemberFormProps) { + const inviteToGroupMutation = useInviteToGroupMutation(groupId); + + // Form for inviting members + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm(); + + async function onSubmitInvite(data: FormData) { + const input = data.usernameOrEmail.trim(); + // For email format, make it lowercase for case-insensitive matching + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const processedInput = emailRegex.test(input) ? input.toLowerCase() : input; + + inviteToGroupMutation.mutate(processedInput, { + onSuccess() { + reset(); // Clear the form on success + }, + }); + } + + return ( + + + + + Add Member + + + Invite someone to join this group by username or email + + + +
+
+ + + {errors.usernameOrEmail && ( +

+ {errors.usernameOrEmail.message} +

+ )} +
+ +
+
+
+ ); +} diff --git a/src/components/Groups/CreateGroupDialog.tsx b/src/components/Groups/CreateGroupDialog.tsx new file mode 100644 index 0000000..a4ee7e9 --- /dev/null +++ b/src/components/Groups/CreateGroupDialog.tsx @@ -0,0 +1,134 @@ +import { useForm } from "react-hook-form"; +// Removed unused useState import +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useCreateGroupMutation } from "@/hooks/queries/useGroupsQuery"; +import { useAuth } from "@/contexts/AuthContext"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Users } from "lucide-react"; + +interface CreateGroupDialogProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + onGroupCreated: (groupId: string) => void; +} + +interface FormData { + name: string; + description: string; +} +export function CreateGroupDialog({ + isOpen, + onOpenChange, + onGroupCreated, +}: CreateGroupDialogProps) { + const { user } = useAuth(); + const createGroupMutation = useCreateGroupMutation(); + // No local creating state needed; use createGroupMutation.isLoading + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm(); + + function onSubmit(data: FormData) { + if (!user?.id) return; + createGroupMutation.mutate( + { + name: data.name.trim(), + description: data.description.trim() || undefined, + userId: user.id, + }, + { + onSuccess: (group) => { + if (group && group.id) { + reset(); + onOpenChange(false); + onGroupCreated(group.id); + } + }, + }, + ); + } + + function handleOpenChange(open: boolean) { + if (!open) { + reset(); // Clear form when closing + } + onOpenChange(open); + } + + return ( + + + + + + Create New Group + + + Create a group to share and compare votes with friends + + + +
+
+ + + {errors.name && ( +

{errors.name.message}

+ )} +
+ +
+ +