Skip to content

Allow users to add multiple email addresses to their account #11629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ src/schema.rs.orig

# insta
*.pending-snap
*.snap.new

# playwright
/test-results/
Expand Down
147 changes: 55 additions & 92 deletions app/components/email-input.hbs
Original file line number Diff line number Diff line change
@@ -1,101 +1,64 @@
<div ...attributes>
{{#unless @user.email}}
<div local-class="friendly-message" data-test-no-email>
<p>
Please add your email address. We will only use
it to contact you about your account. We promise we'll never share it!
</p>
{{#if this.email.id }}
<div local-class="row">
<div local-class="email-column">
<dd>
<span data-test-email-address>{{ this.email.email }}</span>
<span class="badges">
{{#if this.email.verified}}
<span local-class="badge verified" data-test-verified>Verified</span>
{{#if this.email.primary }}
<span local-class="badge" data-test-primary>Primary email</span>
{{/if}}
{{else}}
{{#if this.email.verification_email_sent}}
<span local-class="badge pending-verification" data-test-verification-sent>Unverified - email sent</span>
{{else}}
<span local-class="badge unverified" data-test-verification-not-sent>Unverified</span>
{{/if}}
{{/if}}
</span>
</dd>
</div>
{{/unless}}

{{#if this.isEditing }}
<div local-class="row">
<div local-class="label">
<label for="email-input">Email</label>
</div>
<form local-class="email-form" {{on "submit" (prevent-default (perform this.saveEmailTask))}}>
<Input
@type="email"
@value={{this.value}}
id="email-input"
placeholder="Email"
local-class="input"
data-test-input
/>

<div local-class="actions">
<button
type='submit'
local-class="save-button"
class="button button--small"
disabled={{not this.value}}
data-test-save-button
>
Save
</button>

<button
type="button"
class="button button--small"
data-test-cancel-button
{{on "click" (fn (mut this.isEditing) false)}}
>
Cancel
</button>
</div>
</form>
<div local-class="actions">
{{#unless this.email.verified}}
<button type="button" class="button button--small" disabled={{this.disableResend}} data-test-resend-button
{{on "click" (perform this.resendEmailTask)}}>
{{#if this.disableResend}}
Sent!
{{else if this.email.verification_email_sent}}
Resend
{{else}}
Verify
{{/if}}
</button>
{{/unless}}
{{#if (and (not this.email.primary) this.email.verified)}}
<button type="button" class="button button--small" data-test-primary-button {{on "click" (prevent-default (perform this.markAsPrimaryTask))}}>
Mark as primary
</button>
{{/if}}
{{#if @canDelete}}
<button disabled={{this.email.primary}} title={{if this.email.primary "Cannot delete primary email"}} type="button" class="button button--small button--red" data-test-remove-button {{on "click" (prevent-default (perform this.deleteEmailTask))}}>
Remove
</button>
{{/if}}
</div>
</div>
{{else}}
<div local-class="row">
<div local-class="label">
<dt>Email</dt>
</div>
<div local-class="email-column" data-test-email-address>
<dd>
{{ @user.email }}
{{#if @user.email_verified}}
<span local-class="verified" data-test-verified>Verified!</span>
{{/if}}
</dd>
</div>
<div local-class="row">
<form local-class="email-form" {{on "submit" (prevent-default (perform this.saveEmailTask))}}>
<Input @type="email" @value={{this.value}} id="email-input" placeholder="Email" local-class="input"
data-test-input {{auto-focus}} oninput={{this.validate}} />

<div local-class="actions">
<button
type="button"
class="button button--small"
data-test-edit-button
{{on "click" this.editEmail}}
>
Edit
<button type='submit' local-class="save-button" class="button button--small" disabled={{not this.isValid}}
data-test-save-button>
Verify
</button>
</div>
</div>
{{#if (and @user.email (not @user.email_verified))}}
<div local-class="row">
<div local-class="label">
{{#if @user.email_verification_sent}}
<p data-test-verification-sent>We have sent a verification email to your address.</p>
{{/if}}
<p data-test-not-verified>Your email has not yet been verified.</p>
</div>
<div local-class="actions">
<button
type="button"
class="button button--small"
disabled={{this.disableResend}}
data-test-resend-button
{{on "click" (perform this.resendEmailTask)}}
>
{{#if this.disableResend}}
Sent!
{{else if @user.email_verification_sent}}
Resend
{{else}}
Send verification email
{{/if}}
</button>
</div>
</div>
{{/if}}
</form>
</div>
{{/if}}

</div>
</div>
60 changes: 41 additions & 19 deletions app/components/email-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import { task } from 'ember-concurrency';
export default class EmailInput extends Component {
@service notifications;

@tracked email = this.args.email || { email: '', id: null };
@tracked isValid = false;
@tracked value;
@tracked isEditing = false;
@tracked disableResend = false;

@action validate(event) {
this.isValid = event.target.value.trim().length !== 0 && event.target.checkValidity();
}

resendEmailTask = task(async () => {
try {
await this.args.user.resendVerificationEmail();
await this.args.user.resendVerificationEmail(this.email.id);
this.disableResend = true;
} catch (error) {
let detail = error.errors?.[0]?.detail;
Expand All @@ -26,30 +31,47 @@ export default class EmailInput extends Component {
}
});

@action
editEmail() {
this.value = this.args.user.email;
this.isEditing = true;
}
deleteEmailTask = task(async () => {
try {
await this.args.user.deleteEmail(this.email.id);
} catch (error) {
console.error('Error deleting email:', error);
let detail = error.errors?.[0]?.detail;
if (detail && !detail.startsWith('{')) {
this.notifications.error(`Error in deleting email: ${detail}`);
} else {
this.notifications.error('Unknown error in deleting email');
}
}
});

saveEmailTask = task(async () => {
let userEmail = this.value;
let user = this.args.user;

try {
await user.changeEmail(userEmail);

this.isEditing = false;
this.disableResend = false;
this.email = await this.args.user.addEmail(this.value);
this.disableResend = true;
await this.args.onAddEmail?.();
} catch (error) {
let detail = error.errors?.[0]?.detail;

let msg =
detail && !detail.startsWith('{')
? `An error occurred while saving this email, ${detail}`
: 'An unknown error occurred while saving this email.';
if (detail && !detail.startsWith('{')) {
this.notifications.error(`Error in saving email: ${detail}`);
} else {
console.error('Error saving email:', error);
this.notifications.error('Unknown error in saving email');
}
}
});

this.notifications.error(`Error in saving email: ${msg}`);
markAsPrimaryTask = task(async () => {
try {
await this.args.user.updatePrimaryEmail(this.email.id);
} catch (error) {
let detail = error.errors?.[0]?.detail;
if (detail && !detail.startsWith('{')) {
this.notifications.error(`Error in marking email as primary: ${detail}`);
} else {
this.notifications.error('Unknown error in marking email as primary');
}
}
});
}
49 changes: 42 additions & 7 deletions app/components/email-input.module.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
.friendly-message {
margin-top: 0;
}

.row {
width: 100%;
border: 1px solid var(--gray-border);
border-bottom-width: 0;
padding: var(--space-2xs) var(--space-s);
display: flex;
align-items: center;
justify-content: space-between;

&:last-child {
border-bottom-width: 1px;
Expand All @@ -22,12 +19,41 @@
}

.email-column {
padding: var(--space-xs) 0;
}

.email-column dd {
margin: 0;
display: flex;
flex-wrap: wrap;
gap: var(--space-3xs);
flex: 20;
}

.email-column .badges {
display: flex;
flex-wrap: wrap;
gap: var(--space-3xs);
}

.badge {
padding: var(--space-4xs) var(--space-2xs);
background-color: var(--main-bg-dark);
font-size: 0.8rem;
border-radius: 100px;
}

.verified {
color: green;
font-weight: bold;
background-color: var(--green800);
color: var(--grey200);
}

.pending-verification {
background-color: light-dark(var(--orange-200), var(--orange-500));
}

.unverified {
background-color: light-dark(var(--orange-300), var(--orange-600));
}

.email-form {
Expand All @@ -38,13 +64,22 @@
}

.input {
width: 400px;
background-color: var(--main-bg);
border: 0;
flex: 1;
margin: calc(var(--space-3xs) * -1) calc(var(--space-2xs) * -1);
padding: var(--space-3xs) var(--space-2xs);
margin-right: var(--space-xs);
}

.input:focus {
outline: none;
}

.actions {
display: flex;
align-items: center;
gap: var(--space-3xs);
}

.save-button {
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/settings/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { task } from 'ember-concurrency';
export default class extends Controller {
@service notifications;

@tracked isAddingEmail = false;

@tracked publishNotifications;

@action handleNotificationsChange(event) {
Expand Down
Loading
Loading