Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Career feature Files #50

Merged
merged 13 commits into from
Nov 9, 2023
112 changes: 112 additions & 0 deletions public/src/client/career.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict';

define('forum/career', [
'translator', 'jquery-form',
], function (translator) {
const Career = {};
let validationError = false;

Career.init = function () {
const student_id = $('#student_id');
const age = $('#age');
const gpa = $('#gpa');
const num_programming_languages = $('#num_programming_languages');
const num_past_internships = $('#num_past_internships');
const signup = $('#signup');

function validateForm(callback) {
validationError = false;
validateNonEmpty(student_id.val(), $('#student-id-notify'));
validateRangedInt(age.val(), $('#age-notify'), 18, 25);
validateRangedFloat(gpa.val(), $('#gpa-notify'), 0.0, 4.0);
validateRangedInt(num_programming_languages.val(), $('#num-programming-languages-notify'), 1, 5);
validateRangedInt(num_past_internships.val(), $('#num-past-internships-notify'), 0, 4);
callback();
}

signup.on('click', function (e) {
const registerBtn = $(this);
const errorEl = $('#career-error-notify');
errorEl.addClass('hidden');
e.preventDefault();
validateForm(function () {
if (validationError) {
return;
}

registerBtn.addClass('disabled');

registerBtn.parents('form').ajaxSubmit({
headers: {
'x-csrf-token': config.csrf_token,
},
success: function () {
location.reload();
},
error: function (data) {
translator.translate(data.responseText, config.defaultLang, function (translated) {
if (data.status === 403 && data.responseText === 'Forbidden') {
window.location.href = config.relative_path + '/career?error=csrf-invalid';
} else {
errorEl.find('p').text(translated);
errorEl.removeClass('hidden');
registerBtn.removeClass('disabled');
}
});
},
});
});
});
};

function validateNonEmpty(value, value_notify) {
if (!value || value.length === 0) {
showError(value_notify, 'Must be non-empty');
} else {
showSuccess(value_notify);
}
}

function validateRangedInt(value, value_notify, min_val, max_val) {
if (!value || isNaN(value)) {
showError(value_notify, `Must be a valid integer`);
} else if (parseInt(value, 10) < min_val || parseInt(value, 10) > max_val) {
showError(value_notify, `Must be within the range [${min_val}, ${max_val}]`);
} else {
showSuccess(value_notify);
}
}

function validateRangedFloat(value, value_notify, min_val, max_val) {
if (!value || isNaN(value)) {
showError(value_notify, `Must be a valid floating point value`);
} else if (parseFloat(value) < min_val || parseFloat(value) > max_val) {
showError(value_notify, `Must be within the range [${min_val}, ${max_val}]`);
} else {
showSuccess(value_notify);
}
}

function showError(element, msg) {
translator.translate(msg, function (msg) {
element.html(msg);
element.parent()
.removeClass('register-success')
.addClass('register-danger');
element.show();
});
validationError = true;
}

function showSuccess(element, msg) {
translator.translate(msg, function (msg) {
element.html(msg);
element.parent()
.removeClass('register-danger')
.addClass('register-success');
element.show();
});
}

return Career;
});
13 changes: 12 additions & 1 deletion public/src/modules/helpers.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ module.exports = function (utils, Benchpress, relative_path) {
userAgentIcons,
buildAvatar,
register,
getPredictionColor,
formatPrediction,
__escape: identity,
};

Expand Down Expand Up @@ -333,7 +335,7 @@ module.exports = function (utils, Benchpress, relative_path) {
return '<img ' + attributes.join(' ') + ' src="' + userObj.picture + '" style="' + styles.join(' ') + '" />';
}

// if user is anonymous, use empty icon
// if user is anonymous, use empty icon
if (userObj.isAnonymous) {
return;
}
Expand All @@ -342,6 +344,15 @@ module.exports = function (utils, Benchpress, relative_path) {
return '<span ' + attributes.join(' ') + ' style="' + styles.join(' ') + '">' + userObj['icon:text'] + '</span>';
}

function getPredictionColor(prediction) {
if (prediction === 1) { return `"background-color: rgb(0, 255, 0);"`; }
return `"background-color: rgb(255, 0, 0);"`;
}

function formatPrediction(prediction) {
return prediction;
}

function register() {
Object.keys(helpers).forEach(function (helperName) {
Benchpress.registerHelper(helperName, helpers[helperName]);
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ authenticationController.register = async function (req, res) {
}

if (!userData['account-type'] ||
(userData['account-type'] !== 'student' && userData['account-type'] !== 'instructor')) {
(userData['account-type'] !== 'student' && userData['account-type'] !== 'instructor' && userData['account-type'] !== 'recruiter')) {
throw new Error('Invalid account type');
}

Expand Down Expand Up @@ -507,4 +507,4 @@ async function getBanError(uid) {
}
}

require('../promisify')(authenticationController, ['register', 'registerComplete', 'registerAbort', 'login', 'localLogin', 'logout']);
require('../promisify')(authenticationController, ['register', 'registerComplete', 'registerAbort', 'login', 'localLogin', 'logout']);
22 changes: 21 additions & 1 deletion src/controllers/career.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
'use strict';

const user = require('../user');
const helpers = require('./helpers');

const careerController = module.exports;

careerController.get = async function (req, res) {
const careerData = {};
const userData = await user.getUserFields(req.uid, ['accounttype']);

const accountType = userData.accounttype;
let careerData = {};

if (accountType === 'recruiter') {
careerData.allData = await user.getAllCareerData();
} else {
const userCareerData = await user.getCareerData(req.uid);
if (userCareerData) {
careerData = userCareerData;
} else {
careerData.newAccount = true;
}
}

careerData.accountType = accountType;
careerData.breadcrumbs = helpers.buildBreadcrumbs([{ text: 'Career', url: '/career' }]);
res.render('career', careerData);
};
3 changes: 2 additions & 1 deletion src/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ Controllers.register = async function (req, res, next) {
<select class="form-control" name="account-type" aria-label="Account Type">
<option value="student" selected>Student</option>
<option value="instructor">Instructor</option>
<option value="recruiter">Recruiter</option>
</select>
`,
},
Expand Down Expand Up @@ -372,4 +373,4 @@ Controllers.termsOfUse = async function (req, res, next) {
res.render('tos', {
termsOfUse: termsOfUse.postData.content,
});
};
};
32 changes: 32 additions & 0 deletions src/controllers/write/career.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const helpers = require('../helpers');
const user = require('../../user');
const db = require('../../database');

const Career = module.exports;

Career.register = async (req, res) => {
const userData = req.body;
try {
const userCareerData = {
student_id: userData.student_id,
major: userData.major,
age: userData.age,
gender: userData.gender,
gpa: userData.gpa,
extra_curricular: userData.extra_curricular,
num_programming_languages: userData.num_programming_languages,
num_past_internships: userData.num_past_internships,
};

userCareerData.prediction = Math.round(Math.random()); // TODO: Change this line to do call and retrieve actual candidate success prediction from the model instead of using a random number

await user.setCareerData(req.uid, userCareerData);
db.sortedSetAdd('users:career', req.uid, req.uid);
res.json({});
} catch (err) {
console.log(err);
helpers.noScriptErrors(req, res, err.message, 400);
}
};
25 changes: 25 additions & 0 deletions src/user/career.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const db = require('../database');
const plugins = require('../plugins');

module.exports = function (User) {
User.getCareerData = async function (uid) {
uid = isNaN(uid) ? 0 : parseInt(uid, 10);
const careerData = await db.getObject(`user:${uid}:career`);
return careerData;
};

User.getAllCareerData = async function () {
const uids = await db.getSortedSetRange('users:career', 0, -1);
const allData = await db.getObjects(uids.map(uid => `user:${uid}:career`));
return allData;
};

User.setCareerData = async function (uid, data) {
await db.setObject(`user:${uid}:career`, data);
for (const [field, value] of Object.entries(data)) {
plugins.hooks.fire('action:user.set', { uid, field, value, type: 'set' });
}
};
};
3 changes: 2 additions & 1 deletion src/user/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require('./info')(User);
require('./online')(User);
require('./blocks')(User);
require('./uploads')(User);
require('./career')(User);

User.exists = async function (uids) {
return await (
Expand Down Expand Up @@ -245,4 +246,4 @@ User.addInterstitials = function (callback) {
callback();
};

require('../promisify')(User);
require('../promisify')(User);
84 changes: 84 additions & 0 deletions themes/nodebb-theme-persona/less/career.less
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,87 @@
font-weight: normal;
line-height: 1.42857143;
}

.career-block {
width: 85%;
margin-top: 20px;

.help-block {
font-size: 13px;
}

.card {
position: relative;
display: inline-block;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 1px solid rgba(0,0,0,.125);
border-radius: 0.25rem;
margin: 15px;
}

.card-body {
padding: 15px;
}

.card-title {
font-weight: bold;
font-size: 15px;
}

.card-text {
margin-top: 5px;
}

.prediction {
-webkit-transform: translateX(1.2em);
transform: translateX(1.2em);
color: #fff;
text-align: center;
padding: 10px;
border-radius: 10px;
position: absolute;
z-index: 100;
right: -5px;
bottom: -10px;
white-space: nowrap;
}

.register-success {
.form-control {
border-color: #5cb85c;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");
padding-right: 2.25rem;
background-repeat: no-repeat;
background-position: center right .625rem;
-webkit-background-size: 1.25rem 1.25rem;
background-size: 1.25rem 1.25rem;
}

.register-feedback {
color: #5cb85c;
display: block;
margin-top: .25rem;
}
}

.register-danger {
.form-control {
border-color: #d9534f;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E");
padding-right: 2.25rem;
background-repeat: no-repeat;
background-position: center right .625rem;
-webkit-background-size: 1.25rem 1.25rem;
background-size: 1.25rem 1.25rem;
}

.register-feedback {
color: #d9534f;
display: block;
margin-top: .25rem;
}
}
}
13 changes: 11 additions & 2 deletions themes/nodebb-theme-persona/templates/career.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@
Welcome to the careers page! This is a brand new feature added to allow
students to connect with various job recruiters.
</p>
This page is still under development (testing!!).
<!-- IF (!accountType || (accountType == "instructor")) -->
This page is only available for students and recruiters.
<!-- ELSE -->
<!-- IF (accountType == "student") -->
<!-- IMPORT partials/career/student.tpl -->
<!-- END -->
<!-- IF (accountType == "recruiter") -->
<!-- IMPORT partials/career/recruiter.tpl -->
<!-- END -->
<!-- END -->
</div>
<div data-widget-area="sidebar" class="col-lg-3 col-sm-12 <!-- IF !widgets.sidebar.length -->hidden<!-- ENDIF !widgets.sidebar.length -->">
{{{each widgets.sidebar}}}
Expand All @@ -23,4 +32,4 @@
{{{each widgets.footer}}}
{{widgets.footer.html}}
{{{end}}}
</div>
</div>
Loading