diff --git a/.firebaserc b/.firebaserc
new file mode 100644
index 0000000..d5b713f
--- /dev/null
+++ b/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "debbie-store"
+ }
+}
diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml
new file mode 100644
index 0000000..9e501cb
--- /dev/null
+++ b/.github/workflows/firebase-hosting-merge.yml
@@ -0,0 +1,20 @@
+# This file was auto-generated by the Firebase CLI
+# https://github.com/firebase/firebase-tools
+
+name: Deploy to Firebase Hosting on merge
+'on':
+ push:
+ branches:
+ - main
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: npm run app
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ repoToken: '${{ secrets.GITHUB_TOKEN }}'
+ firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_DEBBIE_STORE }}'
+ channelId: live
+ projectId: debbie-store
diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml
new file mode 100644
index 0000000..247c572
--- /dev/null
+++ b/.github/workflows/firebase-hosting-pull-request.yml
@@ -0,0 +1,17 @@
+# This file was auto-generated by the Firebase CLI
+# https://github.com/firebase/firebase-tools
+
+name: Deploy to Firebase Hosting on PR
+'on': pull_request
+jobs:
+ build_and_preview:
+ if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: npm run app
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ repoToken: '${{ secrets.GITHUB_TOKEN }}'
+ firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_DEBBIE_STORE }}'
+ projectId: debbie-store
diff --git a/client/public/images/rabbits.png b/client/public/images/rabbits.png
new file mode 100644
index 0000000..7541eaf
Binary files /dev/null and b/client/public/images/rabbits.png differ
diff --git a/client/public/images/yellow_rabbits.png b/client/public/images/yellow_rabbits.png
new file mode 100644
index 0000000..6033e75
Binary files /dev/null and b/client/public/images/yellow_rabbits.png differ
diff --git a/client/src/App.js b/client/src/App.js
index b01fcaa..e69098c 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -21,6 +21,8 @@ import CancelScreen from "./screens/CancelScreen";
import YourOrdersScreen from "./screens/YourOrdersScreen";
import SuccessScreen from "./screens/SuccessScreen";
import AdminConsoleScreen from "./screens/AdminConsoleScreen";
+import IdeasScreen from "./screens/IdeasScreen";
+import IdeaScreen from "./screens/IdeaScreen";
function App() {
const theme2 = extendTheme({
@@ -65,7 +67,9 @@ function App() {
} />
} />
} />
- } />
+ } />
+ } />
+ } />
diff --git a/client/src/components/IdeaCard.jsx b/client/src/components/IdeaCard.jsx
new file mode 100644
index 0000000..5e0b4f8
--- /dev/null
+++ b/client/src/components/IdeaCard.jsx
@@ -0,0 +1,55 @@
+import { Box, Image, Text, Badge, Flex, IconButton, Skeleton, useToast, Tooltip } from "@chakra-ui/react";
+import { BiExpand } from "react-icons/bi";
+import React, { useState } from "react";
+import { addToFavorites, removeFromFavorites } from "../redux/actions/productActions";
+import { useSelector, useDispatch } from "react-redux";
+import { MdOutlineFavorite, MdOutlineFavoriteBorder } from "react-icons/md";
+import { Link as ReactLink } from "react-router-dom";
+import { addCartItem } from "../redux/actions/cartActions";
+import { useEffect } from "react";
+import { TbShoppingCartPlus } from "react-icons/tb";
+
+const IdeaCard = ({ idea, loading }) => {
+ const dispatch = useDispatch();
+ const [isShown, setIsShown] = useState(false);
+ console.log(idea.images);
+
+ return (
+
+
+
+ {idea.name}
+ setIsShown(true)}
+ onMouseLeave={() => setIsShown(false)}
+ src={idea.images[isShown && idea.images.length === 2 ? 1 : 0]}
+ fallbackSrc='https://via.placeholder.com/150'
+ alt={idea.name}
+ // height='200px'
+ />
+
+
+
+ );
+};
+
+export default IdeaCard;
diff --git a/client/src/redux/actions/ideaActions.js b/client/src/redux/actions/ideaActions.js
new file mode 100644
index 0000000..9d141bb
--- /dev/null
+++ b/client/src/redux/actions/ideaActions.js
@@ -0,0 +1,60 @@
+import { setIdea, setIdeas, setLoading, setError, setPagination, resetError } from "../slices/idea";
+ import axios from "axios";
+
+ export const getIdeas = (page, favoriteToggle) => async (dispatch) => {
+ dispatch(setLoading());
+ try {
+ const { data } = await axios.get(`/api/ideas/${page}/${10}`);
+ console.log(data)
+ const { ideas, pagination } = data;
+ dispatch(setIdeas(ideas));
+ dispatch(setPagination(pagination));
+ } catch (error) {
+ dispatch(
+ setError(
+ error.response && error.response.data.message
+ ? error.response.data.message
+ : error.message
+ ? error.message
+ : "An unexpected error has occured. Please try again later."
+ )
+ );
+ }
+ };
+
+
+ export const getIdea = (id) => async (dispatch) => {
+ dispatch(setLoading(true));
+ try {
+ const { data } = await axios.get(`/api/ideas/${id}`);
+ dispatch(setIdea(data));
+ } catch (error) {
+ dispatch(
+ setError(
+ error.response && error.response.data.message
+ ? error.response.data.message
+ : error.message
+ ? error.message
+ : 'An expected error has occured. Please try again later.'
+ )
+ );
+ }
+ };
+
+export const createIdeaReview = (ideaId, comment, rating, userName) => async (dispatch, getState) => {
+ try {
+ await axios.post(`/api/ideas/reviews/${ideaId}`, { comment, rating, userName });
+
+ } catch (error) {
+ dispatch(
+ setError(
+ error.response && error.response.data.message
+ ? error.response.data.message
+ : error.message
+ ? error.message
+ : 'An expected error has occured. Please try again later.'
+ )
+ );
+ }
+ };
+
diff --git a/client/src/redux/slices/idea.js b/client/src/redux/slices/idea.js
new file mode 100644
index 0000000..c0291e8
--- /dev/null
+++ b/client/src/redux/slices/idea.js
@@ -0,0 +1,61 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+export const initialState = {
+ loading: false,
+ error: null,
+ ideas: [],
+ idea: null,
+ pagination: {},
+};
+
+export const ideasSlice = createSlice({
+ name: "ideas",
+ initialState,
+ reducers: {
+ setLoading: (state) => {
+ state.loading = true;
+ },
+ setIdeas: (state, { payload }) => {
+ state.loading = false;
+ state.error = null;
+ state.ideas = payload;
+ },
+ setIdea: (state, { payload }) => {
+ state.idea = payload;
+ state.loading = false;
+ state.error = null;
+ },
+ setError: (state, { payload }) => {
+ state.loading = false;
+ state.error = payload;
+ },
+ setPagination: (state, { payload }) => {
+ state.loading = false;
+ state.error = null;
+ state.pagination = payload;
+ },
+
+ resetError: (state) => {
+ state.error = null;
+ state.reviewed = false;
+ state.ideaUpdate = false;
+ },
+
+ },
+});
+
+export const {
+ setLoading,
+ setError,
+ setIdeas,
+ setIdea,
+
+ setPagination,
+
+ resetError,
+
+} = ideasSlice.actions;
+
+export default ideasSlice.reducer;
+export const ideaSelector = (state) => state.ideas;
+
diff --git a/client/src/redux/store.js b/client/src/redux/store.js
index 5c683dd..fca5f39 100644
--- a/client/src/redux/store.js
+++ b/client/src/redux/store.js
@@ -4,7 +4,8 @@ import cart from "./slices/cart";
import user from "./slices/user";
import order from "./slices/order";
import admin from "./slices/admin";
+import idea from "./slices/idea";
-const reducer = combineReducers({ product, cart, user, order, admin });
+const reducer = combineReducers({ product, cart, user, order, admin , idea});
export default configureStore({ reducer });
diff --git a/client/src/screens/IdeaScreen.jsx b/client/src/screens/IdeaScreen.jsx
new file mode 100644
index 0000000..f389d0a
--- /dev/null
+++ b/client/src/screens/IdeaScreen.jsx
@@ -0,0 +1,166 @@
+import { styled, Stack, Box } from "@mui/system";
+import { useDispatch, useSelector } from "react-redux";
+import { useParams } from "react-router-dom";
+import { useEffect, useState } from "react";
+import { getIdea, createIdeaReview } from "../redux/actions/ideaActions";
+import { useToast } from "@chakra-ui/react";
+const Input = styled("textArea")({});
+const Button = styled("button")({});
+const Image = styled("img")({});
+const Table = styled("table")({ textAlign: "left" });
+
+const IdeaScreen = () => {
+ const { id } = useParams();
+ const dispatch = useDispatch();
+ const { idea } = useSelector((state) => state.idea);
+ const { userInfo } = useSelector((state) => state.user);
+ const toast = useToast();
+ const [comment, setComment] = useState("");
+ const [rating, setRating] = useState(1);
+ const [showReviews, setShowReviews] = useState(false);
+ const [reviews, setReviews] = useState([]);
+ const [alreadyReviewed, setAlreadyReviewed] = useState(false);
+
+ useEffect(() => {
+ dispatch(getIdea(id));
+ }, [dispatch, id, showReviews]);
+
+ useEffect(() => {
+ if (idea && idea.reviews) {
+ setReviews(idea.reviews);
+ setRating(1);
+ setComment("");
+ if (userInfo) {
+ const hasReviewed = idea.reviews.some((review) => {
+ return review.name === userInfo?.name;
+ });
+ setAlreadyReviewed(hasReviewed);
+ }
+ }
+ }, [idea, userInfo]);
+
+ console.log("already", alreadyReviewed);
+
+ useEffect(() => {
+ console.log("State after reset:", { comment, rating });
+ }, [comment, rating]);
+
+ const submitReview = async () => {
+ await dispatch(createIdeaReview(id, comment, rating, userInfo?.name));
+ await dispatch(getIdea(id)); // Fetch latest idea data including the new review
+
+ toast({
+ description: "Review has been added.",
+ status: "success",
+ isClosable: true,
+ });
+ };
+
+ return (
+ <>
+ {idea && (
+ <>
+
+
+
+ T-Shirt: {idea.name}
+ {!alreadyReviewed && userInfo && (
+
+
+ Rating
+
+
+
+
+
+ Comment
+
+ setComment(e.target.value)}
+ />
+
+
+
+
+ )}{" "}
+ {alreadyReviewed && {userInfo.name}, you have added a review to this tshirt idea.}
+ {!userInfo && You must sign in to review this shirt.}
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+ {showReviews && (
+
+ Reviews
+
+
+
+ Name |
+ Rating |
+ Review |
+
+
+
+ {reviews?.map((review, index) => (
+
+ {review.name} |
+ {review.rating} |
+ {review.comment} |
+
+ ))}
+
+
+
+ )}
+ >
+ )}
+ >
+ );
+};
+
+export default IdeaScreen;
diff --git a/client/src/screens/IdeasScreen.jsx b/client/src/screens/IdeasScreen.jsx
new file mode 100644
index 0000000..d6cad44
--- /dev/null
+++ b/client/src/screens/IdeasScreen.jsx
@@ -0,0 +1,45 @@
+import { Alert, AlertTitle, AlertDescription, Center, Wrap, WrapItem } from "@chakra-ui/react";
+import { useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { Box } from "@mui/system";
+import IdeaCard from "../components/IdeaCard";
+import { getIdeas } from "../redux/actions/ideaActions";
+
+import { IoMdAlert } from "react-icons/io";
+
+const IdeasScreen = () => {
+ const dispatch = useDispatch();
+ const { loading, error, ideas, pagination } = useSelector((state) => state.idea);
+
+ useEffect(() => {
+ dispatch(getIdeas(1));
+ }, [dispatch]);
+
+ return (
+ <>
+ {ideas.length >= 1 && (
+
+
+ {error ? (
+
+
+ We are sorry!
+ {error}
+
+ ) : (
+ ideas.map((idea) => (
+
+
+
+
+
+ ))
+ )}
+
+
+ )}
+ >
+ );
+};
+
+export default IdeasScreen;
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 0000000..93fb369
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,16 @@
+{
+ "hosting": {
+ "public": "public",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ],
+ "rewrites": [
+ {
+ "source": "**",
+ "destination": "/index.html"
+ }
+ ]
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 94e1e6e..f3d7e0e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"dotenv": "^16.4.5",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
+ "firebase": "^10.10.0",
"fs": "^0.0.1-security",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
@@ -700,6 +701,528 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@fastify/busboy": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
+ "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@firebase/analytics": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.2.tgz",
+ "integrity": "sha512-6Gv/Fndih+dOEEfsBJEeKlwxw9EvCO9D/y+yJMasblvCmj78wUVtn+T96zguSrbhfZ2yBhLS1vukYiPg6hI49w==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/installations": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/analytics-compat": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.8.tgz",
+ "integrity": "sha512-scvzDPIsP9HcLWM77YQD7F3yLQksGvPUzyfqUrPo9XxIx26txJvGMJAS8O8BHa6jIvsjUenaTZ5oXEtKqNZQ9Q==",
+ "dependencies": {
+ "@firebase/analytics": "0.10.2",
+ "@firebase/analytics-types": "0.8.1",
+ "@firebase/component": "0.6.6",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/analytics-types": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.1.tgz",
+ "integrity": "sha512-niv/67/EOkTlGUxyiOYfIkysSMGYxkIUHJzT9pNkeIGt6zOz759oCUXOAwwjJzckh11dMBFjIYBmtWrdSgbmJw=="
+ },
+ "node_modules/@firebase/app": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.0.tgz",
+ "integrity": "sha512-bemcsqQD4teEnCM/+FiK8LFjlfoIFewMY3LOIgxa59ISlkk4zlw4ezz1iLY45yQ6ip6WDwky7cx9UruFBAn6iw==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/app-check": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.3.tgz",
+ "integrity": "sha512-nvlsj5oZBtYDjFTygQJ6xpyiYj8Jao2bFFyNJkUUPdg/QB8uhqDeG74P+gUH6iY9qzd1g5ZokmmGsoIhv9tdSQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/app-check-compat": {
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.10.tgz",
+ "integrity": "sha512-v+jiLG3rQ1fhpIuNIm3WqrL4dkPUIkgOWoic7QABVsZKSAv2YhOFvAenp7IhSP/pz/aiPniJ8G7el/MWieECTg==",
+ "dependencies": {
+ "@firebase/app-check": "0.8.3",
+ "@firebase/app-check-types": "0.5.1",
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/app-check-interop-types": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.1.tgz",
+ "integrity": "sha512-NILZbe6RH3X1pZmJnfOfY2gLIrlKmrkUMMrrK6VSXHcSE0eQv28xFEcw16D198i9JYZpy5Kwq394My62qCMaIw=="
+ },
+ "node_modules/@firebase/app-check-types": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.1.tgz",
+ "integrity": "sha512-NqeIcuGzZjl+khpXV0qsyOoaTqLeiG/K0kIDrebol+gb7xpmfOvXXqPEls+1WFBgHcPGdu+XRLhBA7xLzrVdpA=="
+ },
+ "node_modules/@firebase/app-compat": {
+ "version": "0.2.30",
+ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.30.tgz",
+ "integrity": "sha512-S3FI3yx36xq5NYWXv/rqZiEnkQ89QwfGdl26iWZ9skuOGM96DYQUxs/zs7NkfAQcfpXC8f5DuUrE0Rz/0XdTEg==",
+ "dependencies": {
+ "@firebase/app": "0.10.0",
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/app-types": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.1.tgz",
+ "integrity": "sha512-nFGqTYsnDFn1oXf1tCwPAc+hQPxyvBT/QB7qDjwK+IDYThOn63nGhzdUTXxVD9Ca8gUY/e5PQMngeo0ZW/E3uQ=="
+ },
+ "node_modules/@firebase/auth": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.0.tgz",
+ "integrity": "sha512-xvyCR3Ivan74AwT/rQOqrYkyu4Ccz6GOFaohi1Pw3gLOpG2WIdC/phc4zdQkLJjmbGFcYNisHyqII2P/H9ZJow==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0",
+ "undici": "5.28.3"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x",
+ "@react-native-async-storage/async-storage": "^1.18.1"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-async-storage/async-storage": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@firebase/auth-compat": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.5.tgz",
+ "integrity": "sha512-iAq/wCCEX4TPhZeCOmLxscHh6oZtvJ4g/FcRLynFntW3WOtrWF9/91jq+FsDSSJo9Av8MpnayCbbx+jpGSv4DQ==",
+ "dependencies": {
+ "@firebase/auth": "1.7.0",
+ "@firebase/auth-types": "0.12.1",
+ "@firebase/component": "0.6.6",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0",
+ "undici": "5.28.3"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/auth-interop-types": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.2.tgz",
+ "integrity": "sha512-k3NA28Jfoo0+o391bFjoV9X5QLnUL1WbLhZZRbTQhZdmdGYJfX8ixtNNlHsYQ94bwG0QRbsmvkzDnzuhHrV11w=="
+ },
+ "node_modules/@firebase/auth-types": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.1.tgz",
+ "integrity": "sha512-B3dhiWRWf/njWosx4zdhSEoD4WHJmr4zbnBw6t20mRG/IZ4u0rWUBlMP1vFjhMstKIow1XmoGhTwD65X5ZXLjw==",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/component": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.6.tgz",
+ "integrity": "sha512-pp7sWqHmAAlA3os6ERgoM3k5Cxff510M9RLXZ9Mc8KFKMBc2ct3RkZTWUF7ixJNvMiK/iNgRLPDrLR2gtRJ9iQ==",
+ "dependencies": {
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.4.tgz",
+ "integrity": "sha512-k84cXh+dtpzvY6yOhfyr1B+I1vjvSMtmlqotE0lTNVylc8m5nmOohjzpTLEQDrBWvwACX/VP5fEyajAdmnOKqA==",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.1",
+ "@firebase/auth-interop-types": "0.2.2",
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "faye-websocket": "0.11.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database-compat": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.4.tgz",
+ "integrity": "sha512-GEEDAvsSMAkqy0BIFSVtFzoOIIcKHFfDM4aXHtWL/JCaNn4OOjH7td73jDfN3ALvpIN4hQki0FcxQ89XjqaTjQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/database": "1.0.4",
+ "@firebase/database-types": "1.0.2",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database-types": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.2.tgz",
+ "integrity": "sha512-JRigr5JNLEHqOkI99tAGHDZF47469/cJz1tRAgGs8Feh+3ZmQy/vVChSqwMp2DuVUGp9PlmGsNSlpINJ/hDuIA==",
+ "dependencies": {
+ "@firebase/app-types": "0.9.1",
+ "@firebase/util": "1.9.5"
+ }
+ },
+ "node_modules/@firebase/firestore": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.5.1.tgz",
+ "integrity": "sha512-VQsMKJGuqlx8I+n+NhNrdFRBJU/B1O8mpGIAYABBmVxPyJax/ynuBMJkREmqzRWpbBj5IAtHe+vm4EvJlb6RLg==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "@firebase/webchannel-wrapper": "0.10.6",
+ "@grpc/grpc-js": "~1.9.0",
+ "@grpc/proto-loader": "^0.7.8",
+ "tslib": "^2.1.0",
+ "undici": "5.28.3"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/firestore-compat": {
+ "version": "0.3.28",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.28.tgz",
+ "integrity": "sha512-qaE9QYrWV0K+nh/HWf2EL/V2fPsuTZJ8K4S4e+xUOIxVulmXXwlKg4vgJgRF6r5AlABcSphKNbz/77fChgNwiQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/firestore": "4.5.1",
+ "@firebase/firestore-types": "3.0.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/firestore-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.1.tgz",
+ "integrity": "sha512-mVhPcHr5FICjF67m6JHgj+XRvAz/gZ62xifeGfcm00RFl6tNKfCzCfKeyB2BDIEc9dUnEstkmIXlmLIelOWoaA==",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/functions": {
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.3.tgz",
+ "integrity": "sha512-fpjc3VwxsgFBcR0wmof6kIng7NNvhjqetwWUTMs/ZeOI0QiZoUvSDaudFZvPfvXujSK/sr3tk9G1YzjbwCQkgQ==",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.1",
+ "@firebase/auth-interop-types": "0.2.2",
+ "@firebase/component": "0.6.6",
+ "@firebase/messaging-interop-types": "0.2.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0",
+ "undici": "5.28.3"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/functions-compat": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.9.tgz",
+ "integrity": "sha512-yVcNBUljBFD6VPeTJcnWBEFZlVICKWuJzJmPuvgKEH++8z/CdgUKw0YslceaPQIWnstdviZDEF1cjJnR/bLvzQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/functions": "0.11.3",
+ "@firebase/functions-types": "0.6.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/functions-types": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.1.tgz",
+ "integrity": "sha512-DirqgTXSBzyKsQwcKnx/YdGMaRdJhywnThrINP+Iog8QfQnrL7aprTXHDFHlpZEMwykS54YRk53xzz7j396QXQ=="
+ },
+ "node_modules/@firebase/installations": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.6.tgz",
+ "integrity": "sha512-dNGRGoHmstgEJqh9Kzk22fR2ZrVBH1JWliaL6binQ6pIzlWscreHNczzJDgOKoVT0PjWTrAmh/azztiX/e2uTw==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/util": "1.9.5",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/installations-compat": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.6.tgz",
+ "integrity": "sha512-uxBAt2WsuEMT5dalA/1O+Uyi9DS25zKHgIPdrQ7KO1ZUdBURiGScIyjdhIM/7NMSvHGYugK4PUVdK9NFIffeiw==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/installations": "0.6.6",
+ "@firebase/installations-types": "0.5.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/installations-types": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.1.tgz",
+ "integrity": "sha512-OyREnRTfe2wIWTrzCz65ajyo4lFm6VgbeVqMMP+3GJLfCtNvY9VXkmqs3WFEsyYezzdcRqOt39FynZoLlkO+cQ==",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x"
+ }
+ },
+ "node_modules/@firebase/logger": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.1.tgz",
+ "integrity": "sha512-tTIixB5UJbG9ZHSGZSZdX7THr3KWOLrejZ9B7jYsm6fpwgRNngKznQKA2wgYVyvBc1ta7dGFh9NtJ8n7qfiYIw==",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/messaging": {
+ "version": "0.12.7",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.7.tgz",
+ "integrity": "sha512-FNZiGMZWjU2D13U/XpoGDSfqCx2kqJ171P3VjquBJfd8SkYNyJMkKM82QvTjQaDd4nuWzgvTDR81DGJFUO6AOg==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/installations": "0.6.6",
+ "@firebase/messaging-interop-types": "0.2.1",
+ "@firebase/util": "1.9.5",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/messaging-compat": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.7.tgz",
+ "integrity": "sha512-29eeNzkjJPNc1RAVmxocaA8PzkbtuNvabX8jKw3N8VdAmyugx7+dYB+jCnereiWqIwivIZ2xSbCUQ24vC7+HaQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/messaging": "0.12.7",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/messaging-interop-types": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.1.tgz",
+ "integrity": "sha512-jfGJ7Jc32BDHXvXHyXi34mVLzZY8X0t929DTMwz7Tj2Hc40Zuzx8VRCIPLRrRUyvBrJCd5EpIcQgCygXhtaN1A=="
+ },
+ "node_modules/@firebase/performance": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.6.tgz",
+ "integrity": "sha512-UOUHhvj2GJcjyJewdX1ShnON0/eqTswHvYzzQPC4nrIuMFvHwMGk8NpCaqh7JZmpaxh9AMr6kM+M/p37DrKWXA==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/installations": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/performance-compat": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.6.tgz",
+ "integrity": "sha512-JSGdNNHBAMRTocGpN+m+7tk+9rulBcwuG+Ejw/ooDj45FGcON1Eymxh/qbe5M6Dlj5P1ClbkHLj4yf7MiCHOag==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/performance": "0.6.6",
+ "@firebase/performance-types": "0.2.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/performance-types": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.1.tgz",
+ "integrity": "sha512-kQ8pEr4d6ArhPoYrngcFlEJMNWMdEZTpvMAttWH0C2vegBgj47cm6xXFy9+0j27OBhOIiPn48Z+2WE2XNu33CQ=="
+ },
+ "node_modules/@firebase/remote-config": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.6.tgz",
+ "integrity": "sha512-qtanFS+AX5k/7e/+Azf27Hq4reX28QsUvRcYWyS5cOaRMS9jtll4MK4winWmzX8MdJY637nFzIx43PlMKVnaKw==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/installations": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/remote-config-compat": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.6.tgz",
+ "integrity": "sha512-cFdpmN/rzDhm4pbk0WpOzK9JQ9I1ZhXzhtYbKRBwUag3pG1odEfIORygMDCGQniPpcae/QGXho4srJHfoijKuw==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/logger": "0.4.1",
+ "@firebase/remote-config": "0.4.6",
+ "@firebase/remote-config-types": "0.3.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/remote-config-types": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.1.tgz",
+ "integrity": "sha512-PgmfUugcJAinPLsJlYcBbNZe7KE2omdQw1WCT/z46nKkNVGkuHdVFSq54s3wiFa9BoHmLZ01u4hGXIhm6MdLOw=="
+ },
+ "node_modules/@firebase/storage": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.3.tgz",
+ "integrity": "sha512-JP/rN8fb4CgCo7k/I8OLVgRx5cgExsWOIUQ2O2VQwR6YKItuL375c9v7PDaOfEcFZea/fXtfJJ3Z2NaI9445CQ==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0",
+ "undici": "5.28.3"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/storage-compat": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.6.tgz",
+ "integrity": "sha512-AKv0vwktqdW4SDDDcHSN2ahi1Hpjs8rTM6sE7+yrWpm8cRght/PkqylsFnIe+a/toCNd8WeWaXq/oaXHPvRw1w==",
+ "dependencies": {
+ "@firebase/component": "0.6.6",
+ "@firebase/storage": "0.12.3",
+ "@firebase/storage-types": "0.8.1",
+ "@firebase/util": "1.9.5",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/storage-types": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.1.tgz",
+ "integrity": "sha512-yj0vypPT9UbbfYYwzpXPYchnjWqCADcTbGNawAIebww8rnQYPGbESYTKQdFRPXiLspYPB7xCHTXThmMJuvDcsQ==",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/util": {
+ "version": "1.9.5",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.5.tgz",
+ "integrity": "sha512-PP4pAFISDxsf70l3pEy34Mf3GkkUcVQ3MdKp6aSVb7tcpfUQxnsdV7twDd8EkfB6zZylH6wpUAoangQDmCUMqw==",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/webchannel-wrapper": {
+ "version": "0.10.6",
+ "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.6.tgz",
+ "integrity": "sha512-EnfRJvrnzkHwN3BPMCayCFT5lCqInzg3RdlRsDjDvB1EJli6Usj26T6lJ67BU2UcYXBS5xcp1Wj4+zRzj2NaZg=="
+ },
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.9.14",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.14.tgz",
+ "integrity": "sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw==",
+ "dependencies": {
+ "@grpc/proto-loader": "^0.7.8",
+ "@types/node": ">=12.12.47"
+ },
+ "engines": {
+ "node": "^8.13.0 || >=10.10.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.12",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.12.tgz",
+ "integrity": "sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.4",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz",
@@ -761,6 +1284,60 @@
"sparse-bitfield": "^3.0.3"
}
},
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+ },
"node_modules/@types/node": {
"version": "20.11.22",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.22.tgz",
@@ -1269,6 +1846,17 @@
"resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz",
"integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w=="
},
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
@@ -1286,6 +1874,39 @@
"node": ">= 0.8"
}
},
+ "node_modules/firebase": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.10.0.tgz",
+ "integrity": "sha512-iJxnCKsBTYa4BSv8cscNbwciX42BvwoePTHg7iwWevb+GyVcZFmKi9eSkg/L7Jpu9mvAFv1jdDGbIaG3xRrE+w==",
+ "dependencies": {
+ "@firebase/analytics": "0.10.2",
+ "@firebase/analytics-compat": "0.2.8",
+ "@firebase/app": "0.10.0",
+ "@firebase/app-check": "0.8.3",
+ "@firebase/app-check-compat": "0.3.10",
+ "@firebase/app-compat": "0.2.30",
+ "@firebase/app-types": "0.9.1",
+ "@firebase/auth": "1.7.0",
+ "@firebase/auth-compat": "0.5.5",
+ "@firebase/database": "1.0.4",
+ "@firebase/database-compat": "1.0.4",
+ "@firebase/firestore": "4.5.1",
+ "@firebase/firestore-compat": "0.3.28",
+ "@firebase/functions": "0.11.3",
+ "@firebase/functions-compat": "0.3.9",
+ "@firebase/installations": "0.6.6",
+ "@firebase/installations-compat": "0.2.6",
+ "@firebase/messaging": "0.12.7",
+ "@firebase/messaging-compat": "0.2.7",
+ "@firebase/performance": "0.6.6",
+ "@firebase/performance-compat": "0.2.6",
+ "@firebase/remote-config": "0.4.6",
+ "@firebase/remote-config-compat": "0.2.6",
+ "@firebase/storage": "0.12.3",
+ "@firebase/storage-compat": "0.3.6",
+ "@firebase/util": "1.9.5"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1439,6 +2060,11 @@
"node": ">= 0.8"
}
},
+ "node_modules/http-parser-js": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
+ "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q=="
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -1450,6 +2076,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -1569,6 +2200,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
+ },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -1604,6 +2240,11 @@
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
+ "node_modules/long": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -1866,6 +2507,29 @@
"dev": true,
"peer": true
},
+ "node_modules/protobufjs": {
+ "version": "7.2.6",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
+ "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2198,6 +2862,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/undici": {
+ "version": "5.28.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
+ "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
+ "dependencies": {
+ "@fastify/busboy": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.0"
+ }
+ },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -2266,6 +2941,27 @@
"node": ">=12"
}
},
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/whatwg-url": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
diff --git a/package.json b/package.json
index 5464431..b2fef66 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"dotenv": "^16.4.5",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
+ "firebase": "^10.10.0",
"fs": "^0.0.1-security",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..a1578ec
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Welcome to Firebase Hosting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome
+
Firebase Hosting Setup Complete
+
You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!
+
Open Hosting Documentation
+
+ Firebase SDK Loading…
+
+
+
+
diff --git a/server/index.js b/server/index.js
index 7ea673b..506fea8 100644
--- a/server/index.js
+++ b/server/index.js
@@ -10,6 +10,7 @@ import productRoutes from "./routes/productRoutes.js";
import userRoutes from './routes/userRoutes.js';
import stripeRoute from "./routes/stripeRoute.js";
import orderRoutes from "./routes/orderRoutes.js";
+import ideaRoutes from "./routes/ideaRoutes.js";
import fs from 'fs';
connectToDatabase();
@@ -21,6 +22,7 @@ app.use("/api/products", productRoutes);
app.use("/api/users", userRoutes);
app.use('/api/checkout', stripeRoute);
app.use('/api/orders', orderRoutes);
+app.use('/api/ideas', ideaRoutes)
app.get('/api/config/google', (req, res) => res.send(process.env.GOOGLE_CLIENT_ID));
diff --git a/server/models/Idea.js b/server/models/Idea.js
new file mode 100644
index 0000000..bb93ab6
--- /dev/null
+++ b/server/models/Idea.js
@@ -0,0 +1,40 @@
+import mongoose from "mongoose";
+
+const reviewSchema = new mongoose.Schema(
+ {
+ name: { type: String, required: true },
+ rating: { type: Number, required: true },
+ comment: { type: String, required: true },
+ },
+ { timestamps: true }
+);
+
+const ideaSchema = new mongoose.Schema(
+ {
+ name: {
+ type: String,
+ required: true,
+ },
+ images: {
+ type: Array,
+ required: true,
+ default: [],
+ },
+ subtitle: {
+ type: String,
+ },
+ description: {
+ type: String,
+ },
+ reviews: {
+ type: [reviewSchema],
+ required: true,
+ default: [],
+ },
+ },
+ { timestamps: true }
+);
+
+const Idea = mongoose.model('Idea', ideaSchema);
+
+export default Idea;
\ No newline at end of file
diff --git a/server/routes/ideaRoutes.js b/server/routes/ideaRoutes.js
new file mode 100644
index 0000000..bc95c89
--- /dev/null
+++ b/server/routes/ideaRoutes.js
@@ -0,0 +1,65 @@
+import express from "express";
+import Idea from "../models/Idea.js";
+// import { protectRoute, admin } from "../middleware/authMiddleware.js";
+import asyncHandler from 'express-async-handler';
+// import User from '../models/User.js';
+
+const ideaRoutes = express.Router();
+
+const getIdeas = async (req, res) => {
+ console.log("inside the route")
+ const page = parseInt(req.params.page); // 1, 2 or 3
+ const perPage = parseInt(req.params.perPage); // 10
+ const ideas = await Idea.find({});
+
+ if (page && perPage) {
+ const totalPages = Math.ceil(ideas.length / perPage);
+ const startIndex = (page - 1) * perPage;
+ const endIndex = startIndex + perPage;
+ const paginatedIdeas = ideas.slice(startIndex, endIndex);
+ res.json({ ideas: paginatedIdeas, pagination: { currentPage: page, totalPages } });
+ } else {
+ res.json({ ideas, pagination: {} });
+ }
+};
+
+const getIdea = async (req, res) => {
+ const idea = await Idea.findById(req.params.id);
+
+ if (idea) {
+ res.json(idea);
+ } else {
+ res.status(404).send("Idea not found");
+ throw new Error('Idea not found');
+ }
+};
+
+const createIdeaReview = asyncHandler(async (req, res) => {
+ const { rating, comment, userName } = req.body;
+
+ const idea = await Idea.findById(req.params.id);
+
+ if (idea) {
+ const review = {
+ name: userName,
+ rating: Number(rating),
+ comment,
+ };
+
+ idea.reviews.push(review);
+ await idea.save();
+ res.status(201).json({ message: 'Review has been saved.' });
+ } else {
+ res.status(404).send("Idea not found");
+ throw new Error('Idea not found.');
+ }
+});
+
+
+ideaRoutes.route('/:page/:perPage').get(getIdeas);
+ideaRoutes.route('/').get(getIdeas);
+ideaRoutes.route('/:id').get(getIdea);
+ideaRoutes.route('/reviews/:id').post(createIdeaReview);
+
+
+export default ideaRoutes;
\ No newline at end of file