video conferencing online

parent 40aaf3c7
......@@ -26,6 +26,14 @@
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"
></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<title>Smart Recruiter</title>
</head>
<body>
......
......@@ -14,6 +14,8 @@ import "./components.scss";
import ProtectedRoutes from "./components/ProtectedRoute";
import ApplicationsLayout from "./Layouts/ApplicationLayout";
import Applicant from "./views/Application";
import MeetingRoom from "./views/MeetingRoom";
import Organization from "./views/Organization.view";
function App() {
return (
......@@ -23,9 +25,11 @@ function App() {
<Route element={<ProtectedRoutes />}>
<Route path="/home" element={<Home />} />
<Route path="/settings" element={<Settings />} />
<Route path="/organizations" element={<Organization />} />
<Route element={<ApplicationsLayout />}>
<Route path="/applications" element={<Applications />} />
<Route path="/applicant" element={<Applicant />} />
<Route path="/meeting" element={<MeetingRoom />} />
</Route>
</Route>
</Routes>
......
......@@ -5,14 +5,18 @@ import NavBar from "../components/NavBar";
type OwnProps = {
title: string;
children?: JSX.Element | JSX.Element[];
suffix?: JSX.Element;
};
const Layout = ({ title, children }: OwnProps) => {
const Layout = ({ title, children, suffix }: OwnProps) => {
return (
<>
<NavBar />
<div className="container pb-5">
<div className="layout-title-container">
<h4 className="mb-3">{title}</h4>
{suffix}
</div>
{children}
</div>
</>
......
......@@ -44,3 +44,14 @@ p {
margin-top: 10px;
}
}
.layout-title-container {
display: flex;
flex-direction: row;
justify-content: space-between;
h4,
div {
flex: 1;
}
margin-bottom: 10px;
}
import { ACTIONS } from ".";
import { ApplicationPayloadType } from "../types";
export const updateApplication = (
payload: {
candidateId: string;
applicationId: string;
update: any;
},
success?: () => void
) => ({
type: ACTIONS.UPDATE_APPLICATION,
payload,
success,
});
export const updateApplicationAction = (
payload: {
applicationId: string;
update: any;
},
success?: () => void
) => ({
type: ACTIONS.UPDATE_APPLICATION_ACTION,
payload,
success,
});
export const createMeeting = (
payload: ApplicationPayloadType,
success?: (roomId: string | null) => void
) => ({
type: ACTIONS.CREATE_MEETING,
payload,
success,
});
export const endMeeting = (
payload: { application: ApplicationPayloadType; roomId: string },
success?: () => void
) => ({
type: ACTIONS.END_MEETING,
payload,
success,
});
......@@ -18,19 +18,6 @@ export const updateJob = (payload: JobType, success?: () => void) => ({
success,
});
export const updateApplication = (
payload: {
candidateId: string;
applicationId: string;
update: any;
},
success?: () => void
) => ({
type: ACTIONS.UPDATE_APPLICATION,
payload,
success,
});
export const setDialogBox = (payload: string | null) => ({
type: ACTIONS.SET_DIALOG,
payload,
......
......@@ -11,7 +11,6 @@ export enum ACTIONS {
UPDATE_ORG = "UPDATE_ORG",
SET_APP_STATE = "SET_APP_STATE",
SET_JOBS = "SET_JOBS",
......@@ -19,5 +18,9 @@ export enum ACTIONS {
CREATE_JOB = "CREATE_JOB",
UPDATE_JOB = "UPDATE_JOB",
UPDATE_APPLICATION = "UPDATE_APPLICATION",
UPDATE_APPLICATION_ACTION = "UPDATE_APPLICATION_ACTION",
SET_DIALOG = "SET_DIALOG",
CREATE_MEETING = "CREATE_MEETING",
END_MEETING = "END_MEETING",
}
......@@ -18,12 +18,20 @@ export default class CommonAPI {
static updateApplication = (payload: {
update: any;
applicationId: string;
candidateId: string;
candidateId?: string;
}) => request("<BASE_URL>/applications/update", "PUT", payload);
static updateApplicationAction = (payload: {
update: any;
applicationId: string;
}) => request("<BASE_URL>/applications/update/action", "PUT", payload);
static analyseInterview = (payload: {
startTime: number;
endTime: number;
applicationId: string;
}) => request("<BASE_URL>/applications/analyse", "POST", payload);
static searchJobs = (key: string) =>
request("<BASE_URL>/jobs/search", "GET", { key });
}
import { request } from "../lib/api";
const MEETINGS_URL = "https://api.videosdk.live/v2";
export let meetingToken: string;
export const getToken = async () => {
if (meetingToken) {
return meetingToken;
} else {
try {
const data: { token: string } = await MeetingsAPI.getToken();
meetingToken = data.token;
return data.token;
} catch (error) {}
}
};
export default class MeetingsAPI {
static getToken = (): Promise<any> =>
request("<BASE_URL>/auth/conference/key", "GET", {}, false, undefined);
static createRoom = async (customRoomId: string) =>
request(
`${MEETINGS_URL}/rooms`,
"POST",
{ customRoomId },
false,
undefined,
{ Authorization: await getToken() }
);
static endSession = async (roomId: string) =>
request(
`${MEETINGS_URL}/sessions/end`,
"POST",
{ roomId },
false,
undefined,
{ Authorization: await getToken() }
);
static deactivateRoom = async (roomId: string) =>
request(
`${MEETINGS_URL}/rooms/deactivate`,
"POST",
{ roomId },
false,
undefined,
{ Authorization: await getToken() }
);
static startRecord = async (roomId: string) =>
request(
`${MEETINGS_URL}/recordings/start`,
"POST",
{ roomId },
false,
undefined,
{ Authorization: await getToken() }
);
static getRecordings = async (roomId: string) =>
request(`${MEETINGS_URL}/recordings`, "GET", { roomId }, false, undefined, {
Authorization: await getToken(),
});
}
import { initializeApp } from "firebase/app";
import { getStorage } from "firebase/storage";
export const BASE_URL = "http://localhost:5000";
export const BASE_URL = "http://192.168.8.120:5000";
export const DEFAULT_CONTROLS = {
standard: {
sd: 1.5,
......@@ -55,4 +55,5 @@ export const emotionsData = [
},
];
export const OPEN_API_KEY = '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'
\ No newline at end of file
export const OPEN_API_KEY =
"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";
......@@ -21,7 +21,8 @@ export const request = (
method: AxiosRequestConfig["method"],
requestData?: AxiosRequestConfig["data"] | AxiosRequestConfig["params"],
isGuest?: boolean,
contentType?: string
contentType?: string,
header?: AxiosRequestConfig["headers"]
) =>
new Promise(async (resolve, reject) => {
const endpoint = url?.replace?.("<BASE_URL>", BASE_URL);
......@@ -33,6 +34,7 @@ export const request = (
const headers = {
auth_token,
"Content-Type": contentType || "application/json",
...(header||{}),
};
logger("REQUEST: ", method, endpoint, headers, requestData);
......
import {
AddressType,
ApplicationStatus,
CandidateType,
OrganizationType,
Reducers,
......@@ -52,7 +53,7 @@ export const getUserId = (state: Reducers): string => {
return profile._id as string;
};
export const getStatusColor = (status?: string) => {
export const getStatusColor = (status?: ApplicationStatus) => {
const color =
status === "Accepted"
? "text-bg-success"
......@@ -60,6 +61,8 @@ export const getStatusColor = (status?: string) => {
? "text-bg-warning"
: status === "Rejected"
? "text-bg-danger"
: status === "Analyse"
? "text-bg-info"
: "text-bg-primary";
return color;
};
......
import { put, takeLeading, call } from "redux-saga/effects";
import { ACTIONS } from "../actions";
import { APP_STATE, ApplicationPayloadType } from "../types";
import MeetingsAPI from "../apis/meetings";
import { getJobs } from "./common";
import CommonAPI from "../apis/common";
function* createMeeting({
payload,
success,
}: {
type: typeof ACTIONS.CREATE_MEETING;
payload: ApplicationPayloadType;
success?: (roomId: string | null) => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { roomId: string } = yield call(
MeetingsAPI.createRoom,
payload._id
);
yield call(updateApplicationAction, {
type: ACTIONS.UPDATE_APPLICATION_ACTION,
payload: {
applicationId: payload._id,
update: { interview: { ...payload.interview, link: data.roomId } },
},
});
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
success?.(data.roomId);
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
success?.(null);
}
}
export function* endMeeting({
payload,
success,
}: {
type: typeof ACTIONS.END_MEETING;
payload: { application: ApplicationPayloadType; roomId: string };
success?: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
// yield call(MeetingsAPI.endSession, payload.roomId);
// yield call(MeetingsAPI.deactivateRoom, payload.roomId);
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
success?.();
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.FAILED },
});
success?.();
}
}
export function* updateApplication({
payload,
success,
}: {
type: typeof ACTIONS.UPDATE_APPLICATION;
payload: { applicationId: string; update: any; candidateId?: string };
success?: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { success: boolean } = yield call(
CommonAPI.updateApplication,
payload
);
if (data.success) {
yield call(getJobs, { type: ACTIONS.GET_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 function* updateApplicationAction({
payload,
success,
}: {
type: typeof ACTIONS.UPDATE_APPLICATION_ACTION;
payload: { applicationId: string; update: any };
success?: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { success: boolean } = yield call(
CommonAPI.updateApplicationAction,
payload
);
if (data.success) {
yield call(getJobs, { type: ACTIONS.GET_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* applicationSaga() {
yield takeLeading(ACTIONS.CREATE_MEETING, createMeeting);
yield takeLeading(ACTIONS.END_MEETING, endMeeting);
yield takeLeading(ACTIONS.UPDATE_APPLICATION, updateApplication);
yield takeLeading(ACTIONS.UPDATE_APPLICATION_ACTION, updateApplicationAction);
}
......@@ -3,7 +3,7 @@ import { ACTIONS } from "../actions";
import CommonAPI from "../apis/common";
import { APP_STATE, JobType, Reducers } from "../types";
function* getJobs({
export function* getJobs({
success,
}: {
type: typeof ACTIONS.GET_JOBS;
......@@ -103,46 +103,8 @@ function* updateJob({
}
}
function* updateApplication({
payload,
success,
}: {
type: typeof ACTIONS.UPDATE_APPLICATION;
payload: { applicationId: string; update: any; candidateId: string };
success?: () => void;
}) {
try {
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.LOADING },
});
const data: { success: boolean } = yield call(
CommonAPI.updateApplication,
payload
);
if (data.success) {
yield call(getJobs, { type: ACTIONS.GET_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);
yield takeLeading(ACTIONS.UPDATE_APPLICATION, updateApplication);
}
......@@ -3,7 +3,8 @@ import { all } from "redux-saga/effects";
import Auth from "./auth";
import User from "./user";
import Common from "./common";
import Application from "./application";
export default function* rootSaga() {
yield all([Auth(), User(), Common()]);
yield all([Auth(), User(), Common(), Application()]);
}
......@@ -3,7 +3,14 @@ import { ACTIONS } from "../actions";
import CommonAPI from "../apis/common";
import UserAPI from "../apis/user";
import { getProfile } from "../lib/util";
import { APP_STATE, CandidateType, JobType, OrganizationType, Reducers } from "../types";
import {
APP_STATE,
CandidateType,
JobType,
OrganizationType,
Reducers,
} from "../types";
import { getJobs } from "./common";
function* updateCandidate({
payload,
......@@ -125,11 +132,12 @@ function* applyForJob({
}
}
yield call(getJobs, { type: ACTIONS.GET_JOBS });
yield put({
type: ACTIONS.SET_APP_STATE,
payload: { state: APP_STATE.SUCCESS },
});
window.location.reload();
} catch (error) {
yield put({
type: ACTIONS.SET_APP_STATE,
......@@ -138,8 +146,6 @@ function* applyForJob({
}
}
export default function* authSaga() {
yield takeLeading(ACTIONS.UPDATE_CANDIDATE, updateCandidate);
yield takeLeading(ACTIONS.UPDATE_ORG, updateOrganization);
......
......@@ -144,10 +144,17 @@ export type JobType = {
organization: string;
};
export type ApplicationStatus =
| "Pending"
| "In progress"
| "Analyse"
| "Accepted"
| "Rejected";
export type ApplicationType = {
candidate: string;
job: string;
status: "Pending" | "Accepted" | "In progress" | "Rejected";
status: ApplicationStatus;
interview?: {
date: string;
time: string;
......@@ -222,7 +229,7 @@ export type ApplicationPayloadType = {
_id: string;
candidate: CandidateType;
job: string;
status: string;
status: ApplicationStatus;
interview?: {
date: string;
time: string;
......
......@@ -149,6 +149,7 @@
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 10px;
}
.skill {
background-color: #058700;
......@@ -183,6 +184,7 @@
h5 {
margin: 0;
}
margin-bottom: 10px;
label {
a {
......@@ -218,19 +220,74 @@
}
}
.upload-interview-video {
.interview-room {
background-color: #f5f5f5;
height: 400px;
height: 450px;
display: flex;
align-items: center;
justify-content: center;
input {
display: none;
}
div {
align-items: center;
display: flex;
flex-direction: column;
}
}
.questions-container{
.questions-container {
background-color: #ffefab80;
min-height: 400px;
padding: 0 20px;
}
button.nav-link.active {
color: #0e60e4 !important;
font-weight: 600;
}
.meeting-container {
position: relative;
width: 100%;
height: 100%;
.action-containers {
position: absolute;
display: flex;
flex-direction: row;
bottom: 10px;
z-index: 10;
button {
margin: 0 10px;
}
.meeting-action {
background-color: #dc3545;
height: 40px;
width: 40px;
border-radius: 40px;
border: none;
color: white;
&.record {
background-color: white;
color: #dc3545;
}
}
}
.lobby-actions {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
}
.player-wrapper {
width: auto;
height: auto;
}
.react-player {
position: absolute;
}
......@@ -14,17 +14,24 @@ import { OnProgressProps } from "react-player/base";
import CommonAPI from "../../common/apis/common";
import {
ApplicationPayloadType,
ApplicationStatus,
EmotionsPayloadType,
EmotionsType,
} from "../../common/types";
import Progress from "../Progress";
import { fileStorage } from "../../common/config";
import { useDispatch } from "react-redux";
import { getJobs, updateApplication } from "../../common/actions/common";
import { getJobs } from "../../common/actions/common";
import { updateApplication } from "../../common/actions/application";
import { getVerificationColor } from "../../common/lib/util";
import ApplicationActions from "./ApplicationActions";
import MeetingsAPI from "../../common/apis/meetings";
type OwnProps = {
application: ApplicationPayloadType;
status?: ApplicationStatus;
onChangeStatus: (e: ChangeEvent<HTMLSelectElement>) =>