Compare commits
2 Commits
adce955f69
...
e83cbaafa8
| Author | SHA1 | Date |
|---|---|---|
|
|
e83cbaafa8 | |
|
|
50b3b88762 |
|
|
@ -2,6 +2,22 @@ import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../models/vaModel.js";
|
import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../models/vaModel.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function logVaTime(req, res) {
|
||||||
|
try {
|
||||||
|
const payload = req.body || {};
|
||||||
|
const { vaId, vaEmail, projectId, type } = payload;
|
||||||
|
|
||||||
|
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
|
||||||
|
await LoggedModel.insert({ loggedType: type, vaId, vaEmail, projectId })
|
||||||
|
|
||||||
|
return res.status(201).send({ success: true, message: `${type} Successfully` });
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).send("Internal Server Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Getting all active VA's and their details
|
// Getting all active VA's and their details
|
||||||
async function va_callback(req, res) {
|
async function va_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -135,10 +151,14 @@ async function va_login_callback(req, res) {
|
||||||
{ expiresIn: "30d" }
|
{ expiresIn: "30d" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await VAModel.updateOne({ "userDetails.email": userDetails.email }, {
|
||||||
|
$set: { accessToken, refreshToken }
|
||||||
|
})
|
||||||
|
|
||||||
// Return accessToken and user info
|
// Return accessToken and user info
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Login successful",
|
message: "Login Successfull",
|
||||||
accessToken,
|
accessToken,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
user: {
|
user: {
|
||||||
|
|
@ -152,6 +172,33 @@ async function va_login_callback(req, res) {
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function va_logout_callback(req, res) {
|
||||||
|
try {
|
||||||
|
// const payload = req.body ? JSON.parse(req.body) : {};
|
||||||
|
const vaId = req.body.vaId || {};
|
||||||
|
const vaEmail = req.body.vaEmail || {};
|
||||||
|
if (!vaId || !vaEmail) return res.status(400).json({ success: false, message: "Invalid Credentials" });
|
||||||
|
|
||||||
|
// Find user by email
|
||||||
|
const user = await VAModel.findOne({ "userDetails.email": vaEmail });
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ success: false, message: "Invalid credentials" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await VAModel.updateOne({ "userDetails.email": userDetails.email }, {
|
||||||
|
$set: { accessToken: null, refreshToken: null }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return accessToken and user info
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Log Out Successfull",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("va_register error:", error);
|
||||||
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh Token VA
|
// Refresh Token VA
|
||||||
async function va_refresh_token_callback(req, res) {
|
async function va_refresh_token_callback(req, res) {
|
||||||
|
|
@ -358,35 +405,110 @@ async function va_clients_projects_callback(req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting VA log between specific dates with optional screenshot
|
|
||||||
async function va_log_callback(req, res) {
|
async function va_log_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
const { id, from, to, screentShot } = req.params;
|
const { projectId, from, to } = req.params;
|
||||||
return res.status(200).json({
|
const { vaId } = req.body;
|
||||||
|
|
||||||
|
if (!projectId) return res.status(400).json({ success: false, message: "Missing Props" })
|
||||||
|
|
||||||
|
const project = await ProjectModel.findById(projectId);
|
||||||
|
|
||||||
|
if (!project) return res.status(400).json({ success: false, message: "Project not found" });
|
||||||
|
|
||||||
|
// Default range
|
||||||
|
const fromDate = from && from !== "undefined" ? new Date(from) : new Date(project.startDate || "2000-01-01");
|
||||||
|
const toDate = to && to !== "undefined" ? new Date(to) : new Date();
|
||||||
|
|
||||||
|
// If from and to are the same day
|
||||||
|
if (from === to) {
|
||||||
|
fromDate.setHours(0, 0, 0, 0);
|
||||||
|
toDate.setHours(23, 59, 59, 999);
|
||||||
|
} else {
|
||||||
|
// Otherwise use them as a normal range
|
||||||
|
toDate.setHours(23, 59, 59, 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
vaId: vaId,
|
||||||
|
projectId,
|
||||||
|
time: {
|
||||||
|
$gte: fromDate,
|
||||||
|
$lte: toDate
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const logs = await LoggedModel.find(query).sort({ time: 1 });
|
||||||
|
|
||||||
|
if (!logs.length) {
|
||||||
|
return res.json({ success: true, message: "No logs found", totalWork: "0h 0m" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find first LOGIN and last LOGOUT
|
||||||
|
const firstLogin = logs.find((l) => l.loggedType === "LOGIN");
|
||||||
|
const lastLogout = [...logs].reverse().find((l) => l.loggedType === "LOGOUT");
|
||||||
|
|
||||||
|
if (!firstLogin || !lastLogout) {
|
||||||
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "VA Log Callback",
|
message: "Incomplete log data (missing LOGIN or LOGOUT)",
|
||||||
id,
|
totalWork: "0h 0m",
|
||||||
from,
|
firstLogin,
|
||||||
to,
|
lastLogout,
|
||||||
screentShot
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate duration
|
||||||
|
const diffMs = new Date(lastLogout.time) - new Date(firstLogin.time);
|
||||||
|
const totalMinutes = Math.floor(diffMs / (1000 * 60));
|
||||||
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
|
const minutes = totalMinutes % 60;
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
vaId,
|
||||||
|
projectId,
|
||||||
|
firstLogin: firstLogin.time,
|
||||||
|
lastLogout: lastLogout.time,
|
||||||
|
logs: logs,
|
||||||
|
totalTimeLogged: `${hours}h ${minutes}m`,
|
||||||
|
hoursLogged: hours,
|
||||||
|
minutesLogged: minutes,
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ success: false, message: err.message });
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Getting logged hours in a specific project of VA
|
// Getting logged hours in a specific project of VA
|
||||||
async function va_project_log_callback(req, res) {
|
async function va_project_log_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
const { id, projectID } = req.params;
|
const payload = req.body || {};
|
||||||
return res.status(200).json({
|
const { vaId, vaEmail, projectId, type } = payload;
|
||||||
success: true,
|
|
||||||
message: "VA Project Log Callback",
|
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
|
||||||
id,
|
await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
|
||||||
projectID
|
|
||||||
});
|
return res.status(201).send({ success: true, message: `${type} Successfully` });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
return res.status(500).send("Internal Server Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function va_time_logged_on_proj(req, res) {
|
||||||
|
try {
|
||||||
|
const payload = req.body || {};
|
||||||
|
const { vaId, vaEmail, projectId, type } = payload;
|
||||||
|
|
||||||
|
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
|
||||||
|
await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
|
||||||
|
|
||||||
|
return res.status(201).send({ success: true, message: `${type} Successfully` });
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).send("Internal Server Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,6 +599,7 @@ async function va_filter_search(req, res) {
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
logVaTime,
|
||||||
va_callback,
|
va_callback,
|
||||||
va_username_callback,
|
va_username_callback,
|
||||||
va_clients_callback,
|
va_clients_callback,
|
||||||
|
|
@ -487,5 +610,6 @@ export {
|
||||||
va_register,
|
va_register,
|
||||||
va_projects_callback,
|
va_projects_callback,
|
||||||
va_login_callback,
|
va_login_callback,
|
||||||
va_refresh_token_callback
|
va_logout_callback,
|
||||||
|
va_refresh_token_callback,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,33 @@
|
||||||
import AuthRepository from "../repository/auth.repository.js";
|
import jwt from "jsonwebtoken";
|
||||||
import AuthService from "../services/auth.service.js";
|
import { VAModel } from "../models/vaModel.js";
|
||||||
|
|
||||||
import TemplateRepository from "../repository/template.repository.js";
|
|
||||||
import TemplateService from "../services/template.service.js";
|
|
||||||
|
|
||||||
import httpReqRefToken from "../httpReq/refreshToken.httpReq.js";
|
|
||||||
import httpReqValidateAuth from "../httpReq/validateAuth.httpReq.js";
|
|
||||||
import { decryptCryptr, encryptCryptr } from "../helper/cryptrEncDec.js";
|
|
||||||
import logger from "../config/logger.js";
|
|
||||||
|
|
||||||
const authService = new AuthService(new AuthRepository());
|
|
||||||
const templateService = new TemplateService(new TemplateRepository());
|
|
||||||
|
|
||||||
|
|
||||||
async function authValidator(req, res, next) {
|
async function authValidator(req, res, next) {
|
||||||
try {
|
try {
|
||||||
logger.info("AuthValidator - middleware is running ...")
|
const accessToken = req.headers.token;
|
||||||
logger.info("Process ID", process.pid)
|
if (!accessToken) return res.status(401).json({ success: false, message: "Not Authorized" });
|
||||||
let templateId = null;
|
|
||||||
let orgId;
|
|
||||||
if ('template' in req.body && 'org_auth_id' in req.body.template && req.body.template['org_auth_id'] != null) {
|
|
||||||
|
|
||||||
orgId = req.body.template.org_auth_id;
|
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN);
|
||||||
} else {
|
|
||||||
if ('templateId' in req.body) {
|
if (decoded && ("id" in decoded) && decoded.id !== null && ("email" in decoded)) {
|
||||||
templateId = req.body.templateId;
|
const vaEmail = decoded.email;
|
||||||
} else if ('id' in req.body.template) {
|
|
||||||
templateId = req.body.template.id;
|
const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail })
|
||||||
|
if (!vaUser) {
|
||||||
|
return res.status(401).json({ success: false, message: "Not Authorized" });
|
||||||
|
}
|
||||||
|
if (!("accessToken" in vaUser) || !vaUser.accessToken) {
|
||||||
|
return res.status(401).json({ success: false, message: "Not Authorized" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempRes = await templateService.fetch({ _id: templateId });
|
if (vaUser.accessToken === accessToken) {
|
||||||
const isPublish = tempRes.json_template !== null;
|
req.body.vaId = vaUser._id
|
||||||
req.body.isPublish = isPublish;
|
req.body.vaEmail = vaEmail
|
||||||
orgId = tempRes.org_auth_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authEntryExist = await authService.fetchByOrgId(orgId);
|
|
||||||
|
|
||||||
if (!authEntryExist) {
|
|
||||||
logger.error("AuthValidator - Entry not exist templateId:", templateId);
|
|
||||||
return res.status(403).send({"message": "Entry not exist"});
|
|
||||||
}
|
|
||||||
|
|
||||||
let { instance_url, refresh_token, access_token, is_sandbox } = authEntryExist;
|
|
||||||
|
|
||||||
access_token = decryptCryptr(access_token)
|
|
||||||
refresh_token = decryptCryptr(refresh_token)
|
|
||||||
|
|
||||||
let isValid = await httpReqValidateAuth(orgId, access_token, instance_url, refresh_token)
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
logger.info("AuthValidator - Access_Token expired - refreshing ...");
|
|
||||||
const refTokenRes = await httpReqRefToken(refresh_token, is_sandbox);
|
|
||||||
access_token = refTokenRes.access_token;
|
|
||||||
await authService.update({ org_id: orgId }, { access_token: encryptCryptr(access_token) });
|
|
||||||
}
|
|
||||||
|
|
||||||
isValid = await httpReqValidateAuth(orgId, access_token, instance_url, refresh_token)
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
logger.error("AuthValidator - Not Refreshing RefreshingToken Expired return 401. access_token");
|
|
||||||
return res.send(401).status({"message": "Token expired (acc | ref)"})
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body.access_token = access_token;
|
|
||||||
req.body.instance_url = instance_url;
|
|
||||||
|
|
||||||
logger.info("AuthValidator - middleware Work Done (VALIDATED SUCCESSFULLY) ...")
|
|
||||||
next();
|
next();
|
||||||
|
} else {
|
||||||
|
return res.status(401).json({ success: false, message: "Not Authorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("AuthValidator - Middleware (CATCH Block)", error.message);
|
logger.error("AuthValidator - Middleware (CATCH Block)", error.message);
|
||||||
return res.send(401).status({ "message": error.message })
|
return res.send(401).status({ "message": error.message })
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ const projectSchema = new Schema({
|
||||||
},
|
},
|
||||||
totalLoggedHours: {
|
totalLoggedHours: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
default: 0
|
||||||
},
|
},
|
||||||
vaAssigned: [
|
vaAssigned: [
|
||||||
{
|
{
|
||||||
|
|
@ -113,6 +114,14 @@ const loggedSchema = new Schema({
|
||||||
vaId: {
|
vaId: {
|
||||||
type: Types.ObjectId,
|
type: Types.ObjectId,
|
||||||
ref: "VA"
|
ref: "VA"
|
||||||
|
},
|
||||||
|
vaEmail: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
projectId: {
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "Project"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -127,6 +136,12 @@ const vaSchema = new Schema(
|
||||||
ref: "Client",
|
ref: "Client",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
projectIds: [
|
||||||
|
{
|
||||||
|
type: Types.ObjectId,
|
||||||
|
ref: "Project",
|
||||||
|
},
|
||||||
|
],
|
||||||
schedule: {
|
schedule: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|
@ -135,14 +150,47 @@ const vaSchema = new Schema(
|
||||||
enum: ["PARTTIME", "FULLTIME", "INACTIVE"],
|
enum: ["PARTTIME", "FULLTIME", "INACTIVE"],
|
||||||
default: "FULLTIME",
|
default: "FULLTIME",
|
||||||
},
|
},
|
||||||
|
accessToken: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
refreshToken: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Need to add working hours and time zone tooooo in which VA is going to work, need to calculate total work hours of the day - Schedules
|
||||||
|
// Need to add this Schedule feature on SF there we can change the schedulte of VA - Add in form tooo
|
||||||
|
// Need to ask to client if we want to provide schedule changing functionality on Docker toooooo
|
||||||
|
|
||||||
|
|
||||||
|
// Add IN OUT
|
||||||
|
|
||||||
|
|
||||||
|
// const vaTimeSheetSchema = new Schema({
|
||||||
|
// vaId: {
|
||||||
|
// type: Types.ObjectId,
|
||||||
|
// ref: "VA",
|
||||||
|
// },
|
||||||
|
// projectId: {
|
||||||
|
// type: Types.ObjectId,
|
||||||
|
// ref: "Project",
|
||||||
|
// },
|
||||||
|
// logged: {
|
||||||
|
// enum: ["IN", "OUT"],
|
||||||
|
// default: "IN",
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// { timestamps: true }
|
||||||
|
// )
|
||||||
|
|
||||||
/* ---------------------- Model Exports ---------------------- */
|
/* ---------------------- Model Exports ---------------------- */
|
||||||
const VAModel = model("VA", vaSchema);
|
const VAModel = model("VA", vaSchema);
|
||||||
const ClientModel = model("Client", clientSchema);
|
const ClientModel = model("Client", clientSchema);
|
||||||
const ProjectModel = model("Project", projectSchema);
|
const ProjectModel = model("Project", projectSchema);
|
||||||
const LoggedModel = model("Logged", loggedSchema);
|
const LoggedModel = model("TrackLog", loggedSchema);
|
||||||
|
|
||||||
export { VAModel, ClientModel, ProjectModel, LoggedModel };
|
export { VAModel, ClientModel, ProjectModel, LoggedModel };
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
const vaRoutes = express.Router();
|
const vaRoutes = express.Router();
|
||||||
|
|
||||||
import { va_callback, va_username_callback, va_clients_callback, va_clients_projects_callback, va_log_callback, va_project_log_callback, va_filter_search, va_projects_callback, va_register, va_login_callback, va_refresh_token_callback } from "../../controllers/va.controller.js";
|
import { va_callback, va_username_callback, va_clients_callback, va_clients_projects_callback, va_log_callback, va_project_log_callback, va_filter_search, va_projects_callback, va_register, va_login_callback, va_refresh_token_callback, va_logout_callback } from "../../controllers/va.controller.js";
|
||||||
|
import { authValidator } from "../../middlewares/auth.middleware.js";
|
||||||
|
|
||||||
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
|
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
|
||||||
|
|
||||||
vaRoutes.post("/login", va_login_callback); // VA Callback for login VA
|
vaRoutes.post("/login", va_login_callback); // VA Callback for login VA
|
||||||
|
|
||||||
|
vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for login VA
|
||||||
|
|
||||||
vaRoutes.post("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token
|
vaRoutes.post("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token
|
||||||
|
|
||||||
vaRoutes.post("/register", va_register); // Register a new VA
|
vaRoutes.post("/register", va_register); // Register a new VA
|
||||||
|
|
@ -19,9 +22,10 @@ vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callb
|
||||||
|
|
||||||
vaRoutes.get("/project/:projectId", va_projects_callback); // Projects Callback for getting project details
|
vaRoutes.get("/project/:projectId", va_projects_callback); // Projects Callback for getting project details
|
||||||
|
|
||||||
vaRoutes.get("/:id/log/:from/:to/:screentShot", va_log_callback); // VA Log Callback
|
// FROM || TO -
|
||||||
|
vaRoutes.get("/log/:projectId/:from?/:to?", authValidator, va_log_callback); // VA Log Callback
|
||||||
|
|
||||||
vaRoutes.get("/:id/log/project/:projectID", va_project_log_callback); // VA Logged Hours in specific Project
|
vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged Hours in specific Project
|
||||||
|
|
||||||
// VA filter search: supports query params `name`, `email`, `from`, `to`
|
// VA filter search: supports query params `name`, `email`, `from`, `to`
|
||||||
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31
|
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31
|
||||||
|
|
|
||||||
|
|
@ -4,106 +4,177 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>VA Registration</title>
|
<title>VA Registration</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
||||||
<!-- Custom CSS -->
|
|
||||||
<link rel="stylesheet" href="/public/css/va-register.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<section class="h-100">
|
<!-- Bootstrap -->
|
||||||
<div class="container py-1">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<div class="row d-flex justify-content-center align-items-center">
|
|
||||||
<div class="col">
|
<style>
|
||||||
<div class="card card-registration my-4 shadow">
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-registration {
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-registration img {
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #343a40;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-outline {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-lg {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.7rem 2rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
border: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover {
|
||||||
|
background-color: #e0a800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-light {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
border-bottom: 2px solid #ffc107;
|
||||||
|
display: inline-block;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.card-body {
|
||||||
|
padding: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-registration img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="h-100 py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center align-items-center">
|
||||||
|
<div class="col-lg-10 col-xl-9">
|
||||||
|
<div class="card card-registration shadow-lg">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<!-- Left Side Image -->
|
|
||||||
<div class="col-xl-6 d-none d-xl-block">
|
<!-- Left Image -->
|
||||||
|
<div class="col-xl-5 d-none d-xl-block">
|
||||||
<img
|
<img
|
||||||
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-registration/img4.webp"
|
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-registration/img4.webp"
|
||||||
alt="Registration Image"
|
alt="Registration"
|
||||||
class="img-fluid"
|
class="img-fluid"
|
||||||
style="border-top-left-radius: .25rem; border-bottom-left-radius: .25rem;"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Side Form -->
|
<!-- Right Form -->
|
||||||
<div class="col-xl-6">
|
<div class="col-xl-7 bg-white">
|
||||||
<div class="card-body p-md-5 text-black">
|
<div class="card-body text-black">
|
||||||
<h3 class="mb-5 text-uppercase text-center">Virtual Assistant Registration</h3>
|
<h3 class="text-center mb-4">Virtual Assistant Registration</h3>
|
||||||
|
|
||||||
<form id="vaForm">
|
<form id="vaForm">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-outline">
|
|
||||||
<input type="text" id="firstName" name="userDetails[firstName]" class="form-control form-control-lg" required />
|
|
||||||
<label class="form-label" for="firstName">First Name *</label>
|
<label class="form-label" for="firstName">First Name *</label>
|
||||||
|
<input type="text" id="firstName" name="userDetails[firstName]" class="form-control form-control-lg" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label" for="lastName">Last Name *</label>
|
||||||
|
<input type="text" id="lastName" name="userDetails[lastName]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<div class="form-outline">
|
<div class="mb-3">
|
||||||
<input type="text" id="lastName" name="userDetails[lastName]" class="form-control form-control-lg" required/>
|
|
||||||
<label class="form-label" for="lastName">Last Name</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-outline mb-4">
|
|
||||||
<input type="email" id="email" name="userDetails[email]" class="form-control form-control-lg" required />
|
|
||||||
<label class="form-label" for="email">Email *</label>
|
<label class="form-label" for="email">Email *</label>
|
||||||
|
<input type="email" id="email" name="userDetails[email]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-outline">
|
|
||||||
<input type="text" id="age" name="userDetails[age]" class="form-control form-control-lg not-required" placeholder=""/>
|
|
||||||
<label class="form-label" for="age">Age</label>
|
<label class="form-label" for="age">Age</label>
|
||||||
|
<input type="text" id="age" name="userDetails[age]" class="form-control form-control-lg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-6 mb-3">
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<div class="form-outline">
|
|
||||||
<input type="date" id="dob" name="userDetails[dob]" class="form-control form-control-lg" />
|
|
||||||
<label class="form-label" for="dob">Date of Birth</label>
|
<label class="form-label" for="dob">Date of Birth</label>
|
||||||
</div>
|
<input type="date" id="dob" name="userDetails[dob]" class="form-control form-control-lg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-outline">
|
|
||||||
<input type="password" id="password" name="userDetails[password]" class="form-control form-control-lg" required />
|
|
||||||
<label class="form-label" for="password">Password *</label>
|
<label class="form-label" for="password">Password *</label>
|
||||||
|
<input type="password" id="password" name="userDetails[password]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-6 mb-3">
|
||||||
<div class="col-md-6 mb-4">
|
|
||||||
<div class="form-outline">
|
|
||||||
<input type="password" id="cPassword" name="userDetails[cPassword]" class="form-control form-control-lg" required />
|
|
||||||
<label class="form-label" for="cPassword">Confirm Password *</label>
|
<label class="form-label" for="cPassword">Confirm Password *</label>
|
||||||
</div>
|
<input type="password" id="cPassword" name="userDetails[cPassword]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-outline mb-4">
|
<div class="mb-3">
|
||||||
<input type="text" id="address" name="userDetails[address]" class="form-control form-control-lg not-required" placeholder=""/>
|
|
||||||
<label class="form-label" for="address">Address</label>
|
<label class="form-label" for="address">Address</label>
|
||||||
|
<input type="text" id="address" name="userDetails[address]" class="form-control form-control-lg">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="mt-4 mb-3">Bank Details</h5>
|
<h5>Bank Details</h5>
|
||||||
<div class="form-outline mb-4">
|
<div class="mb-3">
|
||||||
<input type="text" id="bankName" name="userDetails[bankName]" class="form-control form-control-lg" required/>
|
|
||||||
<label class="form-label" for="bankName">Bank Name</label>
|
<label class="form-label" for="bankName">Bank Name</label>
|
||||||
|
<input type="text" id="bankName" name="userDetails[bankName]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-outline mb-4">
|
<div class="mb-3">
|
||||||
<input type="text" id="accountHolderName" name="userDetails[accountHolderName]" class="form-control form-control-lg" required/>
|
|
||||||
<label class="form-label" for="accountHolderName">Account Holder Name</label>
|
<label class="form-label" for="accountHolderName">Account Holder Name</label>
|
||||||
|
<input type="text" id="accountHolderName" name="userDetails[accountHolderName]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-outline mb-4">
|
<div class="mb-3">
|
||||||
<input type="text" id="accountNumber" name="userDetails[accountNumber]" class="form-control form-control-lg" required/>
|
|
||||||
<label class="form-label" for="accountNumber">Account Number</label>
|
<label class="form-label" for="accountNumber">Account Number</label>
|
||||||
|
<input type="text" id="accountNumber" name="userDetails[accountNumber]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
<div class="form-outline mb-4">
|
|
||||||
<input type="text" id="routingNumber" name="userDetails[routingNumber]" class="form-control form-control-lg" required/>
|
|
||||||
<label class="form-label" for="routingNumber">Routing Number</label>
|
<label class="form-label" for="routingNumber">Routing Number</label>
|
||||||
|
<input type="text" id="routingNumber" name="userDetails[routingNumber]" class="form-control form-control-lg" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
|
@ -114,22 +185,21 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end pt-3">
|
<div class="d-flex justify-content-end gap-2 pt-3">
|
||||||
<button type="reset" class="btn btn-light btn-lg">Reset all</button>
|
<button type="reset" class="btn btn-light btn-lg">Reset</button>
|
||||||
<button type="submit" class="btn btn-warning btn-lg ms-2">Submit form</button>
|
<button type="submit" class="btn btn-warning btn-lg">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<!-- Custom JS -->
|
|
||||||
<script src="/public/js/va-register.js"></script>
|
<script src="/public/js/va-register.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue