├── .gitignore ├── style.css ├── README.md ├── .env ├── client ├── reducers │ ├── index.js │ └── taskReducer.js ├── actions │ ├── actionTypes.js │ ├── fetches.js │ └── actions.js ├── components │ ├── about.jsx │ ├── columnContainer.jsx │ ├── cardComponent.jsx │ ├── navBar.jsx │ ├── form.jsx │ └── cardContainer.jsx ├── index.js └── App.jsx ├── server ├── models │ └── cardsModel.js ├── server.js └── controllers │ └── cardController.js ├── index.html ├── webpack.config.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ffdae8; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TROLLO SCRATCH PROJECT 2 | Mock Kanban Board project with dynamic task components 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MONGO_URI = "mongodb+srv://axolotl:axolotl@cluster0.pupqxms.mongodb.net/test" 2 | NODE_ENV = "development" -------------------------------------------------------------------------------- /client/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import TaskReducer from './taskReducer' 3 | 4 | export default combineReducers({ TaskReducer }); -------------------------------------------------------------------------------- /client/actions/actionTypes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const GET_COLUMNS = 'GET_COLUMNS'; 4 | export const ADD_TASK = 'ADD_TASK'; 5 | export const GET_TASKS = 'GET_TASKS'; 6 | export const ADD_COLUMN = 'ADD_COLUMN'; 7 | -------------------------------------------------------------------------------- /client/components/about.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormControl, FormLabel, FormErrorMessage, FormHelperText, Text } from '@chakra-ui/react' 3 | 4 | const About = () => { 5 | return ( 6 |
All Hail Axolotls!
7 | ) 8 | } 9 | export default About -------------------------------------------------------------------------------- /client/actions/fetches.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const url = '/api/getCards' 4 | 5 | export const fetchTasks = () => axios.get(url); 6 | 7 | export const addTask = (body) => {axios.post('/api/createCard',{ 8 | newCardName: body.cardName, 9 | newCardDescription: body.cardDescription, 10 | newCardAssigned: body.cardAssigned, 11 | newCardCategory: body.cardCategory 12 | } 13 | )} -------------------------------------------------------------------------------- /server/models/cardsModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | //okay to remove deleted comments saying hi 6 | 7 | //creating DB schema 8 | 9 | const cardSchema = new Schema ({ 10 | cardName: { type: String, required: true }, 11 | cardAssigned: String, 12 | cardDescription: String, 13 | cardCategory: String, 14 | }); 15 | 16 | //if want users to be in DB, can create user schema below and export 17 | 18 | 19 | const Card = mongoose.model('card', cardSchema) 20 | module.exports = Card; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Axolotl 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import { Provider } from 'react-redux' 5 | import { configureStore, compose, applyMiddleware } from '@reduxjs/toolkit' 6 | //thunk is asynchronous redux 7 | import thunk from 'redux-thunk' 8 | 9 | import reducers from './reducers/index' 10 | // import { createRoot } from 'react-dom/client'; 11 | 12 | 13 | 14 | 15 | // setting up redux store w/ configureStore() 16 | const store = configureStore({reducer: reducers}, compose(applyMiddleware(thunk))) 17 | 18 | 19 | const root = ReactDOM.createRoot(document.getElementById('root')); 20 | 21 | root.render( 22 | 23 | 24 | 25 | 26 | 27 | ); -------------------------------------------------------------------------------- /client/actions/actions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as api from './fetches.js' 3 | 4 | 5 | 6 | export const getTasks = () => async(dispatch) => { 7 | try{ 8 | const { data } = await api.fetchTasks(); 9 | console.log('this is our data: ', data); 10 | dispatch({type: 'GET_TASKS', payload: data}) 11 | } catch (error) { 12 | console.log({error: error.message}) 13 | } 14 | } 15 | 16 | 17 | export const createTask = (task) => async(dispatch) => { 18 | try{ 19 | // const { data } = await api.addTask(task); 20 | // console.log('this is our new task:', data); 21 | // dispatch({type: 'ADD_TASK', payload: data}) 22 | let action; 23 | await axios.post('/api/createCard', task) 24 | .then((res) => { 25 | action = {type: 'ADD_TASK', payload: res.data} 26 | }) 27 | dispatch(action); 28 | } catch (error) { 29 | console.log({error: error.message}) 30 | } 31 | } -------------------------------------------------------------------------------- /client/reducers/taskReducer.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const myState = { 4 | taskContainer: [], 5 | columnContainer: [], 6 | } 7 | 8 | export default (state = myState, action) => { 9 | let newState; 10 | switch (action.type) { 11 | case 'GET_COLUMNS': 12 | // newState = { 13 | // columnContainer: 14 | // } 15 | // return newState 16 | case 'ADD_TASK': 17 | newState = { 18 | columnContainer: state.columnContainer, 19 | taskContainer: state.taskContainer.push(action.payload), 20 | } 21 | return newState; 22 | // return [...state.taskContainer, action.payload] 23 | case 'GET_TASKS': 24 | // assuming payload 25 | newState = { 26 | columnContainer: state.columnContainer, 27 | taskContainer: action.payload, 28 | } 29 | return newState; 30 | case 'ADD_COLUMN': 31 | state.columnContainer.push(action.payload); 32 | default: 33 | return state; 34 | } 35 | } -------------------------------------------------------------------------------- /client/components/columnContainer.jsx: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | // need to import the cards 3 | import CardContainer from './cardContainer.jsx'; 4 | //need to import the actions 5 | // import GetTasks from '../reducers/actionTypes.js' 6 | // we're importing the properties Stack, Card, and Container from Chakra-Ui 7 | import { HStack, Card, CardHeader, Editable, EditablePreview, EditableInput} from '@chakra-ui/react' 8 | import { useEffect } from 'react'; 9 | import { useDispatch } from 'react-redux'; 10 | import { getTasks } from '../actions/actions' 11 | import {AddIcon} from '@chakra-ui/icons' 12 | 13 | const ColumnContainer = () => { 14 | const dispatch = useDispatch(); 15 | 16 | 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default ColumnContainer; -------------------------------------------------------------------------------- /client/components/cardComponent.jsx: -------------------------------------------------------------------------------- 1 | // we need a use selector to get the state. State would hold an array of each column 2 | // need to import react 3 | import React from 'react'; 4 | // we're importing the properties Stack, Card, and Container from Chakra-Ui 5 | import { 6 | Card, 7 | Box, 8 | CardHeader, 9 | Text, 10 | CardBody, 11 | CardFooter, 12 | Stack, 13 | Divider, 14 | Editable, 15 | Accordion, 16 | AccordionItem, 17 | AccordionButton, 18 | AccordionIcon, 19 | AccordionPanel} from '@chakra-ui/react' 20 | 21 | 22 | //declaring our CardComponent variable 23 | const CardComponent = ({ task }) => { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | {task.cardName} 31 | 32 | 33 | {task.cardDescription} 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default CardComponent; 43 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import ColumnContainer from './/components/columnContainer.jsx' 3 | import { extendTheme, ChakraProvider } from '@chakra-ui/react'; 4 | import NavBar from './/components/navBar.jsx'; 5 | import { Box, Flex , Card} from '@chakra-ui/react' 6 | import { useEffect } from 'react'; 7 | import { useDispatch } from 'react-redux'; 8 | 9 | import { getTasks } from './actions/actions' 10 | 11 | //theme variable like CSS, but for the chakra components 12 | const theme = extendTheme({ 13 | fonts: { 14 | body: '"Poor Story", cursive', 15 | } 16 | 17 | }) 18 | 19 | 20 | 21 | function App () { 22 | const dispatch = useDispatch(); 23 | 24 | return( 25 | 26 | 27 | 31 | 35 | 36 | 37 | 38 | 39 | {/* */} 40 | 41 | 42 | ) 43 | } 44 | 45 | export default App; -------------------------------------------------------------------------------- /client/components/navBar.jsx: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | import About from './about.jsx'; 3 | // we're importing the properties Stack, Card, and Container from Chakra-Ui 4 | import { 5 | Button, 6 | Image, 7 | Flex, 8 | Card, 9 | CardHeader, 10 | Text, 11 | CardBody, 12 | CardFooter, 13 | Stack, 14 | Divider, 15 | Modal, 16 | ModalOverlay, 17 | ModalContent, 18 | ModalHeader, 19 | ModalFooter, 20 | ModalBody, 21 | ModalCloseButton,} from '@chakra-ui/react' 22 | 23 | const NavBar = (isOpen, onOpen, onClose) => { 24 | return ( 25 | <> 26 | 37 | 41 | TROLLO 42 | {/* 43 | */} 44 | ABOUT 45 | {/* */} 46 | 47 | 48 | ) 49 | 50 | } 51 | 52 | 53 | 54 | export default NavBar -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | module.exports = { 4 | entry: './client/index.js', 5 | output: { 6 | path: path.join(__dirname, '/build'), 7 | filename: 'bundle.js' 8 | }, 9 | mode: process.env.NODE_ENV, 10 | module: { 11 | rules: [ 12 | { 13 | test: /.(js|jsx)$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: 'babel-loader', 17 | options: { 18 | presets: [ 19 | "@babel/preset-env", '@babel/preset-react' 20 | ] 21 | 22 | } 23 | 24 | }, 25 | }, 26 | { 27 | test: /\.s[ac]ss$/i, 28 | use: [ 29 | // Creates `style` nodes from JS strings 30 | "style-loader", 31 | // Translates CSS into CommonJS 32 | "css-loader", 33 | // Compiles Sass to CSS 34 | "sass-loader", 35 | ], 36 | }, 37 | ] 38 | }, 39 | plugins: [new HtmlWebpackPlugin({ 40 | template: '/index.html' 41 | })], 42 | devServer: { 43 | // static: { 44 | // directory: path.join(__dirname, '/build/bundle.js'), 45 | // }, 46 | compress: true, 47 | port: 8080, 48 | proxy: { 49 | '/api': 'http://localhost:3000', 50 | }, 51 | }, 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /client/components/form.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { FormControl, FormLabel, FormErrorMessage, FormHelperText, Button, Input } from '@chakra-ui/react' 3 | import { addTask } from '../actions/fetches'; 4 | import { useDispatch } from 'react-redux'; 5 | import { createTask } from '../actions/actions.js' 6 | 7 | const Form = () =>{ 8 | const [taskData, setTaskData] = useState({ 9 | cardName: '', cardDescription: '', cardAssigned: '', cardCategory: '' 10 | }) 11 | const dispatch = useDispatch(); 12 | async function handleSubmit(event){ 13 | const data = {cardName: document.getElementById('cardName').value, 14 | cardDescription: document.getElementById('cardDescription').value, 15 | cardAssigned: document.getElementById('cardAssigned').value, 16 | cardCategory: document.getElementById('cardCategory').value 17 | }; 18 | 19 | addTask(data) 20 | } 21 | 22 | 23 | return ( 24 |
25 |
26 | 27 | Task Name 28 | 29 | 30 | 31 | Task Description 32 | 33 | Category 34 | 35 | Created by 36 | 37 | 38 | 39 |
40 |
41 | ) 42 | } 43 | 44 | export default Form; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --mode development --open --hot\" \" nodemon server/server.js\"", 9 | "build": "webpack" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Axolotl-54/Main.git" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/Axolotl-54/Main/issues" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.20.2", 23 | "@babel/preset-env": "^7.20.2", 24 | "@babel/preset-react": "^7.18.6", 25 | "babel-loader": "^9.1.0", 26 | "concurrently": "^6.0.2", 27 | "cross-env": "^7.0.3", 28 | "css-loader": "^6.7.2", 29 | "html-webpack-plugin": "^5.5.0", 30 | "isomorphic-fetch": "^3.0.0", 31 | "nodemon": "^2.0.7", 32 | "sass": "^1.56.1", 33 | "sass-loader": "^13.2.0", 34 | "style-loader": "^3.3.1", 35 | "webpack": "^5.75.0", 36 | "webpack-cli": "^4.8.0", 37 | "webpack-dev-server": "^4.11.1" 38 | }, 39 | "homepage": "https://github.com/Axolotl-54/Main#readme", 40 | "dependencies": { 41 | "@chakra-ui/icons": "^2.0.12", 42 | "@chakra-ui/react": "^2.4.1", 43 | "@emotion/react": "^11.10.5", 44 | "@emotion/styled": "^11.10.5", 45 | "@fontsource/poor-story": "^4.5.9", 46 | "@reduxjs/toolkit": "^1.9.0", 47 | "axios": "^1.1.3", 48 | "concurrently": "^7.5.0", 49 | "dotenv": "^16.0.3", 50 | "es6-promise": "^4.2.8", 51 | "express": "^4.18.2", 52 | "framer-motion": "^7.6.7", 53 | "isomorphic-fetch": "^3.0.0", 54 | "mongoose": "^6.7.2", 55 | "nodemon": "^2.0.20", 56 | "react": "^18.2.0", 57 | "react-dom": "^18.2.0", 58 | "react-redux": "^8.0.5", 59 | "react-router-dom": "^6.4.3", 60 | "redux": "^4.2.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /client/components/cardContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import CardComponent from './cardComponent.jsx' ; 4 | import { Stack, VStack, StackDivider, Button, Card, CardHeader, CardBody, CardFooter } from '@chakra-ui/react'; 5 | import { 6 | Popover, 7 | PopoverTrigger, 8 | PopoverContent, 9 | PopoverHeader, 10 | PopoverBody, 11 | PopoverFooter, 12 | PopoverArrow, 13 | PopoverCloseButton, 14 | PopoverAnchor, 15 | } from '@chakra-ui/react' 16 | 17 | import { getTasks } from '/client/actions/actions.js' 18 | 19 | //we're importing the form file 20 | import Form from './form.jsx'; 21 | 22 | 23 | 24 | 25 | 26 | const CardContainer = () => { 27 | // grabbing array of tasks from state, have to get state from the index.js file because it imports TaskReducer 28 | const dispatch = useDispatch() 29 | // dispatch(getTasks()); 30 | // const tasks = useSelector((state) => state.TaskReducer.taskContainer); 31 | 32 | useEffect(() => { 33 | // fetch get from db and send to redux store 34 | dispatch(getTasks()) 35 | }, [dispatch]) 36 | 37 | const tasks = useSelector((state) => state.TaskReducer.taskContainer); 38 | console.log('these are our tasks: ', tasks); 39 | 40 | 41 | return ( 42 | 43 | To Do List 44 | } 46 | spacing = {2} 47 | align= 'stretch' 48 | > 49 | {tasks.map((task) => ( 50 | 51 | ))} 52 | 53 | 54 | 55 | 56 | 57 | 58 | Create Task 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | ) 68 | } 69 | 70 | export default CardContainer; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const path = require('path'); 4 | const mongoose = require('mongoose'); 5 | require('dotenv').config() 6 | 7 | 8 | const MONGO_URI = process.env.MONGO_URI; 9 | const cardController = require('./controllers/cardController.js') 10 | 11 | mongoose.connect(MONGO_URI, { 12 | // options for the connect method to parse the URI 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | // sets the name of the DB that our collections are part of 16 | dbName: 'test' 17 | }) 18 | .then(() => console.log('Connected to Mongo DB.')) 19 | .catch(err => console.log(err)); 20 | 21 | app.use(express.json()); 22 | app.use(express.urlencoded({ extended: true})); 23 | app.use('/dist', express.static(path.join(__dirname, '../dist'))); 24 | 25 | //all get routes below 26 | app.get('/', (req, res) => { 27 | res.sendFile(path.join(__dirname, '../index.html')); 28 | }); 29 | 30 | app.get('/api/getCards', cardController.findCards, (req, res) => { 31 | res.status(200).json(res.locals.allCards); 32 | }); 33 | 34 | //serve static css file for original get request to / for index 35 | // app.get('/styles.css', (req, res) => { 36 | // res.sendFile(path.join(__dirname,'../client/styles.css')); 37 | // }); 38 | 39 | // all post routes below 40 | app.post('/api/createCard', cardController.createCard, (req, res) => { 41 | res.status(200).json(res.locals.newCard); 42 | }); 43 | 44 | //all patch routes below 45 | app.patch('/api/updateCard', cardController.updateCard, (req, res) => { 46 | res.status(200).json(res.locals.updatedCard); 47 | }); 48 | 49 | //delete route 50 | app.delete('/api/deleteCard', cardController.deleteCard, (req, res) => { 51 | res.status(200).json(res.locals.deletedCard); 52 | }); 53 | 54 | app.use((err, req, res, next) => { 55 | const defErr = { 56 | log: 'Caught middleware error', 57 | status: 500, 58 | message: {err: 'an error occured'} 59 | }; 60 | const errorObj = Object.assign({}, defErr, err); 61 | console.log(errorObj.log); 62 | return res.status(errorObj.status).json(errorObj.message); 63 | }) 64 | 65 | app.listen(3000, console.log('listening on 3000...')); -------------------------------------------------------------------------------- /server/controllers/cardController.js: -------------------------------------------------------------------------------- 1 | const Card = require('../models/cardsModel'); 2 | // const mongoose = require('mongoose'); 3 | 4 | 5 | 6 | const cardController = { 7 | //create card middleware 8 | async createCard(req, res, next) { 9 | console.log('body', req.body) 10 | console.log('clicked create card', req.body); 11 | try { 12 | const { newCardName, newCardAssigned, newCardDescription, newCardCategory } = req.body //assuming they call it cardName 13 | const result = await Card.create({ cardName: newCardName, cardAssigned: newCardAssigned, cardDescription: newCardDescription, cardCategory: newCardCategory }); 14 | res.locals.newCard = result; 15 | console.log(result); 16 | return next(); 17 | } catch (err) { 18 | return next({ 19 | log: 'Express error in cardController.createCard', 20 | status: 500, 21 | message: { 22 | err: 'An error occured inside the cardController.createCard' 23 | } 24 | }) 25 | } 26 | }, 27 | 28 | async findCards(req, res, next) { 29 | try { 30 | const result = await Card.find({});//might need to change to no parameters to get all 31 | res.locals.allCards = result; 32 | console.log('api called') 33 | return next() 34 | } catch (err) { 35 | err.log = 'Expressor error in carController findCards'; 36 | err.status = 500; 37 | err.message = { err: 'Expressor error in carController findCards' }; 38 | return next(err); 39 | } 40 | }, 41 | 42 | 43 | async updateCard(req, res, next) { 44 | console.log('inside updateCard middleware'); 45 | try { 46 | const { cardToUpdate, assignToUpdate, descriptionToUpdate, categoryToUpdate } = req.body; //tell them cardToUpdate; 47 | const result = await Card.findOneAndUpdate({ cardName: cardToUpdate }, { cardName: cardToUpdate, cardAssigned: assignToUpdate, cardDescription: descriptionToUpdate, cardCategory: categoryToUpdate }, { new: true }); 48 | if (!result) { //if query goes through and can't find, need specific error 49 | const err = { message: {err: 'Cannot update card if we cannot find card to update!' }} 50 | return next(err); 51 | }; 52 | res.locals.updatedCard = result; 53 | console.log('updated card?', result) 54 | return next(); 55 | } catch { 56 | return next({ 57 | log: 'Express error in cardController.updateCard', 58 | status: 500, 59 | message: { 60 | err: 'An error occured inside the cardController.updateCard' 61 | } 62 | }) 63 | } 64 | }, 65 | 66 | 67 | // cardAssigned: String, 68 | // cardDescription: String, 69 | // cardCategory 70 | 71 | 72 | async deleteCard(req, res, next) { 73 | console.log('in delete middleware'); 74 | try { 75 | const { cardNameToDelete } = req.body; 76 | const deletedCard = await Card.findOneAndDelete({ cardName: cardNameToDelete }); 77 | if (!deletedCard) { //if query goes through and can't find, need specific error 78 | const err = { message: {err: 'Cannot delete card if we cannot find card!' }} 79 | return next(err); 80 | }; 81 | res.locals.deletedCard = deletedCard; 82 | console.log('deleted card', deletedCard); 83 | return next(); 84 | } catch (err) { 85 | err.log = 'Expressor error in carController deleteCard'; 86 | err.status = 500; 87 | err.message = { err: 'Expressor error in carController deleteCard' }; 88 | return next(err); 89 | } 90 | } 91 | 92 | }; 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | module.exports = cardController; //different way of exporting --------------------------------------------------------------------------------