Skip to content

Commit

Permalink
Add rating picker card
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanl21 committed Oct 28, 2023
1 parent 250a46c commit 1632737
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 11 deletions.
51 changes: 50 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
"dompurify": "^3.0.6",
"firebase": "^10.5.0",
"nanoid": "^5.0.2",
"rc-rate": "^2.12.0",
"react": "^18.2.0",
"react-bootstrap": "^2.9.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",
"react-icons": "^4.11.0"
"react-icons": "^4.11.0",
"react-simple-star-rating": "^5.1.7"
},
"devDependencies": {
"@types/react": "^18.2.28",
Expand Down
48 changes: 44 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import Tab from "react-bootstrap/Tab";
import Tabs from "react-bootstrap/Tabs";

import Modal from "react-bootstrap/Modal";
import { RatingPickerCard } from "./components/RatingPickerCard";
import { RatingItem, getRatingItem } from "./tasks/getRatingItems";
import { getUserRating } from "./tasks/getUserRatings";
import { setRating } from "./tasks/setRating";

function App() {
const [authActionType, setAuthActionType] = useState<"signup" | "signin">(
Expand All @@ -31,15 +35,35 @@ function App() {
const openModal = () => setShowModal(true);
const closeModal = () => setShowModal(false);

// for testing rating card
const [ratingItem, setRatingItem] = useState<RatingItem | null>(null);
const [demoRating, setDemoRating] = useState(0);

const debugId = "TmrDzWYpbZAhhHy8TUSv";
const getDebugRatingItem = async () => {
if (auth.currentUser) {
setRatingItem(await getRatingItem(debugId));

try {
const dbgRating = await getUserRating(auth.currentUser.uid, debugId);
setDemoRating(dbgRating);
} catch (err) {
setDemoRating(0);
}
} else {
alert("only logged in users can rate items!");
}
};

return (
<>
<Tabs
defaultActiveKey="profile"
defaultActiveKey="home"
id="uncontrolled-tab-example"
className="mb-3"
>
<Tab eventKey="home" title="Home">
<>
<div>
<Button variant="primary" onClick={openModal}>
Sign In
</Button>
Expand Down Expand Up @@ -79,9 +103,25 @@ function App() {
</Button>
</Modal.Footer>
</Modal>
</>
</div>
</Tab>
<Tab eventKey="leaderboard" title="Leaderboard">
<Button onClick={getDebugRatingItem}>Get Debug Rating Item</Button>
{ratingItem && (
<RatingPickerCard
id={debugId}
name={ratingItem.name}
description={ratingItem.description}
img_src={ratingItem.image}
rating={demoRating}
OnRatingChanged={(id, rating) =>
setRating(id, rating)
.then(() => alert("rating set!"))
.catch((err) => alert(err))
}
/>
)}
</Tab>
<Tab eventKey="leaderboard" title="Leaderboard"></Tab>
</Tabs>
</>
);
Expand Down
60 changes: 60 additions & 0 deletions src/components/RatingPickerCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import Spinner from "react-bootstrap/Spinner";
import { Rating } from "react-simple-star-rating";
import { useState } from "react";
import { FirebaseError } from "firebase/app";

interface RatingPickerCardProps {
id: string;
name: string;
description: string;
img_src: string;
rating: number;
OnRatingChanged: (
id: string,
rating: number
) => Promise<void | FirebaseError>;
}

export const RatingPickerCard = ({ ...props }: RatingPickerCardProps) => {
const handleRating = async (id: string, rating: number) => {
await props.OnRatingChanged(id, rating);
};

const [rating, setRating] = useState(0);

return (
<>
<Card style={{ width: "25rem" }}>
{props.img_src ? (
<Card.Img variant="top" src={props.img_src} />
) : (
<Spinner />
)}
<Card.Body>
<Card.Title>{props.name}</Card.Title>
<Card.Text>{props.description}</Card.Text>
<div className="d-flex justify-content-center">
<Rating
initialValue={props.rating}
onClick={(val: number) => setRating(val)}
allowFraction
/>
</div>
<hr />
<div className="d-flex justify-content-end">
<Button
variant="primary"
onClick={() => {
handleRating(props.id, rating);
}}
>
Submit
</Button>
</div>
</Card.Body>
</Card>
</>
);
};
42 changes: 40 additions & 2 deletions src/tasks/getRatingItems.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { collection, getDocs, query, limit } from "firebase/firestore";
import { auth, db } from "../config/firebase";
import {
collection,
getDoc,
getDocs,
query,
limit,
doc,
Timestamp,
} from "firebase/firestore";
import { ref } from "firebase/storage";
import { auth, db, storage } from "../config/firebase";
import { getDownloadURL } from "firebase/storage";

export type RatingItem = {
addedBy: string;
averageRating: number;
dateAdded: Timestamp;
description: string;
image: string;
name: string;
ratingCount: number;
};

/**
* @brief Gets items that can be rated from the database
Expand All @@ -26,3 +46,21 @@ export const getRatingItems = async (count: number) => {
return Promise.reject(err);
}
};

/**
* @brief Gets a single rating item, if it exists
* @param id id of the rating to get
*/
export const getRatingItem = async (id: string) => {
const docRef = doc(db, "rating-items", id);
const docSnap = await getDoc(docRef);

if (!docSnap.data()) {
return Promise.reject("Error: Item not found.");
}

const docData = docSnap.data() as RatingItem;
docData.image = await getDownloadURL(ref(storage, docData.image));

return Promise.resolve(docData);
};
20 changes: 20 additions & 0 deletions src/tasks/getUserRatings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,23 @@ export const getUserRatings = async (userId: string) => {
return Promise.reject(err);
}
};

export const getUserRating = async (userId: string, ratingItemId: string) => {
try {
if (!auth.currentUser) {
return Promise.reject("Only logged in users can view a user's ratings.");
}

const docRef = doc(db, "user-ratings", userId);
const docSnap = await getDoc(docRef);
const data = docSnap.data();

if (data && Object.keys(data).indexOf(ratingItemId) >= 0) {
return Promise.resolve(data[ratingItemId]);
} else {
return Promise.reject("Error: rating not found.");
}
} catch (err) {
return Promise.reject(err);
}
};
5 changes: 2 additions & 3 deletions src/tasks/setRating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const setRating = async (ratingItemId: string, ratingValue: number) => {
}

// update the average rating
await updateDoc(ratingItemDoc, ratingItemSnapData);
return updateDoc(ratingItemDoc, ratingItemSnapData);
} else {
await setDoc(
userRatingsRef,
Expand All @@ -79,9 +79,8 @@ export const setRating = async (ratingItemId: string, ratingValue: number) => {
ratingItemSnapData.averageRating = temp / ratingItemSnapData.ratingCount;

// update the average rating
await updateDoc(ratingItemDoc, ratingItemSnapData);
return updateDoc(ratingItemDoc, ratingItemSnapData);
}
return Promise.resolve();
} catch (err) {
return Promise.reject(err);
}
Expand Down

0 comments on commit 1632737

Please sign in to comment.