• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EcrituresNumeriques / stylo / 13673346615

05 Mar 2025 10:02AM UTC coverage: 28.524% (+0.03%) from 28.496%
13673346615

push

github

web-flow
fix(write): évite le re-rendering dû à la biblio (#1313)

Co-authored-by: Thomas Parisot <thom4parisot@users.noreply.github.com>

402 of 602 branches covered (66.78%)

Branch coverage included in aggregate %.

0 of 32 new or added lines in 2 files covered. (0.0%)

56 existing lines in 5 files now uncovered.

3914 of 14529 relevant lines covered (26.94%)

1.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/front/src/components/UserInfos.jsx
1
import React, { useState, useCallback } from 'react'
×
NEW
2
import { Check, Loader } from 'react-feather'
×
3
import { useTranslation } from 'react-i18next'
×
4
import { useSelector, useDispatch, shallowEqual } from 'react-redux'
×
5
import { Helmet } from 'react-helmet'
×
6

7
import { useGraphQLClient } from '../helpers/graphQL'
×
8
import { updateUser } from './Credentials.graphql'
×
9

10
import { useSetAuthToken } from '../hooks/user.js'
×
11

12
import etv from '../helpers/eventTargetValue'
×
13
import styles from './credentials.module.scss'
×
14
import formStyles from './field.module.scss'
×
15
import Button from './Button'
×
16
import Field from './Field'
×
17
import TimeAgo from './TimeAgo.jsx'
×
18

19
export default function UserInfos() {
×
20
  const dispatch = useDispatch()
×
21
  const { t } = useTranslation()
×
22
  const { query } = useGraphQLClient()
×
23
  const activeUser = useSelector((state) => state.activeUser, shallowEqual)
×
24
  const zoteroToken = useSelector((state) => state.activeUser.zoteroToken)
×
25
  const sessionToken = useSelector((state) => state.sessionToken)
×
26
  const [displayName, setDisplayName] = useState(activeUser.displayName)
×
27
  const [firstName, setFirstName] = useState(activeUser.firstName || '')
×
28
  const [lastName, setLastName] = useState(activeUser.lastName || '')
×
29
  const [institution, setInstitution] = useState(activeUser.institution || '')
×
30
  const [isSaving, setIsSaving] = useState(false)
×
31

32
  const updateActiveUserDetails = useCallback(
×
33
    (payload) =>
×
34
      dispatch({
×
35
        type: `UPDATE_ACTIVE_USER_DETAILS`,
×
36
        payload,
×
37
      }),
×
38
    []
×
39
  )
×
40

41
  const { link: linkZoteroAccount, unlink: unlinkZoteroAccount } =
×
42
    useSetAuthToken('zotero')
×
43

44
  const updateInfo = useCallback(
×
45
    async (e) => {
×
46
      e.preventDefault()
×
47
      setIsSaving(true)
×
48
      const variables = {
×
49
        user: activeUser._id,
×
50
        details: { displayName, firstName, lastName, institution },
×
51
      }
×
52
      const { updateUser: userDetails } = await query({
×
53
        query: updateUser,
×
54
        variables,
×
55
      })
×
56
      updateActiveUserDetails(userDetails)
×
57
      setIsSaving(false)
×
58
    },
×
59
    [activeUser._id, displayName, firstName, lastName, institution]
×
60
  )
×
61

62
  return (
×
63
    <>
×
64
      <Helmet>
×
65
        <title>{t('user.account.title')}</title>
×
66
      </Helmet>
×
67
      <section className={styles.section}>
×
68
        <h2>{t('user.account.title')}</h2>
×
69
        <form onSubmit={updateInfo} className={styles.form}>
×
70
          <Field
×
71
            id="displayNameField"
×
72
            label={t('user.account.displayName')}
×
73
            type="text"
×
74
            value={displayName}
×
75
            onChange={(e) => setDisplayName(etv(e))}
×
76
          />
×
77
          <Field
×
78
            id="firstNameField"
×
79
            label={t('user.account.firstName')}
×
80
            type="text"
×
81
            value={firstName}
×
82
            onChange={(e) => setFirstName(etv(e))}
×
83
          />
×
84
          <Field
×
85
            id="lastNameField"
×
86
            label={t('user.account.lastName')}
×
87
            type="text"
×
88
            value={lastName}
×
89
            onChange={(e) => setLastName(etv(e))}
×
90
          />
×
91
          <Field
×
92
            id="institutionField"
×
93
            label={t('user.account.institution')}
×
94
            type="text"
×
95
            value={institution}
×
96
            onChange={(e) => setInstitution(etv(e))}
×
97
          />
×
98
          <Field label="Zotero">
×
99
            <>
×
100
              {zoteroToken && (
×
101
                <div className={styles.zotero}>
×
102
                  <p>
×
103
                    {t('credentials.authentication.linkedService.description', {
×
104
                      service: 'Zotero',
×
105
                      token: zoteroToken,
×
106
                    })}
×
107
                  </p>
×
108
                  <Button
×
109
                    onClick={unlinkZoteroAccount}
×
110
                    type="button"
×
111
                    aria-label={t('credentials.authentication.unlinkLabel', {
×
112
                      service: 'zotero',
×
113
                    })}
×
114
                  >
115
                    {t('credentials.authentication.unlinkButton')}
×
116
                  </Button>
×
117
                </div>
×
118
              )}
119
              {!zoteroToken && (
×
120
                <div className={styles.zotero}>
×
121
                  <p>
×
122
                    {t(
×
123
                      'credentials.authentication.unlinkedService.description'
×
124
                    )}
×
125
                  </p>
×
126

127
                  <Button
×
128
                    onClick={linkZoteroAccount}
×
129
                    type="button"
×
130
                    aria-label={t('credentials.authentication.linkLabel', {
×
131
                      service: 'zotero',
×
132
                    })}
×
133
                  >
134
                    {t('credentials.authentication.linkButton')}
×
135
                  </Button>
×
136
                </div>
×
137
              )}
138
            </>
×
139
          </Field>
×
140
          <div className={formStyles.footer}>
×
141
            <Button primary={true} disabled={isSaving}>
×
142
              {isSaving ? <Loader /> : <Check />}
×
143
              Save changes
144
            </Button>
×
145
          </div>
×
146
        </form>
×
147
      </section>
×
148

149
      <section className={styles.section}>
×
150
        <div className={styles.info}>
×
151
          <Field label={t('user.account.email')}>
×
152
            <>{activeUser.email}</>
×
153
          </Field>
×
154
          {activeUser.username && (
×
155
            <Field label="Username">
×
156
              <>{activeUser.username}</>
×
157
            </Field>
×
158
          )}
159
          <Field label={t('user.account.authentication')}>
×
160
            <>
×
161
              {activeUser.authType === 'oidc'
×
162
                ? 'OpenID (External)'
×
163
                : 'Password'}
×
164
            </>
×
165
          </Field>
×
166
          <Field
×
167
            label={t('user.account.apiKey')}
×
168
            className={styles.apiKeyField}
×
169
          >
NEW
170
            <code
×
NEW
171
              className={styles.apiKeyValue}
×
NEW
172
              title={t('user.account.apiKeyValue', { token: sessionToken })}
×
173
            >
NEW
174
              {sessionToken}
×
NEW
175
            </code>
×
176
          </Field>
×
177
          <Field label={t('user.account.id')}>
×
178
            <code>{activeUser._id}</code>
×
179
          </Field>
×
180
          <Field label={t('user.account.createdAt')}>
×
181
            <TimeAgo date={activeUser.createdAt} />
×
182
          </Field>
×
183
          <Field label={t('user.account.updatedAt')}>
×
184
            <TimeAgo date={activeUser.updatedAt} />
×
185
          </Field>
×
186
        </div>
×
187
      </section>
×
188
    </>
×
189
  )
190
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc