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

EcrituresNumeriques / stylo / 12925854411

23 Jan 2025 09:11AM UTC coverage: 25.831% (-4.7%) from 30.523%
12925854411

push

github

web-flow
Merge pull request #1192 from EcrituresNumeriques/feat/vite6

322 of 518 branches covered (62.16%)

Branch coverage included in aggregate %.

3448 of 14077 relevant lines covered (24.49%)

1.66 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'
×
2
import { Check, Clipboard, Loader } from 'react-feather'
×
3
import { useTranslation } from 'react-i18next'
×
4
import { useSelector, useDispatch, shallowEqual } from 'react-redux'
×
5
import { CopyToClipboard } from 'react-copy-to-clipboard'
×
6

7
import { useGraphQL } from '../helpers/graphQL'
×
8
import { updateUser } from './Credentials.graphql'
×
9
import etv from '../helpers/eventTargetValue'
×
10
import styles from './credentials.module.scss'
×
11
import formStyles from './field.module.scss'
×
12
import Button from './Button'
×
13
import Field from './Field'
×
14
import TimeAgo from './TimeAgo.jsx'
×
15

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

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

42
  const unlinkZoteroAccount = useCallback(async (event) => {
×
43
    event.preventDefault()
×
44

45
    const variables = { user: activeUser._id, details: { zoteroToken: null } }
×
46
    await runQuery({ query: updateUser, variables })
×
47
    clearZoteroToken()
×
48
    setIsSaving(false)
×
49
  }, [])
×
50

51
  const updateInfo = useCallback(
×
52
    async (e) => {
×
53
      e.preventDefault()
×
54
      setIsSaving(true)
×
55
      const variables = {
×
56
        user: activeUser._id,
×
57
        details: { displayName, firstName, lastName, institution },
×
58
      }
×
59
      const { updateUser: userDetails } = await runQuery({
×
60
        query: updateUser,
×
61
        variables,
×
62
      })
×
63
      updateActiveUserDetails(userDetails)
×
64
      setIsSaving(false)
×
65
    },
×
66
    [activeUser._id, displayName, firstName, lastName, institution]
×
67
  )
×
68

69
  return (
×
70
    <>
×
71
      <section className={styles.section}>
×
72
        <h2>{t('user.account.title')}</h2>
×
73
        <form onSubmit={updateInfo} className={styles.form}>
×
74
          <Field
×
75
            id="displayNameField"
×
76
            label={t('user.account.displayName')}
×
77
            type="text"
×
78
            value={displayName}
×
79
            onChange={(e) => setDisplayName(etv(e))}
×
80
          />
×
81
          <Field
×
82
            id="firstNameField"
×
83
            label={t('user.account.firstName')}
×
84
            type="text"
×
85
            value={firstName}
×
86
            onChange={(e) => setFirstName(etv(e))}
×
87
          />
×
88
          <Field
×
89
            id="lastNameField"
×
90
            label={t('user.account.lastName')}
×
91
            type="text"
×
92
            value={lastName}
×
93
            onChange={(e) => setLastName(etv(e))}
×
94
          />
×
95
          <Field
×
96
            id="institutionField"
×
97
            label={t('user.account.institution')}
×
98
            type="text"
×
99
            value={institution}
×
100
            onChange={(e) => setInstitution(etv(e))}
×
101
          />
×
102
          <Field label="Zotero">
×
103
            <>
×
104
              {zoteroToken && (
×
105
                <div className={styles.zotero}>
×
106
                  <div>
×
107
                    Linked with <code>{zoteroToken}</code> account.
×
108
                  </div>
×
109
                  <Button
×
110
                    title="Unlink this Zotero account"
×
111
                    onClick={unlinkZoteroAccount}
×
112
                  >
×
113
                    Unlink
114
                  </Button>
×
115
                </div>
×
116
              )}
117
              {!zoteroToken && <span>No linked account.</span>}
×
118
            </>
×
119
          </Field>
×
120
          <div className={formStyles.footer}>
×
121
            <Button primary={true} disabled={isSaving}>
×
122
              {isSaving ? <Loader /> : <Check />}
×
123
              Save changes
124
            </Button>
×
125
          </div>
×
126
        </form>
×
127
      </section>
×
128

129
      <section className={styles.section}>
×
130
        <div className={styles.info}>
×
131
          <Field label={t('user.account.email')}>
×
132
            <>{activeUser.email}</>
×
133
          </Field>
×
134
          {activeUser.username && (
×
135
            <Field label="Username">
×
136
              <>{activeUser.username}</>
×
137
            </Field>
×
138
          )}
139
          <Field label={t('user.account.authentication')}>
×
140
            <>
×
141
              {activeUser.authType === 'oidc'
×
142
                ? 'OpenID (External)'
×
143
                : 'Password'}
×
144
            </>
×
145
          </Field>
×
146
          <Field
×
147
            label={t('user.account.apiKey')}
×
148
            className={styles.apiKeyField}
×
149
          >
150
            <>
×
151
              <code
×
152
                className={styles.apiKeyValue}
×
153
                title={t('user.account.apiKeyValue', { token: sessionToken })}
×
154
              >
155
                {sessionToken}
×
156
              </code>
×
157
              <CopyToClipboard text={sessionToken}>
×
158
                <Button title={t('user.account.copyApiKey')} icon={true}>
×
159
                  <Clipboard />
×
160
                </Button>
×
161
              </CopyToClipboard>
×
162
            </>
×
163
          </Field>
×
164
          <Field label={t('user.account.id')}>
×
165
            <code>{activeUser._id}</code>
×
166
          </Field>
×
167
          {activeUser.admin && <Field label="Admin">✔️</Field>}
×
168
          <Field label={t('user.account.createdAt')}>
×
169
            <TimeAgo date={activeUser.createdAt} />
×
170
          </Field>
×
171
          <Field label={t('user.account.updatedAt')}>
×
172
            <TimeAgo date={activeUser.updatedAt} />
×
173
          </Field>
×
174
        </div>
×
175
      </section>
×
176
    </>
×
177
  )
178
}
×
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