updated fe and added be

This commit is contained in:
JasonFraser 2024-08-28 14:37:32 -04:00
parent 8ebda489dc
commit 84a080ca73
18 changed files with 1930 additions and 133 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ dist-ssr
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
.idea .idea
.env
.DS_Store .DS_Store
*.suo *.suo
*.ntvs* *.ntvs*

10
backend/config/db.js Normal file
View File

@ -0,0 +1,10 @@
const mongoose = require('mongoose');
const connectDB = async () => {
const conn = await mongoose.connect(process.env.MONGO_URI);
console.log(`MongoDB Connected: ${conn.connection.host}`);
};
mongoose.set('strictQuery', true);
module.exports = connectDB;

20
backend/models/Idea.js Normal file
View File

@ -0,0 +1,20 @@
const mongoose = require('mongoose');
const IdeaSchema = new mongoose.Schema({
text: {
type: String,
required: [true, 'Please add a text field'],
},
tag: {
type: String,
},
username: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('Idea', IdeaSchema);

1385
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
backend/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "randomideas",
"version": "1.0.0",
"description": "idea shring app",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"author": "Jason",
"license": "MIT",
"dependencies": {
"dotenv": "^16.4.5",
"express": "^4.19.2",
"mongoose": "^8.5.4"
},
"devDependencies": {
"nodemon": "^3.1.4"
}
}

77
backend/routes/ideas.js Normal file
View File

@ -0,0 +1,77 @@
const express = require('express');
const router = express.Router();
const Idea = require('../models/Idea');
//get all ideas
router.get('/', async (req, res) => {
try {
const ideas = await Idea.find();
res.json({ success: true, data_base: ideas });
} catch (error) {
res.status(500).json({ success: false, error: 'Something went wrong' });
}
});
//get one idea
router.get('/:id', async (req, res) => {
try {
const idea = await Idea.findById(req.params.id);
res.json({ success: true, data_base: idea });
} catch (error) {
console.log(error);
res.status(500).json({ message: false, error: 'Something went wrong' });
}
res.json({ success: true, data_base: idea });
});
//add an idea
router.post('/', async (req, res) => {
const idea = new Idea({
text: req.body.text,
tag: req.body.tag,
username: req.body.username,
});
try {
const savedIdea = await idea.save();
res.json({ success: true, data_base: savedIdea });
} catch (error) {
res.status(500).json({ success: false, error: 'Something went wrong' });
}
});
//update idea
router.put('/:id', async (req, res) => {
try {
const updatedIdea = await Idea.findByIdAndUpdate(
req.params.id,
{
$set: {
text: req.body.text,
tag: req.body.tag,
},
},
{
new: true, //if the id doesn't exist, a new record will be created
}
);
res.json({ successs: true, data_base: updatedIdea });
} catch (error) {
console.loh(error);
res.status(500).json({ success: false, error: 'Something went wrong' });
}
});
//delete an idea
router.delete('/:id', async (req, res) => {
try {
await Idea.findByIdAndDelete(req.params.id);
res.json({ sucess: true, data: {} });
} catch (error) {
console.log(error);
res.status(500).json({ success: false, error: 'Something went wrong' });
}
});
module.exports = router;

23
backend/server.js Normal file
View File

@ -0,0 +1,23 @@
const express = require('express');
require('dotenv').config();
const port = process.env.PORT || 5000;
const connectDB = require('./config/db');
connectDB();
const app = express();
//Body parser middleware
//used to send POST requests in the body
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the RandomIdeas API' });
});
//registering the routes
const ideasRouter = require('./routes/ideas');
app.use('/api/ideas', ideasRouter);
app.listen(port, () => console.log(`Server listening on port ${port}`));

56
components/IdeaForm.js Normal file
View File

@ -0,0 +1,56 @@
class IdeaForm {
constructor() {
this._formModal = document.querySelector('#form-modal');
}
//add all event listeners in a function
addEventListeners() {
this._form.addEventListener('submit', this.handleSubmit.bind(this));
}
handleSubmit(e) {
e.preventDefault(); //stops the submit and allows us to determine when it should run
//grabs values from the form
const idea = {
text: this._form.elements.text.value,
tag: this._form.elements.tag.value,
username: this._form.elements.username.value,
};
//clear form fields after submit
this._form.elements.text.value = '';
this._form.elements.tag.value = '';
this._form.elements.username.value = '';
//using the dispatch event method of the document object to dispatch a custom event
//to the Modal from the IdeaForm, telling the Modal to close the form on submit
document.dispatchEvent(new Event('closemodal'));
}
//create a render function like react that will output the HTML
render() {
this._formModal.innerHTML = `
<form id="idea-form">
<div class="form-control">
<label for="idea-text">Enter a Username</label>
<input type="text" name="username" id="username" />
</div>
<div class="form-control">
<label for="idea-text">What's Your Idea?</label>
<textarea name="text" id="idea-text"></textarea>
</div>
<div class="form-control">
<label for="tag">Tag</label>
<input type="text" name="tag" id="tag" />
</div>
<button class="btn" type="submit" id="submit">Submit</button>
</form>`;
this._form = document.querySelector('#idea-form');
this.addEventListeners(); //added within the render function because the HTML above is only available
//in the DOM after the render function is called.
}
}
export default IdeaForm;

83
components/IdeaList.js Normal file
View File

@ -0,0 +1,83 @@
//this class returns a list of cards to the DOM
//by mapping (looping) over an array
class IdeaList {
constructor() {
this._ideaListEl = document.querySelector('#idea-list');
this._ideas = [
{
id: 1,
text: 'Idea 1',
username: 'John',
date: '02/01/2023',
tag: 'Business',
},
{
id: 2,
text: 'Idea 2',
username: 'Jimmy',
date: '02/01/2023',
tag: 'Technology',
},
{
id: 3,
text: 'Idea 3',
username: 'Jill',
date: '02/01/2023',
tag: 'Fashion',
},
];
//adding a set (datatype)
this._validTags = new Set();
this._validTags.add('technology');
this._validTags.add('software');
this._validTags.add('business');
this._validTags.add('education');
this._validTags.add('health');
this._validTags.add('inventions');
}
getTagClass(tag) {
tag = tag.toLowerCase();
let tagClass = '';
//check if the tag that is passed in is within the set
if (this._validTags.has(tag)) {
tagClass = `tag-${tag}`;
} else {
tagClass = '';
}
return tagClass;
}
render() {
//use map on the array, creating a card for each item in the array and display
// them on the DOM
//map takes in a function as a parameter
this._ideaListEl.innerHTML = this._ideas
.map((idea) => {
//passing the tag entered for each item into the getTagClass function on each iteration of the map
const tagClass = this.getTagClass(idea.tag);
//it will be treated as an array of template strings
return `
<div class="card">
<button class="delete"><i class="fas fa-times"></i></button>
<h3>
${idea.text}
</h3>
<p class="tag ${tagClass}">${idea.tag.toUpperCase()}</p>
<p>
Posted on <span class="date">${idea.date}</span> by
<span class="author">${idea.username}</span>
</p>
</div>
`;
})
.join(''); //to turn it back in to a string
}
}
export default IdeaList;

36
components/Modal.js Normal file
View File

@ -0,0 +1,36 @@
class Modal {
constructor() {
//selecting DOM elements in the constructor
this._modal = document.querySelector('#modal');
this._modalBtn = document.querySelector('#modal-btn');
this.addEventListeners(); //so that this functions runs right away
}
//add all event listeners in a function
addEventListeners() {
// this._modalBtn.addEventListener('click', this.open.bind(this)); //takes an event and call back function as parameter
this._modalBtn.addEventListener('click', () => {
this.open();
}); //takes an event and call back function as parameter
window.addEventListener('click', this.outsideClick.bind(this)); //takes an event and call back function as parameter
// receiving event from IdeaForm and running a call back function
document.addEventListener('closemodal', () => this.close());
}
//class methods
open() {
this._modal.style.display = 'block';
}
close() {
this._modal.style.display = 'none';
}
outsideClick(e) {
if (e.target === this._modal) {
this.close();
}
}
}
export default Modal;

View File

@ -1,9 +0,0 @@
export function setupCounter(element) {
let counter = 0
const setCounter = (count) => {
counter = count
element.innerHTML = `count is ${counter}`
}
element.addEventListener('click', () => setCounter(counter + 1))
setCounter(0)
}

178
css/style.css Normal file
View File

@ -0,0 +1,178 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
body {
font-family: "Poppins", sans-serif;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
color: #333;
background-color: #f4f4f4;
overflow-x: hidden;
}
header {
padding: 10px 0;
text-align: center;
margin-bottom: 10px;
position: relative;
}
#modal-btn {
border: none;
background: none;
color: green;
cursor: pointer;
position: absolute;
top: 10px;
left: 10px;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
height: 100%;
width: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-box {
margin: 10% auto;
width: 600px;
background-color: steelblue;
color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
animation-name: modalopen;
animation-duration: var(--modal-duration);
}
@keyframes modalopen {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
form input,
form textarea {
width: 95%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin: 10px 0;
}
form textarea {
height: 100px;
}
form label {
display: block;
font-size: 18px;
font-weight: 600;
margin: 5px 0;
}
.btn {
background-color: #333;
color: #fff;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
font-size: inherit;
margin-top: 10px;
width: 100%;
}
.btn:hover {
background-color: #000;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
.ideas {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.ideas .card {
position: relative;
background-color: #fff;
margin: 10px;
padding: 20px 15px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.tag {
display: inline-block;
background-color: #333;
color: #fff;
padding: 5px 10px;
border-radius: 5px;
margin: 5px;
font-size: 14px;
}
.tag-technology {
background: steelblue;
}
.tag-software {
background: coral;
}
.tag-business {
background: green;
}
.tag-education {
background: purple;
}
.tag-health {
background: red;
}
.tag-inventions {
background: orange;
}
.delete {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
color: red;
background: none;
border: none;
}
.delete:hover {
color: #000;
}
.date {
color: #999;
font-size: 14px;
}
@media (max-width: 768px) {
.modal-box {
width: 90%;
}
}

View File

@ -1,13 +1,25 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title> <title>Random Ideas</title>
</head> </head>
<body> <body>
<div id="app"></div> <header>
<h1>Random Ideas</h1>
<button id="modal-btn">
<i class="fa-solid fa-plus fa-2x"></i>
</button>
</header>
<div class="container">
<div id="idea-list" class="ideas"></div>
</div>
<div id="modal" class="modal">
<div id="form-modal" class="modal-box"></div>
</div>
<script type="module" src="/main.js"></script> <script type="module" src="/main.js"></script>
</body> </body>
</html> </html>

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#F7DF1E" d="M0 0h256v256H0V0Z"></path><path d="m67.312 213.932l19.59-11.856c3.78 6.701 7.218 12.371 15.465 12.371c7.905 0 12.89-3.092 12.89-15.12v-81.798h24.057v82.138c0 24.917-14.606 36.259-35.916 36.259c-19.245 0-30.416-9.967-36.087-21.996m85.07-2.576l19.588-11.341c5.157 8.421 11.859 14.607 23.715 14.607c9.969 0 16.325-4.984 16.325-11.858c0-8.248-6.53-11.17-17.528-15.98l-6.013-2.58c-17.357-7.387-28.87-16.667-28.87-36.257c0-18.044 13.747-31.792 35.228-31.792c15.294 0 26.292 5.328 34.196 19.247l-18.732 12.03c-4.125-7.389-8.591-10.31-15.465-10.31c-7.046 0-11.514 4.468-11.514 10.31c0 7.217 4.468 10.14 14.778 14.608l6.014 2.577c20.45 8.765 31.963 17.7 31.963 37.804c0 21.654-17.012 33.51-39.867 33.51c-22.339 0-36.774-10.654-43.819-24.574"></path></svg>

Before

Width:  |  Height:  |  Size: 995 B

33
main.js
View File

@ -1,24 +1,11 @@
import './style.css' import Modal from './components/Modal';
import javascriptLogo from './javascript.svg' import IdeaForm from './components/IdeaForm';
import viteLogo from '/vite.svg' import IdeaList from './components/IdeaList';
import { setupCounter } from './counter.js' import '@fortawesome/fontawesome-free/css/all.css';
import './css/style.css';
document.querySelector('#app').innerHTML = ` const modal = new Modal();
<div> const ideaForm = new IdeaForm();
<a href="https://vitejs.dev" target="_blank"> ideaForm.render();
<img src="${viteLogo}" class="logo" alt="Vite logo" /> const ideaList = new IdeaList();
</a> ideaList.render();
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
<img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
</a>
<h1>Hello Vite!</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the Vite logo to learn more
</p>
</div>
`
setupCounter(document.querySelector('#counter'))

12
package-lock.json generated
View File

@ -7,6 +7,9 @@
"": { "": {
"name": "randomideas", "name": "randomideas",
"version": "0.0.0", "version": "0.0.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0"
},
"devDependencies": { "devDependencies": {
"vite": "^5.3.4" "vite": "^5.3.4"
} }
@ -402,6 +405,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz",
"integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.19.0", "version": "4.19.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",

View File

@ -10,5 +10,8 @@
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.3.4" "vite": "^5.3.4"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0"
} }
} }

View File

@ -1,96 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #f7df1eaa);
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}