mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #21712 from overleaf/ii-flexible-group-licensing-confirmation-page
[web] Request confirmation page for flexible licensing GitOrigin-RevId: 855dcbd46c645da75b8c641f0c49670b2e04df3f
This commit is contained in:
parent
fc84bdf68b
commit
6345ec3b04
11 changed files with 219 additions and 0 deletions
|
@ -8,6 +8,8 @@ import SessionManager from '../Authentication/SessionManager.js'
|
|||
import UserAuditLogHandler from '../User/UserAuditLogHandler.js'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import Modules from '../../infrastructure/Modules.js'
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
|
||||
import ErrorController from '../Errors/ErrorController.js'
|
||||
|
||||
/**
|
||||
* @import { Subscription } from "../../../../types/subscription/dashboard/subscription"
|
||||
|
@ -111,7 +113,33 @@ async function _removeUserFromGroup(
|
|||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async function requestConfirmation(req, res) {
|
||||
const { variant } = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'flexible-group-licensing'
|
||||
)
|
||||
|
||||
if (variant !== 'enabled') {
|
||||
return ErrorController.notFound(req, res)
|
||||
}
|
||||
|
||||
try {
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const subscription =
|
||||
await SubscriptionLocator.promises.getUsersSubscription(userId)
|
||||
|
||||
res.render('subscriptions/request-confirmation-react', {
|
||||
groupName: subscription.teamName,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.err({ error }, 'error trying to request seats to subscription')
|
||||
return res.render('/user/subscription')
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
removeUserFromGroup: expressify(removeUserFromGroup),
|
||||
removeSelfFromGroup: expressify(removeSelfFromGroup),
|
||||
requestConfirmation: expressify(requestConfirmation),
|
||||
}
|
||||
|
|
|
@ -80,6 +80,13 @@ export default {
|
|||
SubscriptionGroupController.removeSelfFromGroup
|
||||
)
|
||||
|
||||
webRouter.get(
|
||||
'/user/subscription/group/request-confirmation',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
|
||||
SubscriptionGroupController.requestConfirmation
|
||||
)
|
||||
|
||||
// Team invites
|
||||
webRouter.get(
|
||||
'/subscription/invites/:token/',
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
extends ../layout-marketing
|
||||
|
||||
block vars
|
||||
- bootstrap5PageStatus = 'enabled' // Enforce BS5 version
|
||||
|
||||
block entrypointVar
|
||||
- entrypoint = 'pages/user/subscription/group-management/request-confirmation'
|
||||
|
||||
block append meta
|
||||
meta(name="ol-groupName", data-type="string", content=groupName)
|
||||
|
||||
block content
|
||||
main.content.content-alt#subscription-manage-group-root
|
|
@ -603,6 +603,7 @@
|
|||
"go_to_overleaf": "",
|
||||
"go_to_pdf_location_in_code": "",
|
||||
"go_to_settings": "",
|
||||
"go_to_subscriptions": "",
|
||||
"group_admin": "",
|
||||
"group_invitations": "",
|
||||
"group_invite_has_been_sent_to_email": "",
|
||||
|
@ -1019,6 +1020,7 @@
|
|||
"other": "",
|
||||
"other_logs_and_files": "",
|
||||
"other_output_files": "",
|
||||
"our_team_will_get_back_to_you_shortly": "",
|
||||
"our_values": "",
|
||||
"out_of_sync": "",
|
||||
"out_of_sync_detail": "",
|
||||
|
@ -1793,6 +1795,7 @@
|
|||
"we_are_unable_to_opt_you_into_this_experiment": "",
|
||||
"we_cant_find_any_sections_or_subsections_in_this_file": "",
|
||||
"we_do_not_share_personal_information": "",
|
||||
"we_got_your_request": "",
|
||||
"we_logged_you_in": "",
|
||||
"we_sent_new_code": "",
|
||||
"webinars": "",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Card, CardBody, Row, Col } from 'react-bootstrap-5'
|
||||
import Button from '@/features/ui/components/bootstrap-5/button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import getMeta from '@/utils/meta'
|
||||
import IconButton from '@/features/ui/components/bootstrap-5/icon-button'
|
||||
|
||||
function RequestConfirmation() {
|
||||
const { t } = useTranslation()
|
||||
const groupName = getMeta('ol-groupName')
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Row>
|
||||
<Col xl={{ span: 4, offset: 4 }} md={{ span: 6, offset: 3 }}>
|
||||
<div className="group-heading" data-testid="group-heading">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
href="/user/subscription"
|
||||
size="lg"
|
||||
icon="arrow_back"
|
||||
accessibilityLabel={t('back_to_subscription')}
|
||||
/>
|
||||
<h2>{groupName || t('group_subscription')}</h2>
|
||||
</div>
|
||||
<Card>
|
||||
<CardBody className="d-grid gap-3">
|
||||
<div className="card-icon">
|
||||
<MaterialIcon type="email" />
|
||||
</div>
|
||||
<div className="d-grid gap-2 text-center">
|
||||
<h3 className="mb-0 fw-bold">{t('we_got_your_request')}</h3>
|
||||
<div className="card-description-secondary">
|
||||
{t('our_team_will_get_back_to_you_shortly')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Button variant="secondary" href="/user/subscription">
|
||||
{t('go_to_subscriptions')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RequestConfirmation
|
|
@ -0,0 +1,8 @@
|
|||
import '../base'
|
||||
import ReactDOM from 'react-dom'
|
||||
import RequestConfirmation from '@/features/group-management/components/request-confirmation'
|
||||
|
||||
const element = document.getElementById('subscription-manage-group-root')
|
||||
if (element) {
|
||||
ReactDOM.render(<RequestConfirmation />, element)
|
||||
}
|
|
@ -183,3 +183,21 @@
|
|||
padding: var(--spacing-10) var(--spacing-09);
|
||||
}
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-08);
|
||||
border-radius: 50%;
|
||||
background-color: var(--bg-light-secondary);
|
||||
color: var(--content-secondary);
|
||||
|
||||
.material-symbols {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-description-secondary {
|
||||
color: var(--content-secondary);
|
||||
}
|
||||
|
|
|
@ -158,3 +158,21 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-04);
|
||||
margin-bottom: var(--spacing-06);
|
||||
|
||||
h2 {
|
||||
@include heading-lg;
|
||||
|
||||
margin: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
--bs-btn-bg: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -860,6 +860,7 @@
|
|||
"go_to_pdf_location_in_code": "Go to PDF location in code (Tip: double click on the PDF for best results)",
|
||||
"go_to_previous_page": "Go to previous page",
|
||||
"go_to_settings": "Go to settings",
|
||||
"go_to_subscriptions": "Go to Subscriptions",
|
||||
"great_for_getting_started": "Great for getting started",
|
||||
"great_for_small_teams_and_departments": "Great for small teams and departments",
|
||||
"group": "Group",
|
||||
|
@ -1455,6 +1456,7 @@
|
|||
"other_output_files": "Download other output files",
|
||||
"other_sessions": "Other Sessions",
|
||||
"other_ways_to_log_in": "Other ways to log in",
|
||||
"our_team_will_get_back_to_you_shortly": "Our team will get back to you shortly.",
|
||||
"our_values": "Our values",
|
||||
"out_of_sync": "Out of sync",
|
||||
"out_of_sync_detail": "Sorry, this file has gone out of sync and we need to do a full refresh.<0 /><1>Please see this help guide for more information</1>",
|
||||
|
@ -2438,6 +2440,7 @@
|
|||
"we_cant_confirm_this_email": "We can’t confirm this email",
|
||||
"we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",
|
||||
"we_do_not_share_personal_information": "See our <0>Privacy Notice</0> for details of how we treat your personal data",
|
||||
"we_got_your_request": "We’ve got your request",
|
||||
"we_logged_you_in": "We have logged you in.",
|
||||
"we_may_also_contact_you_from_time_to_time_by_email_with_a_survey": "<0>We may also contact you</0> from time to time by email with a survey, or to see if you would like to participate in other user research initiatives",
|
||||
"we_sent_new_code": "We’ve sent a new code. If it doesn’t arrive, make sure to check your spam and any promotions folders.",
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import '../../../helpers/bootstrap-5'
|
||||
import RequestConfirmation from '@/features/group-management/components/request-confirmation'
|
||||
|
||||
describe('request confirmation page', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-groupName', 'My Awesome Team')
|
||||
})
|
||||
cy.mount(<RequestConfirmation />)
|
||||
})
|
||||
|
||||
it('renders the back button', function () {
|
||||
cy.findByTestId('group-heading').within(() => {
|
||||
cy.findByRole('button', { name: /back to subscription/i }).should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'/user/subscription'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows the group name', function () {
|
||||
cy.findByTestId('group-heading').within(() => {
|
||||
cy.findByRole('heading', { name: 'My Awesome Team' })
|
||||
})
|
||||
})
|
||||
|
||||
it('indicates the message was received', function () {
|
||||
cy.findByRole('heading', { name: /we’ve got your request/i })
|
||||
cy.findByText(/our team will get back to you shortly/i)
|
||||
})
|
||||
|
||||
it('renders the link to subscriptions', function () {
|
||||
cy.findByRole('button', { name: /go to subscriptions/i }).should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'/user/subscription'
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,5 +1,6 @@
|
|||
import esmock from 'esmock'
|
||||
import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Subscription/SubscriptionGroupController'
|
||||
|
||||
|
@ -24,6 +25,7 @@ describe('SubscriptionGroupController', function () {
|
|||
|
||||
this.subscription = {
|
||||
_id: this.subscriptionId,
|
||||
teamName: 'Cool group',
|
||||
}
|
||||
|
||||
this.SubscriptionGroupHandler = {
|
||||
|
@ -35,6 +37,7 @@ describe('SubscriptionGroupController', function () {
|
|||
this.SubscriptionLocator = {
|
||||
promises: {
|
||||
getSubscription: sinon.stub().resolves(this.subscription),
|
||||
getUsersSubscription: sinon.stub().resolves(this.subscription),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -61,6 +64,13 @@ describe('SubscriptionGroupController', function () {
|
|||
},
|
||||
}
|
||||
|
||||
this.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignment: sinon.stub().resolves({ variant: 'default' }),
|
||||
},
|
||||
getAssignment: sinon.stub().yields(null, { variant: 'default' }),
|
||||
}
|
||||
|
||||
this.Controller = await esmock.strict(modulePath, {
|
||||
'../../../../app/src/Features/Subscription/SubscriptionGroupHandler':
|
||||
this.SubscriptionGroupHandler,
|
||||
|
@ -71,6 +81,12 @@ describe('SubscriptionGroupController', function () {
|
|||
'../../../../app/src/Features/User/UserAuditLogHandler':
|
||||
this.UserAuditLogHandler,
|
||||
'../../../../app/src/infrastructure/Modules': this.Modules,
|
||||
'../../../../app/src/Features/SplitTests/SplitTestHandler':
|
||||
this.SplitTestHandler,
|
||||
'../../../../app/src/Features/Errors/ErrorController':
|
||||
(this.ErrorController = {
|
||||
notFound: sinon.stub(),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -253,4 +269,19 @@ describe('SubscriptionGroupController', function () {
|
|||
this.Controller.removeSelfFromGroup(this.req, res, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('add seats', function () {
|
||||
it('render the request confirmation view', async function () {
|
||||
this.SplitTestHandler.promises.getAssignment.resolves({
|
||||
variant: 'enabled',
|
||||
})
|
||||
await this.Controller.requestConfirmation(this.req, {
|
||||
render: (viewPath, viewParams) => {
|
||||
expect(viewPath).to.equal('subscriptions/request-confirmation-react')
|
||||
expect(viewParams.groupName).to.equal('Cool group')
|
||||
},
|
||||
})
|
||||
expect(this.ErrorController.notFound).to.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue