├── .prettierrc
├── public
├── favicon.png
├── our-team.png
└── logo_transparent.png
├── styles
├── ATS.module.css
├── globals.css
├── Home.module.css
├── antd.less
└── ListingsPage.module.css
├── pages
├── _app.js
├── api
│ ├── pipeline.js
│ ├── jobListing
│ │ └── [id].js
│ ├── applicants
│ │ └── [id].js
│ ├── cv
│ │ └── [id].js
│ ├── jobs
│ │ └── [id].js
│ ├── me.js
│ ├── cv.js
│ ├── jobs.js
│ ├── applicants.js
│ ├── auth.js
│ └── users.js
├── signup.js
├── jobs.js
├── login.js
├── ats.js
├── index.js
└── jobs
│ └── [id].js
├── .eslintrc.json
├── .gitignore
├── middleware
└── database.js
├── next.config.js
├── package.json
├── README.md
├── components
├── addJobModal.js
├── editJobModal.js
├── applicantView.js
├── applicants.js
├── jobListings.js
├── insertApplicantModal.js
└── viewApplicantModal.js
└── LICENSE
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baykamsay/simple-ats/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/our-team.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baykamsay/simple-ats/HEAD/public/our-team.png
--------------------------------------------------------------------------------
/public/logo_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baykamsay/simple-ats/HEAD/public/logo_transparent.png
--------------------------------------------------------------------------------
/styles/ATS.module.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 64px;
3 | margin: 0 56px 0 0;
4 | float: left;
5 | }
6 |
7 | .siteLayoutBackground {
8 | background: #fff;
9 | }
10 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | a {
8 | color: inherit;
9 | text-decoration: none;
10 | }
11 |
12 | * {
13 | box-sizing: border-box;
14 | }
15 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/antd.less";
2 |
3 | // eslint-disable-next-line react/prop-types
4 | function MyApp({ Component, pageProps }) {
5 | return ;
6 | }
7 |
8 | export default MyApp;
9 |
--------------------------------------------------------------------------------
/pages/api/pipeline.js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../middleware/database";
3 |
4 | const handler = nextConnect();
5 |
6 | handler.use(middleware);
7 |
8 | handler.get(async (req, res) => {
9 | let doc = await req.db.collection("pipeline").findOne();
10 | res.json(doc.pipeline);
11 | });
12 |
13 | export default handler;
14 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .main {
11 | width: 100%;
12 | flex: 1;
13 | }
14 |
15 | .footer {
16 | width: 100%;
17 | height: 100px;
18 | border-top: 1px solid #eaeaea;
19 | display: flex;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
--------------------------------------------------------------------------------
/pages/api/jobListing/[id].js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db
11 | .collection("jobs")
12 | .findOne({ _id: ObjectId(req.query.id) });
13 | res.json(doc);
14 | });
15 |
16 | export default handler;
17 |
--------------------------------------------------------------------------------
/pages/api/applicants/[id].js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db
11 | .collection("applicants")
12 | .findOne({ _id: ObjectId(req.query.id) });
13 | res.json(doc);
14 | });
15 |
16 | export default handler;
17 |
--------------------------------------------------------------------------------
/pages/api/cv/[id].js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db
11 | .collection("cvs")
12 | .findOne({ _id: ObjectId(req.query.id) });
13 | doc.file = doc.file.buffer;
14 | res.json(doc);
15 | });
16 |
17 | export default handler;
18 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:react/recommended",
5 | "prettier",
6 | "prettier/react"
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 11,
10 | "sourceType": "module",
11 | "ecmaFeatures": {
12 | "jsx": true
13 | }
14 | },
15 | "env": {
16 | "es6": true,
17 | "browser": true,
18 | "node": true
19 | },
20 | "rules": { "react/react-in-jsx-scope": "off", "react/prop-types": 0 },
21 | "globals": { "React": "writable" }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/middleware/database.js:
--------------------------------------------------------------------------------
1 | import { MongoClient } from "mongodb";
2 | import nextConnect from "next-connect";
3 |
4 | const client = new MongoClient(
5 | process.env.DB_URL, // put your db url in .env.local
6 | {
7 | useNewUrlParser: true,
8 | useUnifiedTopology: true,
9 | }
10 | );
11 |
12 | async function database(req, res, next) {
13 | if (!client.isConnected()) await client.connect();
14 | req.dbClient = client;
15 | req.db = client.db("ATS");
16 | return next();
17 | }
18 |
19 | const middleware = nextConnect();
20 |
21 | middleware.use(database);
22 |
23 | export default middleware;
24 |
--------------------------------------------------------------------------------
/pages/api/jobs/[id].js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db
11 | .collection("jobs")
12 | .findOne({ _id: ObjectId(req.query.id) });
13 | const applicantIds = doc.applicants;
14 | const applicants = await Promise.all(
15 | applicantIds.map(
16 | async (applicantId) =>
17 | await req.db.collection("applicants").findOne({ _id: applicantId })
18 | )
19 | );
20 | res.json(applicants);
21 | });
22 |
23 | export default handler;
24 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withSass = require("@zeit/next-sass");
2 | const withLess = require("@zeit/next-less");
3 | const withCSS = require("@zeit/next-css");
4 |
5 | const isProd = process.env.NODE_ENV === "production";
6 |
7 | // fix: prevents error when .less files are required by node
8 | if (typeof require !== "undefined") {
9 | require.extensions[".less"] = (file) => {};
10 | }
11 |
12 | module.exports = withCSS({
13 | cssModules: true,
14 | cssLoaderOptions: {
15 | importLoaders: 1,
16 | localIdentName: "[local]___[hash:base64:5]",
17 | },
18 | ...withLess(
19 | withSass({
20 | lessLoaderOptions: {
21 | javascriptEnabled: true,
22 | },
23 | })
24 | ),
25 | });
26 |
--------------------------------------------------------------------------------
/pages/api/me.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const jwtSecret = process.env.JWT_SECRET;
3 |
4 | export default (req, res) => {
5 | if (req.method === "GET") {
6 | if (!("token" in req.cookies)) {
7 | res.status(401).json({ message: "Unable to auth" });
8 | return;
9 | }
10 | let decoded;
11 | const token = req.cookies.token;
12 | if (token) {
13 | try {
14 | decoded = jwt.verify(token, jwtSecret);
15 | } catch (e) {
16 | console.error(e);
17 | }
18 | }
19 |
20 | if (decoded) {
21 | res.json(decoded);
22 | return;
23 | } else {
24 | res.status(401).json({ message: "Unable to auth" });
25 | }
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/pages/api/cv.js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../middleware/database";
3 | import { Binary } from "mongodb";
4 | import formidable from "formidable";
5 |
6 | const handler = nextConnect();
7 |
8 | handler.use(middleware);
9 |
10 | handler.post(async (req, res) => {
11 | const form = formidable({ multiples: true });
12 |
13 | await form.parse(req, (err, fields, files) => {
14 | console.log("fields:", fields);
15 | console.log("files:", files);
16 | });
17 |
18 | console.log(form);
19 |
20 | let data = {};
21 | data.file = Binary(req.body);
22 |
23 | let doc = await req.db.collection("cvs").insertOne(data);
24 |
25 | res.json({ message: doc.insertedId });
26 | });
27 |
28 | export default handler;
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-ats",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@zeit/next-css": "^1.0.1",
12 | "@zeit/next-less": "^1.0.1",
13 | "@zeit/next-sass": "^1.0.1",
14 | "antd": "^4.5.2",
15 | "babel-plugin-import": "^1.13.0",
16 | "bcrypt": "^5.0.0",
17 | "file-saver": "^2.0.2",
18 | "formidable": "^1.2.2",
19 | "isomorphic-unfetch": "^3.0.0",
20 | "js-cookie": "^2.2.1",
21 | "jsonwebtoken": "^8.5.1",
22 | "less": "^3.12.2",
23 | "mongodb": "^3.6.0",
24 | "next": "9.5.1",
25 | "next-connect": "^0.8.1",
26 | "react": "16.13.1",
27 | "react-dom": "16.13.1",
28 | "react-markdown": "^4.3.1",
29 | "swr": "^0.3.0",
30 | "uuid": "^8.3.0"
31 | },
32 | "devDependencies": {
33 | "eslint": "^7.6.0",
34 | "eslint-config-prettier": "^6.11.0",
35 | "eslint-plugin-react": "^7.20.5",
36 | "prettier": "2.0.5"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/styles/antd.less:
--------------------------------------------------------------------------------
1 | @import "~antd/dist/antd.less";
2 |
3 | @primary-color: #1890ff; // primary color for all components
4 | @link-color: #1890ff; // link color
5 | @success-color: #52c41a; // success state color
6 | @warning-color: #faad14; // warning state color
7 | @error-color: #f5222d; // error state color
8 | @font-size-base: 14px; // major text font size
9 | @heading-color: rgba(0, 0, 0, 0.85); // heading text color
10 | @text-color: rgba(0, 0, 0, 0.65); // major text color
11 | @text-color-secondary: rgba(0, 0, 0, 0.45); // secondary text color
12 | @disabled-color: rgba(0, 0, 0, 0.25); // disable state color
13 | @border-radius-base: 2px; // major border radius
14 | @border-color-base: #d9d9d9; // major border color
15 | @box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
16 | 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers
17 | @heading-1-size: ceil(@font-size-base * 4);
18 | @heading-2-size: ceil(@font-size-base * 3);
19 | @heading-3-size: ceil(@font-size-base * 2.5);
20 | @heading-4-size: ceil(@font-size-base * 2);
21 | @heading-5-size: ceil(@font-size-base * 1.5);
22 | @typography-title-font-weight: 600;
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple ATS
2 |
3 | Simple ATS is an applicant tracking system with a job listings page.
4 |
5 | ## Features
6 | Applicant side: a landing page, job listings page, and pages for each job listing that are generated from markdown.
7 | Company side: View applicants, access form data, add notes and ratings, create new job listings.
8 |
9 | ## Installation
10 | This is a template repository so just click the "Use this template" button!
11 |
12 | ## Usage
13 | This project uses a MongoDB database. Create your own database and then fill out the environment variables with the following:
14 | - DB_URL= `Your database connection url here`
15 | - JWT_SECRET= `Your jwt secret here`
16 | - URL= `Your hosted url here`
17 |
18 | For demo purposes the api is unprotected. Protecting your api is highly recommended.
19 | The signup route is also unprotected, you should secure this route in production.
20 |
21 | ## Demo
22 | The demo is available at https://ats.demos.baykam.me/.
23 | To access the applicant tracking system visit https://ats.demos.baykam.me/ats.
24 |
25 | ## Contributing
26 | Pull requests are welcome.
27 |
28 | ## License
29 | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
30 |
--------------------------------------------------------------------------------
/pages/api/jobs.js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db.collection("jobs").find().toArray();
11 | res.json(doc);
12 | });
13 |
14 | handler.post(async (req, res) => {
15 | let data = req.body;
16 | data = JSON.parse(data);
17 | await req.db.collection("jobs").insertOne({
18 | title: data.title,
19 | location: data.location,
20 | description: data.description,
21 | applicants: [],
22 | });
23 | res.json({ message: "ok" });
24 | });
25 |
26 | handler.put(async (req, res) => {
27 | let data = req.body;
28 | data = JSON.parse(data);
29 |
30 | await req.db
31 | .collection("jobs")
32 | .updateOne(
33 | { _id: ObjectId(data.id) },
34 | {
35 | $set: {
36 | title: data.title,
37 | location: data.location,
38 | description: data.description,
39 | },
40 | },
41 | function (err) {
42 | if (err) throw err;
43 | }
44 | );
45 | res.json({ message: "ok" });
46 | });
47 |
48 | handler.delete(async (req, res) => {
49 | let id = req.body;
50 | id = JSON.parse(id);
51 | await req.db
52 | .collection("jobs")
53 | .deleteOne({ _id: ObjectId(id) }, function (err) {
54 | if (err) throw err;
55 | });
56 | res.json({ message: "ok" });
57 | });
58 |
59 | export default handler;
60 |
--------------------------------------------------------------------------------
/pages/api/applicants.js:
--------------------------------------------------------------------------------
1 | import nextConnect from "next-connect";
2 | import middleware from "../../middleware/database";
3 | import { ObjectId } from "mongodb";
4 |
5 | const handler = nextConnect();
6 |
7 | handler.use(middleware);
8 |
9 | handler.get(async (req, res) => {
10 | let doc = await req.db.collection("applicants").find().toArray();
11 | res.json(doc);
12 | });
13 |
14 | handler.post(async (req, res) => {
15 | let data = req.body;
16 | data = JSON.parse(data);
17 | const listing = data.listing;
18 | delete data.listing;
19 | data.stage = "Applied";
20 | data.notes = "";
21 | data.rating = 0;
22 | data.cv = ObjectId(data.cv[0].response.message);
23 | let doc = await req.db.collection("applicants").insertOne(data);
24 | await req.db
25 | .collection("jobs")
26 | .updateOne(
27 | { _id: ObjectId(listing) },
28 | { $push: { applicants: doc.insertedId } }
29 | );
30 | res.json({ message: "ok" });
31 | });
32 |
33 | handler.put(async (req, res) => {
34 | let data = req.body;
35 | data = JSON.parse(data);
36 |
37 | await req.db
38 | .collection("applicants")
39 | .updateOne(
40 | { _id: ObjectId(data.id) },
41 | { $set: { stage: data.stage, notes: data.notes, rating: data.rating } },
42 | function (err) {
43 | if (err) throw err;
44 | }
45 | );
46 | res.json({ message: "ok" });
47 | });
48 |
49 | handler.delete(async (req, res) => {
50 | let id = req.body;
51 | id = JSON.parse(id);
52 | await req.db
53 | .collection("applicants")
54 | .deleteOne({ _id: ObjectId(id) }, function (err) {
55 | if (err) throw err;
56 | });
57 | await req.db
58 | .collection("jobs")
59 | .updateMany({}, { $pull: { applicants: ObjectId(id) } });
60 | res.json({ message: "ok" });
61 | });
62 |
63 | export default handler;
64 |
--------------------------------------------------------------------------------
/components/addJobModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal, Button, Form, Input } from "antd";
3 |
4 | const layout = {
5 | labelCol: { span: 4 },
6 | wrapperCol: { span: 20 },
7 | };
8 |
9 | export default function AddJobModal(props) {
10 | const [form] = Form.useForm();
11 | async function handleSubmit(e) {
12 | await fetch("/api/jobs", {
13 | method: "post",
14 | body: JSON.stringify(e),
15 | });
16 | form.resetFields();
17 | props.close();
18 | }
19 | return (
20 |
31 |
39 |
40 |
41 |
46 |
47 |
48 |
53 |
54 |
55 |
61 |
62 | Create
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/pages/signup.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Router from "next/router";
3 | import cookie from "js-cookie";
4 |
5 | const Signup = () => {
6 | const [signupError, setSignupError] = useState("");
7 | const [username, setUsername] = useState("");
8 | const [password, setPassword] = useState("");
9 | // const [passwordConfirmation, setPasswordConfirmation] = useState("");
10 |
11 | function handleSubmit(e) {
12 | e.preventDefault();
13 | fetch("/api/users", {
14 | method: "POST",
15 | headers: {
16 | "Content-Type": "application/json",
17 | },
18 | body: JSON.stringify({
19 | username,
20 | password,
21 | }),
22 | })
23 | .then((r) => r.json())
24 | .then((data) => {
25 | if (data && data.error) {
26 | setSignupError(data.message);
27 | }
28 | if (data && data.token) {
29 | //set cookie
30 | cookie.set("token", data.token, { expires: 2 });
31 | Router.push("/");
32 | }
33 | });
34 | }
35 | return (
36 |
65 | );
66 | };
67 |
68 | export default Signup;
69 |
--------------------------------------------------------------------------------
/pages/jobs.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import Link from "next/link";
3 | import styles from "../styles/Home.module.css";
4 | import listingStyles from "../styles/ListingsPage.module.css";
5 | import { Row, Col, Divider, List } from "antd";
6 |
7 | function Jobs({ jobs }) {
8 | return (
9 |
10 |
11 |
Jobs
12 |
13 |
14 |
15 |
16 |
17 |
18 | Job Openings
19 |
20 | (
24 |
27 |
28 | Apply
29 |
30 | ,
31 | ]}
32 | >
33 | {item.title}} />
34 | {item.location}
35 |
36 | )}
37 | />
38 |
39 |
40 |
41 |
42 |
51 |
52 | );
53 | }
54 |
55 | export async function getServerSideProps() {
56 | return fetch(`${process.env.URL}api/jobs`).then(
57 | async (res) => {
58 | const jobs = await res.json();
59 | return {
60 | props: {
61 | jobs,
62 | },
63 | };
64 | },
65 | () => {
66 | return {
67 | props: {
68 | jobs: [{}],
69 | },
70 | };
71 | }
72 | );
73 | }
74 |
75 | export default Jobs;
76 |
--------------------------------------------------------------------------------
/pages/api/auth.js:
--------------------------------------------------------------------------------
1 | const MongoClient = require("mongodb").MongoClient;
2 | const assert = require("assert");
3 | const bcrypt = require("bcrypt");
4 | const jwt = require("jsonwebtoken");
5 | const jwtSecret = process.env.JWT_SECRET; // put your jwt secret in env.local
6 |
7 | // const saltRounds = 10;
8 | const dbName = "ATS";
9 | const client = new MongoClient(
10 | process.env.DB_URL, // put your db url in .env.local
11 | {
12 | useNewUrlParser: true,
13 | useUnifiedTopology: true,
14 | }
15 | );
16 |
17 | function findUser(db, username, callback) {
18 | const collection = db.collection("user");
19 | collection.findOne({ username }, callback);
20 | }
21 |
22 | function authUser(db, username, password, hash, callback) {
23 | // const collection = db.collection("user");
24 | bcrypt.compare(password, hash, callback);
25 | }
26 |
27 | export default (req, res) => {
28 | if (req.method === "POST") {
29 | //login
30 | try {
31 | assert.notEqual(null, req.body.username, "Email required");
32 | assert.notEqual(null, req.body.password, "Password required");
33 | } catch (bodyError) {
34 | res.status(403).send(bodyError.message);
35 | }
36 |
37 | client.connect(function (err) {
38 | assert.equal(null, err);
39 | console.log("Connected to MongoDB server =>");
40 | const db = client.db(dbName);
41 | const username = req.body.username;
42 | const password = req.body.password;
43 |
44 | findUser(db, username, function (err, user) {
45 | if (err) {
46 | res.status(500).json({ error: true, message: "Error finding User" });
47 | return;
48 | }
49 | if (!user) {
50 | res.status(404).json({ error: true, message: "User not found" });
51 | return;
52 | } else {
53 | authUser(db, username, password, user.password, function (
54 | err,
55 | match
56 | ) {
57 | if (err) {
58 | res.status(500).json({ error: true, message: "Auth Failed" });
59 | }
60 | if (match) {
61 | const token = jwt.sign(
62 | { userId: user.userId, username: user.username },
63 | jwtSecret
64 | );
65 | res.status(200).json({ token });
66 | return;
67 | } else {
68 | res.status(401).json({ error: true, message: "Auth Failed" });
69 | return;
70 | }
71 | });
72 | }
73 | });
74 | });
75 | } else {
76 | // Handle any other HTTP method
77 | res.statusCode = 401;
78 | res.end();
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/styles/ListingsPage.module.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
2 |
3 | h1,
4 | h2,
5 | h3,
6 | h4,
7 | h5,
8 | h6,
9 | p {
10 | font-family: "Roboto", sans-serif;
11 | color: #141414;
12 | }
13 |
14 | h1 {
15 | font-weight: 700;
16 | font-size: 4rem;
17 | margin-bottom: 0;
18 | }
19 |
20 | h2 {
21 | font-weight: 500;
22 | font-size: 3rem;
23 | }
24 |
25 | h3 {
26 | font-weight: 500;
27 | font-size: 2.5rem;
28 | }
29 |
30 | h4 {
31 | font-weight: 400;
32 | font-size: 2rem;
33 | }
34 |
35 | h5 {
36 | font-weight: 400;
37 | font-size: 1.5rem;
38 | }
39 |
40 | p {
41 | font-size: 1rem;
42 | color: #141414;
43 | }
44 |
45 | li {
46 | font-size: 1rem;
47 | color: #141414;
48 | }
49 |
50 | a.link {
51 | font-family: "Roboto", sans-serif;
52 | font-size: 1.2rem;
53 | }
54 |
55 | .primary {
56 | color: #ffffff;
57 | }
58 |
59 | .secondary {
60 | color: #141414;
61 | }
62 |
63 | .primary_b {
64 | background-color: #ffffff;
65 | }
66 |
67 | .secondary_b {
68 | background-color: #141414;
69 | }
70 |
71 | .margin_bottom {
72 | margin-bottom: 0;
73 | }
74 |
75 | .mission {
76 | font-weight: 400;
77 | font-size: 2rem;
78 | margin-bottom: 0;
79 | }
80 |
81 | a.button {
82 | display: inline-block;
83 | padding: 0.35em 1.2em;
84 | border: 0.1em solid #ffffff;
85 | margin: 1rem 0.3em 0.3em 0;
86 | border-radius: 0.12em;
87 | box-sizing: border-box;
88 | text-decoration: none;
89 | font-family: "Roboto", sans-serif;
90 | font-weight: 300;
91 | color: #ffffff;
92 | text-align: center;
93 | transition: all 0.2s;
94 | }
95 | a.button:hover {
96 | color: #000000;
97 | background-color: #ffffff;
98 | }
99 | @media all and (max-width: 30em) {
100 | a.button {
101 | display: block;
102 | margin: 0.4em auto;
103 | }
104 | }
105 |
106 | a.button_s {
107 | display: inline-block;
108 | padding: 0.35em 1.2em;
109 | border: 0.1em solid #141414;
110 | margin: 1rem 0.3em 0.3em 0;
111 | border-radius: 0.12em;
112 | box-sizing: border-box;
113 | text-decoration: none;
114 | font-family: "Roboto", sans-serif;
115 | font-weight: 300;
116 | color: #141414;
117 | text-align: center;
118 | transition: all 0.2s;
119 | scale: 2;
120 | }
121 | a.button_s:hover {
122 | color: #ffffff;
123 | background-color: #141414;
124 | }
125 | @media all and (max-width: 30em) {
126 | a.button_s {
127 | display: block;
128 | margin: 0.4em auto;
129 | }
130 | }
131 |
132 | .list {
133 | font-family: "Roboto", sans-serif;
134 | color: #141414;
135 | font-size: 1rem;
136 | }
137 |
--------------------------------------------------------------------------------
/components/editJobModal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Modal, Button, Form, Input } from "antd";
3 |
4 | const layout = {
5 | labelCol: { span: 4 },
6 | wrapperCol: { span: 20 },
7 | };
8 |
9 | export default function EditJobModal(props) {
10 | const [form] = Form.useForm();
11 |
12 | useEffect(() => {
13 | form.resetFields();
14 | }, [props.data]);
15 |
16 | async function handleSubmit(e) {
17 | e.id = props.data._id;
18 | await fetch("/api/jobs", {
19 | method: "put",
20 | body: JSON.stringify(e),
21 | });
22 | props.close();
23 | }
24 |
25 | async function deleteListing() {
26 | await fetch("/api/jobs", {
27 | method: "delete",
28 | body: JSON.stringify(props.data._id),
29 | });
30 | props.close();
31 | }
32 |
33 | return (
34 |
44 |
57 |
58 |
59 |
65 |
66 |
67 |
73 |
74 |
75 |
81 |
86 | Save
87 |
88 |
94 | Delete
95 |
96 |
97 |
98 |
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/pages/api/users.js:
--------------------------------------------------------------------------------
1 | const MongoClient = require("mongodb").MongoClient;
2 | const assert = require("assert");
3 | const bcrypt = require("bcrypt");
4 | const v4 = require("uuid").v4;
5 | const jwt = require("jsonwebtoken");
6 | const jwtSecret = process.env.JWT_SECRET; // put your jwt secret in env.local
7 |
8 | const saltRounds = 10;
9 | const dbName = "ATS";
10 | const client = new MongoClient(
11 | process.env.DB_URL, // put your db url in .env.local
12 | {
13 | useNewUrlParser: true,
14 | useUnifiedTopology: true,
15 | }
16 | );
17 |
18 | function findUser(db, username, callback) {
19 | const collection = db.collection("user");
20 | collection.findOne({ username }, callback);
21 | }
22 |
23 | function createUser(db, username, password, callback) {
24 | const collection = db.collection("user");
25 | bcrypt.hash(password, saltRounds, function (err, hash) {
26 | // Store hash in your password DB.
27 | collection.insertOne(
28 | {
29 | userId: v4(),
30 | username,
31 | password: hash,
32 | },
33 | function (err, userCreated) {
34 | assert.equal(err, null);
35 | callback(userCreated);
36 | }
37 | );
38 | });
39 | }
40 |
41 | export default (req, res) => {
42 | if (req.method === "POST") {
43 | // signup
44 | try {
45 | assert.notEqual(null, req.body.username, "Username required");
46 | assert.notEqual(null, req.body.password, "Password required");
47 | } catch (bodyError) {
48 | return res.status(403).json({ error: true, message: bodyError.message });
49 | }
50 |
51 | // verify username does not exist already
52 | client.connect(function (err) {
53 | assert.equal(null, err);
54 | console.log("Connected to MongoDB server =>");
55 | const db = client.db(dbName);
56 | const username = req.body.username;
57 | const password = req.body.password;
58 |
59 | findUser(db, username, function (err, user) {
60 | if (err) {
61 | res.status(500).json({ error: true, message: "Error finding User" });
62 | return;
63 | }
64 | if (!user) {
65 | // proceed to Create
66 | createUser(db, username, password, function (creationResult) {
67 | if (creationResult.ops.length === 1) {
68 | const user = creationResult.ops[0];
69 | const token = jwt.sign(
70 | { userId: user.userId, username: user.username },
71 | jwtSecret
72 | // , {
73 | // expiresIn: 3000, //50 minutes
74 | // }
75 | );
76 | res.status(200).json({ token });
77 | return;
78 | }
79 | });
80 | } else {
81 | // User exists
82 | res.status(403).json({ error: true, message: "Username exists" });
83 | return;
84 | }
85 | });
86 | });
87 | } else {
88 | // Handle any other HTTP method
89 | res.status(200).json({ users: ["John Doe"] });
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/components/applicantView.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import fetch from "isomorphic-unfetch";
3 | import useSWR, { mutate } from "swr";
4 | import { List, Spin, Card, Tag, Row, Col, Rate } from "antd";
5 | import ViewApplicantModal from "./viewApplicantModal";
6 |
7 | export default function ApplicantView(props) {
8 | const { data, error } = useSWR(`/api/jobs/${props.data}`, async function (
9 | args
10 | ) {
11 | const res = await fetch(args);
12 | return res.json();
13 | });
14 | const [modalVisible, setModalVisible] = useState(false);
15 | const [applicantData, setApplicantData] = useState({});
16 | function setColor(stage) {
17 | let color;
18 | switch (stage) {
19 | case "Applied":
20 | color = "magenta";
21 | break;
22 | case "Interview":
23 | color = "gold";
24 | break;
25 | case "Offer":
26 | color = "green";
27 | break;
28 | default:
29 | color = "blue";
30 | break;
31 | }
32 | return color;
33 | }
34 | if (error) return failed to load
;
35 | if (!data)
36 | return (
37 |
45 |
46 |
47 | );
48 | return (
49 |
50 | {
54 | mutate(`/api/jobs/${props.data}`);
55 | setModalVisible(false);
56 | }}
57 | pipeline={props.pipeline}
58 | />
59 | (
64 |
65 | {
70 | setApplicantData(item);
71 | setModalVisible(true);
72 | }}
73 | >
74 |
75 |
76 | {item.name}
77 |
78 |
79 | {item.stage}
80 |
81 |
82 |
83 |
84 |
88 | {item.introduction}
89 |
90 |
91 |
92 |
93 | )}
94 | />
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/components/applicants.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Layout,
4 | Menu,
5 | Typography,
6 | Divider,
7 | Spin,
8 | Row,
9 | Col,
10 | Button,
11 | } from "antd";
12 | import fetch from "isomorphic-unfetch";
13 | import useSWR from "swr";
14 | import styles from "../styles/ATS.module.css";
15 | import homeStyle from "../styles/Home.module.css";
16 | import { PlusOutlined } from "@ant-design/icons";
17 | import InsertApplicantModal from "./insertApplicantModal";
18 | import ApplicantView from "./applicantView";
19 |
20 | const { Content, Sider } = Layout;
21 | const { Title } = Typography;
22 |
23 | function Applicants(props) {
24 | const [
25 | insertApplicantModalVisible,
26 | setInsertApplicantModalVisible,
27 | ] = useState(false);
28 | const [selectedListing, setSelectedListing] = useState(props.data.initialId);
29 | const { data, error } = useSWR("/api/jobs", async function (args) {
30 | const res = await fetch(args);
31 | return res.json();
32 | });
33 | if (error) return failed to load
;
34 | if (!data)
35 | return (
36 |
37 |
38 |
39 | );
40 | return (
41 |
45 | {
48 | setInsertApplicantModalVisible(false);
49 | }}
50 | />
51 |
52 |
53 |
54 | Applicants
55 |
56 |
57 |
58 | }
61 | size="large"
62 | onClick={() => {
63 | setInsertApplicantModalVisible(true);
64 | }}
65 | >
66 | Manual Insert
67 |
68 |
69 |
70 |
71 |
72 |
73 | {
78 | setSelectedListing(e.item.props.data);
79 | }}
80 | >
81 | {data.map((job, i) => (
82 |
83 | {job.title}
84 |
85 | ))}
86 |
87 |
88 |
93 |
97 |
98 |
99 |
100 | );
101 | }
102 |
103 | export default Applicants;
104 |
--------------------------------------------------------------------------------
/pages/login.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Router from "next/router";
3 | import cookie from "js-cookie";
4 | import Head from "next/head";
5 | import { Layout, Form, Input, Button, message, Typography } from "antd";
6 |
7 | const { Text } = Typography;
8 | const layout = {
9 | labelCol: { span: 8 },
10 | wrapperCol: { span: 16 },
11 | };
12 | const tailLayout = {
13 | wrapperCol: { offset: 8, span: 16 },
14 | };
15 | const { Content, Footer } = Layout;
16 |
17 | const Login = () => {
18 | function handleSubmit(e) {
19 | const username = e.username;
20 | const password = e.password;
21 | //call api
22 | fetch("/api/auth", {
23 | method: "POST",
24 | headers: {
25 | "Content-Type": "application/json",
26 | },
27 | body: JSON.stringify({
28 | username,
29 | password,
30 | }),
31 | })
32 | .then((r) => {
33 | return r.json();
34 | })
35 | .then(async (data) => {
36 | if (data && data.error) {
37 | message.error(data.message);
38 | }
39 | if (data && data.token) {
40 | //set cookie
41 | cookie.set("token", data.token, {
42 | expires: 2,
43 | secure: true,
44 | sameSite: "strict",
45 | });
46 | Router.push("/ats");
47 | }
48 | });
49 | }
50 |
51 | useEffect(() => {
52 | Router.prefetch("/ats");
53 | }, []);
54 |
55 | return (
56 |
57 |
58 |
Login - Simple ATS
59 |
60 |
61 |
62 |
63 |
70 |
88 |
89 |
90 |
91 |
98 |
99 |
100 |
101 |
102 |
103 | Submit
104 |
105 |
106 |
107 | Demo Username: guest Demo Password: guest
108 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | };
116 |
117 | export default Login;
118 |
--------------------------------------------------------------------------------
/components/jobListings.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import fetch from "isomorphic-unfetch";
3 | import useSWR, { mutate } from "swr";
4 | import {
5 | Layout,
6 | List,
7 | Card,
8 | Typography,
9 | Divider,
10 | Button,
11 | Row,
12 | Col,
13 | Spin,
14 | } from "antd";
15 | import styles from "../styles/ATS.module.css";
16 | import homeStyle from "../styles/Home.module.css";
17 | import { PlusOutlined } from "@ant-design/icons";
18 | import AddJobModal from "./addJobModal";
19 | import EditJobModal from "./editJobModal";
20 |
21 | const { Content } = Layout;
22 | const { Title } = Typography;
23 |
24 | export default function JobListings() {
25 | const [newJobModalVisible, setNewJobModalVisible] = useState(false);
26 | const [editJobModalVisible, setEditJobModalVisible] = useState(false);
27 | const [selectedListing, setSelectedListing] = useState({});
28 | const { data, error } = useSWR("/api/jobs", async function (args) {
29 | const res = await fetch(args);
30 | return res.json();
31 | });
32 | if (error) return failed to load
;
33 | if (!data)
34 | return (
35 |
36 |
37 |
38 | );
39 |
40 | return (
41 |
45 | {
48 | mutate("/api/jobs");
49 | setNewJobModalVisible(false);
50 | }}
51 | />
52 | {
56 | mutate("/api/jobs");
57 | setEditJobModalVisible(false);
58 | }}
59 | />
60 |
66 |
67 |
68 |
69 | Job Listings
70 |
71 |
72 |
73 | }
76 | size="large"
77 | onClick={() => {
78 | setNewJobModalVisible(true);
79 | }}
80 | >
81 | Add Listing
82 |
83 |
84 |
85 |
86 | (
98 |
99 | {
102 | setSelectedListing(item);
103 | setEditJobModalVisible(true);
104 | }}
105 | title={item.title}
106 | >
107 | {item.description.length <= 88
108 | ? item.description
109 | : `${item.description.substr(0, 85)}...`}
110 |
111 |
112 | )}
113 | />
114 |
115 |
116 | );
117 | }
118 |
--------------------------------------------------------------------------------
/pages/ats.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Head from "next/head";
3 | import fetch from "isomorphic-unfetch";
4 | import useSWR from "swr";
5 | import Router from "next/router";
6 | import cookie from "js-cookie";
7 | import { Layout, Menu, Spin, Row, Col, Button } from "antd";
8 | import styles from "../styles/ATS.module.css";
9 | import homeStyle from "../styles/Home.module.css";
10 | import Applicants from "../components/applicants";
11 | import JobListings from "../components/jobListings";
12 |
13 | const { Header, Content } = Layout;
14 |
15 | function ATS({ staticProps }) {
16 | const { data, revalidate } = useSWR("/api/me", async function (args) {
17 | const res = await fetch(args);
18 | return res.json();
19 | });
20 | const [componentId, setComponentId] = useState("1");
21 |
22 | useEffect(() => {
23 | if (data && !data.username && !cookie.get("token")) {
24 | Router.replace("/login");
25 | }
26 | }, [data]);
27 |
28 | if (!data || !data.username)
29 | return (
30 |
31 |
32 |
33 | );
34 |
35 | return (
36 |
37 |
38 |
Simple ATS
39 |
40 |
41 |
42 |
43 |
50 |
51 |
52 |
57 | {
62 | setComponentId(item.key);
63 | }}
64 | >
65 | Applicants
66 | Job Listings
67 | Settings
68 |
69 |
70 |
71 | {
73 | cookie.remove("token");
74 | revalidate();
75 | }}
76 | >
77 | Sign Out
78 |
79 |
80 |
81 |
82 |
87 | {componentId === "1" && }
88 | {componentId === "2" && }
89 |
90 |
91 |
92 | );
93 | }
94 |
95 | export async function getServerSideProps() {
96 | return fetch(`${process.env.URL}api/jobs`).then(
97 | async (res) => {
98 | const jobs = await res.json();
99 | const res2 = await fetch(`${process.env.URL}api/pipeline`);
100 | const pipeline = await res2.json();
101 | return {
102 | props: {
103 | staticProps: {
104 | initialId: jobs[0]._id,
105 | pipeline: pipeline,
106 | },
107 | },
108 | };
109 | },
110 | () => {
111 | return {
112 | props: {
113 | staticProps: {
114 | initialId: 0,
115 | pipeline: [],
116 | },
117 | },
118 | };
119 | }
120 | );
121 | }
122 |
123 | export default ATS;
124 |
--------------------------------------------------------------------------------
/components/insertApplicantModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal, Button, Form, Input, Spin, Select, Upload } from "antd";
3 | import useSWR from "swr";
4 | import homeStyle from "../styles/Home.module.css";
5 |
6 | const layout = {
7 | labelCol: { span: 6 },
8 | wrapperCol: { span: 18 },
9 | };
10 |
11 | const validateMessages = {
12 | required: "This field is required!",
13 | types: {
14 | email: "Not a valid email!",
15 | number: "Not a valid number!",
16 | url: "Not a valid url!",
17 | },
18 | };
19 |
20 | const normFile = (e) => {
21 | if (Array.isArray(e)) {
22 | return e;
23 | }
24 | return e && e.fileList;
25 | };
26 |
27 | export default function InsertApplicantModal(props) {
28 | const [form] = Form.useForm();
29 |
30 | async function handleSubmit(e) {
31 | const eString = JSON.stringify(e, (k, v) => (v === undefined ? null : v));
32 | await fetch("/api/applicants", {
33 | method: "post",
34 | body: eString,
35 | });
36 | form.resetFields();
37 | props.close();
38 | }
39 |
40 | const { data, error } = useSWR("/api/jobs", async function (args) {
41 | const res = await fetch(args);
42 | return res.json();
43 | });
44 |
45 | if (error) return failed to load
;
46 | if (!data)
47 | return (
48 |
49 |
50 |
51 | );
52 |
53 | return (
54 |
64 |
76 |
77 | {data.map((job, i) => (
78 |
79 | {job.title}
80 |
81 | ))}
82 |
83 |
84 |
85 |
86 |
87 |
92 |
93 |
94 |
101 |
102 | Upload CV
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
126 |
127 |
128 |
134 |
135 | Insert
136 |
137 |
138 |
139 |
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/components/viewApplicantModal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Modal, Descriptions, Form, Input, Button, Rate, Select } from "antd";
3 | import fetch from "isomorphic-unfetch";
4 | import { saveAs } from "file-saver";
5 |
6 | export default function ViewApplicantModal(props) {
7 | const [form] = Form.useForm();
8 |
9 | useEffect(() => {
10 | form.resetFields();
11 | }, [props.data]);
12 |
13 | async function handleSubmit(e) {
14 | e.id = props.data._id;
15 | await fetch("/api/applicants", {
16 | method: "put",
17 | body: JSON.stringify(e),
18 | });
19 | props.close();
20 | }
21 |
22 | async function deleteApplicant() {
23 | await fetch("/api/applicants", {
24 | method: "delete",
25 | body: JSON.stringify(props.data._id),
26 | });
27 | props.close();
28 | }
29 |
30 | async function downloadCV() {
31 | const res = await fetch(`/api/cv/${props.data.cv}`);
32 | let resJson = await res.json();
33 | const arr = new Uint8Array(resJson.file.data);
34 |
35 | let blob = new Blob([arr], { type: "application/pdf" });
36 | saveAs(blob, "cv.pdf");
37 | }
38 |
39 | return (
40 |
50 |
153 |
154 | );
155 | }
156 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import Link from "next/link";
3 | import styles from "../styles/Home.module.css";
4 | import listingStyles from "../styles/ListingsPage.module.css";
5 | import { Row, Col, Button } from "antd";
6 | import { DownOutlined } from "@ant-design/icons";
7 |
8 | export default function Home() {
9 | return (
10 |
11 |
12 |
Home
13 |
14 |
15 |
16 |
17 |
25 |
30 | Example Jobs
31 |
32 | Join Us – add small and meaningful slogan here
33 |
34 |
35 | View Open Jobs
36 |
37 |
38 |
43 |
44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
45 | interdum hendrerit pulvinar. Cras accumsan imperdiet augue in
46 | ornare. Ut ac ex eu leo aliquet porttitor ac id ligula.
47 |
48 |
54 | Nulla pellentesque lorem turpis, in rhoncus urna euismod sit amet.
55 | Aenean pharetra justo felis, quis congue dui faucibus non. Nulla
56 | blandit lacinia dolor, sit amet mattis massa fermentum ac.in.
57 |
58 |
59 |
60 |
61 |
62 | Our Mission
63 |
64 |
65 |
70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
71 | interdum hendrerit pulvinar. Cras accumsan imperdiet augue in
72 | ornare.
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
92 | Our Team
93 |
100 |
101 |
102 | Photo by{" "}
103 |
108 | Anna Earl
109 | {" "}
110 | on{" "}
111 |
116 | Unsplash
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | Join Us
125 |
126 |
127 |
128 |
129 |
138 |
139 | );
140 | }
141 |
--------------------------------------------------------------------------------
/pages/jobs/[id].js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import Link from "next/link";
3 | import styles from "../../styles/Home.module.css";
4 | import listingStyles from "../../styles/ListingsPage.module.css";
5 | import {
6 | Row,
7 | Col,
8 | Spin,
9 | Form,
10 | Input,
11 | Button,
12 | Divider,
13 | message,
14 | Upload,
15 | } from "antd";
16 | import { useRouter } from "next/router";
17 | import useSWR from "swr";
18 | import fetch from "isomorphic-unfetch";
19 | import ReactMarkdown from "react-markdown";
20 |
21 | const layout = {
22 | labelCol: { span: 6 },
23 | wrapperCol: { span: 18 },
24 | };
25 |
26 | const validateMessages = {
27 | required: "This field is required!",
28 | types: {
29 | email: "Not a valid email!",
30 | number: "Not a valid number!",
31 | url: "Not a valid url!",
32 | },
33 | };
34 |
35 | const normFile = (e) => {
36 | if (Array.isArray(e)) {
37 | return e;
38 | }
39 | return e && e.fileList;
40 | };
41 |
42 | function Jobs() {
43 | const [form] = Form.useForm();
44 | const router = useRouter();
45 | const { id } = router.query;
46 |
47 | const { data, error } = useSWR(`/api/jobListing/${id}`, async function (
48 | args
49 | ) {
50 | const res = await fetch(args);
51 | return res.json();
52 | });
53 |
54 | async function handleSubmit(e) {
55 | e.listing = id;
56 | const eString = JSON.stringify(e, (k, v) => (v === undefined ? null : v));
57 | await fetch("/api/applicants", {
58 | method: "post",
59 | body: eString,
60 | });
61 | form.resetFields();
62 | message.success("Your application has been submitted");
63 | }
64 |
65 | if (error)
66 | return (
67 |
73 | );
74 | if (!data)
75 | return (
76 |
77 |
78 |
79 | );
80 |
81 | return (
82 |
181 | );
182 | }
183 |
184 | export default Jobs;
185 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2020 Baykam Say
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
--------------------------------------------------------------------------------