├── .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 |
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 |
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
--------------------------------------------------------------------------------