From ce7f8d55d092aa1cc8c580d15f6d37bae4ba4486 Mon Sep 17 00:00:00 2001 From: Harry Mellor <19981378+HMellor@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:30:37 +0100 Subject: [PATCH] `endTime` from ISO string, get items from Firebase data (not local data), and some small tweaks (#30) * Upload item data to Firebase in reset functions * Remove comment * Admin clock fix * Construct grid from Firebase data * Increase clock polling rate on admin page --- README.md | 6 +- js/admin.js | 17 +++--- js/auctions.js | 51 +++++----------- js/demo.js | 37 ------------ js/firebase.js | 115 ------------------------------------ js/items.js | 156 +++++++++++++++++++++++++++++++++++++++++++++++++ js/popups.js | 1 - 7 files changed, 181 insertions(+), 202 deletions(-) delete mode 100644 js/demo.js create mode 100644 js/items.js diff --git a/README.md b/README.md index 275df4f5..cba1cca2 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,16 @@ This is a project I originally worked on for a charity event and I've been impro Here we will cover how to add your own information to the auctions themselves, then how to most a local server to see your changes and finally how to connect it all to Firebase to enable user login and bidding. ### Adding auction information -First, set `isDemo=False` in `js/auctions.js` (this will keep the cats at bay). +First, set `isDemo=False` in `js/items.js` (this will keep the cats at bay). -Then, populate the `Object` at the bottom of `js/firebase.js` with the information for of the items you'll be putting up for auction. The fields are: +Then, populate the `Object` in `js/items.js` with the information for of the items you'll be putting up for auction. The fields are: - `primaryImage` (`String`): path or URL to the primary image - `title` (`String`): item title - `subtitle` (`String`): item subtitle - `detail` (`String`): item detail text - `secondaryImage` (`String`): path or URL to the secondary image - `amount` (`Number`): item starting price, -- `endTime` (`Number`): item end time relative to epoch **in milliseconds**. (See [JavaScript's `Date` class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) for more information.) +- `endTime` (`string`): item end time in [ISO 8601 format](https://tc39.es/ecma262/#sec-date-time-string-format) (`YYYY-MM-DDTHH:mm:ss.sssZ`) ### Firebase setup Here we will cover how to set up your Firebase project and then how to enable the Firebase authentication and database respectively. diff --git a/js/admin.js b/js/admin.js index b855e350..3b7cddf5 100644 --- a/js/admin.js +++ b/js/admin.js @@ -1,13 +1,13 @@ -// Imports import { db } from "./firebase.js"; -import { timeToString, getItems, dataListener } from "./auctions.js"; +import { getItems } from "./items.js"; +import { timeToString, dataListener } from "./auctions.js"; import { doc, setDoc, getDoc, updateDoc, deleteField, - onSnapshot, + Timestamp, } from "https://www.gstatic.com/firebasejs/9.20.0/firebase-firestore.js"; let table = document.querySelector("tbody"); @@ -52,7 +52,7 @@ function dataListenerCallback(data) { // Remove winner name if auction was reset row.children[4].innerText = ""; } - row.children[5].dataset.endTime = bids[0].endTime; + row.children[5].dataset.endTime = bids[0].endTime.toMillis(); } } @@ -63,12 +63,11 @@ function setClocks() { row.children[5].dataset.endTime - now ); }); - setTimeout(setClocks, 1000); } export function setupTable() { dataListener(dataListenerCallback); - setClocks(); + setInterval(setClocks, 100); } function resetItem(i) { @@ -80,6 +79,7 @@ function resetItem(i) { console.debug("resetItem() read from auction/items"); // Find all bids for item i let item = items[i]; + item.endTime = Timestamp.fromDate(item.endTime); let keys = Object.keys(doc.data()).sort(); keys .filter((key) => key.includes(`item${i.toString().padStart(5, "0")}`)) @@ -99,8 +99,9 @@ function resetAll() { getItems().then((items) => { let initialState = {}; items.forEach((item) => { - let field = `item${item.id.toString().padStart(5, "0")}_bid00000`; - initialState[field] = item; + let key = `item${item.id.toString().padStart(5, "0")}_bid00000`; + item.endTime = Timestamp.fromDate(item.endTime); + initialState[key] = item; }); setDoc(doc(db, "auction", "items"), initialState); console.debug("resetAll() write to auction/items"); diff --git a/js/auctions.js b/js/auctions.js index 7879af1b..e073724f 100644 --- a/js/auctions.js +++ b/js/auctions.js @@ -1,13 +1,11 @@ -// Imports -import { db, auctions } from "./firebase.js"; -import { generateRandomAuctionData } from "./demo.js"; +import { db } from "./firebase.js"; +import { getItems } from "./items.js"; import { doc, onSnapshot, } from "https://www.gstatic.com/firebasejs/9.20.0/firebase-firestore.js"; -// For a real auction, set this to false -export const isDemo = true; +let grid = document.getElementById("auction-grid"); // Helper function const divmod = (x, y) => [Math.floor(x / y), x % y]; @@ -40,15 +38,7 @@ function setClocks() { }); } -function argsort(array, key) { - // Insert the index from the unsorted array as the item ID - array.forEach((value, idx) => { - array[idx].id = idx; - }); - return array.sort((a, b) => a[key] - b[key]); -} - -function generateAuctionCard(auction) { +function generateItemCard(auction) { // create auction card let col = document.createElement("div"); col.classList.add("col"); @@ -135,15 +125,6 @@ function generateAuctionCard(auction) { return col; } -// Generatively populate the website with auctions -function populateAuctionGrid(auctions) { - let auctionGrid = document.getElementById("auction-grid"); - auctions.forEach((auction) => { - let auctionCard = generateAuctionCard(auction); - auctionGrid.appendChild(auctionCard); - }); -} - function numberWithCommas(x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } @@ -151,7 +132,13 @@ function numberWithCommas(x) { function dataListenerCallback(data) { // Use structured Object to populate the "Current bid" for each item for (const [id, bids] of Object.entries(data)) { + let item = bids[0]; let card = document.querySelector(`.card[data-id="${id}"]`); + if (card == null) { + let col = generateItemCard(item); + grid.appendChild(col); + card = col.firstChild; + } // Update current bid let currentBid = card.querySelector(".current-bid"); // Extract bid data @@ -162,8 +149,7 @@ function dataListenerCallback(data) { bidCount != 1 ? "s" : "" }]`; // Update everything else - let item = bids[0]; - card.dataset.endTime = item.endTime; + card.dataset.endTime = item.endTime.toMillis(); card.querySelector(".card-img-top").src = item.primaryImage; card.querySelector(".title").innerText = item.title; card.querySelector(".card-subtitle").innerText = item.subtitle; @@ -189,18 +175,7 @@ export function dataListener(callback) { }); } -export async function getItems() { - return argsort( - isDemo ? await generateRandomAuctionData(auctions) : auctions, - "endTime" - ); -} - export function setupItems() { - getItems() - .then((auctions) => populateAuctionGrid(auctions)) - .then(() => { - setInterval(setClocks, 100); - dataListener(dataListenerCallback); - }); + dataListener(dataListenerCallback); + setInterval(setClocks, 100); } diff --git a/js/demo.js b/js/demo.js deleted file mode 100644 index bf7cb515..00000000 --- a/js/demo.js +++ /dev/null @@ -1,37 +0,0 @@ -// Random auction information -export async function generateRandomAuctionData(auctions) { - // Random cat names - await $.getJSON( - "https://random-data-api.com/api/name/random_name", - { size: auctions.length }, - (data) => { - data.forEach((elem, i) => { - auctions[i].title = elem.name; - }); - } - ); - // Random lorem ipsum cat descriptions - await $.getJSON( - "https://random-data-api.com/api/lorem_ipsum/random_lorem_ipsum", - { size: auctions.length }, - (data) => { - data.forEach((elem, i) => { - auctions[i].subtitle = elem.short_sentence; - auctions[i].detail = elem.very_long_sentence; - }); - } - ); - // Random cat images and end times - for (let i = 0; i < auctions.length; i++) { - auctions[i].primaryImage = "https://cataas.com/cat/cute?random=" + i; - auctions[i].secondaryImage = "https://cataas.com/cat/cute?random=" + i; - - let now = new Date(); - let endTime = new Date().setHours(8 + i, 0, 0, 0); - if (endTime - now < 0) { - endTime = new Date(endTime).setDate(now.getDate() + 1); - } - auctions[i].endTime = endTime; - } - return auctions; -} diff --git a/js/firebase.js b/js/firebase.js index 442e4a50..63596f45 100644 --- a/js/firebase.js +++ b/js/firebase.js @@ -1,11 +1,7 @@ -// Import the functions you need from the SDKs you need import { initializeApp } from "https://www.gstatic.com/firebasejs/9.20.0/firebase-app.js"; import { getAuth } from "https://www.gstatic.com/firebasejs/9.20.0/firebase-auth.js"; import { getFirestore } from "https://www.gstatic.com/firebasejs/9.20.0/firebase-firestore.js"; -// TODO: Add SDKs for Firebase products that you want to use -// https://firebase.google.com/docs/web/setup#available-libraries -// Your web app's Firebase configuration const firebaseConfig = { apiKey: "AIzaSyCAYOYDuMKGGjTSJL5uDzG5hjQ6y_vYPiI", authDomain: "auction-website-b12fc.firebaseapp.com", @@ -19,114 +15,3 @@ const firebaseConfig = { const app = initializeApp(firebaseConfig); export const db = getFirestore(app); export const auth = getAuth(app); - -export const auctions = [ - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 55, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 60, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 20, - endTime: 0, - }, - { - rimaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 0, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 4, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 0, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 99, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 0, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 12, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 6, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 3, - endTime: 0, - }, - { - primaryImage: "", - title: "", - subtitle: "", - detail: "", - secondaryImage: "", - amount: 7, - endTime: 0, - }, -]; diff --git a/js/items.js b/js/items.js new file mode 100644 index 00000000..29799958 --- /dev/null +++ b/js/items.js @@ -0,0 +1,156 @@ +// For a real auction, set this to false +export const isDemo = true; + +// Specify item details +let items = [ + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 55, + endTime: "2023-04-25T10:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 60, + endTime: "2023-04-25T11:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 20, + endTime: "2023-04-25T12:00:00+00:00", + }, + { + rimaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 0, + endTime: "2023-04-25T13:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 4, + endTime: "2023-04-25T14:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 0, + endTime: "2023-04-25T15:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 99, + endTime: "2023-04-25T16:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 0, + endTime: "2023-04-25T17:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 12, + endTime: "2023-04-25T18:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 6, + endTime: "2023-04-25T19:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 3, + endTime: "2023-04-25T20:00:00+00:00", + }, + { + primaryImage: "", + title: "", + subtitle: "", + detail: "", + secondaryImage: "", + amount: 7, + endTime: "2023-04-25T21:00:00+00:00", + }, +]; + +// Fill missing fields with random information +async function generateRandomItemData(items) { + // Random cat names + await $.getJSON( + "https://random-data-api.com/api/name/random_name", + { size: items.length }, + (data) => { + data.forEach((elem, i) => { + items[i].title ||= elem.name; + }); + } + ); + // Random lorem ipsum cat descriptions + await $.getJSON( + "https://random-data-api.com/api/lorem_ipsum/random_lorem_ipsum", + { size: items.length }, + (data) => { + data.forEach((elem, i) => { + items[i].subtitle ||= elem.short_sentence; + items[i].detail ||= elem.very_long_sentence; + }); + } + ); + // Random cat images + for (let i = 0; i < items.length; i++) { + items[i].primaryImage ||= "https://cataas.com/cat/cute?random=" + i; + items[i].secondaryImage ||= "https://cataas.com/cat/cute?random=" + i; + } + return items; +} + +export async function getItems() { + items = isDemo ? await generateRandomItemData(items) : items; + // Insert the index from the unsorted array as the item ID + items.forEach((item, idx) => (item.id = idx)); + // Parse endTime from ISO 8601 string + items.forEach((item) => (item.endTime = new Date(item.endTime))); + // Sort items in ascending end time + items.sort((a, b) => a["endTime"] - b["endTime"]); + return items; +} diff --git a/js/popups.js b/js/popups.js index c69ed54e..4ebf19b2 100644 --- a/js/popups.js +++ b/js/popups.js @@ -1,4 +1,3 @@ -// Imports import { auth, db } from "./firebase.js"; import { doc,