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
12 changes: 6 additions & 6 deletions frontend/components/create-group/BulkImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input";
import { AlertCircle, X } from "lucide-react";
import Papa from "papaparse";
import { isValidStellarAddress } from "@/utils/stellarAddress";
import { MAX_POOL_MEMBERS } from "@/lib/constants";

type Member = {
address: string;
Expand All @@ -24,8 +25,6 @@ export default function BulkImport({ onMembersChange }: BulkImportProps) {
const [members, setMembers] = useState<Member[]>([]);
const [errors, setErrors] = useState<string[]>([]);

const MAX_MEMBERS = 50;

const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
Expand All @@ -49,12 +48,13 @@ export default function BulkImport({ onMembersChange }: BulkImportProps) {
}
parsed.push({ address, name, line: lineNum });
});
if (parsed.length > MAX_MEMBERS) {
errorLines.push(`Too many members (${parsed.length}). The maximum allowed is ${MAX_MEMBERS}.`);
if (parsed.length > MAX_POOL_MEMBERS) {
errorLines.push(`Too many members (${parsed.length}). The maximum allowed is ${MAX_POOL_MEMBERS}.`);
}
setMembers(parsed);
const acceptedMembers = parsed.slice(0, MAX_POOL_MEMBERS);
setMembers(acceptedMembers);
setErrors(errorLines);
onMembersChange(parsed.map((m) => m.address));
onMembersChange(acceptedMembers.map((m) => m.address));
},
error: (err) => {
setErrors([`Parsing error: ${err.message}`]);
Expand Down
22 changes: 20 additions & 2 deletions frontend/components/create-group/flexible-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
validatePositiveAmount,
validateWithdrawalFee,
} from "@/lib/form-validation"
import { MAX_POOL_MEMBERS } from "@/lib/constants"

function isValidStellarAddress(addr: string) {
return /^G[A-Z2-7]{55}$/.test(addr)
Expand Down Expand Up @@ -56,6 +57,7 @@ export function FlexibleForm() {
const allMembers = address ? [address, ...members] : members
const validMembers = Array.from(new Set(allMembers.filter(isValidStellarAddress)))
const isCreating = step !== "idle"
const isMemberLimitReached = members.length >= MAX_POOL_MEMBERS

const validateField = useCallback((name: keyof FieldErrors, value: string) => {
let message = ""
Expand All @@ -77,7 +79,11 @@ export function FlexibleForm() {
setMemberErrors(errs)
}

const addMember = () => { setMembers([...members, ""]); setMemberErrors([...memberErrors, ""]) }
const addMember = () => {
if (isMemberLimitReached) return
setMembers([...members, ""])
setMemberErrors([...memberErrors, ""])
}
const removeMember = (i: number) => {
setMembers(members.filter((_, idx) => idx !== i))
setMemberErrors(memberErrors.filter((_, idx) => idx !== i))
Expand Down Expand Up @@ -313,10 +319,22 @@ export function FlexibleForm() {
tooltip="Add the public Stellar address (starts with G) for each person joining this pool. You are automatically included."
required
/>
<Button type="button" variant="outline" size="sm" onClick={addMember}>
<Button
type="button"
variant="outline"
size="sm"
onClick={addMember}
disabled={isMemberLimitReached}
aria-describedby={isMemberLimitReached ? "flexible-member-limit" : undefined}
>
<Plus className="h-4 w-4 mr-1" />Add Member
</Button>
</div>
{isMemberLimitReached && (
<p id="flexible-member-limit" className="text-xs text-muted-foreground">
Maximum of {MAX_POOL_MEMBERS} members reached
</p>
)}

<div className="space-y-3">
<div className="space-y-1">
Expand Down
17 changes: 16 additions & 1 deletion frontend/components/create-group/rotational-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
validatePositiveAmount,
} from "@/lib/form-validation"
import type { DuplicatePrefill } from "@/app/dashboard/create/[type]/page"
import { MAX_POOL_MEMBERS } from "@/lib/constants"

function isValidStellarAddress(addr: string) {
return /^G[A-Z2-7]{55}$/.test(addr)
Expand Down Expand Up @@ -76,6 +77,7 @@ export function RotationalForm({ prefill }: { prefill?: DuplicatePrefill }) {
const allMembers = address ? [address, ...members] : members
const validMembers = Array.from(new Set(allMembers.filter(isValidStellarAddress)))
const isCreating = step !== "idle"
const isMemberLimitReached = members.length >= MAX_POOL_MEMBERS

const validateField = useCallback((name: keyof FieldErrors, value: string) => {
const result =
Expand Down Expand Up @@ -103,6 +105,7 @@ export function RotationalForm({ prefill }: { prefill?: DuplicatePrefill }) {
}

const addMember = () => {
if (isMemberLimitReached) return
setMembers([...members, ""])
setMemberErrors([...memberErrors, ""])
}
Expand Down Expand Up @@ -330,10 +333,22 @@ export function RotationalForm({ prefill }: { prefill?: DuplicatePrefill }) {
tooltip="Add the public Stellar address (starts with G) for each person joining this pool. You are automatically included as the first member."
required
/>
<Button type="button" variant="outline" size="sm" onClick={addMember}>
<Button
type="button"
variant="outline"
size="sm"
onClick={addMember}
disabled={isMemberLimitReached}
aria-describedby={isMemberLimitReached ? "rotational-member-limit" : undefined}
>
<Plus className="h-4 w-4 mr-1" />Add Member
</Button>
</div>
{isMemberLimitReached && (
<p id="rotational-member-limit" className="text-xs text-muted-foreground">
Maximum of {MAX_POOL_MEMBERS} members reached
</p>
)}

<div className="space-y-3">
{/* Creator — always included, read-only */}
Expand Down
22 changes: 20 additions & 2 deletions frontend/components/create-group/target-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
validatePositiveAmount,
validateDeadline,
} from "@/lib/form-validation"
import { MAX_POOL_MEMBERS } from "@/lib/constants"

function isValidStellarAddress(addr: string) {
return /^G[A-Z2-7]{55}$/.test(addr)
Expand Down Expand Up @@ -62,6 +63,7 @@ export function TargetForm() {
const allMembers = address ? [address, ...members] : members
const validMembers = Array.from(new Set(allMembers.filter(isValidStellarAddress)))
const isCreating = step !== "idle"
const isMemberLimitReached = members.length >= MAX_POOL_MEMBERS

const validateField = useCallback((name: keyof FieldErrors, value: string) => {
let message = ""
Expand All @@ -83,7 +85,11 @@ export function TargetForm() {
setMemberErrors(errs)
}

const addMember = () => { setMembers([...members, ""]); setMemberErrors([...memberErrors, ""]) }
const addMember = () => {
if (isMemberLimitReached) return
setMembers([...members, ""])
setMemberErrors([...memberErrors, ""])
}
const removeMember = (i: number) => {
setMembers(members.filter((_, idx) => idx !== i))
setMemberErrors(memberErrors.filter((_, idx) => idx !== i))
Expand Down Expand Up @@ -297,10 +303,22 @@ export function TargetForm() {
tooltip="Add the public Stellar address (starts with G) for each person joining this pool. You are automatically included."
required
/>
<Button type="button" variant="outline" size="sm" onClick={addMember}>
<Button
type="button"
variant="outline"
size="sm"
onClick={addMember}
disabled={isMemberLimitReached}
aria-describedby={isMemberLimitReached ? "target-member-limit" : undefined}
>
<Plus className="h-4 w-4 mr-1" />Add Member
</Button>
</div>
{isMemberLimitReached && (
<p id="target-member-limit" className="text-xs text-muted-foreground">
Maximum of {MAX_POOL_MEMBERS} members reached
</p>
)}

<div className="space-y-3">
<div className="space-y-1">
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_POOL_MEMBERS = 50
Loading