Live update + class progress

This commit is contained in:
SowinskiBraeden committed 2021-01-13 12:38:17 -08:00
1 parent a404f5a61b
commit 3307307025
14 files changed
+8225 -147

No files matched your search

+2 -2
View File
@@ -1,5 +1,5 @@
# Port to start server on.
PORT=5000
PORT=8080
# MongoDB connection URI.
DB_URI=mongodb://localhost/knoldus
DB_URI=mongodb://localhost/27017
+2 -1
View File
@@ -3,6 +3,7 @@
School Project
This school project is for my teacher to have students
log in and check out their XP and assignments.
This website application is not available online, this is just a locally run website at our school.
### Requirements :
1. [Node.js](https://nodejs.org/en/)
@@ -14,7 +15,7 @@ log in and check out their XP and assignments.
3. Run `npm install` to install dependencies.
4. Duplicate `.env.example` and rename the new file to `.env`. Edit to your configurations.
1. Run `npm start` to boot up server.
1. Go to http://localhost:5000. Or Whatever you set it to in the `.env` file.
1. Go to http://localhost:8080. Or Whatever you set it to in the `.env` file.
### Accessing the Database
1. Locally this will use any db you set the DB_URI to in the `.env` file.
+18 -6
View File
@@ -5,9 +5,11 @@ const passport = require('passport');
const flash = require('connect-flash');
const session = require('express-session');
const bodyParser = require('body-parser');
var path = require('path');
var fs = require('fs');
var dotenv = require('dotenv');
const http = require('http');
const path = require('path');
const fs = require('fs');
const dotenv = require('dotenv');
const socket = require('socket.io');
dotenv.config();
const app = express();
@@ -21,7 +23,7 @@ const db = require('./config/keys').mongoURI;
// Connect to MongoDB
mongoose
.connect(
process.env.DB_URI || 'mongodb://localhost/knoldus',
process.env.DB_URI || 'mongodb://localhost/27017',
{ useNewUrlParser: true, useUnifiedTopology: true}
)
.then(() => console.log('MongoDB Connected'))
@@ -63,6 +65,16 @@ app.use(function(req, res, next) {
app.use('/', require('./routes/index.js'));
app.use('/users', require('./routes/users.js'));
const PORT = process.env.PORT || 5000;
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, function() {
console.log(`Server listening on port ${PORT}`);
});
let io = socket(server);
app.listen(PORT, console.log(`Server started on port ${PORT}`));
io.on('connection', function(socket) {
console.log('connected');
socket.on('add:xp', function(data) {
io.sockets.emit("added:xp", data);
});
});
+20
View File
@@ -0,0 +1,20 @@
const mongoose = require('mongoose');
const CourseSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
grade: {
type: Number,
required: true
}
});
const Course = mongoose.model('Course', CourseSchema);
module.exports = Course;
+3
View File
@@ -20,6 +20,9 @@ const QuestSchema = new mongoose.Schema({
instruction: {
type: String,
required: true
},
referenceClass: {
type: [String]
}
});
+10
View File
@@ -28,6 +28,16 @@ const UserSchema = new mongoose.Schema({
xp: {
type: Number,
default: 0
},
class: {
className: {
type: String,
default: null
},
questIndex: {
type: Number,
defualt: 0
}
}
});
+215 -5
View File
@@ -4,6 +4,26 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/component-emitter": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
},
"@types/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
},
"@types/cors": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz",
"integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg=="
},
"@types/node": {
"version": "14.14.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -185,6 +205,16 @@
}
}
},
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@@ -196,6 +226,15 @@
"integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==",
"dev": true
},
"bl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"bluebird": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
@@ -566,6 +605,15 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"crc": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz",
@@ -664,6 +712,11 @@
}
}
},
"denque": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -741,6 +794,48 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"engine.io": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.6.tgz",
"integrity": "sha512-rf7HAVZpcRrcKEKddgIzYUnwg0g5HE1RvJaTLwkcfJmce4g+po8aMuE6vxzp6JwlK8FEq/vi0KWN6tA585DjaA==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.0",
"ws": "~7.4.2"
},
"dependencies": {
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"engine.io-parser": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
"requires": {
"base64-arraybuffer": "0.1.4"
}
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -2020,6 +2115,12 @@
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"optional": true
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -2121,13 +2222,23 @@
}
},
"mongodb": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.2.tgz",
"integrity": "sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==",
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz",
"integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==",
"requires": {
"bson": "^1.1.1",
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"require_optional": "^1.0.1",
"safe-buffer": "^5.1.2"
"safe-buffer": "^5.1.2",
"saslprep": "^1.0.0"
},
"dependencies": {
"bson": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
"integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
}
}
},
"mongoose": {
@@ -2148,6 +2259,16 @@
"sliced": "1.0.1"
},
"dependencies": {
"mongodb": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.2.tgz",
"integrity": "sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==",
"requires": {
"bson": "^1.1.1",
"require_optional": "^1.0.1",
"safe-buffer": "^5.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2687,6 +2808,15 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"optional": true,
"requires": {
"sparse-bitfield": "^3.0.3"
}
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
@@ -2899,6 +3029,72 @@
"kind-of": "^3.2.0"
}
},
"socket.io": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.5.tgz",
"integrity": "sha512-5yWQ43P/4IttmPCGKDQ3CVocBiJWGpibyhYJxgUhf69EHMzmK8XW0DkmHIoYdLmZaVZJyiEkUqpeC7rSCIqekw==",
"requires": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/node": "^14.14.10",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.1",
"engine.io": "~4.0.6",
"socket.io-adapter": "~2.0.3",
"socket.io-parser": "~4.0.3"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"socket.io-adapter": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz",
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
},
"socket.io-parser": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.3.tgz",
"integrity": "sha512-m4ybFiP4UYVORRt7jcdqf8UWx+ywVdAqqsJyruXxAdD3Sv6MDemijWij34mOWdMJ55bEdIb9jACBhxUgNK6sxw==",
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.3.1"
},
"dependencies": {
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"source-map-resolve": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
@@ -2918,6 +3114,15 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
"optional": true,
"requires": {
"memory-pager": "^1.0.2"
}
},
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -3281,6 +3486,11 @@
"signal-exit": "^3.0.2"
}
},
"ws": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
},
"xdg-basedir": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+3 -1
View File
@@ -23,10 +23,12 @@
"express": "^4.16.4",
"express-ejs-layouts": "^2.5.0",
"express-session": "^1.15.6",
"mongodb": "^3.6.3",
"mongoose": "^5.7.5",
"multer": "^1.4.2",
"passport": "^0.4.0",
"passport-local": "^1.0.0"
"passport-local": "^1.0.0",
"socket.io": "^3.0.5"
},
"devDependencies": {
"nodemon": "^1.18.9"
+7417
View File
File diff suppressed because it is too large. Load diff
+8 -4
View File
@@ -4,14 +4,16 @@ const { ensureAuthenticated, forwardAuthenticated } = require('../config/auth');
// DB Config
const db = require('../config/keys').mongoURI;
let MongoClient = require('mongodb').MongoClient;
let UserModal = require('../models/User.js');
let QuestModal = require('../models/Quest.js')
let UserModal = require('../models/User');
let QuestModal = require('../models/Quest')
let CourseModal = require('../models/Course')
// Welcome Page
router.get('/', forwardAuthenticated, (req, res) => res.render('welcome'));
// Dashboard
router.get('/dashboard', ensureAuthenticated, function(req, res) {
//Collects Course Data
CourseModal.find({}, function(err, courses) {
// Collects Quest Data
QuestModal.find({}, function(err, quests) {
// Collects User Data
@@ -19,7 +21,9 @@ router.get('/dashboard', ensureAuthenticated, function(req, res) {
res.render('dashboard', {
user : req.user,
members: data,
quests: quests
quests: quests,
courses: courses
});
});
});
});
+78 -9
View File
@@ -6,6 +6,10 @@ const passport = require('passport');
const MongoClient = require('mongodb').MongoClient;
const ObjectId = require('mongodb').ObjectId;
const Quest = require('../models/Quest');
const Course = require('../models/Course');
const socket = require('socket.io');
const server = require('../app.js').server;
const io = socket(server);
// Load Profanity filter
const Filter = require('bad-words');
@@ -16,7 +20,7 @@ const User = require('../models/User');
const { forwardAuthenticated } = require('../config/auth');
// DB Config
const db = require('../config/keys').mongoURI;
const url = require('../config/keys').mongoURI;
// Loads libraries for uploading img functions
let fs = require('fs');
@@ -52,6 +56,70 @@ router.get('/register', forwardAuthenticated, (req, res) => res.render('register
// Edit page
router.get('/edit', (req, res) => res.render('edit'));
// Create Course / Class
router.post('/createClass', (req, res, next) => {
const { name, description, grade } = req.body;
let errors = [];
//Checks if Class Already Exists
Course.findOne({ name: name }).then(course => {
if (course) {
errors.push({ msg: 'Course Name Already Exists' })
// Redirect to dashboard page with errors
res.render('dashboard', {
errors,
name,
description,
grade
});
} else {
try {
// Creates new Course Object
const newCourse = new Course({
name: name,
description: description,
grade: grade
});
newCourse
.save()
.then(course => {
req.flash(
'success_msg'
);
// Takes to Dashboard on success
let value = encodeURIComponent('createdClass')
res.redirect('/dashboard?successRate=' + value);
})
.catch(err => console.log(err));
} catch (err) {
if (err) throw err;
}
}
});
});
// Delete User
router.post('/deleteUser', (req, res, next) => {
const id = req.body.delete_id;
// Deletes User
MongoClient.connect(url, {
useUnifiedTopology: true,
useNewUrlParser: true}, function(err, db) {
if (err) throw err;
let dbo = db.db("XPmockDB");
dbo.collection("students").deleteOne({ _id: ObjectId(id) }, function(err, obj) {
if (err) throw err;
db.close();
});
// Takes to Dahsboard on success
let value = encodeURIComponent('deletedUser');
res.redirect('/dashboard?successRate=' + value);
}
);
});
// Create Quest
router.post('/createQuest', (req, res, next) => {
const { title, amount, expiry, instructions, campaign} = req.body;
@@ -83,7 +151,7 @@ router.post('/createQuest', (req, res, next) => {
});
newQuest
.save()
.then(user => {
.then(quest => {
req.flash(
'success_msg'
);
@@ -128,7 +196,7 @@ router.post('/addXP', (req, res, next) => {
}
// Updates user xp
MongoClient.connect(db, {
MongoClient.connect(url, {
useUnifiedTopology: true,
useNewUrlParser: true}, function(err, db) {
if (err) throw err;
@@ -138,8 +206,9 @@ router.post('/addXP', (req, res, next) => {
db.close()
});
// Takes to Dahsboard on success
let value = encodeURIComponent(amount.toString())
res.redirect('/dashboard?successRate=' + value);
let value1 = encodeURIComponent(amount.toString())
let value2 = encodeURIComponent(_id_add);
res.redirect('/dashboard?successRate='+value1+'&id='+value2);
}
);
}
@@ -281,7 +350,7 @@ router.post('/edit', upload.single('image'), (req, res, next) => {
});
//Updates user and redirects to dashboard
MongoClient.connect(db, {
MongoClient.connect(url, {
useUnifiedTopology: true,
useNewUrlParser: true}, function(err, db) {
if (err) throw err;
@@ -335,7 +404,7 @@ router.post('/edit', upload.single('image'), (req, res, next) => {
}
}
//Updates user and redirects to dashboard
MongoClient.connect(db, {
MongoClient.connect(url, {
useUnifiedTopology: true,
useNewUrlParser: true}, function(err, db) {
if (err) throw err;
@@ -448,8 +517,8 @@ router.post('/registerTeacher', upload.single('image'), (req, res, next) => {
'success_msg'
);
// Takes to Dashboard on success
let value = encodeURIComponent('success')
res.redirect('/dashboard?createTeacher=' + value);
let value = encodeURIComponent('createdTeacher')
res.redirect('/dashboard?successRate=' + value);
})
.catch(err => console.log(err));
});
+92 -11
View File
@@ -1,8 +1,12 @@
<!-- Simple Styling for layout -->
<style>
.members {
position: absolute;
top: 150px;
left: 25px;
width: 230px;
display: inline-block;
max-height: 30px
}
.buttons-container {
position: absolute;
@@ -15,6 +19,19 @@
border-radius: 10px;
background-color: rgba(112, 196, 255, .3);
}
.alert {
max-width: 58%;
}
.admin-detail {
position: absolute;
left: 25px;
}
#div-container {
position: absolute;
left: 30%;
margin-left: 25vm;
margin-right: 25vm;
}
#newTeacher-btn {
margin-left: 25px;
margin-top: 10px;
@@ -73,13 +90,32 @@
font-size: 30px;
background: #f6f6f6;
}
@media (max-width: 800px) {
.alert {
max-width: 55%;
}
#div-container {
position: absolute;
left: 25px;
top: 125px;
margin-left: 25vm;
margin-right: 25vm;
max-height: 125px;
}
.members {
position: absolute;
left: 24px;
top: 300px;
}
}
</style>
<!-- Header -->
<h1 class="mt-4">Site Admin</h1>
<div class='head'>
<div id='teacher-container'>
<div>
<div class='admin-detail'>
<h1 class="mt-4">Site Admin</h1>
<p id='welcome' class="lead mb-3">Welcome Admin</p>
</div>
<br>
@@ -87,7 +123,7 @@
<!-- Action Buttons -->
<a id='logout-btn' href="/users/logout" class="btn btn-outline-info btn-sm">Logout</a>
<!-- Button trigger modal -->
<button id="newTeacher-btn" onClick='openModal()' type="button" class="btn btn-outline-info btn-sm">
<button id="newTeacher-btn" onClick='openModal("#exampleModal")' type="button" class="btn btn-outline-info btn-sm">
Create New Teacher
</button>
<a id='bug-btn' href="https://github.com/Braeden57/XP-System/issues" target="_blank" class='btn btn-outline-info btn-sm'>Report Bug</a>
@@ -97,8 +133,8 @@
<script>
function openModal() {
$("#exampleModal").modal()
function openModal(modalName) {
$(modalName).modal()
}
// Creates unique ID
@@ -117,10 +153,10 @@
let id = makeid(8);
if (type == 'error') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-warning alert-dismissible fade show" role="alert">';
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none; clear: right;" class="alert alert-warning alert-dismissible fade show" role="alert">';
}
if (type == 'message') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-info alert-dismissible fade show" role="alert">';
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none; clear: right;" class="alert alert-info alert-dismissible fade show" role="alert">';
}
document.getElementById('new-div-'+id).innerHTML += '<div id="message'+id+'">'+message+'</div>';
document.getElementById('new-div-'+id).innerHTML += '<button id="close-btn-'+id+'" class="close" data-dismiss="alert" aria-label="Close"></button>';
@@ -135,9 +171,12 @@
window.onload = function () {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const isSuccess = urlParams.get('createTeacher')
if(isSuccess == 'success') {
createDiv('message', 'Successfully Created Teacher')
const successRate = urlParams.get('successRate')
if(successRate == 'createdTeacher') {
createDiv('message', 'Successfully Created Teacher');
}
if(successRate == 'deletedUser') {
createDiv('message', "Successfully Deleted User");
}
}
</script>
@@ -171,7 +210,32 @@
<!-- Alert container -->
<div id='div-container'></div>
<!-- Modal -->
<!-- Delete User Warning Modal -->
<div class="modal fade" id="warningModal" tabindex="-1" role="dialog" aria-labelledby="warningModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="warningModalLabel">Are you sure you wish to delete this user?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="/users/deleteUser" method="POST" enctype="multipart/form-data">
<input id='delete_id' type='text' name='delete_id' value='' style='display: none'></input>
<button type="submit" class="btn btn-primary btn-block">
Yes, Continue and Delete This User
</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<!-- Create Teacher Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
@@ -264,6 +328,11 @@
<p>XP: <%= member.xp %></p>
<p><%= member.role %></p>
<!-- Delete User Option -->
<button id="delete-btn" onClick='deleteUser("<%= member._id %>")' type="button" class="btn btn-outline-warning btn-sm">
Delete User
</button>
</div>
<br>
@@ -294,6 +363,13 @@
// Display Rank
document.getElementById('userRank-<%= member.id %>').innerHTML = currRank;
// Delete User Function
function deleteUser(id) {
document.getElementById('delete_id').value = id;
openModal("#warningModal");
}
</script>
</div>
<% } %>
@@ -308,6 +384,11 @@
<p class='lead mb-3'><%= member.email %></p>
<div>
<p><%= member.role %></p>
<!-- Delete User Option -->
<button id="delete-btn" onClick='deleteUser("<%= member._id %>")' type="button" class="btn btn-outline-warning btn-sm">
Delete User
</button>
</div>
<br>
</div>
+160 -27
View File
@@ -2,7 +2,7 @@
<style>
.quests-container {
position: absolute;
width: 650px;
width: 625px;
height: 645px;
top: 100px;
left: 450px;
@@ -15,9 +15,13 @@
width: 200px;
}
.quest-buttons {
margin-left: 25px;
margin-left: 10px;
margin-top: 10px;
}
#option1 {
width: 50px;
}
#myProgress {
position: relative;
height: 200px;
@@ -40,6 +44,7 @@
width: 100%;
height: 1%;
background-color: #46c7f2;
transition: 1s;
}
#pro-pic {
position: absolute;
@@ -103,6 +108,24 @@
font-size: 30px;
background: #f6f6f6;
}
@media (max-width: 800px) {
.quest-all {
position: absolute;
top: 770px;
right: 1145px;
}
#quest-title {
transform: translate(-125px, 0px);
}
.quest-container {
width: 400px;
border-style: solid;
border-color: rgba(0, 0, 0, .2);
border-radius: 15px;
background-color: rgba(0, 0, 0, .2);
}
}
</style>
<div class="student-container">
@@ -123,7 +146,7 @@
<!-- User Data -->
<div>
<h1 id='userRank'></h1>
<h1>XP: <%= user.xp %></h1>
<h1 id='userXP'>XP: <%= user.xp %></h1>
<p id='XP-Till'></p>
</div>
<br>
@@ -135,7 +158,7 @@
</div>
</div>
<div>
<div class="quest-all">
<h1 id="quest-title">Quests</h1>
<div class='quests-container'>
<div class='quest-buttons'>
@@ -186,8 +209,29 @@
</div>
</div>
<!-- Quest Display -->
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io.connect('http://localhost:5000');
socket.on("added:xp", function(data) {
if(data.userID == '<%=user._id%>') {
reCalculate(data.addXP);
}
});
window.onload = function() {
calculate();
}
let xp = <%= user.xp %>;
// Passing user _id to edit page script
function passData() {
let url = '/users/edit?name=' + encodeURIComponent('<%= user._id %>');
document.location.href = url;
}
function showHTML() {
let state = document.getElementById('HTML-Display').style.display;
if (state == "block") {
@@ -220,23 +264,6 @@
document.getElementById('JS-Display').style.display = 'block';
}
}
</script>
<!-- Passing user _id to edit page script -->
<script>
function passData() {
let url = '/users/edit?name=' + encodeURIComponent('<%= user._id %>');
document.location.href = url;
}
</script>
<!-- Calculate Rank Data Script -->
<script>
let xp = <%= user.xp %>;
let currRank;
let rankUp;
let remain;
let ranks = [
{"Digital Noob": 0}, // index 0
@@ -254,14 +281,28 @@
{"Master Digital Crafter": 800} // index 12
]
// Get current rank
// Global old variables
let currRank;
let rankUp;
let remain;
let goal;
let total;
let progress;
let percent;
let indexes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12]
// Initial calculate from 0 to current XP
function calculate() {
// Get current rank
if (xp < 800) {
for(var i = 0; i < ranks.length; i++) {
for (let rank in ranks[i]) {
let nextIndex = indexes[i];
// Gets rank after current rank
for (let nextRank in ranks[nextIndex]) {
// Verifies current XP is > currentRank xp level && current XP < nextRank xp level
if (xp >= ranks[i][rank] && xp < ranks[nextIndex][nextRank]) {
// Sets global old variables
currRank = rank;
currRankXP = ranks[i][rank]
// Calculate xp till next Rank
@@ -269,13 +310,13 @@
i++;
let nextRank = ranks[i];
for (let r in nextRank) {
let goal = nextRank[r];
goal = nextRank[r];
rankUp = r;
remain = goal - xp;
// Calculate progress bar
let total = goal - currRankXP;
let progress = xp - currRankXP;
let percent = (progress/total)*100;
total = goal - currRankXP;
progress = xp - currRankXP;
percent = (progress/total)*100;
let elem = document.getElementById("myBar");
elem.style.height = percent + '%';
document.getElementById('percent').innerHTML = progress + '|' + total;
@@ -299,6 +340,98 @@
// Display Data
document.getElementById('userRank').innerHTML = currRank;
document.getElementById('XP-Till').innerHTML = remain + 'XP till your next Rank: ' + rankUp;
}
// Re calculate recaculates all the data from oldXP + addxp to newxp
function reCalculate(addXP) {
let newXP = xp + parseInt(addXP);
console.log(newXP);
// Get current rank
if (newXP < 800) {
for(var i = 0; i < ranks.length; i++) {
for (let rank in ranks[i]) {
let nextIndex = indexes[i];
for (let nextRank in ranks[nextIndex]) {
if (newXP >= ranks[i][rank] && newXP < ranks[nextIndex][nextRank]) {
let newCurrRank = rank;
let newCurrRankXP = ranks[i][rank]
// Calculate xp till next Rank
if (i < 12) {
i++;
let newNextRank = ranks[i];
for (let r in newNextRank) {
let newRankUp = r;
let newPercent;
let diff = addXP;
let newProgress = progress+diff
let newRemain;
let newGoal;
let newTotal;
// If new Progress doesnt go over current total
if (newProgress < total) {
// Set new percent and new remain
let diffPercent=(diff/total)*100;
newRemain = remain - diff;
newTotal = total;
let elem = document.getElementById("myBar");
newPercent = percent + diffPercent;
elem.style.height = newPercent + "%";
document.getElementById('percent').innerHTML = newProgress + "|" + total;
// Update Display with new Data
document.getElementById('userXP').innerHTML = "XP: " + newXP;
document.getElementById('XP-Till').innerHTML = newRemain + "XP till your next Rank: " + rankUp;
document.getElementById('userRank').innerHTML = currRank;
// If new progress goes over current total
} else if (newProgress >= total) {
// Set new variables
newGoal = newNextRank[r];
newTotal = newGoal - newCurrRankXP;
newProgress = newXP - newCurrRankXP;
newRemain = newTotal - newProgress;
// Update percent from current percent to new percent
let elem = document.getElementById("myBar");
newPercent = Math.round((newProgress/newTotal)*100);
elem.style.height = newPercent + "%";
// Adds 1 to progress till progress == total
let progressDiff = total-progress;
document.getElementById('percent').innerHTML = newProgress + "|" + newTotal;
// Update Display with new Data
document.getElementById('XP-Till').innerHTML = newRemain + "XP till your next Rank: " + newRankUp;
document.getElementById('userXP').innerHTML = "XP: " + newXP;
document.getElementById('userRank').innerHTML = newCurrRank;
document.getElementById('percent').innerHTML = newProgress + "|" + newTotal;
}
// Set Main Variables to new values
currRank = newCurrRank;
rankUp = newRankUp;
remain = newRemain;
goal = newGoal;
total = newTotal;
progress = newProgress;
percent = newPercent;
xp = newXP;
}
}
}
}
}
}
} else {
// Display data for max level
document.getElementById('percent').innerHTML = '800|800';
currRank = "Master Digital Crafter";
remain = 0;
xp = 800;
rankUp = 'No higher Rank, Good Job!';
let elem = document.getElementById("myBar");
let percent = 100;
elem.style.height = percent + '%';
// Display Data
document.getElementById('userXP').innerHTML = "XP: " + xp;
document.getElementById('userRank').innerHTML = currRank;
document.getElementById('XP-Till').innerHTML = remain + 'XP till your next Rank: ' + rankUp;
}
}
function redirect(id) {
let url = '/users/quests?id=' + encodeURIComponent(id);
+197 -81
View File
@@ -40,6 +40,20 @@
margin-top: 10px;
margin-right: 15px;
}
.course-container {
position: absolute;
left: 1000px;
background: rgba(0, 0, 0, .2);
border-radius: 24px;
border-style: solid;
border-color: rgba(0, 0, 0, .2);
}
.course-content {
margin-left: 15px;
margin-top: 10px;
margin-right: 15px;
max-width: 200px;
}
.quest {
color: #e05f3a;
}
@@ -66,7 +80,7 @@
}
.buttons-container {
position: absolute;
top: 425px;
top: 380px;
right: 180px;
}
.button-content {
@@ -145,17 +159,9 @@
<button onClick='passData()' class='button-content btn btn-outline-info btn-sm'>Edit</button>
<a href="https://github.com/Braeden57/XP-System/issues" target="_blank" class='button-content btn btn-outline-info btn-sm'>Report Bug</a>
<button class="button-content btn btn-outline-info btn-sm" data-toggle="modal" data-target="#createQuest">New Quest</button>
<button class="button-content btn btn-outline-info btn-sm" data-toggle="modal" data-target="#createClass">New Class</button>
</div>
</div>
<!-- Passing user _id to edit page script -->
<script>
function passData() {
let url = '/users/edit?name=' + encodeURIComponent('<%= user._id %>');
document.location.href = url;
}
</script>
</div>
<br>
@@ -192,7 +198,7 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel"</h5>
<h5 class="modal-title" id="exampleModalLabel">Add XP</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -203,10 +209,6 @@
<input id='_id_add' type='text' name='_id_add' value='' style='display: none'></input>
<input id="current_xp" type="text" name="current_xp" value="" style="display: none"></input>
</div>
<div>
<p>Minimum: 1</p>
<p>Maximum: 50</p>
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input
@@ -214,7 +216,6 @@
name="amount"
type="number"
min="1"
max="50"
value=""
class="form-control"
>
@@ -236,7 +237,7 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createQuestLabel"</h5>
<h5 class="modal-title" id="createQuestLabel">Create Quest</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -309,68 +310,63 @@
</div>
</div>
<script>
// Creates unique ID
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// Create Div
function createDiv(type, message) {
let id = makeid(8);
if (type == 'error') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-warning alert-dismissible fade show" role="alert">';
}
if (type == 'message') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-info alert-dismissible fade show" role="alert">';
}
document.getElementById('new-div-'+id).innerHTML += '<div id="message'+id+'">'+message+'</div>';
document.getElementById('new-div-'+id).innerHTML += '<button id="close-btn-'+id+'" class="close" data-dismiss="alert" aria-label="Close"></button>';
document.getElementById('close-btn-'+id).innerHTML += '<span aria-hidden="true">&times;</span>';
document.getElementById('new-div-'+id).style.display = "block";
}
// Pass name to Modal
function passName(name, xp, member_id) {
if (xp < 800) {
document.getElementById("exampleModalLabel").innerHTML = "Give " + name + " XP";
document.getElementById("_id_add").value = member_id;
document.getElementById("current_xp").value = xp;
$("#exampleModal").modal()
}
if (xp >= 800) {
// Construct and show Max XP Alert
message = name + ' has reached max XP!';
createDiv('message', message);
}
}
// Catching url Parameters
window.onload = function () {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const successRate = urlParams.get('successRate')
if(successRate == 'addXP > 50') {
createDiv('error', "Can't add more than 50 XP")
}
if(successRate == 'addXP <= 0') {
createDiv('error', "XP Must be Greater than 0");
}
if(parseInt(successRate) > 0 && parseInt(successRate) < 50 || parseInt(successRate) == 50) {
createDiv('message', "Gave " + successRate + "XP to a Student");
}
if(successRate == 'createdQuest') {
createDiv('message', "Successfully Created Quest");
}
}
</script>
<!-- Create Class Modal -->
<div class="modal fade" id="createClass" tabindex="-1" role="dialog" aria-labelledby="createClassLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createClassLabel">Create Class</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="/users/createClass" method="POST">
<div>
<label for="name">Class Name</label>
<input
id="name"
name="name"
type="text"
value=""
class="form-control"
required
>
</div>
<div>
<label for="description">Class Description</label>
<textarea
id="description"
name="description"
value=""
class="form-control"
required
></textarea>
</div>
<div>
<label for="grade">Grade</label>
<input
id="grade"
name="grade"
type="number"
min="8"
max="12"
value=""
class="form-control"
required
>
</div>
<button type="submit" class="btn btn-primary">
Create Class
</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="members-container">
<!-- List of all site students (not teachers) -->
@@ -442,13 +438,126 @@
<!-- List of Quests -->
<div class='quest-content'>
<h2>All Quests</h2>
<ul>
<% quests.forEach(function (quest) { %>
<p class='quest'><%= quest.title %></p>
<li><button class='list-button btn btn-outline-success mt-2' onclick="redirect('<%= quest._id %>')"><%= quest.title %></button></li>
<% }) %>
</ul>
</div>
</div>
<div class="course-container">
<!-- List of Courses -->
<div class='course-content'>
<h2>All Courses</h2>
<% courses.forEach(function (course) { %>
<div class='course'>
<p><%= course.name %>: Grade <%= course.grade %></p>
</div>
<% }) %>
</div>
</div>
<!-- Script for show/hide user profile data -->
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io.connect('http://localhost:5000');
// Creates unique ID
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function passData() {
let url = '/users/edit?name=' + encodeURIComponent('<%= user._id %>');
document.location.href = url;
}
// Create Div
function createDiv(type, message) {
let id = makeid(8);
if (type == 'error') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-warning alert-dismissible fade show" role="alert">';
}
if (type == 'message') {
document.getElementById('div-container').innerHTML += '<div id="new-div-'+id+'" style="display: none;" class="alert alert-info alert-dismissible fade show" role="alert">';
}
document.getElementById('new-div-'+id).innerHTML += '<div id="message'+id+'">'+message+'</div>';
document.getElementById('new-div-'+id).innerHTML += '<button id="close-btn-'+id+'" class="close" data-dismiss="alert" aria-label="Close"></button>';
document.getElementById('close-btn-'+id).innerHTML += '<span aria-hidden="true">&times;</span>';
document.getElementById('new-div-'+id).style.display = "block";
}
// Pass name to Modal
function passName(name, xp, member_id) {
if (xp < 800) {
document.getElementById("exampleModalLabel").innerHTML = "Give " + name + " XP";
document.getElementById("_id_add").value = member_id;
document.getElementById("current_xp").value = xp;
$("#exampleModal").modal()
}
if (xp >= 800) {
// Construct and show Max XP Alert
message = name + ' has reached max XP!';
createDiv('message', message);
}
}
let getParameterByName = function() {
let queries = location.search.substring(1).split('&'),
processed = {};
for (let query of queries) {
let [name, value] = query.split('=');
processed[decodeURIComponent(name)] = value? decodeURIComponent(value) : '';
}
return function(name) {
if (typeof processed[name] !== 'undefined')
return processed[name];
else
return null;
};
}();
function updateXP(addXP, id) {
let data = {
userID: id,
addXP: addXP
}
socket.emit('add:xp', data);
}
// Catching url Parameters
window.onload = function () {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const successRate = urlParams.get('successRate')
if(successRate == 'addXP > 50') {
createDiv('error', "Can't add more than 50 XP")
}
if(successRate == 'addXP <= 0') {
createDiv('error', "XP Must be Greater than 0");
}
if(parseInt(successRate) > 0) {
createDiv('message', "Gave " + successRate + "XP to a Student");
updateXP(successRate, getParameterByName('id'));
}
if(successRate == 'createdQuest') {
createDiv('message', "Successfully Created Quest");
}
if(successRate == 'createdClass') {
createDiv('message', "Successfully created Course");
}
}
// Script for show/hide user profile data
function openProfile(id) {
let state = document.getElementById(id).style.display;
if (state == "block") {
@@ -457,4 +566,11 @@
document.getElementById(id).style.display = 'block';
}
}
// Script for redirecting to selected Quest
function redirect(id) {
let url = '/users/quests?id=' + encodeURIComponent(id);
document.location.href = url;
}
</script>