Skip to content

Commit

Permalink
chore(web): More auth refactoring
Browse files Browse the repository at this point in the history
- Simplify useAuth storage and only use local storage
- Load useBlueprint only in one place
  • Loading branch information
SokratisVidros committed May 27, 2024
1 parent 635d365 commit c4b56bf
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 290 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { colors, Text, Title, Container } from '@novu/design-system';
import PageMeta from './PageMeta';

export default function AuthContainer({
export default function AuthLayout({
title,
description = '',
children,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { addOrganization, switchOrganization } from '../../../api/organization';
import { useSpotlightContext } from '../../providers/SpotlightProvider';

export const useOrganizationSelect = () => {
const [value, setValue] = useState<string>('');
const [search, setSearch] = useState<string>('');
const [loadingSwitch, setLoadingSwitch] = useState<boolean>(false);
const { addItem, removeItems } = useSpotlightContext();
Expand Down Expand Up @@ -60,6 +59,8 @@ export const useOrganizationSelect = () => {
});
}

const value = currentOrganization?._id;

const organizationItems = useMemo(() => {
return (organizations || [])
.filter((item) => item._id !== value)
Expand All @@ -72,13 +73,8 @@ export const useOrganizationSelect = () => {
}));
}, [organizations, value, switchOrgCallback]);

useEffect(() => {
setValue(currentOrganization?._id || '');
}, [currentOrganization]);

useEffect(() => {
removeItems(['change-org-' + value]);

addItem(organizationItems);
}, [addItem, removeItems, organizationItems, value]);

Expand Down
14 changes: 2 additions & 12 deletions apps/web/src/components/utils/Spotlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { UTM_CAMPAIGN_QUERY_PARAM } from '@novu/shared';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import { useAuth } from '@novu/shared-web';
import { ROUTES } from '../../constants/routes.enum';
import useThemeChange from '../../hooks/useThemeChange';
import { useSpotlightContext } from '../providers/SpotlightProvider';
Expand All @@ -13,9 +12,8 @@ import useStyles from './Spotlight.styles';
export const SpotLight = ({ children }) => {
const navigate = useNavigate();
const { items, addItem } = useSpotlightContext();
const { logout } = useAuth();
const { themeIcon, toggleColorScheme } = useThemeChange();
const { classes, theme } = useStyles();
const { classes } = useStyles();

useEffect(() => {
addItem([
Expand Down Expand Up @@ -91,16 +89,8 @@ export const SpotLight = ({ children }) => {
toggleColorScheme();
},
},
{
id: 'sign-out',
title: 'Sign out',
icon: <IconLogout />,
onTrigger: () => {
logout();
},
},
]);
}, [navigate, addItem, themeIcon, toggleColorScheme, logout]);
}, [navigate, addItem, themeIcon, toggleColorScheme]);

return (
<SpotlightProvider limit={7} shortcut={['mod + K']} actions={items} classNames={classes}>
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/hooks/useBlueprint.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';

import { useAuth } from '@novu/shared-web';
import { useSegment } from '../components/providers/SegmentProvider';
import { ROUTES } from '../constants/routes.enum';

export const useBlueprint = () => {
const [params] = useSearchParams();
const blueprintId = params.get('blueprintId');
const navigate = useNavigate();
const { pathname } = useLocation();
const segment = useSegment();
const id = localStorage.getItem('blueprintId');
const { token } = useAuth();

useEffect(() => {
if (id) {
navigate(ROUTES.WORKFLOWS_CREATE, {
replace: true,
});
}
}, [navigate, id, token, pathname]);
}, [navigate, id]);

useEffect(() => {
if (blueprintId) {
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/hooks/useVercelParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export function useVercelParams() {
const code = params.get('code');
const next = params.get('next');
const configurationId = params.get('configurationId');

const isFromVercel = !!(code && next);

return {
params,
code,
next,
configurationId,
Expand Down
52 changes: 20 additions & 32 deletions apps/web/src/pages/auth/InvitationPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useLocation, useNavigate, useParams, Link } from 'react-router-dom';
import { useParams, Link } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { Center, LoadingOverlay } from '@mantine/core';
import { IGetInviteResponseDto } from '@novu/shared';

import { getInviteTokenData } from '../../api/invitation';
import AuthContainer from '../../components/layout/components/AuthContainer';
import AuthLayout from '../../components/layout/components/AuthLayout';
import { SignUpForm } from './components/SignUpForm';
import { colors, Text, Button } from '@novu/design-system';
import { useAuth } from '@novu/shared-web';
Expand All @@ -14,16 +14,10 @@ import { LoginForm } from './components/LoginForm';

export default function InvitationPage() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const { token, logout, currentUser } = useAuth();
const location = useLocation();
// TODO: Replace token check with currentUser check
const isLoggedIn = !!token;
const { currentUser, logout } = useAuth();
const { token: invitationToken } = useParams<{ token: string }>();
const tokensRef = useRef({ token, invitationToken });
tokensRef.current = { token, invitationToken };
const { isLoading: isAcceptingInvite, submitToken } = useAcceptInvite();
const { data, isInitialLoading } = useQuery<IGetInviteResponseDto, IGetInviteResponseDto>(
const { isLoading: isAcceptingInvite, acceptInvite } = useAcceptInvite();
const { data, isLoading: isInviteTokenDataLoading } = useQuery<IGetInviteResponseDto, IGetInviteResponseDto>(
['getInviteTokenData'],
() => getInviteTokenData(invitationToken || ''),
{
Expand All @@ -33,30 +27,24 @@ export default function InvitationPage() {
);
const inviterFirstName = data?.inviter?.firstName || '';
const organizationName = data?.organization.name || '';
const existingUser = !!(invitationToken && data?._userId);
const isLoggedInAsInvitedUser = !!(isLoggedIn && existingUser && currentUser && currentUser._id === data?._userId);
const Form = existingUser ? LoginForm : SignUpForm;

const logoutWhenActiveSession = () => {
logout();
navigate(location.pathname);
};
const existingUserId = data?._userId;
const isLoggedInAsInvitedUser = !!(existingUserId && currentUser && currentUser._id === existingUserId);
const Form = existingUserId ? LoginForm : SignUpForm;

useEffect(() => {
// auto accept invitation when logged in as invited user
if (isLoggedInAsInvitedUser) {
submitToken(tokensRef.current.token as string, tokensRef.current.invitationToken as string, true);
if (invitationToken && isLoggedInAsInvitedUser) {
acceptInvite(invitationToken);
}
}, [isLoggedInAsInvitedUser, submitToken]);
}, [isLoggedInAsInvitedUser, acceptInvite, invitationToken]);

useEffect(() => {
return () => {
queryClient.removeQueries(['getInviteTokenData']);
};
}, [queryClient]);

return isLoggedIn ? (
<AuthContainer
return currentUser ? (
<AuthLayout
title="Active Session!"
customDescription={
<Center inline mb={40} mt={20}>
Expand All @@ -70,7 +58,7 @@ export default function InvitationPage() {
</Center>
}
>
<Button data-test-id="success-screen-reset" onClick={logoutWhenActiveSession} inherit>
<Button data-test-id="success-screen-reset" onClick={() => logout()} inherit>
Log out
</Button>
<Center mt={20}>
Expand All @@ -81,10 +69,10 @@ export default function InvitationPage() {
<Text>Dashboard</Text>
</Link>
</Center>
</AuthContainer>
</AuthLayout>
) : (
<AuthContainer
title={existingUser ? 'Sign In & Accept Invite' : 'Get Started'}
<AuthLayout
title={existingUserId ? 'Sign In & Accept Invite' : 'Get Started'}
customDescription={
inviterFirstName && organizationName ? (
<Center inline mb={60} mt={20} data-test-id="invitation-description">
Expand All @@ -107,7 +95,7 @@ export default function InvitationPage() {
) : undefined
}
>
{isInitialLoading ? (
{isInviteTokenDataLoading ? (
<LoadingOverlay
visible
overlayColor={colors.B30}
Expand All @@ -118,6 +106,6 @@ export default function InvitationPage() {
) : (
<Form email={data?.email} invitationToken={invitationToken} />
)}
</AuthContainer>
</AuthLayout>
);
}
76 changes: 4 additions & 72 deletions apps/web/src/pages/auth/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,10 @@
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

import { useAuth } from '@novu/shared-web';
import { LoginForm } from './components/LoginForm';
import AuthContainer from '../../components/layout/components/AuthContainer';
import { useVercelIntegration, useBlueprint, useVercelParams } from '../../hooks';
import SetupLoader from './components/SetupLoader';
import { useSegment } from '../../components/providers/SegmentProvider';
import { useAcceptInvite } from './components/useAcceptInvite';
import { ROUTES } from '../../constants/routes.enum';
import AuthLayout from '../../components/layout/components/AuthLayout';

export default function LoginPage() {
useBlueprint();
const { login, token: oldToken, currentUser } = useAuth();
const segment = useSegment();
const navigate = useNavigate();
const [params] = useSearchParams();
const queryToken = params.get('token');
const invitationToken = params.get('invitationToken');
const source = params.get('source');
const sourceWidget = params.get('source_widget');
const token = queryToken ?? oldToken;

const { startVercelSetup, isLoading } = useVercelIntegration();
const { code, isFromVercel, next } = useVercelParams();
const { isLoading: isLoadingAcceptInvite, submitToken } = useAcceptInvite();

useEffect(() => {
if (token) {
if (!invitationToken && currentUser?._id && (!currentUser?.organizationId || !currentUser?.environmentId)) {
const authApplicationLink = isFromVercel
? `${ROUTES.AUTH_APPLICATION}?code=${code}&next=${next}`
: ROUTES.AUTH_APPLICATION;
login(token);
navigate(authApplicationLink);

return;
}

if (isFromVercel) {
login(token);
startVercelSetup();

return;
}

if (source === 'cli') {
segment.track('Dashboard Visit', {
widget: sourceWidget || 'unknown',
source: 'cli',
});
login(token);
navigate(ROUTES.GET_STARTED);

return;
}

if (invitationToken) {
submitToken(token, invitationToken);

return;
}

login(token);
navigate('/');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);

return isLoading || isLoadingAcceptInvite ? (
<SetupLoader title="Loading..." />
) : (
<AuthContainer title="Sign In" description="Welcome back! Sign in with the data you entered in your registration">
return (
<AuthLayout title="Sign In" description="Welcome back!">
<LoginForm />
</AuthContainer>
</AuthLayout>
);
}
30 changes: 13 additions & 17 deletions apps/web/src/pages/auth/PasswordResetPage.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
import { useParams, useNavigate } from 'react-router-dom';
import { useState } from 'react';
import AuthContainer from '../../components/layout/components/AuthContainer';
import AuthLayout from '../../components/layout/components/AuthLayout';
import { PasswordResetRequestForm } from './components/PasswordResetRequestForm';
import { PasswordResetForm } from './components/PasswordResetForm';
import { Button } from '@novu/design-system';
import { ROUTES } from '../../constants/routes.enum';
import { useVercelParams } from '../../hooks';

type Props = {};

export function PasswordResetPage({}: Props) {
export function PasswordResetPage() {
const navigate = useNavigate();
const { token } = useParams<{ token: string }>();
const [showSentSuccess, setShowSentSuccess] = useState<boolean>();
const { isFromVercel, code, next, configurationId } = useVercelParams();
const { isFromVercel, params } = useVercelParams();

const vercelQueryParams = `code=${code}&next=${next}&configurationId=${configurationId}`;
const loginLink = isFromVercel ? `/auth/login?${vercelQueryParams}` : ROUTES.AUTH_LOGIN;
function onSent() {
setShowSentSuccess(true);
}
const loginLink = isFromVercel ? `${ROUTES.AUTH_LOGIN}?${params.toString()}` : ROUTES.AUTH_LOGIN;

return showSentSuccess ? (
<AuthContainer
if (showSentSuccess) {
<AuthLayout
title="Reset Sent!"
description="We've sent a password reset link to the account associated with your email"
>
<Button data-test-id="success-screen-reset" onClick={() => navigate(loginLink)} inherit>
Go Back
</Button>
</AuthContainer>
) : (
<AuthContainer title="Reset Password" description="">
{!token && <PasswordResetRequestForm onSent={onSent} />}
</AuthLayout>;
}

return (
<AuthLayout title="Reset Password" description="">
{!token && <PasswordResetRequestForm onSent={() => setShowSentSuccess(true)} />}
{token && <PasswordResetForm token={token} />}
</AuthContainer>
</AuthLayout>
);
}
Loading

0 comments on commit c4b56bf

Please sign in to comment.