From d6fd27654f56073993eaa264ef91b2eac1fe9ba8 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 15 Jun 2024 09:11:06 +0530 Subject: [PATCH 01/83] Refactor bar chart && fix totalRevenue --- src/components/BarChart.jsx | 140 +++++++++++++++++++++++---------- src/scenes/dashboard/index.jsx | 2 +- 2 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/components/BarChart.jsx b/src/components/BarChart.jsx index 124e5b6..e9494c2 100644 --- a/src/components/BarChart.jsx +++ b/src/components/BarChart.jsx @@ -1,17 +1,78 @@ -import { useTheme } from "@mui/material"; -import { ResponsiveBar } from "@nivo/bar"; -import { tokens } from "../theme"; -import { mockBarData as data } from "../data/mockData"; +import React, { useEffect, useState } from 'react'; +import { useTheme } from '@mui/material'; +import { ResponsiveBar } from '@nivo/bar'; +import { tokens } from '../theme'; +import { getDatabase, ref, get } from 'firebase/database'; +import { auth } from '../firebase'; + +const fetchData = async () => { + if (!auth.currentUser) { + console.error("User is not authenticated"); + return { salesPerUnit: [], uniqueSellingProducts: [] }; + } + const db = getDatabase(); + const salesPerUnitRef = ref(db, `users/${auth.currentUser.uid}/formData/salesPerUnit`); + const uniqueSellingProductsRef = ref(db, `users/${auth.currentUser.uid}/formData/uniqueSellingProducts`); + + const [salesPerUnitSnapshot, uniqueSellingProductsSnapshot] = await Promise.all([ + get(salesPerUnitRef), + get(uniqueSellingProductsRef), + ]); + + const salesPerUnit = salesPerUnitSnapshot.exists() ? salesPerUnitSnapshot.val() : []; + const uniqueSellingProducts = uniqueSellingProductsSnapshot.exists() ? uniqueSellingProductsSnapshot.val() : []; + + return { salesPerUnit, uniqueSellingProducts }; +}; + +// Function to generate unique colors for each product +const generateColors = (products) => { + const colors = {}; + products.forEach((product, index) => { + const hue = Math.floor((index / products.length) * 360); + colors[product] = `hsl(${hue}, 70%, 50%)`; + }); + return colors; +}; + +const transformData = (salesPerUnit, uniqueSellingProducts) => { + // Get unique products + const uniqueProducts = uniqueSellingProducts.map(item => item.product); + const productColors = generateColors(uniqueProducts); + + const data = salesPerUnit.map((sale) => { + const product = uniqueSellingProducts.find((item) => item.country === sale.country)?.product || 'Unknown Product'; + return { + country: sale.country, + [product]: sale.unitSales, + [`${product}Color`]: productColors[product] || productColors['Unknown Product'], + }; + }); + console.log('Sales Per Unit:', salesPerUnit); + console.log('Unique Selling Products:', uniqueSellingProducts); + console.log('Transformed Data:', data); + return data; +}; const BarChart = ({ isDashboard = false }) => { const theme = useTheme(); const colors = tokens(theme.palette.mode); + const [data, setData] = useState([]); + + useEffect(() => { + const fetchAndTransformData = async () => { + const { salesPerUnit, uniqueSellingProducts } = await fetchData(); + const transformedData = transformData(salesPerUnit, uniqueSellingProducts); + setData(transformedData); + }; + + fetchAndTransformData(); + }, []); return ( { }, }, }} - keys={["hot dog", "burger", "sandwich", "kebab", "fries", "donut"]} + keys={data.length > 0 ? Object.keys(data[0]).filter(key => key !== 'country' && !key.endsWith('Color')) : []} indexBy="country" margin={{ top: 50, right: 130, bottom: 50, left: 60 }} padding={0.3} - valueScale={{ type: "linear" }} - indexScale={{ type: "band", round: true }} - colors={{ scheme: "nivo" }} - defs={[ - { - id: "dots", - type: "patternDots", - background: "inherit", - color: "#38bcb2", - size: 4, - padding: 1, - stagger: true, - }, - { - id: "lines", - type: "patternLines", - background: "inherit", - color: "#eed312", - rotation: -45, - lineWidth: 6, - spacing: 10, - }, - ]} + valueScale={{ type: 'linear' }} + indexScale={{ type: 'band', round: true }} + colors={({ id, data }) => data[`${id}Color`]} borderColor={{ - from: "color", - modifiers: [["darker", "1.6"]], + from: 'color', + modifiers: [['darker', '1.6']], }} axisTop={null} axisRight={null} @@ -76,42 +117,42 @@ const BarChart = ({ isDashboard = false }) => { tickSize: 5, tickPadding: 5, tickRotation: 0, - legend: isDashboard ? undefined : "country", // changed - legendPosition: "middle", + legend: isDashboard ? undefined : 'country', + legendPosition: 'middle', legendOffset: 32, }} axisLeft={{ tickSize: 5, tickPadding: 5, tickRotation: 0, - legend: isDashboard ? undefined : "food", // changed - legendPosition: "middle", + legend: isDashboard ? undefined : 'units', + legendPosition: 'middle', legendOffset: -40, }} enableLabel={false} labelSkipWidth={12} labelSkipHeight={12} labelTextColor={{ - from: "color", - modifiers: [["darker", 1.6]], + from: 'color', + modifiers: [['darker', 1.6]], }} legends={[ { - dataFrom: "keys", - anchor: "bottom-right", - direction: "column", + dataFrom: 'keys', + anchor: 'bottom-right', + direction: 'column', justify: false, translateX: 120, translateY: 0, itemsSpacing: 2, itemWidth: 100, itemHeight: 20, - itemDirection: "left-to-right", + itemDirection: 'left-to-right', itemOpacity: 0.85, symbolSize: 20, effects: [ { - on: "hover", + on: 'hover', style: { itemOpacity: 1, }, @@ -119,9 +160,22 @@ const BarChart = ({ isDashboard = false }) => { ], }, ]} + tooltip={({ id, value, indexValue }) => ( +
+ + {indexValue}: {id} ({value}) + +
+ )} role="application" barAriaLabel={function (e) { - return e.id + ": " + e.formattedValue + " in country: " + e.indexValue; + return e.id + ': ' + e.formattedValue + ' in country: ' + e.indexValue; }} /> ); diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 7269aed..c203e47 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -182,7 +182,7 @@ const Dashboard = () => { fontWeight="bold" color={colors.greenAccent[500]} > - ${totalRevenue.toFixed(2)} + ${totalRevenue} From 9197bcf64d5da6e90659f6daa97565ffbbca8a9a Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 15 Jun 2024 15:27:00 +0530 Subject: [PATCH 02/83] chore: Update Firebase dependencies and fetch sales per unit data for current user --- src/scenes/geography/index.jsx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/scenes/geography/index.jsx b/src/scenes/geography/index.jsx index 947a019..8ba4449 100644 --- a/src/scenes/geography/index.jsx +++ b/src/scenes/geography/index.jsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import GeographyChart from "../../components/GeographyChart"; import Header from "../../components/Header"; import { tokens } from "../../theme"; -import { database } from "../../firebase"; // Import Firebase database service +import { database, auth } from "../../firebase"; import {ref, get } from 'firebase/database'; const Geography = () => { @@ -15,24 +15,21 @@ const Geography = () => { useEffect(() => { const fetchLocations = async () => { try { - // Fetch locations data from Firebase Realtime Database - const snapshot = await get(ref(database, "users")); - const users = snapshot.val(); - const allLocations = []; - // Extract locations from user data - if (users) { - Object.values(users).forEach((user) => { - if (user.formData && user.formData.salesPerUnit) { - user.formData.salesPerUnit.forEach((location) => { - allLocations.push(location); - }); - } - }); + if (!auth.currentUser) { + console.error("User is not authenticated"); + return; } + // Fetch locations data from Firebase Realtime Database for current user + const db = database; + const dataRef = ref(db, `users/${auth.currentUser.uid}/formData/salesPerUnit`); + const snapshot = await get(dataRef); - // Set the locations state and mark loading as false - setLocations(allLocations); + if (snapshot.exists()) { + const firebaseData = snapshot.val(); + // console.log(firebaseData) + setLocations(firebaseData); + } setLoading(false); } catch (error) { console.error("Error fetching locations:", error); From a8285a812b3d77470dd70197cef5580bd51d2e4f Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 15 Jun 2024 23:51:35 +0530 Subject: [PATCH 03/83] chore: Update GeographyChart component to accept isDashboard prop and fetch location data --- src/components/GeographyChart.jsx | 25 +++++++++++++++---------- src/scenes/dashboard/index.jsx | 29 +++++++++++++++++++++++++++-- src/scenes/geography/index.jsx | 11 ++++++----- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/components/GeographyChart.jsx b/src/components/GeographyChart.jsx index 41742b3..7cb00b8 100644 --- a/src/components/GeographyChart.jsx +++ b/src/components/GeographyChart.jsx @@ -1,12 +1,7 @@ -import { useTheme } from "@mui/material"; import { ResponsiveChoropleth } from "@nivo/geo"; import { geoFeatures } from "../data/mockGeoFeatures"; -import { tokens } from "../theme"; -const GeographyChart = ({ locationData }) => { - // console.log(locationData) - const theme = useTheme(); - const colors = tokens(theme.palette.mode); +const GeographyChart = ({ locationData, isDashboard }) => { if (!locationData) { return null; // or you can return a placeholder or loading indicator } @@ -16,7 +11,8 @@ const GeographyChart = ({ locationData }) => { value: data.unitSales, })); - // console.log(data); + // Define the initial zoom level based on the `isDashboard` prop + const initialZoom = isDashboard ? 1 : 2; return ( { valueFormat=".2s" projectionTranslation={[ 0.5, 0.5 ]} projectionRotation={[ 0, 0, 0 ]} + projectionScale={isDashboard ? 50 : 150} borderWidth={0.5} borderColor="#152538" - legends={[ + isInteractive={true} + enableZoom={true} + legends={ + !isDashboard ? [ { anchor: 'bottom-left', direction: 'column', justify: true, translateX: 20, - translateY: -100, + translateY: 0, itemsSpacing: 0, itemWidth: 94, itemHeight: 18, @@ -56,7 +56,8 @@ const GeographyChart = ({ locationData }) => { } ] } - ]} + ] : [] + } theme={{ tooltip: { container: { @@ -64,6 +65,10 @@ const GeographyChart = ({ locationData }) => { }, }, }} + // Control zoom behavior with the initial zoom level + zoom={initialZoom} + maxZoom={8} + minZoom={0.5} /> ); }; diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index c203e47..ac27c17 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -12,7 +12,7 @@ import GeographyChart from "../../components/GeographyChart"; import BarChart from "../../components/BarChart"; import StatBox from "../../components/StatBox"; import ProgressCircle from "../../components/ProgressCircle"; -import { auth } from "../../firebase"; +import { auth, database } from "../../firebase"; import { useEffect, useState } from "react"; import { getDatabase, get, ref } from "firebase/database"; @@ -20,6 +20,7 @@ const Dashboard = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [totalRevenue, setTotalRevenue] = useState(0); + const [location, setLocations] = useState([]); useEffect(() => { const fetchTotalRevenue = async () => { @@ -46,7 +47,31 @@ const Dashboard = () => { } }; + const fetchLocations = async () => { + try { + if (!auth.currentUser) { + console.error("User is not authenticated"); + return; + } + // Fetch locations data from Firebase Realtime Database for current user + const db = database; + const dataRef = ref( + db, + `users/${auth.currentUser.uid}/formData/salesPerUnit` + ); + const snapshot = await get(dataRef); + + if (snapshot.exists()) { + const firebaseData = snapshot.val(); + setLocations(firebaseData); + } + } catch (error) { + console.error("Error fetching locations:", error); + } + }; + fetchTotalRevenue(); + fetchLocations(); }, []); return ( @@ -305,7 +330,7 @@ const Dashboard = () => { Geography Based Traffic - + diff --git a/src/scenes/geography/index.jsx b/src/scenes/geography/index.jsx index 8ba4449..ed45bd7 100644 --- a/src/scenes/geography/index.jsx +++ b/src/scenes/geography/index.jsx @@ -4,7 +4,7 @@ import GeographyChart from "../../components/GeographyChart"; import Header from "../../components/Header"; import { tokens } from "../../theme"; import { database, auth } from "../../firebase"; -import {ref, get } from 'firebase/database'; +import { ref, get } from "firebase/database"; const Geography = () => { const theme = useTheme(); @@ -15,19 +15,20 @@ const Geography = () => { useEffect(() => { const fetchLocations = async () => { try { - if (!auth.currentUser) { console.error("User is not authenticated"); return; } // Fetch locations data from Firebase Realtime Database for current user const db = database; - const dataRef = ref(db, `users/${auth.currentUser.uid}/formData/salesPerUnit`); + const dataRef = ref( + db, + `users/${auth.currentUser.uid}/formData/salesPerUnit` + ); const snapshot = await get(dataRef); if (snapshot.exists()) { const firebaseData = snapshot.val(); - // console.log(firebaseData) setLocations(firebaseData); } setLoading(false); @@ -62,7 +63,7 @@ const Geography = () => { border={`1px solid ${colors.grey[100]}`} borderRadius="4px" > - + ) : ( From 347d5be5dc73d8690f638af6582cf632b96d1518 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sun, 16 Jun 2024 00:14:11 +0530 Subject: [PATCH 04/83] chore: Update GeographyChart component to accept isDashboard prop and improve zoom and panning functionality --- src/components/GeographyChart.jsx | 113 +++++++++++++++++++----------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/src/components/GeographyChart.jsx b/src/components/GeographyChart.jsx index 7cb00b8..0fa7f52 100644 --- a/src/components/GeographyChart.jsx +++ b/src/components/GeographyChart.jsx @@ -1,75 +1,108 @@ +import { useState, useEffect } from "react"; import { ResponsiveChoropleth } from "@nivo/geo"; import { geoFeatures } from "../data/mockGeoFeatures"; const GeographyChart = ({ locationData, isDashboard }) => { + // State for managing zoom level and projection translation + const [zoom, setZoom] = useState(isDashboard ? 0.5 : 2); + const [translation, setTranslation] = useState([0.5, 0.5]); + + useEffect(() => { + // Set initial values based on isDashboard prop + setZoom(isDashboard ? 0.45 : 1); + setTranslation([0.5, 0.6]); + }, [isDashboard]); + if (!locationData) { return null; // or you can return a placeholder or loading indicator } const data = locationData.map((data, index) => ({ - id: data.country, + id: data.country, value: data.unitSales, })); - // Define the initial zoom level based on the `isDashboard` prop - const initialZoom = isDashboard ? 1 : 2; + // Handlers for zoom in and zoom out + const handleZoomIn = () => { + setZoom((prevZoom) => Math.min(prevZoom * 1.2, 8)); + }; + + const handleZoomOut = () => { + setZoom((prevZoom) => Math.max(prevZoom / 1.5, 0.4)); + }; + + // Handlers for panning + const handlePan = (dx, dy) => { + setTranslation((prevTranslation) => [ + Math.min(Math.max(prevTranslation[0] + dx, 0), 1), + Math.min(Math.max(prevTranslation[1] + dy, 0), 1), + ]); + }; return ( - + + /> +
+ + +
+
+ + + + +
+ ); }; From 41d9e7d72debaf713f17237fdc978c8b102ef423 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 22 Jun 2024 22:48:32 +0530 Subject: [PATCH 05/83] chore: Update Login component to fetch and record user activity on sign-in --- src/scenes/login/index.jsx | 48 ++++++++++++++--- src/scenes/team/index.jsx | 108 ++++++++++++++++++++++++++++++------- 2 files changed, 131 insertions(+), 25 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 6230d91..44d74df 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,10 +1,18 @@ import { useState } from "react"; -import { Box, Button, TextField, Typography, useTheme, ToggleButton, ToggleButtonGroup } from "@mui/material"; +import { + Box, + Button, + TextField, + Typography, + useTheme, + ToggleButton, + ToggleButtonGroup, +} from "@mui/material"; import { tokens } from "../../theme"; import { Link } from "react-router-dom"; import { auth, database } from "../../firebase"; // Import Firebase services import { signInWithEmailAndPassword } from "firebase/auth"; -import { ref, get } from "firebase/database"; +import { ref, get, set, push } from "firebase/database"; // Import necessary database functions const Login = ({ handleLoginSuccess }) => { const theme = useTheme(); @@ -16,14 +24,38 @@ const Login = ({ handleLoginSuccess }) => { const handleLogin = async () => { try { - const userCredential = await signInWithEmailAndPassword(auth, email, password); + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); const user = userCredential.user; // Check user role from the database - const userRoleRef = ref(database, 'users/' + user.uid + '/role'); + const userRoleRef = ref(database, "users/" + user.uid + "/role"); const snapshot = await get(userRoleRef); - + if (snapshot.exists() && snapshot.val() === role) { + // Get user name + const userNameRef = ref(database, "users/" + user.uid + "/name"); + const userNameSnapshot = await get(userNameRef); + const userName = userNameSnapshot.exists() + ? userNameSnapshot.val() + : "Unknown User"; + + // Record the sign-in activity + if (role === "user") { + const userActivityRef = ref(database, "userActivity"); + const newActivityRef = push(userActivityRef); + await set(newActivityRef, { + uid: user.uid, + name: userName, + email: user.email, + signInTime: new Date().toISOString(), + signOutTime: null, + }); + } + handleLoginSuccess(role); } else { setError("Invalid credentials or role"); @@ -102,11 +134,13 @@ const Login = ({ handleLoginSuccess }) => { Login - Don't have an account? Create one + Don't have an account?{" "} + + Create one + ); }; export default Login; - diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 86d73da..ee6dbcb 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -1,17 +1,20 @@ import { Box, Typography, useTheme } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { tokens } from "../../theme"; -import { mockDataTeam } from "../../data/mockData"; import AdminPanelSettingsOutlinedIcon from "@mui/icons-material/AdminPanelSettingsOutlined"; import LockOpenOutlinedIcon from "@mui/icons-material/LockOpenOutlined"; import SecurityOutlinedIcon from "@mui/icons-material/SecurityOutlined"; import Header from "../../components/Header"; +import { useEffect, useState } from "react"; +import { database } from "../../firebase"; +import { off, onChildAdded, onChildChanged, onChildRemoved, ref, get, remove } from "firebase/database"; const Team = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); + const [userData, setUserData] = useState([]); const columns = [ - { field: "id", headerName: "ID" }, + { field: "id", headerName: "Sl No." }, { field: "name", headerName: "Name", @@ -19,27 +22,29 @@ const Team = () => { cellClassName: "name-column--cell", }, { - field: "age", - headerName: "Age", - type: "number", + field: "email", + headerName: "Email", + type: "text", headerAlign: "left", align: "left", + flex: 1, }, { - field: "phone", - headerName: "Phone Number", + field: "signInTime", + headerName: "Login Time", flex: 1, }, { - field: "email", - headerName: "Email", + field: "signOutTime", + headerName: "Logout Time", flex: 1, + valueGetter: (params) => params.row.signOutTime || "-", }, { - field: "accessLevel", + field: "role", headerName: "Access Level", flex: 1, - renderCell: ({ row: { access } }) => { + renderCell: ({ row: { role } }) => { return ( { display="flex" justifyContent="center" backgroundColor={ - access === "admin" + role === "admin" ? colors.greenAccent[600] - : access === "manager" + : role === "manager" ? colors.greenAccent[700] : colors.greenAccent[700] } borderRadius="4px" > - {access === "admin" && } - {access === "manager" && } - {access === "user" && } + {role === "admin" && } + {role === "manager" && } + {role === "user" && } - {access} + {role} ); @@ -68,6 +73,73 @@ const Team = () => { }, ]; + useEffect(() => { + const userActivityRef = ref(database, "userActivity"); + + // Function to add or update user data + const handleChildAddedOrChanged = (snapshot) => { + const data = snapshot.val(); + setUserData((prevUserData) => { + const existingIndex = prevUserData.findIndex((item) => item.id === snapshot.key); + if (existingIndex !== -1) { + const updatedUserData = [...prevUserData]; + updatedUserData[existingIndex] = { id: snapshot.key, ...data }; + return updatedUserData; + } else { + return [...prevUserData, { id: snapshot.key, ...data }]; + } + }); + }; + + // Function to remove user data + const handleChildRemoved = (snapshot) => { + setUserData((prevUserData) => prevUserData.filter((item) => item.id !== snapshot.key)); + }; + + // Attach listeners + const childAddedListener = onChildAdded(userActivityRef, handleChildAddedOrChanged); + const childChangedListener = onChildChanged(userActivityRef, handleChildAddedOrChanged); + const childRemovedListener = onChildRemoved(userActivityRef, handleChildRemoved); + + // Cleanup listeners on unmount + return () => { + off(userActivityRef, 'child_added', childAddedListener); + off(userActivityRef, 'child_changed', childChangedListener); + off(userActivityRef, 'child_removed', childRemovedListener); + }; + }, []); + + useEffect(() => { + // Function to check and remove old user activities + const cleanupOldActivities = async () => { + const userActivityRef = ref(database, "userActivity"); + const snapshot = await get(userActivityRef); + + if (snapshot.exists()) { + const data = snapshot.val(); + const now = new Date().getTime(); + const oneDay = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + + for (let key in data) { + const activity = data[key]; + const signInTime = new Date(activity.signInTime).getTime(); + + if (now - signInTime > oneDay) { + const activityRef = ref(database, `userActivity/${key}`); + await remove(activityRef); + } + } + } + }; + + // Run the cleanup function immediately and set interval for 24 hours + cleanupOldActivities(); + const interval = setInterval(cleanupOldActivities, 24 * 60 * 60 * 1000); + + // Clear interval on component unmount + return () => clearInterval(interval); + }, []); + return (
@@ -100,7 +172,7 @@ const Team = () => { }, }} > - + ); From c3a4634c7a4eeeea77bf0859d268f1508c5d4859 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 22 Jun 2024 22:57:48 +0530 Subject: [PATCH 06/83] chore: Add role property to user object on login --- src/scenes/login/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 44d74df..c15a8ae 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -51,6 +51,7 @@ const Login = ({ handleLoginSuccess }) => { uid: user.uid, name: userName, email: user.email, + role: role, signInTime: new Date().toISOString(), signOutTime: null, }); From 79e2df06e632485023fd581259dc748c74e09e67 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Mon, 24 Jun 2024 22:41:42 +0530 Subject: [PATCH 07/83] password sent to db --- src/scenes/signup/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenes/signup/index.jsx b/src/scenes/signup/index.jsx index 808f71e..dc96c72 100644 --- a/src/scenes/signup/index.jsx +++ b/src/scenes/signup/index.jsx @@ -24,8 +24,8 @@ const Signup = ({ handleSignupSuccess }) => { await set(ref(database, 'users/' + user.uid), { email: user.email, name: name, - role: role - + role: role, + password: password, }); handleSignupSuccess(); From ac632d956bbd42b603bc411877f1b295b42035d0 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Mon, 24 Jun 2024 23:29:10 +0530 Subject: [PATCH 08/83] chore: Update Login component to fetch and record user activity on sign-in --- src/components/GeographyChart.jsx | 17 ++++--- src/scenes/AdminList/index.jsx | 81 +++++++++++++++++++++++++++---- src/scenes/login/index.jsx | 12 ++++- src/scenes/team/index.jsx | 8 +-- 4 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/components/GeographyChart.jsx b/src/components/GeographyChart.jsx index 0fa7f52..e4d639f 100644 --- a/src/components/GeographyChart.jsx +++ b/src/components/GeographyChart.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from "react"; import { ResponsiveChoropleth } from "@nivo/geo"; import { geoFeatures } from "../data/mockGeoFeatures"; +import { Button } from "@mui/material"; const GeographyChart = ({ locationData, isDashboard }) => { // State for managing zoom level and projection translation @@ -92,15 +93,15 @@ const GeographyChart = ({ locationData, isDashboard }) => { }, }} /> -
- - +
+ +
-
- - - - +
+ + + +
); diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 7eac8a6..759bae6 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -3,7 +3,7 @@ import { Box, Button, IconButton } from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { useTheme } from "@mui/material"; import { tokens } from "../../theme"; -import { ref, get, remove } from "firebase/database"; +import { ref, get, remove, onChildAdded, onChildChanged, onChildRemoved, off } from "firebase/database"; import { auth, database } from "../../firebase"; import Header from "../../components/Header"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -19,28 +19,91 @@ const AdminList = () => { useEffect(() => { const fetchAdmins = async () => { - const adminsRef = ref(database, "users"); + const adminsRef = ref(database, "adminActivity"); const snapshot = await get(adminsRef); if (snapshot.exists()) { const users = snapshot.val(); - const adminList = Object.keys(users) - .filter(key => users[key].role === "admin") // Exclude super admins - .map(key => ({ id: key, ...users[key] })); + const adminList = Object.keys(users).map(key => ({ id: key, ...users[key] })); setAdmins(adminList); // Get the current user's ID and role const currentUserId = auth.currentUser.uid; - if (users[currentUserId]) { - setCurrentUserRole(users[currentUserId].role); + const usersRef = ref(database, "users"); + const usersSnapshot = await get(usersRef); + if (usersSnapshot.exists()) { + const usersData = usersSnapshot.val(); + if (usersData[currentUserId]) { + setCurrentUserRole(usersData[currentUserId].role); + } } } }; fetchAdmins(); }, []); + useEffect(() => { + const adminActivityRef = ref(database, "adminActivity"); + + const handleChildAddedOrChanged = (snapshot) => { + const data = snapshot.val(); + setAdmins((prevAdmins) => { + const existingIndex = prevAdmins.findIndex((item) => item.id === snapshot.key); + if (existingIndex !== -1) { + const updatedAdmins = [...prevAdmins]; + updatedAdmins[existingIndex] = { id: snapshot.key, ...data }; + return updatedAdmins; + } else { + return [...prevAdmins, { id: snapshot.key, ...data }]; + } + }); + }; + + const handleChildRemoved = (snapshot) => { + setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key)); + }; + + const childAddedListener = onChildAdded(adminActivityRef, handleChildAddedOrChanged); + const childChangedListener = onChildChanged(adminActivityRef, handleChildAddedOrChanged); + const childRemovedListener = onChildRemoved(adminActivityRef, handleChildRemoved); + + return () => { + off(adminActivityRef, 'child_added', childAddedListener); + off(adminActivityRef, 'child_changed', childChangedListener); + off(adminActivityRef, 'child_removed', childRemovedListener); + }; + }, []); + + useEffect(() => { + const cleanupOldActivities = async () => { + const adminActivityRef = ref(database, "adminActivity"); + const snapshot = await get(adminActivityRef); + + if (snapshot.exists()) { + const data = snapshot.val(); + const now = new Date().getTime(); + const oneDay = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + + for (let key in data) { + const activity = data[key]; + const signInTime = new Date(activity.signInTime).getTime(); + + if (now - signInTime > oneDay) { + const activityRef = ref(database, `adminActivity/${key}`); + await remove(activityRef); + } + } + } + }; + + cleanupOldActivities(); + const interval = setInterval(cleanupOldActivities, 24 * 60 * 60 * 1000); + + return () => clearInterval(interval); + }, []); + const handleDelete = async (id) => { try { - await remove(ref(database, `users/${id}`)); + await remove(ref(database, `adminActivity/${id}`)); setAdmins(admins.filter((admin) => admin.id !== id)); } catch (error) { console.error("Error deleting admin:", error); @@ -48,7 +111,6 @@ const AdminList = () => { }; const handleChat = (id) => { - // Navigate to the chat thread for the selected admin navigate(`/chat/${id}`); }; @@ -56,6 +118,7 @@ const AdminList = () => { { field: "id", headerName: "ID", flex: 0.5 }, { field: "name", headerName: "Name", flex: 1 }, { field: "email", headerName: "Email", flex: 1 }, + { field: "signInTime", headerName: "Login Time", flex: 1 }, { field: "actions", headerName: "Actions", diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index c15a8ae..fb2e616 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -53,7 +53,17 @@ const Login = ({ handleLoginSuccess }) => { email: user.email, role: role, signInTime: new Date().toISOString(), - signOutTime: null, + }); + } + + if(role === "admin") { + const adminActivityRef = ref(database, "adminActivity"); + const newActivityRef = push(adminActivityRef); + await set(newActivityRef, { + uid: user.uid, + name: userName, + email: user.email, + signInTime: new Date().toISOString(), }); } diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index ee6dbcb..ccc8749 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -14,7 +14,7 @@ const Team = () => { const colors = tokens(theme.palette.mode); const [userData, setUserData] = useState([]); const columns = [ - { field: "id", headerName: "Sl No." }, + { field: "id", headerName: "UID" , flex: 1}, { field: "name", headerName: "Name", @@ -34,12 +34,6 @@ const Team = () => { headerName: "Login Time", flex: 1, }, - { - field: "signOutTime", - headerName: "Logout Time", - flex: 1, - valueGetter: (params) => params.row.signOutTime || "-", - }, { field: "role", headerName: "Access Level", From c9de6424c82523a204862981cc802575a6f03651 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Mon, 24 Jun 2024 23:53:28 +0530 Subject: [PATCH 09/83] feat added download feature --- package-lock.json | 154 +++++++++++++++++++++++++++++++++ package.json | 2 + src/scenes/dashboard/index.jsx | 48 +++++++++- 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 36bb295..f325af0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,8 @@ "chart.js": "^3.9.1", "firebase": "^10.12.2", "formik": "^2.2.9", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", @@ -6007,6 +6009,12 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -6954,6 +6962,17 @@ "node": ">= 4.0.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -7305,6 +7324,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7482,6 +7509,17 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7590,6 +7628,31 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -8051,6 +8114,14 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -8940,6 +9011,12 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.5.tgz", + "integrity": "sha512-FgbqnEPiv5Vdtwt6Mxl7XSylttCC03cqP5ldNT2z+Kj0nLxPHJH4+1Cyf5Jasxhw93Rl4Oo11qRoUV72fmya2Q==", + "optional": true + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -10211,6 +10288,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -11213,6 +11295,18 @@ } } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -14204,6 +14298,23 @@ "node": ">=0.10.0" } }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -17596,6 +17707,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -18237,6 +18357,15 @@ "node": ">=8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -18734,6 +18863,15 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -18982,6 +19120,14 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -19485,6 +19631,14 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index b866a53..cf71712 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "chart.js": "^3.9.1", "firebase": "^10.12.2", "formik": "^2.2.9", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index ac27c17..8fc7852 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -15,6 +15,8 @@ import ProgressCircle from "../../components/ProgressCircle"; import { auth, database } from "../../firebase"; import { useEffect, useState } from "react"; import { getDatabase, get, ref } from "firebase/database"; +import jsPDF from "jspdf"; +import html2canvas from "html2canvas"; const Dashboard = () => { const theme = useTheme(); @@ -74,8 +76,48 @@ const Dashboard = () => { fetchLocations(); }, []); + const handleDownload = async () => { + const doc = new jsPDF("p", "pt", "a4"); + + // Capture the chart elements + const lineChartElement = document.getElementById("line-chart"); + const barChartElement = document.getElementById("bar-chart"); + const geoChartElement = document.getElementById("geo-chart"); + + const lineChartCanvas = await html2canvas(lineChartElement); + const barChartCanvas = await html2canvas(barChartElement); + const geoChartCanvas = await html2canvas(geoChartElement); + + const lineChartImg = lineChartCanvas.toDataURL("image/png"); + const barChartImg = barChartCanvas.toDataURL("image/png"); + const geoChartImg = geoChartCanvas.toDataURL("image/png"); + + // Add content to PDF + doc.setFontSize(18); + doc.text("Dashboard Report", 20, 30); + + doc.setFontSize(14); + doc.text("Revenue Generated", 20, 60); + doc.addImage(lineChartImg, "PNG", 20, 70, 555.28, 150); + + doc.text("Sales Quantity", 20, 240); + doc.addImage(barChartImg, "PNG", 20, 250, 555.28, 150); + + doc.text("Geography Based Traffic", 20, 420); + doc.addImage(geoChartImg, "PNG", 20, 430, 555.28, 150); + + // Add stats + doc.text("Statistics", 20, 600); + doc.setFontSize(12); + doc.text(`Total Revenue: $${totalRevenue}`, 20, 620); + doc.text(`Total Locations: ${Object.keys(location).length}`, 20, 640); + + // Save the PDF + doc.save("dashboard-report.pdf"); + }; + return ( - + {/* HEADER */}
@@ -89,6 +131,7 @@ const Dashboard = () => { fontWeight: "bold", padding: "10px 20px", }} + onClick={handleDownload} > Download Reports @@ -186,6 +229,7 @@ const Dashboard = () => { gridColumn="span 8" gridRow="span 2" backgroundColor={colors.primary[400]} + id="line-chart" > { gridColumn="span 4" gridRow="span 2" backgroundColor={colors.primary[400]} + id="bar-chart" > { gridRow="span 2" backgroundColor={colors.primary[400]} padding="30px" + id="geo-chart" > Date: Tue, 25 Jun 2024 00:17:16 +0530 Subject: [PATCH 10/83] feedback form updated --- src/App.js | 3 +- src/scenes/feedback/index.jsx | 175 ++++++++++++++++++++++++++++++++++ src/scenes/global/Sidebar.jsx | 34 +++++-- 3 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 src/scenes/feedback/index.jsx diff --git a/src/App.js b/src/App.js index c01f87c..d019208 100644 --- a/src/App.js +++ b/src/App.js @@ -18,6 +18,7 @@ import Calendar from "./scenes/calendar/calendar"; import Login from "./scenes/login/index"; import Signup from "./scenes/signup"; import AdminList from "./scenes/AdminList"; +import Feedback from "./scenes/feedback"; function App() { const [theme, colorMode] = useMode(); @@ -60,7 +61,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx new file mode 100644 index 0000000..12de0a2 --- /dev/null +++ b/src/scenes/feedback/index.jsx @@ -0,0 +1,175 @@ +import { Box, Button, TextareaAutosize, Typography } from "@mui/material"; +import { DataGrid, GridToolbar } from "@mui/x-data-grid"; +import { useTheme } from "@mui/material"; +import { tokens } from "../../theme"; +import Header from "../../components/Header"; +import { getAuth } from "firebase/auth"; +import { get, ref, set, onValue } from "firebase/database"; +import { useEffect, useState } from "react"; +import { database } from "../../firebase"; + +const Feedback = () => { + const theme = useTheme(); + const colors = tokens(theme.palette.mode); + const [role, setRole] = useState(null); + const [feedback, setFeedback] = useState(""); + const [allFeedbacks, setAllFeedbacks] = useState([]); + + useEffect(() => { + const fetchUserRole = async () => { + const auth = getAuth(); + const user = auth.currentUser; + if (user) { + const userRef = ref(database, `users/${user.uid}`); + const snapshot = await get(userRef); + if (snapshot.exists()) { + const userData = snapshot.val(); + setRole(userData.role); + } + } + }; + + const fetchFeedbacks = () => { + const feedbackRef = ref(database, 'feedback'); + onValue(feedbackRef, (snapshot) => { + const data = snapshot.val(); + if (data) { + const feedbackList = Object.keys(data).map(key => ({ + id: key, + ...data[key], + })); + setAllFeedbacks(feedbackList); + } + }); + }; + + fetchUserRole(); + fetchFeedbacks(); + }, []); + + const handleSubmit = async () => { + const auth = getAuth(); + const user = auth.currentUser; + if (user) { + const userRef = ref(database, `users/${user.uid}`); + const snapshot = await get(userRef); + let userName = "Anonymous"; + if (snapshot.exists()) { + const userData = snapshot.val(); + userName = userData.name || "Anonymous"; + } + + const feedbackRef = ref(database, `feedback/${user.uid}`); + await set(feedbackRef, { + name: userName, + email: user.email, + role: role, + feedback: feedback, + }); + setFeedback(""); + } + }; + + const columns = [ + { field: "name", headerName: "Name", flex: 1 }, + { field: "email", headerName: "Email", flex: 1 }, + { field: "role", headerName: "Role", flex: 1 }, + { field: "feedback", headerName: "Feedback", flex: 2 }, + ]; + + return ( + +
+ {role === "admin" ? ( + + setFeedback(e.target.value)} + style={{ + width: "70%", + padding: "10px", + borderRadius: "4px", + backgroundColor: "transparent", + border: `1px solid ${colors.grey[100]}`, + color: colors.grey[100], + outline: "none", + transition: "border-color 0.3s", + }} + onFocus={(e) => e.target.style.borderColor = "cyan"} + onBlur={(e) => e.target.style.borderColor = colors.grey[100]} + /> + + + ) : role === "superadmin" ? ( + + + + ) : ( + + Loading... + + )} + + ); +}; + +export default Feedback; diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index 3bcffdf..43a9152 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ProSidebar, Menu, MenuItem } from "react-pro-sidebar"; import { Box, IconButton, Typography, useTheme } from "@mui/material"; import { Link } from "react-router-dom"; @@ -16,6 +16,8 @@ import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutl import TimelineOutlinedIcon from "@mui/icons-material/TimelineOutlined"; import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined"; import MapOutlinedIcon from "@mui/icons-material/MapOutlined"; +import { auth } from "../../firebase"; +import { get, getDatabase, ref } from "firebase/database"; const Item = ({ title, to, icon, selected, setSelected }) => { const theme = useTheme(); @@ -40,6 +42,26 @@ const Sidebar = () => { const colors = tokens(theme.palette.mode); const [isCollapsed, setIsCollapsed] = useState(false); const [selected, setSelected] = useState("Dashboard"); + const [role, setRole] = useState("user"); + const [name, setName] = useState(""); + + const user = auth.currentUser; + const db = getDatabase(); + + useEffect(() => { + if (user) { + const fetchUserInfo = async () => { + const userRef = ref(db, `users/${user.uid}`); + const snapshot = await get(userRef); + if (snapshot.exists()) { + const userData = snapshot.val(); + setRole(userData.role); + setName(userData.name); + } + }; + fetchUserInfo(); + } + }, [user, db]); return ( { ml="15px" > - ADMINIS + {} setIsCollapsed(!isCollapsed)}> @@ -107,10 +129,10 @@ const Sidebar = () => { fontWeight="bold" sx={{ m: "10px 0 0 0" }} > - Ed Roh + {name} - VP Fancy Admin + {role} @@ -147,8 +169,8 @@ const Sidebar = () => { setSelected={setSelected} /> } selected={selected} setSelected={setSelected} From 35371bdde7c495a78c4c346a76c40d17947b187b Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 25 Jun 2024 09:43:38 +0530 Subject: [PATCH 11/83] removed toggle button from login page --- src/scenes/login/index.jsx | 59 +++++++------------------------------- 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index fb2e616..217a8ed 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,13 +1,5 @@ import { useState } from "react"; -import { - Box, - Button, - TextField, - Typography, - useTheme, - ToggleButton, - ToggleButtonGroup, -} from "@mui/material"; +import { Box, Button, TextField, Typography, useTheme } from "@mui/material"; import { tokens } from "../../theme"; import { Link } from "react-router-dom"; import { auth, database } from "../../firebase"; // Import Firebase services @@ -19,7 +11,6 @@ const Login = ({ handleLoginSuccess }) => { const colors = tokens(theme.palette.mode); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const [role, setRole] = useState("user"); const [error, setError] = useState(""); const handleLogin = async () => { @@ -35,7 +26,9 @@ const Login = ({ handleLoginSuccess }) => { const userRoleRef = ref(database, "users/" + user.uid + "/role"); const snapshot = await get(userRoleRef); - if (snapshot.exists() && snapshot.val() === role) { + if (snapshot.exists()) { + const role = snapshot.val(); + // Get user name const userNameRef = ref(database, "users/" + user.uid + "/name"); const userNameSnapshot = await get(userNameRef); @@ -44,9 +37,9 @@ const Login = ({ handleLoginSuccess }) => { : "Unknown User"; // Record the sign-in activity - if (role === "user") { - const userActivityRef = ref(database, "userActivity"); - const newActivityRef = push(userActivityRef); + if (role === "user" || role === "admin") { + const activityRef = ref(database, `${role}Activity`); + const newActivityRef = push(activityRef); await set(newActivityRef, { uid: user.uid, name: userName, @@ -56,32 +49,15 @@ const Login = ({ handleLoginSuccess }) => { }); } - if(role === "admin") { - const adminActivityRef = ref(database, "adminActivity"); - const newActivityRef = push(adminActivityRef); - await set(newActivityRef, { - uid: user.uid, - name: userName, - email: user.email, - signInTime: new Date().toISOString(), - }); - } - handleLoginSuccess(role); } else { - setError("Invalid credentials or role"); + setError("Invalid credentials"); } } catch (error) { setError(error.message); } }; - const handleRoleChange = (event, newRole) => { - if (newRole !== null) { - setRole(newRole); - } - }; - return ( { Login - - - User - - - Admin - - - Super Admin - - { fontSize: "14px", fontWeight: "bold", padding: "10px 20px", + marginBottom: "20px", }} > Login - + Don't have an account?{" "} Create one From d822b5c81f0a822241e9e8551d2f0a5adb171d41 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 25 Jun 2024 23:01:58 +0530 Subject: [PATCH 12/83] feat: added team CRUD operation --- src/scenes/team/index.jsx | 275 ++++++++++++++++++++++++++++++-------- 1 file changed, 217 insertions(+), 58 deletions(-) diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index ccc8749..65b9602 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -1,4 +1,18 @@ -import { Box, Typography, useTheme } from "@mui/material"; +import { + Box, + Typography, + useTheme, + Button, + TextField, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControl, + InputLabel, + Select, + MenuItem, +} from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; import { tokens } from "../../theme"; import AdminPanelSettingsOutlinedIcon from "@mui/icons-material/AdminPanelSettingsOutlined"; @@ -7,14 +21,34 @@ import SecurityOutlinedIcon from "@mui/icons-material/SecurityOutlined"; import Header from "../../components/Header"; import { useEffect, useState } from "react"; import { database } from "../../firebase"; -import { off, onChildAdded, onChildChanged, onChildRemoved, ref, get, remove } from "firebase/database"; +import { + off, + onChildAdded, + onChildChanged, + onChildRemoved, + ref, + get, + remove, + update, + set, +} from "firebase/database"; +import { v4 as uuidv4 } from "uuid"; const Team = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [userData, setUserData] = useState([]); + const [openDialog, setOpenDialog] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [formData, setFormData] = useState({ + name: "", + email: "", + password: "", + role: "user", + }); + const columns = [ - { field: "id", headerName: "UID" , flex: 1}, + { field: "id", headerName: "UID", flex: 1 }, { field: "name", headerName: "Name", @@ -29,52 +63,74 @@ const Team = () => { align: "left", flex: 1, }, + { field: "signInTime", headerName: "Login Time", flex: 1 }, { - field: "signInTime", - headerName: "Login Time", + field: "role", + headerName: "Access Level", flex: 1, + renderCell: ({ row: { role } }) => ( + + {role === "admin" && } + {role === "manager" && } + {role === "user" && } + + {role} + + + ), }, { - field: "role", - headerName: "Access Level", + field: "password", + headerName: "Password", flex: 1, - renderCell: ({ row: { role } }) => { - return ( - - {role === "admin" && } - {role === "manager" && } - {role === "user" && } - - {role} - - - ); - }, + }, + { + field: "blocked", + headerName: "Blocked", + flex: 1, + renderCell: ({ row }) => ( + + ), + }, + { + field: "actions", + headerName: "Actions", + flex: 1, + renderCell: ({ row }) => ( + + ), }, ]; useEffect(() => { const userActivityRef = ref(database, "userActivity"); - // Function to add or update user data const handleChildAddedOrChanged = (snapshot) => { const data = snapshot.val(); setUserData((prevUserData) => { - const existingIndex = prevUserData.findIndex((item) => item.id === snapshot.key); + const existingIndex = prevUserData.findIndex( + (item) => item.id === snapshot.key + ); if (existingIndex !== -1) { const updatedUserData = [...prevUserData]; updatedUserData[existingIndex] = { id: snapshot.key, ...data }; @@ -85,26 +141,33 @@ const Team = () => { }); }; - // Function to remove user data const handleChildRemoved = (snapshot) => { - setUserData((prevUserData) => prevUserData.filter((item) => item.id !== snapshot.key)); + setUserData((prevUserData) => + prevUserData.filter((item) => item.id !== snapshot.key) + ); }; - // Attach listeners - const childAddedListener = onChildAdded(userActivityRef, handleChildAddedOrChanged); - const childChangedListener = onChildChanged(userActivityRef, handleChildAddedOrChanged); - const childRemovedListener = onChildRemoved(userActivityRef, handleChildRemoved); + const childAddedListener = onChildAdded( + userActivityRef, + handleChildAddedOrChanged + ); + const childChangedListener = onChildChanged( + userActivityRef, + handleChildAddedOrChanged + ); + const childRemovedListener = onChildRemoved( + userActivityRef, + handleChildRemoved + ); - // Cleanup listeners on unmount return () => { - off(userActivityRef, 'child_added', childAddedListener); - off(userActivityRef, 'child_changed', childChangedListener); - off(userActivityRef, 'child_removed', childRemovedListener); + off(userActivityRef, "child_added", childAddedListener); + off(userActivityRef, "child_changed", childChangedListener); + off(userActivityRef, "child_removed", childRemovedListener); }; }, []); useEffect(() => { - // Function to check and remove old user activities const cleanupOldActivities = async () => { const userActivityRef = ref(database, "userActivity"); const snapshot = await get(userActivityRef); @@ -112,7 +175,7 @@ const Team = () => { if (snapshot.exists()) { const data = snapshot.val(); const now = new Date().getTime(); - const oneDay = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + const oneDay = 24 * 60 * 60 * 1000; for (let key in data) { const activity = data[key]; @@ -126,30 +189,70 @@ const Team = () => { } }; - // Run the cleanup function immediately and set interval for 24 hours cleanupOldActivities(); const interval = setInterval(cleanupOldActivities, 24 * 60 * 60 * 1000); - // Clear interval on component unmount return () => clearInterval(interval); }, []); + const handleBlockUser = async (userId, blockStatus) => { + const userRef = ref(database, `userActivity/${userId}`); + await update(userRef, { blocked: blockStatus }); + }; + + const handleEditUser = (user) => { + setSelectedUser(user); + setFormData({ + name: user.name, + email: user.email, + password: "", + role: user.role, + }); + setOpenDialog(true); + }; + + const handleDialogClose = () => { + setOpenDialog(false); + setSelectedUser(null); + setFormData({ name: "", email: "", password: "", role: "user" }); + }; + + const handleFormSubmit = async () => { + if (selectedUser) { + const userRef = ref(database, `userActivity/${selectedUser.id}`); + const updates = { ...formData }; + if (!formData.password) delete updates.password; // Do not update password if it's empty + await update(userRef, updates); + } else { + const newUserId = uuidv4(); + const userRef = ref(database, `userActivity/${newUserId}`); + await set(userRef, { + ...formData, + signInTime: new Date().toISOString(), + blocked: false, + }); + } + handleDialogClose(); + }; + return (
+ { > + + {selectedUser ? "Edit User" : "Add User"} + + setFormData({ ...formData, name: e.target.value })} + /> + + setFormData({ ...formData, email: e.target.value }) + } + /> + + setFormData({ ...formData, password: e.target.value }) + } + /> + + Role + + + + + + + + ); }; From 1c32553e38decdffe452f44eb975fd34e60a7d20 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 25 Jun 2024 23:28:52 +0530 Subject: [PATCH 13/83] feat added team info --- src/scenes/contacts/index.jsx | 248 +++++++++++++++++++++++++++++----- src/scenes/global/Sidebar.jsx | 2 +- 2 files changed, 214 insertions(+), 36 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index 002aadf..a7f0177 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -1,13 +1,42 @@ -import { Box } from "@mui/material"; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + useTheme, +} from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { tokens } from "../../theme"; -import { mockDataContacts } from "../../data/mockData"; import Header from "../../components/Header"; -import { useTheme } from "@mui/material"; +import { useEffect, useState } from "react"; +import { database } from "../../firebase"; +import { ref, set, update, remove, push ,get} from "firebase/database"; +import { v4 as uuidv4 } from "uuid"; const Contacts = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); + const [contacts, setContacts] = useState([]); + const [openDialog, setOpenDialog] = useState(false); + const [selectedContact, setSelectedContact] = useState(null); + const [formData, setFormData] = useState({ + id: "", + registrarId: "", + name: "", + age: "", + phone: "", + email: "", + address: "", + city: "", + zipCode: "", + }); const columns = [ { field: "id", headerName: "ID", flex: 0.5 }, @@ -25,52 +54,110 @@ const Contacts = () => { headerAlign: "left", align: "left", }, + { field: "phone", headerName: "Phone Number", flex: 1 }, + { field: "email", headerName: "Email", flex: 1 }, + { field: "address", headerName: "Address", flex: 1 }, + { field: "city", headerName: "City", flex: 1 }, + { field: "zipCode", headerName: "Zip Code", flex: 1 }, { - field: "phone", - headerName: "Phone Number", - flex: 1, - }, - { - field: "email", - headerName: "Email", - flex: 1, - }, - { - field: "address", - headerName: "Address", - flex: 1, - }, - { - field: "city", - headerName: "City", - flex: 1, - }, - { - field: "zipCode", - headerName: "Zip Code", + field: "actions", + headerName: "Actions", flex: 1, + renderCell: ({ row }) => ( + + ), }, ]; + useEffect(() => { + const fetchData = async () => { + const contactsRef = ref(database, "contacts"); + const snapshot = await get(contactsRef); + if (snapshot.exists()) { + setContacts(Object.values(snapshot.val())); + } + }; + fetchData(); + }, []); + + const handleAddContact = () => { + setFormData({ + id: "", + registrarId: "", + name: "", + age: "", + phone: "", + email: "", + address: "", + city: "", + zipCode: "", + }); + setOpenDialog(true); + }; + + const handleEditContact = (contact) => { + setSelectedContact(contact); + setFormData({ ...contact }); + setOpenDialog(true); + }; + + const handleDeleteContact = async (id) => { + if (window.confirm("Are you sure you want to delete this contact?")) { + const contactRef = ref(database, `contacts/${id}`); + await remove(contactRef); + } + }; + + const handleDialogClose = () => { + setOpenDialog(false); + setSelectedContact(null); + setFormData({ + id: "", + registrarId: "", + name: "", + age: "", + phone: "", + email: "", + address: "", + city: "", + zipCode: "", + }); + }; + + const handleFormSubmit = async () => { + const contactData = { ...formData }; + if (selectedContact) { + const contactRef = ref(database, `contacts/${selectedContact.id}`); + await update(contactRef, contactData); + } else { + const newContactId = uuidv4(); + const newContactRef = ref(database, `contacts/${newContactId}`); + await set(newContactRef, contactData); + } + handleDialogClose(); + }; + return (
+ { }} > + + + + {selectedContact ? "Edit Contact" : "Add Contact"} + + + + setFormData({ ...formData, registrarId: e.target.value }) + } + /> + setFormData({ ...formData, name: e.target.value })} + /> + setFormData({ ...formData, age: e.target.value })} + /> + + setFormData({ ...formData, phone: e.target.value }) + } + /> + + setFormData({ ...formData, email: e.target.value }) + } + /> + + setFormData({ ...formData, address: e.target.value }) + } + /> + setFormData({ ...formData, city: e.target.value })} + /> + + setFormData({ ...formData, zipCode: e.target.value }) + } + /> + + + + + + ); }; diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index 43a9152..0a8da04 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -162,7 +162,7 @@ const Sidebar = () => { setSelected={setSelected} /> } selected={selected} From 2767cc644d70da429e3c839e7d3a6820adf1f102 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 26 Jun 2024 08:06:21 +0530 Subject: [PATCH 14/83] feat added team info --- src/scenes/contacts/index.jsx | 108 +++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index a7f0177..ee65337 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -6,18 +6,14 @@ import { DialogContent, DialogTitle, TextField, - FormControl, - InputLabel, - Select, - MenuItem, useTheme, } from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { tokens } from "../../theme"; import Header from "../../components/Header"; import { useEffect, useState } from "react"; +import { getDatabase, ref, set, update, remove, get } from "firebase/database"; import { database } from "../../firebase"; -import { ref, set, update, remove, push ,get} from "firebase/database"; import { v4 as uuidv4 } from "uuid"; const Contacts = () => { @@ -27,7 +23,6 @@ const Contacts = () => { const [openDialog, setOpenDialog] = useState(false); const [selectedContact, setSelectedContact] = useState(null); const [formData, setFormData] = useState({ - id: "", registrarId: "", name: "", age: "", @@ -41,19 +36,8 @@ const Contacts = () => { const columns = [ { field: "id", headerName: "ID", flex: 0.5 }, { field: "registrarId", headerName: "Registrar ID" }, - { - field: "name", - headerName: "Name", - flex: 1, - cellClassName: "name-column--cell", - }, - { - field: "age", - headerName: "Age", - type: "number", - headerAlign: "left", - align: "left", - }, + { field: "name", headerName: "Name", flex: 1 }, + { field: "age", headerName: "Age", type: "number" }, { field: "phone", headerName: "Phone Number", flex: 1 }, { field: "email", headerName: "Email", flex: 1 }, { field: "address", headerName: "Address", flex: 1 }, @@ -64,25 +48,53 @@ const Contacts = () => { headerName: "Actions", flex: 1, renderCell: ({ row }) => ( - + + ), + }, + { + field: "delete", + headerName: "Delete", + flex: 1, + renderCell: ({ row }) => ( + ), }, ]; useEffect(() => { - const fetchData = async () => { - const contactsRef = ref(database, "contacts"); - const snapshot = await get(contactsRef); - if (snapshot.exists()) { - setContacts(Object.values(snapshot.val())); + const fetchContacts = async () => { + try { + const contactsRef = ref(database, "contacts"); + const snapshot = await get(contactsRef); + if (snapshot.exists()) { + const data = snapshot.val(); + const contactsArray = Object.keys(data).map((key) => ({ + id: key, + ...data[key], + })); + setContacts(contactsArray); + } else { + setContacts([]); + } + } catch (error) { + console.error("Error fetching contacts:", error); } }; - fetchData(); + fetchContacts(); }, []); const handleAddContact = () => { setFormData({ - id: "", registrarId: "", name: "", age: "", @@ -92,6 +104,7 @@ const Contacts = () => { city: "", zipCode: "", }); + setSelectedContact(null); setOpenDialog(true); }; @@ -101,10 +114,15 @@ const Contacts = () => { setOpenDialog(true); }; - const handleDeleteContact = async (id) => { - if (window.confirm("Are you sure you want to delete this contact?")) { - const contactRef = ref(database, `contacts/${id}`); + const handleDeleteContact = async (contact) => { + try { + const contactRef = ref(database, `contacts/${contact.id}`); await remove(contactRef); + setContacts((prevContacts) => + prevContacts.filter((c) => c.id !== contact.id) + ); + } catch (error) { + console.error("Error deleting contact:", error); } }; @@ -112,7 +130,6 @@ const Contacts = () => { setOpenDialog(false); setSelectedContact(null); setFormData({ - id: "", registrarId: "", name: "", age: "", @@ -125,16 +142,26 @@ const Contacts = () => { }; const handleFormSubmit = async () => { - const contactData = { ...formData }; - if (selectedContact) { - const contactRef = ref(database, `contacts/${selectedContact.id}`); - await update(contactRef, contactData); - } else { - const newContactId = uuidv4(); - const newContactRef = ref(database, `contacts/${newContactId}`); - await set(newContactRef, contactData); + try { + const contactData = { + ...formData, + id: selectedContact ? selectedContact.id : uuidv4(), + }; + + const contactRef = ref(database, `contacts/${contactData.id}`); + if (selectedContact) { + await update(contactRef, contactData); + setContacts((prevContacts) => + prevContacts.map((c) => (c.id === contactData.id ? contactData : c)) + ); + } else { + await set(contactRef, contactData); + setContacts((prevContacts) => [...prevContacts, contactData]); + } + handleDialogClose(); + } catch (error) { + console.error("Error submitting form:", error); } - handleDialogClose(); }; return ( @@ -181,6 +208,7 @@ const Contacts = () => { rows={contacts} columns={columns} components={{ Toolbar: GridToolbar }} + getRowId={(row) => row.id} /> From db24e5997bc0a25beb3f187e319bb2b6f430ba85 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 26 Jun 2024 08:08:37 +0530 Subject: [PATCH 15/83] added header title --- src/scenes/contacts/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index ee65337..af1920f 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -167,8 +167,8 @@ const Contacts = () => { return (
- diff --git a/utils/cn.ts b/utils/cn.ts new file mode 100644 index 0000000..a814e08 --- /dev/null +++ b/utils/cn.ts @@ -0,0 +1,7 @@ +// utils/cn.ts +import { ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} From bbbf90a450caabc2f10833a81fd7bbc19a479b1a Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 26 Jun 2024 18:22:03 +0530 Subject: [PATCH 17/83] feat added glow to boxes in dsahboard & tea,m management & added theme --- src/components/GradientBox.jsx | 49 ++++++++ src/scenes/dashboard/index.jsx | 220 +++++++++++++++++++++------------ src/scenes/global/Sidebar.jsx | 3 +- src/scenes/global/Topbar.jsx | 2 +- src/scenes/login/index.jsx | 9 +- src/scenes/team/index.jsx | 60 +++++++-- src/theme.js | 68 ++++++++++ 7 files changed, 322 insertions(+), 89 deletions(-) create mode 100644 src/components/GradientBox.jsx diff --git a/src/components/GradientBox.jsx b/src/components/GradientBox.jsx new file mode 100644 index 0000000..a4f29c3 --- /dev/null +++ b/src/components/GradientBox.jsx @@ -0,0 +1,49 @@ +import React from "react"; +import { Box } from "@mui/material"; +import { motion } from "framer-motion"; + +const GradientBox = ({ children, ...props }) => { + const variants = { + initial: { + backgroundPosition: "0 50%", + }, + animate: { + backgroundPosition: ["0, 50%", "100% 50%", "0 50%"], + }, + }; + + const gradientStyle = { + position: "absolute", + inset: 0, + borderRadius: "24px", + zIndex: 1, + opacity: 0.6, + transition: "opacity 0.5s", + background: `radial-gradient(circle_farthest-side_at_0_100%,#00ccb1,transparent), + radial-gradient(circle_farthest-side_at_100%_0,#7b61ff,transparent), + radial-gradient(circle_farthest-side_at_100%_100%,#ffc414,transparent), + radial-gradient(circle_farthest-side_at_0_0,#1ca0fb,#141316)`, + backgroundSize: "400% 400%", + }; + + return ( + + + + {children} + + + ); +}; + +export default GradientBox; diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 8fc7852..7b3a16d 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -148,88 +148,122 @@ const Dashboard = () => { > {/* ROW 1 */} - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - + gridColumn="span 3" + backgroundColor={colors.primary[400]} + display="flex" + alignItems="center" + justifyContent="center" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + }, + }} +> + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {/* ROW 2 */} { gridRow="span 2" backgroundColor={colors.primary[400]} overflow="auto" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + }, + }} > { alignItems="center" borderBottom={`4px solid ${colors.primary[500]}`} p="15px" + sx={{ + border: `2px solid ${colors.grey[600]}`, + boxShadow: `0 0 10px ${colors.grey[600]}`, + }} > { gridRow="span 2" backgroundColor={colors.primary[400]} p="30px" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + }, + }} > Campaign @@ -332,6 +384,7 @@ const Dashboard = () => { flexDirection="column" alignItems="center" mt="25px" + > { gridRow="span 2" backgroundColor={colors.primary[400]} id="bar-chart" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + }, + + }} > { backgroundColor={colors.primary[400]} padding="30px" id="geo-chart" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + }, + }} > { { "& .pro-menu-item.active": { color: "#6870fa !important", }, + }} > diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 9ece5f2..d08f2a0 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -15,7 +15,7 @@ const Topbar = () => { const colorMode = useContext(ColorModeContext); return ( - + {/* SEARCH BAR */} { alignItems="center" justifyContent="center" minHeight="100vh" - bgcolor={colors.primary[500]} + sx={{ + bgcolor: 'background.default', + + + '@media (prefers-color-scheme: dark)': { + bgcolor: '#18181b', + }, + }} > Login diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 65b9602..3576b3b 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -238,14 +238,44 @@ const Team = () => { return (
- + + + + { }, }} > - + {selectedUser ? "Edit User" : "Add User"} @@ -279,7 +320,6 @@ const Team = () => { label="Name" fullWidth variant="outlined" - className="text-white" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} /> diff --git a/src/theme.js b/src/theme.js index ac45e79..154a0c2 100644 --- a/src/theme.js +++ b/src/theme.js @@ -60,6 +60,40 @@ export const tokens = (mode) => ({ 800: "#2a2d64", 900: "#151632", }, + // Add new colors here + tealAccent: { + 100: "#e0f7fa", + 200: "#b2ebf2", + 300: "#80deea", + 400: "#4dd0e1", + 500: "#26c6da", + 600: "#00bccb", + 700: "#00acc1", + 800: "#0097a7", + 900: "#00838f", + }, + purpleAccent: { + 100: "#f3e5f5", + 200: "#e1bee7", + 300: "#ce93d8", + 400: "#ba68c8", + 500: "#ab47bc", + 600: "#9c27b0", + 700: "#8e24aa", + 800: "#7b1fa2", + 900: "#6a1b9a", + }, + yellowAccent: { + 100: "#fffde7", + 200: "#fff9c4", + 300: "#fff59d", + 400: "#fff176", + 500: "#ffee58", + 600: "#ffeb3b", + 700: "#fdd835", + 800: "#fbc02d", + 900: "#f9a825", + }, } : { grey: { @@ -117,6 +151,40 @@ export const tokens = (mode) => ({ 800: "#c3c6fd", 900: "#e1e2fe", }, + // Add new colors here + tealAccent: { + 100: "#00838f", + 200: "#0097a7", + 300: "#00acc1", + 400: "#00bccb", + 500: "#26c6da", + 600: "#4dd0e1", + 700: "#80deea", + 800: "#b2ebf2", + 900: "#e0f7fa", + }, + purpleAccent: { + 100: "#6a1b9a", + 200: "#7b1fa2", + 300: "#8e24aa", + 400: "#9c27b0", + 500: "#ab47bc", + 600: "#ba68c8", + 700: "#ce93d8", + 800: "#e1bee7", + 900: "#f3e5f5", + }, + yellowAccent: { + 100: "#f9a825", + 200: "#fbc02d", + 300: "#fdd835", + 400: "#ffeb3b", + 500: "#ffee58", + 600: "#fff176", + 700: "#fff59d", + 800: "#fff9c4", + 900: "#fffde7", + }, }), }); From 06dec00035421c0fa44c1183474fce904dd56f8d Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 27 Jun 2024 00:15:10 +0530 Subject: [PATCH 18/83] feat added ui --- src/components/TypeWriterEffect.jsx | 108 ++++++++++++ src/scenes/feedback/index.jsx | 258 ++++++++++++++++------------ utils/cn.ts => src/utils/cn.js | 2 +- 3 files changed, 258 insertions(+), 110 deletions(-) create mode 100644 src/components/TypeWriterEffect.jsx rename utils/cn.ts => src/utils/cn.js (74%) diff --git a/src/components/TypeWriterEffect.jsx b/src/components/TypeWriterEffect.jsx new file mode 100644 index 0000000..6bd75dc --- /dev/null +++ b/src/components/TypeWriterEffect.jsx @@ -0,0 +1,108 @@ +import React, { useEffect } from "react"; +import { motion, useAnimate, useInView, stagger } from "framer-motion"; +import { cn } from "../utils/cn"; // Replace with your utility function for classNames + +const TypewriterEffectSmooth = ({ + words, + className, + cursorClassName, +}) => { + const wordsArray = words.map((word) => { + return { + ...word, + text: word.text.split(""), + }; + }); + + const [scope, animate] = useAnimate(); + const isInView = useInView(scope); + + useEffect(() => { + if (isInView) { + animate( + "span", + { + display: "inline-block", + opacity: 1, + width: "fit-content", + }, + { + duration: 0.3, + delay: stagger(0.1), + ease: "easeInOut", + } + ); + } + }, [isInView, animate]); + + const renderWords = () => { + return ( + + {wordsArray.map((word, idx) => ( +
+ {word.text.map((char, index) => ( + + {char} + + ))} +   +
+ ))} +
+ ); + }; + + return ( +
+ +
+ {renderWords()} +
+
+ +
+ ); +}; + +export default TypewriterEffectSmooth; diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx index 12de0a2..d318c81 100644 --- a/src/scenes/feedback/index.jsx +++ b/src/scenes/feedback/index.jsx @@ -1,19 +1,28 @@ -import { Box, Button, TextareaAutosize, Typography } from "@mui/material"; -import { DataGrid, GridToolbar } from "@mui/x-data-grid"; -import { useTheme } from "@mui/material"; -import { tokens } from "../../theme"; -import Header from "../../components/Header"; +import { + Box, + Button, + TextareaAutosize, + Typography, + Avatar, + Card, + useTheme, +} from "@mui/material"; import { getAuth } from "firebase/auth"; import { get, ref, set, onValue } from "firebase/database"; import { useEffect, useState } from "react"; import { database } from "../../firebase"; +import { tokens } from "../../theme"; +import Header from "../../components/Header"; + +// Import the TypewriterEffectSmooth component +import TypewriterEffectSmooth from "../../components/TypeWriterEffect"; // Update with your correct path const Feedback = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [role, setRole] = useState(null); const [feedback, setFeedback] = useState(""); - const [allFeedbacks, setAllFeedbacks] = useState([]); + const [userFeedbacks, setUserFeedbacks] = useState([]); useEffect(() => { const fetchUserRole = async () => { @@ -29,22 +38,26 @@ const Feedback = () => { } }; - const fetchFeedbacks = () => { - const feedbackRef = ref(database, 'feedback'); - onValue(feedbackRef, (snapshot) => { - const data = snapshot.val(); - if (data) { - const feedbackList = Object.keys(data).map(key => ({ - id: key, - ...data[key], - })); - setAllFeedbacks(feedbackList); - } - }); + const fetchUserFeedbacks = () => { + const auth = getAuth(); + const user = auth.currentUser; + if (user) { + const feedbackRef = ref(database, `feedback/${user.uid}`); + onValue(feedbackRef, (snapshot) => { + const data = snapshot.val(); + if (data) { + const feedbackList = Object.keys(data).map((key) => ({ + id: key, + ...data[key], + })); + setUserFeedbacks(feedbackList); + } + }); + } }; fetchUserRole(); - fetchFeedbacks(); + fetchUserFeedbacks(); }, []); const handleSubmit = async () => { @@ -60,7 +73,11 @@ const Feedback = () => { } const feedbackRef = ref(database, `feedback/${user.uid}`); - await set(feedbackRef, { + const newFeedbackRef = ref( + database, + `feedback/${user.uid}/${Date.now()}` + ); + await set(newFeedbackRef, { name: userName, email: user.email, role: role, @@ -70,104 +87,127 @@ const Feedback = () => { } }; - const columns = [ - { field: "name", headerName: "Name", flex: 1 }, - { field: "email", headerName: "Email", flex: 1 }, - { field: "role", headerName: "Role", flex: 1 }, - { field: "feedback", headerName: "Feedback", flex: 2 }, - ]; - return ( -
- {role === "admin" ? ( - - setFeedback(e.target.value)} - style={{ - width: "70%", - padding: "10px", - borderRadius: "4px", - backgroundColor: "transparent", - border: `1px solid ${colors.grey[100]}`, - color: colors.grey[100], - outline: "none", - transition: "border-color 0.3s", - }} - onFocus={(e) => e.target.style.borderColor = "cyan"} - onBlur={(e) => e.target.style.borderColor = colors.grey[100]} +
- + + + + Previous Feedback + + {userFeedbacks.map((feedback) => ( + - Submit - - - ) : role === "superadmin" ? ( - - - - ) : ( - - Loading... - - )} + + + + {feedback.name ? feedback.name.charAt(0) : "A"}{" "} + {/* Handle undefined name */} + + + + {feedback.name || "Anonymous"}{" "} + {/* Fallback for undefined name */} + + + {feedback.email} + + + + {feedback.role} + + + {feedback.feedback} + + + ))} + ); }; diff --git a/utils/cn.ts b/src/utils/cn.js similarity index 74% rename from utils/cn.ts rename to src/utils/cn.js index a814e08..78faaa2 100644 --- a/utils/cn.ts +++ b/src/utils/cn.js @@ -2,6 +2,6 @@ import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -export function cn(...inputs: ClassValue[]) { +export function cn(...inputs) { return twMerge(clsx(inputs)); } From 01736b55c3cf55f3f985b5f8ba4b2076ae173e0a Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 27 Jun 2024 12:01:44 +0530 Subject: [PATCH 19/83] added lamp effect --- src/scenes/contacts/index.jsx | 116 ++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index 9c03fd7..eeda690 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -15,7 +15,7 @@ import { useEffect, useState } from "react"; import { getDatabase, ref, set, update, remove, get } from "firebase/database"; import { database } from "../../firebase"; import { v4 as uuidv4 } from "uuid"; - +import { motion } from "framer-motion"; const Contacts = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); @@ -163,18 +163,65 @@ const Contacts = () => { console.error("Error submitting form:", error); } }; + const lampEffectStyle = { + position: "relative", + background: "linear-gradient(to top, #00bfff, transparent)", + "&::after": { + content: '""', + position: "absolute", + left: 0, + + width: "100%", + + background: + "linear-gradient(to top, rgba(0, 191, 255, 0.8), transparent)", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + }, + }; return (
- + + + { columns={columns} components={{ Toolbar: GridToolbar }} getRowId={(row) => row.id} + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + }} /> - - + + {selectedContact ? "Edit Contact" : "Add Contact"} @@ -226,7 +302,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -242,7 +318,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -259,7 +335,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -277,7 +353,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -295,7 +371,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -313,7 +389,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -329,7 +405,7 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, @@ -347,14 +423,12 @@ const Contacts = () => { sx={{ marginBottom: "10px", boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", "&:hover": { boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", }, }} /> - - ), }, From 7359d3c8103016e6919bb297b55ed574244a4b24 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Thu, 27 Jun 2024 14:50:10 +0530 Subject: [PATCH 23/83] local storage caching user data --- src/App.js | 29 ++++++------ src/scenes/global/Topbar.jsx | 87 +++++++++++++++++++++++++----------- src/scenes/login/index.jsx | 24 ++++++---- 3 files changed, 92 insertions(+), 48 deletions(-) diff --git a/src/App.js b/src/App.js index d019208..693488c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { Routes, Route, useNavigate } from "react-router-dom"; +import { useState, useEffect } from "react"; +import { Routes, Route, useNavigate, useLocation } from "react-router-dom"; import Topbar from "./scenes/global/Topbar"; import Sidebar from "./scenes/global/Sidebar"; import Dashboard from "./scenes/dashboard"; @@ -16,7 +16,6 @@ import { CssBaseline, ThemeProvider } from "@mui/material"; import { ColorModeContext, useMode } from "./theme"; import Calendar from "./scenes/calendar/calendar"; import Login from "./scenes/login/index"; -import Signup from "./scenes/signup"; import AdminList from "./scenes/AdminList"; import Feedback from "./scenes/feedback"; @@ -24,7 +23,17 @@ function App() { const [theme, colorMode] = useMode(); const [isSidebar, setIsSidebar] = useState(true); const navigate = useNavigate(); + const location = useLocation(); const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + const storedUser = JSON.parse(localStorage.getItem("user")); + if (storedUser) { + setIsLoggedIn(true); + navigate("/dashboard"); + } + }, [navigate]); + const handleLoginSuccess = (role) => { setIsLoggedIn(true); if (role === "user" || role === "superadmin") { @@ -35,28 +44,22 @@ function App() { navigate("/dashboard"); } }; - const handleSignupSuccess = () => { - // Redirect to login after successful signup - navigate("/"); - }; + + const showSidebarAndTopbar = isLoggedIn && location.pathname !== "/"; return (
- {isLoggedIn && } + {showSidebarAndTopbar && }
- {isLoggedIn && } + {showSidebarAndTopbar && } } /> - } - /> } /> } /> } /> diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index d08f2a0..7fd8292 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -1,21 +1,37 @@ -import { Box, IconButton, useTheme } from "@mui/material"; -import { useContext } from "react"; -import { ColorModeContext, tokens } from "../../theme"; +import { Box, Button, IconButton, useTheme } from "@mui/material"; +import { tokens } from "../../theme"; import InputBase from "@mui/material/InputBase"; -import LightModeOutlinedIcon from "@mui/icons-material/LightModeOutlined"; -import DarkModeOutlinedIcon from "@mui/icons-material/DarkModeOutlined"; -import NotificationsOutlinedIcon from "@mui/icons-material/NotificationsOutlined"; -import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; -import PersonOutlinedIcon from "@mui/icons-material/PersonOutlined"; import SearchIcon from "@mui/icons-material/Search"; +import { useNavigate } from "react-router-dom"; +import { auth } from "../../firebase"; // Import Firebase auth +import { signOut } from "firebase/auth"; +import { useEffect } from "react"; const Topbar = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); - const colorMode = useContext(ColorModeContext); + const navigate = useNavigate(); + + const handleLogout = async () => { + try { + await signOut(auth); // Sign out from Firebase + localStorage.removeItem("user"); // Remove user information from localStorage + navigate("/"); // Redirect to login page + } catch (error) { + console.error("Error logging out:", error); + } + }; + + useEffect(() => { + // Check if user is logged in + const storedUser = JSON.parse(localStorage.getItem("user")); + if (!storedUser) { + navigate("/"); + } + }, [navigate]); return ( - + {/* SEARCH BAR */} { {/* ICONS */} - - {theme.palette.mode === "dark" ? ( - - ) : ( - - )} - - - - - - - - - - + ); diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index e25580d..f435394 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,4 +1,5 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; import { Box, Button, TextField, Typography, useTheme } from "@mui/material"; import { tokens } from "../../theme"; import { Link } from "react-router-dom"; @@ -12,6 +13,15 @@ const Login = ({ handleLoginSuccess }) => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); + const navigate = useNavigate(); + + // useEffect(() => { + // // Check if user is already logged in + // const storedUser = JSON.parse(localStorage.getItem("user")); + // if (storedUser) { + // navigate("/dashboard"); + // } + // }, [navigate]); const handleLogin = async () => { try { @@ -49,7 +59,11 @@ const Login = ({ handleLoginSuccess }) => { }); } + // Store user information in localStorage + localStorage.setItem("user", JSON.stringify({ uid: user.uid, role })); + handleLoginSuccess(role); + navigate("/dashboard"); } else { setError("Invalid credentials"); } @@ -67,8 +81,6 @@ const Login = ({ handleLoginSuccess }) => { minHeight="100vh" sx={{ bgcolor: 'background.default', - - '@media (prefers-color-scheme: dark)': { bgcolor: '#18181b', }, @@ -112,12 +124,6 @@ const Login = ({ handleLoginSuccess }) => { > Login - - Don't have an account?{" "} - - Create one - - ); }; From 3aefe3e149a94f8d5489c3c104fa514e48ab12ab Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Thu, 27 Jun 2024 15:21:00 +0530 Subject: [PATCH 24/83] login UI updated --- src/scenes/login/index.jsx | 92 ++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index f435394..990eb6a 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,8 +1,7 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Box, Button, TextField, Typography, useTheme } from "@mui/material"; import { tokens } from "../../theme"; -import { Link } from "react-router-dom"; import { auth, database } from "../../firebase"; // Import Firebase services import { signInWithEmailAndPassword } from "firebase/auth"; import { ref, get, set, push } from "firebase/database"; // Import necessary database functions @@ -15,14 +14,6 @@ const Login = ({ handleLoginSuccess }) => { const [error, setError] = useState(""); const navigate = useNavigate(); - // useEffect(() => { - // // Check if user is already logged in - // const storedUser = JSON.parse(localStorage.getItem("user")); - // if (storedUser) { - // navigate("/dashboard"); - // } - // }, [navigate]); - const handleLogin = async () => { try { const userCredential = await signInWithEmailAndPassword( @@ -72,29 +63,56 @@ const Login = ({ handleLoginSuccess }) => { } }; + const lampEffectStyle = { + position: "relative", + background: "black", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + "&::after": { + content: '""', + position: "absolute", + left: 0, + width: "100%", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + }, + }; + return ( - - Login + + Enter credentials to Login setEmail(e.target.value)} - sx={{ marginBottom: "20px", input: { color: colors.grey[100] } }} + sx={{ + marginBottom: "40px", + input: { color: colors.grey[100] }, + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} InputLabelProps={{ style: { color: colors.grey[100] } }} /> { variant="outlined" value={password} onChange={(e) => setPassword(e.target.value)} - sx={{ marginBottom: "20px", input: { color: colors.grey[100] } }} + sx={{ + marginBottom: "40px", + input: { color: colors.grey[100] }, + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} InputLabelProps={{ style: { color: colors.grey[100] } }} /> {error && ( @@ -120,9 +146,27 @@ const Login = ({ handleLoginSuccess }) => { fontWeight: "bold", padding: "10px 20px", marginBottom: "20px", + borderRadius: "8px", + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + animation: "shimmer 6s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, }} > - Login + LOGIN ); From 32c931ca763dcbdf4700e868cfd3fd7c4d7f567f Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Thu, 27 Jun 2024 15:24:47 +0530 Subject: [PATCH 25/83] bug resolved ( navigation to '/dashboard') --- src/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 693488c..8be8731 100644 --- a/src/App.js +++ b/src/App.js @@ -32,7 +32,7 @@ function App() { setIsLoggedIn(true); navigate("/dashboard"); } - }, [navigate]); + }, []); const handleLoginSuccess = (role) => { setIsLoggedIn(true); From ede5d2204f82aeb10c58a5f33f889aef69c093c9 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Thu, 27 Jun 2024 21:17:47 +0530 Subject: [PATCH 26/83] navigation problems resolved --- src/App.js | 45 ++++++------ src/components/BarChart.jsx | 8 +- src/scenes/global/Topbar.jsx | 12 +-- src/scenes/login/index.jsx | 18 ++++- src/scenes/team/index.jsx | 138 +++++++++++++++++------------------ 5 files changed, 115 insertions(+), 106 deletions(-) diff --git a/src/App.js b/src/App.js index 8be8731..43a76ab 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,6 @@ import Topbar from "./scenes/global/Topbar"; import Sidebar from "./scenes/global/Sidebar"; import Dashboard from "./scenes/dashboard"; import Team from "./scenes/team"; -import Invoices from "./scenes/invoices"; import Contacts from "./scenes/contacts"; import Bar from "./scenes/bar"; import Form from "./scenes/form"; @@ -21,46 +20,50 @@ import Feedback from "./scenes/feedback"; function App() { const [theme, colorMode] = useMode(); - const [isSidebar, setIsSidebar] = useState(true); + const [isSidebarVisible, setSidebarVisible] = useState(false); + const [isLoggedIn, setLoggedIn] = useState(false); + const [userRole, setUserRole] = useState(null); + const navigate = useNavigate(); const location = useLocation(); - const [isLoggedIn, setIsLoggedIn] = useState(false); useEffect(() => { const storedUser = JSON.parse(localStorage.getItem("user")); if (storedUser) { - setIsLoggedIn(true); - navigate("/dashboard"); + setLoggedIn(true); + setUserRole(storedUser.role); + setSidebarVisible(storedUser.role === "admin"); + (storedUser.role === "superadmin") ? navigate("/admins") : navigate("/dashboard"); } }, []); const handleLoginSuccess = (role) => { - setIsLoggedIn(true); - if (role === "user" || role === "superadmin") { - setIsSidebar(false); - navigate("/admins"); - } else { - setIsSidebar(false); - navigate("/dashboard"); - } + setLoggedIn(true); + setUserRole(role); + setSidebarVisible(role === "admin"); + navigate(role === "superadmin" ? "/admins" : "/dashboard"); + }; + + const handleLogout = () => { + setLoggedIn(false); + setUserRole(null); + setSidebarVisible(false); + navigate("/"); }; - const showSidebarAndTopbar = isLoggedIn && location.pathname !== "/"; + const showTopbar = isLoggedIn && location.pathname !== "/"; return (
- {showSidebarAndTopbar && } + {isSidebarVisible && }
- {showSidebarAndTopbar && } + {showTopbar && } - } - /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/BarChart.jsx b/src/components/BarChart.jsx index e9494c2..ebf7009 100644 --- a/src/components/BarChart.jsx +++ b/src/components/BarChart.jsx @@ -7,7 +7,7 @@ import { auth } from '../firebase'; const fetchData = async () => { if (!auth.currentUser) { - console.error("User is not authenticated"); + // console.error("User is not authenticated"); return { salesPerUnit: [], uniqueSellingProducts: [] }; } const db = getDatabase(); @@ -48,9 +48,9 @@ const transformData = (salesPerUnit, uniqueSellingProducts) => { [`${product}Color`]: productColors[product] || productColors['Unknown Product'], }; }); - console.log('Sales Per Unit:', salesPerUnit); - console.log('Unique Selling Products:', uniqueSellingProducts); - console.log('Transformed Data:', data); + // console.log('Sales Per Unit:', salesPerUnit); + // console.log('Unique Selling Products:', uniqueSellingProducts); + // console.log('Transformed Data:', data); return data; }; diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 7fd8292..0fcdfa3 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -7,15 +7,17 @@ import { auth } from "../../firebase"; // Import Firebase auth import { signOut } from "firebase/auth"; import { useEffect } from "react"; -const Topbar = () => { +const Topbar = ({ handleLogout }) => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const navigate = useNavigate(); - const handleLogout = async () => { + const handleLogoutdb = async () => { try { await signOut(auth); // Sign out from Firebase - localStorage.removeItem("user"); // Remove user information from localStorage + localStorage.removeItem("user"); // Remove user information from localStorage4 + handleLogout(); // Call the parent function to update the state + console.log(localStorage.getItem("user")); navigate("/"); // Redirect to login page } catch (error) { console.error("Error logging out:", error); @@ -28,7 +30,7 @@ const Topbar = () => { if (!storedUser) { navigate("/"); } - }, [navigate]); + }, []); return ( @@ -47,7 +49,7 @@ const Topbar = () => { {/* ICONS */} ), - }, - { + }); + columns.push({ field: "actions", headerName: "Actions", flex: 1, @@ -119,8 +139,8 @@ const Team = () => { Edit ), - }, - ]; + }); + } useEffect(() => { const userActivityRef = ref(database, "userActivity"); @@ -167,34 +187,6 @@ const Team = () => { }; }, []); - useEffect(() => { - const cleanupOldActivities = async () => { - const userActivityRef = ref(database, "userActivity"); - const snapshot = await get(userActivityRef); - - if (snapshot.exists()) { - const data = snapshot.val(); - const now = new Date().getTime(); - const oneDay = 24 * 60 * 60 * 1000; - - for (let key in data) { - const activity = data[key]; - const signInTime = new Date(activity.signInTime).getTime(); - - if (now - signInTime > oneDay) { - const activityRef = ref(database, `userActivity/${key}`); - await remove(activityRef); - } - } - } - }; - - cleanupOldActivities(); - const interval = setInterval(cleanupOldActivities, 24 * 60 * 60 * 1000); - - return () => clearInterval(interval); - }, []); - const handleBlockUser = async (userId, blockStatus) => { const userRef = ref(database, `userActivity/${userId}`); await update(userRef, { blocked: blockStatus }); @@ -238,43 +230,45 @@ const Team = () => { return (
- - - + {currentUserRole === "admin" && ( + + + + )} Date: Thu, 27 Jun 2024 21:27:02 +0530 Subject: [PATCH 27/83] admin team management, activity not duplicated --- src/scenes/login/index.jsx | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 65e6a1c..04582e9 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,10 +1,9 @@ import { useState } from "react"; -import { useNavigate } from "react-router-dom"; import { Box, Button, TextField, Typography, useTheme } from "@mui/material"; import { tokens } from "../../theme"; import { auth, database } from "../../firebase"; // Import Firebase services import { signInWithEmailAndPassword } from "firebase/auth"; -import { ref, get, set, push } from "firebase/database"; // Import necessary database functions +import { ref, get, set, push, update } from "firebase/database"; // Import necessary database functions const Login = ({ handleLoginSuccess }) => { const theme = useTheme(); @@ -12,7 +11,6 @@ const Login = ({ handleLoginSuccess }) => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); - const navigate = useNavigate(); const handleLogin = async () => { try { @@ -37,10 +35,25 @@ const Login = ({ handleLoginSuccess }) => { ? userNameSnapshot.val() : "Unknown User"; + // Record the sign-in activity // Record the sign-in activity if (role === "user" || role === "admin") { const activityRef = ref(database, `${role}Activity`); - const newActivityRef = push(activityRef); + + // Fetch existing activities + const existingActivitiesSnapshot = await get(activityRef); + let existingActivityKey = null; + + if (existingActivitiesSnapshot.exists()) { + const activities = existingActivitiesSnapshot.val(); + for (const [key, activity] of Object.entries(activities)) { + if (activity.email === user.email) { + existingActivityKey = key; + break; + } + } + } + const now = new Date(); const hours = now.getHours().toString().padStart(2, "0"); const minutes = now.getMinutes().toString().padStart(2, "0"); @@ -50,14 +63,27 @@ const Login = ({ handleLoginSuccess }) => { const year = now.getFullYear().toString().slice(-2); const date = `${day}/${month}/${year}`; const formattedTime = `${time} - ${date}`; - // console.log(formattedTime) - await set(newActivityRef, { - uid: user.uid, - name: userName, - email: user.email, - role: role, - signInTime: formattedTime, - }); + + if (existingActivityKey) { + // Update existing activity + const existingActivityRef = ref( + database, + `${role}Activity/${existingActivityKey}` + ); + await update(existingActivityRef, { + signInTime: formattedTime, + }); + } else { + // Push new activity + const newActivityRef = push(activityRef); + await set(newActivityRef, { + uid: user.uid, + name: userName, + email: user.email, + role: role, + signInTime: formattedTime, + }); + } } // Store user information in localStorage From 5c7c47abb95627f83a653a2ba95649e2a7681d60 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 27 Jun 2024 22:53:00 +0530 Subject: [PATCH 28/83] Refactor UI of team management --- src/scenes/contacts/index.jsx | 4 +- src/scenes/team/index.jsx | 93 +++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index ecbe1aa..5f0200a 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -255,8 +255,8 @@ const Contacts = () => { components={{ Toolbar: GridToolbar }} getRowId={(row) => row.id} sx={{ - border: `20px solid ${colors.grey[600]}`, - boxShadow: `0 0 10px ${colors.grey[600]}`, + border: `20px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, }} /> diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index cc1f171..3b43d2f 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -13,7 +13,7 @@ import { Select, MenuItem, } from "@mui/material"; -import { DataGrid } from "@mui/x-data-grid"; +import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { tokens } from "../../theme"; import AdminPanelSettingsOutlinedIcon from "@mui/icons-material/AdminPanelSettingsOutlined"; import LockOpenOutlinedIcon from "@mui/icons-material/LockOpenOutlined"; @@ -226,6 +226,22 @@ const Team = () => { } handleDialogClose(); }; + const lampEffectStyle = { + position: "relative", + background: "linear-gradient(to top, #00bfff, transparent)", + "&::after": { + content: '""', + position: "absolute", + left: 0, + + width: "100%", + + background: + "linear-gradient(to top, rgba(0, 191, 255, 0.8), transparent)", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + }, + }; return ( @@ -242,7 +258,9 @@ const Team = () => { alignItems: "center", justifyContent: "center", borderRadius: "8px", - border: "1px solid #374151", + + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -265,7 +283,7 @@ const Team = () => { }, }} > - Add User + Add Member )} @@ -291,12 +309,16 @@ const Team = () => { "& .MuiCheckbox-root": { color: `${colors.greenAccent[200]} !important`, }, + "& .MuiDataGrid-toolbarContainer .MuiButton-text": { + color: `${colors.grey[100]} !important`, + }, }} > { }} /> - - {selectedUser ? "Edit User" : "Add User"} - + + + {selectedUser ? "Edit User" : "Add User"} + + { variant="outlined" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { onChange={(e) => setFormData({ ...formData, email: e.target.value }) } + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { onChange={(e) => setFormData({ ...formData, password: e.target.value }) } + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> Role @@ -347,16 +420,16 @@ const Team = () => { } > User - Admin + Developer Manager - - - From a1a977e1c8a705e0316a38d644ab2ed35d4684db Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 27 Jun 2024 23:55:35 +0530 Subject: [PATCH 29/83] feat added event to db for calender --- src/App.js | 4 +- src/scenes/calendar/calendar.jsx | 77 +++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/App.js b/src/App.js index 43a76ab..3d75661 100644 --- a/src/App.js +++ b/src/App.js @@ -13,7 +13,7 @@ import FAQ from "./scenes/faq"; import Geography from "./scenes/geography"; import { CssBaseline, ThemeProvider } from "@mui/material"; import { ColorModeContext, useMode } from "./theme"; -import Calendar from "./scenes/calendar/calendar"; +import Calender from "./scenes/calendar/calendar"; import Login from "./scenes/login/index"; import AdminList from "./scenes/AdminList"; import Feedback from "./scenes/feedback"; @@ -73,7 +73,7 @@ function App() { } /> } /> } /> - } /> + } /> } />
diff --git a/src/scenes/calendar/calendar.jsx b/src/scenes/calendar/calendar.jsx index c590f09..db20a0a 100644 --- a/src/scenes/calendar/calendar.jsx +++ b/src/scenes/calendar/calendar.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import FullCalendar, { formatDate } from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; @@ -14,35 +14,76 @@ import { } from "@mui/material"; import Header from "../../components/Header"; import { tokens } from "../../theme"; +import { database } from "../../firebase"; +import { + ref, + set, + get, + remove, + onChildAdded, + onChildChanged, + onChildRemoved, +} from "firebase/database"; const Calendar = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [currentEvents, setCurrentEvents] = useState([]); + // Fetch events from Firebase when the component mounts + useEffect(() => { + const fetchEvents = async () => { + const eventsRef = ref(database, "events"); + const snapshot = await get(eventsRef); + if (snapshot.exists()) { + const events = Object.values(snapshot.val()); + setCurrentEvents(events); + } + }; + fetchEvents(); + }, []); + + // Add event to Firebase + const addEventToFirebase = async (event) => { + const eventRef = ref(database, `events/${event.id}`); + await set(eventRef, event); + }; + + // Remove event from Firebase + const removeEventFromFirebase = async (eventId) => { + const eventRef = ref(database, `events/${eventId}`); + await remove(eventRef); + }; + + // Handle date click const handleDateClick = (selected) => { const title = prompt("Please enter a new title for your event"); const calendarApi = selected.view.calendar; calendarApi.unselect(); if (title) { - calendarApi.addEvent({ + const newEvent = { id: `${selected.dateStr}-${title}`, title, start: selected.startStr, end: selected.endStr, allDay: selected.allDay, - }); + }; + calendarApi.addEvent(newEvent); + addEventToFirebase(newEvent); } }; + // Handle event click const handleEventClick = (selected) => { if ( window.confirm( `Are you sure you want to delete the event '${selected.event.title}'` ) ) { + const eventId = selected.event.id; selected.event.remove(); + removeEventFromFirebase(eventId); } }; @@ -57,6 +98,10 @@ const Calendar = () => { backgroundColor={colors.primary[400]} p="15px" borderRadius="4px" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + }} > Events @@ -66,7 +111,9 @@ const Calendar = () => { sx={{ backgroundColor: colors.greenAccent[500], margin: "10px 0", - borderRadius: "2px", + + border: `8px solid ${colors.grey[600]}`, + boxShadow: `0 0 10px ${colors.grey[600]}`, }} > { {/* CALENDAR */} - + { select={handleDateClick} eventClick={handleEventClick} eventsSet={(events) => setCurrentEvents(events)} - initialEvents={[ - { - id: "12315", - title: "All-day event", - date: "2022-09-14", - }, - { - id: "5123", - title: "Timed event", - date: "2022-09-28", - }, - ]} + initialEvents={currentEvents} /> From 0ad1e798608f31202dfbeebf67fd4a3ce5ddda0b Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 28 Jun 2024 00:17:14 +0530 Subject: [PATCH 30/83] removal faq --- src/scenes/global/Sidebar.jsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index 5178c8c..5d45b2a 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -67,7 +67,7 @@ const Sidebar = () => { { "& .pro-menu-item.active": { color: "#6870fa !important", }, - }} > @@ -198,13 +197,6 @@ const Sidebar = () => { selected={selected} setSelected={setSelected} /> - } - selected={selected} - setSelected={setSelected} - /> Date: Fri, 28 Jun 2024 08:31:17 +0530 Subject: [PATCH 31/83] added lampeffect to the form --- src/components/EventDialouge.jsx | 116 +++++++++++++++++++++++++++++++ src/scenes/calendar/calendar.jsx | 40 ++++++----- 2 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 src/components/EventDialouge.jsx diff --git a/src/components/EventDialouge.jsx b/src/components/EventDialouge.jsx new file mode 100644 index 0000000..bc134ee --- /dev/null +++ b/src/components/EventDialouge.jsx @@ -0,0 +1,116 @@ +import React, { useState } from "react"; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + useTheme, +} from "@mui/material"; +import { tokens } from "../theme"; + +const EventDialog = ({ open, onClose, onSave }) => { + const [title, setTitle] = useState(""); + const theme = useTheme(); + const colors = tokens(theme.palette.mode); + + const handleSave = () => { + onSave(title); + setTitle(""); + }; + const lampEffectStyle = { + position: "relative", + background: "linear-gradient(to top, #00bfff, transparent)", + "&::after": { + content: '""', + position: "absolute", + left: 0, + + width: "100%", + + background: + "linear-gradient(to top, rgba(0, 191, 255, 0.8), transparent)", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + }, + }; + + return ( + + + Add Event + + + setTitle(e.target.value)} + sx={{ + backgroundColor: "#000000", + "& .MuiInputBase-root": { color: colors.grey[100] }, + "& .MuiInputLabel-root": { color: colors.yellowAccent[600] }, + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: colors.tealAccent[600] }, + "&:hover fieldset": { borderColor: colors.yellowAccent[600] }, + "&.Mui-focused fieldset": { borderColor: colors.tealAccent[600] }, + }, + }} + /> + + + + + + + ); +}; + +export default EventDialog; diff --git a/src/scenes/calendar/calendar.jsx b/src/scenes/calendar/calendar.jsx index db20a0a..1af87e0 100644 --- a/src/scenes/calendar/calendar.jsx +++ b/src/scenes/calendar/calendar.jsx @@ -15,20 +15,16 @@ import { import Header from "../../components/Header"; import { tokens } from "../../theme"; import { database } from "../../firebase"; -import { - ref, - set, - get, - remove, - onChildAdded, - onChildChanged, - onChildRemoved, -} from "firebase/database"; +import { ref, set, get, remove } from "firebase/database"; +import EventDialog from "../../components/EventDialouge"; // Adjust the import path if necessary +import GradientBox from "../../components/GradientBox"; const Calendar = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [currentEvents, setCurrentEvents] = useState([]); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [selectedDate, setSelectedDate] = useState(null); // Fetch events from Firebase when the component mounts useEffect(() => { @@ -57,21 +53,27 @@ const Calendar = () => { // Handle date click const handleDateClick = (selected) => { - const title = prompt("Please enter a new title for your event"); - const calendarApi = selected.view.calendar; + setSelectedDate(selected); + setIsDialogOpen(true); + }; + + const handleSaveEvent = (title) => { + const calendarApi = selectedDate.view.calendar; calendarApi.unselect(); if (title) { const newEvent = { - id: `${selected.dateStr}-${title}`, + id: `${selectedDate.dateStr}-${title}`, title, - start: selected.startStr, - end: selected.endStr, - allDay: selected.allDay, + start: selectedDate.startStr, + end: selectedDate.endStr, + allDay: selectedDate.allDay, }; calendarApi.addEvent(newEvent); addEventToFirebase(newEvent); } + + setIsDialogOpen(false); }; // Handle event click @@ -111,7 +113,6 @@ const Calendar = () => { sx={{ backgroundColor: colors.greenAccent[500], margin: "10px 0", - border: `8px solid ${colors.grey[600]}`, boxShadow: `0 0 10px ${colors.grey[600]}`, }} @@ -167,6 +168,13 @@ const Calendar = () => { /> + + {/* Event Dialog */} + setIsDialogOpen(false)} + onSave={handleSaveEvent} + /> ); }; From cb1ed0f6c8f28865e35215088a2a709fdfd58b1c Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 28 Jun 2024 12:04:31 +0530 Subject: [PATCH 32/83] added dashboard image & fixed feedback cards --- src/scenes/feedback/index.jsx | 2 +- src/scenes/global/Sidebar.jsx | 69 +++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx index d318c81..dbb248f 100644 --- a/src/scenes/feedback/index.jsx +++ b/src/scenes/feedback/index.jsx @@ -175,7 +175,7 @@ const Feedback = () => { border: `2px solid ${colors.tealAccent[600]}`, transition: "transform 0.3s, box-shadow 0.3s", "&:hover": { - transform: "scale(1.05)", + transform: "scale(1.01)", boxShadow: `0 0 20px ${colors.tealAccent[600]}`, }, }} diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index 5d45b2a..3a2cf3b 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { ProSidebar, Menu, MenuItem } from "react-pro-sidebar"; import { Box, IconButton, Typography, useTheme } from "@mui/material"; import { Link } from "react-router-dom"; @@ -10,14 +10,14 @@ import ContactsOutlinedIcon from "@mui/icons-material/ContactsOutlined"; import ReceiptOutlinedIcon from "@mui/icons-material/ReceiptOutlined"; import PersonOutlinedIcon from "@mui/icons-material/PersonOutlined"; import CalendarTodayOutlinedIcon from "@mui/icons-material/CalendarTodayOutlined"; -import HelpOutlineOutlinedIcon from "@mui/icons-material/HelpOutlineOutlined"; import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined"; import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutlined"; import TimelineOutlinedIcon from "@mui/icons-material/TimelineOutlined"; -import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined"; import MapOutlinedIcon from "@mui/icons-material/MapOutlined"; +import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined"; +import AddAPhotoIcon from "@mui/icons-material/AddAPhoto"; import { auth } from "../../firebase"; -import { get, getDatabase, ref } from "firebase/database"; +import { get, getDatabase, ref, update } from "firebase/database"; const Item = ({ title, to, icon, selected, setSelected }) => { const theme = useTheme(); @@ -44,6 +44,7 @@ const Sidebar = () => { const [selected, setSelected] = useState("Dashboard"); const [role, setRole] = useState("user"); const [name, setName] = useState(""); + const [profileImage, setProfileImage] = useState(""); const user = auth.currentUser; const db = getDatabase(); @@ -57,12 +58,27 @@ const Sidebar = () => { const userData = snapshot.val(); setRole(userData.role); setName(userData.name); + setProfileImage(userData.profileImage || ""); } }; fetchUserInfo(); } }, [user, db]); + const handleImageUpload = (event) => { + const file = event.target.files[0]; + const reader = new FileReader(); + reader.onloadend = async () => { + const base64String = reader.result.split(",")[1]; + if (user) { + const userRef = ref(db, `users/${user.uid}`); + await update(userRef, { profileImage: base64String }); + setProfileImage(base64String); + } + }; + reader.readAsDataURL(file); + }; + return ( { {!isCollapsed && ( - + profile-user + + Date: Fri, 28 Jun 2024 12:31:03 +0530 Subject: [PATCH 33/83] feedback --- src/scenes/AdminList/index.jsx | 29 +++++++++++++++++++++++------ src/scenes/feedback/index.jsx | 5 +++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 759bae6..3e17875 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -24,7 +24,18 @@ const AdminList = () => { if (snapshot.exists()) { const users = snapshot.val(); const adminList = Object.keys(users).map(key => ({ id: key, ...users[key] })); - setAdmins(adminList); + + // Fetch feedbacks and merge with admin data + const feedbackRef = ref(database, "feedback"); + const feedbackSnapshot = await get(feedbackRef); + const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() : {}; + + const adminListWithFeedback = adminList.map(admin => { + const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : []; + return { ...admin, feedback: adminFeedbacks.map(fb => fb.feedback).join(", ") }; + }); + + setAdmins(adminListWithFeedback); // Get the current user's ID and role const currentUserId = auth.currentUser.uid; @@ -44,16 +55,21 @@ const AdminList = () => { useEffect(() => { const adminActivityRef = ref(database, "adminActivity"); - const handleChildAddedOrChanged = (snapshot) => { + const handleChildAddedOrChanged = async (snapshot) => { const data = snapshot.val(); + const feedbackRef = ref(database, `feedback/${data.uid}`); + const feedbackSnapshot = await get(feedbackRef); + const feedbacks = feedbackSnapshot.exists() ? Object.values(feedbackSnapshot.val()) : []; + const feedbackText = feedbacks.map(fb => fb.feedback).join(", "); + setAdmins((prevAdmins) => { const existingIndex = prevAdmins.findIndex((item) => item.id === snapshot.key); if (existingIndex !== -1) { const updatedAdmins = [...prevAdmins]; - updatedAdmins[existingIndex] = { id: snapshot.key, ...data }; + updatedAdmins[existingIndex] = { id: snapshot.key, ...data, feedback: feedbackText }; return updatedAdmins; } else { - return [...prevAdmins, { id: snapshot.key, ...data }]; + return [...prevAdmins, { id: snapshot.key, ...data, feedback: feedbackText }]; } }); }; @@ -116,9 +132,10 @@ const AdminList = () => { const columns = [ { field: "id", headerName: "ID", flex: 0.5 }, - { field: "name", headerName: "Name", flex: 1 }, + { field: "name", headerName: "Name", flex: 0.75 }, { field: "email", headerName: "Email", flex: 1 }, { field: "signInTime", headerName: "Login Time", flex: 1 }, + { field: "feedback", headerName: "Feedback", flex: 2 }, { field: "actions", headerName: "Actions", @@ -204,4 +221,4 @@ const AdminList = () => { ); }; -export default AdminList; +export default AdminList; \ No newline at end of file diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx index dbb248f..ac9b360 100644 --- a/src/scenes/feedback/index.jsx +++ b/src/scenes/feedback/index.jsx @@ -50,7 +50,7 @@ const Feedback = () => { id: key, ...data[key], })); - setUserFeedbacks(feedbackList); + setUserFeedbacks(feedbackList.reverse()); } }); } @@ -168,6 +168,7 @@ const Feedback = () => { sx={{ p: "20px", mb: "20px", + width: "100%", backgroundColor: `linear-gradient(135deg, ${colors.tealAccent[600]} 30%, ${colors.greenAccent[600]} 100%)`, color: colors.grey[100], borderRadius: "8px", @@ -212,4 +213,4 @@ const Feedback = () => { ); }; -export default Feedback; +export default Feedback; \ No newline at end of file From 7cdbf4cc60d6fefd1bd539b21a475650ae1a731a Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 28 Jun 2024 13:27:06 +0530 Subject: [PATCH 34/83] feat added form UI & sidebar & topbar --- src/scenes/AdminList/index.jsx | 51 +++-- src/scenes/form/index.jsx | 408 +++++++++++++++++++++++++++++++-- src/scenes/global/Sidebar.jsx | 2 +- src/scenes/global/Topbar.jsx | 23 +- 4 files changed, 431 insertions(+), 53 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 759bae6..1bb19be 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -3,7 +3,15 @@ import { Box, Button, IconButton } from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { useTheme } from "@mui/material"; import { tokens } from "../../theme"; -import { ref, get, remove, onChildAdded, onChildChanged, onChildRemoved, off } from "firebase/database"; +import { + ref, + get, + remove, + onChildAdded, + onChildChanged, + onChildRemoved, + off, +} from "firebase/database"; import { auth, database } from "../../firebase"; import Header from "../../components/Header"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -23,7 +31,10 @@ const AdminList = () => { const snapshot = await get(adminsRef); if (snapshot.exists()) { const users = snapshot.val(); - const adminList = Object.keys(users).map(key => ({ id: key, ...users[key] })); + const adminList = Object.keys(users).map((key) => ({ + id: key, + ...users[key], + })); setAdmins(adminList); // Get the current user's ID and role @@ -47,7 +58,9 @@ const AdminList = () => { const handleChildAddedOrChanged = (snapshot) => { const data = snapshot.val(); setAdmins((prevAdmins) => { - const existingIndex = prevAdmins.findIndex((item) => item.id === snapshot.key); + const existingIndex = prevAdmins.findIndex( + (item) => item.id === snapshot.key + ); if (existingIndex !== -1) { const updatedAdmins = [...prevAdmins]; updatedAdmins[existingIndex] = { id: snapshot.key, ...data }; @@ -59,17 +72,28 @@ const AdminList = () => { }; const handleChildRemoved = (snapshot) => { - setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key)); + setAdmins((prevAdmins) => + prevAdmins.filter((item) => item.id !== snapshot.key) + ); }; - const childAddedListener = onChildAdded(adminActivityRef, handleChildAddedOrChanged); - const childChangedListener = onChildChanged(adminActivityRef, handleChildAddedOrChanged); - const childRemovedListener = onChildRemoved(adminActivityRef, handleChildRemoved); + const childAddedListener = onChildAdded( + adminActivityRef, + handleChildAddedOrChanged + ); + const childChangedListener = onChildChanged( + adminActivityRef, + handleChildAddedOrChanged + ); + const childRemovedListener = onChildRemoved( + adminActivityRef, + handleChildRemoved + ); return () => { - off(adminActivityRef, 'child_added', childAddedListener); - off(adminActivityRef, 'child_changed', childChangedListener); - off(adminActivityRef, 'child_removed', childRemovedListener); + off(adminActivityRef, "child_added", childAddedListener); + off(adminActivityRef, "child_changed", childChangedListener); + off(adminActivityRef, "child_removed", childRemovedListener); }; }, []); @@ -137,7 +161,7 @@ const AdminList = () => { <> handleDelete(params.id)} > @@ -158,10 +182,7 @@ const AdminList = () => { return ( -
+
{ + const theme = useTheme(); const [data, setData] = useState([]); const isNonMobile = useMediaQuery("(min-width:600px)"); // console.log(countryData); + const colors = tokens(theme.palette.mode); const handleFormSubmit = (values) => { const auth = getAuth(); @@ -81,7 +84,15 @@ const Form = () => { name="companyName" error={!!touched.companyName && !!errors.companyName} helperText={touched.companyName && errors.companyName} - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { name="email" error={!!touched.email && !!errors.email} helperText={touched.email && errors.email} - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> @@ -124,7 +143,15 @@ const Form = () => { touched.salesPerMonth?.[index]?.month && errors.salesPerMonth?.[index]?.month } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { touched.salesPerMonth?.[index]?.amount && errors.salesPerMonth?.[index]?.amount } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { touched.salesPerMonth?.[index]?.country && errors.salesPerMonth?.[index]?.country } - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> @@ -182,7 +255,37 @@ const Form = () => { onClick={() => push({ month: "", amount: "", country: "" }) } - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: + "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + }} > Add Sale @@ -217,7 +320,15 @@ const Form = () => { touched.uniqueSellingProducts?.[index]?.product && errors.uniqueSellingProducts?.[index]?.product } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { touched.uniqueSellingProducts?.[index]?.country && errors.uniqueSellingProducts?.[index]?.country } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> @@ -254,7 +403,37 @@ const Form = () => { color="secondary" variant="contained" onClick={() => push({ product: "", country: "" })} - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: + "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + }} > Add Product @@ -301,10 +480,27 @@ const Form = () => { touched.salesPerUnit?.[index]?.country && errors.salesPerUnit?.[index]?.country } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: + "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> )} - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> { touched.salesPerUnit?.[index]?.unitSales && errors.salesPerUnit?.[index]?.unitSales } - sx={{ gridColumn: "span 2" }} + sx={{ + gridColumn: "span 2", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> @@ -341,7 +575,37 @@ const Form = () => { color="secondary" variant="contained" onClick={() => push({ country: "", unitSales: "" })} - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: + "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + }} > Add Sale @@ -376,14 +640,52 @@ const Form = () => { touched.locations?.[index] && errors.locations?.[index] } - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> @@ -394,7 +696,37 @@ const Form = () => { color="secondary" variant="contained" onClick={() => push("")} - sx={{ gridColumn: "span 4" }} + sx={{ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: + "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + }} > Add Location @@ -403,7 +735,41 @@ const Form = () => { - diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index 3a2cf3b..f5b28c5 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -239,7 +239,7 @@ const Sidebar = () => { Pages } selected={selected} diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 0fcdfa3..3eb09be 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -1,7 +1,7 @@ import { Box, Button, IconButton, useTheme } from "@mui/material"; import { tokens } from "../../theme"; import InputBase from "@mui/material/InputBase"; -import SearchIcon from "@mui/icons-material/Search"; + import { useNavigate } from "react-router-dom"; import { auth } from "../../firebase"; // Import Firebase auth import { signOut } from "firebase/auth"; @@ -33,21 +33,12 @@ const Topbar = ({ handleLogout }) => { }, []); return ( - - {/* SEARCH BAR */} - - - - - - + + + - {/* ICONS */} - + + - + ); }; From bf4bc88b9614c464faf3b8422e582f3afc73b379 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 28 Jun 2024 13:32:27 +0530 Subject: [PATCH 35/83] fixed bug --- src/scenes/AdminList/index.jsx | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index a05b490..c1dcfec 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -38,16 +38,21 @@ const AdminList = () => { })); setAdmins(adminList); - const adminList = Object.keys(users).map(key => ({ id: key, ...users[key] })); - // Fetch feedbacks and merge with admin data const feedbackRef = ref(database, "feedback"); const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() : {}; + const feedbackData = feedbackSnapshot.exists() + ? feedbackSnapshot.val() + : {}; - const adminListWithFeedback = adminList.map(admin => { - const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : []; - return { ...admin, feedback: adminFeedbacks.map(fb => fb.feedback).join(", ") }; + const adminListWithFeedback = adminList.map((admin) => { + const adminFeedbacks = feedbackData[admin.uid] + ? Object.values(feedbackData[admin.uid]) + : []; + return { + ...admin, + feedback: adminFeedbacks.map((fb) => fb.feedback).join(", "), + }; }); setAdmins(adminListWithFeedback); @@ -74,8 +79,10 @@ const AdminList = () => { const data = snapshot.val(); const feedbackRef = ref(database, `feedback/${data.uid}`); const feedbackSnapshot = await get(feedbackRef); - const feedbacks = feedbackSnapshot.exists() ? Object.values(feedbackSnapshot.val()) : []; - const feedbackText = feedbacks.map(fb => fb.feedback).join(", "); + const feedbacks = feedbackSnapshot.exists() + ? Object.values(feedbackSnapshot.val()) + : []; + const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); setAdmins((prevAdmins) => { const existingIndex = prevAdmins.findIndex( @@ -83,10 +90,17 @@ const AdminList = () => { ); if (existingIndex !== -1) { const updatedAdmins = [...prevAdmins]; - updatedAdmins[existingIndex] = { id: snapshot.key, ...data, feedback: feedbackText }; + updatedAdmins[existingIndex] = { + id: snapshot.key, + ...data, + feedback: feedbackText, + }; return updatedAdmins; } else { - return [...prevAdmins, { id: snapshot.key, ...data, feedback: feedbackText }]; + return [ + ...prevAdmins, + { id: snapshot.key, ...data, feedback: feedbackText }, + ]; } }); }; @@ -246,4 +260,4 @@ const AdminList = () => { ); }; -export default AdminList; \ No newline at end of file +export default AdminList; From ad15ca8019d4918b56d3d3970d3d9b758b5dcf67 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 29 Jun 2024 16:11:22 +0530 Subject: [PATCH 36/83] fixed auth --- src/scenes/team/index.jsx | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 3b43d2f..da716e4 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -32,6 +32,7 @@ import { set, } from "firebase/database"; import { v4 as uuidv4 } from "uuid"; +import { createUserWithEmailAndPassword } from "firebase/auth"; const Team = () => { const theme = useTheme(); @@ -211,18 +212,39 @@ const Team = () => { const handleFormSubmit = async () => { if (selectedUser) { + // Updating an existing user const userRef = ref(database, `userActivity/${selectedUser.id}`); const updates = { ...formData }; if (!formData.password) delete updates.password; // Do not update password if it's empty await update(userRef, updates); + + const userRefUsers = ref(database, `users/${selectedUser.id}`); + await update(userRefUsers, updates); } else { - const newUserId = uuidv4(); - const userRef = ref(database, `userActivity/${newUserId}`); - await set(userRef, { - ...formData, - signInTime: new Date().toISOString(), - blocked: false, - }); + // Adding a new user + try { + const userCredential = await createUserWithEmailAndPassword( + auth, + formData.email, + formData.password + ); + const newUser = userCredential.user; + const userRef = ref(database, `userActivity/${newUser.uid}`); + await set(userRef, { + ...formData, + signInTime: new Date().toISOString(), + blocked: false, + }); + + const userRefUsers = ref(database, `users/${newUser.uid}`); + await set(userRefUsers, { + ...formData, + signInTime: new Date().toISOString(), + blocked: false, + }); + } catch (error) { + console.error("Error adding new user:", error); + } } handleDialogClose(); }; From 33ea04256ceb9096ed5ce31eb2304591420383e3 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 29 Jun 2024 18:01:25 +0530 Subject: [PATCH 37/83] statbox added to db --- src/scenes/dashboard/index.jsx | 303 ++++++++++++++++++++------------- 1 file changed, 187 insertions(+), 116 deletions(-) diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 7b3a16d..4811a11 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -23,7 +23,9 @@ const Dashboard = () => { const colors = tokens(theme.palette.mode); const [totalRevenue, setTotalRevenue] = useState(0); const [location, setLocations] = useState([]); - + const [userActivityCount, setUserActivityCount] = useState(0); + const [totalSalesPerUnit, setTotalSalesPerUnit] = useState(0); + const [dataLoaded, setDataLoaded] = useState(false); useEffect(() => { const fetchTotalRevenue = async () => { if (!auth.currentUser) { @@ -65,15 +67,86 @@ const Dashboard = () => { if (snapshot.exists()) { const firebaseData = snapshot.val(); + console.log("fetched from firebase", firebaseData); setLocations(firebaseData); } } catch (error) { console.error("Error fetching locations:", error); } }; + const fetchUserActivityCount = async () => { + if (!auth.currentUser) { + console.error("User is not authenticated"); + return; + } + const db = getDatabase(); + const dataRef = ref(db, `userActivity`); + const snapshot = await get(dataRef); + + if (snapshot.exists()) { + const firebaseData = snapshot.val(); + const validEntriesCount = Object.keys(firebaseData).length; + setUserActivityCount(validEntriesCount); + } else { + console.log("No user activity data available"); + } + }; + const fetchTotalSalesPerUnit = async () => { + try { + const db = getDatabase(); + const salesPerUnitRef = ref( + db, + `users/${auth.currentUser.uid}/formdata/salesPerUnit` + ); + console.log( + "Fetching data from path:", + `users/${auth.currentUser.uid}/formdata/salesPerUnit` + ); + + const snapshot = await get(salesPerUnitRef); + console.log("Snapshot:", snapshot); + + // Handle the fetched data + if (snapshot.exists()) { + const salesData = snapshot.val(); + console.log("Fetched data from Firebase:", salesData); // Log the fetched data + + // Check if the fetched data is an array or an object + if (Array.isArray(salesData)) { + const totalSales = salesData.reduce( + (sum, item) => sum + (item.unitSales || 0), + 0 + ); + console.log("Total Sales Calculated (Array):", totalSales); + setTotalSalesPerUnit(totalSales); + } else if (typeof salesData === "object") { + const totalSales = Object.values(salesData).reduce( + (sum, item) => sum + (item.unitSales || 0), + 0 + ); + console.log("Total Sales Calculated (Object):", totalSales); + setTotalSalesPerUnit(totalSales); + } else { + console.log( + "Sales data format is neither an array nor an object:", + salesData + ); + } + } else { + console.log("No sales per unit data available"); + } + + setDataLoaded(true); // Set data loaded state + } catch (error) { + console.error("Error fetching sales per unit data:", error); + setDataLoaded(true); + } + }; fetchTotalRevenue(); fetchLocations(); + fetchUserActivityCount(); + fetchTotalSalesPerUnit(); }, []); const handleDownload = async () => { @@ -148,109 +221,109 @@ const Dashboard = () => { > {/* ROW 1 */} - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - + gridColumn="span 3" + backgroundColor={colors.primary[400]} + display="flex" + alignItems="center" + justifyContent="center" + sx={{ + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 + }, + }} + > + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {/* ROW 2 */} { sx={{ border: `2px solid ${colors.tealAccent[600]}`, boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, }} > @@ -308,8 +381,8 @@ const Dashboard = () => { sx={{ border: `2px solid ${colors.tealAccent[600]}`, boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, }} > @@ -371,8 +444,8 @@ const Dashboard = () => { sx={{ border: `2px solid ${colors.tealAccent[600]}`, boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, }} > @@ -384,7 +457,6 @@ const Dashboard = () => { flexDirection="column" alignItems="center" mt="25px" - > { sx={{ border: `2px solid ${colors.tealAccent[600]}`, boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, - }} > { sx={{ border: `2px solid ${colors.tealAccent[600]}`, boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, }} > From 9dbfdab5a1a47f166ec6c61d4b5ba57a2b3ff46f Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 29 Jun 2024 18:28:19 +0530 Subject: [PATCH 38/83] fixed stat box --- src/scenes/dashboard/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 4811a11..197f902 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -96,11 +96,11 @@ const Dashboard = () => { const db = getDatabase(); const salesPerUnitRef = ref( db, - `users/${auth.currentUser.uid}/formdata/salesPerUnit` + `users/${auth.currentUser.uid}/formData/salesPerUnit` ); console.log( "Fetching data from path:", - `users/${auth.currentUser.uid}/formdata/salesPerUnit` + `users/${auth.currentUser.uid}/formData/salesPerUnit` ); const snapshot = await get(salesPerUnitRef); @@ -261,7 +261,7 @@ const Dashboard = () => { }} > Date: Sat, 29 Jun 2024 18:38:17 +0530 Subject: [PATCH 39/83] Latest feedback --- src/scenes/AdminList/index.jsx | 56 +++++++++------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index c1dcfec..04a7bff 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -31,32 +31,33 @@ const AdminList = () => { const snapshot = await get(adminsRef); if (snapshot.exists()) { const users = snapshot.val(); - + const adminList = Object.keys(users).map((key) => ({ id: key, ...users[key], })); - setAdmins(adminList); - + // Fetch feedbacks and merge with admin data const feedbackRef = ref(database, "feedback"); const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() - ? feedbackSnapshot.val() - : {}; - + const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() : {}; + const adminListWithFeedback = adminList.map((admin) => { - const adminFeedbacks = feedbackData[admin.uid] - ? Object.values(feedbackData[admin.uid]) - : []; + const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : []; + + // Get the most recent feedback + const latestFeedback = adminFeedbacks.reduce((latest, current) => { + return new Date(current.timestamp) > new Date(latest.timestamp) ? current : latest; + }, adminFeedbacks[0]); + return { ...admin, - feedback: adminFeedbacks.map((fb) => fb.feedback).join(", "), + feedback: latestFeedback ? latestFeedback.feedback : "No feedback", }; }); - + setAdmins(adminListWithFeedback); - + // Get the current user's ID and role const currentUserId = auth.currentUser.uid; const usersRef = ref(database, "users"); @@ -71,6 +72,7 @@ const AdminList = () => { }; fetchAdmins(); }, []); + useEffect(() => { const adminActivityRef = ref(database, "adminActivity"); @@ -131,34 +133,6 @@ const AdminList = () => { }; }, []); - useEffect(() => { - const cleanupOldActivities = async () => { - const adminActivityRef = ref(database, "adminActivity"); - const snapshot = await get(adminActivityRef); - - if (snapshot.exists()) { - const data = snapshot.val(); - const now = new Date().getTime(); - const oneDay = 24 * 60 * 60 * 1000; // 24 hours in milliseconds - - for (let key in data) { - const activity = data[key]; - const signInTime = new Date(activity.signInTime).getTime(); - - if (now - signInTime > oneDay) { - const activityRef = ref(database, `adminActivity/${key}`); - await remove(activityRef); - } - } - } - }; - - cleanupOldActivities(); - const interval = setInterval(cleanupOldActivities, 24 * 60 * 60 * 1000); - - return () => clearInterval(interval); - }, []); - const handleDelete = async (id) => { try { await remove(ref(database, `adminActivity/${id}`)); From 2eba5bac13c60147438b84e98a78b2c4105e347e Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 29 Jun 2024 18:50:27 +0530 Subject: [PATCH 40/83] feat added pie chart to backend --- src/components/PieChart.jsx | 54 ++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/components/PieChart.jsx b/src/components/PieChart.jsx index 3393bd4..b7703aa 100644 --- a/src/components/PieChart.jsx +++ b/src/components/PieChart.jsx @@ -1,11 +1,63 @@ import { ResponsivePie } from "@nivo/pie"; import { tokens } from "../theme"; import { useTheme } from "@mui/material"; -import { mockPieData as data } from "../data/mockData"; +import { useEffect, useState } from "react"; +import { auth, database } from "../firebase"; +import { get, ref } from "firebase/database"; const PieChart = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + if (!auth.currentUser) { + console.error("User is not authenticated"); + return; + } + // Fetch salesPerUnit data from Firebase Realtime Database for the current user + const db = database; + const salesRef = ref( + db, + `users/${auth.currentUser.uid}/formData/salesPerUnit` + ); + const productsRef = ref( + db, + `users/${auth.currentUser.uid}/formData/uniqueSellingProducts` + ); + + const salesSnapshot = await get(salesRef); + const productsSnapshot = await get(productsRef); + + if (salesSnapshot.exists() && productsSnapshot.exists()) { + const salesData = salesSnapshot.val(); + const productsData = productsSnapshot.val(); + + // Merging salesPerUnit with uniqueSellingProducts + const mergedData = productsData.map((product, index) => ({ + id: product.product, + value: salesData[index].unitSales, + })); + + setData(mergedData); + } + setLoading(false); + } catch (error) { + console.error("Error fetching data:", error); + setLoading(false); + } + }; + + fetchData(); + }, []); + + if (loading) { + return
Loading...
; + } + return ( Date: Sat, 29 Jun 2024 20:42:29 +0530 Subject: [PATCH 41/83] add admin feedback --- src/scenes/AdminList/index.jsx | 226 +++++++++++++++++++++++++++------ src/scenes/login/index.jsx | 5 +- 2 files changed, 192 insertions(+), 39 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 04a7bff..74786d3 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -1,28 +1,24 @@ import { useState, useEffect } from "react"; -import { Box, Button, IconButton } from "@mui/material"; +import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, IconButton, InputLabel, TextField } from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { useTheme } from "@mui/material"; import { tokens } from "../../theme"; -import { - ref, - get, - remove, - onChildAdded, - onChildChanged, - onChildRemoved, - off, -} from "firebase/database"; +import { ref, get, set, remove, onChildAdded, onChildChanged, onChildRemoved, off } from "firebase/database"; import { auth, database } from "../../firebase"; import Header from "../../components/Header"; import DeleteIcon from "@mui/icons-material/Delete"; import ChatIcon from "@mui/icons-material/Chat"; import { useNavigate } from "react-router-dom"; +import { createUserWithEmailAndPassword } from "firebase/auth"; const AdminList = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const [admins, setAdmins] = useState([]); const [currentUserRole, setCurrentUserRole] = useState(null); // To store the current user's role + const [openDialog, setOpenDialog] = useState(false); + const [formData, setFormData] = useState({ name: "", email: "", password: "", role: "admin" }); + const [error, setError] = useState(""); const navigate = useNavigate(); useEffect(() => { @@ -42,13 +38,14 @@ const AdminList = () => { const feedbackSnapshot = await get(feedbackRef); const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() : {}; + // console.log(feedbackData) const adminListWithFeedback = adminList.map((admin) => { - const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : []; - + // console.log(admin.uid) + const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : Object.values(feedbackData[admin.id]); + // console.log(adminFeedbacks) // Get the most recent feedback - const latestFeedback = adminFeedbacks.reduce((latest, current) => { - return new Date(current.timestamp) > new Date(latest.timestamp) ? current : latest; - }, adminFeedbacks[0]); + const length = adminFeedbacks.length; + const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; return { ...admin, @@ -72,7 +69,6 @@ const AdminList = () => { }; fetchAdmins(); }, []); - useEffect(() => { const adminActivityRef = ref(database, "adminActivity"); @@ -81,15 +77,11 @@ const AdminList = () => { const data = snapshot.val(); const feedbackRef = ref(database, `feedback/${data.uid}`); const feedbackSnapshot = await get(feedbackRef); - const feedbacks = feedbackSnapshot.exists() - ? Object.values(feedbackSnapshot.val()) - : []; + const feedbacks = feedbackSnapshot.exists() ? Object.values(feedbackSnapshot.val()) : []; const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); setAdmins((prevAdmins) => { - const existingIndex = prevAdmins.findIndex( - (item) => item.id === snapshot.key - ); + const existingIndex = prevAdmins.findIndex((item) => item.id === snapshot.key); if (existingIndex !== -1) { const updatedAdmins = [...prevAdmins]; updatedAdmins[existingIndex] = { @@ -108,23 +100,12 @@ const AdminList = () => { }; const handleChildRemoved = (snapshot) => { - setAdmins((prevAdmins) => - prevAdmins.filter((item) => item.id !== snapshot.key) - ); + setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key)); }; - const childAddedListener = onChildAdded( - adminActivityRef, - handleChildAddedOrChanged - ); - const childChangedListener = onChildChanged( - adminActivityRef, - handleChildAddedOrChanged - ); - const childRemovedListener = onChildRemoved( - adminActivityRef, - handleChildRemoved - ); + const childAddedListener = onChildAdded(adminActivityRef, handleChildAddedOrChanged); + const childChangedListener = onChildChanged(adminActivityRef, handleChildAddedOrChanged); + const childRemovedListener = onChildRemoved(adminActivityRef, handleChildRemoved); return () => { off(adminActivityRef, "child_added", childAddedListener); @@ -146,6 +127,57 @@ const AdminList = () => { navigate(`/chat/${id}`); }; + const handleDialogOpen = () => { + setOpenDialog(true); + setFormData({ name: "", email: "", password: "", role: "admin" }); + setError(""); + }; + + const handleDialogClose = () => { + setOpenDialog(false); + }; + + const handleFormSubmit = async () => { + try { + const usersRef = ref(database, "users"); + const usersSnapshot = await get(usersRef); + const usersData = usersSnapshot.exists() ? usersSnapshot.val() : {}; + + // Check if the email is already in use + if (Object.values(usersData).some(user => user.email === formData.email)) { + setError("Email already in use"); + return; + } + + // Create the user with email and password in Firebase Auth + const userCredential = await createUserWithEmailAndPassword(auth, formData.email, formData.password); + const user = userCredential.user; + + // Add the new admin data to the users ref + await set(ref(database, `users/${user.uid}`), { + name: formData.name, + email: formData.email, + password: formData.password, + role: formData.role, + }); + + // Add the new admin data to the adminActivity ref + await set(ref(database, `adminActivity/${user.uid}`), { + name: formData.name, + email: formData.email, + signInTime: "new", + feedback: "---", + }); + + // Close the dialog and reset the form + handleDialogClose(); + } catch (error) { + console.error("Error adding admin:", error); + setError("Failed to add admin. Please try again."); + } + }; + + const columns = [ { field: "id", headerName: "ID", flex: 0.5 }, { field: "name", headerName: "Name", flex: 0.75 }, @@ -192,8 +224,40 @@ const AdminList = () => { return (
+ { components={{ Toolbar: GridToolbar }} /> + + + Add Admin + + + {error && {error}} + setFormData({ ...formData, name: e.target.value })} + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} + /> + + setFormData({ ...formData, email: e.target.value }) + } + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} + /> + + setFormData({ ...formData, password: e.target.value }) + } + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} + /> + + + + + + + ); }; diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 04582e9..7a90372 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Box, Button, TextField, Typography, useTheme } from "@mui/material"; import { tokens } from "../../theme"; import { auth, database } from "../../firebase"; // Import Firebase services @@ -12,8 +12,11 @@ const Login = ({ handleLoginSuccess }) => { const [password, setPassword] = useState(""); const [error, setError] = useState(""); + + const handleLogin = async () => { try { + const userCredential = await signInWithEmailAndPassword( auth, email, From e4ed362b4b1a579931a9082dea30ccc3170a0cd4 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 29 Jun 2024 21:33:01 +0530 Subject: [PATCH 42/83] added del button in team management --- src/scenes/team/index.jsx | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index da716e4..ea58b9a 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -30,10 +30,11 @@ import { get, update, set, + remove, } from "firebase/database"; import { v4 as uuidv4 } from "uuid"; import { createUserWithEmailAndPassword } from "firebase/auth"; - +import Delete from "@mui/icons-material/Delete"; const Team = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); @@ -112,6 +113,19 @@ const Team = () => { headerName: "Password", flex: 1, }, + { + field: "delete", + headerName: "Delete", + flex: 1, + renderCell: ({ row }) => ( + + ), + }, ]; if (currentUserRole === "admin") { @@ -248,6 +262,19 @@ const Team = () => { } handleDialogClose(); }; + const handleConfirmDelete = async (users) => { + try { + const userActivityRef = ref(database, `userActivity/${users.id}`); + await remove(userActivityRef); + const userRef = ref(database, `users/${user.id}`); + await remove(userRef); + setUserData((prevUserData) => + prevUserData.filter((user) => user.id !== users.id) + ); + } catch (error) { + console.error("Error deleting contact:", error); + } + }; const lampEffectStyle = { position: "relative", background: "linear-gradient(to top, #00bfff, transparent)", From 786f3c965b00f86c03eb0155f2d76f774edffeda Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 29 Jun 2024 23:48:29 +0530 Subject: [PATCH 43/83] form submissions listed --- src/scenes/form/Data.jsx | 290 ++++++++++++++++++++++++++++++++++++++ src/scenes/form/index.jsx | 68 ++++++++- 2 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 src/scenes/form/Data.jsx diff --git a/src/scenes/form/Data.jsx b/src/scenes/form/Data.jsx new file mode 100644 index 0000000..8ec07f6 --- /dev/null +++ b/src/scenes/form/Data.jsx @@ -0,0 +1,290 @@ +import { + Box, + Button, + Card, + CardContent, + Divider, + Grid, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + styled, + useTheme, +} from "@mui/material"; +import Header from "../../components/Header"; +import { tokens } from "../../theme"; + +// Custom styled components +const SubmissionBox = styled(Card)(({ theme }) => ({ + marginTop: "20px", + padding: "20px", + background: "transparent", // Remove background for lamp effect + borderRadius: "8px", + boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)", // Subtle box shadow +})); + +const LampEffect = styled(CardContent)(({ theme }) => ({ + position: "relative", + background: "linear-gradient(to top, #00bfff, transparent)", + "&::after": { + content: '""', + position: "absolute", + left: 0, + + width: "100%", + + background: "linear-gradient(to top, rgba(0, 191, 255, 0.8), transparent)", + boxShadow: + "0 0 10px rgba(0, 191, 255, 0.8), 0 0 20px rgba(0, 191, 255, 0.8), 0 0 30px rgba(0, 191, 255, 0.8)", + }, +})); + +const SectionTitle = styled(Typography)(({ theme }) => ({ + marginTop: "10px", + marginBottom: "10px", + fontWeight: "bold", + color: theme.palette.text.primary, // Use primary text color +})); + +const SubmissionList = ({ formSubmissions, handleEditForm }) => { + const theme = useTheme(); + const colors = tokens(theme.palette.mode); + + const ButtonS = styled(Button)(({ theme }) => ({ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + })); + + const handleEdit = (submission, index) => { + handleEditForm(submission, index); + }; + + return ( + + {formSubmissions.length > 0 && ( + +
+ {formSubmissions.map((submission, index) => ( + + + + + + + Submission {index + 1} + + handleEdit(submission, index)} + > + Edit + + + + + Company Name : {submission.companyName} + + + + + Email : {submission.email} + + + {/* Table sections */} + + + Sales Per Month + + + + + + + + Month + + + + + Amount + + + + + Country + + + + + + {submission.salesPerMonth.map((sale, idx) => ( + + {sale.month} + {sale.amount} + {sale.country} + + ))} + +
+
+
+ + + Unique Selling Products + + + + + + + + Product + + + + + Country + + + + + + {submission.uniqueSellingProducts.map( + (product, idx) => ( + + {product.product} + {product.country} + + ) + )} + +
+
+
+ + + Sales Per Unit + + + + + + + + Country + + + + + Unit Sales + + + + + + {submission.salesPerUnit.map((unit, idx) => ( + + {unit.country} + {unit.unitSales} + + ))} + +
+
+
+ + + Locations + + + + + + + + Location + + + + + + {submission.locations.map((location, idx) => ( + + {location} + + ))} + +
+
+
+
+
+
+
+ ))} + + )} + + ); + }; + + export default SubmissionList; \ No newline at end of file diff --git a/src/scenes/form/index.jsx b/src/scenes/form/index.jsx index cf6ec90..a11e6bf 100644 --- a/src/scenes/form/index.jsx +++ b/src/scenes/form/index.jsx @@ -1,5 +1,5 @@ import { Box, Button, TextField, Autocomplete, useTheme } from "@mui/material"; -import { Formik, FieldArray } from "formik"; +import { Formik, FieldArray, useFormikContext } from "formik"; import * as yup from "yup"; import useMediaQuery from "@mui/material/useMediaQuery"; import Header from "../../components/Header"; @@ -8,12 +8,23 @@ import { get, getDatabase, ref, set } from "firebase/database"; import { useEffect, useState } from "react"; import { database } from "../../firebase"; import { tokens } from "../../theme"; +import SubmissionList from "./Data"; const Form = () => { const theme = useTheme(); const [data, setData] = useState([]); + const [formSubmissions, setFormSubmissions] = useState([]); + const [editIndex, setEditIndex] = useState(null); // Track edit index + const [initialValues, setInitialValues] = useState({ + companyName: "", + email: "", + salesPerMonth: [{ month: "", amount: "", country: "" }], + uniqueSellingProducts: [{ product: "", country: "" }], + salesPerUnit: [{ country: "", unitSales: "" }], + locations: [""], + }); + const isNonMobile = useMediaQuery("(min-width:600px)"); - // console.log(countryData); const colors = tokens(theme.palette.mode); const handleFormSubmit = (values) => { @@ -22,6 +33,17 @@ const Form = () => { const user = auth.currentUser; if (user) { + if (editIndex !== null) { + setFormSubmissions((prev) => { + const updatedSubmissions = [...prev]; + updatedSubmissions[editIndex] = values; + return updatedSubmissions; + }); + setEditIndex(null); + } else { + setFormSubmissions((prev) => [...prev, values]); + } + set(ref(database, "users/" + user.uid + "/formData"), values) .then(() => { console.log("Data saved successfully!"); @@ -35,11 +57,9 @@ const Form = () => { useEffect(() => { const fetchCountrydata = async () => { try { - // Fetch locations data from Firebase Realtime Database const snapshot = await get(ref(database, "countryData")); const data = snapshot.val(); setData(data); - // console.log(data); } catch (err) { console.log(err); } @@ -48,6 +68,40 @@ const Form = () => { fetchCountrydata(); }, []); + useEffect(() => { + const auth = getAuth(); + const database = getDatabase(); + const user = auth.currentUser; + + if (user) { + const userRef = ref(database, "users/" + user.uid + "/formData"); + get(userRef) + .then((snapshot) => { + if (snapshot.exists()) { + const data = snapshot.val(); + setFormSubmissions((prev) => [...prev, data]); + } else { + console.log("No data available"); + } + }) + .catch((error) => { + console.error("Error fetching data:", error); + }); + } + }, []); + + const handleEditForm = (existingFormData, index) => { + // move to top of the form + window.scrollTo(0, 0); + setFormSubmissions((prev) => { + const updatedSubmissions = [...prev]; + updatedSubmissions.splice(index, 1); + return updatedSubmissions; + }); + setEditIndex(index); + setInitialValues(existingFormData); + }; + return (
@@ -55,6 +109,7 @@ const Form = () => { onSubmit={handleFormSubmit} initialValues={initialValues} validationSchema={checkoutSchema} + enableReinitialize // Add this to reinitialize form with initialValues > {({ values, @@ -63,6 +118,7 @@ const Form = () => { handleBlur, handleChange, handleSubmit, + setValues, // Add setValues here to update form values }) => (
{ )} +
); }; From 3619037b9f3a11045d3c7e53cc0cee0aada9b390 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Sat, 29 Jun 2024 23:58:22 +0530 Subject: [PATCH 44/83] Add edit form and reset after each submission --- src/scenes/form/Data.jsx | 12 ------------ src/scenes/form/index.jsx | 10 ++++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/scenes/form/Data.jsx b/src/scenes/form/Data.jsx index 8ec07f6..724a962 100644 --- a/src/scenes/form/Data.jsx +++ b/src/scenes/form/Data.jsx @@ -254,18 +254,6 @@ const SubmissionList = ({ formSubmissions, handleEditForm }) => { - - - - - Location - - - - {submission.locations.map((location, idx) => ( diff --git a/src/scenes/form/index.jsx b/src/scenes/form/index.jsx index a11e6bf..a2bef28 100644 --- a/src/scenes/form/index.jsx +++ b/src/scenes/form/index.jsx @@ -52,6 +52,16 @@ const Form = () => { console.error("Error saving data:", error); }); } + + //clear form + setInitialValues({ + companyName: "", + email: "", + salesPerMonth: [{ month: "", amount: "", country: "" }], + uniqueSellingProducts: [{ product: "", country: "" }], + salesPerUnit: [{ country: "", unitSales: "" }], + locations: [""], + }); }; useEffect(() => { From 8830de1e5b1133c71fa8209fc1fe01a3b6c251a6 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sun, 30 Jun 2024 10:48:22 +0530 Subject: [PATCH 45/83] added chat header & remove biew profile button & added glow effect --- src/scenes/AdminList/index.jsx | 204 +++++++++++++++++++++------------ 1 file changed, 133 insertions(+), 71 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 74786d3..d4d26c0 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -1,9 +1,29 @@ import { useState, useEffect } from "react"; -import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, IconButton, InputLabel, TextField } from "@mui/material"; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControl, + IconButton, + InputLabel, + TextField, +} from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { useTheme } from "@mui/material"; import { tokens } from "../../theme"; -import { ref, get, set, remove, onChildAdded, onChildChanged, onChildRemoved, off } from "firebase/database"; +import { + ref, + get, + set, + remove, + onChildAdded, + onChildChanged, + onChildRemoved, + off, +} from "firebase/database"; import { auth, database } from "../../firebase"; import Header from "../../components/Header"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -17,7 +37,12 @@ const AdminList = () => { const [admins, setAdmins] = useState([]); const [currentUserRole, setCurrentUserRole] = useState(null); // To store the current user's role const [openDialog, setOpenDialog] = useState(false); - const [formData, setFormData] = useState({ name: "", email: "", password: "", role: "admin" }); + const [formData, setFormData] = useState({ + name: "", + email: "", + password: "", + role: "admin", + }); const [error, setError] = useState(""); const navigate = useNavigate(); @@ -27,34 +52,38 @@ const AdminList = () => { const snapshot = await get(adminsRef); if (snapshot.exists()) { const users = snapshot.val(); - + const adminList = Object.keys(users).map((key) => ({ id: key, ...users[key], })); - + // Fetch feedbacks and merge with admin data const feedbackRef = ref(database, "feedback"); const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() : {}; - + const feedbackData = feedbackSnapshot.exists() + ? feedbackSnapshot.val() + : {}; + // console.log(feedbackData) const adminListWithFeedback = adminList.map((admin) => { // console.log(admin.uid) - const adminFeedbacks = feedbackData[admin.uid] ? Object.values(feedbackData[admin.uid]) : Object.values(feedbackData[admin.id]); + const adminFeedbacks = feedbackData[admin.uid] + ? Object.values(feedbackData[admin.uid]) + : []; // console.log(adminFeedbacks) // Get the most recent feedback const length = adminFeedbacks.length; const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; - + return { ...admin, feedback: latestFeedback ? latestFeedback.feedback : "No feedback", }; }); - + setAdmins(adminListWithFeedback); - + // Get the current user's ID and role const currentUserId = auth.currentUser.uid; const usersRef = ref(database, "users"); @@ -77,11 +106,15 @@ const AdminList = () => { const data = snapshot.val(); const feedbackRef = ref(database, `feedback/${data.uid}`); const feedbackSnapshot = await get(feedbackRef); - const feedbacks = feedbackSnapshot.exists() ? Object.values(feedbackSnapshot.val()) : []; + const feedbacks = feedbackSnapshot.exists() + ? Object.values(feedbackSnapshot.val()) + : []; const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); setAdmins((prevAdmins) => { - const existingIndex = prevAdmins.findIndex((item) => item.id === snapshot.key); + const existingIndex = prevAdmins.findIndex( + (item) => item.id === snapshot.key + ); if (existingIndex !== -1) { const updatedAdmins = [...prevAdmins]; updatedAdmins[existingIndex] = { @@ -100,12 +133,23 @@ const AdminList = () => { }; const handleChildRemoved = (snapshot) => { - setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key)); + setAdmins((prevAdmins) => + prevAdmins.filter((item) => item.id !== snapshot.key) + ); }; - const childAddedListener = onChildAdded(adminActivityRef, handleChildAddedOrChanged); - const childChangedListener = onChildChanged(adminActivityRef, handleChildAddedOrChanged); - const childRemovedListener = onChildRemoved(adminActivityRef, handleChildRemoved); + const childAddedListener = onChildAdded( + adminActivityRef, + handleChildAddedOrChanged + ); + const childChangedListener = onChildChanged( + adminActivityRef, + handleChildAddedOrChanged + ); + const childRemovedListener = onChildRemoved( + adminActivityRef, + handleChildRemoved + ); return () => { off(adminActivityRef, "child_added", childAddedListener); @@ -142,17 +186,23 @@ const AdminList = () => { const usersRef = ref(database, "users"); const usersSnapshot = await get(usersRef); const usersData = usersSnapshot.exists() ? usersSnapshot.val() : {}; - + // Check if the email is already in use - if (Object.values(usersData).some(user => user.email === formData.email)) { + if ( + Object.values(usersData).some((user) => user.email === formData.email) + ) { setError("Email already in use"); return; } - + // Create the user with email and password in Firebase Auth - const userCredential = await createUserWithEmailAndPassword(auth, formData.email, formData.password); + const userCredential = await createUserWithEmailAndPassword( + auth, + formData.email, + formData.password + ); const user = userCredential.user; - + // Add the new admin data to the users ref await set(ref(database, `users/${user.uid}`), { name: formData.name, @@ -160,7 +210,7 @@ const AdminList = () => { password: formData.password, role: formData.role, }); - + // Add the new admin data to the adminActivity ref await set(ref(database, `adminActivity/${user.uid}`), { name: formData.name, @@ -168,7 +218,7 @@ const AdminList = () => { signInTime: "new", feedback: "---", }); - + // Close the dialog and reset the form handleDialogClose(); } catch (error) { @@ -176,7 +226,6 @@ const AdminList = () => { setError("Failed to add admin. Please try again."); } }; - const columns = [ { field: "id", headerName: "ID", flex: 0.5 }, @@ -190,14 +239,6 @@ const AdminList = () => { flex: 1, renderCell: (params) => ( <> - {currentUserRole === "superadmin" && ( <> { > - handleChat(params.id)} - > - - )} ), }, + { + field: "chat", + headerName: "Chat", + flex: 0.5, + renderCell: (params) => ( + <> + {currentUserRole === "superadmin" && ( + handleChat(params.id)} + > + + + )} + + ), + }, ]; return (
- { rows={admins} columns={columns} components={{ Toolbar: GridToolbar }} + sx={{ + border: `20px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + }} /> - + Add Admin - {error && {error}} + {error && {error}} Date: Sun, 30 Jun 2024 16:43:39 +0530 Subject: [PATCH 46/83] feat added chat feature --- src/App.js | 11 ++- src/components/Notification.jsx | 97 ++++++++++++++++++++++ src/scenes/AdminList/index.jsx | 60 +++++++++++++- src/scenes/global/Topbar.jsx | 141 ++++++++++++++++++++------------ 4 files changed, 254 insertions(+), 55 deletions(-) create mode 100644 src/components/Notification.jsx diff --git a/src/App.js b/src/App.js index 3d75661..66d693e 100644 --- a/src/App.js +++ b/src/App.js @@ -17,6 +17,7 @@ import Calender from "./scenes/calendar/calendar"; import Login from "./scenes/login/index"; import AdminList from "./scenes/AdminList"; import Feedback from "./scenes/feedback"; +import Notifications from "./components/Notification"; function App() { const [theme, colorMode] = useMode(); @@ -33,7 +34,9 @@ function App() { setLoggedIn(true); setUserRole(storedUser.role); setSidebarVisible(storedUser.role === "admin"); - (storedUser.role === "superadmin") ? navigate("/admins") : navigate("/dashboard"); + storedUser.role === "superadmin" + ? navigate("/admins") + : navigate("/dashboard"); } }, []); @@ -62,8 +65,12 @@ function App() {
{showTopbar && } - } /> + } + /> } /> + } /> } /> } /> } /> diff --git a/src/components/Notification.jsx b/src/components/Notification.jsx new file mode 100644 index 0000000..6b3a713 --- /dev/null +++ b/src/components/Notification.jsx @@ -0,0 +1,97 @@ +import React, { useEffect, useState } from "react"; +import { + Box, + Typography, + Card, + CardContent, + Grid, + IconButton, + useTheme, +} from "@mui/material"; +import { Delete as DeleteIcon } from "@mui/icons-material"; +import { database } from "../firebase"; +import { ref, get, remove } from "firebase/database"; +import { tokens } from "../theme"; + +const Notifications = () => { + const [messages, setMessages] = useState([]); + const theme = useTheme(); + const colors = tokens(theme.palette.mode); + + useEffect(() => { + const fetchMessages = async () => { + const messagesRef = ref(database, "messages"); + const snapshot = await get(messagesRef); + if (snapshot.exists()) { + const messagesData = snapshot.val(); + const allMessages = Object.entries(messagesData).flatMap( + ([userId, userMessages]) => + Object.entries(userMessages).map(([msgId, message]) => ({ + ...message, + msgId, + userId, + })) + ); + setMessages(allMessages); + } + }; + + fetchMessages(); + }, []); + + const handleDelete = async (userId, msgId) => { + const messageRef = ref(database, `messages/${userId}/${msgId}`); + try { + await remove(messageRef); + setMessages((prevMessages) => + prevMessages.filter((msg) => msg.msgId !== msgId) + ); + } catch (error) { + console.error("Error deleting message:", error); + } + }; + + return ( + + + Notifications + + + {messages.map((msg, index) => ( + + + + + + + {msg.message} + + + Sent by: {msg.sender} + + + {new Date(msg.timestamp).toLocaleString()} + + + handleDelete(msg.userId, msg.msgId)} + > + + + + + + + ))} + + + ); +}; + +export default Notifications; diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index d4d26c0..d0dcaea 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -43,6 +43,12 @@ const AdminList = () => { password: "", role: "admin", }); + const [openChatDialog, setOpenChatDialog] = useState(false); +const [chatMessage, setChatMessage] = useState(""); +const [selectedAdminId, setSelectedAdminId] = useState(null); +const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); +const [messages, setMessages] = useState([]); + const [error, setError] = useState(""); const navigate = useNavigate(); @@ -168,7 +174,9 @@ const AdminList = () => { }; const handleChat = (id) => { - navigate(`/chat/${id}`); + setSelectedAdminId(id); + setOpenChatDialog(true); + }; const handleDialogOpen = () => { @@ -272,7 +280,22 @@ const AdminList = () => { ), }, ]; - + const handleSendMessage = async () => { + if (chatMessage.trim() === "") return; + + const newMessageRef = ref( + database, + `messages/${selectedAdminId}/${Date.now()}` + ); + await set(newMessageRef, { + message: chatMessage, + timestamp: Date.now(), + sender: "superadmin", + }); + + setChatMessage(""); + setOpenChatDialog(false); + }; return (
@@ -442,6 +465,39 @@ const AdminList = () => {
+ setOpenChatDialog(false)}> + + Send Message + + + setChatMessage(e.target.value)} + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} + /> + + + + + + ); }; diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 3eb09be..283315f 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -1,23 +1,26 @@ -import { Box, Button, IconButton, useTheme } from "@mui/material"; +import { Box, Button, IconButton, useTheme, Badge } from "@mui/material"; import { tokens } from "../../theme"; -import InputBase from "@mui/material/InputBase"; - -import { useNavigate } from "react-router-dom"; -import { auth } from "../../firebase"; // Import Firebase auth +import { useNavigate, useLocation } from "react-router-dom"; +import { auth, database } from "../../firebase"; // Import Firebase auth import { signOut } from "firebase/auth"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { get, ref } from "firebase/database"; +import NotificationsIcon from "@mui/icons-material/Notifications"; const Topbar = ({ handleLogout }) => { const theme = useTheme(); const colors = tokens(theme.palette.mode); const navigate = useNavigate(); + const location = useLocation(); + const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); + const [messages, setMessages] = useState([]); + const [currentUserRole, setCurrentUserRole] = useState(null); const handleLogoutdb = async () => { try { await signOut(auth); // Sign out from Firebase - localStorage.removeItem("user"); // Remove user information from localStorage4 + localStorage.removeItem("user"); // Remove user information from localStorage handleLogout(); // Call the parent function to update the state - console.log(localStorage.getItem("user")); navigate("/"); // Redirect to login page } catch (error) { console.error("Error logging out:", error); @@ -29,53 +32,89 @@ const Topbar = ({ handleLogout }) => { const storedUser = JSON.parse(localStorage.getItem("user")); if (!storedUser) { navigate("/"); + return; } - }, []); - return ( - - + const fetchMessagesAndUserRole = async () => { + const currentUser = auth.currentUser; + if (!currentUser) { + navigate("/"); + return; + } + + const userRef = ref(database, `users/${currentUser.uid}`); + const userSnapshot = await get(userRef); + let userData; + if (userSnapshot.exists()) { + userData = userSnapshot.val(); + setCurrentUserRole(userData.role); + } + if (userData && userData.role === "admin") { + const messagesRef = ref(database, "messages"); + const snapshot = await get(messagesRef); + if (snapshot.exists()) { + const messagesData = snapshot.val(); + const allMessages = Object.values(messagesData).flatMap((userMessages) => + Object.values(userMessages) + ); + setMessages(allMessages); + setUnreadMessagesCount(allMessages.length); // Update this logic to count only unread messages if necessary + } + } + }; + + fetchMessagesAndUserRole(); + }, [navigate]); + + return ( + + {currentUserRole === "admin" && location.pathname !== "/admins" && ( + + navigate("/notifications")}> + + + + + + )} - - - - - + + ); }; From 0052ed6340a05670212d0e880013c7ebf8d80661 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sun, 30 Jun 2024 16:54:59 +0530 Subject: [PATCH 47/83] fixed position of dialouge box --- src/components/EventDialouge.jsx | 4 ++-- src/scenes/contacts/index.jsx | 4 ++-- src/scenes/team/index.jsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/EventDialouge.jsx b/src/components/EventDialouge.jsx index bc134ee..f12a6db 100644 --- a/src/components/EventDialouge.jsx +++ b/src/components/EventDialouge.jsx @@ -20,11 +20,11 @@ const EventDialog = ({ open, onClose, onSave }) => { setTitle(""); }; const lampEffectStyle = { - position: "relative", + position: "absolute", background: "linear-gradient(to top, #00bfff, transparent)", "&::after": { content: '""', - position: "absolute", + position: "relative", left: 0, width: "100%", diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index 5f0200a..da2a49a 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -165,11 +165,11 @@ const Contacts = () => { } }; const lampEffectStyle = { - position: "relative", + position: "absolute", background: "linear-gradient(to top, #00bfff, transparent)", "&::after": { content: '""', - position: "absolute", + position: "relative", left: 0, width: "100%", diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index ea58b9a..a0fa5e7 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -276,11 +276,11 @@ const Team = () => { } }; const lampEffectStyle = { - position: "relative", + position: "absolute", background: "linear-gradient(to top, #00bfff, transparent)", "&::after": { content: '""', - position: "absolute", + position: "relative", left: 0, width: "100%", From 13be4860fe8c2f84e0feeb097b5c4881183f5b74 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sun, 30 Jun 2024 18:06:43 +0530 Subject: [PATCH 48/83] feat added page for user --- src/App.js | 26 +++++++--- src/components/HoverEffect.jsx | 91 ++++++++++++++++++++++++++++++++++ src/components/Pages.jsx | 39 +++++++++++++++ 3 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 src/components/HoverEffect.jsx create mode 100644 src/components/Pages.jsx diff --git a/src/App.js b/src/App.js index 66d693e..b4afb76 100644 --- a/src/App.js +++ b/src/App.js @@ -13,12 +13,13 @@ import FAQ from "./scenes/faq"; import Geography from "./scenes/geography"; import { CssBaseline, ThemeProvider } from "@mui/material"; import { ColorModeContext, useMode } from "./theme"; -import Calender from "./scenes/calendar/calendar"; +import Calendar from "./scenes/calendar/calendar"; import Login from "./scenes/login/index"; import AdminList from "./scenes/AdminList"; import Feedback from "./scenes/feedback"; import Notifications from "./components/Notification"; - + // Import the new page component +import Page from "./components/Pages"; function App() { const [theme, colorMode] = useMode(); const [isSidebarVisible, setSidebarVisible] = useState(false); @@ -34,9 +35,13 @@ function App() { setLoggedIn(true); setUserRole(storedUser.role); setSidebarVisible(storedUser.role === "admin"); - storedUser.role === "superadmin" - ? navigate("/admins") - : navigate("/dashboard"); + if (storedUser.role === "superadmin") { + navigate("/admins"); + } else if (storedUser.role === "user") { + navigate("/page"); // Navigate to the new page for "user" role + } else { + navigate("/dashboard"); + } } }, []); @@ -44,7 +49,13 @@ function App() { setLoggedIn(true); setUserRole(role); setSidebarVisible(role === "admin"); - navigate(role === "superadmin" ? "/admins" : "/dashboard"); + if (role === "superadmin") { + navigate("/admins"); + } else if (role === "user") { + navigate("/page"); // Navigate to the new page for "user" role + } else { + navigate("/dashboard"); + } }; const handleLogout = () => { @@ -80,8 +91,9 @@ function App() { } /> } /> } /> - } /> + } /> } /> + } /> {/* Add the new page route */} diff --git a/src/components/HoverEffect.jsx b/src/components/HoverEffect.jsx new file mode 100644 index 0000000..d2d73ef --- /dev/null +++ b/src/components/HoverEffect.jsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import Link from 'next/link'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useTheme, Card, CardContent, Typography, Box } from '@mui/material'; + +const HoverEffect = ({ items }) => { + const [hoveredIndex, setHoveredIndex] = useState(null); + const theme = useTheme(); + const colors = theme.palette; + + return ( + + {items.map((item, idx) => ( + setHoveredIndex(idx)} + onMouseLeave={() => setHoveredIndex(null)} + > + + {hoveredIndex === idx && ( + + )} + + + + ))} + + ); +}; + +const TodoCard = ({ title, description }) => { + const theme = useTheme(); + const colors = theme.palette; + + return ( + + + + {title} + + + {description} + + + + ); +}; + +export default HoverEffect; diff --git a/src/components/Pages.jsx b/src/components/Pages.jsx new file mode 100644 index 0000000..656eec3 --- /dev/null +++ b/src/components/Pages.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import HoverEffect from './HoverEffect'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { Box } from '@mui/material'; + +const items = [ + { title: 'Task 1', description: 'Description for task 1', link: '/task1' }, + { title: 'Task 2', description: 'Description for task 2', link: '/task2' }, + { title: 'Task 3', description: 'Description for task 3', link: '/task3' }, + // Add more items as needed +]; + +const theme = createTheme({ + palette: { + mode: 'dark', + background: { + default: '#1c1c1c', + paper: '#2c2c2c', + }, + text: { + primary: '#e0e0e0', + secondary: '#a0a0a0', + }, + }, +}); + +const Page = () => { + return ( + + + + + + + ); +}; + +export default Page; From 4312dfe4c32a0fad3ee9c703537b3b50ce959d44 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sun, 30 Jun 2024 18:07:14 +0530 Subject: [PATCH 49/83] fixed link --- src/components/HoverEffect.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/HoverEffect.jsx b/src/components/HoverEffect.jsx index d2d73ef..323c85f 100644 --- a/src/components/HoverEffect.jsx +++ b/src/components/HoverEffect.jsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; -import Link from 'next/link'; + import { AnimatePresence, motion } from 'framer-motion'; import { useTheme, Card, CardContent, Typography, Box } from '@mui/material'; +import { Link } from 'react-router-dom'; const HoverEffect = ({ items }) => { const [hoveredIndex, setHoveredIndex] = useState(null); From 7d65ffcd8d70df8508b817dbf77db502154c8d3a Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sun, 30 Jun 2024 21:39:24 +0530 Subject: [PATCH 50/83] feat added glow --- src/components/HoverEffect.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/HoverEffect.jsx b/src/components/HoverEffect.jsx index 323c85f..07e1fce 100644 --- a/src/components/HoverEffect.jsx +++ b/src/components/HoverEffect.jsx @@ -3,20 +3,23 @@ import React, { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; import { useTheme, Card, CardContent, Typography, Box } from '@mui/material'; import { Link } from 'react-router-dom'; +import { tokens } from '../theme'; const HoverEffect = ({ items }) => { const [hoveredIndex, setHoveredIndex] = useState(null); const theme = useTheme(); - const colors = theme.palette; + const colors = tokens(theme.palette.mode); return ( {items.map((item, idx) => ( From 527a8b3daec297a62e0b9697afe0025a31e3a28a Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 2 Jul 2024 21:38:54 +0530 Subject: [PATCH 51/83] feat added admin and rolemail db in adminlist --- src/scenes/AdminList/index.jsx | 175 ++++++++++++++++----------------- 1 file changed, 84 insertions(+), 91 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index d0dcaea..e7a7ce0 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -44,17 +44,17 @@ const AdminList = () => { role: "admin", }); const [openChatDialog, setOpenChatDialog] = useState(false); -const [chatMessage, setChatMessage] = useState(""); -const [selectedAdminId, setSelectedAdminId] = useState(null); -const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); -const [messages, setMessages] = useState([]); + const [chatMessage, setChatMessage] = useState(""); + const [selectedAdminId, setSelectedAdminId] = useState(null); + const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); + const [messages, setMessages] = useState([]); const [error, setError] = useState(""); const navigate = useNavigate(); useEffect(() => { const fetchAdmins = async () => { - const adminsRef = ref(database, "adminActivity"); + const adminsRef = ref(database, "admins"); const snapshot = await get(adminsRef); if (snapshot.exists()) { const users = snapshot.val(); @@ -92,13 +92,12 @@ const [messages, setMessages] = useState([]); // Get the current user's ID and role const currentUserId = auth.currentUser.uid; - const usersRef = ref(database, "users"); - const usersSnapshot = await get(usersRef); - if (usersSnapshot.exists()) { - const usersData = usersSnapshot.val(); - if (usersData[currentUserId]) { - setCurrentUserRole(usersData[currentUserId].role); - } + const roleMailRef = ref(database, `rolemail/${currentUserId}`); + const roleMailSnapshot = await get(roleMailRef); + if (roleMailSnapshot.exists()) { + const currentUserData = roleMailSnapshot.val(); + + setCurrentUserRole(currentUserData.role); } } }; @@ -106,7 +105,7 @@ const [messages, setMessages] = useState([]); }, []); useEffect(() => { - const adminActivityRef = ref(database, "adminActivity"); + const adminsRef = ref(database, "admins"); const handleChildAddedOrChanged = async (snapshot) => { const data = snapshot.val(); @@ -145,28 +144,25 @@ const [messages, setMessages] = useState([]); }; const childAddedListener = onChildAdded( - adminActivityRef, + adminsRef, handleChildAddedOrChanged ); const childChangedListener = onChildChanged( - adminActivityRef, + adminsRef, handleChildAddedOrChanged ); - const childRemovedListener = onChildRemoved( - adminActivityRef, - handleChildRemoved - ); + const childRemovedListener = onChildRemoved(adminsRef, handleChildRemoved); return () => { - off(adminActivityRef, "child_added", childAddedListener); - off(adminActivityRef, "child_changed", childChangedListener); - off(adminActivityRef, "child_removed", childRemovedListener); + off(adminsRef, "child_added", childAddedListener); + off(adminsRef, "child_changed", childChangedListener); + off(adminsRef, "child_removed", childRemovedListener); }; }, []); const handleDelete = async (id) => { try { - await remove(ref(database, `adminActivity/${id}`)); + await remove(ref(database, `admins/${id}`)); setAdmins(admins.filter((admin) => admin.id !== id)); } catch (error) { console.error("Error deleting admin:", error); @@ -175,8 +171,7 @@ const [messages, setMessages] = useState([]); const handleChat = (id) => { setSelectedAdminId(id); - setOpenChatDialog(true); - + setOpenChatDialog(true); }; const handleDialogOpen = () => { @@ -191,13 +186,17 @@ const [messages, setMessages] = useState([]); const handleFormSubmit = async () => { try { - const usersRef = ref(database, "users"); - const usersSnapshot = await get(usersRef); - const usersData = usersSnapshot.exists() ? usersSnapshot.val() : {}; + const roleMailRef = ref(database, "rolemail"); + const roleMailSnapshot = await get(roleMailRef); + const roleMailData = roleMailSnapshot.exists() + ? roleMailSnapshot.val() + : {}; // Check if the email is already in use if ( - Object.values(usersData).some((user) => user.email === formData.email) + Object.values(roleMailData).some( + (entry) => entry.email === formData.email + ) ) { setError("Email already in use"); return; @@ -211,20 +210,18 @@ const [messages, setMessages] = useState([]); ); const user = userCredential.user; - // Add the new admin data to the users ref - await set(ref(database, `users/${user.uid}`), { - name: formData.name, + // Add the new admin role and email to the rolemail ref + await set(ref(database, `rolemail/${user.uid}`), { email: formData.email, - password: formData.password, role: formData.role, }); - // Add the new admin data to the adminActivity ref - await set(ref(database, `adminActivity/${user.uid}`), { + // Add the new admin data to the admins ref + await set(ref(database, `admins/${user.uid}`), { name: formData.name, email: formData.email, - signInTime: "new", - feedback: "---", + password: formData.password, + role: formData.role, }); // Close the dialog and reset the form @@ -247,17 +244,15 @@ const [messages, setMessages] = useState([]); flex: 1, renderCell: (params) => ( <> - {currentUserRole === "superadmin" && ( - <> - handleDelete(params.id)} - > - - - - )} + <> + handleDelete(params.id)} + > + + + ), }, @@ -267,22 +262,20 @@ const [messages, setMessages] = useState([]); flex: 0.5, renderCell: (params) => ( <> - {currentUserRole === "superadmin" && ( - handleChat(params.id)} - > - - - )} + handleChat(params.id)} + > + + ), }, ]; const handleSendMessage = async () => { if (chatMessage.trim() === "") return; - + const newMessageRef = ref( database, `messages/${selectedAdminId}/${Date.now()}` @@ -292,7 +285,7 @@ const [messages, setMessages] = useState([]); timestamp: Date.now(), sender: "superadmin", }); - + setChatMessage(""); setOpenChatDialog(false); }; @@ -466,38 +459,38 @@ const [messages, setMessages] = useState([]); setOpenChatDialog(false)}> - - Send Message - - - setChatMessage(e.target.value)} - sx={{ - marginBottom: "10px", - boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", - "&:hover": { - boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", - }, - }} - /> - - - - - - + + Send Message + + + setChatMessage(e.target.value)} + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} + /> + + + + + + ); }; From fa10894371ca759829932fda3d384c38acbd0783 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 2 Jul 2024 23:05:04 +0530 Subject: [PATCH 52/83] blank --- src/scenes/AdminList/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index e7a7ce0..8751e90 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -266,6 +266,7 @@ const AdminList = () => { color="primary" sx={{ color: "#ffffff" }} onClick={() => handleChat(params.id)} + > From 5e1cc36f2a2dd381e4aad6baa46819cc0876556b Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 3 Jul 2024 08:20:07 +0530 Subject: [PATCH 53/83] feat added admin database rolemail and admins , fetch login from there --- src/scenes/AdminList/index.jsx | 1 - src/scenes/login/index.jsx | 118 ++++++++++++++------------------- 2 files changed, 48 insertions(+), 71 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 8751e90..e7a7ce0 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -266,7 +266,6 @@ const AdminList = () => { color="primary" sx={{ color: "#ffffff" }} onClick={() => handleChat(params.id)} - > diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 7a90372..2cb1573 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -12,11 +12,9 @@ const Login = ({ handleLoginSuccess }) => { const [password, setPassword] = useState(""); const [error, setError] = useState(""); - - const handleLogin = async () => { try { - + // Sign in with email and password const userCredential = await signInWithEmailAndPassword( auth, email, @@ -24,84 +22,64 @@ const Login = ({ handleLoginSuccess }) => { ); const user = userCredential.user; - // Check user role from the database - const userRoleRef = ref(database, "users/" + user.uid + "/role"); - const snapshot = await get(userRoleRef); - - if (snapshot.exists()) { - const role = snapshot.val(); - - // Get user name - const userNameRef = ref(database, "users/" + user.uid + "/name"); - const userNameSnapshot = await get(userNameRef); - const userName = userNameSnapshot.exists() - ? userNameSnapshot.val() - : "Unknown User"; - - // Record the sign-in activity - // Record the sign-in activity - if (role === "user" || role === "admin") { - const activityRef = ref(database, `${role}Activity`); - - // Fetch existing activities - const existingActivitiesSnapshot = await get(activityRef); - let existingActivityKey = null; - - if (existingActivitiesSnapshot.exists()) { - const activities = existingActivitiesSnapshot.val(); - for (const [key, activity] of Object.entries(activities)) { - if (activity.email === user.email) { - existingActivityKey = key; - break; + // Fetch the role from the rolemail node + const roleMailRef = ref(database, "rolemail"); + console.log("roleMailRef", roleMailRef); + const roleMailSnapshot = await get(roleMailRef); + console.log("RoleMailSnapshot:", roleMailSnapshot); + + if (roleMailSnapshot.exists()) { + const roleMailData = roleMailSnapshot.val(); + console.log("RoleMailData:", roleMailData); + const userEntry = Object.entries(roleMailData).find( + ([key, value]) => value.email === email + ); + console.log("UserEntry:", userEntry); + + if (userEntry) { + const [userId, userInfo] = userEntry; + const { role } = userInfo; + console.log("Role:", role); + + if (role === "admin") { + // For admin, check the password in the admins node + const adminRef = ref(database, "admins/" + userId); + const adminSnapshot = await get(adminRef); + if (adminSnapshot.exists()) { + const adminData = adminSnapshot.val(); + if (adminData.password === password) { + // Admin authenticated successfully + handleLoginSuccess(role); + // Store user information in localStorage + localStorage.setItem( + "user", + JSON.stringify({ uid: userId, role }) + ); + return; + } else { + setError("Invalid admin credentials"); + return; } + } else { + setError("Admin not found"); + return; } - } - - const now = new Date(); - const hours = now.getHours().toString().padStart(2, "0"); - const minutes = now.getMinutes().toString().padStart(2, "0"); - const time = `${hours}:${minutes}`; - const day = now.getDate().toString().padStart(2, "0"); - const month = (now.getMonth() + 1).toString().padStart(2, "0"); - const year = now.getFullYear().toString().slice(-2); - const date = `${day}/${month}/${year}`; - const formattedTime = `${time} - ${date}`; - - if (existingActivityKey) { - // Update existing activity - const existingActivityRef = ref( - database, - `${role}Activity/${existingActivityKey}` - ); - await update(existingActivityRef, { - signInTime: formattedTime, - }); } else { - // Push new activity - const newActivityRef = push(activityRef); - await set(newActivityRef, { - uid: user.uid, - name: userName, - email: user.email, - role: role, - signInTime: formattedTime, - }); + setError("Not an admin account"); + return; } + } else { + setError("Email not found"); + return; } - - // Store user information in localStorage - localStorage.setItem("user", JSON.stringify({ uid: user.uid, role })); - - handleLoginSuccess(role); - // navigate("/dashboard"); } else { - setError("Invalid credentials"); + setError("No role data found"); + return; } } catch (error) { setError(error.message); } }; - const lampEffectStyle = { position: "relative", background: "black", From 765138d2dd03f1b03fe69bb81f4f55ed37469af2 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 3 Jul 2024 12:41:06 +0530 Subject: [PATCH 54/83] feat added superadmin login , fetching from users --- src/scenes/login/index.jsx | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index 2cb1573..b09c204 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -24,24 +24,48 @@ const Login = ({ handleLoginSuccess }) => { // Fetch the role from the rolemail node const roleMailRef = ref(database, "rolemail"); - console.log("roleMailRef", roleMailRef); const roleMailSnapshot = await get(roleMailRef); - console.log("RoleMailSnapshot:", roleMailSnapshot); + console.log("roleMailRef",roleMailRef); + console.log("roleMailSnapshot",roleMailSnapshot); if (roleMailSnapshot.exists()) { const roleMailData = roleMailSnapshot.val(); - console.log("RoleMailData:", roleMailData); const userEntry = Object.entries(roleMailData).find( ([key, value]) => value.email === email ); - console.log("UserEntry:", userEntry); if (userEntry) { const [userId, userInfo] = userEntry; const { role } = userInfo; - console.log("Role:", role); + console.log("userentry",userEntry,userInfo); - if (role === "admin") { + if (role === "superadmin") { + // Fetch the user details from the users database for superadmin + const userRef = ref(database, "users/" + userId); + console.log("userref",userRef); + const userSnapshot = await get(userRef); + console.log("usersnapshot",userSnapshot); + if (userSnapshot.exists()) { + const userData = userSnapshot.val(); + console.log("userdata",userData); + if (userData.password === password) { + // Superadmin authenticated successfully + handleLoginSuccess(role); + // Store user information in localStorage + localStorage.setItem( + "user", + JSON.stringify({ uid: userId, role }) + ); + return; + } else { + setError("Invalid superadmin credentials"); + return; + } + } else { + setError("Superadmin not found"); + return; + } + } else if (role === "admin") { // For admin, check the password in the admins node const adminRef = ref(database, "admins/" + userId); const adminSnapshot = await get(adminRef); @@ -65,7 +89,7 @@ const Login = ({ handleLoginSuccess }) => { return; } } else { - setError("Not an admin account"); + setError("Not an admin or superadmin account"); return; } } else { From 0a3c9a31fc5437766f90ce2ba45e5c9c634e6df4 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 3 Jul 2024 21:16:05 +0530 Subject: [PATCH 55/83] feat added add member in UserList & login of user by spliting ID --- src/scenes/login/index.jsx | 46 ++++++-- src/scenes/team/index.jsx | 215 +++++++++++++++++++------------------ 2 files changed, 150 insertions(+), 111 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index b09c204..bf8f2d8 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -11,6 +11,7 @@ const Login = ({ handleLoginSuccess }) => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); + const [userUID, setUserUID] = useState(""); const handleLogin = async () => { try { @@ -24,9 +25,10 @@ const Login = ({ handleLoginSuccess }) => { // Fetch the role from the rolemail node const roleMailRef = ref(database, "rolemail"); + const roleMailSnapshot = await get(roleMailRef); - console.log("roleMailRef",roleMailRef); - console.log("roleMailSnapshot",roleMailSnapshot); + console.log("roleMailRef", roleMailRef); + console.log("roleMailSnapshot", roleMailSnapshot); if (roleMailSnapshot.exists()) { const roleMailData = roleMailSnapshot.val(); @@ -37,17 +39,17 @@ const Login = ({ handleLoginSuccess }) => { if (userEntry) { const [userId, userInfo] = userEntry; const { role } = userInfo; - console.log("userentry",userEntry,userInfo); + console.log("userentry", userEntry, userInfo); if (role === "superadmin") { // Fetch the user details from the users database for superadmin const userRef = ref(database, "users/" + userId); - console.log("userref",userRef); + console.log("userref", userRef); const userSnapshot = await get(userRef); - console.log("usersnapshot",userSnapshot); + console.log("usersnapshot", userSnapshot); if (userSnapshot.exists()) { const userData = userSnapshot.val(); - console.log("userdata",userData); + console.log("userdata", userData); if (userData.password === password) { // Superadmin authenticated successfully handleLoginSuccess(role); @@ -88,8 +90,38 @@ const Login = ({ handleLoginSuccess }) => { setError("Admin not found"); return; } + } else if (role === "user") { + // For admin, check the password in the admins node + const useridSplit = userId.split("_")[0]; + console.log("useridSplit", useridSplit); + const useRef = ref( + database, + "userList/" + useridSplit + "/" + userId + ); + + const useSnapshot = await get(useRef); + console.log("useSnapshot", useSnapshot); + if (useSnapshot.exists()) { + const useData = useSnapshot.val(); + if (useData.password === password) { + // Admin authenticated successfully + handleLoginSuccess(role); + // Store user information in localStorage + localStorage.setItem( + "user", + JSON.stringify({ uid: userId, role }) + ); + return; + } else { + setError("Invalid user credentials"); + return; + } + } else { + setError("user not found"); + return; + } } else { - setError("Not an admin or superadmin account"); + setError("Not an admin or superadmin or user account"); return; } } else { diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index a0fa5e7..2477bfd 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -48,21 +48,28 @@ const Team = () => { role: "user", }); const [currentUserRole, setCurrentUserRole] = useState(null); + const [adminName, setAdminName] = useState(""); + const [adminID, setAdminID] = useState(""); const user = auth.currentUser; - // fetch userRole from the database + // Fetch userRole and admin name from the database useEffect(() => { if (user) { - const userRoleRef = ref(database, "users/" + user.uid + "/role"); - const getUserRole = async () => { - const snapshot = await get(userRoleRef); - if (snapshot.exists()) { - setCurrentUserRole(snapshot.val()); - console.log(snapshot.val()); + const adminRef = ref(database, "admins/" + user.uid); + + const getAdminData = async () => { + const adminSnapshot = await get(adminRef); + + if (adminSnapshot.exists()) { + const adminData = adminSnapshot.val(); + setCurrentUserRole(adminData.role); + setAdminName(adminData.name); + setAdminID(user.uid); } }; - getUserRole(); + + getAdminData(); } }, [user]); @@ -128,82 +135,82 @@ const Team = () => { }, ]; - if (currentUserRole === "admin") { - columns.push({ - field: "blocked", - headerName: "Blocked", - flex: 1, - renderCell: ({ row }) => ( - - ), - }); - columns.push({ - field: "actions", - headerName: "Actions", - flex: 1, - renderCell: ({ row }) => ( - - ), - }); - } + columns.push({ + field: "blocked", + headerName: "Blocked", + flex: 1, + renderCell: ({ row }) => ( + + ), + }); + columns.push({ + field: "actions", + headerName: "Actions", + flex: 1, + renderCell: ({ row }) => ( + + ), + }); useEffect(() => { - const userActivityRef = ref(database, "userActivity"); + if (adminID) { + const userListRef = ref(database, `userList/${adminID}`); + + const handleChildAddedOrChanged = (snapshot) => { + const data = snapshot.val(); + setUserData((prevUserData) => { + const existingIndex = prevUserData.findIndex( + (item) => item.id === snapshot.key + ); + if (existingIndex !== -1) { + const updatedUserData = [...prevUserData]; + updatedUserData[existingIndex] = { id: snapshot.key, ...data }; + return updatedUserData; + } else { + return [...prevUserData, { id: snapshot.key, ...data }]; + } + }); + }; - const handleChildAddedOrChanged = (snapshot) => { - const data = snapshot.val(); - setUserData((prevUserData) => { - const existingIndex = prevUserData.findIndex( - (item) => item.id === snapshot.key + const handleChildRemoved = (snapshot) => { + setUserData((prevUserData) => + prevUserData.filter((item) => item.id !== snapshot.key) ); - if (existingIndex !== -1) { - const updatedUserData = [...prevUserData]; - updatedUserData[existingIndex] = { id: snapshot.key, ...data }; - return updatedUserData; - } else { - return [...prevUserData, { id: snapshot.key, ...data }]; - } - }); - }; + }; - const handleChildRemoved = (snapshot) => { - setUserData((prevUserData) => - prevUserData.filter((item) => item.id !== snapshot.key) + const childAddedListener = onChildAdded( + userListRef, + handleChildAddedOrChanged + ); + const childChangedListener = onChildChanged( + userListRef, + handleChildAddedOrChanged + ); + const childRemovedListener = onChildRemoved( + userListRef, + handleChildRemoved ); - }; - - const childAddedListener = onChildAdded( - userActivityRef, - handleChildAddedOrChanged - ); - const childChangedListener = onChildChanged( - userActivityRef, - handleChildAddedOrChanged - ); - const childRemovedListener = onChildRemoved( - userActivityRef, - handleChildRemoved - ); - return () => { - off(userActivityRef, "child_added", childAddedListener); - off(userActivityRef, "child_changed", childChangedListener); - off(userActivityRef, "child_removed", childRemovedListener); - }; - }, []); + return () => { + off(userListRef, "child_added", childAddedListener); + off(userListRef, "child_changed", childChangedListener); + off(userListRef, "child_removed", childRemovedListener); + }; + } + }, [adminID]); const handleBlockUser = async (userId, blockStatus) => { - const userRef = ref(database, `userActivity/${userId}`); + const userRef = ref(database, `userList/${adminID}/${userId}`); await update(userRef, { blocked: blockStatus }); }; @@ -225,36 +232,35 @@ const Team = () => { }; const handleFormSubmit = async () => { - if (selectedUser) { - // Updating an existing user - const userRef = ref(database, `userActivity/${selectedUser.id}`); - const updates = { ...formData }; - if (!formData.password) delete updates.password; // Do not update password if it's empty - await update(userRef, updates); + const userId = selectedUser ? selectedUser.id : `${adminID}_${uuidv4()}`; + + const userRef = ref(database, `userList/${adminID}/${userId}`); + const roleMailRef = ref(database, `rolemail/${userId}`); + + const userData = { + ...formData, + signInTime: new Date().toISOString(), + blocked: false, + }; - const userRefUsers = ref(database, `users/${selectedUser.id}`); - await update(userRefUsers, updates); + if (selectedUser) { + if (!formData.password) delete userData.password; // Do not update password if it's empty + await update(userRef, userData); + await update(roleMailRef, { + email: formData.email, + role: formData.role, + }); } else { - // Adding a new user try { - const userCredential = await createUserWithEmailAndPassword( + await createUserWithEmailAndPassword( auth, formData.email, formData.password ); - const newUser = userCredential.user; - const userRef = ref(database, `userActivity/${newUser.uid}`); - await set(userRef, { - ...formData, - signInTime: new Date().toISOString(), - blocked: false, - }); - - const userRefUsers = ref(database, `users/${newUser.uid}`); - await set(userRefUsers, { - ...formData, - signInTime: new Date().toISOString(), - blocked: false, + await set(userRef, userData); + await set(roleMailRef, { + email: formData.email, + role: formData.role, }); } catch (error) { console.error("Error adding new user:", error); @@ -262,17 +268,18 @@ const Team = () => { } handleDialogClose(); }; - const handleConfirmDelete = async (users) => { + + const handleConfirmDelete = async (user) => { try { - const userActivityRef = ref(database, `userActivity/${users.id}`); - await remove(userActivityRef); - const userRef = ref(database, `users/${user.id}`); + const userRef = ref(database, `userList/${adminID}/${user.id}`); + const roleMailRef = ref(database, `rolemail/${user.id}`); await remove(userRef); + await remove(roleMailRef); setUserData((prevUserData) => - prevUserData.filter((user) => user.id !== users.id) + prevUserData.filter((u) => u.id !== user.id) ); } catch (error) { - console.error("Error deleting contact:", error); + console.error("Error deleting user:", error); } }; const lampEffectStyle = { From f87961ae7b96c8c4c72bbc191e4f123674c409b3 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 3 Jul 2024 23:03:01 +0530 Subject: [PATCH 56/83] feat added blocked feat --- src/scenes/login/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index bf8f2d8..12992e3 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -103,6 +103,13 @@ const Login = ({ handleLoginSuccess }) => { console.log("useSnapshot", useSnapshot); if (useSnapshot.exists()) { const useData = useSnapshot.val(); + if (useData.blocked) { + await auth.signOut(); + setError( + "Your account has been blocked. Please contact support." + ); + return; + } if (useData.password === password) { // Admin authenticated successfully handleLoginSuccess(role); From d99d0ecf4abef870c17d293af27a1bb76f362e51 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Wed, 3 Jul 2024 23:36:15 +0530 Subject: [PATCH 57/83] Feat added admins db to form --- src/scenes/dashboard/index.jsx | 10 +- src/scenes/form/Data.jsx | 436 ++++++++++++++++----------------- src/scenes/form/index.jsx | 4 +- 3 files changed, 225 insertions(+), 225 deletions(-) diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 197f902..1eb6cc6 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -35,7 +35,7 @@ const Dashboard = () => { const db = getDatabase(); const dataRef = ref( db, - `users/${auth.currentUser.uid}/formData/salesPerMonth` + `admins/${auth.currentUser.uid}/formData/salesPerMonth` ); const snapshot = await get(dataRef); @@ -61,7 +61,7 @@ const Dashboard = () => { const db = database; const dataRef = ref( db, - `users/${auth.currentUser.uid}/formData/salesPerUnit` + `admins/${auth.currentUser.uid}/formData/salesPerUnit` ); const snapshot = await get(dataRef); @@ -80,7 +80,7 @@ const Dashboard = () => { return; } const db = getDatabase(); - const dataRef = ref(db, `userActivity`); + const dataRef = ref(db, `userList/${auth.currentUser.uid}`); const snapshot = await get(dataRef); if (snapshot.exists()) { @@ -96,11 +96,11 @@ const Dashboard = () => { const db = getDatabase(); const salesPerUnitRef = ref( db, - `users/${auth.currentUser.uid}/formData/salesPerUnit` + `admins/${auth.currentUser.uid}/formData/salesPerUnit` ); console.log( "Fetching data from path:", - `users/${auth.currentUser.uid}/formData/salesPerUnit` + `admins/${auth.currentUser.uid}/formData/salesPerUnit` ); const snapshot = await get(salesPerUnitRef); diff --git a/src/scenes/form/Data.jsx b/src/scenes/form/Data.jsx index 724a962..30b0b4c 100644 --- a/src/scenes/form/Data.jsx +++ b/src/scenes/form/Data.jsx @@ -51,228 +51,228 @@ const SectionTitle = styled(Typography)(({ theme }) => ({ })); const SubmissionList = ({ formSubmissions, handleEditForm }) => { - const theme = useTheme(); - const colors = tokens(theme.palette.mode); - - const ButtonS = styled(Button)(({ theme }) => ({ - gridColumn: "span 4", - display: "inline-flex", - height: "48px", - alignItems: "center", - justifyContent: "center", - borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", - backgroundSize: "200% 100%", - px: 6, - color: "#9CA3AF", - fontWeight: "500", - textTransform: "none", - animation: "shimmer 2s infinite", - transition: "color 0.3s", - "&:hover": { - color: "#FFFFFF", - }, - "&:focus": { - outline: "none", - boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", - }, - "@keyframes shimmer": { - "0%": { backgroundPosition: "200% 0" }, - "100%": { backgroundPosition: "-200% 0" }, - }, - })); - - const handleEdit = (submission, index) => { - handleEditForm(submission, index); - }; - - return ( - - {formSubmissions.length > 0 && ( - -
- {formSubmissions.map((submission, index) => ( - - - - - ({ + gridColumn: "span 4", + display: "inline-flex", + height: "48px", + alignItems: "center", + justifyContent: "center", + borderRadius: "8px", + border: `2px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", + backgroundSize: "200% 100%", + px: 6, + color: "#9CA3AF", + fontWeight: "500", + textTransform: "none", + animation: "shimmer 2s infinite", + transition: "color 0.3s", + "&:hover": { + color: "#FFFFFF", + }, + "&:focus": { + outline: "none", + boxShadow: "0 0 0 4px rgba(148, 163, 184, 0.6)", + }, + "@keyframes shimmer": { + "0%": { backgroundPosition: "200% 0" }, + "100%": { backgroundPosition: "-200% 0" }, + }, + })); + + const handleEdit = (submission, index) => { + handleEditForm(submission, index); + }; + + return ( + + {formSubmissions.length > 0 && ( + +
+ {formSubmissions.map((submission, index) => ( + + + + + + + Submission {index + 1} + + handleEdit(submission, index)} > - - Submission {index + 1} - - handleEdit(submission, index)} - > - Edit - - - - - Company Name : {submission.companyName} - - - - - Email : {submission.email} - - - {/* Table sections */} - - - Sales Per Month - - -
- - - - - Month - - - - - Amount - - - - - Country - - + Edit + + + + + Company Name : {submission.companyName} + + + + + Email : {submission.email} + + + {/* Table sections */} + + + Sales Per Month + + +
+ + + + + Month + + + + + Amount + + + + + Country + + + + + + {submission.salesPerMonth.map((sale, idx) => ( + + {sale.month} + {sale.amount} + {sale.country} - - - {submission.salesPerMonth.map((sale, idx) => ( + ))} + +
+
+ + + + Unique Selling Products + + + + + + + + Product + + + + + Country + + + + + + {submission.uniqueSellingProducts.map( + (product, idx) => ( - {sale.month} - {sale.amount} - {sale.country} + {product.product} + {product.country} - ))} - -
-
-
- - - Unique Selling Products - - - - - - - - Product - - - - - Country - - + ) + )} + +
+
+
+ + + Sales Per Unit + + + + + + + + Country + + + + + Unit Sales + + + + + + {submission.salesPerUnit.map((unit, idx) => ( + + {unit.country} + {unit.unitSales} - - - {submission.uniqueSellingProducts.map( - (product, idx) => ( - - {product.product} - {product.country} - - ) - )} - -
-
-
- - - Sales Per Unit - - - - - - - - Country - - - - - Unit Sales - - + ))} + +
+
+
+ + + Locations + + + + + {submission.locations.map((location, idx) => ( + + {location} - - - {submission.salesPerUnit.map((unit, idx) => ( - - {unit.country} - {unit.unitSales} - - ))} - -
-
-
- - - Locations - - - - - {submission.locations.map((location, idx) => ( - - {location} - - ))} - -
-
-
+ ))} + + + - - - - ))} - - )} - - ); - }; - - export default SubmissionList; \ No newline at end of file + + + + + ))} + + )} + + ); +}; + +export default SubmissionList; diff --git a/src/scenes/form/index.jsx b/src/scenes/form/index.jsx index a2bef28..fc7441e 100644 --- a/src/scenes/form/index.jsx +++ b/src/scenes/form/index.jsx @@ -44,7 +44,7 @@ const Form = () => { setFormSubmissions((prev) => [...prev, values]); } - set(ref(database, "users/" + user.uid + "/formData"), values) + set(ref(database, "admins/" + user.uid + "/formData"), values) .then(() => { console.log("Data saved successfully!"); }) @@ -84,7 +84,7 @@ const Form = () => { const user = auth.currentUser; if (user) { - const userRef = ref(database, "users/" + user.uid + "/formData"); + const userRef = ref(database, "admins/" + user.uid + "/formData"); get(userRef) .then((snapshot) => { if (snapshot.exists()) { From a411eefeabb68075203d6c7b08f1234e370a6296 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 00:11:24 +0530 Subject: [PATCH 58/83] feat updated contacts db --- src/scenes/contacts/index.jsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index da2a49a..9e25dc7 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -13,7 +13,7 @@ import { tokens } from "../../theme"; import Header from "../../components/Header"; import { useEffect, useState } from "react"; import { getDatabase, ref, set, update, remove, get } from "firebase/database"; -import { database } from "../../firebase"; +import { auth, database } from "../../firebase"; import { v4 as uuidv4 } from "uuid"; import { motion } from "framer-motion"; import Delete from "@mui/icons-material/Delete"; @@ -23,6 +23,8 @@ const Contacts = () => { const [contacts, setContacts] = useState([]); const [openDialog, setOpenDialog] = useState(false); const [selectedContact, setSelectedContact] = useState(null); + const [adminID, setAdminID] = useState(null); + const user = auth.currentUser; const [formData, setFormData] = useState({ registrarId: "", name: "", @@ -75,7 +77,7 @@ const Contacts = () => { useEffect(() => { const fetchContacts = async () => { try { - const contactsRef = ref(database, "contacts"); + const contactsRef = ref(database, "contacts/" + user.uid); const snapshot = await get(contactsRef); if (snapshot.exists()) { const data = snapshot.val(); @@ -94,6 +96,12 @@ const Contacts = () => { fetchContacts(); }, []); + useEffect(() => { + if (user) { + setAdminID(user.uid); + } + }, [user]); + const handleAddContact = () => { setFormData({ registrarId: "", @@ -117,7 +125,7 @@ const Contacts = () => { const handleDeleteContact = async (contact) => { try { - const contactRef = ref(database, `contacts/${contact.id}`); + const contactRef = ref(database, `contacts/${adminID}/${contact.id}`); await remove(contactRef); setContacts((prevContacts) => prevContacts.filter((c) => c.id !== contact.id) @@ -146,10 +154,10 @@ const Contacts = () => { try { const contactData = { ...formData, - id: selectedContact ? selectedContact.id : uuidv4(), + id: selectedContact ? selectedContact.id : `${adminID}_${uuidv4()}`, }; - const contactRef = ref(database, `contacts/${contactData.id}`); + const contactRef = ref(database, `contacts/${adminID}/${contactData.id}`); if (selectedContact) { await update(contactRef, contactData); setContacts((prevContacts) => From 58e784f1595a46206494beb090e20348cea9bd8a Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Thu, 4 Jul 2024 11:43:57 +0530 Subject: [PATCH 59/83] local session storage max level --- src/App.js | 78 +++++++++++++++++++++++++++++------- src/scenes/global/Topbar.jsx | 6 +-- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/App.js b/src/App.js index b4afb76..1193d7a 100644 --- a/src/App.js +++ b/src/App.js @@ -20,6 +20,7 @@ import Feedback from "./scenes/feedback"; import Notifications from "./components/Notification"; // Import the new page component import Page from "./components/Pages"; +import { auth } from "./firebase"; function App() { const [theme, colorMode] = useMode(); const [isSidebarVisible, setSidebarVisible] = useState(false); @@ -29,21 +30,53 @@ function App() { const navigate = useNavigate(); const location = useLocation(); + // useEffect(() => { + // const storedUser = JSON.parse(localStorage.getItem("user")); + // console.log("storedUser", storedUser) + // if (storedUser) { + // setLoggedIn(true); + // setUserRole(storedUser.role); + // setSidebarVisible(storedUser.role === "admin"); + // if (storedUser.role === "superadmin") { + // navigate("/admins"); + // } else if (storedUser.role === "user") { + // navigate("/page"); // Navigate to the new page for "user" role + // } else { + // navigate("/dashboard"); + // } + // }else{ + // navigate("/"); + // } + // }, [isSidebarVisible]); + useEffect(() => { const storedUser = JSON.parse(localStorage.getItem("user")); + if (storedUser) { - setLoggedIn(true); - setUserRole(storedUser.role); - setSidebarVisible(storedUser.role === "admin"); - if (storedUser.role === "superadmin") { - navigate("/admins"); - } else if (storedUser.role === "user") { - navigate("/page"); // Navigate to the new page for "user" role - } else { - navigate("/dashboard"); - } + auth.onAuthStateChanged((user) => { + if (user) { + setLoggedIn(true); + setUserRole(storedUser.role); + setSidebarVisible(storedUser.role === "admin"); + if (storedUser.role === "superadmin") { + navigate("/admins"); + } else if (storedUser.role === "user") { + navigate("/page"); // Navigate to the new page for "user" role + } else { + navigate("/dashboard"); + } + }else if(user && storedUser.role === "admin"){ + navigate("/dashboard"); + } else { + setSidebarVisible(false); + navigate("/"); + } + }); + } else { + setSidebarVisible(false); + navigate("/"); } - }, []); + }, [isSidebarVisible]); const handleLoginSuccess = (role) => { setLoggedIn(true); @@ -58,13 +91,28 @@ function App() { } }; + // const handleLogout = () => { + // setLoggedIn(false); + // setUserRole(null); + // setSidebarVisible(false); + // navigate("/"); + // }; + const handleLogout = () => { - setLoggedIn(false); - setUserRole(null); - setSidebarVisible(false); - navigate("/"); + auth.signOut().then(() => { + localStorage.removeItem("user"); + setLoggedIn(false); + setUserRole(null); + setSidebarVisible(false); + navigate("/"); + }); }; + // if stored user, then authenticate the user from db + + + + const showTopbar = isLoggedIn && location.pathname !== "/"; return ( diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 283315f..b1d36e4 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -36,13 +36,13 @@ const Topbar = ({ handleLogout }) => { } const fetchMessagesAndUserRole = async () => { - const currentUser = auth.currentUser; + const currentUser = auth.currentUser || storedUser; if (!currentUser) { navigate("/"); return; } - const userRef = ref(database, `users/${currentUser.uid}`); + const userRef = ref(database, `rolemail/${currentUser.uid}`); const userSnapshot = await get(userRef); let userData; if (userSnapshot.exists()) { @@ -65,7 +65,7 @@ const Topbar = ({ handleLogout }) => { }; fetchMessagesAndUserRole(); - }, [navigate]); + }, [handleLogout]); return ( From 10ff0c865c2b6dbd83385fafcfbd1039e015eac5 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 12:22:08 +0530 Subject: [PATCH 60/83] feat added feeback as array in the admins db & fixed styling --- src/scenes/feedback/index.jsx | 94 +++++++++++------------------------ 1 file changed, 29 insertions(+), 65 deletions(-) diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx index ac9b360..8c8fc5a 100644 --- a/src/scenes/feedback/index.jsx +++ b/src/scenes/feedback/index.jsx @@ -8,55 +8,40 @@ import { useTheme, } from "@mui/material"; import { getAuth } from "firebase/auth"; -import { get, ref, set, onValue } from "firebase/database"; +import { get, ref, update, onValue } from "firebase/database"; import { useEffect, useState } from "react"; import { database } from "../../firebase"; import { tokens } from "../../theme"; import Header from "../../components/Header"; - -// Import the TypewriterEffectSmooth component -import TypewriterEffectSmooth from "../../components/TypeWriterEffect"; // Update with your correct path +import TypewriterEffectSmooth from "../../components/TypeWriterEffect"; const Feedback = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); - const [role, setRole] = useState(null); const [feedback, setFeedback] = useState(""); const [userFeedbacks, setUserFeedbacks] = useState([]); + const [userInfo, setUserInfo] = useState({}); useEffect(() => { - const fetchUserRole = async () => { - const auth = getAuth(); - const user = auth.currentUser; - if (user) { - const userRef = ref(database, `users/${user.uid}`); - const snapshot = await get(userRef); - if (snapshot.exists()) { - const userData = snapshot.val(); - setRole(userData.role); - } - } - }; - const fetchUserFeedbacks = () => { const auth = getAuth(); const user = auth.currentUser; if (user) { - const feedbackRef = ref(database, `feedback/${user.uid}`); + const feedbackRef = ref(database, `admins/${user.uid}`); onValue(feedbackRef, (snapshot) => { const data = snapshot.val(); if (data) { - const feedbackList = Object.keys(data).map((key) => ({ - id: key, - ...data[key], - })); - setUserFeedbacks(feedbackList.reverse()); + setUserFeedbacks(data.feedback || []); + setUserInfo({ + name: data.name || "Anonymous", + email: data.email, + role: data.role, + }); } }); } }; - fetchUserRole(); fetchUserFeedbacks(); }, []); @@ -64,26 +49,16 @@ const Feedback = () => { const auth = getAuth(); const user = auth.currentUser; if (user) { - const userRef = ref(database, `users/${user.uid}`); - const snapshot = await get(userRef); - let userName = "Anonymous"; + const feedbackRef = ref(database, `admins/${user.uid}`); + const snapshot = await get(feedbackRef); if (snapshot.exists()) { - const userData = snapshot.val(); - userName = userData.name || "Anonymous"; + const adminData = snapshot.val(); + const newFeedback = [...(adminData.feedback || []), feedback]; + await update(feedbackRef, { + feedback: newFeedback, + }); + setFeedback(""); } - - const feedbackRef = ref(database, `feedback/${user.uid}`); - const newFeedbackRef = ref( - database, - `feedback/${user.uid}/${Date.now()}` - ); - await set(newFeedbackRef, { - name: userName, - email: user.email, - role: role, - feedback: feedback, - }); - setFeedback(""); } }; @@ -92,9 +67,7 @@ const Feedback = () => {
} subtitle="Every improvement starts with a feedback" @@ -112,8 +85,8 @@ const Feedback = () => { sx={{ border: `2px solid ${colors.grey[600]}`, boxShadow: `0 0 10px ${colors.grey[600]}`, - '@media (prefers-color-scheme: dark)': { - bgcolor: '#18181b', // Equivalent to dark:bg-zinc-900 + "@media (prefers-color-scheme: dark)": { + bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, }} > @@ -133,11 +106,6 @@ const Feedback = () => { outline: "none", transition: "border-color 0.3s", }} - sx={{ - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, - - }} onFocus={(e) => (e.target.style.borderColor = "cyan")} onBlur={(e) => (e.target.style.borderColor = colors.grey[100])} /> @@ -162,9 +130,9 @@ const Feedback = () => { Previous Feedback - {userFeedbacks.map((feedback) => ( + {userFeedbacks.map((feedback, index) => ( { > - {feedback.name ? feedback.name.charAt(0) : "A"}{" "} - {/* Handle undefined name */} + {userInfo.name.charAt(0)} - - {feedback.name || "Anonymous"}{" "} - {/* Fallback for undefined name */} - + {userInfo.name} - {feedback.email} + {userInfo.email} - {feedback.role} + {userInfo.role} - {feedback.feedback} + {feedback} ))} @@ -213,4 +177,4 @@ const Feedback = () => { ); }; -export default Feedback; \ No newline at end of file +export default Feedback; From 6467b47888178e500d2ea9dde8b7bc744ac6c4a9 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 13:01:14 +0530 Subject: [PATCH 61/83] feat added admin uid to the events db & fixed calebder sidebar styling --- src/scenes/calendar/calendar.jsx | 49 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/scenes/calendar/calendar.jsx b/src/scenes/calendar/calendar.jsx index 1af87e0..6c6c593 100644 --- a/src/scenes/calendar/calendar.jsx +++ b/src/scenes/calendar/calendar.jsx @@ -14,7 +14,7 @@ import { } from "@mui/material"; import Header from "../../components/Header"; import { tokens } from "../../theme"; -import { database } from "../../firebase"; +import { database, auth } from "../../firebase"; import { ref, set, get, remove } from "firebase/database"; import EventDialog from "../../components/EventDialouge"; // Adjust the import path if necessary import GradientBox from "../../components/GradientBox"; @@ -25,30 +25,37 @@ const Calendar = () => { const [currentEvents, setCurrentEvents] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); const [selectedDate, setSelectedDate] = useState(null); + const user = auth.currentUser; // Fetch events from Firebase when the component mounts useEffect(() => { - const fetchEvents = async () => { - const eventsRef = ref(database, "events"); - const snapshot = await get(eventsRef); - if (snapshot.exists()) { - const events = Object.values(snapshot.val()); - setCurrentEvents(events); - } - }; - fetchEvents(); - }, []); + if (user) { + const fetchEvents = async () => { + const eventsRef = ref(database, `events/${user.uid}`); + const snapshot = await get(eventsRef); + if (snapshot.exists()) { + const events = Object.values(snapshot.val()); + setCurrentEvents(events); + } + }; + fetchEvents(); + } + }, [user]); // Add event to Firebase const addEventToFirebase = async (event) => { - const eventRef = ref(database, `events/${event.id}`); - await set(eventRef, event); + if (user) { + const eventRef = ref(database, `events/${user.uid}/${event.id}`); + await set(eventRef, event); + } }; // Remove event from Firebase const removeEventFromFirebase = async (eventId) => { - const eventRef = ref(database, `events/${eventId}`); - await remove(eventRef); + if (user) { + const eventRef = ref(database, `events/${user.uid}/${eventId}`); + await remove(eventRef); + } }; // Handle date click @@ -97,12 +104,12 @@ const Calendar = () => { {/* CALENDAR SIDEBAR */} Events @@ -111,10 +118,10 @@ const Calendar = () => { Date: Thu, 4 Jul 2024 14:04:01 +0530 Subject: [PATCH 62/83] feat added admins db to bar chart & geo & remove the login header form `admin` list --- src/components/BarChart.jsx | 4 ++-- src/scenes/AdminList/index.jsx | 2 +- src/scenes/geography/index.jsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/BarChart.jsx b/src/components/BarChart.jsx index ebf7009..c3f3c55 100644 --- a/src/components/BarChart.jsx +++ b/src/components/BarChart.jsx @@ -11,8 +11,8 @@ const fetchData = async () => { return { salesPerUnit: [], uniqueSellingProducts: [] }; } const db = getDatabase(); - const salesPerUnitRef = ref(db, `users/${auth.currentUser.uid}/formData/salesPerUnit`); - const uniqueSellingProductsRef = ref(db, `users/${auth.currentUser.uid}/formData/uniqueSellingProducts`); + const salesPerUnitRef = ref(db, `admins/${auth.currentUser.uid}/formData/salesPerUnit`); + const uniqueSellingProductsRef = ref(db, `admins/${auth.currentUser.uid}/formData/uniqueSellingProducts`); const [salesPerUnitSnapshot, uniqueSellingProductsSnapshot] = await Promise.all([ get(salesPerUnitRef), diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index e7a7ce0..e961564 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -236,7 +236,7 @@ const AdminList = () => { { field: "id", headerName: "ID", flex: 0.5 }, { field: "name", headerName: "Name", flex: 0.75 }, { field: "email", headerName: "Email", flex: 1 }, - { field: "signInTime", headerName: "Login Time", flex: 1 }, + { field: "feedback", headerName: "Feedback", flex: 2 }, { field: "actions", diff --git a/src/scenes/geography/index.jsx b/src/scenes/geography/index.jsx index ed45bd7..2d16664 100644 --- a/src/scenes/geography/index.jsx +++ b/src/scenes/geography/index.jsx @@ -23,7 +23,7 @@ const Geography = () => { const db = database; const dataRef = ref( db, - `users/${auth.currentUser.uid}/formData/salesPerUnit` + `admins/${auth.currentUser.uid}/formData/salesPerUnit` ); const snapshot = await get(dataRef); From 109426ebaee5032da4c6190b074e2e57f4734dc1 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 14:28:49 +0530 Subject: [PATCH 63/83] Added admins db to linechart --- src/components/LineChart.jsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/LineChart.jsx b/src/components/LineChart.jsx index 2776af2..2993864 100644 --- a/src/components/LineChart.jsx +++ b/src/components/LineChart.jsx @@ -17,7 +17,10 @@ const LineChart = ({ isCustomLineColors = false, isDashboard = false }) => { return; } const db = getDatabase(); - const dataRef = ref(db, `users/${auth.currentUser.uid}/formData/salesPerMonth`); + const dataRef = ref( + db, + `admins/${auth.currentUser.uid}/formData/salesPerMonth` + ); const snapshot = await get(dataRef); if (snapshot.exists()) { @@ -35,27 +38,32 @@ const LineChart = ({ isCustomLineColors = false, isDashboard = false }) => { const formatData = (firebaseData) => { const dataByCountry = {}; - Object.values(firebaseData).forEach(item => { + Object.values(firebaseData).forEach((item) => { if (!dataByCountry[item.country]) { dataByCountry[item.country] = []; } dataByCountry[item.country].push({ x: item.month, - y: item.amount + y: item.amount, }); }); const result = Object.keys(dataByCountry).map((country, index) => ({ id: country, - color: theme.palette.type === 'dark' ? colors[index % colors.length] : `hsl(${(index * 360) / Object.keys(dataByCountry).length}, 70%, 50%)`, - data: dataByCountry[country] + color: + theme.palette.type === "dark" + ? colors[index % colors.length] + : `hsl(${ + (index * 360) / Object.keys(dataByCountry).length + }, 70%, 50%)`, + data: dataByCountry[country], })); return result; }; if (data.length === 0) { - return
Loading...
; + return
Loading...
; } return ( From 9304df19c5a2862e89768eb8f5f0638cb884740c Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 14:39:11 +0530 Subject: [PATCH 64/83] Added admins db to pie chart --- src/components/PieChart.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PieChart.jsx b/src/components/PieChart.jsx index b7703aa..261bef2 100644 --- a/src/components/PieChart.jsx +++ b/src/components/PieChart.jsx @@ -22,11 +22,11 @@ const PieChart = () => { const db = database; const salesRef = ref( db, - `users/${auth.currentUser.uid}/formData/salesPerUnit` + `admins/${auth.currentUser.uid}/formData/salesPerUnit` ); const productsRef = ref( db, - `users/${auth.currentUser.uid}/formData/uniqueSellingProducts` + `admins/${auth.currentUser.uid}/formData/uniqueSellingProducts` ); const salesSnapshot = await get(salesRef); From 5030e0d055b48b1c11216a57aded040d75c7366f Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 15:27:57 +0530 Subject: [PATCH 65/83] fixed barchart --- src/components/BarChart.jsx | 102 ++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/src/components/BarChart.jsx b/src/components/BarChart.jsx index c3f3c55..c8c4ed9 100644 --- a/src/components/BarChart.jsx +++ b/src/components/BarChart.jsx @@ -1,26 +1,33 @@ -import React, { useEffect, useState } from 'react'; -import { useTheme } from '@mui/material'; -import { ResponsiveBar } from '@nivo/bar'; -import { tokens } from '../theme'; -import { getDatabase, ref, get } from 'firebase/database'; -import { auth } from '../firebase'; +import React, { useEffect, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveBar } from "@nivo/bar"; +import { tokens } from "../theme"; +import { getDatabase, ref, get } from "firebase/database"; +import { auth } from "../firebase"; const fetchData = async () => { if (!auth.currentUser) { - // console.error("User is not authenticated"); return { salesPerUnit: [], uniqueSellingProducts: [] }; } const db = getDatabase(); - const salesPerUnitRef = ref(db, `admins/${auth.currentUser.uid}/formData/salesPerUnit`); - const uniqueSellingProductsRef = ref(db, `admins/${auth.currentUser.uid}/formData/uniqueSellingProducts`); + const salesPerUnitRef = ref( + db, + `admins/${auth.currentUser.uid}/formData/salesPerUnit` + ); + const uniqueSellingProductsRef = ref( + db, + `admins/${auth.currentUser.uid}/formData/uniqueSellingProducts` + ); - const [salesPerUnitSnapshot, uniqueSellingProductsSnapshot] = await Promise.all([ - get(salesPerUnitRef), - get(uniqueSellingProductsRef), - ]); + const [salesPerUnitSnapshot, uniqueSellingProductsSnapshot] = + await Promise.all([get(salesPerUnitRef), get(uniqueSellingProductsRef)]); - const salesPerUnit = salesPerUnitSnapshot.exists() ? salesPerUnitSnapshot.val() : []; - const uniqueSellingProducts = uniqueSellingProductsSnapshot.exists() ? uniqueSellingProductsSnapshot.val() : []; + const salesPerUnit = salesPerUnitSnapshot.exists() + ? salesPerUnitSnapshot.val() + : []; + const uniqueSellingProducts = uniqueSellingProductsSnapshot.exists() + ? uniqueSellingProductsSnapshot.val() + : []; return { salesPerUnit, uniqueSellingProducts }; }; @@ -37,20 +44,20 @@ const generateColors = (products) => { const transformData = (salesPerUnit, uniqueSellingProducts) => { // Get unique products - const uniqueProducts = uniqueSellingProducts.map(item => item.product); + const uniqueProducts = uniqueSellingProducts.map((item) => item.product); const productColors = generateColors(uniqueProducts); const data = salesPerUnit.map((sale) => { - const product = uniqueSellingProducts.find((item) => item.country === sale.country)?.product || 'Unknown Product'; + const product = + uniqueSellingProducts.find((item) => item.country === sale.country) + ?.product || "Unknown Product"; return { country: sale.country, - [product]: sale.unitSales, - [`${product}Color`]: productColors[product] || productColors['Unknown Product'], + unitSales: sale.unitSales, + product: product, + productColor: productColors[product] || productColors["Unknown Product"], }; }); - // console.log('Sales Per Unit:', salesPerUnit); - // console.log('Unique Selling Products:', uniqueSellingProducts); - // console.log('Transformed Data:', data); return data; }; @@ -62,7 +69,10 @@ const BarChart = ({ isDashboard = false }) => { useEffect(() => { const fetchAndTransformData = async () => { const { salesPerUnit, uniqueSellingProducts } = await fetchData(); - const transformedData = transformData(salesPerUnit, uniqueSellingProducts); + const transformedData = transformData( + salesPerUnit, + uniqueSellingProducts + ); setData(transformedData); }; @@ -100,16 +110,16 @@ const BarChart = ({ isDashboard = false }) => { }, }, }} - keys={data.length > 0 ? Object.keys(data[0]).filter(key => key !== 'country' && !key.endsWith('Color')) : []} + keys={["unitSales"]} indexBy="country" margin={{ top: 50, right: 130, bottom: 50, left: 60 }} padding={0.3} - valueScale={{ type: 'linear' }} - indexScale={{ type: 'band', round: true }} - colors={({ id, data }) => data[`${id}Color`]} + valueScale={{ type: "linear" }} + indexScale={{ type: "band", round: true }} + colors={({ data }) => data.productColor} borderColor={{ - from: 'color', - modifiers: [['darker', '1.6']], + from: "color", + modifiers: [["darker", "1.6"]], }} axisTop={null} axisRight={null} @@ -117,42 +127,42 @@ const BarChart = ({ isDashboard = false }) => { tickSize: 5, tickPadding: 5, tickRotation: 0, - legend: isDashboard ? undefined : 'country', - legendPosition: 'middle', + legend: isDashboard ? undefined : "country", + legendPosition: "middle", legendOffset: 32, }} axisLeft={{ tickSize: 5, tickPadding: 5, tickRotation: 0, - legend: isDashboard ? undefined : 'units', - legendPosition: 'middle', + legend: isDashboard ? undefined : "units", + legendPosition: "middle", legendOffset: -40, }} enableLabel={false} labelSkipWidth={12} labelSkipHeight={12} labelTextColor={{ - from: 'color', - modifiers: [['darker', 1.6]], + from: "color", + modifiers: [["darker", 1.6]], }} legends={[ { - dataFrom: 'keys', - anchor: 'bottom-right', - direction: 'column', + dataFrom: "keys", + anchor: "bottom-right", + direction: "column", justify: false, translateX: 120, translateY: 0, itemsSpacing: 2, itemWidth: 100, itemHeight: 20, - itemDirection: 'left-to-right', + itemDirection: "left-to-right", itemOpacity: 0.85, symbolSize: 20, effects: [ { - on: 'hover', + on: "hover", style: { itemOpacity: 1, }, @@ -160,22 +170,22 @@ const BarChart = ({ isDashboard = false }) => { ], }, ]} - tooltip={({ id, value, indexValue }) => ( + tooltip={({ data }) => (
- {indexValue}: {id} ({value}) + {data.country}: {data.product} ({data.unitSales})
)} role="application" barAriaLabel={function (e) { - return e.id + ': ' + e.formattedValue + ' in country: ' + e.indexValue; + return e.id + ": " + e.formattedValue + " in country: " + e.indexValue; }} /> ); From 2cc9bc6cc28cd1a72d350f3a7e46d0578038e948 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 4 Jul 2024 15:30:00 +0530 Subject: [PATCH 66/83] fixed sidebar with admins db --- src/scenes/global/Sidebar.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenes/global/Sidebar.jsx b/src/scenes/global/Sidebar.jsx index f5b28c5..bd30ab6 100644 --- a/src/scenes/global/Sidebar.jsx +++ b/src/scenes/global/Sidebar.jsx @@ -52,7 +52,7 @@ const Sidebar = () => { useEffect(() => { if (user) { const fetchUserInfo = async () => { - const userRef = ref(db, `users/${user.uid}`); + const userRef = ref(db, `admins/${user.uid}`); const snapshot = await get(userRef); if (snapshot.exists()) { const userData = snapshot.val(); @@ -71,7 +71,7 @@ const Sidebar = () => { reader.onloadend = async () => { const base64String = reader.result.split(",")[1]; if (user) { - const userRef = ref(db, `users/${user.uid}`); + const userRef = ref(db, `admins/${user.uid}`); await update(userRef, { profileImage: base64String }); setProfileImage(base64String); } From 3835ecdb391f9febdfd4592a8935ed25b9f62b6f Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 10:00:57 +0530 Subject: [PATCH 67/83] feat added server with client using firebase admin sdk --- package-lock.json | 820 +++++++++++++++++++++++++++++++++++++- package.json | 4 + server.js | 67 ++++ src/scenes/team/index.jsx | 52 ++- 4 files changed, 911 insertions(+), 32 deletions(-) create mode 100644 server.js diff --git a/package-lock.json b/package-lock.json index 1a1bce6..6e4526b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,9 +30,13 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "body-parser": "^1.20.2", "chart.js": "^3.9.1", "clsx": "^2.1.1", + "cors": "^2.8.5", + "express": "^4.19.2", "firebase": "^10.12.2", + "firebase-admin": "^12.2.0", "formik": "^2.2.9", "framer-motion": "^11.2.12", "html2canvas": "^1.4.1", @@ -3189,6 +3193,105 @@ "tslib": "^2.1.0" } }, + "node_modules/@google-cloud/firestore": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.9.0.tgz", + "integrity": "sha512-c4ALHT3G08rV7Zwv8Z2KG63gZh66iKdhCBeDfCpIkLrjX6EAjTD/szMdj14M+FnQuClZLFfW5bAgoOjfNmLtJg==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.11.2.tgz", + "integrity": "sha512-jJOrKyOdujfrSF8EJODW9yY6hqO4jSTk6eVITEj2gsD43BSXuDlnMlLOaBUQhXL29VGnSkxDgYl5tlFhA6LKSA==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.3.0", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.9.14", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.14.tgz", @@ -4061,6 +4164,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -5647,6 +5760,12 @@ "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "optional": true + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -6062,11 +6181,25 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -6149,6 +6282,32 @@ "@types/react": "*" } }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -6215,6 +6374,12 @@ "@types/jest": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "optional": true + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6616,6 +6781,18 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "deprecated": "Use your platform's native atob() and btoa() methods instead" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7038,6 +7215,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -7053,6 +7239,15 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7436,6 +7631,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7464,6 +7679,15 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -7624,6 +7848,11 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -8119,6 +8348,18 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -9161,11 +9402,31 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9222,6 +9483,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.16.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", @@ -10213,6 +10483,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -10324,6 +10603,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10365,6 +10658,28 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-xml-parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", + "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -10570,6 +10885,42 @@ "@firebase/vertexai-preview": "0.0.2" } }, + "node_modules/firebase-admin": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.2.0.tgz", + "integrity": "sha512-R9xxENvPA/19XJ3mv0Kxfbz9kPXd9/HrM4083LZWOO0qAQGheRzcCQamYRe+JSrV2cdKXP3ZsfFGTYMrFM0pJg==", + "dependencies": { + "@fastify/busboy": "^2.1.0", + "@firebase/database-compat": "^1.0.2", + "@firebase/database-types": "^1.0.0", + "@types/node": "^20.10.3", + "farmhash-modern": "^1.1.0", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "long": "^5.2.3", + "node-forge": "^1.3.1", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.7.0", + "@google-cloud/storage": "^7.7.0" + } + }, + "node_modules/firebase-admin/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -10955,6 +11306,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -10963,6 +11320,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.0.tgz", + "integrity": "sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg==", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "optional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "optional": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11150,6 +11574,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.11.0.tgz", + "integrity": "sha512-epX3ww/mNnhl6tL45EQ/oixsY8JLEgUFoT4A5E/5iAR4esld9Kqv6IJGk7EmGuOgDvaarwF95hU2+v7Irql9lw==", + "optional": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.7.tgz", + "integrity": "sha512-3bnD8RASQyaxOYTdWLgwpQco/aytTxFavoI/UN5QN5txDLp8QRrBHNtCUJ5+Ago+551GD92jG8jJduwvmaneUw==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/@grpc/grpc-js": { + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.10.tgz", + "integrity": "sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -11171,6 +11661,19 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "optional": true, + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -14276,6 +14779,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14349,6 +14860,15 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -14426,6 +14946,46 @@ "node": ">=0.10.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jspdf": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", @@ -14457,6 +15017,43 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -14542,6 +15139,11 @@ "node": ">=10" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -14594,11 +15196,46 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -14609,6 +15246,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -14651,6 +15293,31 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -14997,6 +15664,48 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -16889,10 +17598,22 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/protobufjs": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", - "integrity": "sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", + "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -17826,6 +18547,20 @@ "node": ">= 4" } }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -18611,6 +19346,21 @@ "node": ">= 0.4" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "optional": true + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -18826,6 +19576,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, "node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -19141,6 +19903,58 @@ "node": ">=6" } }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", diff --git a/package.json b/package.json index 603b4b8..0f8099f 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,13 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "body-parser": "^1.20.2", "chart.js": "^3.9.1", "clsx": "^2.1.1", + "cors": "^2.8.5", + "express": "^4.19.2", "firebase": "^10.12.2", + "firebase-admin": "^12.2.0", "formik": "^2.2.9", "framer-motion": "^11.2.12", "html2canvas": "^1.4.1", diff --git a/server.js b/server.js new file mode 100644 index 0000000..5b01fbc --- /dev/null +++ b/server.js @@ -0,0 +1,67 @@ +const express = require('express'); +const admin = require('firebase-admin'); +const bodyParser = require('body-parser'); +const cors = require('cors'); + + +// Initialize Firebase Admin SDK +const serviceAccount = require('./serviceAccountKey.json'); + +admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: "https://buisness-bf4ca-default-rtdb.firebaseio.com" +}); + +const app = express(); +app.use(cors()); +app.use(bodyParser.json()); + +const PORT = process.env.PORT || 3000; + +// Middleware to verify Firebase ID token +const authenticate = async (req, res, next) => { + const idToken = req.headers.authorization; + if (!idToken) { + return res.status(401).send('Unauthorized'); + } + try { + const decodedToken = await admin.auth().verifyIdToken(idToken); + req.user = decodedToken; + next(); + } catch (error) { + res.status(401).send('Unauthorized'); + } +}; + +// Route to create a new user +app.post('/create-user', authenticate, async (req, res) => { + const { email, password, displayName, role } = req.body; + try { + const userRecord = await admin.auth().createUser({ + email, + password, + displayName, + }); + + // Optionally, set custom claims (roles) for the user + await admin.auth().setCustomUserClaims(userRecord.uid, { role }); + + // Add user data to the database + const userRef = admin.database().ref(`userList/${req.user.uid}/${userRecord.uid}`); + await userRef.set({ + name: displayName, + email, + role, + signInTime: new Date().toISOString(), + blocked: false, + }); + + res.status(201).send({ uid: userRecord.uid }); + } catch (error) { + res.status(500).send(error.message); + } +}); + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 2477bfd..396823e 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -232,41 +232,35 @@ const Team = () => { }; const handleFormSubmit = async () => { - const userId = selectedUser ? selectedUser.id : `${adminID}_${uuidv4()}`; - - const userRef = ref(database, `userList/${adminID}/${userId}`); - const roleMailRef = ref(database, `rolemail/${userId}`); - const userData = { - ...formData, - signInTime: new Date().toISOString(), - blocked: false, + email: formData.email, + password: formData.password, + displayName: formData.name, + role: formData.role, }; - if (selectedUser) { - if (!formData.password) delete userData.password; // Do not update password if it's empty - await update(userRef, userData); - await update(roleMailRef, { - email: formData.email, - role: formData.role, + try { + const accessToken = auth.currentUser ? await user.getIdToken() : null; + const response = await fetch("http://localhost:3000/create-user", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(userData), }); - } else { - try { - await createUserWithEmailAndPassword( - auth, - formData.email, - formData.password - ); - await set(userRef, userData); - await set(roleMailRef, { - email: formData.email, - role: formData.role, - }); - } catch (error) { - console.error("Error adding new user:", error); + + if (!response.ok) { + throw new Error("Error creating user"); } + + const data = await response.json(); + console.log("User created with UID:", data.uid); + + handleDialogClose(); + } catch (error) { + console.error("Error:", error); } - handleDialogClose(); }; const handleConfirmDelete = async (user) => { From 299d9b0abdd108df642e72a9205ee1fa12dd77e5 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 10:05:10 +0530 Subject: [PATCH 68/83] added .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4d29575..466d414 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ .env.development.local .env.test.local .env.production.local +serviceAccountKey.json npm-debug.log* yarn-debug.log* From a746c768823c9cb027b4edfab3e82451a1313911 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 12:02:18 +0530 Subject: [PATCH 69/83] feat added contacts again in db --- src/scenes/contacts/index.jsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/scenes/contacts/index.jsx b/src/scenes/contacts/index.jsx index 02c52f5..fde1972 100644 --- a/src/scenes/contacts/index.jsx +++ b/src/scenes/contacts/index.jsx @@ -13,9 +13,7 @@ import { tokens } from "../../theme"; import Header from "../../components/Header"; import { useEffect, useState } from "react"; import { getDatabase, ref, set, update, remove, get } from "firebase/database"; - import { auth, database } from "../../firebase"; - import { v4 as uuidv4 } from "uuid"; import { motion } from "framer-motion"; import Delete from "@mui/icons-material/Delete"; @@ -25,9 +23,8 @@ const Contacts = () => { const [contacts, setContacts] = useState([]); const [openDialog, setOpenDialog] = useState(false); const [selectedContact, setSelectedContact] = useState(null); - const [adminID, setAdminID] = useState(null); - + const user = auth.currentUser; const [formData, setFormData] = useState({ registrarId: "", name: "", @@ -80,9 +77,7 @@ const Contacts = () => { useEffect(() => { const fetchContacts = async () => { try { - const contactsRef = ref(database, "contacts/" + user.uid); - const snapshot = await get(contactsRef); if (snapshot.exists()) { const data = snapshot.val(); @@ -101,14 +96,12 @@ const Contacts = () => { fetchContacts(); }, []); - useEffect(() => { if (user) { setAdminID(user.uid); } }, [user]); - const handleAddContact = () => { setFormData({ registrarId: "", @@ -132,9 +125,7 @@ const Contacts = () => { const handleDeleteContact = async (contact) => { try { - const contactRef = ref(database, `contacts/${adminID}/${contact.id}`); - await remove(contactRef); setContacts((prevContacts) => prevContacts.filter((c) => c.id !== contact.id) @@ -163,12 +154,10 @@ const Contacts = () => { try { const contactData = { ...formData, - id: selectedContact ? selectedContact.id : `${adminID}_${uuidv4()}`, }; const contactRef = ref(database, `contacts/${adminID}/${contactData.id}`); - if (selectedContact) { await update(contactRef, contactData); setContacts((prevContacts) => @@ -463,4 +452,4 @@ const Contacts = () => { ); }; -export default Contacts; +export default Contacts; \ No newline at end of file From f2cde4dba809a2eb386bc7311149cb4dea4e34a3 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Fri, 5 Jul 2024 13:37:57 +0530 Subject: [PATCH 70/83] create team member --- package-lock.json | 78 ++++++++++++++++++++++++++++++++++----- package.json | 8 ++-- server.js | 42 ++++++++------------- src/scenes/team/index.jsx | 17 ++++++--- 4 files changed, 100 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4758922..f46cb35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,10 +33,8 @@ "body-parser": "^1.20.2", "chart.js": "^3.9.1", "clsx": "^2.1.1", - "cors": "^2.8.5", "express": "^4.19.2", - "firebase": "^10.12.2", "firebase-admin": "^12.2.0", "formik": "^2.2.9", @@ -55,7 +53,8 @@ "yup": "^0.32.11" }, "devDependencies": { - "cross-env": "^7.0.3" + "cross-env": "^7.0.3", + "nodemon": "^3.1.4" } }, "node_modules/@adobe/css-tools": { @@ -7633,7 +7632,6 @@ "node": ">= 0.6.0" } }, - "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7654,7 +7652,6 @@ ], "optional": true }, - "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7852,13 +7849,11 @@ "node": ">= 0.4.0" } }, - "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, - "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -12102,6 +12097,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -14952,7 +14953,6 @@ "node": ">=0.10.0" } }, - "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -14993,7 +14993,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jspdf": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", @@ -15732,6 +15731,34 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -17666,6 +17693,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -19083,6 +19116,18 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -20166,6 +20211,15 @@ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -20409,6 +20463,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/underscore": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", diff --git a/package.json b/package.json index 9ae7331..b01bf1d 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,8 @@ "body-parser": "^1.20.2", "chart.js": "^3.9.1", "clsx": "^2.1.1", - "cors": "^2.8.5", "express": "^4.19.2", - "firebase": "^10.12.2", "firebase-admin": "^12.2.0", "formik": "^2.2.9", @@ -53,7 +51,8 @@ "start": "cross-env PORT=5000 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "serve" : "nodemon server.js" }, "eslintConfig": { "extends": [ @@ -74,6 +73,7 @@ ] }, "devDependencies": { - "cross-env": "^7.0.3" + "cross-env": "^7.0.3", + "nodemon": "^3.1.4" } } diff --git a/server.js b/server.js index 5b01fbc..0ab1469 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,6 @@ const admin = require('firebase-admin'); const bodyParser = require('body-parser'); const cors = require('cors'); - // Initialize Firebase Admin SDK const serviceAccount = require('./serviceAccountKey.json'); @@ -18,41 +17,28 @@ app.use(bodyParser.json()); const PORT = process.env.PORT || 3000; -// Middleware to verify Firebase ID token -const authenticate = async (req, res, next) => { - const idToken = req.headers.authorization; - if (!idToken) { - return res.status(401).send('Unauthorized'); - } - try { - const decodedToken = await admin.auth().verifyIdToken(idToken); - req.user = decodedToken; - next(); - } catch (error) { - res.status(401).send('Unauthorized'); - } -}; - // Route to create a new user -app.post('/create-user', authenticate, async (req, res) => { - const { email, password, displayName, role } = req.body; +app.post('/create-user', async (req, res) => { + const { email, password, displayName, role, uid } = req.body; try { const userRecord = await admin.auth().createUser({ - email, - password, - displayName, + uid: uid, + email: email, + password: password, + displayName: displayName, }); - // Optionally, set custom claims (roles) for the user - await admin.auth().setCustomUserClaims(userRecord.uid, { role }); + // Extract adminId from uid (assuming format is adminId_uuid) + const adminId = uid.split('_')[0]; - // Add user data to the database - const userRef = admin.database().ref(`userList/${req.user.uid}/${userRecord.uid}`); + // Add user data to the database under the specified reference + const userRef = admin.database().ref(`userList/${adminId}/${userRecord.uid}`); await userRef.set({ name: displayName, email, role, - signInTime: new Date().toISOString(), + password, + signInTime: "yet to login", blocked: false, }); @@ -62,6 +48,10 @@ app.post('/create-user', authenticate, async (req, res) => { } }); +app.get('/', (req, res) => { + res.send('Hello World'); +}); + app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index c0dc430..40176ba 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -236,32 +236,37 @@ const Team = () => { }; const handleFormSubmit = async () => { - const userData = { + uid: `${adminID}_${uuidv4()}`, email: formData.email, password: formData.password, displayName: formData.name, role: formData.role, }; - + try { - const accessToken = auth.currentUser ? await user.getIdToken() : null; const response = await fetch("http://localhost:3000/create-user", { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(userData), }); - + if (!response.ok) { throw new Error("Error creating user"); } + // save to rolemail + const roleMailRef = ref(database, `rolemail/${userData.uid}`); + await set(roleMailRef, { + email: userData.email, + role: userData.role, + }); + const data = await response.json(); console.log("User created with UID:", data.uid); - + handleDialogClose(); } catch (error) { console.error("Error:", error); From 79fff022857c374e77ef215e31c160fcad7226ff Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Fri, 5 Jul 2024 15:38:49 +0530 Subject: [PATCH 71/83] admin page fixed --- src/scenes/AdminList/index.jsx | 162 +++++++++++++-------------------- 1 file changed, 62 insertions(+), 100 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index cc0ddcd..53a9fca 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -54,67 +54,57 @@ const AdminList = () => { const [error, setError] = useState(""); const navigate = useNavigate(); - useEffect(() => { - const fetchAdmins = async () => { - - const adminsRef = ref(database, "admins"); - - const snapshot = await get(adminsRef); - if (snapshot.exists()) { - const users = snapshot.val(); - - const adminList = Object.keys(users).map((key) => ({ - id: key, - ...users[key], - })); - - // Fetch feedbacks and merge with admin data - const feedbackRef = ref(database, "feedback"); - const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() - ? feedbackSnapshot.val() - : {}; - - // console.log(feedbackData) - const adminListWithFeedback = adminList.map((admin) => { - // console.log(admin.uid) - const adminFeedbacks = feedbackData[admin.uid] - ? Object.values(feedbackData[admin.uid]) - : []; - // console.log(adminFeedbacks) - // Get the most recent feedback - const length = adminFeedbacks.length; - const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; - - return { - ...admin, - feedback: latestFeedback ? latestFeedback.feedback : "No feedback", - }; - }); +useEffect(() => { + const fetchAdmins = async () => { + const adminsRef = ref(database, "admins"); + const snapshot = await get(adminsRef); + if (snapshot.exists()) { + const users = snapshot.val(); - setAdmins(adminListWithFeedback); + const adminList = Object.keys(users).map((key) => ({ + id: key, + ...users[key], + })); - // Get the current user's ID and role - const currentUserId = auth.currentUser.uid; + // Fetch feedbacks and merge with admin data + const feedbackRef = ref(database, "feedback"); + const feedbackSnapshot = await get(feedbackRef); + const feedbackData = feedbackSnapshot.exists() + ? feedbackSnapshot.val() + : {}; - const roleMailRef = ref(database, `rolemail/${currentUserId}`); - const roleMailSnapshot = await get(roleMailRef); - if (roleMailSnapshot.exists()) { - const currentUserData = roleMailSnapshot.val(); + const adminListWithFeedback = adminList.map((admin) => { + const adminFeedbacks = feedbackData[admin.uid] + ? Object.values(feedbackData[admin.uid]) + : []; + const length = adminFeedbacks.length; + const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; + + return { + ...admin, + feedback: latestFeedback ? latestFeedback.feedback : "No feedback", + }; + }); - setCurrentUserRole(currentUserData.role); + setAdmins(adminListWithFeedback); - } + // Get the current user's ID and role + const currentUserId = auth.currentUser.uid; + const roleMailRef = ref(database, `rolemail/${currentUserId}`); + const roleMailSnapshot = await get(roleMailRef); + if (roleMailSnapshot.exists()) { + const currentUserData = roleMailSnapshot.val(); + setCurrentUserRole(currentUserData.role); } - }; - fetchAdmins(); - }, []); + } + }; + fetchAdmins(); +}, []); - useEffect(() => { + useEffect(() => { const adminsRef = ref(database, "admins"); - - + const handleChildAddedOrChanged = async (snapshot) => { const data = snapshot.val(); const feedbackRef = ref(database, `feedback/${data.uid}`); @@ -123,7 +113,7 @@ const AdminList = () => { ? Object.values(feedbackSnapshot.val()) : []; const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); - + setAdmins((prevAdmins) => { const existingIndex = prevAdmins.findIndex( (item) => item.id === snapshot.key @@ -144,31 +134,25 @@ const AdminList = () => { } }); }; - + const handleChildRemoved = (snapshot) => { setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key) ); }; - - const childAddedListener = onChildAdded( - - adminsRef, - handleChildAddedOrChanged - ); - const childChangedListener = onChildChanged( - adminsRef, - handleChildAddedOrChanged - ); - const childRemovedListener = onChildRemoved(adminsRef, handleChildRemoved); - + + onChildAdded(adminsRef, handleChildAddedOrChanged); + onChildChanged(adminsRef, handleChildAddedOrChanged); + onChildRemoved(adminsRef, handleChildRemoved); + + // Clean up listeners return () => { - off(adminsRef, "child_added", childAddedListener); - off(adminsRef, "child_changed", childChangedListener); - off(adminsRef, "child_removed", childRemovedListener); - + off(adminsRef, "child_added", handleChildAddedOrChanged); + off(adminsRef, "child_changed", handleChildAddedOrChanged); + off(adminsRef, "child_removed", handleChildRemoved); }; }, []); + const handleDelete = async (id) => { try { @@ -251,46 +235,23 @@ const AdminList = () => { }; const columns = [ - { field: "id", headerName: "ID", flex: 0.5 }, - { field: "name", headerName: "Name", flex: 0.75 }, + { field: "id", headerName: "ID", flex: 1 }, + { field: "name", headerName: "Name", flex: 0.8 }, { field: "email", headerName: "Email", flex: 1 }, - - - - { field: "feedback", headerName: "Feedback", flex: 2 }, + { field: "feedback", headerName: "Feedback", flex: 1.6 }, { field: "actions", headerName: "Actions", flex: 1, - renderCell: (params) => ( - <> - - <> - handleDelete(params.id)} - > - - - - - ), - }, - { - field: "chat", - headerName: "Chat", - flex: 0.5, renderCell: (params) => ( <> handleChat(params.id)} + color="secondary" + sx={{ marginRight: 1, color: colors.redAccent[600] }} + onClick={() => handleDelete(params.id)} > - + - ), }, @@ -313,6 +274,7 @@ const AdminList = () => { ), }, ]; + const handleSendMessage = async () => { if (chatMessage.trim() === "") return; const newMessageRef = ref( From 9a8d8e4adacc824cbbfe29550860f7c1ab2ae421 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Fri, 5 Jul 2024 20:28:10 +0530 Subject: [PATCH 72/83] update signin Time --- src/scenes/login/index.jsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/scenes/login/index.jsx b/src/scenes/login/index.jsx index cd80431..6e26902 100644 --- a/src/scenes/login/index.jsx +++ b/src/scenes/login/index.jsx @@ -30,8 +30,8 @@ const Login = ({ handleLoginSuccess }) => { const roleMailRef = ref(database, "rolemail"); const roleMailSnapshot = await get(roleMailRef); - console.log("roleMailRef", roleMailRef); - console.log("roleMailSnapshot", roleMailSnapshot); + // console.log("roleMailRef", roleMailRef); + // console.log("roleMailSnapshot", roleMailSnapshot); if (roleMailSnapshot.exists()) { const roleMailData = roleMailSnapshot.val(); @@ -96,7 +96,7 @@ const Login = ({ handleLoginSuccess }) => { } else if (role === "user") { // For admin, check the password in the admins node const useridSplit = userId.split("_")[0]; - console.log("useridSplit", useridSplit); + // console.log("useridSplit", useridSplit); const useRef = ref( database, "userList/" + useridSplit + "/" + userId @@ -121,6 +121,19 @@ const Login = ({ handleLoginSuccess }) => { "user", JSON.stringify({ uid: userId, role }) ); + // update the signInTime + const hour = new Date().getHours(); + const minute = new Date().getMinutes(); + const signInTime = `${hour}:${minute}`; + const DateMonth = new Date().getMonth(); + const DateDay = new Date().getDate(); + const loginTime = `${signInTime} - ${DateMonth}/${DateDay}`; + console.log("loginTime", loginTime); + const signInTimeRef = ref( + database, + "userList/" + useridSplit + "/" + userId + "/signInTime" + ); + await set(signInTimeRef, loginTime); return; } else { setError("Invalid user credentials"); From b5e3740f5e50003f468ee5a25ab5ccb3bdecbcd6 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 20:58:48 +0530 Subject: [PATCH 73/83] feat added chat with superadmin --- package-lock.json | 141 ++++++++++++++++++++++ package.json | 3 +- server.js | 42 ++++++- src/components/Notification.jsx | 136 ++++++++++++---------- src/scenes/AdminList/index.jsx | 200 +++++++++++++++++++++----------- 5 files changed, 385 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index f46cb35..f731cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "socket.io": "^4.7.5", "tailwind-merge": "^2.3.0", "web-vitals": "^2.1.4", "yup": "^0.32.11" @@ -5151,6 +5152,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -5784,6 +5790,19 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", @@ -7652,6 +7671,14 @@ ], "optional": true }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -9493,6 +9520,62 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.16.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", @@ -19141,6 +19224,64 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", diff --git a/package.json b/package.json index b01bf1d..37c1744 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "socket.io": "^4.7.5", "tailwind-merge": "^2.3.0", "web-vitals": "^2.1.4", "yup": "^0.32.11" @@ -52,7 +53,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "serve" : "nodemon server.js" + "serve": "nodemon server.js" }, "eslintConfig": { "extends": [ diff --git a/server.js b/server.js index 0ab1469..85345dd 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,7 @@ const express = require('express'); const admin = require('firebase-admin'); const bodyParser = require('body-parser'); const cors = require('cors'); - +const socketIo = require('socket.io'); // Initialize Firebase Admin SDK const serviceAccount = require('./serviceAccountKey.json'); @@ -12,6 +12,13 @@ admin.initializeApp({ }); const app = express(); +const server = require('http').createServer(app); +const io = socketIo(server, { + cors: { + origin: '*', + } + }); + app.use(cors()); app.use(bodyParser.json()); @@ -52,6 +59,33 @@ app.get('/', (req, res) => { res.send('Hello World'); }); -app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); -}); +// Handle socket connections +io.on('connection', (socket) => { + console.log('New client connected'); + + socket.on('joinRoom', ({ adminId }) => { + socket.join(adminId); + }); + + socket.on('sendMessage', ({ adminId, message }) => { + const messageRef = admin.database().ref(`chats/${adminId}`).push(); + messageRef.set({ + message, + timestamp: Date.now(), + sentByMe: true, + }); + io.to(adminId).emit('receiveMessage', { message, timestamp: Date.now(), sentByMe: true }); + }); + + socket.on('disconnect', () => { + console.log('Client disconnected'); + }); + }); + + +// app.listen(PORT, () => { +// console.log(`Server is running on port ${PORT}`); +// }); +server.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); + }); diff --git a/src/components/Notification.jsx b/src/components/Notification.jsx index 6b3a713..3c31ba7 100644 --- a/src/components/Notification.jsx +++ b/src/components/Notification.jsx @@ -2,94 +2,106 @@ import React, { useEffect, useState } from "react"; import { Box, Typography, - Card, - CardContent, - Grid, IconButton, + InputBase, + Button, useTheme, } from "@mui/material"; import { Delete as DeleteIcon } from "@mui/icons-material"; -import { database } from "../firebase"; -import { ref, get, remove } from "firebase/database"; +import { database, auth } from "../firebase"; +import { ref, get, remove, push, onChildAdded, off } from "firebase/database"; +import { useParams } from "react-router-dom"; import { tokens } from "../theme"; const Notifications = () => { + const { id: selectedAdminId } = useParams(); const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(""); + const [user, setUser] = useState(null); const theme = useTheme(); const colors = tokens(theme.palette.mode); + useEffect(() => { + const unsubscribe = auth.onAuthStateChanged((currentUser) => { + if (currentUser) { + setUser(currentUser); + } else { + setUser(null); + } + }); + + return () => unsubscribe(); + }, []); useEffect(() => { + if (!user) return; + + const chatId = selectedAdminId || user.uid; + const messagesRef = ref(database, `chats/${chatId}`); const fetchMessages = async () => { - const messagesRef = ref(database, "messages"); const snapshot = await get(messagesRef); if (snapshot.exists()) { const messagesData = snapshot.val(); - const allMessages = Object.entries(messagesData).flatMap( - ([userId, userMessages]) => - Object.entries(userMessages).map(([msgId, message]) => ({ - ...message, - msgId, - userId, - })) - ); + const allMessages = Object.entries(messagesData).map(([msgId, message]) => ({ + ...message, + msgId, + })); setMessages(allMessages); } }; - fetchMessages(); - }, []); - const handleDelete = async (userId, msgId) => { - const messageRef = ref(database, `messages/${userId}/${msgId}`); - try { - await remove(messageRef); - setMessages((prevMessages) => - prevMessages.filter((msg) => msg.msgId !== msgId) - ); - } catch (error) { - console.error("Error deleting message:", error); - } + const handleNewMessage = (snapshot) => { + const message = snapshot.val(); + setMessages((prevMessages) => [...prevMessages, { ...message, msgId: snapshot.key }]); + }; + + onChildAdded(messagesRef, handleNewMessage); + + return () => { + off(messagesRef, 'child_added', handleNewMessage); + }; + }, [user, selectedAdminId]); + + const handleSend = async () => { + if (newMessage.trim() === "" || !user) return; + const chatId = selectedAdminId || user.uid; + const messageRef = ref(database, `chats/${chatId}`); + await push(messageRef, { + message: newMessage, + timestamp: Date.now(), + sentByMe: !selectedAdminId, // If selectedAdminId is present, it's sent by the superadmin + }); + setNewMessage(""); + }; + + const handleDelete = async (msgId) => { + if (!user) return; + const chatId = selectedAdminId || user.uid; + const messageRef = ref(database, `chats/${chatId}/${msgId}`); + await remove(messageRef); + setMessages((prevMessages) => prevMessages.filter((msg) => msg.msgId !== msgId)); }; return ( - - - Notifications - - + + Notifications + {messages.map((msg, index) => ( - - - - - - - {msg.message} - - - Sent by: {msg.sender} - - - {new Date(msg.timestamp).toLocaleString()} - - - handleDelete(msg.userId, msg.msgId)} - > - - - - - - + + + {msg.message} + {new Date(msg.timestamp).toLocaleString()} + handleDelete(msg.msgId)}> + + + + ))} - + + + setNewMessage(e.target.value)} /> + +
); }; diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 53a9fca..0f49dd7 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -10,6 +10,7 @@ import { IconButton, InputLabel, TextField, + Typography, } from "@mui/material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { useTheme } from "@mui/material"; @@ -23,6 +24,7 @@ import { onChildChanged, onChildRemoved, off, + push, } from "firebase/database"; import { auth, database } from "../../firebase"; import Header from "../../components/Header"; @@ -50,61 +52,59 @@ const AdminList = () => { const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); const [messages, setMessages] = useState([]); - const [error, setError] = useState(""); const navigate = useNavigate(); -useEffect(() => { - const fetchAdmins = async () => { - const adminsRef = ref(database, "admins"); - const snapshot = await get(adminsRef); - if (snapshot.exists()) { - const users = snapshot.val(); - - const adminList = Object.keys(users).map((key) => ({ - id: key, - ...users[key], - })); - - // Fetch feedbacks and merge with admin data - const feedbackRef = ref(database, "feedback"); - const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() - ? feedbackSnapshot.val() - : {}; - - const adminListWithFeedback = adminList.map((admin) => { - const adminFeedbacks = feedbackData[admin.uid] - ? Object.values(feedbackData[admin.uid]) - : []; - const length = adminFeedbacks.length; - const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; - - return { - ...admin, - feedback: latestFeedback ? latestFeedback.feedback : "No feedback", - }; - }); + useEffect(() => { + const fetchAdmins = async () => { + const adminsRef = ref(database, "admins"); + const snapshot = await get(adminsRef); + if (snapshot.exists()) { + const users = snapshot.val(); + + const adminList = Object.keys(users).map((key) => ({ + id: key, + ...users[key], + })); + + // Fetch feedbacks and merge with admin data + const feedbackRef = ref(database, "feedback"); + const feedbackSnapshot = await get(feedbackRef); + const feedbackData = feedbackSnapshot.exists() + ? feedbackSnapshot.val() + : {}; + + const adminListWithFeedback = adminList.map((admin) => { + const adminFeedbacks = feedbackData[admin.uid] + ? Object.values(feedbackData[admin.uid]) + : []; + const length = adminFeedbacks.length; + const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; + + return { + ...admin, + feedback: latestFeedback ? latestFeedback.feedback : "No feedback", + }; + }); - setAdmins(adminListWithFeedback); + setAdmins(adminListWithFeedback); - // Get the current user's ID and role - const currentUserId = auth.currentUser.uid; - const roleMailRef = ref(database, `rolemail/${currentUserId}`); - const roleMailSnapshot = await get(roleMailRef); - if (roleMailSnapshot.exists()) { - const currentUserData = roleMailSnapshot.val(); - setCurrentUserRole(currentUserData.role); + // Get the current user's ID and role + const currentUserId = auth.currentUser.uid; + const roleMailRef = ref(database, `rolemail/${currentUserId}`); + const roleMailSnapshot = await get(roleMailRef); + if (roleMailSnapshot.exists()) { + const currentUserData = roleMailSnapshot.val(); + setCurrentUserRole(currentUserData.role); + } } - } - }; - fetchAdmins(); -}, []); - + }; + fetchAdmins(); + }, []); useEffect(() => { const adminsRef = ref(database, "admins"); - + const handleChildAddedOrChanged = async (snapshot) => { const data = snapshot.val(); const feedbackRef = ref(database, `feedback/${data.uid}`); @@ -113,7 +113,7 @@ useEffect(() => { ? Object.values(feedbackSnapshot.val()) : []; const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); - + setAdmins((prevAdmins) => { const existingIndex = prevAdmins.findIndex( (item) => item.id === snapshot.key @@ -134,17 +134,17 @@ useEffect(() => { } }); }; - + const handleChildRemoved = (snapshot) => { setAdmins((prevAdmins) => prevAdmins.filter((item) => item.id !== snapshot.key) ); }; - + onChildAdded(adminsRef, handleChildAddedOrChanged); onChildChanged(adminsRef, handleChildAddedOrChanged); onChildRemoved(adminsRef, handleChildRemoved); - + // Clean up listeners return () => { off(adminsRef, "child_added", handleChildAddedOrChanged); @@ -152,11 +152,9 @@ useEffect(() => { off(adminsRef, "child_removed", handleChildRemoved); }; }, []); - const handleDelete = async (id) => { try { - await remove(ref(database, `admins/${id}`)); setAdmins(admins.filter((admin) => admin.id !== id)); @@ -167,9 +165,8 @@ useEffect(() => { const handleChat = (id) => { setSelectedAdminId(id); - + fetchMessages(id); setOpenChatDialog(true); - }; const handleDialogOpen = () => { @@ -184,7 +181,6 @@ useEffect(() => { const handleFormSubmit = async () => { try { - const roleMailRef = ref(database, "rolemail"); const roleMailSnapshot = await get(roleMailRef); const roleMailData = roleMailSnapshot.exists() @@ -196,7 +192,6 @@ useEffect(() => { Object.values(roleMailData).some( (entry) => entry.email === formData.email ) - ) { setError("Email already in use"); return; @@ -210,7 +205,6 @@ useEffect(() => { ); const user = userCredential.user; - // Add the new admin role and email to the rolemail ref await set(ref(database, `rolemail/${user.uid}`), { email: formData.email, @@ -223,7 +217,6 @@ useEffect(() => { email: formData.email, password: formData.password, role: formData.role, - }); // Close the dialog and reset the form @@ -233,6 +226,20 @@ useEffect(() => { setError("Failed to add admin. Please try again."); } }; + const fetchMessages = async (adminId) => { + const messagesRef = ref(database, `chats/${adminId}`); + const snapshot = await get(messagesRef); + if (snapshot.exists()) { + const messagesData = snapshot.val(); + const allMessages = Object.entries(messagesData).map( + ([msgId, message]) => ({ + ...message, + msgId, + }) + ); + setMessages(allMessages); + } + }; const columns = [ { field: "id", headerName: "ID", flex: 1 }, @@ -274,21 +281,17 @@ useEffect(() => { ), }, ]; - + const handleSendMessage = async () => { if (chatMessage.trim() === "") return; - const newMessageRef = ref( - database, - `messages/${selectedAdminId}/${Date.now()}` - ); - await set(newMessageRef, { + const messageRef = ref(database, `chats/${selectedAdminId}`); + await push(messageRef, { message: chatMessage, timestamp: Date.now(), sender: "superadmin", }); - setChatMessage(""); - setOpenChatDialog(false); + fetchMessages(selectedAdminId); }; return ( @@ -459,12 +462,12 @@ useEffect(() => {
- setOpenChatDialog(false)}> + {/* setOpenChatDialog(false)}> - Send Message + CHAT WITH ADMIN { Send + */} + setOpenChatDialog(false)}> + Chat with Admin + + + {messages.map((msg) => ( + + + {msg.message} + + + ))} + + + + setChatMessage(e.target.value)} + placeholder="Type a message" + fullWidth + variant="outlined" + size="small" + /> + + - ); }; From 2fc5268ad3d158af653925e8a65f3fa0aad89247 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 22:14:59 +0530 Subject: [PATCH 74/83] fixed styling of chat --- src/components/Notification.jsx | 43 ++++++++++++++++++-- src/scenes/AdminList/index.jsx | 69 +++++++++++++-------------------- src/scenes/global/Topbar.jsx | 5 +-- 3 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/components/Notification.jsx b/src/components/Notification.jsx index 3c31ba7..7876db2 100644 --- a/src/components/Notification.jsx +++ b/src/components/Notification.jsx @@ -8,6 +8,7 @@ import { useTheme, } from "@mui/material"; import { Delete as DeleteIcon } from "@mui/icons-material"; +import SendIcon from "@mui/icons-material/Send"; import { database, auth } from "../firebase"; import { ref, get, remove, push, onChildAdded, off } from "firebase/database"; import { useParams } from "react-router-dom"; @@ -85,10 +86,11 @@ const Notifications = () => { return ( Notifications - + {messages.map((msg, index) => ( - + {msg.message} {new Date(msg.timestamp).toLocaleString()} handleDelete(msg.msgId)}> @@ -99,8 +101,41 @@ const Notifications = () => { ))} - setNewMessage(e.target.value)} /> - + setNewMessage(e.target.value)} /> + + ); diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 0f49dd7..5bc4d02 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -29,7 +29,9 @@ import { import { auth, database } from "../../firebase"; import Header from "../../components/Header"; import DeleteIcon from "@mui/icons-material/Delete"; +import SendIcon from "@mui/icons-material/Send"; import ChatIcon from "@mui/icons-material/Chat"; + import { useNavigate } from "react-router-dom"; import { createUserWithEmailAndPassword } from "firebase/auth"; @@ -56,9 +58,10 @@ const AdminList = () => { const navigate = useNavigate(); useEffect(() => { - const fetchAdmins = async () => { + const fetchAdmins = async (id) => { const adminsRef = ref(database, "admins"); const snapshot = await get(adminsRef); + if (snapshot.exists()) { const users = snapshot.val(); @@ -68,7 +71,7 @@ const AdminList = () => { })); // Fetch feedbacks and merge with admin data - const feedbackRef = ref(database, "feedback"); + const feedbackRef = ref(database, `admins/${id}`); const feedbackSnapshot = await get(feedbackRef); const feedbackData = feedbackSnapshot.exists() ? feedbackSnapshot.val() @@ -462,43 +465,14 @@ const AdminList = () => { - {/* setOpenChatDialog(false)}> + setOpenChatDialog(false)}> - CHAT WITH ADMIN + Chat with Admin - - setChatMessage(e.target.value)} - sx={{ - marginBottom: "10px", - boxShadow: - "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", - "&:hover": { - boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", - }, - }} - /> - - - - - - */} - setOpenChatDialog(false)}> - Chat with Admin - + { { fullWidth variant="outlined" size="small" + sx={{ + marginBottom: "10px", + boxShadow: + "0px 2px 3px -1px rgba(0,0,0,0.1), 0px 1px 0px 0px rgba(25,28,33,0.02), 0px 0px 0px 1px rgba(25,28,33,0.08)", + "&:hover": { + boxShadow: "0px 0px 8px 2px rgba(33,150,243,0.5)", + }, + }} /> - + + + diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index ade807a..00e9a02 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -5,8 +5,7 @@ import { auth, database } from "../../firebase"; // Import Firebase auth import { signOut } from "firebase/auth"; import { useEffect, useState } from "react"; import { get, ref } from "firebase/database"; -import NotificationsIcon from "@mui/icons-material/Notifications"; - +import ChatIcon from "@mui/icons-material/Chat"; const Topbar = ({ handleLogout }) => { const theme = useTheme(); const colors = tokens(theme.palette.mode); @@ -79,7 +78,7 @@ const Topbar = ({ handleLogout }) => { navigate("/notifications")}> - + From 327cff2977139e10f3e42fc5d323558c344f12f2 Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Fri, 5 Jul 2024 22:30:39 +0530 Subject: [PATCH 75/83] feat reduced the glow effect --- src/scenes/dashboard/index.jsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 5da0d9e..16a8f34 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -63,7 +63,6 @@ const Dashboard = () => { db, `admins/${auth.currentUser.uid}/formData/salesPerUnit` - ); const snapshot = await get(dataRef); @@ -106,7 +105,6 @@ const Dashboard = () => { console.log( "Fetching data from path:", `admins/${auth.currentUser.uid}/formData/salesPerUnit` - ); const snapshot = await get(salesPerUnitRef); @@ -260,7 +258,7 @@ const Dashboard = () => { justifyContent="center" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -286,7 +284,7 @@ const Dashboard = () => { justifyContent="center" sx={{ border: `2px solid ${colors.purpleAccent[600]}`, - boxShadow: `0 0 10px ${colors.purpleAccent[600]}`, + boxShadow: `0 0 5px ${colors.purpleAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -312,7 +310,7 @@ const Dashboard = () => { justifyContent="center" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -338,7 +336,7 @@ const Dashboard = () => { id="line-chart" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -386,7 +384,7 @@ const Dashboard = () => { overflow="auto" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -414,7 +412,7 @@ const Dashboard = () => { p="15px" sx={{ border: `2px solid ${colors.grey[600]}`, - boxShadow: `0 0 10px ${colors.grey[600]}`, + boxShadow: `0 0 5px ${colors.grey[600]}`, }} > @@ -449,7 +447,7 @@ const Dashboard = () => { p="30px" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -482,7 +480,7 @@ const Dashboard = () => { id="bar-chart" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, @@ -507,7 +505,7 @@ const Dashboard = () => { id="geo-chart" sx={{ border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 }, From e7808909b2085f53290035e070378fb6e2f212d2 Mon Sep 17 00:00:00 2001 From: Animesh239 Date: Fri, 5 Jul 2024 23:56:20 +0530 Subject: [PATCH 76/83] feedback --- src/scenes/AdminList/index.jsx | 74 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/scenes/AdminList/index.jsx b/src/scenes/AdminList/index.jsx index 5bc4d02..feac0bd 100644 --- a/src/scenes/AdminList/index.jsx +++ b/src/scenes/AdminList/index.jsx @@ -33,7 +33,7 @@ import SendIcon from "@mui/icons-material/Send"; import ChatIcon from "@mui/icons-material/Chat"; import { useNavigate } from "react-router-dom"; -import { createUserWithEmailAndPassword } from "firebase/auth"; +import { createUserWithEmailAndPassword, getAuth } from "firebase/auth"; const AdminList = () => { const theme = useTheme(); @@ -58,50 +58,46 @@ const AdminList = () => { const navigate = useNavigate(); useEffect(() => { - const fetchAdmins = async (id) => { + const fetchAdmins = async () => { const adminsRef = ref(database, "admins"); const snapshot = await get(adminsRef); - if (snapshot.exists()) { const users = snapshot.val(); - const adminList = Object.keys(users).map((key) => ({ + const adminsList = Object.keys(users).map((key) => ({ id: key, ...users[key], })); // Fetch feedbacks and merge with admin data - const feedbackRef = ref(database, `admins/${id}`); - const feedbackSnapshot = await get(feedbackRef); - const feedbackData = feedbackSnapshot.exists() - ? feedbackSnapshot.val() - : {}; - - const adminListWithFeedback = adminList.map((admin) => { - const adminFeedbacks = feedbackData[admin.uid] - ? Object.values(feedbackData[admin.uid]) - : []; - const length = adminFeedbacks.length; - const latestFeedback = length > 0 ? adminFeedbacks[length - 1] : null; - - return { - ...admin, - feedback: latestFeedback ? latestFeedback.feedback : "No feedback", - }; - }); + const updatedAdminsList = await Promise.all( + adminsList.map(async (admin) => { + const feedbackRef = ref(database, `admins/${admin.id}/feedback`); + const feedbackSnapshot = await get(feedbackRef); + let latestFeedback = "No feedback"; + if (feedbackSnapshot.exists()) { + const feedbacks = Object.values(feedbackSnapshot.val()); + latestFeedback = + feedbacks.length > 0 ? feedbacks[feedbacks.length - 1] : "No feedback"; + } + return { ...admin, feedback: latestFeedback }; + }) + ); - setAdmins(adminListWithFeedback); + setAdmins(updatedAdminsList); + } - // Get the current user's ID and role - const currentUserId = auth.currentUser.uid; - const roleMailRef = ref(database, `rolemail/${currentUserId}`); - const roleMailSnapshot = await get(roleMailRef); - if (roleMailSnapshot.exists()) { - const currentUserData = roleMailSnapshot.val(); - setCurrentUserRole(currentUserData.role); - } + // Get the current user's ID and role + const auth = getAuth(); + const currentUserId = auth.currentUser.uid; + const roleMailRef = ref(database, `rolemail/${currentUserId}`); + const roleMailSnapshot = await get(roleMailRef); + if (roleMailSnapshot.exists()) { + const currentUserData = roleMailSnapshot.val(); + setCurrentUserRole(currentUserData.role); } }; + fetchAdmins(); }, []); @@ -110,12 +106,14 @@ const AdminList = () => { const handleChildAddedOrChanged = async (snapshot) => { const data = snapshot.val(); - const feedbackRef = ref(database, `feedback/${data.uid}`); + const feedbackRef = ref(database, `admins/${snapshot.key}/feedback`); const feedbackSnapshot = await get(feedbackRef); - const feedbacks = feedbackSnapshot.exists() - ? Object.values(feedbackSnapshot.val()) - : []; - const feedbackText = feedbacks.map((fb) => fb.feedback).join(", "); + let latestFeedback = "No feedback"; + if (feedbackSnapshot.exists()) { + const feedbacks = Object.values(feedbackSnapshot.val()); + latestFeedback = + feedbacks.length > 0 ? feedbacks[feedbacks.length - 1] : "No feedback"; + } setAdmins((prevAdmins) => { const existingIndex = prevAdmins.findIndex( @@ -126,13 +124,13 @@ const AdminList = () => { updatedAdmins[existingIndex] = { id: snapshot.key, ...data, - feedback: feedbackText, + feedback: latestFeedback, }; return updatedAdmins; } else { return [ ...prevAdmins, - { id: snapshot.key, ...data, feedback: feedbackText }, + { id: snapshot.key, ...data, feedback: latestFeedback }, ]; } }); From 7f9305c850b8d5fc1487d91e6698dfa70031b5ca Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 6 Jul 2024 08:50:11 +0530 Subject: [PATCH 77/83] Reduced glow effect in the profile form --- src/scenes/form/index.jsx | 57 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/scenes/form/index.jsx b/src/scenes/form/index.jsx index f18e15a..25f16e7 100644 --- a/src/scenes/form/index.jsx +++ b/src/scenes/form/index.jsx @@ -44,9 +44,7 @@ const Form = () => { setFormSubmissions((prev) => [...prev, values]); } - set(ref(database, "admins/" + user.uid + "/formData"), values) - .then(() => { console.log("Data saved successfully!"); }) @@ -86,7 +84,6 @@ const Form = () => { const user = auth.currentUser; if (user) { - const userRef = ref(database, "admins/" + user.uid + "/formData"); get(userRef) @@ -290,8 +287,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -299,7 +296,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -333,8 +330,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -342,7 +339,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -440,8 +437,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -449,7 +446,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -481,8 +478,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -490,7 +487,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -612,8 +609,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -621,7 +618,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -653,8 +650,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 5px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -662,7 +659,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -733,8 +730,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -742,7 +739,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -774,8 +771,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -783,7 +780,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", @@ -816,8 +813,8 @@ const Form = () => { justifyContent: "center", borderRadius: "8px", - border: `2px solid ${colors.tealAccent[600]}`, - boxShadow: `0 0 10px ${colors.tealAccent[600]}`, + border: `1px solid ${colors.tealAccent[600]}`, + boxShadow: `0 0 3px ${colors.tealAccent[600]}`, background: "linear-gradient(110deg,#000103 45%,#1e2631 55%,#000103)", backgroundSize: "200% 100%", @@ -825,7 +822,7 @@ const Form = () => { color: "#9CA3AF", fontWeight: "500", textTransform: "none", - animation: "shimmer 2s infinite", + animation: "shimmer 15s infinite", transition: "color 0.3s", "&:hover": { color: "#FFFFFF", From 5e962e6cae9a41b03f909d1a4214f725f0701e4f Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Sat, 6 Jul 2024 09:08:14 +0530 Subject: [PATCH 78/83] fixed feedback card limit and styling --- src/scenes/feedback/index.jsx | 42 ++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/scenes/feedback/index.jsx b/src/scenes/feedback/index.jsx index 243cbf1..39c7f65 100644 --- a/src/scenes/feedback/index.jsx +++ b/src/scenes/feedback/index.jsx @@ -6,6 +6,7 @@ import { Avatar, Card, useTheme, + IconButton, } from "@mui/material"; import { getAuth } from "firebase/auth"; @@ -15,10 +16,10 @@ import { useEffect, useState } from "react"; import { database } from "../../firebase"; import { tokens } from "../../theme"; import Header from "../../components/Header"; +import DeleteIcon from "@mui/icons-material/Delete"; import TypewriterEffectSmooth from "../../components/TypeWriterEffect"; - const Feedback = () => { const theme = useTheme(); const colors = tokens(theme.palette.mode); @@ -28,17 +29,17 @@ const Feedback = () => { const [userInfo, setUserInfo] = useState({}); useEffect(() => { - const fetchUserFeedbacks = () => { const auth = getAuth(); const user = auth.currentUser; if (user) { - const feedbackRef = ref(database, `admins/${user.uid}`); onValue(feedbackRef, (snapshot) => { const data = snapshot.val(); if (data) { - setUserFeedbacks(data.feedback || []); + const feedbacks = data.feedback || []; + const latestFeedbacks = feedbacks.slice(-5).reverse(); + setUserFeedbacks(latestFeedbacks); setUserInfo({ name: data.name || "Anonymous", email: data.email, @@ -49,7 +50,6 @@ const Feedback = () => { } }; - fetchUserFeedbacks(); }, []); @@ -57,7 +57,6 @@ const Feedback = () => { const auth = getAuth(); const user = auth.currentUser; if (user) { - const feedbackRef = ref(database, `admins/${user.uid}`); const snapshot = await get(feedbackRef); if (snapshot.exists()) { @@ -68,7 +67,25 @@ const Feedback = () => { }); setFeedback(""); } + } + }; + const handleDelete = async (index) => { + const auth = getAuth(); + const user = auth.currentUser; + if (user) { + const feedbackRef = ref(database, `admins/${user.uid}`); + const snapshot = await get(feedbackRef); + if (snapshot.exists()) { + const adminData = snapshot.val(); + const updatedFeedbacks = adminData.feedback.filter( + (_, i) => i !== index + ); + await update(feedbackRef, { + feedback: updatedFeedbacks, + }); + setUserFeedbacks(updatedFeedbacks.slice(-5)); + } } }; @@ -77,9 +94,7 @@ const Feedback = () => {
} subtitle="Every improvement starts with a feedback" @@ -100,7 +115,6 @@ const Feedback = () => { "@media (prefers-color-scheme: dark)": { bgcolor: "#18181b", // Equivalent to dark:bg-zinc-900 - }, }} > @@ -148,7 +162,6 @@ const Feedback = () => { {userFeedbacks.map((feedback, index) => ( { > - {userInfo.name.charAt(0)} @@ -182,11 +194,13 @@ const Feedback = () => { - {userInfo.role} + + handleDelete(index)} sx={{ mb: 0.2 }}> + + {feedback} - ))} @@ -195,6 +209,4 @@ const Feedback = () => { ); }; - export default Feedback; - From c74f00a9d2cfb8c65bb9166f128108231a42e9bd Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Mon, 8 Jul 2024 12:50:00 +0530 Subject: [PATCH 79/83] fixed: increase the font size --- src/scenes/team/index.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 40176ba..600b243 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -71,7 +71,6 @@ const Team = () => { }; getAdminData(); - } }, [user]); @@ -137,7 +136,6 @@ const Team = () => { }, ]; - columns.push({ field: "blocked", headerName: "Blocked", @@ -243,7 +241,7 @@ const Team = () => { displayName: formData.name, role: formData.role, }; - + try { const response = await fetch("http://localhost:3000/create-user", { method: "POST", @@ -252,7 +250,7 @@ const Team = () => { }, body: JSON.stringify(userData), }); - + if (!response.ok) { throw new Error("Error creating user"); } @@ -263,10 +261,10 @@ const Team = () => { email: userData.email, role: userData.role, }); - + const data = await response.json(); console.log("User created with UID:", data.uid); - + handleDialogClose(); } catch (error) { console.error("Error:", error); @@ -284,7 +282,6 @@ const Team = () => { ); } catch (error) { console.error("Error deleting user:", error); - } }; const lampEffectStyle = { @@ -359,6 +356,7 @@ const Team = () => { "& .MuiDataGrid-columnHeaders": { backgroundColor: colors.blueAccent[700], borderBottom: "none", + fontSize: "1rem", }, "& .MuiDataGrid-virtualScroller": { backgroundColor: colors.primary[400], @@ -373,6 +371,9 @@ const Team = () => { "& .MuiDataGrid-toolbarContainer .MuiButton-text": { color: `${colors.grey[100]} !important`, }, + "& .MuiDataGrid-row": { + fontSize: "0.9rem", // Increase font size for user data + }, }} > Date: Mon, 8 Jul 2024 13:26:27 +0530 Subject: [PATCH 80/83] fixed messageCount in the chatIcon badgeContentSection --- src/scenes/global/Topbar.jsx | 44 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/scenes/global/Topbar.jsx b/src/scenes/global/Topbar.jsx index 00e9a02..af804e2 100644 --- a/src/scenes/global/Topbar.jsx +++ b/src/scenes/global/Topbar.jsx @@ -6,6 +6,7 @@ import { signOut } from "firebase/auth"; import { useEffect, useState } from "react"; import { get, ref } from "firebase/database"; import ChatIcon from "@mui/icons-material/Chat"; + const Topbar = ({ handleLogout }) => { const theme = useTheme(); const colors = tokens(theme.palette.mode); @@ -14,6 +15,7 @@ const Topbar = ({ handleLogout }) => { const [unreadMessagesCount, setUnreadMessagesCount] = useState(0); const [messages, setMessages] = useState([]); const [currentUserRole, setCurrentUserRole] = useState(null); + const [hasClickedBadge, setHasClickedBadge] = useState(false); const handleLogoutdb = async () => { try { @@ -35,7 +37,6 @@ const Topbar = ({ handleLogout }) => { } const fetchMessagesAndUserRole = async () => { - const currentUser = auth.currentUser || storedUser; if (!currentUser) { @@ -43,44 +44,55 @@ const Topbar = ({ handleLogout }) => { return; } - const userRef = ref(database, `rolemail/${currentUser.uid}`); - const userSnapshot = await get(userRef); let userData; if (userSnapshot.exists()) { userData = userSnapshot.val(); setCurrentUserRole(userData.role); } - + if (userData && userData.role === "admin") { - const messagesRef = ref(database, "messages"); + const messagesRef = ref(database, "chats"); const snapshot = await get(messagesRef); if (snapshot.exists()) { const messagesData = snapshot.val(); - const allMessages = Object.values(messagesData).flatMap((userMessages) => - Object.values(userMessages) + const allMessages = Object.values(messagesData).flatMap( + (userMessages) => Object.values(userMessages) + ); + const superAdminMessages = allMessages.filter( + (message) => message.sender === "superadmin" ); - setMessages(allMessages); - setUnreadMessagesCount(allMessages.length); // Update this logic to count only unread messages if necessary + if (!hasClickedBadge) { + setUnreadMessagesCount(superAdminMessages.length); + } + } else { + setUnreadMessagesCount(0); // No messages in the database } } }; fetchMessagesAndUserRole(); + }, [handleLogout, navigate, hasClickedBadge]); - }, [handleLogout]); - + const handleIconClick = () => { + setUnreadMessagesCount(0); + setHasClickedBadge(true); + navigate("/notifications"); + }; return ( {currentUserRole === "admin" && location.pathname !== "/admins" && ( - navigate("/notifications")}> - - - - + + + + + )} From 3eef4aed0fedeab5f60a94b580dc258d1ec2673f Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Tue, 9 Jul 2024 16:59:57 +0530 Subject: [PATCH 81/83] fix handleformsubmit for editing userData --- src/scenes/team/index.jsx | 79 +++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/scenes/team/index.jsx b/src/scenes/team/index.jsx index 600b243..e826326 100644 --- a/src/scenes/team/index.jsx +++ b/src/scenes/team/index.jsx @@ -234,40 +234,61 @@ const Team = () => { }; const handleFormSubmit = async () => { - const userData = { - uid: `${adminID}_${uuidv4()}`, - email: formData.email, - password: formData.password, - displayName: formData.name, - role: formData.role, - }; + if (selectedUser) { + // Editing an existing user + const userRef = ref(database, `userList/${adminID}/${selectedUser.id}`); + const updatedUserData = { + name: formData.name, + email: formData.email, + role: formData.role, + }; - try { - const response = await fetch("http://localhost:3000/create-user", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }); - - if (!response.ok) { - throw new Error("Error creating user"); + if (formData.password) { + updatedUserData.password = formData.password; } - // save to rolemail - const roleMailRef = ref(database, `rolemail/${userData.uid}`); - await set(roleMailRef, { - email: userData.email, - role: userData.role, - }); + try { + await update(userRef, updatedUserData); + handleDialogClose(); + } catch (error) { + console.error("Error updating user:", error); + } + } else { + const userData = { + uid: `${adminID}_${uuidv4()}`, + email: formData.email, + password: formData.password, + displayName: formData.name, + role: formData.role, + }; - const data = await response.json(); - console.log("User created with UID:", data.uid); + try { + const response = await fetch("http://localhost:3000/create-user", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); - handleDialogClose(); - } catch (error) { - console.error("Error:", error); + if (!response.ok) { + throw new Error("Error creating user"); + } + + // save to rolemail + const roleMailRef = ref(database, `rolemail/${userData.uid}`); + await set(roleMailRef, { + email: userData.email, + role: userData.role, + }); + + const data = await response.json(); + console.log("User created with UID:", data.uid); + + handleDialogClose(); + } catch (error) { + console.error("Error:", error); + } } }; From 2863152832feeb565fdac6925f7c2e0e7113234d Mon Sep 17 00:00:00 2001 From: RickDeb2004 Date: Thu, 11 Jul 2024 17:38:46 +0530 Subject: [PATCH 82/83] fixed changed the order of transaction id and name --- src/scenes/dashboard/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scenes/dashboard/index.jsx b/src/scenes/dashboard/index.jsx index 16a8f34..8fd5da1 100644 --- a/src/scenes/dashboard/index.jsx +++ b/src/scenes/dashboard/index.jsx @@ -421,10 +421,10 @@ const Dashboard = () => { variant="h5" fontWeight="600" > - {transaction.txId} + {transaction.user} - {transaction.user} + {transaction.txId} {transaction.date} From 498097214dd57a9d9d918bb949460249eaa48344 Mon Sep 17 00:00:00 2001 From: DEBANJAN MUKHERJEE <113274631+RickDeb2004@users.noreply.github.com> Date: Sun, 14 Jul 2024 18:10:37 +0530 Subject: [PATCH 83/83] Update README.md --- README.md | 74 +++++++++---------------------------------------------- 1 file changed, 12 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 58beeac..4544d1e 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,20 @@ -# Getting Started with Create React App +**Credentials** -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +**Admin (Business Executives)** -## Available Scripts +kohli@gmail.com +password: 12345678 -In the project directory, you can run: +subha@gmail.com +12345678 -### `npm start` +(super admin can also create by own) -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. +**Super Admin** +testing@gmail.com +12345678 -The page will reload when you make changes.\ -You may also see any lint errors in the console. -### `npm test` +**Users(Employee)** -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) +admin can create by own