Skip to content

Commit

Permalink
Merge pull request #10 from Gin-n-Tonicc/OAuth-google
Browse files Browse the repository at this point in the history
FE - OAuth with Google
  • Loading branch information
stefan-petrov1 committed Apr 21, 2024
2 parents 217622a + 76c8613 commit efff6cc
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 23 deletions.
2 changes: 1 addition & 1 deletion client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

<!-- Template Stylesheet -->
<link href="/css/style.css" rel="stylesheet">
<title>React App</title>
<title>EventEntry</title>

</head>

Expand Down
22 changes: 19 additions & 3 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Admin from './pages/admin/Admin';
import AdminTableDefault from './pages/admin/admin-tables/AdminTableDefault';
import AdminTableSkills from './pages/admin/admin-tables/AdminTableSkills';
import AdminTableUsers from './pages/admin/admin-tables/AdminTableUsers';
import FinishRegister from './pages/auth/finish-register/FinishRegister';
import Login from './pages/auth/login/Login';
import Logout from './pages/auth/logout/Logout';
import Register from './pages/auth/register/Register';
Expand Down Expand Up @@ -51,16 +52,31 @@ function App() {
<Route path={PageEnum.Register} element={<Register />} />
</Route>

{/* Only logged users */}
{/* Only logged users AND finished OAuth2*/}
<Route
element={
<ProtectedRoute role={RoleEnum.USER} onlyAuth={true} />
<ProtectedRoute
role={RoleEnum.USER}
onlyAuth={true}
blockNotFinishedOAuth={true}
/>
}>
<Route path={PageEnum.Chat} element={<Chat />} />
<Route path={PageEnum.Logout} element={<Logout />} />
<Route path={PageEnum.Profile} element={<Profile />} />
</Route>

{/* Only logged users with or without finished OAuth2*/}
<Route
element={
<ProtectedRoute role={RoleEnum.USER} onlyAuth={true} />
}>
<Route
path={PageEnum.FinishRegister}
element={<FinishRegister />}
/>
<Route path={PageEnum.Logout} element={<Logout />} />
</Route>

{/* Only organisations */}
<Route
element={<ProtectedRoute role={RoleEnum.ORGANISATION} />}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import { PageEnum, RoleEnum } from '../../../types';
type ProtectedRouteProps = {
role: RoleEnum | null;
onlyAuth?: boolean;
blockNotFinishedOAuth?: boolean;
};

// The component that protects a route based on the user data
// whether he is logged or not, whether he is a teacher or not, etc.
export default function ProtectedRoute({
role,
onlyAuth,
blockNotFinishedOAuth,
}: ProtectedRouteProps) {
const { user, isOrganisation, isAuthenticated } = useAuthContext();
const { user, isAuthenticated, hasFinishedOAuth2 } = useAuthContext();
const { pathname } = useLocation();

// Attach redirectTo search param
Expand All @@ -38,6 +40,10 @@ export default function ProtectedRoute({
(role === user.role && isAuthenticated) ||
(onlyAuth && isAuthenticated);

if (passThrough && blockNotFinishedOAuth && !hasFinishedOAuth2) {
return <Navigate to={PageEnum.Home} />;
}

if (!user.role && !passThrough && pathname !== PageEnum.Logout) {
return <Navigate to={generateNavPath(PageEnum.Login)} />;
}
Expand Down
15 changes: 10 additions & 5 deletions client/src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ function LoggedNav(props: {
}) {
return (
<>
{!props.hasFinishedOAuth2 && (
<NavItem to={PageEnum.FinishRegister} text="Finish Register" />
)}
<NavItem to={PageEnum.Logout} text="Log out" />
<NavItem
to={PageEnum.Profile.replace(':userId', props.userId.toString())}
text="Profile"
img={profileUserIcon}
/>
{props.hasFinishedOAuth2 && (
<NavItem
to={PageEnum.Profile.replace(':userId', props.userId.toString())}
text="Profile"
img={profileUserIcon}
/>
)}
</>
);
}
Expand Down
274 changes: 274 additions & 0 deletions client/src/pages/auth/finish-register/FinishRegister.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import { useCallback } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { MultiValue } from 'react-select';
import { CachePolicies, useFetch } from 'use-http';
import FormErrorWrapper from '../../../components/form-error-wrapper/FormErrorWrapper';
import FormInput from '../../../components/form-input/FormInput';
import SkillsSelect, {
SkillOption,
} from '../../../components/skills-select/SkillsSelect';
import { OAuthPaths, skillsPaths } from '../../../config/api';
import { useAuthContext } from '../../../contexts/AuthContext';
import { useErrorContext } from '../../../contexts/ErrorContext';
import useValidators from '../../../hooks/useValidator';
import { IAuthResponse, ISkill, PageEnum, RoleEnum } from '../../../types';
import '../styles/Register.scss';

type Inputs = {
'First Name': string;
'Last Name': string;
'Current Workplace': string;
Education: string;
Experience: string;
Address: string;
WICHW: string;
skillsHave: MultiValue<SkillOption>;
skillsNeed: MultiValue<SkillOption>;
role: RoleEnum;
};

function FinishRegister() {
const navigate = useNavigate();
const { user, loginUser } = useAuthContext();
const { auth } = useValidators();
const { addError } = useErrorContext();

const {
handleSubmit,
control,
register,
reset,
watch,
setError,
clearErrors,
setValue,
formState: { errors },
} = useForm<Inputs>({
defaultValues: {
'First Name': user.firstname || '',
'Last Name': '',
'Current Workplace': '',
Education: '',
Experience: '',
Address: '',
WICHW: '',
skillsHave: [],
skillsNeed: [],
role: RoleEnum.USER,
},
mode: 'onChange',
});

const formValues = watch();

const { data: skills } = useFetch<ISkill[]>(
skillsPaths.getAll,
{
cachePolicy: CachePolicies.CACHE_AND_NETWORK,
},
[]
);

const { put, response, loading } = useFetch<IAuthResponse>(
OAuthPaths.completeOAuth
);

const onSubmit: SubmitHandler<Inputs> = async (data) => {
const body = {
firstname: data['First Name'].trim(),
lastname: data['Last Name'].trim(),
currentWorkPlace: data['Current Workplace'].trim(),
education: data.Education.trim(),
workExperience: data.Experience.trim(),
address: data.Address.trim(),
whatCanHelpWith: data.WICHW.trim(),
skills: data.skillsHave.map((x) =>
skills?.find((y) => y.id === Number(x.value))
),
lookingForSkills: data.skillsNeed.map((x) =>
skills?.find((y) => y.id === Number(x.value))
),
role: data.role,
};

const res = await put(body);

if (response.ok) {
reset();
loginUser(res);
navigate(PageEnum.Home);
}
};

const skillsAsSelectOptions =
skills?.map((x) => ({
value: x.id.toString(),
label: x.name,
})) || [];

const onSkillsHaveChange = useCallback(
(val: MultiValue<SkillOption>) => {
setValue('skillsHave', val, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
},
[setValue]
);

const onSkillsNeedChange = useCallback(
(val: MultiValue<SkillOption>) => {
setValue('skillsNeed', val, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
},
[setValue]
);

return (
<div className="row">
<div className="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div className="card border-0 shadow rounded-3 my-5">
<div className="card-body p-4 p-sm-5">
<h5 className="card-title text-center mb-5 fw-light fs-5">
Finish Register
</h5>
<form onSubmit={handleSubmit(onSubmit)}>
<FormInput
control={control}
type="text"
inputClasses="form-control"
name="First Name"
placeholder="John"
labelText="First Name*"
rules={auth.FIRST_NAME_VALIDATIONS}
/>

<FormInput
control={control}
type="text"
inputClasses="form-control"
name="Last Name"
placeholder="Johnson"
labelText="Last Name*"
rules={auth.LAST_NAME_VALIDATIONS}
/>

<FormInput
control={control}
type="text"
inputClasses="form-control"
name="Address"
placeholder="LudogorieSoft, Targovishte 7700, Bulgaria"
labelText="Address*"
rules={auth.ADDRESS_VALIDATIONS}
/>

<FormInput
control={control}
type="text"
inputClasses="form-control"
name="Education"
placeholder="1 SU 'st. Sedmochislenici'"
labelText="Education*"
rules={auth.EDUCATION_VALIDATIONS}
/>

<FormErrorWrapper message={errors.Experience?.message}>
<div className="form-floating">
<textarea
className="form-control"
placeholder="Leave a comment here"
id="floatingTextarea2"
style={{ height: '100px' }}
{...register('Experience', {
...auth.EXPERIENCE_VALIDATIONS,
})}></textarea>
<label htmlFor="floatingTextarea2">Experience*</label>
</div>
</FormErrorWrapper>

<FormErrorWrapper message={errors.WICHW?.message}>
<div className="form-floating">
<textarea
className="form-control"
placeholder="Leave a comment here"
id="floatingTextarea2"
style={{ height: '100px' }}
{...register('WICHW', {
...auth.WICHW_VALIDATIONS,
})}></textarea>
<label htmlFor="floatingTextarea2">
What I can help with*
</label>
</div>
</FormErrorWrapper>

<FormInput
control={control}
type="text"
inputClasses="form-control"
name="Current Workplace"
placeholder="LudogorieSoft, Targovishte 7700, Bulgaria"
labelText="Workplace"
/>

<FormErrorWrapper message={undefined}>
<SkillsSelect
options={skillsAsSelectOptions}
placeholder={'Select what skills you HAVE...'}
onChange={onSkillsHaveChange}
/>
</FormErrorWrapper>
<FormErrorWrapper message={undefined}>
<SkillsSelect
options={skillsAsSelectOptions}
placeholder={'Select what skills you NEED...'}
onChange={onSkillsNeedChange}
/>
</FormErrorWrapper>

<FormErrorWrapper message={undefined}>
<div className="d-flex justify-content-between align-items-center">
<div className="d-flex justify-content-center align-items-center gap-2">
<input
{...register('role')}
type="radio"
className="form-check-input m-0"
value={RoleEnum.USER}
/>
<p className="m-0">User</p>
</div>
<div className="d-flex justify-content-center align-items-center gap-2">
<p className="m-0">Organisation</p>
<input
{...register('role')}
type="radio"
className="form-check-input m-0"
value={RoleEnum.ORGANISATION}
/>
</div>
</div>
</FormErrorWrapper>

<div className="d-grid">
<button
disabled={loading}
className="btn btn-primary btn-login text-uppercase fw-bold"
type="submit">
Finish Register
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
}

export default FinishRegister;
Loading

0 comments on commit efff6cc

Please sign in to comment.