Skip to content
Open
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 src/components/shared/__tests__/navigation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('navigation', () => {
isSidebarOpen={false}
openSidebar={jest.fn()}
navItems={defaultNavItems}
updateNavItems={jest.fn()}
></Navigation>
</MockThemeProvider>
</AuthProvider>,
Expand Down
74 changes: 36 additions & 38 deletions src/components/shared/layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,51 +29,54 @@ interface OwnProps {

type LayoutProps = OwnProps;

interface NavState {
interface NavVisibilityState {
isNavAtTop: boolean;
isNavVisible: boolean;
}

interface SidebarState {
isSidebarOpen: boolean;
interface NavItemsState {
navItems: NavItem[];
}

interface AuthState {
isUserAuthenticated: boolean;
interface SidebarState {
isSidebarOpen: boolean;
}

type LayoutState = NavState & SidebarState & AuthState;
type LayoutState = NavVisibilityState & SidebarState & NavItemsState;

const AUTH_IS = '[layout] AUTH_IS' as const;
const NAV_TOGGLE = '[layout] NAV_TOGGLE' as const;
const SIDEBAR_TOGGLE = '[layout] SIDEBAR_TOGGLE' as const;
const NAV_ITEMS_CHANGE = '[layout] NAV_ITEMS_CHANGE' as const;

type ActionType = typeof AUTH_IS | typeof SIDEBAR_TOGGLE | typeof NAV_TOGGLE;
type ActionType =
| typeof SIDEBAR_TOGGLE
| typeof NAV_TOGGLE
| typeof NAV_ITEMS_CHANGE;

interface LayoutAction<T = ActionType> {
type: T;
payload: NavState | SidebarState | AuthState;
payload: NavVisibilityState | SidebarState | NavItemsState;
}

const initialState: LayoutState = {
isNavAtTop: true,
isNavVisible: true,
navItems: [],
isSidebarOpen: false,
isUserAuthenticated: false,
};

const reducer: Reducer<LayoutState, LayoutAction> = (state, action) => {
switch (action.type) {
case NAV_TOGGLE:
case AUTH_IS:
case SIDEBAR_TOGGLE:
case NAV_ITEMS_CHANGE:
return { ...state, ...action.payload };
default:
throw new Error('unknown action type');
}
};

const Layout: FC<LayoutProps> = ({ children, navItems = defaultNavItems }) => {
const Layout: FC<LayoutProps> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);

useScrollPosition(
Expand Down Expand Up @@ -103,34 +106,28 @@ const Layout: FC<LayoutProps> = ({ children, navItems = defaultNavItems }) => {
const setOpen = (isSidebarOpen: boolean) => () =>
dispatch({ type: SIDEBAR_TOGGLE, payload: { isSidebarOpen } });

useLayoutEffect(() => {
const updateNavItems = () => {
const isUserAuthenticated = UserAuthHelper.isUserAuthenticated();
const newNavItems = defaultNavItems.filter(({ show }) => {
switch (show) {
case Show.Always:
return true;
case Show.AuthOnly:
return isUserAuthenticated;
case Show.GuestOnly:
return !isUserAuthenticated;
case Show.Never:
default:
return false;
}
});
dispatch({ type: NAV_ITEMS_CHANGE, payload: { navItems: newNavItems } });
};

dispatch({ type: AUTH_IS, payload: { isUserAuthenticated } });
useLayoutEffect(() => {
updateNavItems();
}, []);

// TODO: Possibly mutation to be added to useEffects
/*
https://reactjs.org/docs/hooks-reference.html#useeffect
Mutations, subscriptions, timers, logging, and other side effects are
not allowed inside the main body of a function component (referred to
as React’s render phase). Doing so will lead to confusing bugs and
inconsistencies in the UI.
*/
navItems = navItems.filter(({ show }) => {
switch (show) {
case Show.Always:
return true;
case Show.AuthOnly:
return state.isUserAuthenticated;
case Show.GuestOnly:
return !state.isUserAuthenticated;
case Show.Never:
default:
return false;
}
});

return (
<Fragment>
<Seo title="Home" />
Expand All @@ -141,9 +138,10 @@ const Layout: FC<LayoutProps> = ({ children, navItems = defaultNavItems }) => {
<Navigation
isAtTop={state.isNavAtTop}
isVisible={state.isNavVisible}
navItems={navItems}
navItems={state.navItems}
isSidebarOpen={state.isSidebarOpen}
openSidebar={setOpen(true)}
updateNavItems={updateNavItems}
/>

<ErrorBoundary
Expand All @@ -165,7 +163,7 @@ const Layout: FC<LayoutProps> = ({ children, navItems = defaultNavItems }) => {
onClose={setOpen(false)}
labelledby="menu-button"
>
<Sidebar navItems={navItems} />
<Sidebar navItems={state.navItems} />
</OffCanvas>
</Fragment>
</ThemeProvider>
Expand Down
17 changes: 8 additions & 9 deletions src/components/shared/navigation/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
isSidebarOpen: boolean;
navItems?: NavItem[];
openSidebar: () => void;
updateNavItems: () => void;
}

type NavigationProps = OwnProps;
Expand Down Expand Up @@ -101,6 +102,7 @@
isSidebarOpen,
navItems = [],
openSidebar,
updateNavItems,
}) => {
const authContext = useContext(AuthContext);

Expand All @@ -109,18 +111,15 @@
return;
}
authContext.signOut();
updateNavItems();

Check warning on line 114 in src/components/shared/navigation/navigation.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/shared/navigation/navigation.tsx#L114

Added line #L114 was not covered by tests
navigate('/');
};

const setAvatar = (avatar: string) => {
navItems.map((navItem) => {
if (navItem.key == 'user-avatar-dropdown') {
navItem.item = <Profile content={avatar} signOut={signOut} />;
}
});
};

setAvatar(authContext.avatar);
navItems.map((navItem) => {
if (navItem.key == 'user-avatar-dropdown') {
navItem.item = <Profile content={authContext.avatar} signOut={signOut} />;
}
});

return (
<Wrapper isAtTop={isAtTop} isVisible={isAtTop || isVisible}>
Expand Down
32 changes: 20 additions & 12 deletions src/components/shared/navigation/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { navigate } from 'gatsby';
import React, { FC, useState, useRef } from 'react';
import React, { FC, useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';

import { UserAuthHelper } from '@helpers';
Expand Down Expand Up @@ -65,30 +65,38 @@

const Profile: FC<ProfileProps> = ({ content, isOnline = false, signOut }) => {
const [expanded, setExpanded] = useState(false);
const dropdownRef = useRef<HTMLDivElement | null>(null);

const close = () => {
const close = useCallback(() => {
setExpanded(false);
document.removeEventListener('click', close);
};

const setRef = (ref: HTMLDivElement | null) => {
dropdownRef.current = ref;
document.addEventListener('click', close);
};
}, []);

const toggle = () => setExpanded((expanded) => !expanded);
const goToProfile = () => navigate(`/profile/${UserAuthHelper.getUserId()}`);

useEffect(() => {
if (expanded) {
document.addEventListener('click', close);
return;

Check warning on line 79 in src/components/shared/navigation/profile.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/shared/navigation/profile.tsx#L78-L79

Added lines #L78 - L79 were not covered by tests
}
document.removeEventListener('click', close);
}, [expanded]);

return (
<Wrapper onClick={toggle}>
<Icon src={content} height={46} width={46} alt="profile image" />
{isOnline && <Dot src={dotIcon} height={16} width={16} alt="blue dot" />}

{expanded ? (
<Dropdown onBlur={close} ref={setRef}>
<Dropdown>

Check warning on line 90 in src/components/shared/navigation/profile.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/shared/navigation/profile.tsx#L90

Added line #L90 was not covered by tests
<DropdownItem onClick={goToProfile}>Profile</DropdownItem>
<DropdownItem onClick={signOut}>Sign Out</DropdownItem>
<DropdownItem
onClick={() => {
document.removeEventListener('click', close);
signOut();

Check warning on line 95 in src/components/shared/navigation/profile.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/shared/navigation/profile.tsx#L93-L95

Added lines #L93 - L95 were not covered by tests
}}
>
Sign Out
</DropdownItem>
</Dropdown>
) : null}
</Wrapper>
Expand Down
16 changes: 8 additions & 8 deletions src/mocks/responses/get-recent-devs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,31 @@ export const getRecentDevs: ApiResponse<RecentDev[]> = {
{
id: '08d6c5e7-6100-c770-61c3-834f6474a77b',
bio:
'Hi. Im Dan. Ive been working as a programmer for ~15 years now. Python, ML, NLP, server-side...',
"Hi. I'm Dan. I've been working as a programmer for ~15 years now. Python, ML, NLP, server-side...",
updatedAt: today,
},
{
id: '08d6c5e7-6100-c770-61c3-834f6474a77c',
bio:
'Hi. Im Dan. Ive been working as a programmer for ~15 years now. Python, ML, NLP, server-side...',
"Hi. I'm Dan. I've been working as a programmer for ~15 years now. Python, ML, NLP, server-side...",
updatedAt: oneDayAgo,
},
{
id: '08d6c5e7-6100-c770-61c3-834f6474a77c',
id: '08d6c5e7-6100-c770-61c3-834f6474a77d',
bio:
'Hi. Im Dan. Ive been working as a programmer for ~15 years now. Python, ML, NLP, server-side...',
"Hi. I'm Dan. I've been working as a programmer for ~15 years now. Python, ML, NLP, server-side...",
updatedAt: twoDaysAgo,
},
{
id: '08d6c5e7-6100-c770-61c3-834f6474a77d',
id: '08d6c5e7-6100-c770-61c3-834f6474a77e',
bio:
'Hi. Im Dan. Ive been working as a programmer for ~15 years now. Python, ML, NLP, server-side...',
"Hi. I'm Dan. I've been working as a programmer for ~15 years now. Python, ML, NLP, server-side...",
updatedAt: oneMonthAgo,
},
{
id: '08d6c5e7-6100-c770-61c3-834f6474a77d',
id: '08d6c5e7-6100-c770-61c3-834f6474a77f',
bio:
'Hi. Im Dan. Ive been working as a programmer for ~15 years now. Python, ML, NLP, server-side...',
"Hi. I'm Dan. I've been working as a programmer for ~15 years now. Python, ML, NLP, server-side...",
updatedAt: twoMonthsAgo,
},
],
Expand Down