Replace placeholders with labels (#26811)

* Replace placeholders for labels

* Remove redundant aria-label and update labels with `.visually-hidden`

* Change "Invite more members" to a label

* Fix helper text for group members and fix responsiveness

* Change error message to an error notification

* Use label and helper text instead of a placeholder

* Remove redundant label and use the placeholder text instead for the admin user searchbar

* Extract translations

GitOrigin-RevId: a504fda9779da82920b57c7b8aad38a8b027d09a
This commit is contained in:
Rebeka Dekany
2025-08-25 10:59:22 +02:00
committed by Copybot
parent 7c811fef11
commit 0c011dfa0e
47 changed files with 244 additions and 256 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ describe('filestore migration', function () {
name: /Create First Project|New Project/,
}).click()
cy.findByRole('link', { name: 'Example Project' }).click()
cy.findByPlaceholderText('Project Name').type(projectName)
cy.findByLabelText(/Project name/i).type(projectName)
cy.findByRole('button', { name: 'Create' }).click()
cy.url()
.should('match', /\/project\/[a-fA-F0-9]{24}/)
+4 -2
View File
@@ -106,8 +106,10 @@ function shareProjectByEmail(
openProjectByName(projectName)
cy.findByText('Share').click()
cy.findByRole('dialog').within(() => {
cy.findByLabelText('Add people', { selector: 'input' }).type(`${email},`)
cy.findByLabelText('Add people', { selector: 'input' })
cy.findByLabelText('Add email address', { selector: 'input' }).type(
`${email},`
)
cy.findByLabelText('Add email address', { selector: 'input' })
.parents('form')
.within(() => {
cy.findByTestId('add-collaborator-select')
+3 -1
View File
@@ -21,7 +21,9 @@ describe('History', function () {
cy.findByText('Label this version').click()
})
cy.findByRole('dialog').within(() => {
cy.get('input[placeholder="New label name"]').type(`${name}{enter}`)
cy.findByLabelText(/New label name/i)
.as('input')
.type(`${name}{enter}`)
})
}
+6 -12
View File
@@ -28,11 +28,10 @@ block content
form(method='post' action='/admin/messages')
input(name='_csrf' type='hidden' value=csrfToken)
.form-group
label.form-label(for='content')
input.form-control(
label.form-label(for='system-message') Message
input#system-message.form-control(
name='content'
type='text'
placeholder='Message…'
required
)
button.btn.btn-primary(type='submit') Post Message
@@ -79,11 +78,11 @@ block content
form.col-xs-6(method='post' action='/admin/flushProjectToTpds')
input(name='_csrf' type='hidden' value=csrfToken)
.form-group
label.form-label(for='project_id') project_id
label.form-label(for='project-id') project_id
input.form-control(
name='project_id'
id='project-id'
type='text'
placeholder='project_id'
required
)
.form-group
@@ -94,12 +93,7 @@ block content
form.col-xs-6(method='post' action='/admin/pollDropboxForUser')
input(name='_csrf' type='hidden' value=csrfToken)
.form-group
label.form-label(for='user_id') user_id
input.form-control(
name='user_id'
type='text'
placeholder='user_id'
required
)
label.form-label(for='user-id') user_id
input.form-control(name='user_id' id='user-id' type='text' required)
.form-group
button.btn-primary.btn(type='submit') Poll
+6 -4
View File
@@ -22,19 +22,21 @@ block content
+customFormMessageNewStyle('password-compromised')
| !{translate('password_compromised_try_again_or_use_known_device_or_reset', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: '/user/password/reset', target: '_blank'}}])}.
.form-group
input.form-control(
label(for='email') #{translate("email")}
input#email.form-control(
name='email'
type='email'
required
placeholder='email@example.com'
autofocus='true'
autocomplete='username'
)
.form-group
input.form-control(
label(for='password') #{translate("password")}
input#password.form-control(
name='password'
type='password'
autocomplete='current-password'
required
placeholder='********'
)
.actions
button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
@@ -56,7 +56,6 @@ block content
label.form-label(for='email') #{translate("email")}
input#email.form-control(
name='email'
aria-label='email'
type='email'
required
autocomplete='username'
+1 -3
View File
@@ -45,11 +45,9 @@ block content
input(name='_csrf' type='hidden' value=csrfToken)
.form-group.mb-3
label.form-label(for='email') #{translate("please_enter_email")}
input.form-control(
input#email.form-control(
name='email'
aria-label='email'
type='email'
placeholder='email@example.com'
required
autofocus
value=email
@@ -82,12 +82,11 @@
"add_files": "",
"add_more_collaborators": "",
"add_more_licenses_to_my_plan": "",
"add_more_managers": "",
"add_more_manager_emails": "",
"add_new_email": "",
"add_on": "",
"add_ons": "",
"add_or_remove_project_from_tag": "",
"add_people": "",
"add_role_and_department": "",
"add_to_dictionary": "",
"add_to_tag": "",
@@ -491,6 +490,7 @@
"edit_figure": "",
"edit_sso_configuration": "",
"edit_tag": "",
"edit_tag_name": "",
"edit_your_custom_dictionary": "",
"editing": "",
"editing_captions": "",
@@ -536,7 +536,6 @@
"end_of_document": "",
"ensure_recover_account": "",
"enter_any_size_including_units_or_valid_latex_command": "",
"enter_image_url": "",
"enter_the_code": "",
"enter_the_confirmation_code": "",
"enter_the_number_of_licenses_youd_like_to_add_to_see_the_cost_breakdown": "",
@@ -604,6 +603,7 @@
"fixed_width_wrap_text": "",
"fold_line": "",
"folder_location": "",
"folder_name": "",
"following_paths_conflict": "",
"font_family": "",
"font_size": "",
@@ -1019,6 +1019,7 @@
"manage_publisher_managers": "",
"manage_sessions": "",
"manage_subscription": "",
"manage_tag": "",
"manage_your_ai_assist_add_on": "",
"managed": "",
"managed_user_accounts": "",
@@ -1096,7 +1097,6 @@
"new_navigation_introducing_left_hand_side_rail_and_top_menus": "",
"new_password": "",
"new_project": "",
"new_project_name": "",
"new_subscription_will_be_billed_immediately": "",
"new_tag": "",
"new_tag_name": "",
@@ -27,7 +27,7 @@ function MessageInput({ resetUnreadMessages, sendMessage }: MessageInputProps) {
return (
<form className="new-message">
<label htmlFor="chat-input" className="visually-hidden">
{t('your_message_to_collaborators')}
{`${t('your_message_to_collaborators')}`}
</label>
<textarea
id="chat-input"
@@ -99,7 +99,6 @@ export default function CloneProjectModalContent({
<OLFormLabel>{t('new_name')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('new_project_name')}
required
value={clonedProjectName}
onChange={event => setClonedProjectName(event.target.value)}
@@ -11,6 +11,9 @@ import OLModal, {
OLModalTitle,
} from '@/shared/components/ol/ol-modal'
import OLButton from '@/shared/components/ol/ol-button'
import OLFormControl from '@/shared/components/ol/ol-form-control'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import OLFormGroup from '@/shared/components/ol/ol-form-group'
function FileTreeModalCreateFolder() {
const { t } = useTranslation()
@@ -116,6 +119,7 @@ function InputName({
handleCreateFolder: () => void
}) {
const { autoFocusedRef } = useRefWithAutoFocus<HTMLInputElement>()
const { t } = useTranslation()
function handleFocus(ev: React.FocusEvent<HTMLInputElement>) {
ev.target.setSelectionRange(0, -1)
@@ -133,15 +137,17 @@ function InputName({
}
return (
<input
ref={autoFocusedRef}
className="form-control"
type="text"
value={name}
onKeyDown={handleKeyDown}
onChange={handleChange}
onFocus={handleFocus}
/>
<OLFormGroup controlId="folder-name">
<OLFormLabel>{t('folder_name')}</OLFormLabel>
<OLFormControl
type="text"
ref={autoFocusedRef}
value={name}
onKeyDown={handleKeyDown}
onChange={handleChange}
onFocus={handleFocus}
/>
</OLFormGroup>
)
}
@@ -14,6 +14,7 @@ import OLButton from '@/shared/components/ol/ol-button'
import OLFormControl from '@/shared/components/ol/ol-form-control'
import OLFormText from '@/shared/components/ol/ol-form-text'
import OLNotification from '@/shared/components/ol/ol-notification'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
export default function GroupMembers() {
const { isReady } = useWaitForI18n()
@@ -142,7 +143,6 @@ export default function GroupMembers() {
className="add-more-members-form"
data-testid="add-more-members-form"
>
<p className="small">{t('invite_more_members')}</p>
{memberAdded && (
<OLNotification
content={t('members_added')}
@@ -152,37 +152,35 @@ export default function GroupMembers() {
)}
<ErrorAlert error={inviteError} />
<form onSubmit={onAddMembersSubmit}>
<OLRow>
<OLCol xs={6}>
<OLRow className="align-items-center">
<OLCol lg={8}>
<OLFormLabel htmlFor="add-members-emails">
{t('invite_more_members')}
</OLFormLabel>
<OLFormControl
id="add-members-emails"
type="input"
placeholder="jane@example.com, joe@example.com"
aria-describedby="add-members-description"
value={emailString}
onChange={handleEmailsChange}
data-testid="add-members-input"
aria-describedby="invite-more-members-help-text"
/>
</OLCol>
<OLCol xs={4}>
<OLButton
variant="primary"
onClick={onAddMembersSubmit}
isLoading={inviteMemberLoading}
loadingLabel={t('inviting')}
>
{t('invite')}
</OLButton>
</OLCol>
<OLCol xs={2}>
<a href={paths.exportMembers}>{t('export_csv')}</a>
</OLCol>
</OLRow>
<OLRow>
<OLCol xs={8}>
<OLFormText>
<OLFormText id="invite-more-members-help-text">
{t('add_comma_separated_emails_help')}
</OLFormText>
</OLCol>
<OLCol lg={4} className="mt-3 mt-lg-0">
<div className="align-items-center d-flex flex-column flex-lg-row gap-3 text-center">
<OLButton
variant="primary"
onClick={onAddMembersSubmit}
isLoading={inviteMemberLoading}
loadingLabel={t('inviting')}
>
{t('invite')}
</OLButton>
<a href={paths.exportMembers}>{t('export_csv')}</a>
</div>
</OLCol>
</OLRow>
</form>
</div>
@@ -18,6 +18,7 @@ import OLFormText from '@/shared/components/ol/ol-form-text'
import OLTable from '@/shared/components/ol/ol-table'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
import OLFormCheckbox from '@/shared/components/ol/ol-form-checkbox'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
type ManagersPaths = {
addMember: string
@@ -238,20 +239,28 @@ export function ManagersTable({
<>
<hr />
<div>
<p className="small">{t('add_more_managers')}</p>
<ErrorAlert error={inviteError} />
<form onSubmit={addManagers} data-testid="add-members-form">
<OLRow>
<OLCol xs={6}>
<OLCol lg={8}>
<OLFormLabel htmlFor="add-manager-emails">
{t('add_more_manager_emails')}
</OLFormLabel>
<OLFormControl
id="add-manager-emails"
type="input"
placeholder="jane@example.com, joe@example.com"
aria-describedby="add-members-description"
value={emailString}
onChange={handleEmailsChange}
aria-describedby="invite-more-manager-help-text"
/>
<OLFormText id="invite-more-manager-help-text">
{t('add_comma_separated_emails_help')}
</OLFormText>
</OLCol>
<OLCol xs={4}>
<OLCol
lg={2}
className="mt-3 mt-lg-0 d-flex align-items-center d-flex flex-column flex-lg-row"
>
<OLButton
variant="primary"
onClick={addManagers}
@@ -261,13 +270,6 @@ export function ManagersTable({
</OLButton>
</OLCol>
</OLRow>
<OLRow>
<OLCol xs={8}>
<OLFormText>
{t('add_comma_separated_emails_help')}
</OLFormText>
</OLCol>
</OLRow>
</form>
</div>
</>
@@ -19,6 +19,7 @@ import { Label } from '../../services/types/label'
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
import { debugConsole } from '@/utils/debugging'
import OLFormControl from '@/shared/components/ol/ol-form-control'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
type AddLabelModalProps = {
show: boolean
@@ -91,11 +92,11 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) {
<OLForm onSubmit={handleSubmit}>
<OLModalBody>
{isError && <ModalError error={responseError} />}
<OLFormGroup>
<OLFormGroup controlId="add-label-modal">
<OLFormLabel>{t('history_new_label_name')}</OLFormLabel>
<OLFormControl
ref={autoFocusedRef}
type="text"
placeholder={t('history_new_label_name')}
required
value={comment}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
@@ -20,6 +20,7 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import OLButton from '@/shared/components/ol/ol-button'
import Notification from '@/shared/components/notification'
import OLFormControl from '@/shared/components/ol/ol-form-control'
type EditTagModalProps = {
id: string
@@ -93,12 +94,12 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
<OLModalBody>
<OLForm onSubmit={handleSubmit}>
<OLFormGroup>
<input
<OLFormGroup controlId="edit-tag-modal">
<OLFormLabel>{t('edit_tag_name')}</OLFormLabel>
<OLFormControl
ref={autoFocusedRef}
className="form-control"
type="text"
placeholder="Tag Name"
name="new-tag-name"
value={newTagName === undefined ? (tag.name ?? '') : newTagName}
required
@@ -19,6 +19,7 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import OLButton from '@/shared/components/ol/ol-button'
import Notification from '@/shared/components/notification'
import OLFormControl from '@/shared/components/ol/ol-form-control'
type ManageTagModalProps = {
id: string
@@ -94,12 +95,12 @@ export function ManageTagModal({
<OLModalBody>
<OLForm onSubmit={handleSubmit}>
<OLFormGroup>
<input
<OLFormGroup controlId="manage-tag-modal">
<OLFormLabel>{t('manage_tag')}</OLFormLabel>
<OLFormControl
ref={autoFocusedRef}
className="form-control"
type="text"
placeholder="Tag Name"
name="new-tag-name"
value={newTagName === undefined ? (tag.name ?? '') : newTagName}
required
@@ -121,7 +121,6 @@ function RenameProjectModal({
<OLFormLabel>{t('new_name')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('project_name')}
required
value={newProjectName}
onChange={handleOnChange}
@@ -17,6 +17,8 @@ import {
import OLFormControl from '@/shared/components/ol/ol-form-control'
import OLButton from '@/shared/components/ol/ol-button'
import OLForm from '@/shared/components/ol/ol-form'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import OLFormGroup from '@/shared/components/ol/ol-form-group'
type NewProjectData = {
project_id: string
@@ -86,13 +88,15 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
</div>
)}
<OLForm onSubmit={handleSubmit}>
<OLFormControl
type="text"
ref={autoFocusedRef}
placeholder={t('project_name')}
onChange={handleChangeName}
value={projectName}
/>
<OLFormGroup controlId="project-name">
<OLFormLabel>{t('project_name')}</OLFormLabel>
<OLFormControl
type="text"
ref={autoFocusedRef}
onChange={handleChangeName}
value={projectName}
/>
</OLFormGroup>
</OLForm>
</OLModalBody>
@@ -77,7 +77,6 @@ function LeaveModalForm({
<OLFormLabel>{t('email')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('email')}
required
value={email}
onChange={handleEmailChange}
@@ -87,7 +86,6 @@ function LeaveModalForm({
<OLFormLabel>{t('password')}</OLFormLabel>
<OLFormControl
type="password"
placeholder={t('password')}
required
value={password}
onChange={handlePasswordChange}
@@ -14,6 +14,7 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
import { Select } from '@/shared/components/select'
import OLButton from '@/shared/components/ol/ol-button'
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
import OLFormText from '@/shared/components/ol/ol-form-text'
export default function AddCollaborators({ readOnly }: { readOnly?: boolean }) {
const [privileges, setPrivileges] = useState<PermissionsLevel>('readAndWrite')
@@ -211,11 +212,12 @@ export default function AddCollaborators({ readOnly }: { readOnly?: boolean }) {
<SelectCollaborators
loading={!nonMemberContacts}
options={nonMemberContacts || []}
placeholder="Email, comma separated"
multipleSelectionProps={multipleSelectionProps}
/>
<OLFormText id="add-collaborator-help-text">
{t('add_comma_separated_emails_help')}
</OLFormText>
</OLFormGroup>
<OLFormGroup>
<div className="float-end add-collaborator-controls">
<Select
@@ -9,6 +9,7 @@ import Tag from '@/shared/components/tag'
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
import { Spinner } from 'react-bootstrap'
import { Contact } from '../utils/types'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
export type ContactItem = {
email: string
@@ -25,12 +26,10 @@ const matchAllSpaces =
export default function SelectCollaborators({
loading,
options,
placeholder,
multipleSelectionProps,
}: {
loading: boolean
options: Contact[]
placeholder: string
multipleSelectionProps: UseMultipleSelectionReturnValue<ContactItem>
}) {
const { t } = useTranslation()
@@ -166,20 +165,18 @@ export default function SelectCollaborators({
return (
<div className="tags-input tags-new">
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
<label className="small" {...getLabelProps()}>
<strong>
{t('add_people')}
&nbsp;
</strong>
<OLFormLabel className="small" {...getLabelProps()}>
{t('add_email_address')}
{loading && (
<Spinner
animation="border"
aria-hidden="true"
size="sm"
role="status"
className="ms-2"
/>
)}
</label>
</OLFormLabel>
<div className="host">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
@@ -197,16 +194,14 @@ export default function SelectCollaborators({
<input
data-testid="collaborator-email-input"
aria-describedby="add-collaborator-help-text"
{...getInputProps(
getDropdownProps({
className: classnames('input', {
'invalid-tag': !isValidInput,
}),
type: 'email',
placeholder,
size: inputValue.length
? inputValue.length + 5
: placeholder.length,
size: inputValue.length ? inputValue.length + 5 : 5,
ref: inputRef,
// preventKeyAction: showDropdown,
onBlur: () => {
@@ -79,7 +79,6 @@ export const FigureModalUrlSource: FC = () => {
<OLFormLabel>{t('image_url')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('enter_image_url')}
value={url}
onChange={e => {
setUrl(e.target.value)
@@ -88,37 +88,20 @@ export const Error: Story = {
return (
<>
<FormGroup controlId="id-1">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Large input"
size="lg"
isInvalid
{...args}
/>
<Form.Label>Large input label</Form.Label>
<FormControl size="lg" isInvalid {...args} />
<FormFeedback type="invalid">Error</FormFeedback>
</FormGroup>
<hr />
<FormGroup controlId="id-2">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Regular input"
isInvalid
{...args}
/>
<Form.Label>Regular input label</Form.Label>
<FormControl isInvalid {...args} />
<FormFeedback type="invalid">Error</FormFeedback>
</FormGroup>
<hr />
<FormGroup controlId="id-3">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Small input"
size="sm"
isInvalid
{...args}
/>
<Form.Label>Small input label</Form.Label>
<FormControl size="sm" isInvalid {...args} />
<FormFeedback type="invalid">Error</FormFeedback>
</FormGroup>
</>
@@ -131,34 +114,20 @@ export const Warning: Story = {
return (
<>
<FormGroup controlId="id-1">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Large input"
size="lg"
{...args}
/>
<Form.Label>Large input label</Form.Label>
<FormControl size="lg" {...args} />
<FormText type="warning">Warning</FormText>
</FormGroup>
<hr />
<FormGroup controlId="id-2">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Regular input"
{...args}
/>
<Form.Label>Regular input label</Form.Label>
<FormControl {...args} />
<FormText type="warning">Warning</FormText>
</FormGroup>
<hr />
<FormGroup controlId="id-3">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Small input"
size="sm"
{...args}
/>
<Form.Label>Small input label</Form.Label>
<FormControl size="sm" {...args} />
<FormText type="warning">Warning</FormText>
</FormGroup>
</>
@@ -171,34 +140,20 @@ export const Success: Story = {
return (
<>
<FormGroup controlId="id-1">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Large input"
size="lg"
{...args}
/>
<Form.Label>Large input label</Form.Label>
<FormControl size="lg" {...args} />
<FormText type="success">Success</FormText>
</FormGroup>
<hr />
<FormGroup controlId="id-2">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Regular input"
{...args}
/>
<Form.Label>Regular input label</Form.Label>
<FormControl {...args} />
<FormText type="success">Success</FormText>
</FormGroup>
<hr />
<FormGroup controlId="id-3">
<Form.Label>Label</Form.Label>
<FormControl
placeholder="Placeholder"
defaultValue="Small input"
size="sm"
{...args}
/>
<Form.Label>Small input label</Form.Label>
<FormControl size="sm" {...args} />
<FormText type="success">Success</FormText>
</FormGroup>
</>
@@ -4,10 +4,6 @@
box-sizing: border-box;
}
.tags-input label.small {
font-weight: normal;
}
.tags-input .host {
position: relative;
height: 100%;
+10 -7
View File
@@ -96,14 +96,13 @@
"add_manager_user_not_found": "This email address is not registered in Overleaf. Please ask the person you are trying to add as a manager to create an Overleaf account if they have not already done so. When adding a manager, please use one of the email addresses that are associated with their account.",
"add_more_collaborators": "Add more collaborators",
"add_more_licenses_to_my_plan": "Add more licenses to my plan",
"add_more_managers": "Add more managers",
"add_more_manager_emails": "Add more manager emails",
"add_new_email": "Add new email",
"add_on": "Add-on",
"add_ons": "Add-ons",
"add_ons_for_any_plan": "Add-ons for any plan",
"add_ons_for_any_plan_subheading": "Buy add-ons for any Overleaf plan (including the free plan) to unlock additional features.",
"add_or_remove_project_from_tag": "Add or remove project from tag __tagName__",
"add_people": "Add people",
"add_role_and_department": "Add role and department",
"add_to_dictionary": "Add to dictionary",
"add_to_tag": "Add to tag",
@@ -634,7 +633,8 @@
"edit_dictionary_remove": "Remove from dictionary",
"edit_figure": "Edit figure",
"edit_sso_configuration": "Edit SSO Configuration",
"edit_tag": "Edit Tag",
"edit_tag": "Edit tag",
"edit_tag_name": "Edit tag name",
"edit_your_custom_dictionary": "Edit your custom dictionary",
"editing": "Editing",
"editing_and_collaboration": "Editing and collaboration",
@@ -690,11 +690,12 @@
"end_of_document": "End of document",
"ensure_recover_account": "This will ensure that it can be used to recover your __appName__ account in case you lose access to your primary email address.",
"enter_any_size_including_units_or_valid_latex_command": "Enter any size (including units) or valid LaTeX command",
"enter_image_url": "Enter image URL",
"enter_the_code": "Enter the 6-digit code sent to __email__.",
"enter_the_confirmation_code": "Enter the 6-digit confirmation code sent to __email__.",
"enter_the_number_of_licenses_youd_like_to_add_to_see_the_cost_breakdown": "Enter the number of licenses youd like to add to see the cost breakdown.",
"enter_your_email": "Enter your email",
"enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password": "Enter your email address below, and we will send you a link to reset your password",
"enter_your_password": "Enter your password",
"equation_generator": "Equation Generator",
"equation_preview": "Equation preview",
"error": "Error",
@@ -784,6 +785,7 @@
"flexible_plans_for_everyone": "Flexible plans for everyone—from individual students and researchers, to large businesses and universities.",
"fold_line": "Fold line",
"folder_location": "Folder location",
"folder_name": "Folder name",
"folders": "Folders",
"following_paths_conflict": "The following files and folders conflict with the same path",
"font_family": "Font Family",
@@ -1038,6 +1040,7 @@
"import_from_github": "Import from GitHub",
"import_idp_metadata": "Import IdP metadata",
"import_to_sharelatex": "Import to __appName__",
"important_message": "Important message",
"imported_from_another_project_at_date": "Imported from <0>Another project</0>/__sourceEntityPathHTML__, at __formattedDate__ __relativeDate__",
"imported_from_external_provider_at_date": "Imported from <0>__shortenedUrlHTML__</0> at __formattedDate__ __relativeDate__",
"imported_from_mendeley_at_date": "Imported from Mendeley at __formattedDate__ __relativeDate__",
@@ -1327,6 +1330,7 @@
"manage_publisher_managers": "Manage publisher managers",
"manage_sessions": "Manage Your Sessions",
"manage_subscription": "Manage subscription",
"manage_tag": "Manage tag",
"manage_your_ai_assist_add_on": "Manage your AI Assist add-on",
"managed": "Managed",
"managed_user_accounts": "Managed user accounts",
@@ -1419,11 +1423,10 @@
"new_folder": "New folder",
"new_look_and_feel": "New look and feel",
"new_look_and_placement_of_the_settings": "New look and placement of the settings",
"new_name": "New Name",
"new_name": "New name",
"new_navigation_introducing_left_hand_side_rail_and_top_menus": "New navigation - introducing left-hand side rail and top menus",
"new_password": "New password",
"new_project": "New project",
"new_project_name": "New project name",
"new_snippet_project": "Untitled",
"new_subscription_will_be_billed_immediately": "Your new subscription will be billed immediately to your current payment method.",
"new_tag": "New tag",
@@ -1735,7 +1738,7 @@
"project_history_labels": "Project history and labels",
"project_last_published_at": "Your project was last published at",
"project_linked_to": "This project is linked to",
"project_name": "Project Name",
"project_name": "Project name",
"project_not_linked_to_github": "This project is not linked to a GitHub repository. You can create a repository for it in GitHub:",
"project_ownership_transfer_confirmation_1": "Are you sure you want to make <0>__user__</0> the owner of <1>__project__</1>?",
"project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
@@ -1,10 +1,14 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import PropTypes from 'prop-types'
import { postJSON } from '@/infrastructure/fetch-json'
import OLButton from '@/shared/components/ol/ol-button'
import OLForm from '@/shared/components/ol/ol-form'
import OLFormControl from '@/shared/components/ol/ol-form-control'
import OLFormLabel from '@/shared/components/ol/ol-form-label'
import OLFormText from '@/shared/components/ol/ol-form-text'
import OLCol from '@/shared/components/ol/ol-col'
import OLRow from '@/shared/components/ol/ol-row'
function RegisterForm({
setRegistrationSuccess,
setEmails,
@@ -12,6 +16,8 @@ function RegisterForm({
setFailedEmails,
}) {
const [isLoading, setIsLoading] = useState(false)
const { t } = useTranslation()
function handleRegister(event) {
event.preventDefault()
const formData = new FormData(event.target)
@@ -53,25 +59,30 @@ function RegisterForm({
return (
<OLForm onSubmit={handleRegister}>
<div className="d-flex gap-2 flex-wrap">
<div className="flex-grow-1 max-width">
<OLRow>
<OLCol lg={8}>
<OLFormLabel htmlFor="register-new-user-email">
Emails to register new users
</OLFormLabel>
<OLFormControl
className="form-control"
id="register-new-user-email"
name="email"
type="text"
placeholder="jane@example.com, joe@example.com"
aria-label="emails to register"
aria-describedby="input-details"
aria-describedby="register-new-user-email-helper"
/>
<p id="input-details" className="visually-hidden">
Enter the emails you would like to register and separate them using
commas
</p>
</div>
<OLButton type="submit" className="ms-auto" isLoading={isLoading}>
Register
</OLButton>
</div>
<OLFormText id="register-new-user-email-helper">
{t('add_comma_separated_emails_help')}
</OLFormText>
</OLCol>
<OLCol
lg={4}
className="mt-3 mt-lg-0 d-flex align-items-center flex-column flex-lg-row"
>
<OLButton type="submit" isLoading={isLoading}>
Register
</OLButton>
</OLCol>
</OLRow>
</OLForm>
)
}
@@ -4,6 +4,7 @@ import RegisterForm from './register-form'
import OLRow from '@/shared/components/ol/ol-row'
import OLCol from '@/shared/components/ol/ol-col'
import OLCard from '@/shared/components/ol/ol-card'
import Notification from '@/shared/components/notification'
function UserActivateRegister() {
const [emails, setEmails] = useState([])
@@ -16,7 +17,7 @@ function UserActivateRegister() {
<OLCol>
<OLCard>
<div className="page-header">
<h1>Register New Users</h1>
<h1>Register new users</h1>
</div>
<RegisterForm
setRegistrationSuccess={setRegistrationSuccess}
@@ -42,11 +43,17 @@ function UserActivateRegister() {
function UserActivateError({ failedEmails }) {
return (
<div className="row-spaced text-danger">
<p>Sorry, an error occured, failed to register these emails.</p>
{failedEmails.map(email => (
<p key={email}>{email}</p>
))}
<div className="row-spaced">
<Notification
type="error"
content="Sorry, an error occured, failed to register these email:"
className="mb-3"
/>
<ul>
{failedEmails.map(email => (
<li key={email}>{email}</li>
))}
</ul>
</div>
)
}
@@ -25,7 +25,7 @@ describe('RegisterForm', function () {
setFailedEmails={setFailedEmailsStub}
/>
)
await screen.findByLabelText('emails to register')
await screen.findByLabelText('Emails to register new users')
screen.getByRole('button', { name: /register/i })
})
@@ -53,7 +53,7 @@ describe('RegisterForm', function () {
setFailedEmails={setFailedEmailsStub}
/>
)
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
@@ -19,7 +19,7 @@ describe('UserActivateRegister', function () {
}
fetchMock.get('/user/tpds/queues', TPDS_SYNCED)
const registerMock = fetchMock.post('/admin/register', endPointResponse)
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
@@ -41,7 +41,7 @@ describe('UserActivateRegister', function () {
}
fetchMock.get('/user/tpds/queues', TPDS_SYNCED)
const registerMock = fetchMock.post('/admin/register', endPointResponse)
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
@@ -76,7 +76,7 @@ describe('UserActivateRegister', function () {
if (body.email === 'abc@gmail.com') return endPointResponse1
else if (body.email === 'def@gmail.com') return endPointResponse2
})
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
@@ -102,7 +102,7 @@ describe('UserActivateRegister', function () {
if (body.email === 'abc@') return endPointResponse1
else if (body.email === 'def@') return endPointResponse2
})
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
@@ -133,7 +133,7 @@ describe('UserActivateRegister', function () {
else if (body.email === 'def@gmail.com') return endPointResponse2
else return 500
})
const registerInput = screen.getByLabelText('emails to register')
const registerInput = screen.getByLabelText('Emails to register new users')
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
@@ -166,9 +166,9 @@ describe('<EditorLeftMenu />', function () {
cy.findByRole('button', { name: 'Cancel' }).click()
cy.findByRole('button', { name: 'Copy project' }).click()
cy.findByLabelText('New Name').focus()
cy.findByLabelText('New Name').clear()
cy.findByLabelText('New Name').type('Project Renamed')
cy.findByLabelText(/New name/i).focus()
cy.findByLabelText(/New name/i).clear()
cy.findByLabelText(/New name/i).type('Project Renamed')
cy.get('#clone-project-form-name[value="Project Renamed"')
})
@@ -21,7 +21,7 @@ describe('<MessageInput />', function () {
/>
)
screen.getByLabelText('Send a message to your collaborators')
screen.getByLabelText('Send a message to your collaborators')
})
it('sends a message after typing and hitting enter', function () {
@@ -68,7 +68,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
})
expect(submitButton.disabled).to.be.false
const input = await screen.getByLabelText('New Name')
const input = await screen.getByLabelText(/New name/i)
fireEvent.change(input, {
target: { value: '' },
@@ -23,7 +23,7 @@ describe('<ActionsCopyProject />', function () {
fireEvent.click(screen.getByRole('button', { name: 'Copy project' }))
screen.getByPlaceholderText('New project name')
screen.getByLabelText(/New name/i)
})
it('loads the project page when submitted', async function () {
@@ -38,7 +38,7 @@ describe('<ActionsCopyProject />', function () {
fireEvent.click(screen.getByRole('button', { name: 'Copy project' }))
const input = screen.getByPlaceholderText('New project name')
const input = screen.getByLabelText(/New name/i)
fireEvent.change(input, { target: { value: 'New project' } })
const button = screen.getByRole('button', { name: 'Copy' })
@@ -68,8 +68,10 @@ describe('group managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByTestId('managed-entities-table')
@@ -94,8 +96,10 @@ describe('group managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByRole('alert').should('contain.text', 'Error: User already added')
})
@@ -92,7 +92,7 @@ describe('GroupMembers', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByTestId('managed-entities-table')
@@ -119,7 +119,7 @@ describe('GroupMembers', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByRole('alert').contains('Error: User already added')
})
@@ -297,7 +297,7 @@ describe('GroupMembers', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByTestId('managed-entities-table')
@@ -326,7 +326,7 @@ describe('GroupMembers', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByRole('alert').contains('Error: User already added')
})
@@ -68,8 +68,10 @@ describe('institution managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByTestId('managed-entities-table')
@@ -94,8 +96,10 @@ describe('institution managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByRole('alert').should('contain.text', 'Error: User already added')
})
@@ -131,7 +131,7 @@ describe('group members, with managed users', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByTestId('managed-entities-table')
@@ -160,7 +160,7 @@ describe('group members, with managed users', function () {
},
})
cy.findByTestId('add-members-input').type('someone.else@test.com')
cy.findByLabelText('Invite more members').type('someone.else@test.com')
cy.findByRole('button', { name: /invite/i }).click()
cy.findByRole('alert').contains('Error: User already added')
})
@@ -68,8 +68,10 @@ describe('publisher managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByTestId('managed-entities-table')
@@ -94,8 +96,10 @@ describe('publisher managers', function () {
})
cy.findByTestId('add-members-form').within(() => {
cy.findByRole('textbox').type('someone.else@test.com')
cy.findByRole('button').click()
cy.findByLabelText(/Add more manager emails/i).type(
'someone.else@test.com'
)
cy.findByRole('button', { name: /add/i }).click()
})
cy.findByRole('alert').should('contain.text', 'Error: User already added')
})
@@ -515,7 +515,7 @@ describe('change list', function () {
cy.findByRole('heading', { name: /add label/i })
cy.findByRole('button', { name: /cancel/i })
cy.findByRole('button', { name: /add label/i }).should('be.disabled')
cy.findByPlaceholderText(/new label name/i).as('input')
cy.findByLabelText(/New label name/i).as('input')
cy.get('@input').type(newLabel)
cy.findByRole('button', { name: /add label/i }).should('be.enabled')
cy.get('@input').type('{enter}')
@@ -51,13 +51,13 @@ describe('<NewProjectButton />', function () {
it('open new project modal when clicking at Blank project', function () {
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank project' }))
screen.getByPlaceholderText('Project Name')
screen.getByLabelText(/Project name/i)
})
it('open new project modal when clicking at Example project', function () {
fireEvent.click(screen.getByRole('menuitem', { name: 'Example project' }))
screen.getByPlaceholderText('Project Name')
screen.getByLabelText(/Project name/i)
})
it('close the new project modal when clicking at the top right "x" button', function () {
@@ -34,7 +34,7 @@ describe('<ModalContentNewProjectForm />', function () {
expect(createButton.getAttribute('disabled')).to.exist
fireEvent.change(screen.getByPlaceholderText('Project Name'), {
fireEvent.change(screen.getByLabelText('Project name'), {
target: { value: 'Test Name' },
})
@@ -61,7 +61,7 @@ describe('<ModalContentNewProjectForm />', function () {
render(<ModalContentNewProjectForm onCancel={() => {}} />)
fireEvent.change(screen.getByPlaceholderText('Project Name'), {
fireEvent.change(screen.getByLabelText('Project name'), {
target: { value: '/' },
})
@@ -87,7 +87,7 @@ describe('<ModalContentNewProjectForm />', function () {
render(<ModalContentNewProjectForm onCancel={() => {}} />)
fireEvent.change(screen.getByPlaceholderText('Project Name'), {
fireEvent.change(screen.getByLabelText('Project name'), {
target: { value: '\\' },
})
@@ -113,7 +113,7 @@ describe('<ModalContentNewProjectForm />', function () {
render(<ModalContentNewProjectForm onCancel={() => {}} />)
fireEvent.change(screen.getByPlaceholderText('Project Name'), {
fireEvent.change(screen.getByLabelText('Project name'), {
target: {
value: `
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu risus quis varius quam quisque id diam vel quam. Sit amet porttitor eget dolor morbi non arcu risus quis. In aliquam sem fringilla ut. Gravida cum sociis natoque penatibus. Semper risus in hendrerit gravida rutrum quisque non. Ut aliquam purus sit amet luctus venenatis. Neque ornare aenean euismod elementum nisi. Adipiscing bibendum est ultricies integer quis auctor elit. Nulla posuere sollicitudin aliquam ultrices sagittis. Nulla facilisi nullam vehicula ipsum a arcu cursus. Tristique senectus et netus et malesuada fames ac. Pulvinar pellentesque habitant morbi tristique senectus et netus et. Nisi scelerisque eu ultrices vitae auctor eu. Hendrerit gravida rutrum quisque non tellus orci. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non. Donec enim diam vulputate ut pharetra sit amet aliquam id. Ullamcorper eget nulla facilisi etiam.
@@ -894,7 +894,9 @@ describe('<ProjectListRoot />', function () {
expect(confirmButton.disabled).to.be.true
// no name
const input = screen.getByLabelText('New Name') as HTMLButtonElement
const input = screen.getByLabelText(
/New name/i
) as HTMLButtonElement
fireEvent.change(input, {
target: { value: '' },
})
@@ -928,7 +930,7 @@ describe('<ProjectListRoot />', function () {
// a valid name
const newProjectName = 'A new project name'
const input = (await within(modal).findByLabelText(
'New Name'
/New name/i
)) as HTMLButtonElement
const oldName = input.value
fireEvent.change(input, {
@@ -175,7 +175,7 @@ describe('<TagsList />', function () {
it('modal is open', async function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
within(modal).getByRole('heading', { name: 'Edit Tag' })
within(modal).getByRole('heading', { name: 'Edit tag' })
})
it('click on cancel closes the modal', async function () {
@@ -56,7 +56,7 @@ describe('<CopyProjectButton />', function () {
fireEvent.click(btn)
screen.getByText('Copy project')
screen.getByLabelText('New Name')
screen.getByLabelText(/New name/i)
screen.getByDisplayValue(`${copyableProject.name} (Copy)`)
const copyBtn = screen.getAllByRole<HTMLButtonElement>('button', {
name: 'Copy',
@@ -56,7 +56,7 @@ describe('<RenameProjectButton />', function () {
}) as HTMLButtonElement
expect(confirmBtn.disabled).to.be.true
const nameInput = screen.getByDisplayValue(ownedProject.name)
fireEvent.change(nameInput, { target: { value: 'new name' } })
fireEvent.change(nameInput, { target: { value: /New name/i } })
expect(confirmBtn.disabled).to.be.false
fireEvent.click(confirmBtn)
expect(confirmBtn.disabled).to.be.true
@@ -33,7 +33,7 @@ describe('<RenameProjectModal />', function () {
)
screen.getByText('Rename Project')
const input = screen.getByRole('textbox', {
name: 'New Name',
name: /New name/i,
}) as HTMLInputElement
expect(input.value).to.equal(currentProjects[0].name)
@@ -80,7 +80,7 @@ describe('<RenameProjectModal />', function () {
/>
)
screen.getByText('Rename Project')
const input = screen.getByLabelText('New Name') as HTMLButtonElement
const input = screen.getByLabelText(/New name/i) as HTMLButtonElement
expect(input.value).to.equal(currentProjects[0].name)
fireEvent.change(input, {
@@ -603,7 +603,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps({ publicAccessLevel: 'tokenBased' })
)
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
// loading contacts
await waitFor(() => {
@@ -775,7 +775,7 @@ describe('<ShareProjectModal/>', function () {
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
const submitButton: HTMLButtonElement = screen.getByRole('button', {
name: 'Invite',
@@ -883,7 +883,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps()
)
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
// Wait for contacts to load
await waitFor(() => {
@@ -938,7 +938,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps()
)
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
// Wait for contacts to load
await waitFor(() => {
@@ -974,7 +974,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps()
)
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
// Wait for contacts to load
await waitFor(() => {
@@ -1009,7 +1009,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps()
)
const [inputElement] = await screen.findAllByLabelText('Add people')
const [inputElement] = await screen.findAllByLabelText('Add email address')
// Wait for contacts to load
await waitFor(() => {