DEV Community

caseyconlan
caseyconlan

Posted on

Pivoting to Plantables: A Coding Journey

During the fourth phase of our coding bootcamp, my group and I embarked on a journey of creation. Our project, originally conceptualized as an interactive game called Plantables, transformed into something much different but just as meaningful – an online storefront for a plant shop. This pivot and the work that followed became a potent lesson in managing expectations and sizing project scopes in alignment with deadlines.

Understanding the Project Management Triangle
The key learning point from this experience can be summarized through the lens of the Project Management Triangle. For those unfamiliar, this concept, also known as the Iron Triangle, includes three key dimensions: scope, cost, and time. The scope entails what needs to be accomplished, the cost refers to the resources required, and the time indicates the deadline or timeframe for completion.

The triangle is "iron" because changing one dimension often impacts the others. For instance, if we increase the scope, we might need more resources (increasing cost) or more time to accomplish the goal. As budding software engineers, it's crucial for us to be well-versed with this concept, as it impacts our ability to deliver successful projects within realistic constraints.

Image description

In our case, our original idea for Plantables was too ambitious – a larger scope than we could manage within our given timeframe. Realizing this, we had to pivot our idea and turn it into a plant shop storefront, which was more manageable but still allowed us to apply the skills we learned throughout the bootcamp.

Working with CRUD Operations
As we built the Plantables storefront, we faced a significant hurdle when implementing CRUD (Create, Read, Update, and Delete) capabilities for our app. Specifically, we wanted to apply CRUD to account creation, allowing users to create an account, review account details, update passwords, and delete accounts.

We initially attempted to embed our CRUD operations within the main component, Main.js. However, it didn't work as intended. After much thought and troubleshooting, we decided to move our CRUD operations to the Login component, Login.js, which turned out to be an effective resolution to our problem.

Here's a look at the code we placed on the Login.js component:

const handleNewPlayer = (e) => {
    e.preventDefault();
    if (password !== passwordConfirmation) {
      console.log("Password and confirmation do not match");
      return;
    }
    console.log(`New Player ${firstName} ${lastName} Created! Username: ${username}`);

    const requestData = {
      first_name: firstName,
      last_name: lastName,
      email,
      username,
      password,
    };

    const headers = {
      "X-CSRF-Token": csrfToken,
      "Content-Type": "application/json",
    };

    if (csrfToken) {
      headers["X-CSRF-Token"] = csrfToken;
    }

    axios
      .post("/owners", requestData, {
        withCredentials: true,
        headers: headers,
      })
      .then((response) => {
        // Handle the new player creation response
        setLoggedIn(true); // Assuming that creating a new player logs them in
      })
      .catch((error) => {
        // Handle the new player creation error
      });
  };

  const renderLoginForm = () => (
    <form onSubmit={handleLogin}>
      <label>
        Username:
        <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
      </label>
      <label>
        Password:
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      </label>
      <button type="submit">Log In</button>
      <button className="button-1" role="button" onClick={handleForgotPassword}>Forgot Password</button>
      <button className="button-1" role="button" onClick={handleDeleteAccount}>Delete Account</button>
  </form>
  );

  const handleForgotPassword = (e) => {
    e.preventDefault();

    axios
        .post("/forgot-password", { username }, {
            headers: {
                "X-CSRF-Token": csrfToken,
                "Content-Type": "application/json",
            },
        })
        .then((response) => {
            // Prompt the user to enter a new password
            const new_password = window.prompt('Enter a new password:');

            // Update the password on the server
            axios
                .patch("/update-password", { username, new_password }, {
                    headers: {
                        "X-CSRF-Token": csrfToken,
                        "Content-Type": "application/json",
                    },
                })
                .then((response) => {
                    console.log(response.data);
                })
                .catch((error) => {
                    console.log(error);
                });
        })
        .catch((error) => {
            console.log(error);
        });
};

const handleDeleteAccount = (e) => {
  e.preventDefault();

  const requestData = {
    username,
    password,
  };

  const headers = {
    "X-CSRF-Token": csrfToken,
    "Content-Type": "application/json",
  };

  axios
    .post("/delete-account", requestData, {
      headers,
      withCredentials: true,
    })
    .then((response) => {
      if (response.data.message === 'Account deleted successfully') {
        setLoggedIn(false);
      } else {
        console.log('Invalid username or password');
      }
    })
    .catch((error) => {
      console.log('Delete account error:', error);
    });
};

  const renderNewPlayerForm = () => (
    <form onSubmit={handleNewPlayer}>
      <label>
        First Name:
        <input
          type="text"
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
        />
      </label>
      <label>
        Last Name:
        <input
          type="text"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </label>
      <label>
        Username:
        <input
          type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
      </label>
      <label>
        Password:
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
      </label>
      <label>
        Confirm Password:
        <input
          type="password"
          value={passwordConfirmation}
          onChange={(e) => setPasswordConfirmation(e.target.value)}
        />
      </label>
      <button type="submit">Create New Player</button>
    </form>
  );

  return (
    <>
      <div className="title">Welcome to Plantables!</div>
      <div className="description">Grow your plants with love</div>
      <div>
        <button className="button-1" role="button" onClick={() => handleFormType("login")}>Returning Customer</button>
        <button className="button-1" role="button" onClick={() => handleFormType("newPlayer")}>New Customer</button>
        {formType === "login" && renderLoginForm()}
        {formType === "newPlayer" && renderNewPlayerForm()}
      </div>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

In this component, we handled the creation of new players, rendering of the login form, the 'forgot password' functionality, account deletion, and the form for new players. All the functions are well encapsulated, easy to follow, and now placed in an appropriate component.

Through this exercise, we learned the importance of appropriate component architecture, and how that can significantly impact the functioning and usability of our application.

Wrapping Up
Our journey with the Plantables project was enlightening and challenging, but it was well worth it. We grappled with project management principles, wrestled with the intricacies of component placement and CRUD operations, and learned to adapt our ambitions to meet the constraints of time.

Ultimately, this project was a testament to the iterative nature of software development. It emphasized that the initial idea, though important, is not set in stone. Instead, it can, and often should, evolve based on the constraints and opportunities that become apparent as we delve into the process of bringing it to life.

The real achievement lies not in clinging to the original idea but in creating something functional, learning new skills, and growing as a developer. And that, indeed, is a valuable lesson for all of us in this coding bootcamp.

We encountered and overcame challenges in implementing CRUD functionalities to our app, but through those hurdles, we expanded our understanding of proper component placement and the efficient application of CRUD operations. This experience not only bolstered our technical skills but also reinforced the importance of resilience and adaptability in the face of challenges.

This iterative process of planning, coding, testing, and refining is the heart of software engineering. It's what turns a good idea into a great application, and it's what turns a coding bootcamp student into a future-ready software engineer.

To wrap up, the Plantables project was a challenging but rewarding ride. It served as a reminder of the importance of realistic project scope and time management, and it solidified our understanding of core principles in software development. It was a truly enriching experience that we'll carry forward in our journey as budding software engineers.

Top comments (0)