├── README.md
├── .babelrc
├── .gitignore
├── src
├── index.js
├── components
│ ├── Spinner.jsx
│ ├── Landing.jsx
│ ├── NavigateBar.jsx
│ ├── App.jsx
│ ├── Login.jsx
│ ├── Profile.jsx
│ ├── Signup.jsx
│ ├── IdeaPage.jsx
│ ├── Explore.jsx
│ └── SubmitIdea.jsx
└── styles
│ ├── ideapage.scss
│ ├── user-profile.scss
│ └── login-signup.scss
├── public
└── index.html
├── server
├── Routers
│ ├── signupRouter.js
│ ├── loginRouter.js
│ ├── profileRouter.js
│ ├── submitRouter.js
│ └── exploreRouter.js
├── Models
│ └── model.js
├── Controllers
│ ├── techController.js
│ ├── authController.js
│ └── ideaController.js
├── passport.js
├── server.js
└── database.sql
├── tsconfig.json
├── webpack.config.js
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # scratch-project
2 |
3 |
4 | Welcome to Goblin Shark territory. V2
5 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | modules: false
7 | }
8 | ],
9 | '@babel/preset-react'
10 | ],
11 | plugins: [
12 | 'react-hot-loader/babel'
13 | ]
14 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache/
2 | coverage/
3 | dist/
4 | !dist/index.html
5 | node_modules/
6 | *.log
7 | .env
8 |
9 | # OS generated files
10 | .DS_Store
11 | .DS_Store?
12 | ._*
13 | .Spotlight-V100
14 | .Trashes
15 | ehthumbs.db
16 | Thumbs.db
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './components/App';
4 |
5 | // uncomment so that webpack can bundle styles
6 | // import styles from './scss/application.scss';
7 |
8 | render(, document.getElementById('root'));
9 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Scratch Project
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/server/Routers/signupRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const authController = require('../Controllers/authController.js');
3 |
4 | const router = express.Router();
5 |
6 | router.post('/', authController.register, (req, res) => {
7 | res.status(200).send('register success');
8 | // res.redirect('/')
9 | });
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strict": true,
6 | "noImplicitReturns": true,
7 | "noImplicitAny": true,
8 | "module": "es6",
9 | "moduleResolution": "node",
10 | "target": "es5",
11 | "allowJs": true,
12 | "jsx": "react",
13 | },
14 | "include": [
15 | "./src/**/*"
16 | ]
17 | }
--------------------------------------------------------------------------------
/server/Models/model.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | require('dotenv').config();
3 |
4 | const PG_URI = process.env.elephantURI;
5 |
6 | const pool = new Pool({
7 | connectionString: PG_URI,
8 | });
9 |
10 | module.exports = {
11 | query: (text, params, callback) => {
12 | console.log('executed query', text);
13 | return pool.query(text, params, callback);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/server/Routers/loginRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const authController = require('../Controllers/authController.js');
3 | const passport = require('passport');
4 |
5 | const router = express.Router();
6 | // when get a post request fine and compare user
7 | router.post('/', passport.authenticate('local'), (req, res) => {
8 | res.status(200).send('logIn success');
9 | });
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/server/Routers/profileRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const authController = require('../Controllers/authController.js');
3 |
4 | const router = express.Router();
5 |
6 | // get router for explore page
7 | router.get('/:username', authController.getProfile, (req, res) => {
8 | // console.log('res.locals.ideas', res.locals.ideas);
9 | res.json(res.locals.userData);
10 | });
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/src/components/Spinner.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | // import spinner from '../../img/spinner.gif';
3 | const spinner = 'http://static.onemansblog.com/wp-content/uploads/2016/05/Spinner-Loading.gif'
4 |
5 | export default () =>
6 | (
7 |
8 |
12 |
13 | )
--------------------------------------------------------------------------------
/src/styles/ideapage.scss:
--------------------------------------------------------------------------------
1 | #idea-wrapper {
2 | margin-top: 10vh;
3 | border: 1px solid black;
4 | border-radius: 8px;
5 | min-width: fit-content;
6 | width: 75%;
7 | }
8 |
9 | #idea-pic {
10 | height: 200px;
11 | align-self: center;
12 | }
13 |
14 | .prof-pic {
15 | height: 30px;
16 | }
17 |
18 | #button-container {
19 | display: flex;
20 | flex-direction: column;
21 | }
22 |
23 | .unstyled-li {
24 | list-style: none;
25 | }
26 |
27 | ul {
28 | padding: 0;
29 | }
30 |
--------------------------------------------------------------------------------
/server/Routers/submitRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const ideaController = require('../Controllers/ideaController.js');
3 | const techController = require('../Controllers/techController.js');
4 |
5 | const router = express.Router();
6 |
7 | router.get('/', techController.getTechs, (req, res) => {
8 | res.json(res.locals.techs);
9 | });
10 |
11 | router.post('/', ideaController.submitIdea, (req, res) => {
12 | res.sendStatus(200);
13 | });
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/src/styles/user-profile.scss:
--------------------------------------------------------------------------------
1 | #userProfileContainer {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .cardHeader {
7 | display: flex;
8 | background-color: #2E7BFF;
9 | justify-content: center;
10 | align-items: center;
11 | height: 50px;
12 | width: 2rem;
13 | }
14 |
15 | #row1 {
16 | margin-top: 100px;
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | }
21 |
22 | #profilePic {
23 | height: 150px;
24 | width: 150px;
25 | margin: 20px;
26 | }
--------------------------------------------------------------------------------
/server/Controllers/techController.js:
--------------------------------------------------------------------------------
1 | const model = require('../Models/model.js');
2 |
3 | const techController = {};
4 |
5 | techController.getTechs = (req, res, next) => {
6 | const queryText = 'SELECT * FROM Tech_stacks';
7 | model.query(queryText, (err, results) => {
8 | if (err) {
9 | console.log(err);
10 | return next({
11 | log: `error occurred at getTechs middleware. error message is: ${err}`,
12 | status: 400,
13 | message: { err: 'An error occurred' },
14 | });
15 | }
16 |
17 | res.locals.techs = results.rows;
18 | return next();
19 | });
20 | };
21 |
22 | module.exports = techController;
23 |
--------------------------------------------------------------------------------
/src/styles/login-signup.scss:
--------------------------------------------------------------------------------
1 | .login-container {
2 | height: 80vh;
3 | width: 100vw;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .login-box {
11 | border: 1px solid black;
12 | border-radius: 8px;
13 | padding: 1.2rem;
14 | margin-bottom: 10rem;
15 | min-width: 350px;
16 | }
17 |
18 | .error-msg {
19 | margin-top: 0.5rem;
20 | color: red;
21 | }
22 |
23 | .hidden {
24 | display: none;
25 | }
26 |
27 | input[type=checkbox]
28 | {
29 | /* Double-sized Checkboxes */
30 | width: 22px;
31 | height: 22px;
32 | background-color: #ddd;
33 | position: relative;
34 | }
35 |
36 | .form-check-label {
37 | color: red;
38 | }
--------------------------------------------------------------------------------
/server/Routers/exploreRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const ideaController = require('../Controllers/ideaController.js');
3 | const techController = require('../Controllers/techController.js');
4 |
5 | const router = express.Router();
6 |
7 | // get router for explore page
8 | router.get(
9 | '/',
10 | ideaController.getIdeas,
11 | techController.getTechs,
12 | (req, res) => {
13 | // console.log('res.locals.ideas', res.locals.ideas);
14 | res.json([res.locals.ideas, res.locals.techs]);
15 | }
16 | );
17 |
18 | router.get('/:ideaID', ideaController.getOneIdea, (req, res) => {
19 | // console.log('res.locals.idea', res.locals.idea);
20 | res.json(res.locals.idea);
21 | });
22 |
23 | module.exports = router;
24 |
--------------------------------------------------------------------------------
/src/components/Landing.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Button, Container, Row, Col } from 'react-bootstrap';
3 | import { withRouter } from 'react-router-dom';
4 |
5 | /*
6 | * HELPER FUNCTION: nextPath
7 | * Push `path` to Landing component's destructured `history` prop
8 | * (provided by invoking withRouter on Landing component before export)
9 | */
10 | const redirectToPath = (history, path) => {
11 | history.push(path);
12 | };
13 |
14 | const Landing = ({ history }) => {
15 | return (
16 |
17 |
18 |
19 | {" "}
20 | Welcome to Scratch Project{" "}
21 |
22 |
23 |
24 | {" "}
25 | A place where developers make their dreams come true{" "}
26 |
27 |
28 |
29 |
30 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default withRouter(Landing);
45 |
--------------------------------------------------------------------------------
/src/components/NavigateBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Navbar,
4 | Nav /* Form, FormControl, Button, Container */,
5 | } from 'react-bootstrap';
6 | import { Link } from 'react-router-dom';
7 |
8 | const NavigateBar = () => {
9 | return (
10 |
11 | {/* Leftside Nav Logo/Link */}
12 | {/* TODO: Point this href to `/explore` if User is authenticated */}
13 |
14 | Scratch Project
15 |
16 | {/* Rightside Nav Links */}
17 | {/* Set class for Login and Signup button Nav item to `margin-left: auto;`*/}
18 |
38 |
39 | );
40 |
41 | // // Search Bar Component
42 | // < Form inline >
43 | //
44 | //
45 | //
46 | };
47 |
48 | export default NavigateBar;
49 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const config = {
6 | entry: ['react-hot-loader/patch', './src/index.js'],
7 | output: {
8 | path: path.resolve(__dirname, 'dist'),
9 | filename: 'bundle.js',
10 | },
11 | mode: process.env.NODE_ENV,
12 | module: {
13 | rules: [
14 | {
15 | test: /\.(js|jsx)$/,
16 | exclude: /node_modules/,
17 | loader: 'babel-loader',
18 | options: {
19 | presets: ['@babel/preset-env', '@babel/preset-react'],
20 | plugins: ['@babel/transform-runtime'],
21 | },
22 | },
23 | {
24 | test: /\.scss?/,
25 | exclude: /(node_modules)/,
26 | use: ['style-loader', 'css-loader', 'sass-loader'],
27 | },
28 | {
29 | test: /\.css$/,
30 | use: [
31 | // 2. Inject CSS into the DOM
32 | {
33 | loader: 'style-loader',
34 | },
35 | // 1. Convert CSS to CommonJS
36 | {
37 | loader: 'css-loader',
38 | },
39 | ],
40 | },
41 | {
42 | test: /\.ts(x)?$/,
43 | loader: 'ts-loader',
44 | exclude: /node_modules/,
45 | },
46 | ],
47 | },
48 | resolve: {
49 | extensions: ['.js', '.jsx', '.tsx', '.ts'],
50 | alias: {
51 | 'react-dom': '@hot-loader/react-dom',
52 | },
53 | },
54 | devServer: {
55 | contentBase: './dist',
56 | proxy: {
57 | '/api': 'http://localhost:3000',
58 | },
59 | historyApiFallback: true,
60 | },
61 | plugins: [
62 | new HtmlWebpackPlugin({
63 | template: `./public/index.html`,
64 | }),
65 | ],
66 | };
67 |
68 | module.exports = config;
69 |
--------------------------------------------------------------------------------
/server/passport.js:
--------------------------------------------------------------------------------
1 | const LocalStrategy = require('passport-local').Strategy;
2 | const model = require('./Models/model');
3 | const bcrypt = require('bcrypt');
4 | // to Authenticate with passport
5 | const initialize = (passport) => {
6 | const autheticateUser = (username, password, done) => {
7 | //find same username in database
8 | model.query(
9 | `SELECT * FROM User_credentials WHERE username = $1`,
10 | [username],
11 | (err, results) => {
12 | if (err) {
13 | console.log(err, 'user_credentials error');
14 | return;
15 | }
16 | //find and compare same password
17 | if (results.rows.length > 0) {
18 | const user = results.rows[0];
19 |
20 | bcrypt.compare(password, user.password, (err, isMatch) => {
21 | if (err) {
22 | console.log(err, 'bcrypt compare error');
23 | return;
24 | }
25 | //if password matched send user body
26 | if (isMatch) {
27 | console.log('match');
28 | return done(null, user);
29 | } else {
30 | console.log('password is not matched');
31 | return done(null, false, { message: 'password is not matched' });
32 | }
33 | });
34 | } else {
35 | return done(null, false, { message: 'username is not registered' });
36 | }
37 | }
38 | );
39 | };
40 |
41 | passport.use(
42 | new LocalStrategy(
43 | {
44 | usernameField: 'username',
45 | passwordField: 'password',
46 | },
47 | autheticateUser
48 | )
49 | );
50 | // about session with cookie
51 | passport.serializeUser((user, done) => {
52 | done(null, user.username);
53 | });
54 | passport.deserializeUser((username, done) => {
55 | model.query(
56 | `SELECT * FROM User_credentials WHERE username = $1`,
57 | [username],
58 | (err, results) => {
59 | if (err) {
60 | console.log(err, 'deserializeUser error');
61 | return;
62 | }
63 |
64 | return done(null, results.rows[0]);
65 | }
66 | );
67 | });
68 | };
69 |
70 | module.exports = initialize;
71 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 | const cookieParser = require('cookie-parser');
6 | const bcrypt = require('bcrypt');
7 | const session = require('express-session');
8 | const model = require('./Models/model');
9 | const signUpRouter = require('./Routers/signupRouter');
10 | const exploreRouter = require('./Routers/exploreRouter');
11 | const submitRouter = require('./Routers/submitRouter');
12 | const loginRouter = require('./Routers/loginRouter');
13 | const profileRouter = require('./Routers/profileRouter');
14 | const flash = require('express-flash');
15 | const initializePassport = require('./passport');
16 | const passport = require('passport');
17 | initializePassport(passport);
18 | require('dotenv').config();
19 | const PORT = 3000;
20 |
21 | /*
22 | * Handle parsing request body
23 | */
24 | app.use(express.json());
25 | app.use(express.urlencoded({ extended: true }));
26 | app.use(express.static(path.resolve(__dirname, 'public')));
27 | app.use(
28 | session({
29 | secret: 'secret',
30 | resave: false,
31 | saveUninitialized: false,
32 | })
33 | );
34 | app.use(passport.initialize());
35 | app.use(passport.session());
36 | app.use(flash());
37 | app.use('/api/login', loginRouter);
38 | app.use('/api/signup', signUpRouter);
39 | app.use('/api/explore', exploreRouter);
40 | app.use('/api/submit', submitRouter);
41 | app.use('/api/profile', profileRouter);
42 |
43 | // globoal error handler
44 | app.use((err, req, res, next) => {
45 | const defaultErr = {
46 | log: 'Express error handler caught unknown middleware error',
47 | status: 400,
48 | message: { err: 'An error occurred' },
49 | };
50 | const errorObj = Object.assign({}, defaultErr, err);
51 | console.log(errorObj.log);
52 | return res.status(errorObj.status).json(errorObj.message);
53 | });
54 |
55 | /*
56 | * Start server
57 | */
58 | app.listen(PORT, () => {
59 | console.log(`Server listening on port: ${PORT}`);
60 | });
61 |
62 | module.exports = app;
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bob",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node ./server/server.js",
8 | "build": "cross-env NODE_ENV=production webpack",
9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot & nodemon ./server/server.js",
10 | "dew": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --inline --progress --colors --watch --content-base ./\" \"nodemon ./server/server.js\"",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/the-goblin-shark/scratch-project.git"
16 | },
17 | "keywords": [],
18 | "author": "",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/the-goblin-shark/scratch-project/issues"
22 | },
23 | "homepage": "https://github.com/the-goblin-shark/scratch-project#readme",
24 | "devDependencies": {
25 | "@babel/core": "^7.10.5",
26 | "@babel/plugin-transform-runtime": "^7.10.5",
27 | "@babel/preset-env": "^7.10.4",
28 | "@babel/preset-react": "^7.10.4",
29 | "@hot-loader/react-dom": "^16.13.0",
30 | "@types/react": "^16.9.43",
31 | "@types/react-dom": "^16.9.8",
32 | "babel-loader": "^8.1.0",
33 | "cross-env": "^7.0.2",
34 | "css-loader": "^3.6.0",
35 | "html-webpack-plugin": "^4.3.0",
36 | "node-sass": "^4.14.1",
37 | "nodemon": "^2.0.4",
38 | "sass-loader": "^9.0.2",
39 | "style-loader": "^1.2.1",
40 | "ts-loader": "^8.0.1",
41 | "typescript": "^3.9.7",
42 | "webpack": "^4.44.0",
43 | "webpack-cli": "^3.3.12",
44 | "webpack-dev-server": "^3.11.0"
45 | },
46 | "dependencies": {
47 | "@babel/runtime": "^7.10.5",
48 | "axios": "^0.19.2",
49 | "bcrypt": "^5.0.0",
50 | "bootstrap": "^4.5.0",
51 | "cookie": "^0.4.1",
52 | "cookie-parser": "^1.4.5",
53 | "dotenv": "^8.2.0",
54 | "express": "^4.17.1",
55 | "express-flash": "0.0.2",
56 | "express-session": "^1.17.1",
57 | "passport": "^0.4.1",
58 | "passport-local": "^1.0.0",
59 | "pg": "^8.3.0",
60 | "react": "^16.13.1",
61 | "react-bootstrap": "^1.3.0",
62 | "react-bootstrap-typeahead": "^5.1.0",
63 | "react-dom": "^16.13.1",
64 | "react-hot-loader": "^4.12.21",
65 | "react-router-dom": "^5.2.0"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from 'react';
2 | import Landing from './Landing.jsx';
3 | import Explore from './Explore.jsx';
4 | import Login from './Login.jsx';
5 | import Signup from './Signup.jsx';
6 | import Profile from './Profile.jsx';
7 | import NavigateBar from './NavigateBar';
8 | import IdeaPage from './IdeaPage';
9 | import SubmitIdea from './SubmitIdea';
10 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
11 | import '../../node_modules/bootstrap/dist/css/bootstrap.css';
12 |
13 | const App = () => {
14 | const [authStatus, setAuthStatus] = useState({
15 | isLoggedIn: false,
16 | username: '',
17 | });
18 |
19 | return (
20 |
21 | {/* Using Fragment rather than native div to avoid React warnings */}
22 |
23 | {/* Navigation Bar is ever-present */}
24 |
25 | {/* Use the first Route whose path matches current URL */}
26 |
27 | {/* Render given component if given path matches current URL */}
28 | {/* */}
29 | }
33 | />
34 | (
38 |
39 | )}
40 | />
41 | (
45 |
46 | )}
47 | />
48 | }
52 | />
53 |
54 | }
58 | />
59 | }
63 | />
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default App;
71 |
--------------------------------------------------------------------------------
/src/components/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import '../styles/login-signup.scss';
4 | import '../../node_modules/bootstrap/dist/css/bootstrap.css';
5 | import { Form, Button } from 'react-bootstrap';
6 |
7 | const Login = (props) => {
8 | const { authStatus, setAuthStatus } = props;
9 |
10 | const [loginInputs, setLoginInputs] = useState({
11 | username: '',
12 | password: '',
13 | });
14 |
15 | //used to toggle error message if auth fails
16 | //as well as redirect if auth succeeds
17 | const [loginStatus, setLoginStatus] = useState(null);
18 |
19 | const handleSubmit = async (e) => {
20 | e.preventDefault();
21 | const { username, password } = loginInputs;
22 | const body = {
23 | username,
24 | password,
25 | };
26 | let response = await fetch('/api/login', {
27 | method: 'POST',
28 | headers: {
29 | 'Content-Type': 'application/json',
30 | },
31 | body: JSON.stringify(body),
32 | });
33 |
34 | if (response.status === 200) {
35 | setLoginStatus(true);
36 | setAuthStatus({ isLoggedIn: true, username });
37 | } else setLoginStatus(false);
38 | };
39 |
40 | const setInput = (e) => {
41 | setLoginInputs({ ...loginInputs, [e.target.id]: e.target.value });
42 | };
43 |
44 | return loginStatus || authStatus.isLoggedIn ? (
45 |
46 | ) : (
47 |
48 |
49 |
50 | Welcome Back!
51 |
52 |
54 | Username
55 |
60 |
61 |
62 |
63 | Password
64 |
69 |
70 |
71 |
74 |
75 | Sorry, your username/password was invalid.
76 |
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export default Login;
84 |
--------------------------------------------------------------------------------
/server/Controllers/authController.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt');
2 | const model = require('../Models/model.js');
3 |
4 | const authController = {};
5 |
6 | authController.register = async (req, res, next) => {
7 | const { username, password, email, firstname, lastname } = req.body;
8 | const queryText = `INSERT INTO User_credentials (username,password,email) VALUES ($1,$2,$3)`;
9 | const usersQueryText = `INSERT INTO Users (username, firstname, lastname) VALUES($1,$2,$3)`;
10 | const hashedPassWord = await bcrypt.hash(password, 10);
11 | try {
12 | await model.query(queryText, [username, hashedPassWord, email]);
13 | await model.query(usersQueryText, [username, firstname, lastname]);
14 | return next();
15 | } catch (err) {
16 | console.log(err);
17 | return next({
18 | log: `error occurred at register middleware. error message is: ${err}`,
19 | status: 400,
20 | message: { err: 'An error occurred' },
21 | });
22 | }
23 | };
24 |
25 | // ToDO : new columns in the Users table, about, linkedin, personal url
26 | // new table to link users to tech stack(association table),
27 | // signup new fields for firstname and lastname
28 |
29 | // middleware for get profiles
30 | authController.getProfile = async (req, res, next) => {
31 | const { username } = req.params;
32 | const queryText = `SELECT * FROM Users WHERE username=$1`;
33 | try {
34 | const userData = await model.query(queryText, [username]);
35 | [res.locals.userData] = userData.rows;
36 | return next();
37 | } catch (err) {
38 | console.log(err);
39 | return next({
40 | log: `error occurred at getProfile middleware. error message is: ${err}`,
41 | status: 400,
42 | message: { err: 'An error occurred' },
43 | });
44 | }
45 | };
46 |
47 | // middeware to edit profiles (INCOMPLETE)
48 | authController.editProfile = async (req, res, next) => {
49 | const {
50 | username,
51 | firstName,
52 | lastName,
53 | about,
54 | profilepic,
55 | githubHandle,
56 | linkedIn,
57 | personalPage,
58 | } = req.body;
59 |
60 | const queryText = `UPDATE Users
61 | SET firstname=$1,
62 | lastname=$2,
63 | about=$3
64 | profilepic=$4,
65 | githubhandle=$5,
66 | linkedin=$6,
67 | personalpage=$7
68 | WHERE username=$8`;
69 |
70 | const queryValue = [
71 | firstName,
72 | lastName,
73 | about,
74 | profilepic,
75 | githubHandle,
76 | linkedIn,
77 | personalPage,
78 | username,
79 | ];
80 |
81 | try {
82 | await model.query(queryText, queryValue);
83 | return next();
84 | } catch (err) {
85 | console.log(err);
86 | return next({
87 | log: `error occurred at getProfile middleware. error message is: ${err}`,
88 | status: 400,
89 | message: { err: 'An error occurred' },
90 | });
91 | }
92 | };
93 |
94 | module.exports = authController;
95 |
--------------------------------------------------------------------------------
/src/components/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from 'react'
2 | import { Container, Col, Row, Button } from 'react-bootstrap';
3 | import Spinner from './Spinner';
4 | import '../styles/user-profile.scss';
5 |
6 | const Profile = (props) => {
7 | /*
8 | * Possible Props:
9 | * creatorUsername (possibly) passed in from IdeaPage
10 | * authStatus always passed in from App
11 | */
12 | let { ideaCreator, authStatus } = props;
13 |
14 | // Destructure currently authenticated user's username from authStatus
15 | let { username } = authStatus;
16 |
17 | // Initialize creator name-to-display to currently authenticated user
18 | let creatorName = username;
19 |
20 | // Accessing Profile from Idea Page?
21 | if (ideaCreator) {
22 | console.log('idea creator is : ', ideaCreator)
23 | // If logged-in user is _not_ clicking on their own profile picture,
24 | // RESET name-to-display to that of the User being clicked by logged-in User
25 | if (loggedInUsername !== ideaCreator) {
26 | creatorName = ideaCreator;
27 | }
28 | }
29 | // Set up user data to display on Profile
30 | const [userData, setUserData] = useState({});
31 |
32 | // componentDidMount() functional equivalent
33 | useEffect(() => {
34 | getUser();
35 | }, []);
36 |
37 | const getUser = async () => {
38 | // Get all existing user data, sending username as a parameter
39 | const res = await fetch(`/api/profile/${creatorName}`);
40 | // Expect in response an object with all User table column properties
41 | const userTableData = await res.json();
42 | setUserData(userTableData);
43 | };
44 |
45 | /*
46 | * PROFILE COMPONENT USER FLOW:
47 |
48 | * Case 1: Viewing your own profile (READ and WRITE)
49 | * On first render, display all data in this DB row (distinguished by `username`)
50 | *
51 | * If current User clicks edit, then submit:
52 | * 1a. Send all data stored from initial GET request
53 | * 1b. Except for the modified fields, whose values will be set to User input
54 | *
55 | * Case 2: Viewing another User's profile (whether or not you're a registered user)
56 | * Same page without edit button functionality (READ-ONLY)
57 | */
58 |
59 | if (!Object.keys(userData).length) {
60 | return ;
61 | }
62 | else if (userData.err) {
63 | return Could not load user;
64 | }
65 |
66 | return (
67 |
68 |
69 |
70 | {creatorName}'s Developer Profile
71 |
72 |
73 |
74 |
75 |
76 | Bio
77 |
78 |
79 | Where else can your future teammates contact you?
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default Profile;
87 |
--------------------------------------------------------------------------------
/server/database.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.User_credentials
2 | (
3 | username varchar(50) NOT NULL,
4 | password varchar(255) NOT NULL,
5 | email varchar(255) UNIQUE NOT NULL,
6 | CONSTRAINT PK_users PRIMARY KEY (username)
7 | );
8 |
9 | CREATE TABLE Users
10 | (
11 | user_id serial NOT NULL,
12 | firstname varchar(50) NOT NULL,
13 | lastname varchar(50) NOT NULL,
14 | about varchar(255) NULL,
15 | profilepic varchar(65535) NULL DEFAULT 'https://www.eguardtech.com/wp-content/uploads/2018/08/Network-Profile.png',
16 | githubhandle varchar(50) NULL,
17 | username varchar(50) NOT NULL,
18 | linkedin varchar(255) NULL,
19 | personalpage varchar(255) NULL,
20 | CONSTRAINT PK_user_profile PRIMARY KEY (user_id),
21 | CONSTRAINT FK_34 FOREIGN KEY (username) REFERENCES public.User_credentials (username)
22 | );
23 |
24 | CREATE INDEX fkIdx_34 ON Users
25 | (z1
26 | username
27 | );
28 |
29 | CREATE TABLE Ideas
30 | (
31 | idea_id serial NOT NULL,
32 | name varchar(50) NOT NULL,
33 | description varchar(255) NOT NULL,
34 | why varchar(255) NOT NULL,
35 | when_start date NOT NULL,
36 | when_end date NULL,
37 | who int NOT NULL,
38 | image varchar(65535) NULL DEFAULT 'https://foroalfa.org/imagenes/ilustraciones/idea.png',
39 | creator_username varchar(50) NOT NULL,
40 | CONSTRAINT PK_ideas PRIMARY KEY (idea_id),
41 | CONSTRAINT FK_56 FOREIGN KEY (creator_username) REFERENCES public.User_credentials (username)
42 | );
43 |
44 | CREATE INDEX fkIdx_56 ON Ideas
45 | (
46 | creator_username
47 | );
48 |
49 | CREATE TABLE Idea_Participants
50 | (
51 | idea_id integer NOT NULL,
52 | participant_username varchar(50) NOT NULL,
53 | CONSTRAINT FK_59 FOREIGN KEY (idea_id) REFERENCES Ideas ( idea_id ),
54 | CONSTRAINT FK_62 FOREIGN KEY (participant_username) REFERENCES public.User_credentials (username)
55 | );
56 |
57 | CREATE INDEX fkIdx_59 ON Idea_Participants
58 | (
59 | idea_id
60 | );
61 |
62 | CREATE INDEX fkIdx_62 ON Idea_Participants
63 | (
64 | participant_username
65 | );
66 |
67 | CREATE TABLE Favorites
68 | (
69 | username varchar(50) NOT NULL,
70 | idea_id integer NOT NULL,
71 | CONSTRAINT FK_66 FOREIGN KEY (username) REFERENCES public.User_credentials (username),
72 | CONSTRAINT FK_69 FOREIGN KEY (idea_id) REFERENCES Ideas (idea_id)
73 | );
74 |
75 | CREATE INDEX fkIdx_66 ON Favorites
76 | (
77 | username
78 | );
79 |
80 | CREATE INDEX fkIdx_69 ON Favorites
81 | (
82 | idea_id
83 | );
84 |
85 | CREATE TABLE Idea_Tech_Stacks
86 | (
87 | idea_id integer NOT NULL,
88 | tech_id integer NOT NULL,
89 | CONSTRAINT FK_84 FOREIGN KEY ( idea_id ) REFERENCES Ideas (idea_id),
90 | CONSTRAINT FK_87 FOREIGN KEY ( tech_id ) REFERENCES Tech_Stacks ( tech_id )
91 | );
92 |
93 | CREATE INDEX fkIdx_84 ON Idea_Tech_Stacks
94 | (
95 | idea_id
96 | );
97 |
98 | CREATE INDEX fkIdx_87 ON Idea_Tech_Stacks
99 | (
100 | tech_id
101 | );
102 |
103 | CREATE TABLE Tech_Stacks
104 | (
105 | tech_id serial NOT NULL,
106 | name varchar(50) NOT NULL,
107 | CONSTRAINT PK_tech_stacks PRIMARY KEY ( tech_id )
108 | );
109 |
110 | CREATE TABLE User_tech_stacks
111 | (
112 | user_id integer NOT NULL,
113 | tech_id integer NOT NULL,
114 | CONSTRAINT FK_94 FOREIGN KEY (user_id) REFERENCES public.Users (user_id),
115 | CONSTRAINT FK_97 FOREIGN KEY (tech_id) REFERENCES Tech_Stacks
116 | (tech_id)
117 | );
118 |
119 | CREATE INDEX fkIdx_94 ON User_tech_stacks
120 | (
121 | user_id
122 | );
123 |
124 | CREATE INDEX fkIdx_97 ON User_tech_stacks
125 | (
126 | tech_id
127 | );
--------------------------------------------------------------------------------
/src/components/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import '../styles/login-signup.scss';
4 | import '../../node_modules/bootstrap/dist/css/bootstrap.css';
5 | import { Form, Button } from 'react-bootstrap';
6 |
7 | const Signup = (props) => {
8 | const { authStatus, setAuthStatus } = props;
9 |
10 | const [registrationInputs, setRegistrationInputs] = useState({
11 | firstname: '',
12 | lastname: '',
13 | username: '',
14 | password: '',
15 | confirmPassword: '',
16 | email: '',
17 | });
18 |
19 | const [errorMsg, setErrorMsg] = useState('');
20 | const [registerStatus, setRegisterStatus] = useState(false);
21 |
22 | const handleSubmit = async (e) => {
23 | e.preventDefault();
24 | const {
25 | username,
26 | password,
27 | email,
28 | confirmPassword,
29 | firstname,
30 | lastname,
31 | } = registrationInputs;
32 | if (password !== confirmPassword)
33 | return setErrorMsg(`Passwords don't match!`);
34 |
35 | const body = {
36 | username,
37 | password,
38 | email,
39 | firstname,
40 | lastname,
41 | };
42 |
43 | let response = await fetch('/api/signup', {
44 | method: 'POST',
45 | headers: {
46 | 'Content-Type': 'application/json',
47 | },
48 | body: JSON.stringify(body),
49 | });
50 |
51 | if (response.status === 200) {
52 | setRegisterStatus(true);
53 | setAuthStatus({ isLoggedIn: true, username });
54 | } else
55 | setErrorMsg('New user could not be created - duplicate username/email');
56 | };
57 |
58 | const setInput = (e) => {
59 | setRegistrationInputs({
60 | ...registrationInputs,
61 | [e.target.id]: e.target.value,
62 | });
63 | };
64 |
65 | return registerStatus || authStatus.isLoggedIn ? (
66 |
71 | ) : (
72 |
73 |
74 |
75 | Welcome!
76 |
77 |
79 | Username
80 |
86 |
87 |
88 |
89 | First Name
90 |
96 |
97 |
98 |
99 | Last Name
100 |
106 |
107 |
108 |
109 | Email
110 |
115 |
116 |
117 |
118 | Password
119 |
125 |
126 |
127 |
128 | Password
129 |
135 |
136 |
137 |
140 |
141 |
{errorMsg}
142 |
143 |
144 | );
145 | };
146 |
147 | export default Signup;
148 |
--------------------------------------------------------------------------------
/src/components/IdeaPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import Spinner from './Spinner';
4 | import '../styles/ideapage.scss';
5 | import { Container, Col, Row, Button } from 'react-bootstrap';
6 |
7 | const IdeaPage = (props) => {
8 | //passed in from Explore
9 | let { idea_id, authStatus } = props.location.state;
10 | const [ideaData, setIdeaData] = useState({});
11 | const [interested, setInterested] = useState(false);
12 |
13 | useEffect(() => {
14 | getIdea();
15 | }, []);
16 |
17 | const getIdea = async () => {
18 | const res = await fetch(`/api/explore/${idea_id}`);
19 | const parsed = await res.json();
20 | setIdeaData(parsed);
21 | };
22 |
23 | const handleInterestClick = async () => {
24 | setInterested(true);
25 | //TODO: actually build out functionality to email/notify creator
26 | };
27 |
28 | if (!Object.keys(ideaData).length) return ;
29 | else if (ideaData.err)
30 | return Could not load idea;
31 |
32 | let {
33 | name,
34 | description,
35 | why,
36 | when_start,
37 | when_end = null,
38 | who,
39 | creator_username,
40 | image,
41 | participants,
42 | techStacks,
43 | profilepic,
44 | } = ideaData;
45 |
46 | return (
47 |
48 |
49 |
50 | WHY
51 | {why}
52 |
53 | HOW
54 |
55 | Tech Stack
56 |
57 |
58 |
59 | {techStacks.map((stack, idx) => (
60 | - {stack.name}
61 | ))}
62 |
63 |
64 |
65 | WHEN
66 |
67 |
68 | Start Date: {when_start ? when_start.substring(0, 10) : undefined}
69 |
70 | {when_end ? (
71 |
72 | End Date: {when_end ? when_end.substring(0, 10) : undefined}
73 |
74 | ) : undefined}
75 |
76 |
77 | WHO
78 |
79 | Current Teammates - {who} Desired
80 | {/* TODO: MAKE EACH LI A LINK TO USER PROFILE */}
81 |
82 | -
83 | {/* TODO:CONVERT THE PROFILE PIC IN SCHEMA TO STRING */}
84 |
93 |
94 |
95 | {creator_username} (creator)
96 |
97 | {participants.map((user, idx) => (
98 | -
99 |
100 | {user.username}
101 |
102 | ))}
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | {name}
111 |
112 | {description}
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | {!interested ? (
123 |
130 | ) : (
131 |
134 | )}
135 |
136 |
137 |
138 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | );
148 | };
149 |
150 | export default IdeaPage;
151 |
--------------------------------------------------------------------------------
/src/components/Explore.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from 'react';
2 | import { Container, Col, Row, Form, Card, Button } from 'react-bootstrap';
3 | import axios from 'axios';
4 | import { NavLink } from 'react-router-dom';
5 | import Spinner from './Spinner';
6 |
7 | const Explore = (props) => {
8 | const { authStatus } = props;
9 | const [response, setResponse] = useState([
10 | {
11 | idea_id: '',
12 | name: '',
13 | description: '',
14 | why: '',
15 | when_start: '',
16 | when_end: '',
17 | who: '',
18 | creator_username: '',
19 | image: '',
20 | techstacks: [],
21 | },
22 | ]);
23 |
24 | const [query, setQuery] = useState('');
25 | const [techList, setTechList] = useState([{ tech_id: '', name: '' }]);
26 | //for sort by tech stack functionality, if user checks off a tech, it gets added to array
27 | const [techFilter, setTechFilter] = useState([]);
28 |
29 | useEffect(() => {
30 | const fetchData = async () => {
31 | const results = await axios.get('api/explore');
32 | // console.log('results.data', results.data);
33 | setResponse(results.data[0]);
34 | setTechList(results.data[1]);
35 | // console.log(results.data[0]);
36 | };
37 |
38 | fetchData();
39 | }, []);
40 |
41 | const handleTechFilter = (e) => {
42 | let newTechFilter;
43 | if (techFilter.includes(e.target.value)) {
44 | newTechFilter = techFilter.filter((tech) => tech !== e.target.value);
45 | } else newTechFilter = [...techFilter, e.target.value];
46 | setTechFilter(newTechFilter);
47 | };
48 |
49 | // get technologies
50 | const techStack = techList.map((tech) => tech.name);
51 | // Generate checkbox component for each technology
52 | const generateTech = techStack.map((tech, idx) => {
53 | return (
54 |
70 | );
71 | });
72 |
73 | const onChange = (q) => setQuery(q);
74 |
75 | const ideas = response;
76 |
77 | const sortedIdeas = ideas.filter((data) => {
78 | return data.name.toLowerCase().indexOf(query.toLowerCase()) !== -1;
79 | });
80 |
81 | //check if user wants to filter for tech, otherwise just return sortedIdeas as-is
82 | const filteredIdeas = techFilter.length
83 | ? sortedIdeas.filter((idea) => {
84 | //if idea has tech that is inside techFilter, then include that idea
85 | for (let i = 0; i < techFilter.length; i++) {
86 | const selectedTech = techFilter[i];
87 | if (!idea.techstacks.includes(selectedTech)) return false;
88 | }
89 | return true;
90 | })
91 | : sortedIdeas;
92 |
93 | const generateBoxes = filteredIdeas.map((idea, idx) => {
94 | return (
95 |
96 |
97 |
98 | {idea.name}
99 | {idea.description}
100 |
101 |
102 | Tech Stack:{' '}
103 | {' '}
104 |
105 | {idea.techstacks.join(', ')}
106 |
107 |
116 |
117 |
118 |
119 |
120 | );
121 | });
122 |
123 | // Search box component
124 | const searchIdea = (
125 |
127 |
128 | {' '}
129 | May your dream come true
{' '}
130 |
131 | onChange(e.target.value)}
136 | />
137 | {/* */}
138 |
139 |
140 | );
141 |
142 | const explorePage = (
143 |
144 |
145 |
146 |
147 | {' '}
148 |
152 | {' '}
153 | Choose your tech stack:{' '}
154 |
155 |
156 | {generateTech}
157 |
158 |
159 |
160 | {searchIdea}
161 | {generateBoxes}
162 |
163 |
164 |
165 | );
166 |
167 | return response.length === 1 ? (
168 |
169 | ) : (
170 | {explorePage}
171 | );
172 | };
173 |
174 | export default Explore;
175 |
--------------------------------------------------------------------------------
/server/Controllers/ideaController.js:
--------------------------------------------------------------------------------
1 | const model = require('../Models/model.js');
2 |
3 | // controllers for explor page
4 | const ideaController = {};
5 |
6 | // middleware to get all ideas data from database
7 | ideaController.getIdeas = (req, res, next) => {
8 | /* query text will join tables for ideas, idea_tech_stacks, and tech_stacks
9 | then aggregate the tech stack names into an array
10 | */
11 | const queryText = `SELECT Ideas.*, array_agg(tech_stacks.name) AS techstacks FROM Ideas
12 | JOIN Idea_tech_stacks ON Idea_tech_stacks.idea_id = Ideas.idea_id
13 | JOIN tech_stacks ON tech_stacks.tech_id=Idea_tech_stacks.tech_id
14 | GROUP BY Ideas.idea_id`;
15 |
16 | model.query(queryText, (err, results) => {
17 | if (err) {
18 | console.log(err);
19 | return next({
20 | log: `error occurred at getIdeas middleware. error message is: ${err}`,
21 | status: 400,
22 | message: { err: 'An error occurred' },
23 | });
24 | }
25 | // console.log('results', results.rows);
26 | res.locals.ideas = results.rows;
27 | return next();
28 | });
29 | };
30 |
31 | // INSERT INTO Ideas (name, description, why, when_start, when_end, who, image, creator_username) VALUES ('scratch', 'scratch project', 'for fun', '2020-07-25', '2020-08-15', '3', 'image.png', 'hello1');
32 |
33 | // we need to know who's submitting the idea
34 | ideaController.submitIdea = (req, res, next) => {
35 | const {
36 | name,
37 | description,
38 | why,
39 | techStack,
40 | whenStart,
41 | whenEnd,
42 | teamNumber,
43 | imageURL,
44 | username,
45 | } = req.body;
46 |
47 | const teamNumberInt = Number(teamNumber);
48 |
49 | // will need to get user's username (may need to modify database to grab user_id instead...)
50 | // front end to modify date to following format YYYY-MM-DD
51 | // if dateEnd is not given, front end to assign it as null
52 |
53 | // if imageurl or endDate is falsy, then we have to omit from query text/value so that it will default to default image or date(null)
54 | let queryText1;
55 | let queryValue1;
56 | if (!whenEnd && !imageURL) {
57 | queryText1 = `INSERT INTO Ideas (name, description, why, when_start, who, creator_username) VALUES ($1, $2, $3, $4, $5, $6) RETURNING idea_id`;
58 | queryValue1 = [name, description, why, whenStart, teamNumberInt, username];
59 | } else if (!imageURL) {
60 | queryText1 = `INSERT INTO Ideas (name, description, why, when_start, when_end, who, creator_username) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING idea_id`;
61 | queryValue1 = [
62 | name,
63 | description,
64 | why,
65 | whenStart,
66 | whenEnd,
67 | teamNumberInt,
68 | username,
69 | ];
70 | } else if (!whenEnd) {
71 | queryText1 = `INSERT INTO Ideas (name, description, why, when_start, who, image, creator_username) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING idea_id`;
72 | queryValue1 = [
73 | name,
74 | description,
75 | why,
76 | whenStart,
77 | teamNumberInt,
78 | imageURL,
79 | username,
80 | ];
81 | } else {
82 | queryText1 = `INSERT INTO Ideas (name, description, why, when_start, when_end, who, image, creator_username) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING idea_id`;
83 | queryValue1 = [
84 | name,
85 | description,
86 | why,
87 | whenStart,
88 | whenEnd,
89 | teamNumberInt,
90 | imageURL,
91 | username,
92 | ];
93 | }
94 | let addedIdeaId;
95 | model.query(queryText1, queryValue1, async (err, result) => {
96 | if (err) {
97 | console.log(err);
98 | return next({
99 | log: `error occurred at submitIdea middleware query1. error message is: ${err}`,
100 | status: 400,
101 | message: { err: 'An error occurred' },
102 | });
103 | }
104 | addedIdeaId = result.rows[0].idea_id;
105 |
106 | // separate query to insert tech stacks into idea_tech_stacks
107 | let queryText2;
108 | const quertValue2 = [];
109 | for (let i = 0; i < techStack.length; i += 1) {
110 | quertValue2.push([addedIdeaId, techStack[i]]);
111 | }
112 | // console.log(techStack);
113 | for (let i = 0; i < techStack.length; i += 1) {
114 | queryText2 = `INSERT INTO Idea_tech_stacks (idea_id, tech_id) VALUES ($1, $2)`;
115 | await model.query(queryText2, quertValue2[i], (err) => {
116 | if (err) {
117 | console.log(err);
118 | return next({
119 | log: `error occurred at submitIdea middleware query2. error message is: ${err}`,
120 | status: 400,
121 | message: { err: 'An error occurred' },
122 | });
123 | }
124 | });
125 | }
126 | return next();
127 | });
128 | };
129 |
130 | // middleware to get one idea
131 | // need to set up route for this
132 | ideaController.getOneIdea = async (req, res, next) => {
133 | const id = req.params.ideaID;
134 | try {
135 | const ideasQueryText = `SELECT * FROM Ideas
136 | JOIN Users
137 | ON ideas.creator_username = users.username
138 | WHERE idea_id=${id}`;
139 | const ideaDetail = await model.query(ideasQueryText);
140 | // rows will only contain one. ok to destructure
141 | [res.locals.idea] = ideaDetail.rows;
142 |
143 | const participantQueryText = `SELECT *
144 | FROM idea_participants
145 | JOIN users
146 | ON idea_participants.participant_username = users.username
147 | WHERE idea_id = ${id}`;
148 | const participants = await model.query(participantQueryText);
149 | //will return array of objects
150 | res.locals.idea = { ...res.locals.idea, participants: participants.rows };
151 |
152 | const stackQueryText = `SELECT * FROM idea_tech_stacks
153 | JOIN tech_stacks
154 | ON tech_stacks.tech_id = idea_tech_stacks.tech_id
155 | WHERE idea_id = ${id}`;
156 | const techStacks = await model.query(stackQueryText);
157 | res.locals.idea = { ...res.locals.idea, techStacks: techStacks.rows };
158 |
159 | return next();
160 | } catch (err) {
161 | return next({
162 | log: `error occurred at getOneIdea middleware. error message is: ${err}`,
163 | status: 400,
164 | message: { err: 'An error occurred' },
165 | });
166 | }
167 | };
168 |
169 | module.exports = ideaController;
170 |
--------------------------------------------------------------------------------
/src/components/SubmitIdea.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Container, Form, Button, Row, Col } from 'react-bootstrap';
3 | import { Typeahead } from 'react-bootstrap-typeahead';
4 | import 'react-bootstrap-typeahead/css/Typeahead.css';
5 | import axios from 'axios';
6 |
7 | const SubmitIdea = (props) => {
8 | const { authStatus } = props;
9 | const { username } = authStatus;
10 |
11 | const [retrievedTechStacks, setRetrievedTechStacks] = useState([]);
12 |
13 | const [name, setName] = useState('');
14 | const [description, setDescription] = useState('');
15 | const [why, setWhy] = useState('');
16 | const [techStack, setTechStack] = useState([]);
17 | const [teamNumber, setTeamNumber] = useState('');
18 | const [imageURL, setImageURL] = useState('');
19 | const [whenStart, setWhenStart] = useState('');
20 | const [whenEnd, setWhenEnd] = useState('');
21 | const [isSubmitted, setIsSubmitted] = useState(false);
22 |
23 | // get request to backend to fetch tech stack data
24 | useEffect(() => {
25 | const fetchTechs = async () => {
26 | const results = await axios.get('/api/submit');
27 | // array of objects with id and name. need to filter for just names
28 |
29 | const techNamesList = results.data.map((el) => el.name);
30 | // console.log('techNamesList', techNamesList);
31 | setRetrievedTechStacks(techNamesList);
32 | // console.log('techStacks', techStacks);
33 | };
34 |
35 | fetchTechs();
36 | // console.log('techStacks', techStacks);
37 | }, []);
38 |
39 | const handleChange = (e) => {
40 | const { id, value } = e.target;
41 | switch (id) {
42 | case 'name':
43 | setName(value);
44 | break;
45 | case 'description':
46 | setDescription(value);
47 | break;
48 | case 'why':
49 | setWhy(value);
50 | break;
51 | case 'who':
52 | setTeamNumber(value);
53 | break;
54 | case 'uploadImage':
55 | setImageURL(value);
56 | break;
57 | default:
58 | console.log('not working');
59 | }
60 | };
61 |
62 | // helper function to convert tech stack into tech stack id
63 | const techIDConverter = (techs) => {
64 | const result = techs.map((el) => retrievedTechStacks.indexOf(el) + 1);
65 | return result;
66 | };
67 |
68 | const handleSubmit = async (e) => {
69 | e.preventDefault();
70 | // convert tech stack into tech stack id
71 | const techStackID = techIDConverter(techStack);
72 |
73 | const data = {
74 | name,
75 | description,
76 | why,
77 | techStack: techStackID,
78 | whenStart,
79 | whenEnd,
80 | teamNumber,
81 | imageURL,
82 | // hardcode, need a logic to pass username as prop
83 | username,
84 | };
85 | // console.log('data', data);
86 | await axios.post('/api/submit', data);
87 | setIsSubmitted(true);
88 | };
89 |
90 | return isSubmitted ? (
91 |
100 |
101 | You submitted your idea successfully!
102 |
103 |
112 |
115 |
123 |
124 |
125 | ) : (
126 |
127 |
222 |
223 | );
224 | };
225 |
226 | export default SubmitIdea;
227 |
--------------------------------------------------------------------------------