mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 21:33:10 +01:00
Auth route and login form. Client error handler
This commit is contained in:
88
client/src/components/Auth/AuthForm.tsx
Normal file
88
client/src/components/Auth/AuthForm.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { ChangeEvent, FormEvent, useState, useContext, Fragment } from 'react';
|
||||||
|
import { AuthContext } from '../../store';
|
||||||
|
import { Button } from '../UI';
|
||||||
|
|
||||||
|
export const AuthForm = (): JSX.Element => {
|
||||||
|
const [isInLogin, setIsInLogin] = useState(true);
|
||||||
|
const [formData, setFormData] = useState({ email: '', password: '' });
|
||||||
|
const { login } = useContext(AuthContext);
|
||||||
|
|
||||||
|
const inputHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[name]: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formHandler = (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (isInLogin) {
|
||||||
|
login(formData);
|
||||||
|
} else {
|
||||||
|
// register
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h5 className='card-title'>{isInLogin ? 'Login' : 'Register'}</h5>
|
||||||
|
<p className=''>
|
||||||
|
{isInLogin ? "Don't have an account yet?" : 'Already a user?'}
|
||||||
|
<span
|
||||||
|
onClick={() => setIsInLogin(!isInLogin)}
|
||||||
|
className='text-success cursor-pointer'
|
||||||
|
>
|
||||||
|
{isInLogin ? ' Sign Up' : ' Login'}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form onSubmit={e => formHandler(e)}>
|
||||||
|
<div className='mb-3'>
|
||||||
|
<label htmlFor='email' className='form-label'>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='email'
|
||||||
|
className='form-control'
|
||||||
|
id='email'
|
||||||
|
name='email'
|
||||||
|
placeholder='john@doe.com'
|
||||||
|
required
|
||||||
|
autoComplete='email'
|
||||||
|
value={formData.email}
|
||||||
|
onChange={e => inputHandler(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='mb-3'>
|
||||||
|
<label htmlFor='password' className='form-label'>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type='password'
|
||||||
|
className='form-control'
|
||||||
|
id='password'
|
||||||
|
name='password'
|
||||||
|
placeholder='••••••'
|
||||||
|
required
|
||||||
|
autoComplete='current-password'
|
||||||
|
value={formData.password}
|
||||||
|
onChange={e => inputHandler(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<div className='d-grid gap-2'>
|
||||||
|
<Button
|
||||||
|
text={isInLogin ? 'Login' : 'Register'}
|
||||||
|
color='secondary'
|
||||||
|
type='submit'
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
client/src/components/Auth/index.ts
Normal file
1
client/src/components/Auth/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './AuthForm';
|
||||||
@@ -14,6 +14,11 @@
|
|||||||
"name": "Editor",
|
"name": "Editor",
|
||||||
"dest": "/editor",
|
"dest": "/editor",
|
||||||
"isPublic": false
|
"isPublic": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Auth",
|
||||||
|
"dest": "/auth",
|
||||||
|
"isPublic": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
14
client/src/containers/Auth.tsx
Normal file
14
client/src/containers/Auth.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { AuthForm } from '../components/Auth';
|
||||||
|
import { Card, Layout } from '../components/UI';
|
||||||
|
|
||||||
|
export const Auth = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className='col-12 col-md-6 mx-auto'>
|
||||||
|
<Card>
|
||||||
|
<AuthForm />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,3 +2,4 @@ export * from './Home';
|
|||||||
export * from './Snippet';
|
export * from './Snippet';
|
||||||
export * from './Snippets';
|
export * from './Snippets';
|
||||||
export * from './Editor';
|
export * from './Editor';
|
||||||
|
export * from './Auth';
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useState, createContext, ReactNode } from 'react';
|
import axios from 'axios';
|
||||||
|
import { createContext, ReactNode } from 'react';
|
||||||
|
|
||||||
import { AuthContext as Context } from '../typescript/interfaces';
|
import { AuthContext as Context, Response } from '../typescript/interfaces';
|
||||||
|
import { errorHandler } from '../utils';
|
||||||
|
|
||||||
export const AuthContext = createContext<Context>({
|
export const AuthContext = createContext<Context>({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
@@ -13,7 +15,19 @@ interface Props {
|
|||||||
|
|
||||||
export const AuthContextProvider = (props: Props): JSX.Element => {
|
export const AuthContextProvider = (props: Props): JSX.Element => {
|
||||||
const login = async (formData: { email: string; password: string }) => {
|
const login = async (formData: { email: string; password: string }) => {
|
||||||
console.table(formData);
|
try {
|
||||||
|
const res = await axios.post<Response<{ token: string }>>(
|
||||||
|
'/api/auth/login',
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
localStorage.setItem('token', res.data.data.token);
|
||||||
|
|
||||||
|
// get profile
|
||||||
|
// redirect to snippets? / home?
|
||||||
|
} catch (err) {
|
||||||
|
errorHandler(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const context: Context = {
|
const context: Context = {
|
||||||
|
|||||||
5
client/src/typescript/interfaces/User.ts
Normal file
5
client/src/typescript/interfaces/User.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Model } from '.';
|
||||||
|
|
||||||
|
export interface User extends Model {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
@@ -5,3 +5,4 @@ export * from './Response';
|
|||||||
export * from './Context';
|
export * from './Context';
|
||||||
export * from './Statistics';
|
export * from './Statistics';
|
||||||
export * from './SearchQuery';
|
export * from './SearchQuery';
|
||||||
|
export * from './User';
|
||||||
|
|||||||
15
client/src/utils/errorHandler.ts
Normal file
15
client/src/utils/errorHandler.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
export const errorHandler = (err: any) => {
|
||||||
|
const error = err as AxiosError<{ error: string }>;
|
||||||
|
|
||||||
|
let msg: string;
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
msg = error.response.data.error;
|
||||||
|
} else {
|
||||||
|
msg = 'Something went wrong';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(msg);
|
||||||
|
};
|
||||||
@@ -3,3 +3,4 @@ export * from './badgeColor';
|
|||||||
export * from './findLanguage';
|
export * from './findLanguage';
|
||||||
export * from './searchParser';
|
export * from './searchParser';
|
||||||
export * from './ProtectedRoute';
|
export * from './ProtectedRoute';
|
||||||
|
export * from './errorHandler';
|
||||||
|
|||||||
Reference in New Issue
Block a user