Skip to content
Closed
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
22 changes: 13 additions & 9 deletions jsapp/js/account/security/email/emailSection.api.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import type {PaginatedResponse} from 'js/dataInterface';
import {fetchGet, fetchPost, fetchDelete} from 'jsapp/js/api';
import { fetchDelete, fetchGet, fetchPost } from '#/api'
import type { PaginatedResponse } from '#/dataInterface'

export interface EmailResponse {
primary: boolean;
email: string;
verified: boolean;
primary: boolean
email: string
verified: boolean
}

const LIST_URL = '/me/emails/';
export interface EmailError {
email: string[]
}

const LIST_URL = '/me/emails/'

export async function getUserEmails() {
return fetchGet<PaginatedResponse<EmailResponse>>(LIST_URL);
return fetchGet<PaginatedResponse<EmailResponse>>(LIST_URL)
}

export async function setUserEmail(newEmail: string) {
return fetchPost<EmailResponse>(LIST_URL, {email: newEmail});
return fetchPost<EmailResponse | EmailError>(LIST_URL, { email: newEmail })
}

/** Removes all unverified/non-primary emails (there should only be one anyway)*/
export async function deleteUnverifiedUserEmails() {
return fetchDelete(LIST_URL);
return fetchDelete(LIST_URL)
}
199 changes: 100 additions & 99 deletions jsapp/js/account/security/email/emailSection.component.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,63 @@
import React, {useEffect, useState} from 'react';
import sessionStore from 'js/stores/session';
import {deleteUnverifiedUserEmails, EmailResponse} from './emailSection.api';
import {getUserEmails, setUserEmail} from './emailSection.api';
import style from './emailSection.module.scss';
import Button from 'jsapp/js/components/common/button';
import TextBox from 'jsapp/js/components/common/textBox';
import Icon from 'jsapp/js/components/common/icon';
import {formatTime} from 'jsapp/js/utils';
import React, { useEffect, useState } from 'react'
import Button from '#/components/common/button'
import Icon from '#/components/common/icon'
import TextBox from '#/components/common/textBox'
import sessionStore from '#/stores/session'
import { formatTime } from '#/utils'
import { type EmailResponse, deleteUnverifiedUserEmails } from './emailSection.api'
import { getUserEmails, setUserEmail } from './emailSection.api'
import style from './emailSection.module.scss'

interface EmailState {
emails: EmailResponse[];
newEmail: string;
refreshedEmail: boolean;
refreshedEmailDate: string;
emails: EmailResponse[]
newEmail: string
refreshedEmail: boolean
refreshedEmailDate: string
fieldErrors: string[]
}

export default function EmailSection() {
const [session] = useState(() => sessionStore);
const [session] = useState(() => sessionStore)

const [email, setEmail] = useState<EmailState>({
emails: [],
newEmail: '',
refreshedEmail: false,
refreshedEmailDate: '',
});
fieldErrors: [],
})

useEffect(() => {
getUserEmails().then((data) => {
setEmail({
...email,
emails: data.results,
});
});
}, []);
})
})
}, [])

function setNewUserEmail(newEmail: string) {
setUserEmail(newEmail).then(() => {
getUserEmails().then((data) => {
setEmail({
...email,
fieldErrors: [],
})

setUserEmail(newEmail).then((response) => {
if ('primary' in response) {
getUserEmails().then((data) => {
setEmail({
...email,
emails: data.results,
newEmail: '',
})
})
} else {
setEmail({
...email,
emails: data.results,
newEmail: '',
});
});
});
fieldErrors: response.email,
})
}
})
}

function deleteNewUserEmail() {
Expand All @@ -54,37 +68,37 @@ export default function EmailSection() {
emails: data.results,
newEmail: '',
refreshedEmail: false,
});
});
});
})
})
})
}

function resendNewUserEmail(unverfiedEmail: string) {
setEmail({
...email,
refreshedEmail: false,
});
})

deleteUnverifiedUserEmails().then(() => {
setUserEmail(unverfiedEmail).then(() => {
setEmail({
...email,
refreshedEmail: true,
refreshedEmailDate: formatTime(new Date().toUTCString()),
});
});
});
})
})
})
}

function onTextFieldChange(value: string) {
setEmail({
...email,
newEmail: value,
});
})
}

const currentAccount = session.currentAccount;
const unverifiedEmail = email.emails.find((userEmail) => !userEmail.verified && !userEmail.primary);
const currentAccount = session.currentAccount
const unverifiedEmail = email.emails.find((userEmail) => !userEmail.verified && !userEmail.primary)

return (
<div className={style.root}>
Expand All @@ -93,67 +107,50 @@ export default function EmailSection() {
</div>

<div className={style.bodySection}>
{!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<p className={style.currentEmail}>{currentAccount.email}</p>
)}

{unverifiedEmail?.email &&
!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<>
<div className={style.unverifiedEmail}>
<Icon name='alert' />
<p className={style['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={style.editEmail}>
<Button
label='Resend'
size='m'
color='blue'
type='frame'
onClick={resendNewUserEmail.bind(
resendNewUserEmail,
unverifiedEmail.email
)}
/>
<Button
label='Remove'
size='m'
color='red'
type='frame'
onClick={deleteNewUserEmail}
/>
</div>

{email.refreshedEmail &&
<label>
{t('Email was sent again: ##TIMESTAMP##').replace('##TIMESTAMP##', email.refreshedEmailDate)}
</label>
}
</>
)}
{!session.isPending && session.isInitialLoadComplete && 'email' in currentAccount && (
<p className={style.currentEmail}>{currentAccount.email}</p>
)}

{unverifiedEmail?.email && !session.isPending && session.isInitialLoadComplete && 'email' in currentAccount && (
<>
<div className={style.unverifiedEmail}>
<Icon name='alert' />
<p className={style['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={style.editEmail}>
<Button
label='Resend'
size='m'
color='blue'
type='frame'
onClick={resendNewUserEmail.bind(resendNewUserEmail, unverifiedEmail.email)}
/>
<Button label='Remove' size='m' color='red' type='frame' onClick={deleteNewUserEmail} />
</div>

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

<form
className={style.optionsSection}
onSubmit={(e) => {
e.preventDefault();
setNewUserEmail(email.newEmail);
e.preventDefault()
setNewUserEmail(email.newEmail)
}}
>
{/*TODO: Move TextBox into a modal--it messes up the flow of the row right now*/}
Expand All @@ -164,14 +161,18 @@ export default function EmailSection() {
onChange={onTextFieldChange.bind(onTextFieldChange)}
/>

<Button
label='Change'
size='m'
color='blue'
type='frame'
onClick={setNewUserEmail.bind(setNewUserEmail, email.newEmail)}
/>
<div className={style.optionsSectionButtons}>
<label>{email.fieldErrors}</label>

<Button
label='Change'
size='m'
color='blue'
type='frame'
onClick={setNewUserEmail.bind(setNewUserEmail, email.newEmail)}
/>
</div>
</form>
</div>
);
)
}
11 changes: 11 additions & 0 deletions jsapp/js/account/security/email/emailSection.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@
}
}

.optionsSectionButtons {
> * {
display: inline-block;
}

label {
margin-right: sizes.$x12;
color: colors.$kobo-red;
}
}

.currentEmail {
font-weight: bold;
margin-top: 0;
Expand Down