Merge pull request #19249 from overleaf/ii-invite-token-response

[web] Fix share modal resend status code

GitOrigin-RevId: 303f7b6c49d9522df6317789bb7c3c69d774715f
This commit is contained in:
ilkin-overleaf 2024-07-12 12:00:26 +03:00 committed by Copybot
parent 309340f94a
commit b442a74f54
8 changed files with 122 additions and 59 deletions

View file

@ -240,6 +240,12 @@ const CollaboratorsInviteController = {
inviteId
)
EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
{ invites: true }
)
if (invite != null) {
ProjectAuditLogHandler.addEntryInBackground(
projectId,
@ -251,9 +257,11 @@ const CollaboratorsInviteController = {
privileges: invite.privileges,
}
)
}
res.status(201).json({ newInviteId: invite._id })
res.sendStatus(201)
} else {
res.sendStatus(404)
}
},
async viewInvite(req, res) {

View file

@ -657,8 +657,10 @@
"invalid_password_too_similar": "",
"invalid_request": "",
"invite": "",
"invite_expired": "",
"invite_more_collabs": "",
"invite_not_accepted": "",
"invite_resend_limit_hit": "",
"invited_to_group": "",
"invited_to_group_have_individual_subcription": "",
"invited_to_join": "",

View file

@ -44,25 +44,21 @@ Invite.propTypes = {
function ResendInvite({ invite }) {
const { t } = useTranslation()
const { updateProject, monitorRequest } = useShareProjectContext()
const { _id: projectId, invites } = useProjectContext()
const { monitorRequest, setError, inFlight } = useShareProjectContext()
const { _id: projectId } = useProjectContext()
// const buttonRef = useRef(null)
//
const handleClick = useCallback(
() =>
monitorRequest(() => resendInvite(projectId, invite))
.then(({ newInviteId }) => {
const updatedInvites = invites.map(existing => {
if (existing === invite) {
// Update the invitation id for the project
existing._id = newInviteId
}
return existing
})
updateProject({
invites: updatedInvites,
})
.catch(error => {
if (error?.response?.status === 404) {
setError('invite_expired')
}
if (error?.response?.status === 429) {
setError('invite_resend_limit_hit')
}
})
.finally(() => {
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
@ -71,7 +67,7 @@ function ResendInvite({ invite }) {
// }
document.activeElement.blur()
}),
[invite, monitorRequest, projectId, invites, updateProject]
[invite, monitorRequest, projectId, setError]
)
return (
@ -79,6 +75,7 @@ function ResendInvite({ invite }) {
bsStyle="link"
className="btn-inline-link"
onClick={handleClick}
disabled={inFlight}
// ref={buttonRef}
>
{t('resend')}

View file

@ -43,21 +43,30 @@ Invite.propTypes = {
function ResendInvite({ invite }) {
const { t } = useTranslation()
const { monitorRequest } = useShareProjectContext()
const { monitorRequest, setError, inFlight } = useShareProjectContext()
const { _id: projectId } = useProjectContext()
// const buttonRef = useRef(null)
//
const handleClick = useCallback(
() =>
monitorRequest(() => resendInvite(projectId, invite)).finally(() => {
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
// if (buttonRef.current) {
// buttonRef.current.blur()
// }
document.activeElement.blur()
}),
[invite, monitorRequest, projectId]
monitorRequest(() => resendInvite(projectId, invite))
.catch(error => {
if (error?.response?.status === 404) {
setError('invite_expired')
}
if (error?.response?.status === 429) {
setError('invite_resend_limit_hit')
}
})
.finally(() => {
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
// if (buttonRef.current) {
// buttonRef.current.blur()
// }
document.activeElement.blur()
}),
[invite, monitorRequest, projectId, setError]
)
return (
@ -65,6 +74,7 @@ function ResendInvite({ invite }) {
bsStyle="link"
className="btn-inline-link"
onClick={handleClick}
disabled={inFlight}
// ref={buttonRef}
>
{t('resend')}

View file

@ -98,6 +98,12 @@ function ErrorMessage({ error }: Pick<ShareProjectModalContentProps, 'error'>) {
case 'too_many_requests':
return <>{t('too_many_requests')}</>
case 'invite_expired':
return <>{t('invite_expired')}</>
case 'invite_resend_limit_hit':
return <>{t('invite_resend_limit_hit')}</>
default:
return <>{t('generic_something_went_wrong')}</>
}

View file

@ -98,6 +98,12 @@ function ErrorMessage({ error }: Pick<ShareProjectModalContentProps, 'error'>) {
case 'too_many_requests':
return <>{t('too_many_requests')}</>
case 'invite_expired':
return <>{t('invite_expired')}</>
case 'invite_resend_limit_hit':
return <>{t('invite_resend_limit_hit')}</>
default:
return <>{t('generic_something_went_wrong')}</>
}

View file

@ -951,10 +951,12 @@
"invalid_request": "Invalid Request. Please correct the data and try again.",
"invalid_zip_file": "Invalid zip file",
"invite": "Invite",
"invite_expired": "The invite may have expired",
"invite_more_collabs": "Invite more collaborators",
"invite_not_accepted": "Invite not yet accepted",
"invite_not_valid": "This is not a valid project invite",
"invite_not_valid_description": "The invite may have expired. Please contact the project owner",
"invite_resend_limit_hit": "The invite resend limit hit",
"invited_to_group": "<0>__inviterName__</0> has invited you to join a group subscription on __appName__",
"invited_to_group_have_individual_subcription": "__inviterName__ has invited you to join a group __appName__ subscription. If you join this group, you may not need your individual subscription. Would you like to cancel it?",
"invited_to_group_login": "To accept this invitation you need to log in as __emailAddress__.",

View file

@ -1269,46 +1269,78 @@ describe('CollaboratorsInviteController', function () {
})
describe('when generateNewInvite does not produce an error', function () {
beforeEach(function (done) {
this.res.callback = () => done()
this.CollaboratorsInviteController.generateNewInvite(
this.req,
this.res,
this.next
)
})
describe('and returns an invite object', function () {
beforeEach(function (done) {
this.res.callback = () => done()
this.CollaboratorsInviteController.generateNewInvite(
this.req,
this.res,
this.next
)
})
it('should produce a 201 response with new invitation id', function () {
this.res.status.callCount.should.equal(1)
this.res.status.calledWith(201).should.equal(true)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
newInviteId: this.invite._id,
it('should produce a 201 response', function () {
this.res.sendStatus.callCount.should.equal(1)
this.res.sendStatus.calledWith(201).should.equal(true)
})
it('should have called generateNewInvite', function () {
this.CollaboratorsInviteHandler.promises.generateNewInvite.callCount.should.equal(
1
)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('should check the rate limit', function () {
this.CollaboratorsInviteController.promises._checkRateLimit.callCount.should.equal(
1
)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'resend-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
it('should have called generateNewInvite', function () {
this.CollaboratorsInviteHandler.promises.generateNewInvite.callCount.should.equal(
1
)
})
describe('and returns a null invite', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.generateNewInvite.resolves(
null
)
this.res.callback = () => done()
this.CollaboratorsInviteController.generateNewInvite(
this.req,
this.res,
this.next
)
})
it('should check the rate limit', function () {
this.CollaboratorsInviteController.promises._checkRateLimit.callCount.should.equal(
1
)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'resend-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
it('should produce a 404 response when invite is null', function () {
this.res.sendStatus.callCount.should.equal(1)
this.res.sendStatus.should.have.been.calledWith(404)
})
})
})