Skip to content

Commit

Permalink
Merge branch 'master' into SCRUM-54
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/pages/Graphs/Graphs.tsx
#	src/pages/dashboard/DashboardPage.tsx
  • Loading branch information
joesumargo committed Apr 26, 2024
2 parents f58c7fd + d0258ed commit 2179a62
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 65 deletions.
32 changes: 15 additions & 17 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import useWindowDimensions from "../hooks/WindowDimensionsHook.tsx";
import "../assets/css/Header.css"
import {Link, useNavigate, useLocation} from "react-router-dom";
import {Link, useNavigate} from "react-router-dom";
import {auth} from "../utils/firebase.ts";
import {useState} from "react";
import {User} from "firebase/auth";

interface Props {
user?: string;
children?: React.ReactNode;
}

export function Header({ user }: Props) {
export function Header() {
const { width, height } = useWindowDimensions();
const aspect_ratio = (width == 0? 1 : width) / (height == 0? 1 : height);
const use_narrow = aspect_ratio < 0.7;

const currentUser = auth.currentUser?.displayName?? "";
const [userName, setUserName] = useState<string | null>(null);
auth.onAuthStateChanged((new_user: User | null) => {
if (new_user === null) { setUserName(null); }
else { setUserName(new_user?.displayName) }
});

const navigate = useNavigate();
const location = useLocation();

const handleLogout = () => {
auth.signOut();
const handleLogout = async () => {
await auth.signOut();
navigate("/", { replace: true });
};

const isIndexPage = location.pathname === "/";

return (
<header className="header">
{/* App Name reloads home page */}
Expand All @@ -35,16 +33,16 @@ export function Header({ user }: Props) {
{/* Pages */}
<ul className="header-nav">
{/* Links to dashboard with tiles */}
<li><Link to="/dash" className={`header-item ${isIndexPage ? "text-muted bg-transparent" : "header-item"}`}>Dashboard</Link></li>
<li><Link to="/dash" className={`header-item ${userName ? "header-item" : "text-muted bg-transparent"}`}>Dashboard</Link></li>

{/* Links to transactions page with table of expenses */}
<li><Link to="/transactions" className={`header-item ${isIndexPage ? "text-muted bg-transparent" : "header-item"}`}>Transactions</Link></li>
<li><Link to="/transactions" className={`header-item ${userName ? "header-item" : "text-muted bg-transparent"}`}>Transactions</Link></li>

<li><Link to="/test" className="header-item">Firestore Test</Link></li>

{user && (
{userName && (
<li>
<span className="username">{currentUser}</span>
<span className="username">{userName}</span>
<button type="button" className="logout-btn" onClick={handleLogout}>Logout</button>
</li>
)}
Expand Down
31 changes: 16 additions & 15 deletions src/components/transactions/CSVUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
import { useState } from "react";
import { Transaction } from "./Transaction";
import { Alert, Button, Form, Modal } from "react-bootstrap";
import { writeNewTransactionsBatched } from "../../utils/transaction.ts";
import { auth } from "../../utils/firebase";

export function CSVUpload({ show, setShow }: { show: boolean, setShow: React.Dispatch<React.SetStateAction<boolean>> }) {
const [error, setError] = useState<string | null>();
const [successMsg, setSuccessMsg] = useState<string | null>();

const reader = new FileReader();

function handleUpload() {
async function handleUpload() {
setError(null);
setSuccessMsg(null);

const fileElement = document.getElementById("file") as HTMLInputElement | null;
if (!fileElement || fileElement.files?.length === 0) return setError("You haven't uploaded a CSV file");

const file = fileElement.files![0];
if (!file) return setError("You haven't uploaded a CSV file");

reader.onload = (event) => {
reader.onload = async (event) => {
if (!auth.currentUser) return setError("You are not signed in");

const csvContent = event.target?.result;
if (!csvContent || csvContent instanceof ArrayBuffer) return setError("Unable to read uploaded CSV file");

const rows = csvContent
const transactionDocuments = csvContent
.split("\n")
.slice(1)
.map((row) => row.split(","));

const transactions = rows
.map((row) => row.split(","))
.filter((row) => row[3] === "Card payment" || row[3] === "Faster payment") // filter by type
.map((row) => new Transaction().fromRow(row));
.map((row) => new Transaction().fromRow(row))
.filter((transaction) => transaction.isValid)
.map((transaction) => transaction.toDocument(auth.currentUser!.uid));

const validTransactions = transactions.filter((transaction) => transaction.isValid);
if (validTransactions.length === 0) return setError("The uploaded CSV file has no valid transactions");

// -------------------------------------------------------------------------------------
// TODO: STORE "validTransactions" IN THE DATABASE
// -------------------------------------------------------------------------------------
if (transactionDocuments.length === 0) return setError("The uploaded CSV file has no valid transactions");

await writeNewTransactionsBatched(auth.currentUser, transactionDocuments);

setSuccessMsg(`${validTransactions.length} valid transactions have been imported out of ${transactions.length} total transactions`);
setSuccessMsg(`${transactionDocuments.length} transactions have been imported`);
setTimeout(() => setSuccessMsg(null), 10000);

fileElement.value = "";
Expand Down
10 changes: 6 additions & 4 deletions src/components/transactions/InputTransaction.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState } from "react";
import { Transaction, emojis, formatDate, formatTime } from "./Transaction";
import { Button, Modal, Form, Alert } from "react-bootstrap";
import { auth } from "../../utils/firebase";
import { writeNewTransaction } from "../../utils/transaction.ts";

export function InputTransaction({ show, setShow}: { show: boolean, setShow: React.Dispatch<React.SetStateAction<boolean>> }) {
const [name, setName] = useState<string>("");
Expand All @@ -15,10 +17,12 @@ export function InputTransaction({ show, setShow}: { show: boolean, setShow: Rea
const [error, setError] = useState<string | null>("");
const [successMsg, setSuccessMsg] = useState<string | null>("");

function addTransaction() {
async function addTransaction() {
setError(null);
setSuccessMsg(null);

if (!auth.currentUser) return setError("You are not signed in");

const date = new Date();

const transaction = new Transaction()
Expand All @@ -39,9 +43,7 @@ export function InputTransaction({ show, setShow}: { show: boolean, setShow: Rea
return;
}

// -------------------------------------------------------------------------------------
// TODO: STORE "transaction" IN THE DATABASE
// -------------------------------------------------------------------------------------
await writeNewTransaction(auth.currentUser, transaction.toDocument(auth.currentUser.uid));

setSuccessMsg("Transaction has been successfully added");
setTimeout(() => setSuccessMsg(null), 10000);
Expand Down
17 changes: 17 additions & 0 deletions src/components/transactions/Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Transaction as TransactionDocument } from "../../utils/transaction.ts";

export const emojis: { [index: string]: string } = {
"Income": "💸",
"Transfers": "🏦",
Expand Down Expand Up @@ -148,6 +150,21 @@ export class Transaction {
return this;
}

toDocument(uid: string): TransactionDocument {
return new TransactionDocument(
this.address as string,
this.amount as number,
this.category as string,
this.currency as string,
new Date((this.date as string) + (this.time as string)).getTime(),
this.description as string,
this.emoji as string,
this.name as string,
this.notes as string,
uid
);
}

isNotEmpty(val?: string): val is string {
return !!val && val.trim() !== "";
}
Expand Down
66 changes: 39 additions & 27 deletions src/pages/index/IndexPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@ import {Header} from "../../components/Header.tsx";
import {Footer} from "../../components/Footer.tsx";

import "./IndexPage.scss"
import {Link, useNavigate} from "react-router-dom";
import {useNavigate} from "react-router-dom";
import { signInWithGoogle } from '../../utils/authentication';
import { useState } from "react";
import {useState} from "react";
import {auth} from "../../utils/firebase.ts";
import {User} from "firebase/auth";

interface IndexPageProps {
user?: string;
}

function IndexPage({ user }: IndexPageProps) {
const [currentUser, setCurrentUser] = useState<string | undefined>(user);
function IndexPage() {
const navigate = useNavigate();

const handleSignIn = async () => {
const user = await signInWithGoogle();
setCurrentUser(user);
navigate('/dash', { replace: true }); // Redirect to /dash after signing in
await signInWithGoogle();
if (auth.currentUser !== null) {
navigate('/dash', { replace: true }); // Redirect to /dash after signing in
}
};

const [userName, setUserName] = useState<string | null>(null);
auth.onAuthStateChanged((new_user: User | null) => {
if (new_user === null) { setUserName(null); }
else { setUserName(new_user?.displayName) }
});

return (<>
<div className="d-flex vh-100 flex-column">
{currentUser? (
<Header user={currentUser} />
) : (
<Header user="" /> // or some other default value
)}
<Header/>
<div className="row vw-100" style={{flexGrow: 1, maxWidth: "99vw"}}>
<div className="col-6 p-0 d-flex justify-content-center align-items-center">
<div className="text-center">
Expand All @@ -45,21 +46,32 @@ function IndexPage({ user }: IndexPageProps) {
}}></div>
<div className="col-6 p-0 d-flex justify-content-center align-items-center">
<div className="text-center">
<h1 className="pb-2" style={{fontSize: "60px"}}>Login</h1>
<div className="row">
<div className="col p-2">
<button type="button" className="login-with-google-btn" onClick={handleSignIn}>
Sign in with Google
</button>
{userName ? <>
<h1 className="pb-2" style={{fontSize: "60px"}}>Hello, {userName}</h1>
<div className="row">
<div className="col p-2">
<button type="button" className="login-with-google-btn" onClick={async () => { await auth.signOut() }}>
Logout
</button>
</div>
</div>
<div className="col p-2">
<Link to="/login-page">
<button type="button" className="login-with-ms-btn text-nowrap">Sign in with
Microsoft
</> : <>
<h1 className="pb-2" style={{fontSize: "60px"}}>Login</h1>
<div className="row">
<div className="col p-2">
<button type="button" className="login-with-google-btn" onClick={handleSignIn}>
Sign in with Google
</button>
</Link>
</div>
{/*<div className="col p-2">*/}
{/* <Link to="/login-page">*/}
{/* <button type="button" className="login-with-ms-btn text-nowrap">Sign in with*/}
{/* Microsoft*/}
{/* </button>*/}
{/* </Link>*/}
{/*</div>*/}
</div>
</div>
</> }
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const router = createBrowserRouter([

{
path: "/user-test",
element: <IndexPage user={"testUserName"}/>,
element: <IndexPage/>,
errorElement:<_404Page/>
},
{
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
Expand Down

0 comments on commit 2179a62

Please sign in to comment.