-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add invite modal #4009
base: master
Are you sure you want to change the base?
feat: add invite modal #4009
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CzarekDryl Nice work, thank you for the PR, working as expected. Just a couple of points of feedback.
- The "Up to 100 people can join" is hard coded, can the number
100
be dynamic based on the actual invites available.
}, | ||
modalDescription: { | ||
id: `${displayName}.modalDescription`, | ||
defaultMessage: | ||
'You can invite up to 100 members to your Colony during the private beta using the invite link below.', | ||
'Up to 100 people can join to follow and be apart of this colony during early access. If you exceed the limit, you can request more.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the 100 be dynamic based on actual limit returned from DB.
/> | ||
) : ( | ||
<Link | ||
to={`https://colony.io/request-member-invites?colony=${colonyName}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be added to the const file with all other URLs so that it can easily be updated.
@arrenv All the things have been fixed and are it's ready for review again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @CzarekDryl really nice start on this issue 💯
The modal is working as expected
Screen.Recording.2025-01-07.at.12.24.28.mov
However I left some refactoring comments and found a couple of misalignments with Figma
if you could please be so kind to tackle them 🙌
{!isActionSidebarOpen && isWalletConnected && user && isInColony ? ( | ||
<Button | ||
text={isMobile ? undefined : MSG.invite} | ||
mode="tertiary" | ||
icon={UserPlus} | ||
size="small" | ||
isFullRounded | ||
className="md:hover:!border-blue-400" | ||
onClick={() => setIsInviteMembersModalOpen(true)} | ||
/> | ||
) : null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to add iconSize={16}
here according to Figma
icon={UserPlus} | ||
size="small" | ||
isFullRounded | ||
className="md:hover:!border-blue-400" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And please add gap-1
@@ -54,43 +76,76 @@ const InviteMembersModal = ({ isOpen, onClose }: Props) => { | |||
`/invite/${colonyName}/${colonyMemberInvite?.id}`, | |||
); | |||
const invitesUsed = 100 - (colonyMemberInvite?.invitesRemaining || 0); | |||
const isOutOfInvites = invitesUsed >= invitesAvailable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could make use of the const { totalMemberCount } = useMemberContext();
here to compute how many invites are used and colonyMemberInvite?.invitesRemaining
for deciding how many invites are available, thus replacing lines 74-79
with
const inviteLink = useBaseUrl(
`/invite/${colonyName}/${colonyMemberInvite?.id}`,
);
const { totalMemberCount } = useMemberContext();
const invitesAvailable = colonyMemberInvite?.invitesRemaining || 0;
const invitesUsed = totalMemberCount;
const isOutOfInvites = invitesUsed >= invitesAvailable;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arrenv do we want to hardcode the invites limit to 100
?
I think it would be best to actually use the stored remaining invites, as well as the number of existing followers and decide based on that if we are or are not out of invites
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CzarekDryl & @mmioana no, I was not previously aware this was hardcoded, this is a value that can be increased in the database. As a result of this and to minimise impact on the database, we updated the design last week to remove the need to show the upper limit and just show the remaining available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your response @arrenv 🙌
In this case and after looking over the code, I saw we actually have a lambda in place that will subtract 1 from the remaining invites once a user accept it. Initially, due to our dev
env I thought we need to account also for the existing number of followers 😶🌫️
So, in this case @CzarekDryl, I believe isOutOfInvites
should just check if invitesAvailable
is 0
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually let's please refactor this component a bit, given we have lots of conditional content based on the isOutOfInvites
flag
So my proposal would be the following
const InviteMembersModal = ({ isOpen, onClose }: Props) => {
const {
colony: { colonyMemberInvite, name: colonyName },
} = useColonyContext();
const inviteLink = useBaseUrl(
`/invite/${colonyName}/${colonyMemberInvite?.id}`,
);
const { totalMemberCount } = useMemberContext();
const invitesAvailable = colonyMemberInvite?.invitesRemaining || 0;
const invitesUsed = totalMemberCount;
const isOutOfInvites = invitesUsed >= invitesAvailable;
const { handleClipboardCopy, isCopied } = useCopyToClipboard();
const commonClassName = 'rounded-lg p-2 text-sm font-medium';
const getModalSubtitle = () => {
if (isOutOfInvites) {
<FormattedMessage
{...MSG.modalDescriptionReached}
values={{ invitesAvailable }}
/>;
}
return (
<FormattedMessage
{...MSG.modalDescription}
values={{ invitesAvailable }}
/>
);
};
const getModalContent = () => {
if (isOutOfInvites) {
return (
<CardWithCallout
title={
<span
className={clsx(
commonClassName,
'bg-negative-100 text-negative-400',
)}
>
<FormattedMessage
{...MSG.invitesUsed}
values={{ invitesAvailable }}
/>
</span>
}
subtitle={<FormattedMessage {...MSG.limitReached} />}
button={
<Link
to={getRequestInvitesLink(colonyName)}
target="_blank"
className="flex min-h-8.5 items-center justify-center gap-2 whitespace-nowrap
rounded-lg border border-gray-900 bg-base-white px-2.5 py-1.5 text-sm font-medium text-gray-900
transition-all duration-normal disabled:border-gray-300 disabled:text-gray-300 md:hover:border-gray-900
md:hover:bg-gray-900 md:hover:!text-base-white"
>
{formatText(MSG.requestInvites)}
</Link>
}
>
<FormattedMessage {...MSG.requestMoreInvites} />
</CardWithCallout>
);
}
return (
<CardWithCallout
title={
<span className={clsx(commonClassName, 'bg-blue-100 text-blue-400')}>
<FormattedMessage
{...MSG.invitesUsed}
values={{ invitesAvailable }}
/>
</span>
}
subtitle={<FormattedMessage {...MSG.inviteLinkHeading} />}
button={
<Button
text={MSG.buttonText}
mode={isCopied ? 'completed' : 'quinary'}
icon={isCopied ? undefined : CopySimple}
onClick={() => handleClipboardCopy(inviteLink)}
size="small"
textValues={{ isCopied }}
/>
}
>
{inviteLink}
</CardWithCallout>
);
};
return (
<Modal isOpen={isOpen} onClose={() => onClose()}>
<div className="mb-8 mt-10 flex flex-col items-center">
<SmileySticker size={42} className="mb-3" />
<Heading3
appearance={{ theme: 'dark' }}
className="font-semibold text-gray-900"
text={MSG.modalTitle}
/>
<p className="mt-1 text-center text-sm text-gray-600">
{getModalSubtitle()}
</p>
</div>
{getModalContent()}
</Modal>
);
};
Of course, bonus points if you feel like splitting the code up into partial components 💯
feat: add request button fix: modal changes, button hover fix: description copy
26ccfd5
to
2bd744a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for implementing my suggestions @CzarekDryl 🥇
The invite modal works really nicely 🌻
Screen.Recording.2025-01-16.at.19.46.38.mov
I took the chance to manually modify the invites remaining value
And it showed up in the modal 👍
However @CzarekDryl, due to my misunderstanding, we shouldn't account for totalMemberCount
for the isOutOfInvites
flag. I left a comment in the code, if you can please check 🙏
@@ -54,43 +76,76 @@ const InviteMembersModal = ({ isOpen, onClose }: Props) => { | |||
`/invite/${colonyName}/${colonyMemberInvite?.id}`, | |||
); | |||
const invitesUsed = 100 - (colonyMemberInvite?.invitesRemaining || 0); | |||
const isOutOfInvites = invitesUsed >= invitesAvailable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your response @arrenv 🙌
In this case and after looking over the code, I saw we actually have a lambda in place that will subtract 1 from the remaining invites once a user accept it. Initially, due to our dev
env I thought we need to account also for the existing number of followers 😶🌫️
So, in this case @CzarekDryl, I believe isOutOfInvites
should just check if invitesAvailable
is 0
.
@mmioana Fixed :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CzarekDryl Thank you for the updates, looks to be working well!
Modal working well
Hover state updated
Invite works
Count updates
No invites remaining
Goes to the correct link and populates the form:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @CzarekDryl really nice work, now it behaves properly 🙌
With 100
invites available
With 12
invites available
With no invites available
Got redirected to the proper page when requesting more invites
The copy-link functionality works as expected
However @CzarekDryl I did find some mismatches according to Figma
for the mobile
version
- The copy link action is on a new line
While inFigma
- Same for
Request invites
While inFigma
- The invite button is fully rounded
While inFigma
there is some horizontal padding
After these issues are fixed, we're good to go 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work @CzarekDryl 🥇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great start on this PR @CzarekDryl ! I left bits and bobs on your PR about areas of improvement. Give me a shout when you want me to re-test this PR! 👌
}, | ||
modalDescription: { | ||
id: `${displayName}.modalDescription`, | ||
defaultMessage: | ||
'You can invite up to 100 members to your Colony during the private beta using the invite link below.', | ||
'You can invite {invitesAvailable} more people to join and follow this colony during early access. If you run out, you will be able to request more.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking with @JoinColony/product if you want "people" to be "person" if there's only 1 invite available
}, | ||
invitesUsed: { | ||
id: `${displayName}.invitesUsed`, | ||
defaultMessage: '{invitesUsed}/{invitesAvailable} invites used', | ||
defaultMessage: | ||
'{invitesAvailable} {invitesAvailable, plural, one {invite} other {invites}} remaining', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨
modalDescription: { | ||
id: `${displayName}.modalDescription`, | ||
defaultMessage: | ||
'You can invite {invitesAvailable} more people to join and follow this colony during early access. If you run out, you will be able to request more.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about "people" pluralisation here @JoinColony/product
id: `${displayName}.buttonText`, | ||
defaultMessage: `{isCopied, select, | ||
true {Link copied} | ||
other {Copy link} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noice ✨
const inviteLink = useBaseUrl( | ||
`/invite/${colonyName}/${colonyMemberInvite?.id}`, | ||
); | ||
const commonClassName = 'rounded-lg p-2 text-sm font-medium'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can write this outside the component since it's just a constant, so it's not part of the re-render cycle
@@ -79,6 +90,18 @@ const UserNavigation: FC<UserNavigationProps> = ({ | |||
|
|||
return ( | |||
<div data-tour={TourTargets.UserMenu} className="flex gap-1 md:relative"> | |||
{!isActionSidebarOpen && isWalletConnected && user && isInColony ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that this isInColony
boolean is prop drilled at least 3 levels deep. I think it's better if you just use the context within this component:
const colonyContext = useColonyContext({ nullableContext: true });
const isInColony = !!colonyContext;
...
{!isActionSidebarOpen && isWalletConnected && user && isInColony ? (
iconSize={16} | ||
size="small" | ||
isFullRounded | ||
className="gap-1 px-[0.875rem] sm:px-2.5 md:hover:!border-blue-400" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's cleaner if you just do px-3.5
@rumzledz Thanks for checking, I've made changes with description pluralisation - for better UX. Please check it again :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @CzarekDryl thanks for your latest changes. It all looks good 💯
However, after @rumzledz pointed out the messages pluralisation, realised we have unused translations in both ModalContent
and InviteMembersModal
. I left a few comments about what needs to be removed. Once this is done, I'll be more than happy to approve your PR 🙌 Thanks for your hard and continuous work on this issue! ✨
modalTitle: { | ||
id: `${displayName}.modalTitle`, | ||
defaultMessage: 'Invite people to this colony', | ||
}, | ||
modalDescription: { | ||
id: `${displayName}.modalDescription`, | ||
defaultMessage: | ||
'You can invite {invitesAvailable} more {invitesAvailable, plural, one {person} other {people}} to join and follow this colony during early access. If you run out, you will be able to request more.', | ||
}, | ||
modalDescriptionReached: { | ||
id: `${displayName}.modalDescriptionReached`, | ||
defaultMessage: | ||
'You have reached your invite limit during early access. If you need more, please make a request.', | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove these unused translations
invitesUsed: { | ||
id: `${displayName}.invitesUsed`, | ||
defaultMessage: '{invitesUsed}/{invitesAvailable} invites used', | ||
defaultMessage: | ||
'{invitesAvailable} {invitesAvailable, plural, one {invite} other {invites}} remaining', | ||
}, | ||
inviteLinkHeading: { | ||
id: `${displayName}.inviteLinkHeading`, | ||
defaultMessage: 'Your unique invite link:', | ||
defaultMessage: 'Unique colony invite link:', | ||
}, | ||
limitReached: { | ||
id: `${displayName}.limitReached`, | ||
defaultMessage: 'Invite limit reached', | ||
}, | ||
requestInvites: { | ||
id: `${displayName}.requestInvites`, | ||
defaultMessage: 'Request invites', | ||
}, | ||
requestMoreInvites: { | ||
id: `${displayName}.requestMoreInvites`, | ||
defaultMessage: 'Request more invites for your colony', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove these unused descriptions
}, | ||
requestMoreInvites: { | ||
id: `${displayName}.requestMoreInvites`, | ||
defaultMessage: 'Request more invites for your colony', | ||
}, | ||
buttonText: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this one
@mmioana Unused messages removed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @CzarekDryl for all your work! Good to go from my side! 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const invitesUsed = 100 - (colonyMemberInvite?.invitesRemaining || 0); | ||
const getModalSubtitle = () => { | ||
if (isOutOfInvites) { | ||
<FormattedMessage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dropped a return
here, we have a tiny bug because of it 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @CzarekDryl everything still looks to be working well. After fixing the bug @bassgeta raised, let's get it merged.
Description
Testing
https://colony.io/request-member-invites?colony={colony_name}
Resolves #3927