mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #14756 from overleaf/mf-group-invite-new-user-redirection
[web] Redirect to invite screen if new user register with a pending group invitations GitOrigin-RevId: 39aeffd65b9d793c87e53398a700ad140794594e
This commit is contained in:
parent
c4bea21ee2
commit
0639f266d8
15 changed files with 215 additions and 4 deletions
|
@ -99,6 +99,14 @@ const SubscriptionLocator = {
|
|||
Subscription.find({ invited_emails: email }, callback)
|
||||
},
|
||||
|
||||
getGroupsWithTeamInvitesEmail(email, callback) {
|
||||
Subscription.find(
|
||||
{ teamInvites: { $elemMatch: { email } } },
|
||||
{ teamInvites: 1 },
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
getGroupWithV1Id(v1TeamId, callback) {
|
||||
Subscription.findOne({ 'overleaf.id': v1TeamId }, callback)
|
||||
},
|
||||
|
@ -151,6 +159,9 @@ SubscriptionLocator.promises = {
|
|||
getGroupsWithEmailInvite: promisify(
|
||||
SubscriptionLocator.getGroupsWithEmailInvite
|
||||
),
|
||||
getGroupsWithTeamInvitesEmail: promisify(
|
||||
SubscriptionLocator.getGroupsWithTeamInvitesEmail
|
||||
),
|
||||
getGroupWithV1Id: promisify(SubscriptionLocator.getGroupWithV1Id),
|
||||
getUserDeletedSubscriptions: promisify(
|
||||
SubscriptionLocator.getUserDeletedSubscriptions
|
||||
|
|
|
@ -64,6 +64,12 @@ module.exports = {
|
|||
PermissionsController.useCapabilities(),
|
||||
TeamInvitesController.viewInvite
|
||||
)
|
||||
webRouter.get(
|
||||
'/subscription/invites/',
|
||||
AuthenticationController.requireLogin(),
|
||||
PermissionsController.useCapabilities(),
|
||||
TeamInvitesController.viewInvites
|
||||
)
|
||||
webRouter.put(
|
||||
'/subscription/invites/:token/',
|
||||
AuthenticationController.requireLogin(),
|
||||
|
|
|
@ -171,6 +171,21 @@ async function viewInvite(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
async function viewInvites(req, res, next) {
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const userEmail = await UserGetter.promises.getUserEmail(userId)
|
||||
const groupSubscriptions =
|
||||
await SubscriptionLocator.promises.getGroupsWithTeamInvitesEmail(userEmail)
|
||||
|
||||
const teamInvites = groupSubscriptions.map(groupSubscription =>
|
||||
groupSubscription.teamInvites.find(invite => invite.email === userEmail)
|
||||
)
|
||||
|
||||
return res.render('subscriptions/team/group-invites', {
|
||||
teamInvites,
|
||||
})
|
||||
}
|
||||
|
||||
async function acceptInvite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
|
@ -255,6 +270,7 @@ async function resendInvite(req, res, next) {
|
|||
module.exports = {
|
||||
createInvite: expressify(createInvite),
|
||||
viewInvite: expressify(viewInvite),
|
||||
viewInvites: expressify(viewInvites),
|
||||
acceptInvite: expressify(acceptInvite),
|
||||
revokeInvite,
|
||||
resendInvite: expressify(resendInvite),
|
||||
|
|
10
services/web/app/views/subscriptions/team/group-invites.pug
Normal file
10
services/web/app/views/subscriptions/team/group-invites.pug
Normal file
|
@ -0,0 +1,10 @@
|
|||
extends ../../layout-marketing
|
||||
|
||||
block entrypointVar
|
||||
- entrypoint = 'pages/user/subscription/group-invites'
|
||||
|
||||
block append meta
|
||||
meta(name="ol-teamInvites" data-type="json" content=teamInvites)
|
||||
|
||||
block content
|
||||
main.content.content-alt.team-invite#group-invites-root
|
|
@ -454,6 +454,7 @@
|
|||
"go_to_pdf_location_in_code": "",
|
||||
"go_to_settings": "",
|
||||
"group_admin": "",
|
||||
"group_invitations": "",
|
||||
"group_invite_has_been_sent_to_email": "",
|
||||
"group_libraries": "",
|
||||
"group_managed_by_group_administrator": "",
|
||||
|
@ -580,6 +581,7 @@
|
|||
"is_email_affiliated": "",
|
||||
"join_now": "",
|
||||
"join_project": "",
|
||||
"join_team_explanation": "",
|
||||
"joining": "",
|
||||
"keep_current_plan": "",
|
||||
"keep_personal_projects_separate": "",
|
||||
|
@ -1372,6 +1374,7 @@
|
|||
"view_hub": "",
|
||||
"view_hub_subtext": "",
|
||||
"view_in_template_gallery": "",
|
||||
"view_invitation": "",
|
||||
"view_logs": "",
|
||||
"view_metrics": "",
|
||||
"view_metrics_commons_subtext": "",
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import type { TeamInvite } from '../../../../../../types/team-invite'
|
||||
|
||||
type GroupInvitesItemFooterProps = {
|
||||
teamInvite: TeamInvite
|
||||
}
|
||||
|
||||
export default function GroupInvitesItemFooter({
|
||||
teamInvite,
|
||||
}: GroupInvitesItemFooterProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p data-cy="group-invites-item-footer-text">
|
||||
{t('join_team_explanation')}
|
||||
</p>
|
||||
<div data-cy="group-invites-item-footer-link">
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href={`/subscription/invites/${teamInvite.token}`}
|
||||
>
|
||||
{t('view_invitation')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import GroupInvitesItemFooter from './group-invites-item-footer'
|
||||
import type { TeamInvite } from '../../../../../../types/team-invite'
|
||||
|
||||
type GroupInvitesItemProps = {
|
||||
teamInvite: TeamInvite
|
||||
}
|
||||
|
||||
export default function GroupInvitesItem({
|
||||
teamInvite,
|
||||
}: GroupInvitesItemProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Row className="row-spaced">
|
||||
<Col md={8} mdOffset={2} className="text-center">
|
||||
<div className="card">
|
||||
<div className="page-header">
|
||||
<h2>
|
||||
{t('invited_to_group', {
|
||||
inviterName: teamInvite.inviterName,
|
||||
})}
|
||||
</h2>
|
||||
</div>
|
||||
<GroupInvitesItemFooter teamInvite={teamInvite} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import useWaitForI18n from '../../../../shared/hooks/use-wait-for-i18n'
|
||||
import GroupInvites from './group-invites'
|
||||
|
||||
function GroupInvitesRoot() {
|
||||
const { isReady } = useWaitForI18n()
|
||||
|
||||
if (!isReady) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <GroupInvites />
|
||||
}
|
||||
|
||||
export default GroupInvitesRoot
|
|
@ -0,0 +1,34 @@
|
|||
import { useEffect } from 'react'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import GroupInvitesItem from './group-invites-item'
|
||||
import type { TeamInvite } from '../../../../../../types/team-invite'
|
||||
|
||||
function GroupInvites() {
|
||||
const { t } = useTranslation()
|
||||
const teamInvites: TeamInvite[] = getMeta('ol-teamInvites')
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
if (teamInvites.length === 0) {
|
||||
location.assign('/project')
|
||||
}
|
||||
}, [teamInvites, location])
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Row>
|
||||
<Col md={8} mdOffset={2}>
|
||||
<h1>{t('group_invitations')}</h1>
|
||||
</Col>
|
||||
</Row>
|
||||
{teamInvites.map(teamInvite => (
|
||||
<GroupInvitesItem teamInvite={teamInvite} key={teamInvite._id} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupInvites
|
|
@ -0,0 +1,8 @@
|
|||
import './base'
|
||||
import ReactDOM from 'react-dom'
|
||||
import GroupInvitesRoot from '@/features/subscription/components/group-invites/group-invites-root'
|
||||
|
||||
const element = document.getElementById('group-invites-root')
|
||||
if (element) {
|
||||
ReactDOM.render(<GroupInvitesRoot />, element)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import GroupInvites from '@/features/subscription/components/group-invites/group-invites'
|
||||
import type { TeamInvite } from '../../../../types/team-invite'
|
||||
import { useMeta } from '../../hooks/use-meta'
|
||||
import { ScopeDecorator } from '../../decorators/scope'
|
||||
|
||||
export const GroupInvitesDefault = () => {
|
||||
const teamInvites: TeamInvite[] = [
|
||||
{
|
||||
email: 'email1@exammple.com',
|
||||
token: 'token123',
|
||||
inviterName: 'inviter1@example.com',
|
||||
sentAt: new Date(),
|
||||
_id: '123abc',
|
||||
},
|
||||
{
|
||||
email: 'email2@exammple.com',
|
||||
token: 'token456',
|
||||
inviterName: 'inviter2@example.com',
|
||||
sentAt: new Date(),
|
||||
_id: '456bcd',
|
||||
},
|
||||
]
|
||||
|
||||
useMeta({ 'ol-teamInvites': teamInvites })
|
||||
|
||||
return (
|
||||
<div className="content content-alt team-invite">
|
||||
<GroupInvites />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Subscription / Group Invites',
|
||||
component: GroupInvites,
|
||||
args: {
|
||||
show: true,
|
||||
},
|
||||
argTypes: {
|
||||
handleHide: { action: 'close modal' },
|
||||
onDisableSSO: { action: 'callback' },
|
||||
},
|
||||
decorators: [ScopeDecorator],
|
||||
}
|
|
@ -718,6 +718,7 @@
|
|||
"group_admins_get_access_to": "Group admins get access to",
|
||||
"group_admins_get_access_to_info": "Special features available only on group plans.",
|
||||
"group_full": "This group is already full",
|
||||
"group_invitations": "Group Invitations",
|
||||
"group_invite_has_been_sent_to_email": "Group invite has been sent to <0>__email__</0>",
|
||||
"group_libraries": "Group Libraries",
|
||||
"group_managed_by_group_administrator": "User accounts in this group are managed by the group administrator.",
|
||||
|
@ -2021,6 +2022,7 @@
|
|||
"view_hub": "View Admin Hub",
|
||||
"view_hub_subtext": "Access and download subscription statistics and a list of users",
|
||||
"view_in_template_gallery": "View it in the template gallery",
|
||||
"view_invitation": "View Invitation",
|
||||
"view_logs": "View logs",
|
||||
"view_metrics": "View metrics",
|
||||
"view_metrics_commons_subtext": "Monitor and download usage metrics for your Commons subscription",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GroupPolicy } from '../subscription/dashboard/subscription'
|
||||
import { TeamInvite } from './team-invite'
|
||||
import { TeamInvite } from '../team-invite'
|
||||
|
||||
export type Subscription = {
|
||||
_id: string
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export type TeamInvite = {
|
||||
email: string
|
||||
}
|
7
services/web/types/team-invite.ts
Normal file
7
services/web/types/team-invite.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export type TeamInvite = {
|
||||
email: string
|
||||
token: string
|
||||
inviterName: string
|
||||
sentAt: Date
|
||||
_id: string
|
||||
}
|
Loading…
Reference in a new issue