Skip to content

Commit

Permalink
Merge pull request #1018 from internxt/release/staging-2-feb-2024
Browse files Browse the repository at this point in the history
[_] release/Staging
  • Loading branch information
CandelR authored Feb 6, 2024
2 parents eb305b5 + 44ea805 commit 70edcc0
Show file tree
Hide file tree
Showing 48 changed files with 1,217 additions and 337 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ REACT_APP_DEBUG=
REACT_APP_ANALYTICS_ERROR_REPORTING_WRITE_KEY=
REACT_APP_CDP_DATA_PLANE=
REACT_APP_IMPACT_API=
REACT_APP_GA_ID=
REACT_APP_GA_ID=
REACT_APP_INTERCOM_PROVIDER_KEY=
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.format": "explicit"
"source.fixAll.eslint": true,
"source.fixAll.format": true
},
"svg.preview.background": "editor"
}
Expand Down
3 changes: 2 additions & 1 deletion 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.63",
"@internxt/sdk": "^1.4.65",
"@phosphor-icons/react": "^2.0.10",
"@popperjs/core": "^2.11.6",
"@reduxjs/toolkit": "^1.6.0",
Expand All @@ -33,6 +33,7 @@
"history": "^4.10.1",
"https-browserify": "^1.0.0",
"i18next": "^22.4.9",
"i18next-browser-languagedetector": "^7.2.0",
"idb": "^6.1.5",
"js-file-download": "^0.4.12",
"jszip": "=3.2.2",
Expand Down
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { LRUFilesPreviewCacheManager } from './app/database/services/database.se
import { LRUPhotosPreviewsCacheManager } from './app/database/services/database.service/LRUPhotosPreviewCacheManager';
import { LRUPhotosCacheManager } from './app/database/services/database.service/LRUPhotosCacheManager';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
import { t } from 'i18next';
import i18next, { t } from 'i18next';
import authService from './app/auth/services/auth.service';
import localStorageService from './app/core/services/local-storage.service';
import Mobile from './app/drive/views/MobileView/MobileView';
Expand Down Expand Up @@ -58,6 +58,7 @@ const App = (props: AppProps): JSX.Element => {

useEffect(() => {
initialState();
i18next.changeLanguage();
}, []);

if ((token && skipSignupIfLoggedIn) || (token && navigationService.history.location.pathname !== '/new')) {
Expand Down
6 changes: 6 additions & 0 deletions src/app/analytics/TrackingPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export namespace TrackingPlan {
method: string;
}

export interface AccountUnblockProperties {
email: string;
}

export enum EventNames {
FileUploadStart = 'Upload Started',
FileUploadError = 'Upload Error',
Expand All @@ -124,5 +128,7 @@ export namespace TrackingPlan {
FilePreviewClicked = 'File Preview Clicked',
BackupKeyDownloaded = 'Backup Key Downloaded',
PasswordRecovered = 'Password Recovered',
UnblockAccountEmailSent = 'Unblock Account Email Sent',
AccountUnblocked = 'Account Unblocked',
}
}
7 changes: 7 additions & 0 deletions src/app/analytics/services/analytics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ export function trackPasswordRecovered(properties: TrackingPlan.PasswordRecovere
analytics.track(TrackingPlan.EventNames.PasswordRecovered, properties);
}

export function trackAccountUnblockEmailSent(properties: TrackingPlan.AccountUnblockProperties): void {
analytics.track(TrackingPlan.EventNames.UnblockAccountEmailSent, properties);
}
export function trackAccountUnblocked(properties: TrackingPlan.AccountUnblockProperties): void {
analytics.track(TrackingPlan.EventNames.AccountUnblocked, properties);
}

function trackData(properties, actionName) {
const user = localStorageService.getUser();
httpService.post(`${process.env.REACT_APP_API_URL}/api/data`, {
Expand Down
22 changes: 19 additions & 3 deletions src/app/auth/components/LogIn/LogIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { auth } from '@internxt/lib';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet-async';
import QueryString from 'qs';

import { initializeUserThunk, userActions } from 'app/store/slices/user';
import { RootState } from 'app/store';
Expand All @@ -17,7 +18,7 @@ import { WarningCircle } from '@phosphor-icons/react';
import { planThunks } from 'app/store/slices/plan';
import { productsThunks } from 'app/store/slices/products';
import errorService from 'app/core/services/error.service';
import { AppView, IFormValues } from 'app/core/types';
import AppError, { AppView, IFormValues } from 'app/core/types';
import navigationService from 'app/core/services/navigation.service';
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';
import TextInput from '../TextInput/TextInput';
Expand All @@ -26,6 +27,7 @@ import { referralsThunks } from 'app/store/slices/referrals';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import shareService from '../../../share/services/share.service';
import notificationsService, { ToastType } from '../../../notifications/services/notifications.service';
import { trackAccountUnblockEmailSent } from '../../../analytics/services/analytics.service';

export default function LogIn(): JSX.Element {
const { translate } = useTranslationContext();
Expand Down Expand Up @@ -130,6 +132,14 @@ export default function LogIn(): JSX.Element {
}
};

const sendUnblockAccountEmail = async (email: string) => {
try {
await authService.requestUnblockAccount(email);
} catch (error) {
errorService.reportError(error);
}
};

const onSubmit: SubmitHandler<IFormValues> = async (formData, event) => {
event?.preventDefault();
setIsLoggingIn(true);
Expand Down Expand Up @@ -172,12 +182,18 @@ export default function LogIn(): JSX.Element {

if (castedError.message.includes('not activated') && auth.isValidEmail(email)) {
navigationService.history.push(`/activate/${email}`);
} else {
// analyticsService.signInAttempted(email, castedError);
}

setLoginError([castedError.message]);
setShowErrors(true);
if ((err as AppError)?.status === 403) {
await sendUnblockAccountEmail(email);
trackAccountUnblockEmailSent({ email });
navigationService.history.push({
pathname: AppView.BlockedAccount,
search: QueryString.stringify({ email: email }),
});
}
} finally {
setIsLoggingIn(false);
}
Expand Down
20 changes: 20 additions & 0 deletions src/app/auth/components/ResendButton/ResendButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useTranslationContext } from '../../../i18n/provider/TranslationProvider';

interface IResendButtonProps {
enableButton: boolean;
onClick: () => void;
countDown: number;
}

export const ResendButton = ({ enableButton, onClick, countDown }: IResendButtonProps) => {
const { translate } = useTranslationContext();

if (enableButton) {
return (
<button className="ml-2 cursor-pointer text-primary" onClick={onClick}>
{translate('blockedAccount.resend')}
</button>
);
}
return <span className="ml-2 font-medium">&nbsp;{countDown}</span>;
};
6 changes: 2 additions & 4 deletions src/app/auth/components/SignUp/SignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,8 @@ function SignUp(props: SignUpProps): JSX.Element {
<div className="flex space-x-2.5 rounded-lg bg-primary/10 p-3 pr-4 dark:bg-primary/20">
<Info size={20} className="shrink-0 text-primary" />
<p className="text-xs">
Internxt doesn't store passwords.{' '}
<span className="font-semibold">
In case you forget your password, you will lose access to all your files.
</span>
{translate('auth.signup.info1')}{' '}
<span className="font-semibold">{translate('auth.signup.info2')}</span>
</p>
</div>

Expand Down
20 changes: 16 additions & 4 deletions src/app/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export async function logOut(loginParams?: Record<string, string>): Promise<void
await databaseService.clear();
localStorageService.clear();
RealtimeService.getInstance().stop();
navigationService.push(AppView.Login, loginParams);
if (!navigationService.isCurrentPath(AppView.BlockedAccount)) {
navigationService.push(AppView.Login, loginParams);
}
}

export function cancelAccount(): Promise<void> {
Expand Down Expand Up @@ -156,9 +158,7 @@ export const doLogin = async (
};
})
.catch((error) => {
if (error instanceof UserAccessError) {
analyticsService.signInAttempted(email, error.message);
}
analyticsService.signInAttempted(email, error.message);
throw error;
});
};
Expand Down Expand Up @@ -401,6 +401,16 @@ const sendChangePasswordEmail = (email: string): Promise<void> => {
return authClient.sendChangePasswordEmail(email);
};

export const requestUnblockAccount = (email: string): Promise<void> => {
const authClient = SdkFactory.getNewApiInstance().createAuthClient();
return authClient.requestUnblockAccount(email);
};

export const unblockAccount = (token: string): Promise<void> => {
const authClient = SdkFactory.getNewApiInstance().createAuthClient();
return authClient.unblockAccount(token);
};

const authService = {
logOut,
doLogin,
Expand All @@ -414,6 +424,8 @@ const authService = {
sendChangePasswordEmail,
updateCredentialsWithToken,
resetAccountWithToken,
requestUnblockAccount,
unblockAccount,
};

export default authService;
153 changes: 153 additions & 0 deletions src/app/auth/views/BlockedAccountView/BlockedAccountView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import navigationService from 'app/core/services/navigation.service';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { ReactComponent as InternxtLogo } from 'assets/icons/big-logo.svg';
import { ShieldWarning } from '@phosphor-icons/react';
import Button from 'app/shared/components/Button/Button';
import { AppView } from 'app/core/types';
import { useCallback, useEffect, useState } from 'react';
import queryString from 'query-string';
import { useParams } from 'react-router-dom';
import Spinner from 'app/shared/components/Spinner/Spinner';
import authService from 'app/auth/services/auth.service';
import errorService from 'app/core/services/error.service';
import notificationsService, { ToastType } from 'app/notifications/services/notifications.service';
import ExpiredLink from 'app/shared/views/ExpiredLink/ExpiredLinkView';
import { ResendButton } from '../../components/ResendButton/ResendButton';
import { trackAccountUnblockEmailSent } from '../../../analytics/services/analytics.service';

const COUNTDOWN_TIME = 30;

export default function BlockedAccountView(): JSX.Element {
const { translate } = useTranslationContext();
const [userEmail, setUserEmail] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [isInvalidToken, setIsInvalidToken] = useState(false);
const [sendingEmail, setSendingEmail] = useState(false);
const [enableResendButton, setEnableResendButton] = useState(true);
const [countDown, setCountDown] = useState<number>(COUNTDOWN_TIME);

const { token } = useParams<{ token: string }>();

const goToRecoveryLink = () => {
navigationService.push(AppView.RecoveryLink);
};

useEffect(() => {
const queryParameters = navigationService.history.location.search;
const email = queryString.parse(queryParameters).email as string;
setUserEmail(email);
}, []);

useEffect(() => {
const unblockAccount = async () => {
if (token) {
try {
setIsLoading(true);
await authService.unblockAccount(token);
navigationService.push(AppView.Login);
} catch (error) {
errorService.reportError(error);
setIsInvalidToken(true);
}
}
setIsLoading(false);
};
unblockAccount();
}, [token]);

useEffect(() => {
if (!enableResendButton && countDown > 0) {
setTimeout(() => {
setCountDown(countDown - 1);
}, 1000);
} else {
setEnableResendButton(true);
setCountDown(COUNTDOWN_TIME);
}
}, [enableResendButton, countDown]);

const resendAccountUnblockEmail = useCallback(async () => {
setSendingEmail(true);
try {
await authService.requestUnblockAccount(userEmail);
trackAccountUnblockEmailSent({ email: userEmail });
setEnableResendButton(false);
} catch (error) {
errorService.reportError(error);
notificationsService.show({
text: translate('error.serverError'),
type: ToastType.Error,
});
} finally {
setSendingEmail(false);
}
}, [userEmail]);

if (isLoading) {
return (
<div className="flex h-full items-center justify-center">
<Spinner className="h-7 w-7" />
</div>
);
}

if (isInvalidToken) {
return <ExpiredLink />;
}

return (
<div className="flex h-full w-full flex-col overflow-auto bg-surface dark:bg-gray-1">
<div className="flex shrink-0 flex-row justify-center py-10 sm:justify-start sm:pl-20">
<InternxtLogo className="h-auto w-28 text-gray-100" />
</div>
<div className="flex h-full flex-col items-center justify-center">
<div className="flex w-96 flex-col items-center justify-center text-center">
<ShieldWarning size={80} className="mb-5 text-primary" weight="thin" />
<h2 className="mb-4 text-3xl font-medium text-gray-100">{translate('blockedAccount.title')}</h2>
<p className="font-regular mb-4 text-base text-gray-80">{translate('blockedAccount.text1')}</p>
<p className="font-regular mb-4 text-base text-gray-80">
{translate('blockedAccount.text2', { email: userEmail })}
</p>
<Button
loading={false}
variant="secondary"
className="mb-5 h-11 w-full text-base font-medium"
onClick={goToRecoveryLink}
>
{translate('blockedAccount.forgotPassword')}
</Button>
<span className="mb-5 h-px w-72 bg-gray-10"></span>
<p className="font-regular flex flex-row items-center justify-center text-base text-gray-80">
{translate('blockedAccount.text3')}
{sendingEmail === true ? (
<Spinner className="ml-2 h-5 w-5 text-primary" />
) : (
<ResendButton
enableButton={enableResendButton}
onClick={resendAccountUnblockEmail}
countDown={countDown}
/>
)}
</p>
</div>
</div>

<div className="flex shrink-0 flex-row justify-center py-8">
<a
href="https://internxt.com/legal"
target="_blank"
className="font-regular mr-4 mt-6 text-base text-gray-80 no-underline hover:text-gray-100"
>
{translate('general.terms')}
</a>
<a
href="https://help.internxt.com"
target="_blank"
className="font-regular mr-4 mt-6 text-base text-gray-80 no-underline hover:text-gray-100"
>
{translate('general.help')}
</a>
</div>
</div>
);
}
6 changes: 6 additions & 0 deletions src/app/core/config/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
"path": "/appsumo",
"exact": true
},
{
"id": "blocked-account",
"layout": "empty",
"path": "/blocked-account/:token?",
"exact": true
},
{
"id": "login",
"layout": "empty",
Expand Down
Loading

0 comments on commit 70edcc0

Please sign in to comment.