Commit 55f24436 authored by Birahavi Kugathasan's avatar Birahavi Kugathasan

resume analysis

parent 9b30774e
......@@ -11,8 +11,10 @@
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"axios": "^1.3.4",
"firebase": "^9.17.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-modal": "^3.16.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.4.5",
"react-scripts": "5.0.1",
......@@ -48,6 +50,7 @@
]
},
"devDependencies": {
"@types/react-modal": "^3.13.1",
"sass": "^1.56.2"
},
"proxy": "http://localhost:5000"
......
......@@ -26,7 +26,7 @@
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"
></script>
<title>React App</title>
<title>Smart Recruiter</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
......
......@@ -4,6 +4,7 @@ import { Routes, BrowserRouter as Router, Route } from "react-router-dom";
import Landing from "./views/Landing.view";
import Home from "./views/Home.view";
import Settings from "./views/Settings.view";
import Applications from "./views/Applications.view";
import AppState from "./components/AppState";
......@@ -19,6 +20,7 @@ function App() {
<Route element={<ProtectedRoutes />}>
<Route path="/home" element={<Home />} />
<Route path="/settings" element={<Settings />} />
<Route path="/applications" element={<Applications />} />
</Route>
</Routes>
<AppState />
......
import { ACTIONS } from ".";
import { ControlsType, Reducers, UpdatePasswordPayload } from "../types";
import {
ControlsType,
Reducers,
SignInPayload,
UpdatePasswordPayload,
} from "../types";
export const setAppState = (payload: Reducers["auth"]["appState"]) => ({
type: ACTIONS.SET_APP_STATE,
......@@ -15,3 +20,12 @@ export const changePassword = (payload: UpdatePasswordPayload) => ({
type: ACTIONS.CHANGE_PASSWORD,
payload,
});
export const signIn = (
payload: SignInPayload,
success: (state: string) => void
) => ({
type: ACTIONS.SIGN_IN,
payload,
success,
});
import { ACTIONS } from ".";
import { JobType } from "../types";
export const getJobs = (success?: () => void) => ({
type: ACTIONS.GET_JOBS,
success,
});
export const createJob = (payload: JobType, success?: () => void) => ({
type: ACTIONS.CREATE_JOB,
payload,
success,
});
export const updateJob = (payload: JobType, success?: () => void) => ({
type: ACTIONS.UPDATE_JOB,
payload,
success,
});
export enum ACTIONS {
SET_AUTH = "SET_AUTH",
SIGN_IN = "SIGN_IN",
SIGN_OUT_USER = "SIGN_OUT_USER",
UPDATE_KEYSTROKE_SETTINGS = "UPDATE_KEYSTROKE_SETTINGS",
CHANGE_PASSWORD = "CHANGE_PASSWORD",
SET_USER_TYPE = "SET_USER_TYPE",
UPDATE_CANDIDATE = "UPDATE_CANDIDATE",
APPLY_FOR_JOB = "APPLY_FOR_JOB",
SET_APP_STATE = "SET_APP_STATE",
SET_JOBS = "SET_JOBS",
GET_JOBS = "GET_JOBS",
CREATE_JOB = "CREATE_JOB",
UPDATE_JOB = "UPDATE_JOB",
}
import { ACTIONS } from ".";
import { CandidateType } from "../types";
export const updateCandidate = (payload: Partial<CandidateType>) => ({
type: ACTIONS.UPDATE_CANDIDATE,
payload,
});
export const applyForJob = (payload: string) => ({
type: ACTIONS.APPLY_FOR_JOB,
payload,
});
import { request } from "../lib/api";
import { ApplicationType, JobType } from "../types";
export default class CommonAPI {
static getJobs = () => request("<BASE_URL>/jobs", "GET");
static createJob = (payload: JobType) =>
request("<BASE_URL>/jobs", "POST", payload);
static updateJob = (payload: JobType) =>
request("<BASE_URL>/jobs", "PUT", payload);
static applyForJob = (payload: {
application: ApplicationType;
resumeUrl: string;
}) => request("<BASE_URL>/jobs/apply", "PUT", payload);
}
import { request } from "../lib/api";
import { CandidateType } from "../types";
export default class UserAPI {
static updateCandidate = (payload: Partial<CandidateType>) =>
request("<BASE_URL>/user/candidate", "POST", payload);
}
import { initializeApp } from "firebase/app";
import { getStorage, ref } from "firebase/storage";
export const BASE_URL = "http://localhost:5000";
export const DEFAULT_CONTROLS = {
standard: {
......@@ -10,3 +13,14 @@ export const DEFAULT_CONTROLS = {
use: true,
},
};
const FIREBASE_CONFIG = {
apiKey: "AIzaSyAVE9S4CZfUpUcib8DczFuzTrlWOQcqk80",
authDomain: "smart-recruiter-909f6.firebaseapp.com",
projectId: "smart-recruiter-909f6",
storageBucket: "smart-recruiter-909f6.appspot.com",
messagingSenderId: "512045119582",
appId: "1:512045119582:web:8751af70c3d75da0295ccf",
};
const app = initializeApp(FIREBASE_CONFIG);
export const fileStorage = getStorage(app);
import {
ApplicationType,
CandidateType,
OrganizationType,
Reducers,
USER_TYPE,
} from "../types";
export const logger = (log: any, ...optionalparams: any[]) => {
console.log(log, ...optionalparams);
};
......@@ -28,3 +36,30 @@ export function keyEvent(e: any) {
time: Date.now(),
};
}
export const getProfile = (
state: Reducers
): CandidateType | OrganizationType | {} => {
if (state.auth.userType === USER_TYPE.CANDIDATE) {
return state.auth.candidate;
}
return state.auth.organization;
};
export const getUserId = (state: Reducers): string => {
const profile: any = getProfile(state);
return profile._id as string;
};
export const getStatusColor = (status?: ApplicationType["status"]) => {
const color =
status === "Accepted"
? "text-bg-success"
: status === "Pending"
? "text-bg-warning"
: status === "Rejected"
? "text-bg-danger"
: "text-bg-secondary";
return color;
};
......@@ -21,6 +21,9 @@ const authReducer = (
case ACTIONS.SET_APP_STATE:
return { ...state, appState: payload };
case ACTIONS.SET_USER_TYPE:
return { ...state, userType: payload };
case ACTIONS.SIGN_OUT_USER:
return { ...state, token: null };
......
import { ACTIONS } from "../actions/index";
import { CommonReducer } from "../types";
const INITIAL_STATE: CommonReducer = {
jobs: [],
};
const commonReducer = (
state = INITIAL_STATE,
{ type, payload }: { type: ACTIONS; payload: any }
): CommonReducer => {
switch (type) {
case ACTIONS.SET_JOBS:
return { ...state, jobs: payload };
case ACTIONS.SIGN_OUT_USER:
return INITIAL_STATE;
default:
return state;
}
};
export default commonReducer;
......@@ -3,12 +3,19 @@ import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import authReducer from "../reducers/auth";
import commonReducer from "../reducers/common";
const AuthPersistConfig = {
storage,
key: "auth",
};
const CommonPersistConfig = {
storage,
key: "common",
};
export default combineReducers({
auth: persistReducer(AuthPersistConfig, authReducer),
common: persistReducer(CommonPersistConfig, commonReducer),
});
......@@ -6,9 +6,35 @@ import {
ControlsType,
KeystrokeResultType,
Reducers,
SignInPayload,
UpdatePasswordPayload,
USER_TYPE,
} from "../types";
function* signIn({
payload,
success,
}: {
type: typeof ACTIONS.SIGN_IN;
payload: SignInPayload;
success: (state: string) => void;
}) {
try {
const data: { user: any } = yield call(AuthAPI.signin, payload);
yield put({ type: ACTIONS.SET_USER_TYPE, payload: payload.userType });
const auth = {
...data,
organization:
payload.userType === USER_TYPE.ORGANIZATION ? data.user : {},
candidate: payload.userType === USER_TYPE.CANDIDATE ? data.user : {},
};
yield put({ type: ACTIONS.SET_AUTH, payload: auth });
success(data.user.state);
} catch (error) {
success("failed");
}
}
function* updateKeystrokeSettings({
payload,
}: {
......@@ -35,7 +61,7 @@ function* updateKeystrokeSettings({
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED, msg: error },
payload: { state: APP_STATE.FAILED },
});
}
}
......@@ -59,12 +85,13 @@ function* changePassword({
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED, msg: error },
payload: { state: APP_STATE.FAILED },
});
}
}
export default function* authSaga() {
yield takeLeading(ACTIONS.SIGN_IN, signIn);
yield takeLeading(ACTIONS.UPDATE_KEYSTROKE_SETTINGS, updateKeystrokeSettings);
yield takeLeading(ACTIONS.CHANGE_PASSWORD, changePassword);
}
import { takeLeading, call, put, select } from "redux-saga/effects";
import { ACTIONS } from "../actions";
import CommonAPI from "../apis/common";
import { APP_STATE, JobType, Reducers } from "../types";
function* getJobs({
success,
}: {
type: typeof ACTIONS.GET_JOBS;
success: () => void;
}) {
try {
const data: { jobs: JobType; success: boolean } = yield call(
CommonAPI.getJobs
);
if (data.success) {
yield put({ type: ACTIONS.SET_JOBS, payload: data.jobs });
}
success();
} catch (error) {
success();
}
}
function* createJob({
payload,
success,
}: {
type: typeof ACTIONS.CREATE_JOB;
payload: JobType;
success: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { success: boolean; job: JobType } = yield call(
CommonAPI.createJob,
payload
);
if (data.success) {
const jobs: JobType[] = yield select(
(state: Reducers) => state.common.jobs
);
yield put({ type: ACTIONS.SET_JOBS, payload: [data.job, ...jobs] });
}
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
success();
} catch (error) {
success();
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
}
}
function* updateJob({
payload,
success,
}: {
type: typeof ACTIONS.UPDATE_JOB;
payload: JobType;
success: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { success: boolean } = yield call(CommonAPI.updateJob, payload);
if (data.success) {
let jobs: JobType[] = yield select(
(state: Reducers) => state.common.jobs
);
const found = jobs.findIndex((_job) => _job._id === payload._id);
jobs[found] = { ...jobs[found], ...payload };
yield put({ type: ACTIONS.SET_JOBS, payload: [...jobs] });
}
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
success();
} catch (error) {
success();
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
}
}
export default function* authSaga() {
yield takeLeading(ACTIONS.GET_JOBS, getJobs);
yield takeLeading(ACTIONS.CREATE_JOB, createJob);
yield takeLeading(ACTIONS.UPDATE_JOB, updateJob);
}
import { all } from "redux-saga/effects";
import Auth from "./auth";
import User from "./user";
import Common from "./common";
export default function* rootSaga() {
yield all([Auth()]);
yield all([Auth(), User(), Common()]);
}
import { put, takeLeading, call, select } from "redux-saga/effects";
import { ACTIONS } from "../actions";
import CommonAPI from "../apis/common";
import UserAPI from "../apis/user";
import { getProfile } from "../lib/util";
import { APP_STATE, CandidateType, JobType, Reducers } from "../types";
function* updateCandidate({
payload,
}: {
type: typeof ACTIONS.UPDATE_CANDIDATE;
payload: Partial<CandidateType>;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
yield call(UserAPI.updateCandidate, payload);
const candidate: CandidateType = yield select(getProfile);
yield put({
type: ACTIONS.SET_AUTH,
payload: { candidate: { ...candidate, ...payload } },
});
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
}
}
function* applyForJob({
payload,
}: {
type: typeof ACTIONS.APPLY_FOR_JOB;
payload: string;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const profile: CandidateType = yield select(getProfile);
if (profile._id && profile.resume) {
const data: { applicationId: string; success: boolean } = yield call(
CommonAPI.applyForJob,
{
application: {
candidate: profile._id,
job: payload,
status: "Pending",
},
resumeUrl: profile.resume,
}
);
if (data.success) {
let jobs: JobType[] = yield select(
(state: Reducers) => state.common.jobs
);
const found = jobs.findIndex((_job) => _job._id === payload);
jobs[found] = {
...jobs[found],
applications: [
...(jobs[found]?.applications || []),
data.applicationId,
],
};
yield put({ type: ACTIONS.SET_JOBS, payload: [...jobs] });
}
}
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
}
}
export default function* authSaga() {
yield takeLeading(ACTIONS.UPDATE_CANDIDATE, updateCandidate);
yield takeLeading(ACTIONS.APPLY_FOR_JOB, applyForJob);
}
......@@ -93,6 +93,8 @@ export type CandidateType = {
dateOfBirth: string;
jobIds: string[];
profilePicture: string;
state: "INTIAL" | "READY";
resume?: string;
};
export type OrganizationType = {
......@@ -120,6 +122,33 @@ export type ControlsType = {
};
};
export type JobType = {
_id?: string;
title: string;
description: string;
primarySkills: string[];
secondarySkills: string[];
salary: {
min: number;
max: number;
currency: string;
};
applications?: string[];
organization: string;
};
export type ApplicationType = {
candidate: string;
job: string;
status: "Pending" | "Accepted" | "In progress" | "Rejected";
interview?: {
date: string;
time: string;
link: string;
videoRef?: string;
};
};
//PAYLOADS
export type SignUpPayload = {
passwords: string[];
......@@ -146,6 +175,41 @@ export type UpdatePasswordPayload = {
oldPassword: string;
};
export type JobPayloadType = {
_id?: string;
title: string;
description: string;
primarySkills: string[];
secondarySkills: string[];
salary: {
min: number;
max: number;
currency: string;
};
applications?: ApplicationType[] | ApplicationPayloadType[];
organization: OrganizationType[];
};
export type ApplicationPayloadType = {
candidate: CandidateType;
job: string;
status: "Pending" | "Accepted" | "In progress" | "Rejected";
interview?: {
date: string;
time: string;
link: string;
videoRef?: string;
};
score: {
primary: number;
primatyMatch?: string[];
secondary: number;
secondaryMatch?: string[];
similarity: number;
total: number;
};
};
//REDUCERS
export type AuthReducer = {
token: string | null;
......@@ -160,6 +224,11 @@ export type AuthReducer = {
} | null;
};
export type CommonReducer = {
jobs: JobType[] | JobPayloadType[];
};
export type Reducers = {
auth: AuthReducer;
common: CommonReducer;
};
......@@ -44,7 +44,6 @@
align-items: center;
justify-content: center;
color: white;
margin: auto;
&.lg {
height: 100px;
......@@ -132,3 +131,79 @@
);
}
}
.ReactModal__Overlay {
opacity: 0;
transition: all 300ms ease-in-out;
}
.ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModal__Overlay--before-close {
opacity: 0;
}
.skills {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.skill {
background-color: #058700;
padding: 2px 10px 3px 10px;
border-radius: 10px;
color: white;
margin-right: 5px;
margin-top: 1px;
&.secondary {
background-color: #f3d13b;
color: black;
}
&.display {
background-color: #e0dfdf;
color: black;
}
.span-btn {
font-size: 12px;
padding: 1px 0px 1px 6px;
cursor: pointer;
font-weight: bold;
}
}
.job-card,
.job-preview {
cursor: pointer;
h6,
h5 {
margin: 0;
}
label {
a {
color: gray;
}
}
.desc {
font-size: 12px;
line-height: 15px;
color: gray;
}