Skip to content

Commit 6e3e4b2

Browse files
authored
Merge pull request #1 from toggle-corp/feat/authentication
feat(authentication): Added login, routes and navigation
2 parents 1175711 + dc88698 commit 6e3e4b2

File tree

30 files changed

+6324
-6702
lines changed

30 files changed

+6324
-6702
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ jobs:
3333
- name: Install dependencies
3434
run: pnpm install --frozen-lockfile
3535

36+
- name: Generate Type
37+
run: pnpm generate:type
38+
3639
- name: Lint
3740
run: pnpm lint
3841
build:
@@ -58,5 +61,8 @@ jobs:
5861
- name: Install dependencies
5962
run: pnpm install --frozen-lockfile
6063

64+
- name: Generate Type
65+
run: pnpm generate:type
66+
6167
- name: Build
6268
run: pnpm build

app/components/Navbar/index.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React, {
2+
use,
3+
useCallback,
4+
} from 'react';
5+
import { useNavigate } from 'react-router';
6+
import {
7+
Button,
8+
DropdownMenu,
9+
Heading,
10+
} from '@ifrc-go/ui';
11+
import { gql } from 'urql';
12+
13+
import UserContext from '#contexts/UserContext';
14+
import { useLogoutMutation } from '#generated/types/graphql';
15+
import useAlert from '#hooks/useAlert';
16+
17+
import styles from './styles.module.css';
18+
19+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
20+
const LOGOUT = gql`
21+
mutation Logout {
22+
logout
23+
}
24+
`;
25+
26+
function Navbar() {
27+
const { user, setUser } = use(UserContext);
28+
const alert = useAlert();
29+
const navigate = useNavigate();
30+
31+
const [{ fetching: pendingLogout }, triggerLogout] = useLogoutMutation();
32+
33+
const handleLogout = useCallback(async () => {
34+
const res = await triggerLogout({});
35+
const logoutResponse = res.data?.logout;
36+
if (logoutResponse) {
37+
setUser(undefined);
38+
navigate('/login');
39+
alert.show('Logout Successful', { variant: 'success' });
40+
}
41+
}, [navigate, triggerLogout, setUser, alert]);
42+
43+
return (
44+
<nav className={styles.navbar}>
45+
<Heading className={styles.title}>NRCS</Heading>
46+
<DropdownMenu
47+
variant="tertiary"
48+
label={(
49+
<div className={styles.userInfo}>
50+
<Heading level={5}>
51+
{user?.firstName}
52+
{' '}
53+
{user?.lastName}
54+
</Heading>
55+
<Heading level={6}>
56+
Admin
57+
</Heading>
58+
</div>
59+
)}
60+
>
61+
<React.Fragment key=".0">
62+
<Button
63+
name="logout"
64+
variant="tertiary"
65+
className={styles.dropdownOption}
66+
onClick={handleLogout}
67+
disabled={pendingLogout}
68+
>
69+
{pendingLogout ? 'Logging out' : 'Logout'}
70+
</Button>
71+
</React.Fragment>
72+
</DropdownMenu>
73+
</nav>
74+
);
75+
}
76+
77+
export default Navbar;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.navbar {
2+
display: flex;
3+
flex-shrink: 0;
4+
justify-content: space-between;
5+
border-bottom: var(--go-ui-width-separator-thin) solid
6+
var(--go-ui-color-primary-red);
7+
background-color: var(--go-ui-color-white);
8+
padding: var(--go-ui-spacing-md) var(--go-ui-spacing-lg);
9+
animation: slide-down var(--go-ui-duration-animation-medium) ease-in
10+
forwards;
11+
.title {
12+
color: var(--go-ui-color-red);
13+
font-size: var(--go-ui-font-size-3xl);
14+
}
15+
.userInfo {
16+
display: flex;
17+
align-items: start;
18+
flex-direction: column;
19+
}
20+
}
21+
.dropdown-option {
22+
cursor: pointer;
23+
padding: var(--go-ui-spacing-xs);
24+
padding-left: var(--go-ui-spacing-md);
25+
width: 100%;
26+
overflow: hidden;
27+
&:hover {
28+
color: var(--go-ui-color-primary-red);
29+
}
30+
}
31+
.test {
32+
width: 150px;
33+
}
34+
35+
@keyframes slide-down {
36+
0% {
37+
transform: translateY(-0.25rem);
38+
opacity: 0;
39+
}
40+
100% {
41+
transform: translateY(0);
42+
opacity: 1;
43+
}
44+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {
2+
ReactNode,
3+
useCallback,
4+
useState,
5+
} from 'react';
6+
import {
7+
IoChevronDownOutline,
8+
IoChevronUpOutline,
9+
} from 'react-icons/io5';
10+
import { NavLink } from 'react-router';
11+
import { Button } from '@ifrc-go/ui';
12+
import { _cs } from '@togglecorp/fujs';
13+
14+
import styles from './styles.module.css';
15+
16+
export interface NavigationItem {
17+
title: string;
18+
to?: string;
19+
icon?: ReactNode;
20+
variant: 'root' | 'group' | 'leaf';
21+
children?: NavigationItem[];
22+
23+
}
24+
interface NavigationProps {
25+
navigationItem: NavigationItem[];
26+
}
27+
function Navigation({ navigationItem }: NavigationProps) {
28+
const [openAccordion, setOpenAccordion] = useState(
29+
navigationItem.map((_, i) => i),
30+
);
31+
32+
const toggleAccordion = useCallback((index: number) => {
33+
setOpenAccordion((prev) => (prev.includes(index)
34+
? prev.filter((i) => i !== index)
35+
: [...prev, index]));
36+
}, []);
37+
38+
return (
39+
<nav className={styles.nav}>
40+
{navigationItem.map((item, index) => {
41+
const isOpen = openAccordion.includes(index);
42+
return (
43+
<div
44+
key={item.title}
45+
className={_cs(
46+
styles[item.variant ?? 'leaf'],
47+
)}
48+
>
49+
<Button
50+
name={index}
51+
type="button"
52+
className={styles.navHeaderContainer}
53+
childrenContainerClassName={styles.navHeader}
54+
onClick={toggleAccordion}
55+
variant="tertiary"
56+
icons={item.icon}
57+
>
58+
{item.title}
59+
{isOpen ? <IoChevronUpOutline /> : <IoChevronDownOutline />}
60+
</Button>
61+
{isOpen && (
62+
<div
63+
className={_cs(
64+
styles.navContent,
65+
styles[item.variant ?? 'leaf'],
66+
)}
67+
>
68+
{item.children && item.children.map((child) => {
69+
if (!child.to && child.children) {
70+
return (
71+
<Navigation
72+
key={child.title}
73+
navigationItem={[child]}
74+
/>
75+
);
76+
}
77+
return (
78+
<NavLink
79+
key={child.to}
80+
to={child.to ?? ''}
81+
className={({ isActive }) => _cs(
82+
styles.routeLink,
83+
isActive && styles.activeRoute,
84+
styles[child.variant ?? 'leaf'],
85+
86+
)}
87+
>
88+
{child.title}
89+
</NavLink>
90+
);
91+
})}
92+
93+
</div>
94+
)}
95+
</div>
96+
);
97+
})}
98+
</nav>
99+
);
100+
}
101+
102+
export default Navigation;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.nav {
2+
border-right: 2px solid var(--go-ui-color-gray-20);
3+
height: 100%;
4+
.nav {
5+
border: none;
6+
}
7+
.navHeaderContainer {
8+
border-bottom: 2px solid var(--go-ui-color-gray-20);
9+
background-color: var(--go-ui-color-gray-10);
10+
padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-2xl);
11+
width: 100%;
12+
.navHeader {
13+
display: flex;
14+
justify-content: space-between;
15+
}
16+
}
17+
.navContent {
18+
display: flex;
19+
flex-direction: column;
20+
.routeLink {
21+
border-bottom: 2px solid var(--go-ui-color-gray-20);
22+
&:hover {
23+
color: var(--go-ui-color-primary-red);
24+
}
25+
padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-2xl);
26+
}
27+
28+
.activeRoute {
29+
text-decoration: underline;
30+
color: var(--go-ui-color-primary-red);
31+
}
32+
}
33+
.group {
34+
.navHeaderContainer {
35+
background: none;
36+
font-weight: var(--go-ui-font-weight-normal);
37+
}
38+
.leaf {
39+
padding-left: var(--go-ui-spacing-3xl);
40+
}
41+
}
42+
}

app/components/Page/index.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { RefObject } from 'react';
2+
import { _cs } from '@togglecorp/fujs';
3+
4+
import styles from './styles.module.css';
5+
6+
interface Props {
7+
className?: string;
8+
children?: React.ReactNode;
9+
elementRef?: RefObject<HTMLDivElement>
10+
leftPaneContent?: React.ReactNode;
11+
leftPaneContainerClassName?: string;
12+
}
13+
function Page(props: Props) {
14+
const {
15+
className,
16+
children,
17+
elementRef,
18+
leftPaneContent,
19+
leftPaneContainerClassName,
20+
} = props;
21+
22+
return (
23+
<div className={_cs(className, styles.page)} ref={elementRef}>
24+
{leftPaneContent && (
25+
<div className={_cs(leftPaneContainerClassName, styles.leftPane)}>
26+
{leftPaneContent}
27+
</div>
28+
)}
29+
{children}
30+
</div>
31+
);
32+
}
33+
34+
export default Page;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.page {
2+
display: flex;
3+
flex-direction: row;
4+
flex-grow: 1;
5+
overflow: hidden;
6+
7+
.leftPane {
8+
display: flex;
9+
flex-direction: column;
10+
background-color: var(--go-ui-color-white);
11+
gap: var(--go-ui-spacing-md);
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import styles from './styles.module.css';
2+
3+
interface Props {
4+
children?: React.ReactNode;
5+
}
6+
7+
function PreloadMessage(props: Props) {
8+
const { children } = props;
9+
10+
return (
11+
<div className={styles.preloadMessage}>
12+
{children}
13+
</div>
14+
);
15+
}
16+
17+
export default PreloadMessage;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.preload-message {
2+
display: flex;
3+
align-items: center;
4+
flex-direction: column;
5+
flex-grow: 1;
6+
justify-content: center;
7+
background-color: var(--go-ui-color-background);
8+
height: 100vh;
9+
text-align: center;
10+
font-size: var(--go-ui-font-size-lg);
11+
}

0 commit comments

Comments
 (0)