Skip to content

Commit 720d960

Browse files
fix: don't allow adding external emails if no email identity is associated (#711)
* fix(new-convo): empty email identity handled * chore(new-convo): disabled for new contacts * fix: email disappears after typing even if disabled --------- Co-authored-by: BlankParticle <[email protected]>
1 parent ab7105c commit 720d960

File tree

1 file changed

+76
-62
lines changed

1 file changed

+76
-62
lines changed

apps/web/src/app/[orgShortcode]/convo/_components/create-convo-form.tsx

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ export default function CreateConvoForm({
573573
<div className="flex w-full flex-col gap-2 text-sm">
574574
<h4 className="font-bold">Participants</h4>
575575
<ParticipantsComboboxPopover
576+
disabled={userEmailIdentities?.emailIdentities.length === 0}
576577
participants={allParticipants}
577578
loading={allParticipantsLoaded}
578579
selectedParticipants={selectedParticipants}
@@ -646,39 +647,49 @@ export default function CreateConvoForm({
646647
<SelectValue placeholder="Select an email address" />
647648
</SelectTrigger>
648649
<SelectContent>
649-
{userEmailIdentities?.emailIdentities.map((email) => (
650-
<SelectItem
651-
key={email.publicId}
652-
value={email.publicId}
653-
className="[&>span:last-child]:w-full">
654-
<span
655-
className={cn(
656-
'flex !min-w-0 items-center justify-between',
657-
!email.sendingEnabled && 'text-base-11'
658-
)}>
659-
<span className="truncate">
660-
{`${email.sendName} (${email.username}@${email.domainName})`}
650+
{userEmailIdentities?.emailIdentities.length == 0 ? (
651+
<span
652+
className={cn(
653+
'text-base-11 flex !min-w-0 items-center justify-between truncate text-sm'
654+
)}>
655+
{`No email identities available`}
656+
</span>
657+
) : (
658+
userEmailIdentities?.emailIdentities.map((email) => (
659+
<SelectItem
660+
key={email.publicId}
661+
value={email.publicId}
662+
className="[&>span:last-child]:w-full">
663+
<span
664+
className={cn(
665+
'flex !min-w-0 items-center justify-between',
666+
!email.sendingEnabled && 'text-base-11'
667+
)}>
668+
<span className="truncate">
669+
{`${email.sendName} (${email.username}@${email.domainName})`}
670+
</span>
671+
{!email.sendingEnabled && (
672+
<Tooltip>
673+
<TooltipTrigger>
674+
<Question size={14} />
675+
</TooltipTrigger>
676+
<TooltipContent className="flex flex-col">
677+
<span>
678+
Sending from this email identity is
679+
disabled.
680+
</span>
681+
<span>
682+
{isAdmin
683+
? 'Please check that the DNS records are correctly set up.'
684+
: 'Please contact your admin for assistance.'}
685+
</span>
686+
</TooltipContent>
687+
</Tooltip>
688+
)}
661689
</span>
662-
{!email.sendingEnabled && (
663-
<Tooltip>
664-
<TooltipTrigger>
665-
<Question size={14} />
666-
</TooltipTrigger>
667-
<TooltipContent className="flex flex-col">
668-
<span>
669-
Sending from this email identity is disabled.
670-
</span>
671-
<span>
672-
{isAdmin
673-
? 'Please check that the DNS records are correctly set up.'
674-
: 'Please contact your admin for assistance.'}
675-
</span>
676-
</TooltipContent>
677-
</Tooltip>
678-
)}
679-
</span>
680-
</SelectItem>
681-
))}
690+
</SelectItem>
691+
))
692+
)}
682693
</SelectContent>
683694
</Select>
684695
</div>
@@ -740,9 +751,16 @@ export default function CreateConvoForm({
740751
);
741752
}
742753

754+
const showDisabledMessage = () => {
755+
toast.warning(
756+
'You cannot add participants to conversation until you have an email identity associated.'
757+
);
758+
};
759+
743760
type ParticipantsComboboxPopoverProps = {
744761
participants: NewConvoParticipant[];
745762
loading: boolean;
763+
disabled?: boolean;
746764
selectedParticipants: NewConvoParticipant[];
747765
setSelectedParticipants: Dispatch<SetStateAction<NewConvoParticipant[]>>;
748766
setNewEmailParticipants: Dispatch<SetStateAction<string[]>>;
@@ -751,6 +769,7 @@ type ParticipantsComboboxPopoverProps = {
751769
function ParticipantsComboboxPopover({
752770
participants,
753771
loading,
772+
disabled = false,
754773
selectedParticipants,
755774
setSelectedParticipants,
756775
setNewEmailParticipants
@@ -768,6 +787,7 @@ function ParticipantsComboboxPopover({
768787
);
769788

770789
const addEmailParticipant = (email: string) => {
790+
if (disabled) return;
771791
setNewEmailParticipants((prev) =>
772792
prev.includes(email) ? prev : prev.concat(email)
773793
);
@@ -795,29 +815,11 @@ function ParticipantsComboboxPopover({
795815
onOpenChange={setOpen}>
796816
<PopoverTrigger asChild>
797817
<Button
798-
variant={'outline'}
818+
variant="outline"
799819
className="h-fit w-full justify-between">
800820
{selectedParticipants.length > 0 ? (
801821
<div className="flex flex-wrap gap-2 overflow-hidden">
802822
{selectedParticipants.map((participant, i) => {
803-
let info = '';
804-
switch (participant.type) {
805-
case 'orgMember':
806-
info = participant.name;
807-
break;
808-
case 'team':
809-
info = participant.name;
810-
break;
811-
case 'contact':
812-
info = participant.name
813-
? `${participant.name} (${participant.address!})`
814-
: participant.address!;
815-
break;
816-
case 'email':
817-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
818-
info = participant.address!;
819-
break;
820-
}
821823
return (
822824
<div
823825
key={participant.publicId}
@@ -838,9 +840,13 @@ function ParticipantsComboboxPopover({
838840
hideTooltip
839841
/>
840842
<p className="text-base-11 text-sm">
841-
{participant.own && participant.own
843+
{participant.own
842844
? 'You (already a participant)'
843-
: `${participant.name} ${participant.title ? `(${participant.title})` : ''}`}
845+
: `${participant.name} ${
846+
participant.title
847+
? `(${participant.title})`
848+
: ''
849+
}`}
844850
</p>
845851
<AvatarIcon
846852
avatarProfilePublicId={
@@ -889,22 +895,19 @@ function ParticipantsComboboxPopover({
889895
value={search}
890896
onChange={(e) => setSearch(e.target.value)}
891897
onKeyDown={(e) => {
892-
// Hack to prevent cmdk from preventing Home and End keys
893898
if (e.key === 'Home' || e.key === 'End') {
894899
e.stopPropagation();
895900
}
896901
if (e.key === 'Enter') {
902+
if (disabled) return showDisabledMessage();
897903
if (z.string().email().safeParse(search).success) {
898904
addEmailParticipant(search);
899905
setCurrentSelectValue('');
900906
setSearch('');
901907
}
902908
}
903909
}}
904-
onFocus={() => {
905-
// Remove current select value when input is focused
906-
setCurrentSelectValue('');
907-
}}
910+
onFocus={() => setCurrentSelectValue('')}
908911
/>
909912
</CommandInput>
910913
<CommandList className="max-h-[calc(var(--radix-popover-content-available-height)*0.9)] overflow-x-clip overflow-y-scroll">
@@ -914,6 +917,7 @@ function ParticipantsComboboxPopover({
914917
<EmptyStateHandler
915918
addSelectedParticipant={setSelectedParticipants}
916919
setEmailParticipants={setNewEmailParticipants}
920+
disabled={disabled}
917921
/>
918922
)}
919923
{participants.map((participant) => (
@@ -931,7 +935,7 @@ function ParticipantsComboboxPopover({
931935
}}>
932936
<HoverCard>
933937
<Button
934-
variant={'ghost'}
938+
variant="ghost"
935939
className={cn(
936940
'my-1 w-full items-center justify-start gap-2 px-1',
937941
selectedParticipants.find(
@@ -964,9 +968,13 @@ function ParticipantsComboboxPopover({
964968
hideTooltip
965969
/>
966970
<p className="text-base-11 text-sm">
967-
{participant.own && participant.own
971+
{participant.own
968972
? 'You (already a participant)'
969-
: `${participant.name} ${participant.title ? `(${participant.title})` : ''}`}
973+
: `${participant.name} ${
974+
participant.title
975+
? `(${participant.title})`
976+
: ''
977+
}`}
970978
</p>
971979
</HoverCardTrigger>
972980
<AvatarIcon
@@ -1022,11 +1030,13 @@ function ParticipantsComboboxPopover({
10221030
type EmptyStateHandlerProps = {
10231031
setEmailParticipants: Dispatch<SetStateAction<string[]>>;
10241032
addSelectedParticipant: Dispatch<SetStateAction<NewConvoParticipant[]>>;
1033+
disabled?: boolean;
10251034
};
10261035

10271036
function EmptyStateHandler({
10281037
setEmailParticipants,
1029-
addSelectedParticipant
1038+
addSelectedParticipant,
1039+
disabled = false
10301040
}: EmptyStateHandlerProps) {
10311041
const isEmpty = useCommandState((state) => state.filtered.count === 0);
10321042
const email = useCommandState((state) => state.search);
@@ -1036,6 +1046,7 @@ function EmptyStateHandler({
10361046
);
10371047

10381048
const addEmailParticipant = (email: string) => {
1049+
if (disabled) return;
10391050
setEmailParticipants((prev) =>
10401051
prev.includes(email) ? prev : prev.concat(email)
10411052
);
@@ -1060,10 +1071,12 @@ function EmptyStateHandler({
10601071
<CommandItem
10611072
key={email}
10621073
value={email}
1074+
disabled={disabled}
10631075
forceMount
10641076
onKeyDown={(e) => {
10651077
// Submit email on Enter key
10661078
if (e.key === 'Enter') {
1079+
if (disabled) return showDisabledMessage();
10671080
addEmailParticipant(email);
10681081
}
10691082
}}>
@@ -1072,6 +1085,7 @@ function EmptyStateHandler({
10721085
className="my-1 w-full justify-start px-1"
10731086
color="gray"
10741087
onClick={() => {
1088+
if (disabled) return showDisabledMessage();
10751089
addEmailParticipant(email);
10761090
}}>
10771091
<At className="mr-2 h-4 w-4" />

0 commit comments

Comments
 (0)