<file_sep>import ExpensesPage from "../frontend/pages/ExpensesPage";

function Index() {
  return <ExpensesPage />;
}

export default Index;
<file_sep>import React, { useState } from "react";
import { useSpring, animated } from "react-spring";
import styles from "./ExpenseItem.module.css";
import Button from "./Button";

function ExpenseItem(props) {
  console.log(props, " are props");
  const [modal, setModal] = useState(false);
  const months = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
  ];

  let date = null;
  date = props.date
    .slice(0, 10)
    .split("-")
    .map((element) => parseInt(element));

  const day = date[2];
  const month = months[date[1] - 1];

  const offset = props.offset ? props.offset : 100;
  const animation = useSpring({
    from: {
      opacity: 0
    },
    to: { opacity: 1 },
    delay: offset
  });

  const archiveExpense = (thisExpenseId) => {
    const newExpenses = props.allExpenses.map((expense) => {
      if (expense.purchase_id == thisExpenseId) {
        expense.archived = 1;
      }
      return expense;
    });
    props.setExpenses(newExpenses);
    fetch("/api/setArchive", {
      method: "put",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        id: thisExpenseId
      })
    });
  };

  const modalStyles = useSpring({
    from: {
      transform: "scale(0)"
    },
    to: { transform: `scale(${modal ? 1 : 0})` }
  });

  return (
    <animated.div
      style={animation}
      className={styles.item}
      onClick={() => {
        setModal(true);
      }}
    >
      <div className={styles.col}>
        <p className={styles.day}>
          {day}
          <span className={styles.month}> {month}</span>
        </p>
      </div>
      <div className={styles.col}>
        <p className={styles.description}>{props.description}</p>
      </div>
      <div className={styles.col}>
        <p className={styles.amount}>
          {props.amount}
          <span className={styles.euro}>€</span>
        </p>
      </div>
      <animated.div style={modalStyles} className={styles.modal}>
        <div className={styles.modal__body}>
          <div className={styles.modal__expense}>
            <p className={styles.modal__amount}>
              {props.amount}
              <span className={styles.modal__euro}>€</span>
            </p>
            <p className={styles.modal__meta}>
              {props.description}, {day}. {month}
            </p>
          </div>
          <div className={styles.modal__buttons}>
            <Button
              text={"Archive expense"}
              handleClick={(e) => {
                e.stopPropagation();
                archiveExpense(props.expense_id);
                setModal(false);
              }}
            />

            <Button
              text={"Close modal"}
              handleClick={(e) => {
                e.stopPropagation();
                setModal(false);
              }}
            />
          </div>
        </div>
      </animated.div>
    </animated.div>
  );
}

export default ExpenseItem;
<file_sep>const db = require("../../db");
const sql = require("sql-template-strings");

const express = require("express");

const app = express();
module.exports = app;

app.get("*", async (req, res) => {
  const user = await db.query(sql`
    SELECT * FROM owners
  `);
  res.set("Content-Type", "application/json");
  res.end(JSON.stringify(user, null, 4));
});
<file_sep>import React from "react";
import App, { Container } from "next/app";
import Page from "../frontend/layout/Page";
import styles from "../frontend/layout/Page.module.css";
import { UserContextProvider } from "../frontend/context/UserContext";

export default class MyApp extends App {
  constructor(props) {
    super(props);

    this.state = {
      name: null,
      id: null,
      otherUsers: [],
      selectedUsers: []
    };
  }

  render() {
    const { Component, pageProps } = this.props;
    return (
      <Container>
        <UserContextProvider
          value={{
            name: this.state.name,
            id: this.state.id,
            otherUsers: this.state.otherUsers,
            selectedUsers: this.state.selectedUsers,
            switchUser: (name, id) => {
              this.setState({
                name,
                id
              });
            },
            setOtherUsers: (otherUsers) => {
              this.setState({
                otherUsers
              });
            },
            setSelectedUsers: (selectedUser) => {
              const selectedUsers = this.state.selectedUsers;
              selectedUsers.push(selectedUser);

              this.setState({
                selectedUsers
              });
            }
          }}
        >
          <Page className={styles.main}>
            <Component {...pageProps} />
          </Page>
        </UserContextProvider>
      </Container>
    );
  }
}
<file_sep>import styles from "./index.module.css";
import Link from "next/link";
import SignInPage from "../frontend/pages/SignInPage";

import Form from "../frontend/components/Form";

function Index() {
  return <SignInPage />;
}

export default Index;
<file_sep>import React from "react";
import styles from "./Page.module.css";

function Page({ children }) {
  return (
    <React.Fragment>
      <main className={styles.main}>{children}</main>
    </React.Fragment>
  );
}

export default Page;
<file_sep>import React, { useState } from "react";
import styles from "./MainModal.module.css";
import { useSpring, animated, interpolate } from "react-spring";

function MainModal(props) {
  const animationProps = useSpring({
    opacity: 1,

    transform: `translate3d(${props.isOpen ? 0 : -360}px, 0 ,0)`,
    from: {
      opacity: props.isOpen ? 1 : 0,
      transform: `translate3d(-360px, 0, 0)`
    }
  });

  return (
    <animated.div style={animationProps} className={styles.modal}>
      <div className={styles.modal__body}>
        {props.children}
        <button className={styles.main__button} onClick={props.close}>
          Close modal
        </button>
      </div>
    </animated.div>
  );
}

export default MainModal;
<file_sep>const db = require("../../db");
const sql = require("sql-template-strings");

const express = require("express");

const app = express();
module.exports = app;

app.get("*", async (req, res) => {
  let id = req.query.id;
  const expenses = await db.query(sql`
    SELECT * FROM purchases 
    LEFT JOIN expenses ON purchases.id = expenses.purchase_id
    WHERE owner_id = ${id}
    `);

  res.set("Content-Type", "application/json");
  res.end(JSON.stringify(expenses, null, 4));
});
<file_sep>import React, { useState, useEffect } from "react";
import PartialExpense from "./PartialExpense";
import styles from "./FormSplitExpense.module.css";
import UserChip from "./UserChip";

function FormSplitExpense({
  amount,
  userdata,
  setUserdata,
  otherUsers,
  selectedUsers
}) {
  let usersThatCanBeAdded = otherUsers.map((user, index) => (
    <UserChip
      key={index}
      name={user.name}
      handleClick={() => {
        const newSelectedUsers = selectedUsers;
        // Add this user selectedUsers
        newSelectedUsers.push(user);
        debugger;
        // Split the amount between the selected users
        let numberOfUsers = newSelectedUsers.length;
        newSelectedUsers.map((user) => {
          user.amount = parseInt(amount) / numberOfUsers;
        });
        // Update state
        setUserdata({ ...userdata, selectedUsers: newSelectedUsers });
      }}
    />
  ));
  let usersAddedToTheExpense = [];
  if (selectedUsers) {
    if (selectedUsers.length == 2) {
      usersAddedToTheExpense = selectedUsers.map(
        (userAddedToExpense, index) => {
          return (
            <PartialExpense
              key={index}
              currentUser={userAddedToExpense}
              userdata={userdata}
              setUserdata={setUserdata}
              fullAmount={amount}
            />
          );
        }
      );
    }
  }

  return (
    <div>
      <div className={styles.selected_users}>{usersAddedToTheExpense}</div>
      <div className={styles.split_expense}>
        {usersAddedToTheExpense.length !== 2 && usersThatCanBeAdded}
      </div>
    </div>
  );
}

export default FormSplitExpense;
<file_sep>const db = require("../../db");
const sql = require("sql-template-strings");
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
module.exports = app;

app.use(bodyParser.json());

app.put("*", async (req, res) => {
  if (req.body == null) {
    return res.status(400).send({ error: "no JSON object in the request" });
  }
  const id = req.body.id;
  console.log("id is", id);
  const query = await db.query(sql`
    UPDATE purchases SET archived = 1 WHERE id = ${id}
    `);
  console.log("query is", query);

  res.set("Content-Type", "application/json");
  res.status(200).send(JSON.stringify(query, null, 4));
});

app.all("*", (req, res) => {
  res.status(405).send({ error: "only PUT requests are accepted" });
});
<file_sep>import React, { useState } from "react";
import styles from "./Button.module.css";



function Button(props) {
  return (
    <button className={styles.button} onClick={props.handleClick}>
      {props.text}
    </button>
  );
}

export default Button;
<file_sep>import React, { useState } from "react";
import styles from "./PartialExpense.module.css";
import Input from "./Input";
import { FaPlus, FaMinus } from "react-icons/fa";

function PartialExpense(props) {
  console.log(props.userdata);
  return (
    <div className={styles.main}>
      <p>{props.currentUser.name} paid</p>
      <div className={styles.input_group}>
        <button
          className={styles.button}
          onClick={() => {
            const newSelectedUsers = props.userdata.selectedUsers;

            props.userdata.selectedUsers.map((user) => {
              if (user == props.currentUser) {
                user.amount -= 1;
              } else {
                user.amount += 1;
              }
              props.setUserdata({
                ...props.userdata,
                selectedUsers: newSelectedUsers
              });
              return user;
            });
          }}
        >
          <FaMinus />
        </button>
        <input
          className={styles.input}
          name={"Amount"}
          type={"number"}
          onChange={(e) => {
            const localSelectedUsers = props.userdata.selectedUsers.map(
              (localSelectedUser) => {
                const inputAmount = parseInt(e.target.value);
                if (localSelectedUser === props.currentUser) {
                  if (inputAmount <= props.fullAmount && inputAmount >= 0)
                    localSelectedUser.amount = inputAmount;
                } else {
                  if (inputAmount <= props.fullAmount && inputAmount >= 0)
                    localSelectedUser.amount = props.fullAmount - inputAmount;
                }
                return localSelectedUser;
              }
            );
            props.setUserdata({
              ...props.userdata,
              selectedUsers: localSelectedUsers
            });
          }}
          value={props.currentUser.amount}
        />

        <button
          className={styles.button}
          onClick={() => {
            const newSelectedUsers = props.userdata.selectedUsers;
            props.userdata.selectedUsers.map((user) => {
              if (user == props.currentUser) {
                user.amount += 1;
              } else {
                user.amount -= 1;
              }
              props.setUserdata({
                ...props.userdata,
                selectedUsers: newSelectedUsers
              });
              return user;
            });
          }}
        >
          <FaPlus />
        </button>
      </div>
    </div>
  );
}

export default PartialExpense;
<file_sep>// _document is only rendered on the server side and not on the client side
// Event handlers like onClick can't be added to this file

// ./pages/_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);

    return { ...initialProps };
  }

  render() {
    return (
      <Html>
        <Head>
          <meta
            name="viewport"
            content="initial-scale=1.0, width=device-width"
          />
          <link
            href="https://fonts.googleapis.com/css?family=Montserrat:400,700|Raleway:400,700"
            rel="stylesheet"
          />
          <style>{`
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: Raleway, sans-serif;
       color: #424242;
    }
    `}</style>
        </Head>
        <body className="custom_class">
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;
<file_sep>const db = require("../../db");
const sql = require("sql-template-strings");
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
module.exports = app;

app.use(bodyParser.json());

app.post("*", async (req, res) => {
  if (req.body == null) {
    return res.status(400).send({ error: "no JSON object in the request" });
  }

  const insertPurchaseResults = await db.query(sql`
  INSERT INTO purchases (description, archived, date) VALUES (${
    req.body.description
  }, ${req.body.archived}, ${req.body.date});
  `);
  let insertExpense = null;
  if (!req.body.owners) {
    insertExpense = await db.query(sql`
  INSERT INTO expenses  ( amount, owner_id, purchase_id, full_amount) VALUES ( ${
    req.body.amount
  }, ${req.body.owner}, ${insertPurchaseResults.insertId}, ${req.body.amount})
`);
  } else {
    let fullAmount = 0;
    for (const user of req.body.owners) {
      insertExpense = await db.query(sql`
      INSERT INTO expenses  ( amount, owner_id, purchase_id, full_amount) VALUES ( ${
        user.amount
      }, ${user.id}, ${insertPurchaseResults.insertId}, ${req.body.amount})
      `);
      fullAmount += user.amount;
    }
  }

  res.set("Content-Type", "application/json");
  res.status(200).send(JSON.stringify(insertExpense, null, 4));
});

app.all("*", (req, res) => {
  res.status(405).send({ error: "only POST requests are accepted" });
});

// loon uue kulutuse

// INSERT INTO purchases (description, archived) VALUES ('test', false);

// Võtan viimase kulutuse id kasutades counti

// SELECT COUNT(*) from purchases;

// Leian kasutaja
// SELECT id from owners WHERE name = 'Mart'
// Teen uue expense-i kusjuures väärtused owner_id ja purchase_id tulevad varasematest päringutest

// INSERT INTO expenses  ( amount, owner_id, purchase_id) VALUES ( ... , ... , ...)
<file_sep># Next Expenses

This project is created to learn more about Next.js and deploying with Now, not to mention
It was aliased to [https://nextjs.martlepanen.now.sh]. It is now archived. See the repo 'Expenses' for the current "branch" of this project.



https://nextjs.martlepanen.now.sh

## Features

Select your (pre-created) user and log your expenses. You are able to set
the amount, date, and description of your expense. You can also split an
expense in two (between another already created user). Created expenses can
be archived to prevent them from being displayed.

## To Do

- Allow user creation.
- Allow more than two way expense splitting.
- Show archived expenses.
<file_sep>import React, { useState } from "react";
import styles from "./Form.module.css";
import Button from "./Button";
import Input from "./Input";
import FormSplitExpense from "./FormSplitExpense";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker-cssmodules.css";

import { useContext } from "react";
import { UserContext } from "../context/UserContext";

function Form() {
  const [form, setForm] = useState({
    description: "",
    amount: 0,
    date: new Date()
  });
  let { selectedUsers, otherUsers } = useContext(UserContext);
  let currentUserId = useContext(UserContext).id;
  let currentUserName = useContext(UserContext).name;
  const [userdata, setUserdata] = useState({
    selectedUsers: [
      {
        id: currentUserId,
        name: currentUserName
      }
    ],
    otherUsers
  });
  const [splitExpense, setSplitExpense] = useState(false);

  const handleChange = (event) => {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    const name = target.name;
    if (target.name === "description") {
      setForm({ ...form, description: value });
    }
    if (target.name === "amount") {
      setForm({ ...form, amount: parseInt(value) });
    }
  };

  const handleDateChange = (date) => {
    setForm({ ...form, date: date });
  };

  const handleSubmit = () => {
    let expense = {};

    if (userdata.selectedUsers.length > 1) {
      expense = {
        description: form.description,
        amount: form.amount,
        owners: userdata.selectedUsers,
        date: form.date
      };
    } else {
      expense = {
        description: form.description,
        amount: form.amount,
        owner: currentUserName,
        date: form.date
      };
    }

    addExpense(expense);
    setSplitExpense(false);
    const initialSelectedUsers = [
      {
        id: currentUserId,
        name: currentUserName
      }
    ];
    setUserdata({ ...userdata, selectedUsers: initialSelectedUsers });
    setForm({ ...form, description: "", amount: 0 });
  };

  const addExpense = (expense) => {
    const description = expense.description;
    const date = expense.date;
    const amount = expense.amount;
    const owners = expense.owners ? expense.owners : null;
    fetch("/api/postToDb", {
      method: "post",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        owner: currentUserId,
        owners: owners,
        description: description,
        amount: amount,
        date: date,
        archived: false
      })
    });
  };

  const discardExpense = () => {
    const newDate = new Date();
    setForm({ ...form, description: "", amount: 0, date: newD });
  };

  const handleSplitExpense = () => {
    setSplitExpense(true);
  };

  return (
    <div>
      <form className={styles.form}>
        <div className={styles.inputGroup}>
          <Input
            name="amount"
            type="number"
            handleChange={handleChange}
            value={form.amount}
          />
        </div>
        <div className={styles.inputGroup}>
          <Input
            name="description"
            type="text"
            handleChange={handleChange}
            value={form.description}
          />
        </div>

        <div>
          <label className={styles.row}>
            Date:
            <DatePicker selected={form.date} onChange={handleDateChange} />
          </label>
        </div>
      </form>

      <div className={styles.formButtons}>
        {!splitExpense && (
          <Button text={"Split expense"} handleClick={handleSplitExpense} />
        )}
        {splitExpense && (
          <FormSplitExpense
            amount={form.amount}
            userdata={userdata}
            setUserdata={setUserdata}
            selectedUsers={userdata.selectedUsers}
            otherUsers={userdata.otherUsers}
          />
        )}
        <Button text={"Add expense"} handleClick={handleSubmit} />
        <Button text={"Discard expense"} handleClick={discardExpense} />
      </div>
    </div>
  );
}

export default Form;
<file_sep>import styles from "./SignInPage.module.css";
import Button from "../components/Button";
import { useContext } from "react";
import { UserContext } from "../context/UserContext";
import { useState, useEffect } from "react";
import Link from "next/link";

function useEffectUsers() {
  const [users, setUsers] = useState({});
  useEffect(function() {
    // This is from <NAME>,
    // https://youtu.be/1C_XpWdbimM?t=1351
    // useEffect takes a function, which in turn contains an IIFE

    (async () => {

      const result = await fetch("http:///api.martpart.ee/getUsers", {
        method: "get"
      });
      console.log({result})
      let usersFromDB = await result.json();

      setUsers(usersFromDB);
    })();
  }, []);

  if (users.length == undefined) {
    return ["Loading"];
  } else {
    if (process.browser) {
      localStorage.setItem("users", JSON.stringify(users));
    }
    return users;
  }
}

export default function SignInPage() {
  const { name, switchUser, setOtherUsers } = useContext(UserContext);
  let users = null;
  let localUsers = null;
  // Local Storage
  if (process.browser) {
    localUsers = localStorage.getItem("users");
  }
  if (localUsers) {
    users = JSON.parse(localUsers);
  } else {
    users = useEffectUsers();
  }

  if (name) {
    return (
      <React.Fragment>
        <div className={styles.main}>
          <h1>Hello, {name}</h1>

          <div>
            <Link href="/expenses">
              <a>Go to Expenses</a>
            </Link>
          </div>
        </div>
      </React.Fragment>
    );
  } else {
    return (
      <React.Fragment>
        <div className={styles.main}>
          <h1>Please choose a user</h1>
          <div className={styles.users}>
            {users.length > 1 &&
              users.map((user, index) => (
                <Button
                  key={index}
                  handleClick={() => {
                    const otherUsers = users.filter(
                      (otherUser) => user !== otherUser
                    );
                    switchUser(user.name, user.id);
                    setOtherUsers(otherUsers);
                  }}
                  text={user.name}
                />
              ))}
          </div>
        </div>
      </React.Fragment>
    );
  }
}
