Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/submit #12

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions api/app/controllers/Evidence.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ exports.getEvidenceById = async (req, res) => {
}
};

exports.createNewEvidence = async (req, res) => {
try {
// TODO: I LEFT THIS OUT BECAUSE I'M HAVING ISSUES LOGGING IN
// ALSO I DON'T KNOW IF THIS SHOULD BE HERE OR IN THE DATABASE FILE
// const author = req.user.id || null;
// if (!author) {
// return handleError(res, 'You must be logged in to submit new evidence.');
// }
const submission = new Evidence(req.body);
submission.save((err, submission) => {
if (err) res.send(err);
res.json(submission);
});
} catch (error) {
handleError(res, error);
}
};

exports.createEvidenceReview = async (req, res) => {
try {
const id = await isIDValid(req.params.id);
Expand Down
16 changes: 11 additions & 5 deletions api/app/models/Evidence.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const baseOptions = {
const EvidenceSchema = new Schema(
{
keywords: {
type: [String],
required: [true, 'A few keywords are required.']
type: [String]
// required: [true, 'A few keywords are required.']
},
se_method: {
type: [String],
required: [true, 'At least one SE method is required.']
type: [String]
// required: [true, 'At least one SE method is required.']
},
research_question: {
type: String
Expand All @@ -30,7 +30,13 @@ const EvidenceSchema = new Schema(
type: String
},
doi: {
type: String
type: String,
required: [true, 'The DOI is required.']

},
link: {
type: String,
required: [true, 'The link to view evidence is required.']
},
ratings: {
type: [RatingSchema]
Expand Down
4 changes: 4 additions & 0 deletions api/app/routes/Evidence.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const router = express.Router();
const {
getEvidence,
getEvidenceById,
createNewEvidence,
createEvidenceReview,
moderateSubmission
} = require('../controllers/Evidence');
Expand All @@ -14,6 +15,9 @@ router.get('/', getEvidence);
/* Get specific evidence */
router.get('/:id', getEvidenceById);

/* Create new evidence submission */
router.post('/', createNewEvidence);

/* Create evidence review */
router.post('/:id/reviews', isAuthed(), createEvidenceReview);

Expand Down
4 changes: 4 additions & 0 deletions client/src/components/App/Router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Moderation from "../../Moderation";
import Profile from "../../Profile";
import Search from "../../Search";
import Suggestion from "../../Suggestion";
import Submission from '../../Suggestion/components/Submission';
import { Role } from "../Authentication";
import ProtectedRoute from "../Authentication/ProtectedRoute";
import ErrorRoute from "./ErrorRoute";
Expand All @@ -18,6 +19,9 @@ const Router = () => (
<Switch>
<Route exact path="/" component={Landing} />
<Route exact path="/suggest" component={Suggestion} />
<Route path="/suggest/:type" children={<Submission />} />
<Route exact path="/moderate" component={Moderation} />
<Route exact path="/dashboard" component={Dashboard} />
<ProtectedRoute
exact
roles={[Role.MODERATOR, Role.ADMIN]}
Expand Down
7 changes: 6 additions & 1 deletion client/src/components/Moderation/components/RejectionNote.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const RejectionNote = ({ id, isOpen, toggle, refreshPage }) => {
.catch((error) => error);
};

const mockRejectAPICall = () => {
console.log("Rejecting " + id + " because " + reason);
handleBackButton();
}

return (
<div className={`modal ${isOpen ? "is-active" : ""}`}>
<div className="modal-background"></div>
Expand All @@ -68,7 +73,7 @@ const RejectionNote = ({ id, isOpen, toggle, refreshPage }) => {
key={r.name}
className={`button ${
reason === r.name ? "is-primary" : ""
}`}
}`}
onClick={() => setReason(r.name)}
>
{r.button}
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/Suggestion/components/Category.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from "react";
import { Link } from "react-router-dom";

const Category = ({ type }) => (
<div className="box has-background-info" style={{ cursor: "pointer" }}>
<div className="columns is-gapless is-flex is-vcentered is-centered has-text-white">
<Link to={`/suggest/${type.url}`} className="columns is-gapless is-flex is-vcentered is-centered has-text-white" >
{/* Icon */}
<div className="column is-one-fifth">
<i className={type.icon} aria-hidden="true"></i>
</div>
{/* Title */}
<div className="column has-text-white">
<div className="column">
<strong className="subtitle has-text-white">{type.name}</strong>
</div>
</div>
</Link>
</div>
);

Expand Down
109 changes: 109 additions & 0 deletions client/src/components/Suggestion/components/Submission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useState, useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import { RecordType } from "../../../utils/RecordType";
import { RiCheckLine, RiDeleteBin2Line } from "react-icons/ri";

const Submission = () => {
// DISPLAY ICON
const { type } = useParams();
const icon = RecordType[type].icon || RecordType.UNCLASSIFIED.icon;
// GET SUBMITTED INFO
const [doi, setDoi] = useState("");
const [link, setLink] = useState("");
const [disableSubmit, setDisableSubmit] = useState(true);
const [message, setMessage] = useState("");
const [failed, setFailed] = useState("");
// VALIDATE LINK
const validateLink = (userInput) => {
let regex = userInput.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g);
if (regex == null) {
setFailed("Please enter a valid link");
} else {
setFailed("");
setLink(userInput); // if not valid, link var will not have length, so submit will not be enabled
}
}
// DISABLE/ENABLE SUBMIT BUTTON
useEffect(() => {
setDisableSubmit(doi.length < 5 || link.length < 5)
}, [doi, link])

const submitEvidence = () => {
return fetch("/api/v1/evidence", {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ doi, link }),
})
.then((res) => res.json())
.then((res) => {
if (!res?.error && !res?.message) {
setMessage("Submitted!");
}
return res;
})
.catch((error) => error);
};

return (
<>
<section className="section">
<div className="container">
<h2 className="subtitle">Suggest a new {type.toLowerCase()}</h2>
<div className="columns is-multiline">
<div className="column is-6-tablet is-6-desktop">
<i className={`${icon} has-text-primary`} aria-hidden="true"></i>
</div>
<div className="column is-6-tablet is-6-desktop">
{/* <form> */}
<div className="field">
<div className="control">
<input id="doi" className="input" type="text" placeholder="DOI"
onChange={(e) => setDoi(e.target.value)}
/>
</div>
</div>
<div className="field">
<div className="control">
<input className="input" type="text" placeholder="Link to evidence record"
onChange={(e) => validateLink(e.target.value)}
/>
</div>
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-success" disabled={disableSubmit} onClick={submitEvidence}>
<span className="icon is-small">
<RiCheckLine />
</span>
<span>Submit</span>
</button>
</div>
<div className="control">
<Link className="button" to="./">
<span className="icon is-small">
<RiDeleteBin2Line />
</span>
<span>Cancel</span>
</Link>
</div>
</div>
{/* </form> */}
{message && (
<div className="notification is-success is-light">
{JSON.stringify(message)}
</div>
)}
{failed && (
<div className="notification is-danger is-light">
{JSON.stringify(failed)}
</div>
)}
</div>
</div>
</div>
</section>
</>
);
}

export default Submission;
13 changes: 7 additions & 6 deletions client/src/utils/RecordType.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
* Types of records supported by SEER.
*/
export const RecordType = {
BOOK: { name: "Book", icon: "fa fa-book fa-3x" },
BOOKSECTION: { name: "Book Section", icon: "fas fa-book-open fa-3x" },
JOURNAL: { name: "Article (Journal)", icon: "far fa-file fa-3x" },
PERIODICAL: { name: "Article (Periodical)", icon: "far fa-file fa-3x" },
WEBSITE: { name: "Website", icon: "fa fa-globe fa-3x" },
PROCEEDINGS: { name: "Proceedings", icon: "fas fa-user-friends fa-3x" },
BOOK: { name: "Book", url: "BOOK", icon: "fa fa-book fa-3x" },
BOOKSECTION: { name: "Book Section", url: "BOOKSECTION", icon: "fas fa-book-open fa-3x" },
JOURNAL: { name: "Article (Journal)", url: "JOURNAL", icon: "far fa-file fa-3x" },
PERIODICAL: { name: "Article (Periodical)", url: "PERIODICAL", icon: "far fa-file fa-3x" },
WEBSITE: { name: "Website", url: "WEBSITE", icon: "fa fa-globe fa-3x" },
PROCEEDINGS: { name: "Proceedings", url: "PROCEEDINGS", icon: "fas fa-user-friends fa-3x" },
UNCLASSIFIED: {
name: "Unclassified",
url: "UNCLASSIFIED",
icon: "far fa-question-circle fa-3x",
},
};