first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
import useFetchMock from '../hooks/use-fetch-mock'
import AccountInfoSection from '../../js/features/settings/components/account-info-section'
import { setDefaultMeta, defaultSetupMocks } from './helpers/account-info'
import { UserProvider } from '../../js/shared/context/user-context'
import getMeta from '@/utils/meta'
export const Success = args => {
setDefaultMeta()
useFetchMock(defaultSetupMocks)
return (
<UserProvider>
<AccountInfoSection {...args} />
</UserProvider>
)
}
export const ReadOnly = args => {
setDefaultMeta()
window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', true)
window.metaAttributesCache.set('ol-shouldAllowEditingDetails', false)
return (
<UserProvider>
<AccountInfoSection {...args} />
</UserProvider>
)
}
export const NoEmailInput = args => {
setDefaultMeta()
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
})
useFetchMock(defaultSetupMocks)
return (
<UserProvider>
<AccountInfoSection {...args} />
</UserProvider>
)
}
export const Error = args => {
setDefaultMeta()
useFetchMock(fetchMock => fetchMock.post(/\/user\/settings/, 500))
return (
<UserProvider>
<AccountInfoSection {...args} />
</UserProvider>
)
}
export default {
title: 'Account Settings / Account Info',
component: AccountInfoSection,
}

View File

@@ -0,0 +1,30 @@
import useFetchMock from './../hooks/use-fetch-mock'
import Input from '../../js/features/settings/components/emails/add-email/input'
export const EmailInput = (args: any) => {
useFetchMock(fetchMock =>
fetchMock.get(/\/institutions\/domains/, [
{
hostname: 'autocomplete.edu',
university: { id: 123, name: 'Auto Complete University' },
},
])
)
return (
<>
<Input {...args} />
<br />
<div>
Use <code>autocomplete.edu</code> as domain to trigger an autocomplete
</div>
</>
)
}
export default {
title: 'Account Settings / Emails and Affiliations',
component: Input,
argTypes: {
onChange: { action: 'change' },
},
}

View File

@@ -0,0 +1,27 @@
import BetaProgramSection from '../../js/features/settings/components/beta-program-section'
import { UserProvider } from '../../js/shared/context/user-context'
export const SectionNotEnrolled = args => {
window.metaAttributesCache.set('ol-user', { betaProgram: false })
return (
<UserProvider>
<BetaProgramSection {...args} />
</UserProvider>
)
}
export const SectionEnrolled = args => {
window.metaAttributesCache.set('ol-user', { betaProgram: true })
return (
<UserProvider>
<BetaProgramSection {...args} />
</UserProvider>
)
}
export default {
title: 'Account Settings / Beta Program',
component: BetaProgramSection,
}

View File

@@ -0,0 +1,43 @@
import EmailsSection from '../../js/features/settings/components/emails-section'
import useFetchMock from './../hooks/use-fetch-mock'
import {
setDefaultMeta,
setReconfirmationMeta,
defaultSetupMocks,
reconfirmationSetupMocks,
errorsMocks,
emailLimitSetupMocks,
} from './helpers/emails'
export const EmailsList = args => {
useFetchMock(defaultSetupMocks)
setDefaultMeta()
return <EmailsSection {...args} />
}
export const EmailLimitList = args => {
useFetchMock(emailLimitSetupMocks)
setDefaultMeta()
return <EmailsSection {...args} />
}
export const ReconfirmationEmailsList = args => {
useFetchMock(reconfirmationSetupMocks)
setReconfirmationMeta()
return <EmailsSection {...args} />
}
export const NetworkErrors = args => {
useFetchMock(errorsMocks)
setDefaultMeta()
return <EmailsSection {...args} />
}
export default {
title: 'Account Settings / Emails and Affiliations',
component: EmailsSection,
}

View File

@@ -0,0 +1,23 @@
import getMeta from '@/utils/meta'
const MOCK_DELAY = 1000
export function defaultSetupMocks(fetchMock) {
fetchMock.post(/\/user\/settings/, 200, {
delay: MOCK_DELAY,
})
}
export function setDefaultMeta() {
window.metaAttributesCache.set('ol-user', {
...window.metaAttributesCache.get('ol-user'),
email: 'sherlock@holmes.co.uk',
first_name: 'Sherlock',
last_name: 'Holmes',
})
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: false,
})
window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false)
window.metaAttributesCache.set('ol-shouldAllowEditingDetails', true)
}

View File

@@ -0,0 +1,219 @@
import getMeta from '@/utils/meta'
const MOCK_DELAY = 1000
const fakeUsersData = [
{
affiliation: {
institution: {
confirmed: true,
id: 1,
name: 'Overleaf',
},
licence: 'pro_plus',
},
confirmedAt: '2022-03-09T10:59:44.139Z',
email: 'foo@overleaf.com',
default: true,
emailHasInstitutionLicence: true,
},
{
confirmedAt: '2022-03-10T10:59:44.139Z',
email: 'bar@overleaf.com',
default: false,
},
{
affiliation: {
institution: {
confirmed: true,
id: 2,
name: 'Overleaf',
},
licence: 'pro_plus',
department: 'Art & Art History',
role: 'Postdoc',
},
email: 'baz@overleaf.com',
default: false,
},
{
email: 'qux@overleaf.com',
default: false,
},
]
const fakeReconfirmationUsersData = [
{
affiliation: {
institution: {
confirmed: true,
isUniversity: true,
id: 4,
name: 'Reconfirmable Email Highlighted',
},
licence: 'pro_plus',
inReconfirmNotificationPeriod: true,
},
email: 'reconfirmation-highlighted@overleaf.com',
confirmedAt: '2022-03-09T10:59:44.139Z',
default: false,
},
{
affiliation: {
institution: {
confirmed: true,
isUniversity: true,
id: 4,
name: 'Reconfirmable Emails Primary',
},
licence: 'pro_plus',
inReconfirmNotificationPeriod: true,
},
email: 'reconfirmation-nonsso@overleaf.com',
confirmedAt: '2022-03-09T10:59:44.139Z',
default: true,
},
{
affiliation: {
institution: {
confirmed: true,
ssoEnabled: true,
isUniversity: true,
id: 3,
name: 'Reconfirmable SSO',
},
licence: 'pro_plus',
inReconfirmNotificationPeriod: true,
},
email: 'reconfirmation-sso@overleaf.com',
confirmedAt: '2022-03-09T10:59:44.139Z',
samlProviderId: 'reconfirmation-sso-provider-id',
default: false,
},
{
affiliation: {
institution: {
confirmed: true,
isUniversity: true,
ssoEnabled: true,
id: 5,
name: 'Reconfirmed SSO',
},
licence: 'pro_plus',
},
confirmedAt: '2022-03-09T10:59:44.139Z',
email: 'sso@overleaf.com',
samlProviderId: 'sso-reconfirmed-provider-id',
default: false,
},
]
const fakeInstitutions = [
{
id: 9326,
name: 'Unknown',
country_code: 'al',
departments: ['New department'],
},
]
const fakeInstitution = {
id: 123,
name: 'test',
country_code: 'de',
departments: [],
team_id: null,
}
const bazFakeInstitution = {
id: 2,
name: 'Baz',
country_code: 'de',
departments: ['Custom department 1', 'Custom department 2'],
team_id: null,
}
const fakeInstitutionDomain1 = [
{
university: {
id: 1234,
ssoEnabled: true,
name: 'Auto Complete University',
},
hostname: 'autocomplete.edu',
confirmed: true,
},
]
const fakeInstitutionDomain2 = [
{
university: {
id: 5678,
ssoEnabled: false,
name: 'Fake Auto Complete University',
},
hostname: 'fake-autocomplete.edu',
confirmed: true,
},
]
export function defaultSetupMocks(fetchMock) {
fetchMock
.get(/\/user\/emails/, fakeUsersData, { delay: MOCK_DELAY })
.get(/\/institutions\/list\/2/, bazFakeInstitution, { delay: MOCK_DELAY })
.get(/\/institutions\/list\/\d+/, fakeInstitution, { delay: MOCK_DELAY })
.get(/\/institutions\/list\?country_code=.*/, fakeInstitutions, {
delay: MOCK_DELAY,
})
.get(/\/institutions\/domains\?hostname=a/, fakeInstitutionDomain1)
.get(/\/institutions\/domains\?hostname=f/, fakeInstitutionDomain2)
.get(/\/institutions\/domains/, [])
.post(/\/user\/emails\/*/, 200, {
delay: MOCK_DELAY,
})
}
export function reconfirmationSetupMocks(fetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.get(/\/user\/emails/, fakeReconfirmationUsersData, {
delay: MOCK_DELAY,
})
}
export function emailLimitSetupMocks(fetchMock) {
const userData = []
for (let i = 0; i < 10; i++) {
userData.push({ email: `example${i}@overleaf.com` })
}
defaultSetupMocks(fetchMock)
fetchMock.get(/\/user\/emails/, userData, {
delay: MOCK_DELAY,
})
}
export function errorsMocks(fetchMock) {
fetchMock
.get(/\/user\/emails/, fakeUsersData, { delay: MOCK_DELAY })
.post(/\/user\/emails\/*/, 500)
}
export function setDefaultMeta() {
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
hasSamlFeature: true,
samlInitPath: 'saml/init',
})
localStorage.setItem(
'showInstitutionalLeaversSurveyUntil',
(Date.now() - 1000 * 60 * 60).toString()
)
}
export function setReconfirmationMeta() {
setDefaultMeta()
window.metaAttributesCache.set(
'ol-reconfirmationRemoveEmail',
'reconfirmation-highlighted@overleaf.com'
)
window.metaAttributesCache.set(
'ol-reconfirmedViaSAML',
'sso-reconfirmed-provider-id'
)
}

View File

@@ -0,0 +1,17 @@
import getMeta from '@/utils/meta'
const MOCK_DELAY = 1000
export function defaultSetupMocks(fetchMock) {
fetchMock.post(/\/user\/delete/, 200, {
delay: MOCK_DELAY,
})
}
export function setDefaultMeta() {
window.metaAttributesCache.set('ol-usersEmail', 'user@primary.com')
Object.assign(getMeta('ol-ExposedSettings'), {
isOverleaf: true,
})
window.metaAttributesCache.set('ol-hasPassword', true)
}

View File

@@ -0,0 +1,78 @@
const MOCK_DELAY = 1000
export function defaultSetupMocks(fetchMock) {
fetchMock
.post('/user/oauth-unlink', 200, { delay: MOCK_DELAY })
.get(
'express:/user/tpds/queues',
{ tpdsToWeb: 0, webToTpds: 0 },
{ delay: MOCK_DELAY }
)
}
export function setDefaultMeta() {
window.metaAttributesCache.set('ol-user', {
...window.metaAttributesCache.get('ol-user'),
features: { github: true, dropbox: true, mendeley: false, zotero: false },
refProviders: {
mendeley: true,
zotero: true,
},
})
window.metaAttributesCache.set('ol-github', { enabled: false })
window.metaAttributesCache.set('ol-dropbox', { registered: true })
window.metaAttributesCache.set('ol-thirdPartyIds', {
collabratec: 'collabratec-id',
google: 'google-id',
})
window.metaAttributesCache.set('ol-oauthProviders', {
collabratec: {
descriptionKey: 'linked_collabratec_description',
descriptionOptions: { appName: 'Overleaf' },
name: 'IEEE Collabratec®',
hideWhenNotLinked: true,
linkPath: '/collabratec/auth/link',
},
google: {
descriptionKey: 'login_with_service',
descriptionOptions: { service: 'Google' },
name: 'Google',
linkPath: '/auth/google',
},
orcid: {
descriptionKey: 'oauth_orcid_description',
descriptionOptions: {
link: '/blog/434',
appName: 'Overleaf',
},
name: 'ORCID',
linkPath: '/auth/orcid',
},
})
window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
window.metaAttributesCache.delete('ol-ssoErrorMessage')
}
export function setPersonalAccessTokensMeta() {
function generateToken(_id) {
const oneYearFromNow = new Date()
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
const tokenHasBeenUsed = Math.random() > 0.5
return {
_id,
accessTokenPartial: 'olp_abc' + _id,
createdAt: new Date(),
accessTokenExpiresAt: oneYearFromNow,
lastUsedAt: tokenHasBeenUsed ? new Date() : undefined,
}
}
const tokens = []
for (let i = 0; i < 6; i++) {
tokens.push(generateToken(i))
}
window.metaAttributesCache.set('ol-personalAccessTokens', tokens)
}

View File

@@ -0,0 +1,35 @@
import getMeta from '@/utils/meta'
const MOCK_DELAY = 1000
export function defaultSetupMocks(fetchMock) {
fetchMock.post(
/\/user\/password\/update/,
{
status: 200,
body: {
message: {
type: 'success',
email: 'tim.alby@overleaf.com',
text: 'Password changed',
},
},
},
{
delay: MOCK_DELAY,
}
)
}
export function setDefaultMeta() {
Object.assign(getMeta('ol-ExposedSettings'), {
isOverleaf: true,
})
window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false)
window.metaAttributesCache.set('ol-hasPassword', true)
window.metaAttributesCache.set('ol-passwordStrengthOptions', {
length: {
min: 2,
},
})
}

View File

@@ -0,0 +1,71 @@
import useFetchMock from '../hooks/use-fetch-mock'
import LeaveModal from '../../js/features/settings/components/leave/modal'
import LeaveSection from '../../js/features/settings/components/leave-section'
import { setDefaultMeta, defaultSetupMocks } from './helpers/leave'
export const Section = args => {
useFetchMock(defaultSetupMocks)
setDefaultMeta()
return <LeaveSection {...args} />
}
Section.component = LeaveSection
Section.parameters = { controls: { include: [], hideNoControlsWarning: true } }
export const ModalSuccess = args => {
setDefaultMeta()
useFetchMock(defaultSetupMocks)
return <LeaveModal {...args} />
}
export const ModalWithoutPassword = args => {
setDefaultMeta()
window.metaAttributesCache.set('ol-hasPassword', false)
useFetchMock(defaultSetupMocks)
return <LeaveModal {...args} />
}
export const ModalAuthError = args => {
setDefaultMeta()
useFetchMock(fetchMock => {
fetchMock.post(/\/user\/delete/, 403)
})
return <LeaveModal {...args} />
}
export const ModalServerError = args => {
setDefaultMeta()
useFetchMock(fetchMock => {
fetchMock.post(/\/user\/delete/, 500)
})
return <LeaveModal {...args} />
}
export const ModalSubscriptionError = args => {
setDefaultMeta()
useFetchMock(fetchMock => {
fetchMock.post(/\/user\/delete/, {
status: 422,
body: {
error: 'SubscriptionAdminDeletionError',
},
})
})
return <LeaveModal {...args} />
}
export default {
title: 'Account Settings / Leave',
component: LeaveModal,
args: {
isOpen: true,
},
argTypes: {
handleClose: { action: 'handleClose' },
},
}

View File

@@ -0,0 +1,21 @@
import EmailsSection from '../../js/features/settings/components/emails-section'
import { UserEmailsProvider } from '../../js/features/settings/context/user-email-context'
import { LeaversSurveyAlert } from '../../js/features/settings/components/leavers-survey-alert'
import localStorage from '@/infrastructure/local-storage'
export const SurveyAlert = () => {
localStorage.setItem(
'showInstitutionalLeaversSurveyUntil',
Date.now() + 1000 * 60 * 60
)
return (
<UserEmailsProvider>
<LeaversSurveyAlert />
</UserEmailsProvider>
)
}
export default {
title: 'Account Settings / Survey Alerts',
component: EmailsSection,
}

View File

@@ -0,0 +1,96 @@
import useFetchMock from '../hooks/use-fetch-mock'
import LinkingSection from '../../js/features/settings/components/linking-section'
import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
import { UserProvider } from '../../js/shared/context/user-context'
import { SSOProvider } from '../../js/features/settings/context/sso-context'
import { ScopeDecorator } from '../decorators/scope'
import { useEffect } from 'react'
import { useMeta } from '../hooks/use-meta'
const MOCK_DELAY = 1000
export const Section = args => {
useFetchMock(defaultSetupMocks)
setDefaultMeta()
return (
<UserProvider>
<SSOProvider>
<LinkingSection {...args} />
</SSOProvider>
</UserProvider>
)
}
export const SectionAllUnlinked = args => {
useFetchMock(defaultSetupMocks)
useMeta({
'ol-thirdPartyIds': {},
'ol-user': {
features: { github: true, dropbox: true, mendeley: true, zotero: true },
refProviders: {
mendeley: false,
zotero: false,
},
},
'ol-github': { enabled: false },
'ol-dropbox': { registered: false },
})
useEffect(() => {
setDefaultMeta()
}, [])
return (
<UserProvider>
<SSOProvider>
<LinkingSection {...args} />
</SSOProvider>
</UserProvider>
)
}
export const SectionSSOErrors = args => {
useFetchMock(fetchMock =>
fetchMock.post('/user/oauth-unlink', 500, { delay: MOCK_DELAY })
)
setDefaultMeta()
window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
window.metaAttributesCache.set(
'ol-ssoErrorMessage',
'Account already linked to another Overleaf user'
)
return (
<UserProvider>
<SSOProvider>
<LinkingSection {...args} />
</SSOProvider>
</UserProvider>
)
}
export const SectionProjetSyncSuccess = args => {
useFetchMock(defaultSetupMocks)
setDefaultMeta()
window.metaAttributesCache.set('ol-github', { enabled: true })
window.metaAttributesCache.set(
'ol-projectSyncSuccessMessage',
'Thanks, weve successfully linked your GitHub account to Overleaf. You can now export your Overleaf projects to GitHub, or import projects from your GitHub repositories.'
)
return (
<UserProvider>
<SSOProvider>
<LinkingSection {...args} />
</SSOProvider>
</UserProvider>
)
}
export default {
title: 'Account Settings / Linking',
component: LinkingSection,
decorators: [ScopeDecorator],
}

View File

@@ -0,0 +1,10 @@
import NewsletterSection from '../../js/features/settings/components/newsletter-section'
export const Section = args => {
return <NewsletterSection {...args} />
}
export default {
title: 'Account Settings / Newsletter',
component: NewsletterSection,
}

View File

@@ -0,0 +1,81 @@
import useFetchMock from '../hooks/use-fetch-mock'
import SettingsPageRoot from '../../js/features/settings/components/root'
import {
setDefaultMeta as setDefaultLeaveMeta,
defaultSetupMocks as defaultSetupLeaveMocks,
} from './helpers/leave'
import {
setDefaultMeta as setDefaultAccountInfoMeta,
defaultSetupMocks as defaultSetupAccountInfoMocks,
} from './helpers/account-info'
import {
setDefaultMeta as setDefaultPasswordMeta,
defaultSetupMocks as defaultSetupPasswordMocks,
} from './helpers/password'
import {
setDefaultMeta as setDefaultEmailsMeta,
defaultSetupMocks as defaultSetupEmailsMocks,
} from './helpers/emails'
import {
setDefaultMeta as setDefaultLinkingMeta,
defaultSetupMocks as defaultSetupLinkingMocks,
setPersonalAccessTokensMeta,
} from './helpers/linking'
import { UserProvider } from '../../js/shared/context/user-context'
import { ScopeDecorator } from '../decorators/scope'
import getMeta from '@/utils/meta'
export const Overleaf = args => {
setDefaultLeaveMeta()
setDefaultAccountInfoMeta()
setDefaultPasswordMeta()
setDefaultEmailsMeta()
setDefaultLinkingMeta()
useFetchMock(fetchMock => {
defaultSetupLeaveMocks(fetchMock)
defaultSetupAccountInfoMocks(fetchMock)
defaultSetupPasswordMocks(fetchMock)
defaultSetupEmailsMocks(fetchMock)
defaultSetupLinkingMocks(fetchMock)
})
return (
<UserProvider>
<SettingsPageRoot {...args} />
</UserProvider>
)
}
export const OverleafWithAccessTokens = args => {
setPersonalAccessTokensMeta()
return Overleaf(args)
}
export const ServerPro = args => {
setDefaultAccountInfoMeta()
setDefaultPasswordMeta()
setPersonalAccessTokensMeta()
useFetchMock(fetchMock => {
defaultSetupAccountInfoMocks(fetchMock)
defaultSetupPasswordMocks(fetchMock)
})
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: false,
isOverleaf: false,
})
window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
window.metaAttributesCache.delete('ol-oauthProviders')
return (
<UserProvider>
<SettingsPageRoot {...args} />
</UserProvider>
)
}
export default {
title: 'Account Settings / Full Page',
component: SettingsPageRoot,
decorators: [ScopeDecorator],
}

View File

@@ -0,0 +1,49 @@
import useFetchMock from '../hooks/use-fetch-mock'
import PasswordSection from '../../js/features/settings/components/password-section'
import { setDefaultMeta, defaultSetupMocks } from './helpers/password'
import getMeta from '@/utils/meta'
export const Success = args => {
setDefaultMeta()
useFetchMock(defaultSetupMocks)
return <PasswordSection {...args} />
}
export const ManagedExternally = args => {
setDefaultMeta()
Object.assign(getMeta('ol-ExposedSettings'), {
isOverleaf: false,
})
window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', true)
useFetchMock(defaultSetupMocks)
return <PasswordSection {...args} />
}
export const NoExistingPassword = args => {
setDefaultMeta()
window.metaAttributesCache.set('ol-hasPassword', false)
useFetchMock(defaultSetupMocks)
return <PasswordSection {...args} />
}
export const Error = args => {
setDefaultMeta()
useFetchMock(fetchMock =>
fetchMock.post(/\/user\/password\/update/, {
status: 400,
body: {
message: 'Your old password is wrong',
},
})
)
return <PasswordSection {...args} />
}
export default {
title: 'Account Settings / Password',
component: PasswordSection,
}

View File

@@ -0,0 +1,10 @@
import SessionsSection from '../../js/features/settings/components/sessions-section'
export const Section = args => {
return <SessionsSection {...args} />
}
export default {
title: 'Account Settings / Sessions',
component: SessionsSection,
}

View File

@@ -0,0 +1,56 @@
import EmailsSection from '../../js/features/settings/components/emails-section'
import { SSOAlert } from '../../js/features/settings/components/emails/sso-alert'
export const Info = () => {
window.metaAttributesCache.set('ol-institutionLinked', {
universityName: 'Overleaf University',
})
return <SSOAlert />
}
export const InfoWithEntitlement = () => {
window.metaAttributesCache.set('ol-institutionLinked', {
universityName: 'Overleaf University',
hasEntitlement: true,
})
return <SSOAlert />
}
export const NonCanonicalEmail = () => {
window.metaAttributesCache.set('ol-institutionLinked', {
universityName: 'Overleaf University',
})
window.metaAttributesCache.set(
'ol-institutionEmailNonCanonical',
'user@example.com'
)
return <SSOAlert />
}
export const Error = () => {
window.metaAttributesCache.set('ol-samlError', {
translatedMessage: 'There was an Error',
})
return <SSOAlert />
}
export const ErrorTranslated = () => {
window.metaAttributesCache.set('ol-samlError', {
translatedMessage: 'Translated Error Message',
message: 'There was an Error',
})
return <SSOAlert />
}
export const ErrorWithTryAgain = () => {
window.metaAttributesCache.set('ol-samlError', {
message: 'There was an Error',
tryAgain: true,
})
return <SSOAlert />
}
export default {
title: 'Account Settings / SSO Alerts',
component: EmailsSection,
}