Skip to content

Commit

Permalink
feat(organizations): disable email updates for regular organization m…
Browse files Browse the repository at this point in the history
…embers TASK-997 (#5233)

### 🗒️ Checklist

1. [x] run linter locally
2. [x] update all related docs (API, README, inline, etc.), if any
3. [x] draft PR with a title `<type>(<scope>)<!>: <title> TASK-1234`
4. [x] tag PR: at least `frontend` or `backend` unless it's global
5. [x] fill in the template below and delete template comments
6. [x] review thyself: read the diff and repro the preview as written
7. [ ] open PR & confirm that CI passes
8. [x] request reviewers, if needed
9. [ ] delete this section before merging

### 📣 Summary
<!-- Delete this section if changes are internal only. -->
<!-- One sentence summary for the public changelog, worded for
non-technical seasoned Kobo users. -->

UI for changing emails is disabled for `Members` of a multi-member
organization.


### 👀 Preview steps
1. Make sure your user's email is correct in both the user section of
the django admin as well as the email address section. If there is a
difference it would only show the former here. See
https://www.notion.so/kobotoolbox/task-1236
2. Make an MMO that has an owner, admin, and a member, as well as a
regular user
3. Navigate to the account settings page
4. Visit the security section
5. If the user is a regular user, or an MMO owner or admin, they should
be able to change their email address as normal
6. If the user is a member of an MMO the text box and button should be
gone

### 📖 Description
<!-- Delete this section if summary already said everything. -->
<!-- Full description for the public changelog, worded for non-technical
seasoned Kobo users. -->

Remove the text box and button entirely if the user is a `member`.
Replaced with just text of the email returned from `/me`.

### 💭 Notes
<!-- Delete this section if empty. -->
<!-- Anything else useful that's not said above,worded for
reviewers, testers, and future git archaeologist collegues. Examples:
- screenshots, copy-pasted logs, etc.
- what was tried but didn't work,
- conscious short-term vs long-term tradeoffs,
- proactively answer likely questions,
-->

Make sure the user either has the proper email reflected in the user
section of the django admin or was created through an email confirmation
link. See https://www.notion.so/kobotoolbox/task-1236

---------

Co-authored-by: James Kiger <[email protected]>
  • Loading branch information
duvld and jamesrkiger authored Nov 21, 2024
1 parent 9bea2b2 commit 6729e75
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 74 deletions.
159 changes: 85 additions & 74 deletions jsapp/js/account/security/email/emailSection.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
deleteUnverifiedUserEmails,
} from './emailSection.api';
import type {EmailResponse} from './emailSection.api';
import {useOrganizationQuery} from '../../organization/organizationQuery';

// Partial components
import Button from 'jsapp/js/components/common/button';
Expand All @@ -33,6 +34,8 @@ interface EmailState {
export default function EmailSection() {
const [session] = useState(() => sessionStore);

const orgQuery = useOrganizationQuery();

let initialEmail = '';
if ('email' in session.currentAccount) {
initialEmail = session.currentAccount.email;
Expand Down Expand Up @@ -116,90 +119,98 @@ export default function EmailSection() {
const unverifiedEmail = email.emails.find(
(userEmail) => !userEmail.verified && !userEmail.primary
);
const isReady = session.isInitialLoadComplete && 'email' in currentAccount;
const userCanChangeEmail = orgQuery.data?.is_mmo
? orgQuery.data.request_user_role !== 'member'
: true;

return (
<section className={securityStyles.securitySection}>
<div className={securityStyles.securitySectionTitle}>
<h2 className={securityStyles.securitySectionTitleText}>{t('Email address')}</h2>
</div>

<div className={cx(securityStyles.securitySectionBody, styles.body)}>
{!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>
)}

{unverifiedEmail?.email &&
!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<>
<div className={styles.unverifiedEmail}>
<Icon name='alert' />
<p className={styles.blurb}>
<strong>
{t('Check your email ##UNVERIFIED_EMAIL##. ').replace(
'##UNVERIFIED_EMAIL##',
unverifiedEmail.email
)}
</strong>

{t(
'A verification link has been sent to confirm your ownership. Once confirmed, this address will replace ##UNVERIFIED_EMAIL##'
).replace('##UNVERIFIED_EMAIL##', currentAccount.email)}
</p>
</div>

<div className={styles.unverifiedEmailButtons}>
<Button
label='Resend'
size='m'
type='secondary'
onClick={resendNewUserEmail.bind(
resendNewUserEmail,
<div
className={cx([
securityStyles.securitySectionBody,
userCanChangeEmail ? styles.body : styles.emailUpdateDisabled,
])}
>
{isReady && userCanChangeEmail ? (
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>
) : (
<div className={styles.emailText}>{email.newEmail}</div>
)}

{unverifiedEmail?.email && isReady && (
<>
<div className={styles.unverifiedEmail}>
<Icon name='alert' />
<p className={styles.blurb}>
<strong>
{t('Check your email ##UNVERIFIED_EMAIL##. ').replace(
'##UNVERIFIED_EMAIL##',
unverifiedEmail.email
)}
/>
<Button
label='Remove'
size='m'
type='secondary-danger'
onClick={deleteNewUserEmail}
/>
</div>

{email.refreshedEmail && (
<label>
{t('Email was sent again: ##TIMESTAMP##').replace(
'##TIMESTAMP##',
email.refreshedEmailDate
)}
</label>
)}
</>
)}
</strong>

{t(
'A verification link has been sent to confirm your ownership. Once confirmed, this address will replace ##UNVERIFIED_EMAIL##'
).replace('##UNVERIFIED_EMAIL##', currentAccount.email)}
</p>
</div>

<div className={styles.unverifiedEmailButtons}>
<Button
label='Resend'
size='m'
type='secondary'
onClick={resendNewUserEmail.bind(
resendNewUserEmail,
unverifiedEmail.email
)}
/>
<Button
label='Remove'
size='m'
type='secondary-danger'
onClick={deleteNewUserEmail}
/>
</div>

{email.refreshedEmail && (
<label>
{t('Email was sent again: ##TIMESTAMP##').replace(
'##TIMESTAMP##',
email.refreshedEmailDate
)}
</label>
)}
</>
)}
</div>

<form
className={styles.options}
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<Button
label='Change'
size='m'
type='primary'
onClick={handleSubmit}
/>
</form>
{userCanChangeEmail && (
<div className={styles.options}>
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<Button
label='Change'
size='m'
type='primary'
onClick={handleSubmit}
/>
</form>
</div>
)}
</section>
);
}
10 changes: 10 additions & 0 deletions jsapp/js/account/security/email/emailSection.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@
display: flex;
gap: 10px;
}

.emailText {
font-weight: 600;
}

.emailUpdateDisabled {
flex: 5;
// To compensate for the `options` class not displaying when there is no email
margin-right: calc(30% + 8px);
}

0 comments on commit 6729e75

Please sign in to comment.