diff --git a/src/main.tsx b/src/main.tsx index a1fb6fd..084976b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import React from 'react' import ReactDOM from 'react-dom/client' import './utils/firebase.ts' -import './utils/firestore.ts' +import './utils/transaction.ts' import {RouterProvider} from "react-router-dom"; import {router} from "./router.tsx"; diff --git a/src/pages/test firestore/TestFirestore.tsx b/src/pages/test firestore/TestFirestore.tsx index ba10d12..434b0f4 100644 --- a/src/pages/test firestore/TestFirestore.tsx +++ b/src/pages/test firestore/TestFirestore.tsx @@ -7,12 +7,14 @@ import { overwriteTransaction, Transaction, writeNewTransaction -} from "../../utils/firestore.ts"; +} from "../../utils/transaction.ts"; import {faker, fakerEN_GB} from "@faker-js/faker"; import {getCurrentBalance} from "../../utils/transaction_utils.ts"; import {signInWithGoogle} from "../../utils/authentication.ts"; import {Header} from "../../components/Header.tsx"; import { orderBy } from "firebase/firestore"; +import {User} from "firebase/auth"; +import {getUserPrefs, setUserPrefs, UserPrefs} from "../../utils/user_prefs.ts"; function writeSampleData() { if (auth.currentUser === null) { @@ -43,6 +45,8 @@ export function TestFirestorePage() { const [transactions, setTransactions] = useState<Transaction[]>([]); const [balance, setBalance] = useState(0); const [update ,setUpdate] = useState(0); + const [user, setUser] = useState<string | undefined>(undefined); + const [userPrefs, _setUserPrefs] = useState<UserPrefs | null>(null); if (!authResolved) { auth.authStateReady().then(() => setAuthResolved(true)); @@ -56,6 +60,11 @@ export function TestFirestorePage() { } if (auth.currentUser === null) { + auth.onAuthStateChanged((new_user: User | null) => { + if (new_user !== null && new_user.uid !== user) { + setUser(new_user.uid) + } + }); return <> <Header/> <FullscreenCenter> @@ -71,6 +80,7 @@ export function TestFirestorePage() { getTransactionsFilterOrderBy(auth.currentUser, orderBy("dateTime", "desc")).then((t) => setTransactions(t)); getCurrentBalance(auth.currentUser).then((b) => setBalance(b)); + getUserPrefs(auth.currentUser).then((up) => _setUserPrefs(up)); return ( <> @@ -81,30 +91,48 @@ export function TestFirestorePage() { Sign in with Google </button> </div> - <button onClick={() => { + <div> + <h4>UserPrefs</h4> + { + userPrefs ? <> + <p>Goal: {userPrefs.goal}</p> + <button className="mb-4" onClick={() => { + userPrefs!.goal += 100; + setUserPrefs(auth.currentUser!, userPrefs).then(() => setUpdate(update + 1)); + }}>Increment + </button> + </> : <p>Loading</p> + } + </div> + <h1> + Transactions + </h1> + <p> + Balance: {balance} + </p> + <button className="mb-4" onClick={() => { writeSampleData(); setUpdate(update + 1); }}>Add Sample Transaction </button> - <p> - Balance: {balance} - </p> - <p> - Transactions: <br/> - </p> { transactions.map((t) => <div className="mb-4" key={t.forceGetDocName()}> - <p className="m-0">{new Date(t.dateTime).toISOString()}</p> - <textarea readOnly={true} style={{width: "100%", height: "320px"}} value={JSON.stringify(t, null, 4)}/> - <button onClick={() => {deleteTransaction(t.forceGetDocName()).then(() => setUpdate(update + 1))}}>Delete</button> - <button onClick={() => { - if (auth.currentUser != null) { - t.dateTime = new Date(Date.now()).valueOf(); - overwriteTransaction(auth.currentUser, t.forceGetDocName(), t).then(() => setUpdate(update + 1)); - } - }}>Set Time To Now</button> - </div> - )} + <p className="m-0">{new Date(t.dateTime).toISOString()}</p> + <textarea readOnly={true} style={{width: "100%", height: "320px"}} + value={JSON.stringify(t, null, 4)}/> + <button onClick={() => { + deleteTransaction(t.forceGetDocName()).then(() => setUpdate(update + 1)) + }}>Delete + </button> + <button onClick={() => { + if (auth.currentUser != null) { + t.dateTime = new Date(Date.now()).valueOf(); + overwriteTransaction(auth.currentUser, t.forceGetDocName(), t).then(() => setUpdate(update + 1)); + } + }}>Set Time To Now + </button> + </div> + )} </> ) } diff --git a/src/utils/firestore.test.ts b/src/utils/transaction.test.ts similarity index 99% rename from src/utils/firestore.test.ts rename to src/utils/transaction.test.ts index f05741c..28b8bbb 100644 --- a/src/utils/firestore.test.ts +++ b/src/utils/transaction.test.ts @@ -6,7 +6,7 @@ import { Transaction, writeNewTransaction, writeNewTransactionsBatched -} from "./firestore.ts"; +} from "./transaction.ts"; import {faker, fakerEN_GB} from "@faker-js/faker"; import _ from "lodash"; import { describe, expect, test } from "vitest"; @@ -27,7 +27,7 @@ function fakeTransaction(uid: string, name?: string): Transaction { ); } -describe("Firestore Tests", () => { +describe("Firestore Transaction Tests", () => { test("Write/Read Test", async () => { const user = { uid: "sample_uid" } diff --git a/src/utils/firestore.ts b/src/utils/transaction.ts similarity index 100% rename from src/utils/firestore.ts rename to src/utils/transaction.ts diff --git a/src/utils/transaction_utils.ts b/src/utils/transaction_utils.ts index 2882890..53dd236 100644 --- a/src/utils/transaction_utils.ts +++ b/src/utils/transaction_utils.ts @@ -1,5 +1,5 @@ import {User} from "firebase/auth"; -import {getTransactions, getTransactionsFilterOrderBy, Transaction} from "./firestore.ts"; +import {getTransactions, getTransactionsFilterOrderBy, Transaction} from "./transaction.ts"; import {limit, startAfter, where} from "firebase/firestore"; export async function getCurrentBalance(user: User): Promise<number> { diff --git a/src/utils/user_prefs.test.ts b/src/utils/user_prefs.test.ts new file mode 100644 index 0000000..6ab94d1 --- /dev/null +++ b/src/utils/user_prefs.test.ts @@ -0,0 +1,32 @@ +import {describe, expect, test} from "vitest"; +import {faker} from "@faker-js/faker"; +import {UserPrefs, getUserPrefs, setUserPrefs} from "./user_prefs.ts"; +import _ from "lodash"; + + +describe("Firestore UserPrefs Tests", () => { + test("Read/Write/Default Value Test", async () => { + const user = { uid: faker.string.alphanumeric(20) } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const prefs = await getUserPrefs(user); + + expect(_.isEqual(prefs, UserPrefs.default()), "Non-existent UserPrefs should return default").toBeTruthy(); + + prefs.goal = 213; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + await setUserPrefs(user, prefs); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const read_prefs = await getUserPrefs(user); + + console.log(prefs); + console.log(read_prefs); + + expect(_.isEqual(read_prefs, prefs), "UserPref changes to be read").toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/utils/user_prefs.ts b/src/utils/user_prefs.ts new file mode 100644 index 0000000..2aa65b5 --- /dev/null +++ b/src/utils/user_prefs.ts @@ -0,0 +1,43 @@ +import {collection, doc, DocumentSnapshot, getDoc, setDoc, SnapshotOptions} from "firebase/firestore"; +import {User} from "firebase/auth"; +import {db} from "./firebase.ts"; + + +export class UserPrefs { + public goal: number; + + constructor(goal: number) { + this.goal = goal; + } + + static default(): UserPrefs { + return new UserPrefs(100); + } + + // Utility method for creating `Transactions` + static fromFirestore(snapshot: DocumentSnapshot, options: SnapshotOptions): UserPrefs { + const data = snapshot.data(options); + if (!data) { + throw Error("No data returned for snapshot!"); + } + return new UserPrefs(data.goal); + } + + toSendObject(): object { + const {...transObject} = this; + return transObject; + } +} + +export async function getUserPrefs(user: User): Promise<UserPrefs> { + const docRef = doc(collection(db, "UserPrefs"), user.uid); + + return await getDoc(docRef).then((ds) => + UserPrefs.fromFirestore(ds, {}) + ).catch(() => UserPrefs.default()); +} + +export async function setUserPrefs(user: User, prefs: UserPrefs): Promise<void> { + const docRef = doc(collection(db, "UserPrefs"), user.uid); + await setDoc(docRef, prefs.toSendObject()); +}