Skip to content

Commit

Permalink
Merge pull request #1000 from internxt/staging
Browse files Browse the repository at this point in the history
[PB-115] release/Ability to change my email address for my account
  • Loading branch information
CandelR authored Jan 17, 2024
2 parents 5c0e2e1 + 2b11131 commit 533a128
Show file tree
Hide file tree
Showing 17 changed files with 145 additions and 76 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@iconscout/react-unicons": "^1.1.6",
"@internxt/inxt-js": "=1.2.21",
"@internxt/lib": "^1.2.0",
"@internxt/sdk": "^1.4.62",
"@internxt/sdk": "^1.4.63",
"@phosphor-icons/react": "^2.0.10",
"@popperjs/core": "^2.11.6",
"@reduxjs/toolkit": "^1.6.0",
Expand Down Expand Up @@ -173,4 +173,4 @@
"prettier --write"
]
}
}
}
20 changes: 20 additions & 0 deletions src/app/auth/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';
import { SdkFactory } from '../../core/factory/sdk';
import {
CheckChangeEmailExpirationResponse,
FriendInvite,
InitializeUserResponse,
PreCreateUserResponse,
UpdateProfilePayload,
UserPublicKeyResponse,
VerifyEmailChangeResponse,
} from '@internxt/sdk/dist/drive/users/types';

export async function initializeUser(email: string, mnemonic: string): Promise<InitializeUserResponse> {
Expand Down Expand Up @@ -66,6 +68,21 @@ const getPublicKeyByEmail = (email: string): Promise<UserPublicKeyResponse> => {
return usersClient.getPublicKeyByEmail({ email });
};

const changeEmail = (newEmail: string): Promise<void> => {
const authClient = SdkFactory.getNewApiInstance().createNewUsersClient();
return authClient.changeUserEmail(newEmail);
};

const verifyEmailChange = (verifyToken: string): Promise<VerifyEmailChangeResponse> => {
const authClient = SdkFactory.getNewApiInstance().createNewUsersClient();
return authClient.verifyEmailChange(verifyToken);
};

const checkChangeEmailLinkExpiration = (verifyToken: string): Promise<CheckChangeEmailExpirationResponse> => {
const authClient = SdkFactory.getNewApiInstance().createNewUsersClient();
return authClient.checkChangeEmailExpiration(verifyToken);
};

const userService = {
initializeUser,
refreshUser,
Expand All @@ -77,6 +94,9 @@ const userService = {
deleteUserAvatar,
sendVerificationEmail,
getPublicKeyByEmail,
changeEmail,
verifyEmailChange,
checkChangeEmailLinkExpiration,
preCreateUser,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const SidenavItem = ({
<Icon
weight={isActive ? 'fill' : undefined}
size={24}
className={isActive ? 'text-primary' : 'text-gray-80'}
data-cy={iconDataCy}
className={isActive ? 'text-primary' : 'text-gray-80'}
/>
<span className={`ml-2 ${isActive ? 'text-primary dark:text-white' : 'text-gray-80 hover:text-gray-80'}`}>
{label}
Expand Down
1 change: 1 addition & 0 deletions src/app/core/factory/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class SdkFactory {
const apiSecurity = this.getApiSecurity();
return Users.client(apiUrl, appDetails, apiSecurity);
}

public createNewUsersClient(): Users {
const apiUrl = this.getApiUrl();
const appDetails = SdkFactory.getAppDetails();
Expand Down
91 changes: 54 additions & 37 deletions src/app/core/views/ChangeEmailView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,50 @@ import Input from 'app/shared/components/Input';
import Button from 'app/shared/components/Button/Button';
import { areCredentialsCorrect } from 'app/auth/services/auth.service';
import Spinner from 'app/shared/components/Spinner/Spinner';
import localStorageService from '../../services/local-storage.service';
import userService from '../../../auth/services/user.service';
import errorService from '../../services/error.service';
import { userThunks } from '../../../store/slices/user';
import { useDispatch } from 'react-redux';

type StatusType = 'loading' | 'auth' | 'error' | 'success' | 'expired';

const STATUS = {
LOADING: 'loading',
AUTH: 'auth',
ERROR: 'error',
SUCCESS: 'success',
EXPIRED: 'expired',
} as const;

export default function ChangeEmailView(): JSX.Element {
const { translate } = useTranslationContext();
const dispatch = useDispatch();
const { params } = useRouteMatch<{ token: string }>();
const { token } = params;
const urlParams = new URLSearchParams(window.location.search);
const newEmailParam = urlParams.get('n');

const [status, setStatus] = useState<'auth' | 'loading' | 'error' | 'success' | 'expired'>('loading');
const [status, setStatus] = useState<StatusType>(STATUS.LOADING);
const [email, setEmail] = useState<string>('');
const [newEmail, setNewEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [expired, setExpired] = useState<boolean | null>(null);
const [auth, setAuth] = useState<boolean>(false);

async function getInfo() {
// TODO -> Get user "email", "newEmail" and "expired" from the token
const isExpired = false; //! Check if link has expired
const isExpired = (await userService.checkChangeEmailLinkExpiration(token)).isExpired;

if (isExpired) {
setStatus('expired');
setStatus(STATUS.EXPIRED);
setExpired(true);
} else {
setStatus('auth');
setStatus(STATUS.AUTH);
setExpired(false);

// TODO -> Set info from "email" and "newEmail"
setEmail('[email protected]'); //! Change with current email
setNewEmail('[email protected]'); //! Change with new email
const user = localStorageService.getUser();
if (user) setEmail(user.email);
if (newEmailParam) setNewEmail(newEmailParam);
}
}

Expand All @@ -42,32 +59,30 @@ export default function ChangeEmailView(): JSX.Element {

async function verify(e) {
e.preventDefault();
setStatus('loading');
setStatus(STATUS.LOADING);

try {
const correctPassword = await areCredentialsCorrect(email, password);
if (correctPassword) {
const isCorrectPassword = await areCredentialsCorrect(email, password);
if (isCorrectPassword) {
setAuth(true);

try {
// TODO -> Run changeEmail thunk (if chaged successfully return true, if not return false)
const emailChanged = true; //! Change for changeEmail thunk

if (emailChanged) {
setStatus('success');
setAuth(true);
} else {
setStatus('error');
setAuth(true);
}
} catch (err) {
console.error(err);
setStatus('error');
const { newAuthentication } = await userService.verifyEmailChange(token);
const { user, token: oldToken, newToken } = newAuthentication;
dispatch(userThunks.updateUserEmailCredentialsThunk({ newUserData: user, token: oldToken, newToken }));

setStatus(STATUS.SUCCESS);
} catch (error) {
errorService.reportError(error);
setStatus(STATUS.ERROR);
}
} else {
setStatus('error');
setStatus(STATUS.ERROR);
setAuth(false);
}
} catch (err) {
console.error(err);
setStatus('error');
} catch (error) {
errorService.reportError(error);
setStatus(STATUS.ERROR);
}
}

Expand Down Expand Up @@ -123,27 +138,29 @@ export default function ChangeEmailView(): JSX.Element {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="flex w-full max-w-xs flex-col items-center space-y-5">
{status === 'loading' && expired === null ? (
{status === STATUS.LOADING && expired === null ? (
<Spinner size={24} />
) : !expired && !auth ? (
<>
<State {...layout['auth']} />
<State {...layout[STATUS.AUTH]} />

<form className="flex w-full flex-col space-y-3" onSubmit={verify}>
<Input
required
disabled={status === 'loading'}
disabled={status === STATUS.LOADING}
variant="password"
label={translate('views.emailChange.password')}
onChange={setPassword}
autofocus
accent={status === 'error' ? 'error' : undefined}
message={status === 'error' ? (translate('views.emailChange.auth.wrongPassword') as string) : undefined}
accent={status === STATUS.ERROR ? 'error' : undefined}
message={
status === STATUS.ERROR ? (translate('views.emailChange.auth.wrongPassword') as string) : undefined
}
name="password"
/>

<Button loading={status === 'loading'} type="submit">
{translate('views.account.tabs.account.accountDetails.changeEmail.sendingVerification')}
<Button loading={status === STATUS.LOADING} type="submit">
{translate('views.account.tabs.account.accountDetails.changeEmail.confirm')}
</Button>
</form>
</>
Expand All @@ -153,9 +170,9 @@ export default function ChangeEmailView(): JSX.Element {

<Link
className="flex h-10 items-center justify-center rounded-lg bg-primary px-5 font-medium text-white no-underline hover:text-white"
to={cta[status].path}
to={cta[status]?.path}
>
{cta[status].label}
{cta[status]?.label}
</Link>
</>
)}
Expand Down
21 changes: 9 additions & 12 deletions src/app/core/views/Preferences/tabs/Account/AccountDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useAppDispatch } from '../../../../../store/hooks';
import { updateUserProfileThunk } from '../../../../../store/slices/user';
import Section from '../../components/Section';
import { areCredentialsCorrect } from 'app/auth/services/auth.service';
import envService from '../../../../services/env.service';
import errorService from '../../../../services/error.service';

export default function AccountDetails({ className = '' }: { className?: string }): JSX.Element {
const { translate } = useTranslationContext();
Expand Down Expand Up @@ -209,14 +209,11 @@ function AccountDetailsModal({
value={emailValue}
name="email"
/>

{!envService.isProduction() && (
<div className="flex h-11 items-center">
<Button disabled={status.tag === 'loading'} variant="secondary" onClick={openEditEmail}>
{translate('actions.change')}
</Button>
</div>
)}
<div className="flex h-11 items-center">
<Button disabled={status.tag === 'loading'} variant="secondary" onClick={openEditEmail}>
{translate('actions.change')}
</Button>
</div>
</div>

<div className="flex justify-end">
Expand Down Expand Up @@ -261,8 +258,7 @@ function ChangeEmailModal({ isOpen, onClose, email }: { isOpen: boolean; onClose
setStatus({ tag: 'loading' });
const correctPassword = await areCredentialsCorrect(email, password);
if (correctPassword) {
// TODO -> Send verificaion email
// Send verification to newEmail
await userService.changeEmail(newEmail);
notificationsService.show({
text: translate('views.account.tabs.account.accountDetails.changeEmail.sucessSendingVerification', {
email: newEmail,
Expand All @@ -273,7 +269,8 @@ function ChangeEmailModal({ isOpen, onClose, email }: { isOpen: boolean; onClose
} else {
setStatus({ tag: 'error', type: 'PASSWORD_INVALID' });
}
} catch {
} catch (error) {
errorService.reportError(error);
setStatus({ tag: 'error', type: 'UNKNOWN' });
notificationsService.show({
text: translate('views.account.tabs.account.accountDetails.changeEmail.errorSendingVerification'),
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@
"sendVerification": "发送验证",
"sendingVerification": "发送验证",
"sucessSendingVerification": "发送电子邮件到 {{email}}",
"errorSendingVerification": "错误发送验证"
"errorSendingVerification": "错误发送验证",
"confirm": "确认"
}
},
"support": {
Expand Down
13 changes: 13 additions & 0 deletions src/app/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,19 @@
"name": "Name",
"lastname": "Nachname"
},
"changeEmail": {
"title": "E-Mail ändern",
"email": "E-Mail",
"newEmail": "Neue E-Mail",
"password": "Passwort",
"errorSameEmail": "Die neue E-Mail muss sich von der aktuellen unterscheiden",
"errorPassword": "Falsches Passwort, bitte versuchen Sie es erneut",
"sendVerification": "Verifikation senden",
"sendingVerification": "Verifikation wird gesendet",
"sucessSendingVerification": "Verifikation an {{email}} gesendet",
"errorSendingVerification": "Fehler beim Senden der Verifikation",
"confirm": "Bestätigen"
},
"updateProfile": "Profil erfolgreich aktualisiert",
"errorUpdatingProfile": "Wir konnten dein Profil nicht aktualisieren"
},
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@
"sendVerification": "Send verification",
"sendingVerification": "Sending verification",
"sucessSendingVerification": "Email verification sent to {{email}}",
"errorSendingVerification": "Error sending verification"
"errorSendingVerification": "Error sending verification",
"confirm": "Confirm"
}
},
"support": {
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@
"sendVerification": "Enviar verificación",
"sendingVerification": "Enviando verificación",
"sucessSendingVerification": "Verificación enviada a {{email}}",
"errorSendingVerification": "Error enviando verificación"
"errorSendingVerification": "Error enviando verificación",
"confirm": "Confirmar"
}
},
"support": {
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@
"sendVerification": "Envoyer la vérification",
"sendingVerification": "Envoi de la vérification",
"sucessSendingVerification": "Vérification par courriel envoyée",
"errorSendingVerification": "Erreur d'envoi de la vérification"
"errorSendingVerification": "Erreur d'envoi de la vérification",
"confirm": "Confirmer"
}
},
"support": {
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@
"sendVerification": "Invia verifica",
"sendingVerification": "Invio verifica",
"sucessSendingVerification": "Verifica e-mail inviata a {{email}}",
"errorSendingVerification": "Errore nell'invio della verifica"
"errorSendingVerification": "Errore nell'invio della verifica",
"confirm": "Confermare"
}
},
"support": {
Expand Down
3 changes: 2 additions & 1 deletion src/app/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@
"sendVerification": "Отправить верификацию",
"sendingVerification": "Отправка верификации",
"sucessSendingVerification": "Проверка электронной почты отправлена на {{email}}",
"errorSendingVerification": "Ошибка при отправке верификации"
"errorSendingVerification": "Ошибка при отправке верификации",
"confirm": "Подтвердить"
}
},
"support": {
Expand Down
Loading

1 comment on commit 533a128

@vercel
Copy link

@vercel vercel bot commented on 533a128 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.