Skip to content

Commit

Permalink
add: Navbar, react-router-dom, AuthGuard and Login, Logout, User pages (
Browse files Browse the repository at this point in the history
#16)

* add: Navbar and AuthGuard

* add: react router, login, logout and user pages, auth guard

* update readme + minor changes

* fix: lints
  • Loading branch information
ahmedcognite authored Jan 15, 2024
1 parent e417b64 commit 6fbba74
Show file tree
Hide file tree
Showing 24 changed files with 288 additions and 32 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ List of things done so far
- [x] Added locize integration
- [x] Added github action to verify lint, test & build of affected nx projects
- [x] Added Api class in sdk lib to provide an interfaace for fetching
- [x] Added sequential/paginated (load more) fetching of data with react-query
- [x] Added sequential (load more) fetching of data with react-query
- [x] Added react-router-dom and auth guard
1 change: 1 addition & 0 deletions apps/asdf/src/app.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.app-container {
padding: 16px;
width: 840px;
max-width: 1040px;
margin: 0 auto;
Expand Down
60 changes: 39 additions & 21 deletions apps/asdf/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
import styles from './app.module.scss';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { useTranslation } from 'react-i18next';
// import styles from './app.module.scss';

import { Button } from '@asdf/ui';
// import { useTranslation } from 'react-i18next';
import { ApiProvider } from './providers';
import { Posts } from './components';
import { QueryClientProvider } from './providers/ReactQueryProvider';
import { Navbar } from './components/Navbar/Navbar';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { AuthGuard } from './components/AuthGuard/AuthGuard';
import { Login } from './pages/Login/Login';
import { Logout } from './pages/Logout/Logout';
import { User } from './pages/User/User';

export function App() {
const {
t,
i18n: { changeLanguage, language },
} = useTranslation();
// const {
// t,
// i18n: { changeLanguage, language },
// } = useTranslation();

return (
<ApiProvider>
<div className={styles['app-container']}>
<Button
onClick={() =>
language === 'en' ? changeLanguage('nb') : changeLanguage('en')
}
>
{t('generic_button_text')}
</Button>
<QueryClientProvider>
<Posts />
</QueryClientProvider>
</div>
<QueryClientProvider>
<BrowserRouter basename="/">
<div className={styles['app-container']}>
<Navbar />
<Routes>
<Route path="/" element={<Posts />} />
<Route path="/login" element={<Login />} />
<Route
path="/logout"
element={
<AuthGuard redirectTo="/">
<Logout />
</AuthGuard>
}
/>
<Route
path="/user"
element={
<AuthGuard redirectTo="/">
<User />
</AuthGuard>
}
/>
</Routes>
<Routes></Routes>
</div>
</BrowserRouter>
</QueryClientProvider>
</ApiProvider>
);
}
Expand Down
28 changes: 28 additions & 0 deletions apps/asdf/src/components/AuthGuard/AuthGuard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useNavigate } from 'react-router-dom';
import { ApiContext } from '../../providers';
import { PropsWithChildren, useContext, useEffect } from 'react';

export type AuthGuardProps = PropsWithChildren & {
redirectTo?: string;
fallback?: React.ReactNode;
};

export const AuthGuard: React.FC<AuthGuardProps> = ({
children,
redirectTo,
fallback,
}) => {
const { api } = useContext(ApiContext);
const navigate = useNavigate();

useEffect(() => {
if (api !== null && api.getUser() === null && redirectTo !== undefined)
navigate(redirectTo);
}, [api, navigate, redirectTo]);

if (api !== null && api.getUser() === null && fallback) return fallback;

if (api !== null && api.getUser() === null) return null;

return children;
};
6 changes: 6 additions & 0 deletions apps/asdf/src/components/Navbar/Navbar.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.navbar {
display: flex;
gap: 16px;
flex-direction: column;
margin-bottom: 16px;
}
25 changes: 25 additions & 0 deletions apps/asdf/src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Link } from 'react-router-dom';
import styles from './Navbar.module.scss';
import { AuthGuard } from '../AuthGuard/AuthGuard';

export const Navbar: React.FC = () => {
return (
<nav className={styles.navbar}>
<ul>
<li>
<Link to={'/'}>Home</Link>
</li>
<li>
<AuthGuard fallback={<Link to={'/login'}>Login</Link>}>
<Link to={'/user'}>User</Link>
</AuthGuard>
</li>
<li>
<AuthGuard>
<Link to={'/logout'}>Logout</Link>
</AuthGuard>
</li>
</ul>
</nav>
);
};
3 changes: 3 additions & 0 deletions apps/asdf/src/components/Navbar/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PropsWithChildren } from 'react';

export type NavbarProps = PropsWithChildren;
2 changes: 1 addition & 1 deletion apps/asdf/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './Posts/Posts';
export * from '../pages/Posts/Posts';
9 changes: 5 additions & 4 deletions apps/asdf/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StrictMode } from 'react';
// import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';

import App from './app';
Expand All @@ -12,7 +12,8 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<StrictMode>
<App />
</StrictMode>
// <StrictMode>
// <App />
// </StrictMode>
<App />
);
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/Login/Login.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.login-page {
margin: 0 auto;
}
77 changes: 77 additions & 0 deletions apps/asdf/src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import styles from './Login.module.scss';
import { useContext, useEffect, useState } from 'react';
import { ApiContext } from '../../providers';
import { LoginProps } from './types';
import { Button } from '@asdf/ui';
import { useNavigate } from 'react-router-dom';

export const Login: React.FC<LoginProps> = () => {
const { api } = useContext(ApiContext);
const navigate = useNavigate();
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [error, setError] = useState('');

useEffect(() => {
if (api !== null && api.getUser() !== null) navigate('/');
}, [api, navigate]);

if (api === null) return <p>Loading...</p>;

return (
<div className={styles['login-page']}>
<form
onSubmit={(e) => {
e.preventDefault();
if (isAuthenticating || api === null) return;
setIsAuthenticating(true);

api
.login({
username: e.currentTarget.username.value,
password: e.currentTarget.password.value,
})
.then((user) => {
if (user !== null) navigate('/');
})
.catch((err) => {
setError(err.message);
})
.finally(() => {
setIsAuthenticating(false);
});
}}
>
<h2>Login</h2>
<p>
<pre style={{ padding: 0 }}>
Username: kminchelle
<br />
Password: 0lelplR
</pre>
</p>
<label>
Username
<input
type="text"
name="username"
style={{ display: 'block', width: '100%' }}
defaultValue={'kminchelle'}
/>
</label>
<label>
Password
<input
type="password"
name="password"
style={{ display: 'block', width: '100%' }}
defaultValue={'0lelplR'}
/>
</label>
<Button type="submit" fullWidth disabled={isAuthenticating}>
{isAuthenticating ? 'Logging in...' : 'Login'}
</Button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</form>
</div>
);
};
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/Login/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PropsWithChildren } from 'react';

export type LoginProps = PropsWithChildren;
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/Logout/Logout.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.logout-page {
margin: 0 auto;
}
23 changes: 23 additions & 0 deletions apps/asdf/src/pages/Logout/Logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styles from './Logout.module.scss';
import { useContext, useEffect } from 'react';
import { ApiContext } from '../../providers';
import { LogoutProps } from './types';
import { useNavigate } from 'react-router-dom';

export const Logout: React.FC<LogoutProps> = () => {
const { api } = useContext(ApiContext);
const navigate = useNavigate();

useEffect(() => {
api?.logout();
navigate('/');
}, [api, navigate]);

if (api === null) return <p>Loading...</p>;

return (
<div className={styles['logout-page']}>
<p>Logging out...</p>
</div>
);
};
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/Logout/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PropsWithChildren } from 'react';

export type LogoutProps = PropsWithChildren;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.posts-container {
display: flex;
gap: 16px;
padding: 16px;
flex-direction: column;

> article {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Posts: React.FC = () => {
const [isAllFetched, setIsAllFetched] = useState(false);
const [posts, setPosts] = useState<PostType[]>([]);

const { data, error, isLoading } = useQuery({
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ['posts', pageSize, currentPage],
queryFn: () => api?.getAllPosts({ pageSize, pageNumber: currentPage }),
placeholderData: keepPreviousData,
Expand Down Expand Up @@ -51,8 +51,9 @@ export const Posts: React.FC = () => {
onClick={() => {
if (!isAllFetched) setCurrentPage((prev) => prev + 1);
}}
disabled={isFetching}
>
Load more
{isFetching ? 'Loading...' : 'Load more'}
</Button>
) : (
<p>All posts have been fetched.</p>
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/User/User.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.user-page {
margin: 0 auto;
}
33 changes: 33 additions & 0 deletions apps/asdf/src/pages/User/User.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styles from './User.module.scss';
import { useContext } from 'react';
import { ApiContext } from '../../providers';
import { UserProps } from './types';

export const User: React.FC<UserProps> = () => {
const { api } = useContext(ApiContext);

if (api === null) return <p>Loading...</p>;

return (
<div className={styles['user-page']}>
<pre
style={{
wordWrap: 'break-word',
// whiteSpace: 'break-spaces',
wordBreak: 'break-all',
lineHeight: '1.8',
letterSpacing: '0.01rem',
margin: 0,
padding: 0,
}}
>
<code>
<br />
{JSON.stringify(api.getUser(), null, 4)}
<br />
<br />
</code>
</pre>
</div>
);
};
3 changes: 3 additions & 0 deletions apps/asdf/src/pages/User/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PropsWithChildren } from 'react';

export type UserProps = PropsWithChildren;
Loading

0 comments on commit 6fbba74

Please sign in to comment.