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,29 @@
import AddAffiliation from '../../js/features/project-list/components/add-affiliation'
import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
import getMeta from '@/utils/meta'
export const Add = (args: any) => {
Object.assign(getMeta('ol-ExposedSettings'), {
isOverleaf: true,
})
window.metaAttributesCache.set('ol-userAffiliations', [])
useFetchMock(fetchMock => {
fetchMock.post(/\/api\/project/, {
projects: projectsData,
totalSize: projectsData.length,
})
})
return (
<ProjectListProvider>
<AddAffiliation {...args} />
</ProjectListProvider>
)
}
export default {
title: 'Project List / Affiliation',
component: AddAffiliation,
}

View File

@@ -0,0 +1,15 @@
import { ColorPicker } from '../../js/features/project-list/components/color-picker/color-picker'
import { ColorPickerProvider } from '../../js/features/project-list/context/color-picker-context'
export const Select = (args: any) => {
return (
<ColorPickerProvider>
<ColorPicker {...args} />
</ColorPickerProvider>
)
}
export default {
title: 'Project List / Color Picker',
component: ColorPicker,
}

View File

@@ -0,0 +1,65 @@
import ProjectListTable from '../../js/features/project-list/components/table/project-list-table'
import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
export const Successful = (args: any) => {
window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
useFetchMock(fetchMock => {
fetchMock.post(/\/api\/project/, {
projects: projectsData,
totalSize: projectsData.length,
})
fetchMock.post(
/\/compile/,
{
status: 'success',
compileGroup: 'standard',
clsiServerId: 'server-1',
outputFiles: [{ path: 'output.pdf', build: '123-321' }],
},
{
delay: 1_000,
}
)
})
return (
<ProjectListProvider>
<ProjectListTable {...args} />
</ProjectListProvider>
)
}
export const Failure = (args: any) => {
window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
useFetchMock(fetchMock => {
fetchMock.post(/\/api\/project/, {
projects: projectsData,
totalSize: projectsData.length,
})
fetchMock.post(
/\/compile/,
{ status: 'failure', outputFiles: [] },
{ delay: 1_000 }
)
})
return (
<ProjectListProvider>
<ProjectListTable {...args} />
</ProjectListProvider>
)
}
export default {
title: 'Project List / PDF download',
component: ProjectListTable,
decorators: [
(Story: any) => (
<div className="project-list-react">
<Story />
</div>
),
],
}

View File

@@ -0,0 +1,75 @@
import CurrentPlanWidget from '../../js/features/project-list/components/current-plan-widget/current-plan-widget'
export const FreePlan = (args: any) => {
window.metaAttributesCache.set('ol-usersBestSubscription', {
type: 'free',
})
return <CurrentPlanWidget {...args} />
}
export const PaidPlanTrialLastDay = (args: any) => {
window.metaAttributesCache.set('ol-usersBestSubscription', {
type: 'individual',
remainingTrialDays: 1,
plan: {
name: 'Individual',
},
subscription: {
name: 'Example Name',
},
})
return <CurrentPlanWidget {...args} />
}
export const PaidPlanRemainingDays = (args: any) => {
window.metaAttributesCache.set('ol-usersBestSubscription', {
type: 'individual',
remainingTrialDays: 5,
plan: {
name: 'Individual',
},
subscription: {
name: 'Example Name',
},
})
return <CurrentPlanWidget {...args} />
}
export const PaidPlanActive = (args: any) => {
window.metaAttributesCache.set('ol-usersBestSubscription', {
type: 'individual',
plan: {
name: 'Individual',
},
subscription: {
name: 'Example Name',
},
})
return <CurrentPlanWidget {...args} />
}
export const PausedPlan = (args: any) => {
window.metaAttributesCache.set('ol-usersBestSubscription', {
type: 'individual',
plan: {
name: 'Individual',
},
subscription: {
name: 'Example Name',
recurlyStatus: {
state: 'paused',
},
},
})
return <CurrentPlanWidget {...args} />
}
export default {
title: 'Project List / Current Plan Widget',
component: CurrentPlanWidget,
}

View File

@@ -0,0 +1,128 @@
import { merge, cloneDeep } from 'lodash'
import { type FetchMock } from 'fetch-mock'
import { UserEmailData } from '../../../../types/user-email'
import {
Institution,
Notification,
} from '../../../../types/project/dashboard/notification'
import { DeepPartial, DeepReadonly } from '../../../../types/utils'
import { Project } from '../../../../types/project/dashboard/api'
import getMeta from '@/utils/meta'
const MOCK_DELAY = 1000
const fakeInstitutionData = {
email: 'email@example.com',
institutionEmail: 'institution@example.com',
institutionId: 123,
institutionName: 'Abc Institution',
requestedEmail: 'requested@example.com',
} as DeepReadonly<Institution>
export const fakeReconfirmationUsersData = {
affiliation: {
institution: {
ssoEnabled: false,
ssoBeta: false,
name: 'Abc Institution',
},
},
samlProviderId: 'Saml Provider',
email: 'reconfirmation-email@overleaf.com',
default: false,
} as DeepReadonly<UserEmailData>
export function defaultSetupMocks(fetchMock: FetchMock) {
// at least one project is required to show some notifications
const projects = [{}] as Project[]
fetchMock.post(/\/api\/project/, {
status: 200,
body: {
projects,
totalSize: projects.length,
},
})
}
export function setDefaultMeta() {
Object.assign(getMeta('ol-ExposedSettings'), {
emailConfirmationDisabled: false,
samlInitPath: '/fakeSaml',
appName: 'Overleaf',
})
window.metaAttributesCache.set('ol-notificationsInstitution', [])
window.metaAttributesCache.set('ol-userEmails', [])
}
export function errorsMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(/\/user\/emails\/*/, 500, { delay: MOCK_DELAY })
fetchMock.post(
/\/project\/[A-Za-z0-9]+\/invite\/token\/[A-Za-z0-9]+\/accept/,
500,
{ delay: MOCK_DELAY }
)
fetchMock.post(/\/user\/emails\/send-reconfirmation/, 500, {
delay: MOCK_DELAY,
})
}
export function setInstitutionMeta(institutionData: Partial<Institution>) {
setDefaultMeta()
window.metaAttributesCache.set('ol-notificationsInstitution', [
merge(cloneDeep(fakeInstitutionData), institutionData),
])
}
export function institutionSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.delete(/\/notifications\/*/, 200, { delay: MOCK_DELAY })
}
export function setCommonMeta(notificationData: DeepPartial<Notification>) {
setDefaultMeta()
window.metaAttributesCache.set('ol-notifications', [notificationData])
}
export function commonSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(
/\/project\/[A-Za-z0-9]+\/invite\/token\/[A-Za-z0-9]+\/accept/,
200,
{ delay: MOCK_DELAY }
)
}
export function setReconfirmationMeta() {
setDefaultMeta()
window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
}
export function reconfirmationSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(/\/user\/emails\/resend_confirmation/, 200, {
delay: MOCK_DELAY,
})
}
export function setReconfirmAffiliationMeta() {
setDefaultMeta()
window.metaAttributesCache.set(
'ol-reconfirmedViaSAML',
fakeReconfirmationUsersData.samlProviderId
)
}
export function reconfirmAffiliationSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(/\/api\/project/, {
status: 200,
body: {
projects: [{}],
totalSize: 0,
},
})
fetchMock.post(/\/user\/emails\/send-reconfirmation/, 200, {
delay: MOCK_DELAY,
})
}

View File

@@ -0,0 +1,10 @@
import INRBanner from '@/features/project-list/components/notifications/ads/inr-banner'
export const Default = () => {
return <INRBanner />
}
export default {
title: 'Project List / INR Banner',
component: INRBanner,
}

View File

@@ -0,0 +1,107 @@
import NewProjectButton from '@/features/project-list/components/new-project-button'
import { ProjectListProvider } from '@/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import getMeta from '@/utils/meta'
import { SplitTestProvider } from '@/shared/context/split-test-context'
const templateLinks = [
{
name: 'Journal articles',
url: '/gallery/tagged/academic-journal',
},
{
name: 'Books',
url: '/gallery/tagged/book',
},
{
name: 'Formal letters',
url: '/gallery/tagged/formal-letter',
},
{
name: 'Assignments',
url: '/gallery/tagged/homework',
},
{
name: 'Posters',
url: '/gallery/tagged/poster',
},
{
name: 'Presentations',
url: '/gallery/tagged/presentation',
},
{
name: 'Reports',
url: '/gallery/tagged/report',
},
{
name: 'CVs and résumés',
url: '/gallery/tagged/cv',
},
{
name: 'Theses',
url: '/gallery/tagged/thesis',
},
{
name: 'view_all',
url: '/latex/templates',
},
]
export const Success = () => {
Object.assign(getMeta('ol-ExposedSettings'), {
templateLinks,
})
useFetchMock(fetchMock => {
fetchMock.post(
'express:/project/new',
{
status: 200,
body: {
project_id: '123',
},
},
{ delay: 250 }
)
})
return (
<ProjectListProvider>
<SplitTestProvider>
<NewProjectButton id="new-project-button-story" />
</SplitTestProvider>
</ProjectListProvider>
)
}
export const Error = () => {
Object.assign(getMeta('ol-ExposedSettings'), {
templateLinks,
})
useFetchMock(fetchMock => {
fetchMock.post(
'express:/project/new',
{
status: 400,
body: {
message: 'Something went horribly wrong!',
},
},
{ delay: 250 }
)
})
return (
<ProjectListProvider>
<SplitTestProvider>
<NewProjectButton id="new-project-button-story" />
</SplitTestProvider>
</ProjectListProvider>
)
}
export default {
title: 'Project List / New Project Button',
component: NewProjectButton,
}

View File

@@ -0,0 +1,346 @@
import UserNotifications from '../../js/features/project-list/components/notifications/user-notifications'
import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import {
commonSetupMocks,
errorsMocks,
fakeReconfirmationUsersData,
institutionSetupMocks,
reconfirmAffiliationSetupMocks,
reconfirmationSetupMocks,
setCommonMeta,
setInstitutionMeta,
setReconfirmAffiliationMeta,
setReconfirmationMeta,
} from './helpers/emails'
import { useMeta } from '../hooks/use-meta'
export const ProjectInvite = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
templateKey: 'notification_project_invite',
messageOpts: {
projectId: '123',
projectName: 'Abc Project',
userName: 'fakeUser',
token: 'abcdef',
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ProjectInviteNetworkError = (args: any) => {
useFetchMock(errorsMocks)
setCommonMeta({
templateKey: 'notification_project_invite',
messageOpts: {
projectId: '123',
projectName: 'Abc Project',
userName: 'fakeUser',
token: 'abcdef',
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const Wfh2020UpgradeOffer = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'wfh_2020_upgrade_offer',
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const IPMatchedAffiliationSsoEnabled = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_ip_matched_affiliation',
messageOpts: {
university_name: 'Abc University',
institutionId: '456',
ssoEnabled: true,
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const IPMatchedAffiliationSsoDisabled = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_ip_matched_affiliation',
messageOpts: {
university_name: 'Abc University',
institutionId: '456',
ssoEnabled: false,
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const TpdsFileLimit = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_tpds_file_limit',
messageOpts: {
projectName: 'Abc Project',
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const DropBoxDuplicateProjectNames = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_dropbox_duplicate_project_names',
messageOpts: {
projectName: 'Abc Project',
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const DropBoxUnlinkedDueToLapsedReconfirmation = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
})
useMeta({
'ol-user': { features: {} },
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const NotificationGroupInvitation = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_group_invitation',
messageOpts: {
inviterName: 'John Doe',
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const NotificationGroupInvitationCancelSubscription = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({
_id: 1,
templateKey: 'notification_group_invitation',
messageOpts: {
inviterName: 'John Doe',
},
})
window.metaAttributesCache.set('ol-hasIndividualRecurlySubscription', true)
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const NonSpecificMessage = (args: any) => {
useFetchMock(commonSetupMocks)
setCommonMeta({ _id: 1, html: 'Non specific message' })
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const InstitutionSsoAvailable = (args: any) => {
useFetchMock(institutionSetupMocks)
setInstitutionMeta({
_id: 1,
templateKey: 'notification_institution_sso_available',
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const InstitutionSsoLinked = (args: any) => {
useFetchMock(institutionSetupMocks)
setInstitutionMeta({
_id: 1,
templateKey: 'notification_institution_sso_linked',
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const InstitutionSsoNonCanonical = (args: any) => {
useFetchMock(institutionSetupMocks)
setInstitutionMeta({
_id: 1,
templateKey: 'notification_institution_sso_non_canonical',
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const InstitutionSsoAlreadyRegistered = (args: any) => {
useFetchMock(institutionSetupMocks)
setInstitutionMeta({
_id: 1,
templateKey: 'notification_institution_sso_already_registered',
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const InstitutionSsoError = (args: any) => {
useFetchMock(institutionSetupMocks)
setInstitutionMeta({
templateKey: 'notification_institution_sso_error',
error: {
message: 'message',
translatedMessage: 'Translated Message',
tryAgain: true,
},
})
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ResendConfirmationEmail = (args: any) => {
useFetchMock(reconfirmationSetupMocks)
setReconfirmationMeta()
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ResendConfirmationEmailNetworkError = (args: any) => {
useFetchMock(errorsMocks)
setReconfirmationMeta()
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ReconfirmAffiliation = (args: any) => {
useFetchMock(reconfirmAffiliationSetupMocks)
setReconfirmAffiliationMeta()
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
fakeReconfirmationUsersData,
])
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ReconfirmAffiliationNetworkError = (args: any) => {
useFetchMock(errorsMocks)
setReconfirmAffiliationMeta()
window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
fakeReconfirmationUsersData,
])
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export const ReconfirmedAffiliationSuccess = (args: any) => {
useFetchMock(reconfirmAffiliationSetupMocks)
setReconfirmAffiliationMeta()
window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
return (
<ProjectListProvider>
<UserNotifications {...args} />
</ProjectListProvider>
)
}
export default {
title: 'Project List / Notifications',
component: UserNotifications,
}

View File

@@ -0,0 +1,60 @@
import ProjectListTable from '../../js/features/project-list/components/table/project-list-table'
import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import {
copyableProject,
projectsData,
} from '../../../test/frontend/features/project-list/fixtures/projects-data'
import { useMeta } from '../hooks/use-meta'
import { tags } from '../../../test/frontend/features/project-list/fixtures/tags-data'
import { v4 as uuid } from 'uuid'
const MOCK_DELAY = 500
export const Interactive = (args: any) => {
window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
useFetchMock(fetchMock => {
fetchMock.post(
/\/api\/project/,
{ projects: projectsData, totalSize: projectsData.length },
{ delay: MOCK_DELAY }
)
fetchMock.post(
'express:/project/:projectId/clone',
() => ({
project_id: uuid(),
name: copyableProject.name,
lastUpdated: new Date().toISOString(),
owner: {
_id: copyableProject.owner?.id,
email: copyableProject.owner?.id,
first_name: copyableProject.owner?.firstName,
last_name: copyableProject.owner?.lastName,
},
}),
{ delay: MOCK_DELAY }
)
})
useMeta({
'ol-tags': tags,
})
return (
<ProjectListProvider>
<ProjectListTable {...args} />
</ProjectListProvider>
)
}
export default {
title: 'Project List / Project Table',
component: ProjectListTable,
decorators: [
(Story: any) => (
<div className="project-list-react">
<Story />
</div>
),
],
}

View File

@@ -0,0 +1,27 @@
import SearchForm from '../../js/features/project-list/components/search-form'
import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
import useFetchMock from '../hooks/use-fetch-mock'
import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
export const Search = (args: any) => {
useFetchMock(fetchMock => {
fetchMock.post(/\/api\/project/, {
projects: projectsData,
totalSize: projectsData.length,
})
})
return (
<ProjectListProvider>
<SearchForm {...args} />
</ProjectListProvider>
)
}
export default {
title: 'Project List / Project Search',
component: SearchForm,
args: {
inputValue: '',
},
}

View File

@@ -0,0 +1,31 @@
import SurveyWidget from '../../js/features/project-list/components/survey-widget'
export const Survey = (args: any) => {
localStorage.clear()
window.metaAttributesCache.set('ol-survey', {
name: 'my-survey',
preText: 'To help shape the future of Overleaf',
linkText: 'Click here!',
url: 'https://example.com/my-survey',
})
return <SurveyWidget {...args} />
}
export const UndefinedSurvey = (args: any) => {
localStorage.clear()
return <SurveyWidget {...args} />
}
export const EmptySurvey = (args: any) => {
localStorage.clear()
window.metaAttributesCache.set('ol-survey', {})
return <SurveyWidget {...args} />
}
export default {
title: 'Project List / Survey Widget',
component: SurveyWidget,
}

View File

@@ -0,0 +1,42 @@
import SystemMessages from '@/shared/components/system-messages'
import useFetchMock from '../hooks/use-fetch-mock'
import { type FetchMock } from 'fetch-mock'
export const SystemMessage = (args: any) => {
useFetchMock((fetchMock: FetchMock) => {
fetchMock.get(/\/system\/messages/, [
{
_id: 1,
content: `
Closing this message will mark it as hidden.
Remove it from the local storage to make it appear again.
`,
},
{
_id: 'protected',
content: 'A protected message content - cannot be closed',
},
])
})
return <SystemMessages {...args} />
}
export const TranslationMessage = (args: any) => {
useFetchMock((fetchMock: FetchMock) => {
fetchMock.get(/\/system\/messages/, [])
})
window.metaAttributesCache.set('ol-suggestedLanguage', {
url: '/dev/null',
lngName: 'German',
imgUrl: 'https://flagcdn.com/w40/de.png',
})
return <SystemMessages {...args} />
}
export default {
title: 'Project List / System Messages',
component: SystemMessages,
}