100 Days of Coding Day 4

100 Days of Coding Day 4

Hello, my name is Rick and I am in search for gig as a developer :). I am familiar with the MERN stack (MongoDB, Express, React, and NodeJs), HTML, and CSS. I have been deeply immersed in these technologies since January 2021. These blogs are documenting my daily activities as I navigate through each day coding and learning.

I've decided to revisit a project I started in December, the ticket-tracker. It's a ticket tracking app similar to Jira. I am building this using the MERN stack, along with material UI and Redux for state management. Here are links to my frontend and backend repositories. A live version can be found here.

Screen Shot 2022-02-08 at 1.11.01 PM.jpg

My last big accomplishment was adding the feature of ticket attachments. The user can choose any file type as long as it's not too large, name it, upload it and it's stored in an Amazon S3 bucket with the information available for download afterwards. This took me a few days to figure out but well worth it.

Adding Comments to Tickets

One of the style issues I was having is this- by default, a scroll bar has 90 degree angles that would hang over a div with rounded edges that was hiding content on scroll.

Screen Shot 2022-02-08 at 1.25.06 PM.jpg

So after some "googling" , in my opinion something every good developer does, I found a solution that works great.

::-webkit-scrollbar {
  width: 12px;
}

/* Track */
::-webkit-scrollbar-track {
  -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 
  -webkit-border-radius: 10px;
  border-radius: 10px;
}

/* Handle */
::-webkit-scrollbar-thumb {
  -webkit-border-radius: 10px;
  border-radius: 10px;
  background:  rgba(0, 72, 255, 0.4); 
  -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(0, 72, 255, 0.4); 
}

Which produced this final result:

Screen Shot 2022-02-08 at 1.17.42 PM.jpg

You know, these small details do matter in my opinion.

Now that I have the layout looking good and the routes in the backend coded out, lets put it all together. Below are the two functions in my 'ticketCommentController' folder of my express backend. Of course these are protected routes, so I am using JWT middleware.

const createComment = async (req, res, next) => {
  const ticket_id = req.params.id;
  const comment = req.body.comment;


  try {
    const commenter = {
      firstName: res.locals.decodedJwt.firstName,
      lastName: res.locals.decodedJwt.lastName,
    };
    const ticket = await Ticket.findOne({
      _id: ticket_id,
    });
    const newComment = new Comment({ ticket, commenter, comment });

    const savedNewComment = await newComment.save();

    ticket.comments.push(savedNewComment._id);

    await ticket.save();

    res.json({
      message: 'comment saved',
    });
  } catch (e) {
    next(e);
  }
};
const getAllTicketComments = async (req, res, next) => {
  const ticket_id = req.params.id;
  try {
    let payload = await Ticket.findOne({ _id: ticket_id })
      .populate({
        path: 'comments',
        model: TicketComments,
        select: '-__v',
      })
      .select(
        '-projectId -description -priorityLevel -ticketType -ticketStatus -attachments -developer -createdAt -updatedAt -__v -_id -title '
      );
    console.log(payload);
    res.json(payload);
  } catch (e) {
    next(e);
  }
};

In my frontend I am not only using React, but I am using redux for state management. Along with redux comes different 'actions', 'reducers', and 'types'. In my ticketActions.js folder I used the following two functions to call to my backend API:

export const getAllCommentsByTicket = (ticketId) => async (dispatch) => {
  try {
    let res = await Axios.get(
      `/comment/get-all-comments-by-ticket/${ticketId}`
    );
    console.log(res.data.comments)
    dispatch({
      type: SET_ALL_COMMENTS_BY_TICKET,
      payload: res.data.comments,
    });
  } catch (err) {
    dispatch({
      type: SET_ALERT,
      payload: {
        isOpen: true,
        alertMessage: err.response,
        typeOfMessage: 'error',
      },
    });
  }
};
export const submitNewTicketComment =
  (comment, ticket_id, onSuccess) => async (dispatch) => {
    if (comment === '') {
      return dispatch({
        type: SET_ALERT,
        payload: {
          isOpen: true,
          alertMessage: 'Please provide a comment.',
          typeOfMessage: 'error',
        },
      });
    }

    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
    };

    const body = JSON.stringify({'comment':comment});

    try {
      const res = await Axios.post(
        `comment/create-comment-by-ticket/${ticket_id}`,
        body,
        config
      );
      dispatch({
        type: SET_ALERT,
        payload: {
          isOpen: true,
          alertMessage: res.data.message,
          typeOfMessage: 'success',
        },
      });
     onSuccess()
    } catch (err) {
      dispatch({
        type: SET_ALERT,
        payload: {
          isOpen: true,
          alertMessage: err.response.data.message,
          typeOfMessage: 'error',
        },
      });
    }
  };

In the "ticketDetails" page, there's quite a bit going on. UseEffect replaces our component lifecycle methods and I am able to make the API call to retrieve all ticket comments when opening up the page in the browser(also known as componentDidMount) But we aren't using that with functional components in react. When a comment is typed, the string is captured with the onChange method, and then sent to the ticketActions file with the ticket id(params) and a callback function (onSuccess) to do another 'getAllComments') API call. There's a lot going on here, but somehow I'm able to make sense of all this madness again.

import * as React from 'react';
import Layout from '../layout/Layout';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import Button from '@mui/material/Button';
import { containerClasses, Divider } from '@mui/material';
import HistoryIcon from '@mui/icons-material/History';
import UploadFilesToS3 from '../layout/UploadFilesToS3';
import { useParams } from 'react-router';
import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import { getTicketByTicketId, getAllCommentsByTicket, submitNewTicketComment } from '../../store/actions/ticketActions';
import Spinner from '../ui/Spinner';
import moment from 'moment';

const TicketDetails = () => {
  const params = useParams();
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(getTicketByTicketId(params.ticketId));
    dispatch(getAllCommentsByTicket(params.ticketId));
  }, [dispatch, params.ticketId]);

  const { comments } = useSelector((state) => state.tickets);

  const [comment, setComment] = useState('')

  const handleOnClick = () =>{
    dispatch(submitNewTicketComment(comment, params.ticketId, onSuccess))
  }
  const handleOnChange = (e)=>{
    setComment(e.target.value)
  }
  const onSuccess = ()=>{
    dispatch(getAllCommentsByTicket(params.ticketId))
  }

  if (!comments) {
    return (
      <Layout>
        <Spinner />
      </Layout>
    );
  }

  return (
    <Layout>
      {/*Project and Ticket Name*/}
      <Grid item xs={12} textAlign={'center'}>
        <Typography component='span' variant='h4'>
          Ticket: A Bug Tracking App{' '}
        </Typography>
        <Typography component='span' variant='h5'>
          (User Authentication)
        </Typography>
      </Grid>

      {/*Tickets summary*/}

      <Grid
        item
        xs={12}
        md={8}
        lg={6}
        sx={{
          display: 'flex',
        }}
      >
        <Paper
          sx={{
            p: 2,
            display: 'flex',
            flexDirection: 'row',
            justifyContent: 'space-between',
            height: '100%',
          }}
        >
          <Grid
            item
            xs={8}
            sx={{
              p: 1,
              display: 'flex',
              flexDirection: 'column',
              justifyContent:'center',
              height: '100%',
            }}
          >
            <Typography variant='h5' gutterBottom>
              Ticket Details
            </Typography>

            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Description:{' '}
            </Typography>
            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Please make sure that the forms are laid out according to the wire
              frame provided. The user should provide a strong password of no
              less than eight characters in length.
            </Typography>
          </Grid>
          <Grid
            item
            xs={6}
            sx={{
              p: 3,
              display: 'flex',
              flexDirection: 'column',
              justifyContent: 'center',
              height: '100%',
            }}
          >
            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Assigned Developer: John Doe
            </Typography>
            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Created: December 15, 2021
            </Typography>

            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Ticket Type: New Feature
            </Typography>
            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Ticket Priority: Low
            </Typography>
            <Typography component='span' variant='h7' sx={{ my: 1 }}>
              Ticket Status: Development
            </Typography>
          </Grid>
        </Paper>
      </Grid>

      <Grid item xs={12} md={4} lg={6}>
        <Paper
          sx={{
            p: 2,
            display: 'flex',
            flexDirection: 'row',
            height: '100%',
          }}
        >
          <UploadFilesToS3 params={params} />
        </Paper>
      </Grid>

      {/* Comments */}
      <Grid item xs={12} md={12} lg={6}>
        <Paper
          sx={{
            height: '100%',
            p: 3,
          }}
        >
          <Grid item xs={12} display='flex' flexDirection='column'>
            <Typography component='span' variant='h5' gutterBottom>
              Ticket Comments
            </Typography>
            <Grid item xs={12}>
              <TextField
                id='outlined-multiline-flexible'
                label='Comment'
                name='description'
                multiline
                required
                maxRows={6}
                value={comment}
                onChange={handleOnChange}
                fullWidth
              />
            </Grid>
            <Button
              fullWidth
              variant='contained'
              sx={{ mt: 2 }}
              onClick={handleOnClick}
            >
              Comment
            </Button>
            <Divider sx={{ mt: 2 }} />
            <Typography
              textAlign={'center'}
              component='div'
              variant='h7'
              width={'30%'}
              margin={'auto'}
              sx={{
                mt: 2,
                padding: '5px',
                borderRadius: '10px',
                bgcolor: '#dedede',
              }}
            >
              {comments.length} Comment(s)
            </Typography>
            <Typography
              component='div'
              variant='h7'
              sx={{
                mt: 2,
                padding: '10px',
                height: '100px',
                borderRadius: '10px',
                border: '1px solid grey',
                overflowY: 'scroll',
                scrollbarColor: 'red',
              }}
            >
            {comments.map((comment)=>( 
              <>
              <Typography
                key={comment._id}
                sx={{
                  backgroundColor: 'lightblue',
                  padding: '10px',
                  borderRadius: '10px',
                  mt:1
                }}
              >
                <div>{comment.commenter.firstName} {comment.commenter.lastName} posted on {moment(comment.createdAt).format('MMMM Do, YYYY')}</div>
                <div>{comment.comment}</div>
              </Typography>
              <Divider sx={{ mt: 1 }} />
              </>
            ))}
            </Typography>
          </Grid>
        </Paper>
      </Grid>
      {/* Ticket History */}
      <Grid item xs={12} md={12} lg={6}>
        <Paper
          sx={{
            p: 3,
            display: 'flex',
            flexDirection: 'column',
            height: '100%',
          }}
        >
          <Typography component='span' variant='h5' gutterBottom>
            Ticket History
          </Typography>
          <Typography
            component='span'
            style={{
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <HistoryIcon />
            <Typography sx={{ m: 2 }}>
              New Ticket Created by John Doe on Dec 20, 2021
            </Typography>
          </Typography>
          <Typography
            component='span'
            style={{
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <HistoryIcon />
            <Typography sx={{ m: 2 }}>
              New Ticket Description by John Doe on Dec 21, 2021
            </Typography>
          </Typography>
          <Typography
            component='span'
            style={{
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <HistoryIcon />
            <Typography sx={{ m: 2 }}>
              New Ticket Created by John Doe on Dec 22, 2021
            </Typography>
          </Typography>
          <Typography
            component='span'
            style={{
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <HistoryIcon />
            <Typography sx={{ m: 2 }}>
              New Ticket Created by John Doe on Dec 20, 2021
            </Typography>
          </Typography>
        </Paper>
      </Grid>
    </Layout>
  );
};

export default TicketDetails;

Screen Shot 2022-02-08 at 10.08.58 PM.jpg

Final Thoughts

After taking some time off from looking at these folders and files, it was a slow start getting things moving again but I did it. Mission accomplished. The user can now make comments that are attached to tickets in the ticket-tracker. Just a few more things to do, clear the input field after submit (which is easy-peasy) and refactor some of the code above. Thanks for checking this stuff out :)