Skip to content

Commit 659945c

Browse files
committed
small improvements to MultiValueInput, toast, zod schema
1 parent 714a8bf commit 659945c

File tree

3 files changed

+24
-11
lines changed

3 files changed

+24
-11
lines changed

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,13 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) {
135135

136136
if (invitedCount > 0) {
137137
parts.push(
138-
invitedCount === 1
139-
? "Invitation sent to 1 partner."
140-
: `Invitations sent to ${invitedCount} partners.`,
138+
`${pluralize("Invitation", invitedCount)} sent to ${invitedCount} ${pluralize("partner", invitedCount)}.`,
141139
);
142140
}
143141

144142
if (skippedCount > 0) {
145143
parts.push(
146-
`${skippedCount} ${pluralize("partner", skippedCount)} were skipped because they're already enrolled or previously invited.`,
144+
`Skipped ${skippedCount} ${pluralize("partner", skippedCount)} because they're already enrolled or previously invited.`,
147145
);
148146
}
149147

apps/web/lib/zod/schemas/partners.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ export const bulkInvitePartnersSchema = z.object({
742742
groupId: z.string().nullish(),
743743
emails: z
744744
.array(z.email().trim().min(1).max(100))
745+
.min(1)
745746
.max(MAX_PARTNERS_INVITES_PER_REQUEST),
746747
});
747748

packages/ui/src/multi-value-input.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,7 @@ const MultiValueInput = React.forwardRef<
9090

9191
const addValues = useCallback(
9292
(candidates: string[]) => {
93-
const normalized = candidates
94-
.map(normalize)
95-
.filter(Boolean)
96-
.filter((v) => !values.includes(v));
93+
const normalized = candidates.map(normalize).filter(Boolean);
9794
if (normalized.length === 0) return values;
9895
const next = [...values];
9996
for (const v of normalized) {
@@ -105,6 +102,16 @@ const MultiValueInput = React.forwardRef<
105102
[values, normalize, maxValues],
106103
);
107104

105+
/** Deduplicate preserving first occurrence order; used only on blur. */
106+
const deduplicateValues = useCallback((list: string[]): string[] => {
107+
const seen = new Set<string>();
108+
return list.filter((v) => {
109+
if (seen.has(v)) return false;
110+
seen.add(v);
111+
return true;
112+
});
113+
}, []);
114+
108115
const commitPendingInput = useCallback((): string[] => {
109116
const parsed = parseCsvLikeValues(inputValue);
110117
if (parsed.length === 0) {
@@ -216,6 +223,9 @@ const MultiValueInput = React.forwardRef<
216223

217224
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
218225
const pasted = e.clipboardData.getData("text");
226+
const hasDelimiter = /[,\n\r]/.test(pasted);
227+
if (!hasDelimiter) return;
228+
219229
const parsed = parseCsvLikeValues(pasted);
220230
if (parsed.length === 0) return;
221231

@@ -227,7 +237,11 @@ const MultiValueInput = React.forwardRef<
227237

228238
const handleBlur = () => {
229239
setSelectedValue(null);
230-
commitPendingInput();
240+
const afterCommit = commitPendingInput();
241+
const deduped = deduplicateValues(afterCommit);
242+
if (deduped.length !== afterCommit.length) {
243+
onChange(deduped);
244+
}
231245
};
232246

233247
const removeValue = (value: string) => {
@@ -245,9 +259,9 @@ const MultiValueInput = React.forwardRef<
245259
className,
246260
)}
247261
>
248-
{values.map((value) => (
262+
{values.map((value, index) => (
249263
<span
250-
key={value}
264+
key={`${value}-${index}`}
251265
onClick={() => setSelectedValue(value)}
252266
className={cn(
253267
"inline-flex items-center gap-1 rounded-md py-0.5 pl-1.5 pr-1 text-sm leading-6",

0 commit comments

Comments
 (0)