diff --git a/app/api/getevents/events.json b/app/api/getevents/events.json new file mode 100644 index 0000000..7f9de70 --- /dev/null +++ b/app/api/getevents/events.json @@ -0,0 +1,42 @@ +[ + { + "id": 4546902, + "name": "DZR After Party Series", + "eventStart": "2024-10-17T15:15:00.000+0000" + }, + { + "id": 4548972, + "name": "In the Zone 2 with DZR", + "eventStart": "2024-10-19T07:30:00.000+0000" + }, + { + "id": 4549062, + "name": "In the Zone 2 with DZR", + "eventStart": "2024-10-19T12:30:00.000+0000" + }, + { + "id": 4550504, + "name": "The Zwifty Fifty with DZR", + "eventStart": "2024-10-20T12:45:00.000+0000" + }, + { + "id": 4544106, + "name": "STAGES by DZR - The iTT (1/4)", + "eventStart": "2024-10-22T17:20:00.000+0000" + }, + { + "id": 4544110, + "name": "STAGES by DZR - The Sprint (2/4)", + "eventStart": "2024-10-22T17:45:00.000+0000" + }, + { + "id": 4544115, + "name": "STAGES by DZR - The Break Away (3/4)", + "eventStart": "2024-10-29T18:20:00.000+0000" + }, + { + "id": 4544116, + "name": "STAGES by DZR - The Hill (4/4)", + "eventStart": "2024-11-05T18:20:00.000+0000" + } +] \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 725e7e0..b6b1eff 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,29 +1,33 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import SidebarWithHeader from "@/components/Sidebar"; -import { Analytics } from "@vercel/analytics/react" -import { SpeedInsights } from "@vercel/speed-insights/next" +import { Analytics } from "@vercel/analytics/react"; +import { SpeedInsights } from "@vercel/speed-insights/next"; +import { AuthProvider } from '@/components/auth/AuthContext'; require('dotenv').config(); // app/layout.tsx -import { Providers } from './providers' +import { Providers } from './providers'; export default function RootLayout({ children, }: { - children: React.ReactNode, + children: React.ReactNode; }) { return ( - + - - - {children} - - - + {/* Wrap the app with AuthProvider to enable authentication */} + + + + {children} + + + + - ) -} + ); +} \ No newline at end of file diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..8e21b17 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,82 @@ +// app/login/page.tsx + +'use client'; // This line is essential for client components + +import React, { useState, useContext } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { + Container, + Heading, + Text, + Grid, + InputGroup, + InputRightElement, + Input, + Stack, + Image, + Flex, + Button, +} from '@chakra-ui/react'; +import ForgotPasswordModal from '@/components/auth/ForgotPasswordModel'; +import SignUpModal from '@/components/auth/SignUpModal'; // Adjust the path as necessary + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const { login } = useContext(AuthContext); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + console.log("Login form submitted"); // Log form submission + try { + await login(email, password); + console.log("Redirecting to Members Zone..."); + router.push('/members-zone'); // Redirect to members-zone + } catch (error) { + console.error("Login failed", error); // Log any errors during login + } + }; + + const [show, setShow] = useState(false); + const handleClick = () => setShow(!show); + + const handleSignUpRedirect = () => { + router.push('/signup'); // Redirect to the signup page + }; + + return ( + + + + DZR logo + Members Login + + + + setEmail(e.target.value)} /> + + + setPassword(e.target.value)} /> + + + + + + + + + {/* Include the modal here */} + + + ); +} diff --git a/app/members-zone/dzr-team-race/page.tsx b/app/members-zone/dzr-team-race/page.tsx new file mode 100644 index 0000000..97f65f6 --- /dev/null +++ b/app/members-zone/dzr-team-race/page.tsx @@ -0,0 +1,49 @@ +// pages/coming-soon.tsx +'use client'; + +import ComingSoon from "@/components/ComingSoon"; +import { useContext, useEffect } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { signOut } from 'firebase/auth'; // Import signOut +import { auth } from '@/app/utils/firebaseConfig'; // Adjust path if necessary +import LoadingSpinnerMemb from '@/components/LoadingSpinnerMemb'; + +import { + Container, + Heading, + Text, + Stack, + Button, + Center, // Import Button +} from '@chakra-ui/react'; + +const DZRTeamRace = () => { + const { currentUser, loading } = useContext(AuthContext); + const router = useRouter(); + + useEffect(() => { + if (!loading && !currentUser) { + router.push('/login'); // Redirect to login if not authenticated + } + }, [currentUser, loading, router]); + + if (loading) { + return // Show loading while checking auth + } + + return ( +
+ {currentUser ? ( +
+ +
+ ) : ( + You need to login. + )} + +
+ ); +}; + +export default DZRTeamRace; \ No newline at end of file diff --git a/app/members-zone/page.tsx b/app/members-zone/page.tsx new file mode 100644 index 0000000..d0f2fe5 --- /dev/null +++ b/app/members-zone/page.tsx @@ -0,0 +1,82 @@ +'use client'; + +import React, { useContext, useEffect, useState } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { signOut } from 'firebase/auth'; // Import signOut +import { auth } from '@/app/utils/firebaseConfig'; // Adjust path if necessary +import FeaturesMembers from "@/components/FeaturesMembers"; +import LoadingSpinnerMemb from '@/components/LoadingSpinnerMemb'; +import EditProfileModal from '@/components/auth/EditProfileModal'; + +import { + Container, + Heading, + Text, + Stack, + Button, + Box, + Divider, + Center, // Import Button +} from '@chakra-ui/react'; + +const MembersZone = () => { + const { currentUser, loading } = useContext(AuthContext); + const router = useRouter(); + const [isEditProfileModalOpen, setEditProfileModalOpen] = useState(false); + + useEffect(() => { + if (!loading && !currentUser) { + router.push('/login'); // Redirect to login if not authenticated + } + }, [currentUser, loading, router]); + + const handleLogout = async () => { + try { + await signOut(auth); // Sign out the user + router.push('/login'); // Redirect to login after logout + } catch (error: any) { // Use 'any' for general error type + console.error("Logout failed:", error.message); // Safely access error.message + } + }; + + if (loading) { + return ; // Show loading while checking auth + } + + return ( +
+ + + {currentUser ? ( + <> + + Members Zone + Welcome, {currentUser.displayName} +
+ + +
+ +
+ + + {/* Edit Profile Modal */} + setEditProfileModalOpen(false)} + /> + + ) : ( + You need to login. + )} +
+
+ ); + }; + +export default MembersZone; diff --git a/app/members-zone/race-calendar/page.tsx b/app/members-zone/race-calendar/page.tsx new file mode 100644 index 0000000..99765e2 --- /dev/null +++ b/app/members-zone/race-calendar/page.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useContext, useEffect } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { signOut } from 'firebase/auth'; // Import signOut +import { auth } from '@/app/utils/firebaseConfig'; // Adjust path if necessary +import LoadingSpinnerMemb from '@/components/LoadingSpinnerMemb'; + +import GoogleCalendarEmbed from "@/components/GoogleCalendar"; +import { + Container, + Heading, + Text, + Stack, + Button, + Center, // Import Button +} from '@chakra-ui/react'; + +const RaceCalendar = () => { + const { currentUser, loading } = useContext(AuthContext); + const router = useRouter(); + + useEffect(() => { + if (!loading && !currentUser) { + router.push('/login'); // Redirect to login if not authenticated + } + }, [currentUser, loading, router]); + + const handleLogout = async () => { + try { + await signOut(auth); // Sign out the user + router.push('/login'); // Redirect to login after logout + } catch (error: any) { // Use 'any' for general error type + console.error("Logout failed:", error.message); // Safely access error.message + } + }; + + if (loading) { + return ; // Show loading while checking auth + } + + return ( +
+ {currentUser ? ( +
+ +
+ +
+
+ ) : ( + You need to login. + )} + +
+ ); +}; + +export default RaceCalendar; \ No newline at end of file diff --git a/app/members-zone/shop/page.tsx b/app/members-zone/shop/page.tsx new file mode 100644 index 0000000..7c136d8 --- /dev/null +++ b/app/members-zone/shop/page.tsx @@ -0,0 +1,157 @@ +// members-zone/shop/page.tsx +'use client'; + +import { useContext, useEffect, useState } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { signOut } from 'firebase/auth'; +import { auth } from '@/app/utils/firebaseConfig'; +import LoadingSpinnerMemb from '@/components/LoadingSpinnerMemb'; +import ProductList from '@/components/shop/ProductList'; +import { Center, Heading, IconButton, Flex, Box, Grid, GridItem, Text, Badge } from '@chakra-ui/react'; +import { FiShoppingCart } from 'react-icons/fi'; // Add icon for basket +import CartCheckoutModal from '@/components/shop/CartCheckoutModal'; +import ActiveOrders from '@/components/shop/ActiveOrders'; + +const Shop = () => { + const { currentUser, loading } = useContext(AuthContext); + const router = useRouter(); + + // Effect to redirect if not authenticated + useEffect(() => { + if (!loading && !currentUser) { + router.push('/login'); + } + }, [currentUser, loading, router]); + + interface Product { + id: number; + name: string; + description: React.ReactNode; + price: number; + imageUrl: string; + availableSizes: string[]; + availableFit: string[]; + weight: number; + } + + interface CartItem extends Product { + size: string; + fit: string; + quantity: number; + } + + const [cartItems, setCartItems] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); // State to open/close modal + + const addToCart = (product: Product, quantity: number, size: string, fit: string) => { + setCartItems((prevItems) => { + const existingItem = prevItems.find((item) => item.id === product.id && item.size === size && item.fit === fit); + if (existingItem) { + return prevItems.map((item) => + item.id === product.id && item.size === size && item.fit === fit + ? { ...item, quantity: item.quantity + quantity } + : item + ); + } + return [...prevItems, { ...product, size, fit, quantity }]; + }); + }; + + const removeFromCart = (id: number) => { + setCartItems((prevItems) => prevItems.filter((item) => item.id !== id)); + }; + + const clearCart = () => { + setCartItems([]); + }; + + const handleLogout = async () => { + try { + await signOut(auth); + router.push('/login'); + } catch (error: any) { + console.error('Logout failed:', error.message); + } + }; + + const handleModalOpen = () => { + setIsModalOpen(true); + }; + + const handleModalClose = () => { + setIsModalOpen(false); + }; + + const cartItemCount = cartItems.reduce((total, item) => total + item.quantity, 0); // Calculate total items in cart + + if (loading) { + return ; + } + + return ( + <> + {currentUser ? ( +
+ + + + + + DZR tøjbestilling + + + + } + colorScheme="red" + onClick={handleModalOpen} // Function to open the modal + + /> + {cartItemCount > 0 && ( // Only show badge if there are items in the cart + + {cartItemCount} + + )} + + + + + + + + + + +
+ ) : ( + You need to login. + )} + + ); +}; + +export default Shop; diff --git a/app/members-zone/zrl/page.tsx b/app/members-zone/zrl/page.tsx new file mode 100644 index 0000000..4de7467 --- /dev/null +++ b/app/members-zone/zrl/page.tsx @@ -0,0 +1,49 @@ +// app/members-zone/ZRL/page.tsx +'use client'; + +import ComingSoon from "@/components/ComingSoon"; +import { useContext, useEffect } from 'react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { useRouter } from 'next/navigation'; +import { auth } from '@/app/utils/firebaseConfig'; // Adjust path if necessary +import LoadingSpinnerMemb from '@/components/LoadingSpinnerMemb'; +import ZRL from "@/components/ZRL/ZRL"; + +import { + Container, + Heading, + Text, + Stack, + Button, + Center, // Import Button +} from '@chakra-ui/react'; + +const ZRLpage = () => { + const { currentUser, loading } = useContext(AuthContext); + const router = useRouter(); + + useEffect(() => { + if (!loading && !currentUser) { + router.push('/login'); // Redirect to login if not authenticated + } + }, [currentUser, loading, router]); + + if (loading) { + return // Show loading while checking auth + } + + return ( +
+ {currentUser ? ( +
+ +
+ ) : ( + You need to login. + )} + +
+ ); +}; + +export default ZRLpage; \ No newline at end of file diff --git a/app/members/page.tsx b/app/members/page.tsx deleted file mode 100644 index f308ccb..0000000 --- a/app/members/page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -'use client' - -import Image from "next/image"; -import { Inter } from "next/font/google"; -import HeroSection from "@/components/HeroSection"; -import Features from "@/components/Features"; -import GoogleCalendarEmbed from "@/components/GoogleCalendar"; - -import { ColorModeScript } from '@chakra-ui/react' - -import { - Flex, - Container, - chakra, - VisuallyHidden, - Heading, - Stack, - Text, - Button, - Icon, - IconProps, - Circle, - Square, - Input, - Grid, - InputRightElement, - InputGroup, - } from '@chakra-ui/react' - -const inter = Inter({ subsets: ['latin']}) - - -import React, { useState } from 'react'; - -function ProtectedPage() { - const [password, setPassword] = useState(''); - const [authenticated, setAuthenticated] = useState(false); - - const handleLogin = () => { - const password = process.env.MEMBERS_ZONE - if (password === password) { - setAuthenticated(true); - } else { - alert('Incorrect password'); - } - }; - const [show, setShow] = React.useState(false) - const handleClick = () => setShow(!show) - - return authenticated ? ( -
- -
- ) : ( - - - Sign In - - - - setPassword(e.target.value)}/> - - - - - - - - - ); -} - -export default ProtectedPage; \ No newline at end of file diff --git a/app/utils/firebaseConfig.js b/app/utils/firebaseConfig.js new file mode 100644 index 0000000..807dac4 --- /dev/null +++ b/app/utils/firebaseConfig.js @@ -0,0 +1,22 @@ +// app/utils/firebaseConfig.js + +import { initializeApp } from 'firebase/app'; +import { getAuth } from 'firebase/auth'; +import { getFirestore } from 'firebase/firestore'; // Import Firestore + +const firebaseConfig = { + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); +const db = getFirestore(app); // Initialize Firestore + +export { auth, db }; // Export Firestore as well diff --git a/components/ComingSoon.tsx b/components/ComingSoon.tsx new file mode 100644 index 0000000..8d429be --- /dev/null +++ b/components/ComingSoon.tsx @@ -0,0 +1,35 @@ +// components/ComingSoon.tsx +import React from 'react'; + +const ComingSoon: React.FC = () => { + return ( +
+

Coming Soon!

+

This page is a work in progress. Stay tuned for updates.

+
+ ); +}; + +const styles = { + container: { + display: 'flex', + flexDirection: 'column' as 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + textAlign: 'center' as 'center', + backgroundColor: 'black', + color: 'white', + padding: '20px', + }, + title: { + fontSize: '3rem', + margin: '0 0 10px', + }, + subtitle: { + fontSize: '1.5rem', + margin: '0', + }, +}; + +export default ComingSoon; diff --git a/components/FeaturesMembers.tsx b/components/FeaturesMembers.tsx new file mode 100644 index 0000000..6568da3 --- /dev/null +++ b/components/FeaturesMembers.tsx @@ -0,0 +1,104 @@ +'use client' +import './css/Features.css' + +import { + Box, + Button, + Container, + Flex, + Heading, + Icon, + Stack, + Text, + useColorModeValue, + Link, + Divider, + Center, + AbsoluteCenter, + Circle, + Card, + CardBody, + CardBodyProps, + CardHeader, + SimpleGrid, + SimpleGridProps, + keyframes, + VStack, +} from '@chakra-ui/react' +import { IconType } from 'react-icons'; + +import { motion } from 'framer-motion'; + +import { LiaMountainSolid } from "react-icons/lia"; +import { Im500Px } from "react-icons/im"; +import { AiOutlineAim } from "react-icons/ai"; +import { RiBoxingFill } from "react-icons/ri"; +import { MdOutlineTimer } from "react-icons/md"; +import { FaTrophy } from "react-icons/fa6"; +import { MdCalendarMonth } from "react-icons/md"; +import { BsShopWindow } from "react-icons/bs"; +import { MdOutlineDirectionsBike } from "react-icons/md"; +import { countItems } from '@/components/shop/countItems'; +import { useState, useEffect } from 'react'; + +interface Props { + href: string; + icon: IconType; + heading: string; + text1: string; + text2: string; +} + +const CustomCard: React.FC = ({ href, icon, heading, text1, text2 }) => { + return ( + + + + + + + + + + + {heading} + + {text1}
{text2} +
+
+
+
+
+ + ); +}; + +export default function FeaturesMembers() { + const [totalQuantity, setTotalQuantity] = useState(0); + + useEffect(() => { + const loadData = async () => { + try { + // Fetch total item counts from all users + const total = await countItems(); + setTotalQuantity(total); + } catch (error) { + console.error('Error loading item counts:', error); + } + }; + + loadData(); + }, []); + return ( +<> + + + + + {/**/} + + + + + ) +} \ No newline at end of file diff --git a/components/LoadingSpinnerMemb.tsx b/components/LoadingSpinnerMemb.tsx new file mode 100644 index 0000000..2988699 --- /dev/null +++ b/components/LoadingSpinnerMemb.tsx @@ -0,0 +1,69 @@ +import { + Box, + Text, + + } from '@chakra-ui/react' + +export default function LoadingSpinnerMemb() { + return ( + + + {/* Inner rim of the bike wheel */} + + {/* Spokes */} + {[...Array(12)].map((_, i) => ( + + ))} + + Loading... + + + ); +} \ No newline at end of file diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 65d1445..9523b41 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -70,11 +70,11 @@ interface SidebarProps extends BoxProps { const LinkItems: Array = [ { name: 'Home', href: '/', icon: MdDirectionsBike }, // { name: 'Puncheurs Summer Cup', href: 'puncheurs-summer-cup', icon: RiBoxingFill }, - { name: 'STAGES by DZR', href: 'stages', icon: MdOutlineTimer }, - { name: 'DZR After Party Series', href: 'dzr-after-party', icon: LiaMountainSolid }, - { name: 'In The Zone 2', href: 'in-the-zone-2', icon: AiOutlineAim }, - { name: 'The Zwifty Fifty', href: 'the-zwifty-fifty', icon: Im500Px }, - { name: 'Members Zone', href: 'members', icon: FaPeopleGroup }, + { name: 'STAGES by DZR', href: '/stages', icon: MdOutlineTimer }, + { name: 'DZR After Party Series', href: '/dzr-after-party', icon: LiaMountainSolid }, + { name: 'In The Zone 2', href: '/in-the-zone-2', icon: AiOutlineAim }, + { name: 'The Zwifty Fifty', href: '/the-zwifty-fifty', icon: Im500Px }, + { name: 'Members Zone', href: '/members-zone', icon: FaPeopleGroup }, ] const SidebarContent = ({ onClose, ...rest }: SidebarProps) => { diff --git a/components/ZRL/ZRL.tsx b/components/ZRL/ZRL.tsx new file mode 100644 index 0000000..da20ee4 --- /dev/null +++ b/components/ZRL/ZRL.tsx @@ -0,0 +1,242 @@ +import { useState, useEffect } from 'react'; +import { + Box, + Heading, + Grid, + GridItem, + Text, + Stack, + Button, + Center, + Flex, + Divider, + SimpleGrid, + Link, +} from '@chakra-ui/react'; +import { auth, db } from '@/app/utils/firebaseConfig'; +import { collection, onSnapshot, deleteDoc, doc } from 'firebase/firestore'; +import ZRLRegister from './ZRLRegister'; +import ZRLEditDelete from './ZRLEditDelete'; +import ZRLRider from './ZRLRider'; // Import the new ZRLRider component +import ZRLRiderEditDelete from './ZRLRiderEditDelete'; +import { ExternalLinkIcon } from '@chakra-ui/icons' + +interface Rider { + id?: string; // Optional ID for the rider + userId: string; // Field to store the user ID of the rider + name: string; // Rider's name + division: string; // Rider's division + rideTime: string; // Preferred race time +} + +interface Team { + id?: string; + name: string; + captainId: string; + captainName: string; + createdAt: string; + rideTime: string; + division: string; + lookingForRiders?: boolean; // New field for looking for riders +} + +const ZRL = () => { + const [teams, setTeams] = useState([]); + const [interestedRiders, setInterestedRiders] = useState([]); + const [editMode, setEditMode] = useState(false); + const [currentTeam, setCurrentTeam] = useState(null); // For editing teams + const [currentRider, setCurrentRider] = useState(null); // For editing riders + const [isRiderEditMode, setIsRiderEditMode] = useState(false); // New state for rider edit mode + + // Real-time listener for teams collection + useEffect(() => { + const teamsRef = collection(db, 'teams'); + const unsubscribeTeams = onSnapshot(teamsRef, (snapshot) => { + const teamsList = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) as Team[]; + setTeams(teamsList); + }); + + return () => unsubscribeTeams(); + }, []); + + // Real-time listener for riders collection + useEffect(() => { + const ridersRef = collection(db, 'riders'); + const unsubscribeRiders = onSnapshot(ridersRef, (snapshot) => { + const ridersList = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })) as Rider[]; + setInterestedRiders(ridersList); + }); + + return () => unsubscribeRiders(); + }, []); + + const handleDeleteTeam = async (teamId: string) => { + try { + const teamRef = doc(db, 'teams', teamId); + await deleteDoc(teamRef); + } catch (error) { + console.error('Error deleting team:', error); + } + }; + + const openEditModal = (team: Team) => { + setCurrentTeam(team); + setEditMode(true); + }; + + const closeEditModal = () => { + setEditMode(false); + setCurrentTeam(null); + }; + + const handleDeleteRider = async (riderId: string) => { + try { + const riderRef = doc(db, 'riders', riderId); + await deleteDoc(riderRef); + } catch (error) { + console.error('Error deleting rider:', error); + } + }; + + const openRiderEditModal = (rider: Rider) => { + setCurrentRider(rider); + setIsRiderEditMode(true); // Set edit mode for riders + }; + + const closeRiderEditModal = () => { + setCurrentRider(null); + setIsRiderEditMode(false); // Reset edit mode for riders + }; + + // Group and sort teams by division and ride time + const divisionGroups = teams.reduce((acc: { [key: string]: Team[] }, team) => { + const divisionKey = team.division.charAt(0).toUpperCase(); + if (!acc[divisionKey]) acc[divisionKey] = []; + acc[divisionKey].push(team); + return acc; + }, {}); + + // Sort the teams within each division by rideTime + Object.keys(divisionGroups).forEach((division) => { + divisionGroups[division].sort((a, b) => { + const timeA = a.rideTime.split(':').map(Number); + const timeB = b.rideTime.split(':').map(Number); + const totalMinutesA = timeA[0] * 60 + timeA[1]; + const totalMinutesB = timeB[0] * 60 + timeB[1]; + return totalMinutesA - totalMinutesB; + }); + }); + + return ( + +
+ + DZR Zwift Racing League Teams + +
+ + + + Dette er første udgave af en side, hvor man kan få overblik over hvilke hold vi har i ZRL. Derudover kan hold aktivt vise, at de leder efter + ryttere og ryttere kan vise at de leder efter et hold. Feedback og forslag er meget velkomne på Discord + + + + • Holdkaptajner kan registere hold. Detaljer kan ændres af kaptajnen, når holdet er oprettet (Edit) og slettes (Delete). Kryds af i "Looking for riders" + for at vise at I aktivt leder efter flere ryttere. + + + + + • Ryttere kan flage interesse i at finde et team. Detaljer kan ændres af rytteren, når interessen er oprettet (Edit) og slettes (Delete). + + + + + + Current Teams + + + {['A', 'B', 'C', 'D'].map((division) => ( + + {`Division ${division}`} + {divisionGroups[division]?.map((team) => ( + + + Name: {team.name}
Captain: {team.captainName}
Race Time: {team.rideTime}
Division: {team.division} +
+ {team.lookingForRiders && ( + Looking for riders + )} + {team.captainId === auth.currentUser?.uid && ( + + + + + )} +
+ ))} +
+ ))} +
+ + {/* Interested Riders Section */} + + + Riders Looking For Team + + + {interestedRiders.length > 0 ? ( + interestedRiders.map((rider) => ( + + + + Name: {rider.name}
Preferred Division: {rider.division}
Preferred Race Time: {rider.rideTime} +
+ {rider.userId === auth.currentUser?.uid && ( // Check if the current user is the rider + + + + + )} +
+ + )) + ) : ( + No interested riders yet. + )} +
+
+ + {/* Edit Team Component (conditionally rendered if a team is selected for editing) */} + {editMode && currentTeam && ( + + )} + + {/* Edit Rider Component (conditionally rendered if a rider is selected for editing) */} + {isRiderEditMode && currentRider && ( + + )} +
+ ); +}; + +export default ZRL; diff --git a/components/ZRL/ZRLEditDelete.tsx b/components/ZRL/ZRLEditDelete.tsx new file mode 100644 index 0000000..80ff70b --- /dev/null +++ b/components/ZRL/ZRLEditDelete.tsx @@ -0,0 +1,127 @@ +// ZRLEditDelete.tsx + +import { useState, useEffect } from 'react'; +import { Box, Button, FormControl, FormLabel, Input, Checkbox, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, ModalFooter } from '@chakra-ui/react'; +import { db } from '@/app/utils/firebaseConfig'; +import { doc, updateDoc } from 'firebase/firestore'; + +interface Team { + id?: string; + name: string; + captainId: string; + captainName: string; + createdAt: string; + rideTime: string; + division: string; + lookingForRiders?: boolean; // New field +} + +interface ZRLEditDeleteProps { + team: Team | null; // Allow team to be null + onClose: () => void; +} + +const ZRLEditDelete: React.FC = ({ team, onClose }) => { + const [newTeamName, setNewTeamName] = useState(''); + const [rideTime, setRideTime] = useState(''); + const [division, setDivision] = useState(''); + const [captainName, setCaptainName] = useState(''); + const [lookingForRiders, setLookingForRiders] = useState(false); // New state for checkbox + + // Update local state when team changes + useEffect(() => { + if (team) { + setNewTeamName(team.name); + setRideTime(team.rideTime); + setDivision(team.division); + setCaptainName(team.captainName); + setLookingForRiders(team.lookingForRiders || false); // Set initial checkbox value + } + }, [team]); + + const handleEditTeam = async () => { + if (!team?.id) return; + + try { + const teamRef = doc(db, 'teams', team.id); + await updateDoc(teamRef, { + name: newTeamName, + rideTime, + division, + captainName, + lookingForRiders, // Save the checkbox value + }); + onClose(); // Close the modal after update + } catch (error) { + console.error('Error updating team:', error); + } + }; + + return ( + + + + Edit Team + + + + Team Name + setNewTeamName(e.target.value)} + placeholder="Enter team name" + bg="white" + /> + + + Captain Name + setCaptainName(e.target.value)} + placeholder="Enter captain's name" + bg="white" + /> + + + Ride Time + setRideTime(e.target.value)} + placeholder="Enter ride time" + bg="white" + /> + + + Division + setDivision(e.target.value)} + placeholder="Enter division (A, B, C, or D)" + bg="white" + /> + + + setLookingForRiders(e.target.checked)} + > + Looking for riders + + + + + + + + + + ); +}; + +export default ZRLEditDelete; diff --git a/components/ZRL/ZRLRegister.tsx b/components/ZRL/ZRLRegister.tsx new file mode 100644 index 0000000..f03bef2 --- /dev/null +++ b/components/ZRL/ZRLRegister.tsx @@ -0,0 +1,134 @@ +import { useState } from 'react'; +import { + Box, + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + ModalFooter, +} from '@chakra-ui/react'; +import { auth, db } from '@/app/utils/firebaseConfig'; +import { collection, addDoc } from 'firebase/firestore'; + +interface Team { + name: string; + captainId: string; + captainName: string; + createdAt: string; + rideTime: string; + division: string; +} + +const ZRLRegister = () => { + const [newTeamName, setNewTeamName] = useState(''); + const [rideTime, setRideTime] = useState(''); + const [division, setDivision] = useState(''); + const [captainName, setCaptainName] = useState(''); + const [isOpen, setIsOpen] = useState(false); + + const handleTeamRegister = async () => { + if (!newTeamName || !auth.currentUser || !rideTime || !division) return; + + try { + const teamData: Team = { + name: newTeamName, + captainId: auth.currentUser.uid, + captainName: captainName || auth.currentUser.displayName || 'Unknown', + createdAt: new Date().toISOString(), + rideTime, + division, + }; + + await addDoc(collection(db, 'teams'), teamData); + resetForm(); + } catch (error) { + console.error('Error registering team:', error); + } + }; + + const resetForm = () => { + setNewTeamName(''); + setRideTime(''); + setDivision(''); + setCaptainName(''); + setIsOpen(false); + }; + + const openRegisterModal = () => { + setCaptainName(auth.currentUser?.displayName || ''); + setIsOpen(true); + }; + + return ( + + + + + + Register a New Team + + + + Team Name + setNewTeamName(e.target.value)} + placeholder="Enter team name" + bg="white" + /> + + + Captain Name + setCaptainName(e.target.value)} + placeholder="Enter captain's name" + bg="white" + /> + + + Race Time + setRideTime(e.target.value)} + placeholder="Enter race time" + bg="white" + /> + + + Division + setDivision(e.target.value)} + placeholder="Enter division (A, B, C, or D)" + bg="white" + /> + + + + + + + + + + ); +}; + +export default ZRLRegister; \ No newline at end of file diff --git a/components/ZRL/ZRLRider.tsx b/components/ZRL/ZRLRider.tsx new file mode 100644 index 0000000..5ca0896 --- /dev/null +++ b/components/ZRL/ZRLRider.tsx @@ -0,0 +1,118 @@ +import { useState } from 'react'; +import { + Box, + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + ModalFooter, +} from '@chakra-ui/react'; +import { auth, db } from '@/app/utils/firebaseConfig'; +import { collection, addDoc } from 'firebase/firestore'; + +interface Rider { + id?: string; // Optional ID for the rider + userId: string; // Field to store the user ID of the rider + name: string; // Rider's name + division: string; // Rider's division + rideTime: string; // Preferred race time +} + +const ZRLRider = () => { + const [name, setName] = useState(auth.currentUser?.displayName || ''); // Default to user's display name + const [division, setDivision] = useState(''); + const [rideTime, setRideTime] = useState(''); + const [isOpen, setIsOpen] = useState(false); + + const handleRegisterInterest = async () => { + if (!rideTime || !auth.currentUser || !division) return; + + try { + const riderData: Rider = { + userId: auth.currentUser.uid, + name, + division, + rideTime, + }; + + await addDoc(collection(db, 'riders'), riderData); + resetForm(); + } catch (error) { + console.error('Error registering interest:', error); + } + }; + + const resetForm = () => { + setName(auth.currentUser?.displayName || ''); // Reset name to display name + setDivision(''); + setRideTime(''); + setIsOpen(false); + }; + + const openRegisterModal = () => { + setIsOpen(true); + }; + + return ( + + + + + + Register Interest + + + + Name + setName(e.target.value)} + placeholder="Enter your name" + bg="white" + /> + + + Preferred Division + setDivision(e.target.value)} + placeholder="Enter division (eg. A1-A2, C3, D2, ...)" + bg="white" + /> + + + Preferred Race Time + setRideTime(e.target.value)} + placeholder="Enter preferred race time (eg. 19:15-20:00)" + bg="white" + /> + + + + + + + + + + ); +}; + +export default ZRLRider; diff --git a/components/ZRL/ZRLRiderEditDelete.tsx b/components/ZRL/ZRLRiderEditDelete.tsx new file mode 100644 index 0000000..962203e --- /dev/null +++ b/components/ZRL/ZRLRiderEditDelete.tsx @@ -0,0 +1,116 @@ +// ZRLRiderEditDelete.tsx + +import { useState, useEffect } from 'react'; +import { + Box, + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + ModalFooter, +} from '@chakra-ui/react'; +import { db } from '@/app/utils/firebaseConfig'; +import { doc, updateDoc, deleteDoc } from 'firebase/firestore'; + +interface Rider { + id?: string; // Optional ID for the rider + userId: string; // Field to store the user ID of the rider + name: string; // Rider's name + division: string; // Rider's division + rideTime: string; // Preferred race time +} + +interface ZRLRiderEditDeleteProps { + rider: Rider | null; // Allow rider to be null + onClose: () => void; +} + +const ZRLRiderEditDelete: React.FC = ({ rider, onClose }) => { + const [name, setName] = useState(''); + const [division, setDivision] = useState(''); + const [rideTime, setRideTime] = useState(''); + + // Update local state when rider changes + useEffect(() => { + if (rider) { + setName(rider.name); + setDivision(rider.division); + setRideTime(rider.rideTime); + } + }, [rider]); + + const handleEditRider = async () => { + if (!rider?.id) return; + + try { + const riderRef = doc(db, 'riders', rider.id); + await updateDoc(riderRef, { + name, + division, + rideTime, + }); + onClose(); // Close the modal after update + } catch (error) { + console.error('Error updating rider:', error); + } + }; + + + + return ( + + + + Edit Interested Rider + + + + Rider Name + setName(e.target.value)} + placeholder="Enter rider's name" + bg="white" + /> + + + Division + setDivision(e.target.value)} + placeholder="Enter division (A, B, C, or D)" + bg="white" + /> + + + Ride Time + setRideTime(e.target.value)} + placeholder="Enter preferred race time" + bg="white" + /> + + + + + + + + + + ); +}; + +export default ZRLRiderEditDelete; diff --git a/components/auth/AuthContext.js b/components/auth/AuthContext.js new file mode 100644 index 0000000..198cbe4 --- /dev/null +++ b/components/auth/AuthContext.js @@ -0,0 +1,69 @@ +// components/auth/AuthContext.js + +'use client'; + +import React, { createContext, useState, useEffect } from 'react'; +import { + onAuthStateChanged, + signInWithEmailAndPassword, + createUserWithEmailAndPassword, + setPersistence, + browserSessionPersistence, + updateProfile, +} from 'firebase/auth'; +import { auth } from '@/app/utils/firebaseConfig'; // Adjust the path if needed + + +export const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [currentUser, setCurrentUser] = useState(null); + const [loading, setLoading] = useState(true); // Add loading state + + useEffect(() => { + // Set persistence to local so the user stays logged in across sessions + setPersistence(auth, browserSessionPersistence) + .then(() => { + // Listen for auth state changes + return onAuthStateChanged(auth, (user) => { + setCurrentUser(user); + setLoading(false); // Set loading to false once we know the auth state + }); + }) + .catch((error) => { + console.error("Error setting persistence:", error); + setLoading(false); // Ensure loading is false on error + }); + }, []); + + const login = async (email, password) => { + try { + await signInWithEmailAndPassword(auth, email, password); + } catch (error) { + console.error("Login failed:", error.message); + throw error; + } + }; + + const signup = async (email, password, displayName) => { + try { + // Create user with email and password + const userCredential = await createUserWithEmailAndPassword(auth, email, password); + const user = userCredential.user; // Get the user object + + // Use updateProfile function from Firebase + await updateProfile(user, { displayName }); // Correctly update the profile + + return user; // Return user object after successful signup + } catch (error) { + console.error("Signup failed:", error.message); + throw error; + } + }; + + return ( + + {children} + + ); +}; diff --git a/components/auth/EditProfileModal.js b/components/auth/EditProfileModal.js new file mode 100644 index 0000000..fe927c4 --- /dev/null +++ b/components/auth/EditProfileModal.js @@ -0,0 +1,108 @@ +import React, { useState, useContext } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + Button, + Input, + useDisclosure, + Text, + useToast, +} from '@chakra-ui/react'; +import { AuthContext } from '@/components/auth/AuthContext'; +import { updateProfile, deleteUser } from 'firebase/auth'; // Import the deleteUser function +import { auth } from '@/app/utils/firebaseConfig'; + +const EditProfileModal = ({ isOpen, onClose }) => { + const { currentUser } = useContext(AuthContext); + const [displayName, setDisplayName] = useState(currentUser?.displayName || ''); + const toast = useToast(); + + const handleUpdateProfile = async () => { + if (!currentUser) return; + + try { + await updateProfile(currentUser, { displayName }); + toast({ + title: "Profile updated.", + description: "Your profile has been updated successfully.", + status: "success", + duration: 3000, + isClosable: true, + }); + onClose(); // Close the modal after successful update + } catch (error) { + console.error("Error updating profile:", error); + toast({ + title: "Error updating profile.", + description: "There was an error updating your profile.", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + const handleDeleteProfile = async () => { + if (!currentUser) return; + + // Confirm deletion with the user + const confirmDelete = window.confirm("Are you sure you want to delete your profile? This action cannot be undone."); + if (!confirmDelete) return; + + try { + await deleteUser(currentUser); // Delete the user + toast({ + title: "Profile deleted.", + description: "Your profile has been deleted.", + status: "success", + duration: 3000, + isClosable: true, + }); + onClose(); // Close the modal after deletion + } catch (error) { + console.error("Error deleting profile:", error); + toast({ + title: "Error deleting profile.", + description: "There was an error deleting your profile.", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + return ( + + + + Edit Profile + + Display Name: + setDisplayName(e.target.value)} + mb={3} + /> + + + + + + + + + ); +}; + +export default EditProfileModal; diff --git a/components/auth/ForgotPasswordModel.tsx b/components/auth/ForgotPasswordModel.tsx new file mode 100644 index 0000000..a473f54 --- /dev/null +++ b/components/auth/ForgotPasswordModel.tsx @@ -0,0 +1,54 @@ +// components/auth/ForgotPasswordModal.tsx + +import React from 'react'; +import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, ModalFooter, Button, Input, useDisclosure } from '@chakra-ui/react'; +import { getAuth, sendPasswordResetEmail } from "firebase/auth"; // Import Firebase functions + +const ForgotPasswordModal = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [email, setEmail] = React.useState(''); + + const handleResetPassword = async () => { + const auth = getAuth(); + try { + await sendPasswordResetEmail(auth, email); + alert('Password reset link sent! Check your email.'); // Notify the user + onClose(); // Close the modal + } catch (error) { + console.error("Error sending password reset email:", error); + alert('Error sending password reset email.'); // Notify the user of the error + } + }; + + return ( + <> + + + + + Reset Password + + + setEmail(e.target.value)} // Update email state + mb={4} // Add margin below the input + /> + + + + + + + + + ); +}; + +export default ForgotPasswordModal; // Ensure this is the default export diff --git a/components/auth/SignUpModal.tsx b/components/auth/SignUpModal.tsx new file mode 100644 index 0000000..7c0076e --- /dev/null +++ b/components/auth/SignUpModal.tsx @@ -0,0 +1,108 @@ +import React, { useState, useContext } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + Button, + Input, + useDisclosure, + Text, + useToast, +} from '@chakra-ui/react'; +import { AuthContext } from '@/components/auth/AuthContext'; + +export default function SignUpModal() { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [dzrSecret, setDzrSecret] = useState(''); // State for DZR secret + const [displayName, setDisplayName] = useState(''); // State for display name + const [error, setError] = useState(''); + const { signup } = useContext(AuthContext); + const toast = useToast(); + + // Get the predefined secret from the environment variable + const predefinedSecret = process.env.NEXT_PUBLIC_MEMBERS_ZONE; // Use NEXT_PUBLIC_ for client-side access + + const handleSignUp = async () => { + setError(''); + + if (dzrSecret !== predefinedSecret) { + setError('Invalid DZR secret. Please contact the admin for access.'); + return; + } + + // Check if display name is provided + if (!displayName) { + setError('Display name is required.'); + return; + } + + try { + await signup(email, password, displayName); // Include display name in signup + toast({ + title: "Account created.", + description: "Welcome to DZR!", + status: "success", + duration: 3000, + isClosable: true, + }); + onClose(); // Close the modal after successful sign-up + } catch (error) { + console.error("Error signing up:", error); + setError("Error creating account. Please try again."); + } + }; + + return ( + <> + + + + + + Sign Up + + setDisplayName(e.target.value)} + mb={3} + /> + setEmail(e.target.value)} + mb={3} + /> + setPassword(e.target.value)} + mb={3} + /> + setDzrSecret(e.target.value)} + mb={3} + /> + {error && {error}} + + + + + + + + + + ); +} diff --git a/components/shop/ActiveOrders.js b/components/shop/ActiveOrders.js new file mode 100644 index 0000000..67d86ca --- /dev/null +++ b/components/shop/ActiveOrders.js @@ -0,0 +1,112 @@ +// app/components/ActiveOrders.js + +import { useEffect, useState, useContext } from 'react'; +import { Center, Box, ListItem, UnorderedList, Text, Flex, Spinner, Stack, Button, useToast, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, ModalFooter } from '@chakra-ui/react'; +import { db } from '@/app/utils/firebaseConfig'; // Adjust the import path as necessary +import { collection, query, where, onSnapshot, doc, deleteDoc } from 'firebase/firestore'; +import { AuthContext } from '@/components/auth/AuthContext'; // Import your AuthContext + +const ActiveOrders = () => { + const { currentUser } = useContext(AuthContext); // Get the current user from context + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const { isOpen, onOpen, onClose } = useDisclosure(); + const toast = useToast(); + + useEffect(() => { + if (currentUser) { + const ordersRef = collection(db, 'orders'); + const q = query(ordersRef, where('userId', '==', currentUser.uid)); // Query for orders for the current user + + const unsubscribe = onSnapshot(q, (snapshot) => { + const orderData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); + setOrders(orderData); + setLoading(false); + }); + + return () => unsubscribe(); // Clean up the listener on unmount + } else { + setLoading(false); // If there's no user, stop loading + } + }, [currentUser]); + + const handleDeleteOrder = async (orderId) => { + try { + await deleteDoc(doc(db, 'orders', orderId)); // Delete the order from Firestore + const remainingOrders = orders.filter(order => order.id !== orderId); + setOrders(remainingOrders); // Update the orders list + + toast({ + title: "Order Deleted", + description: "Your order has been deleted successfully.", + status: "success", + duration: 3000, + isClosable: true, + }); + + // Close the modal if no orders remain + if (remainingOrders.length === 0) { + onClose(); + } + + } catch (error) { + console.error('Error deleting order:', error); + toast({ + title: "Delete Failed", + description: "There was a problem deleting your order. Please try again.", + status: "error", + duration: 3000, + isClosable: true, + }); + } + }; + + return ( + + {loading ? ( + + ) : orders.length > 0 ? ( + + + + + + + + Dine aktive ordrer + + + {orders.map(order => ( + + Navn: {order.customerName} + Email: {order.email} + Adresse: {order.customerAddress}, {order.postalCode} {order.city} + Telefon: {order.phone} + Bestilling: + + {order.items.map(item => ( + {item.name}: {item.price * item.quantity} kr (Størrelse: {item.size}, Fit: {item.fit}, Antal: {item.quantity}) + ))} + {!order.delivery && (Fragt: {order.transportation} kr)} + + Total: {order.totalAmount.toFixed(2)} kr +
+ +
+
+ ))} +
+
+
+ +
+ ) : ( + <> + )} +
+ ); +}; + +export default ActiveOrders; diff --git a/components/shop/Cart.tsx b/components/shop/Cart.tsx new file mode 100644 index 0000000..c59b803 --- /dev/null +++ b/components/shop/Cart.tsx @@ -0,0 +1,87 @@ +// components/shop/Cart.tsx + +import React from 'react'; +import { + Box, + Button, + Heading, + Stack, + Text, + Image, + Flex, + Center, +} from '@chakra-ui/react'; + +// Define the type for a single cart item +interface CartItem { + id: number; + name: string; + price: number; + quantity: number; + size: string; + fit: string; + imageUrl: string; +} + +// Define the props for the Cart component +interface CartProps { + cartItems: CartItem[]; // Define the type of cartItems as an array of CartItem + removeFromCart: (id: number) => void; // Function to remove items from the cart + clearCart: () => void; // Function to clear the cart +} + +const Cart: React.FC = ({ cartItems, removeFromCart, clearCart }) => { + // Calculate the total price of all items in the cart + const totalPrice = cartItems.reduce((total, item) => total + item.price * item.quantity, 0); + + return ( +
+ + Din kurv + {cartItems.length === 0 ? ( + Din kurv er tom. + ) : ( + + {cartItems.map((item) => ( + + + + {item.name} + Størrelse: {item.size} + Fit: {item.fit} + Antal: {item.quantity} + Pris: {item.price} kr + + {item.name} + + + + + + ))} + + + + )} + +
+ ); +}; + +export default Cart; diff --git a/components/shop/CartCheckoutModal.tsx b/components/shop/CartCheckoutModal.tsx new file mode 100644 index 0000000..6680b6e --- /dev/null +++ b/components/shop/CartCheckoutModal.tsx @@ -0,0 +1,52 @@ +// components/shop/CartCheckoutModal.tsx +import React from 'react'; +import { Modal, ModalOverlay, ModalCloseButton, ModalContent, ModalHeader, ModalFooter, ModalBody, Button, useDisclosure, Box, Heading } from '@chakra-ui/react'; +import Cart from './Cart'; +import Checkout from './CheckOut'; +import Head from 'next/head'; + +interface Product { + id: number; + name: string; + description: React.ReactNode; + price: number; + imageUrl: string; + availableSizes: string[]; + availableFit: string[]; + weight: number; + } + + +interface CartItem extends Product { + size: string; + fit: string; + quantity: number; + } + +interface CartCheckoutModalProps { + isOpen: boolean; + onClose: () => void; + cartItems: CartItem[]; + removeFromCart: (id: number) => void; + clearCart: () => void; +} + +const CartCheckoutModal: React.FC = ({ isOpen, onClose, cartItems, removeFromCart, clearCart }) => { + return ( + + + + + Check ud + + + + + + + + + ); +}; + +export default CartCheckoutModal; diff --git a/components/shop/CheckOut.tsx b/components/shop/CheckOut.tsx new file mode 100644 index 0000000..5b512bc --- /dev/null +++ b/components/shop/CheckOut.tsx @@ -0,0 +1,190 @@ +import { Heading, UnorderedList, ListItem, Flex, Stack, Box, Button, Text, Input, Checkbox, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, useDisclosure, useToast, Center, Divider } from '@chakra-ui/react'; +import { useState, useContext } from 'react'; +import { db } from '@/app/utils/firebaseConfig'; // Adjust the import path as necessary +import { collection, addDoc } from 'firebase/firestore'; // Import Firestore functions +import { AuthContext } from '@/components/auth/AuthContext'; // Import your AuthContext + +interface Product { + id: number; + name: string; + description: React.ReactNode; + price: number; + imageUrl: string; + availableSizes: string[]; + availableFit: string[]; + weight: number; +} + +interface CartItem extends Product { + size: string; + fit: string; + quantity: number; +} + +interface CheckoutProps { + cartItems: CartItem[]; + clearCart: () => void; + onClose: () => void; +} + +const Checkout = ({ cartItems, clearCart, onClose }: CheckoutProps) => { + + const toast = useToast(); + const [name, setName] = useState(''); + const [address, setAddress] = useState(''); + const [postalCode, setPostalCode] = useState(''); // State for postal code + const [city, setCity] = useState(''); // State for city + const [phone, setPhone] = useState(''); // State for city + const [isPickup, setIsPickup] = useState(true); // State for pickup option + const [isLoading, setIsLoading] = useState(false); // Loading state + + const totalWeight = cartItems.reduce((total, item) => total + item.weight * item.quantity, 0); + const transportationCost = totalWeight <= 0 + ? 0 + : totalWeight < 1 + ? 45 + : totalWeight <= 3 + ? 65 + : 85; + + const totalAmount = cartItems.reduce((total, item) => total + item.price * item.quantity, 0) + (isPickup ? 0 : transportationCost); // Add transportation cost if not picking up + + // Get the current user from context + const { currentUser } = useContext(AuthContext); // Assuming you have this context + + + const handlePlaceOrder = async () => { + if (!name || (!isPickup && (!address || !postalCode || !city))) { + toast({ + title: "Missing Information", + description: "Please fill in your name and address, or select pickup.", + status: "warning", + duration: 3000, + isClosable: true, + }); + return; + } + + const orderData = { + items: cartItems.map(({ description, ...item }) => item), // Exclude description + totalAmount, + customerName: name, + delivery: isPickup, + transportation: isPickup ? "" : transportationCost, + customerAddress: isPickup ? "Afhentning på Frederiksberg" : address, + postalCode: isPickup ? "" : postalCode, // Only include postal code if not picking up + city: isPickup ? "" : city, // Only include city if not picking up + phone: isPickup ? "" : phone, + email: currentUser.email, + userId: currentUser.uid, // Attach the user ID to the order + createdAt: new Date(), // Timestamp for the order + }; + + setIsLoading(true); // Set loading state + try { + // Save the order to Firestore + const docRef = await addDoc(collection(db, 'orders'), orderData); + + toast({ + title: "Order Placed", + description: "Your order has been placed successfully.", + status: "success", + duration: 3000, + isClosable: true, + }); + clearCart(); // Clear cart after successful order + setIsPickup(true); + onClose(); // Close the modal + } catch (error) { + console.error('Error placing order:', error); + toast({ + title: "Order Failed", + description: "There was a problem placing your order. Please try again.", + status: "error", + duration: 3000, + isClosable: true, + }); + } finally { + setIsLoading(false); // Reset loading state + } + }; + + return ( + + Indtast forsendelsesoplysninger + {/* Modal for entering name and address */} + + setName(e.target.value)} + mb={4} + /> + setIsPickup(!isPickup)} + mb={4} + > + Afhent på Frederiksberg + + {/* Conditionally render the address, postal code, and city inputs */} + {!isPickup && ( + <> + *Der vil blive tilføjet transportomkostninger på {transportationCost} kr. + setAddress(e.target.value)} + mb={4} + /> + + setPostalCode(e.target.value)} + mb={4} + mr={4} + width={'100px'} + /> + setCity(e.target.value)} + mb={4} + flex='1' + /> + + setPhone(e.target.value)} + mb={4} + /> + + )} + Bestilling: + + {cartItems.map((item) => ( + {item.name}: {item.price*item.quantity} kr (Størrelse: {item.size}, Fit: {item.fit}, Antal: {item.quantity}) + ))} + {!isPickup && (Fragt: {transportationCost} kr)} + + + Total: {totalAmount.toFixed(2)} kr + + + + + ); +}; + +export default Checkout; diff --git a/components/shop/ProductList.tsx b/components/shop/ProductList.tsx new file mode 100644 index 0000000..1dadffc --- /dev/null +++ b/components/shop/ProductList.tsx @@ -0,0 +1,274 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Button, + Flex, + Heading, + Text, + Input, + Stack, + Image, + SimpleGrid, + Center, + Select, + Modal, // Import Modal components + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + Link, + useDisclosure, // For opening and closing the modal +} from '@chakra-ui/react'; + +interface Product { + id: number; + name: string; + description: React.ReactNode; // Change type to allow JSX in the description + price: number; + imageUrl: string; + availableSizes: string[]; + availableFit: string[]; + weight: number; +} + +interface ProductListProps { + addToCart: (product: Product, quantity: number, selectedSize: string, selectedFit: string) => void; +} + +const products: Product[] = [ + { + id: 1, + name: 'Bibs', + description: ( + <> + Nopinz Subzero DZR Bibs. Details on{' '} + + nopinz.com + + + ), + price: 875, + imageUrl: '/shop/bibs.jpg', + availableSizes: ['2XS','XS','S', 'M', 'L', 'XL','2XL'], + availableFit: ['Standard','Tall'], + weight: 0.25, + }, + { + id: 2, + name: 'Suit', + description: ( + <> + Nopinz Subzero DZR suit. Details on{' '} + + nopinz.com + + + ), + price: 1275, + imageUrl: '/shop/suit.jpg', + availableSizes: ['2XS','XS','S', 'M', 'L', 'XL','2XL'], + availableFit: ['Standard','Tall'], + weight: 0.25, + }, + { + id: 3, + name: 'Socks', + description: ( + <> + Nopinz DZR socks. Details on{' '} + + nopinz.com + + + ), + price: 175, + imageUrl: '/shop/socks.jpeg', + availableSizes: ['XS','S', 'M', 'L', 'XL'], + availableFit: ['One fit'], + weight: 0.05, + }, + { + id: 4, + name: 'Cooling Packs', + description: ( + <> + Subzero gel packs and cool-bag for bibs and suit. Details on{' '} + + nopinz.com + + + ), + price: 300, + imageUrl: '/shop/coolingpacks.jpeg', + availableSizes: ['One size'], + availableFit: ['One fit'], + weight: 1.6, + }, +]; + +const ProductList: React.FC = ({ addToCart }) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [selectedImage, setSelectedImage] = useState(null); + const [quantities, setQuantities] = useState<{ [key: number]: number }>({}); + const [selectedSizes, setSelectedSizes] = useState<{ [key: number]: string }>({}); + const [selectedFit, setSelectedFit] = useState<{ [key: number]: string }>({}); + + // Handle size preselection if only one size is available + useEffect(() => { + products.forEach((product) => { + if (product.availableSizes.length === 1) { + setSelectedSizes((prev) => ({ ...prev, [product.id]: product.availableSizes[0] })); + } + if (product.availableFit.length === 1) { + setSelectedFit((prev) => ({ ...prev, [product.id]: product.availableFit[0] })); + } + }); + }, []); + + const handleQuantityChange = (productId: number, value: string) => { + const quantity = parseInt(value) || 1; + setQuantities((prev) => ({ ...prev, [productId]: quantity })); + }; + + const handleSizeChange = (productId: number, value: string) => { + setSelectedSizes((prev) => ({ ...prev, [productId]: value })); + }; + + const handleFitChange = (productId: number, value: string) => { + setSelectedFit((prev) => ({ ...prev, [productId]: value })); + }; + + const handleImageClick = (imageUrl: string) => { + setSelectedImage(imageUrl); + onOpen(); // Open the modal when an image is clicked + }; + + return ( +
+ + + Bestil dit nye IRL DZR kit her! Når vi har omkring 20 bestillinger (suit og bibs), kontakter vi dig for betaling + og sætter derefter tøjet i produktion. Det er mulig at ændre bestillingen her på websiden, indtil tøjet er betalt og sat i produktion. + Du kan vælge at afhente det på Frederiksberg eller få det leveret direkte til din adresse med DAO.

+ + Tøjet er en replika af vores in-game DZR-kit og bliver produceret af Nopinz, der har udviklet bibs og suit (et suit er grundlæggende bibs med "indbygget" svedtrøje) + specifikt til indendørscykling. Materialet er ultralet og åndbart for at maksimere køleeffekten, og puden er optimeret til indendørs cykling + med bedre komfort og fugtstyring. Der er lommer til køleelementer (købes separat) på øvre og nedre ryg, der reducerer kropstemperaturen, + så du kan præstere bedre i længere tid!

Du kan læse reviews her: + ZwiftInsider og + Road.cc +
+ + {products.map((product) => ( + + {product.name} handleImageClick(product.imageUrl)} // Open modal on click + /> + + + {product.name} + {product.description} + + + {product.price} kr + + + + + + + + handleQuantityChange(product.id, e.target.value)} + /> + + + + + ))} + + + + {/* Modal for enlarged image */} + {selectedImage && ( + + + + + + Enlarged product + + + + + + )} +
+
+ ); +}; + +export default ProductList; diff --git a/components/shop/countItems.js b/components/shop/countItems.js new file mode 100644 index 0000000..c33e8cf --- /dev/null +++ b/components/shop/countItems.js @@ -0,0 +1,32 @@ +// app/utils/countItems.js +import { db } from '@/app/utils/firebaseConfig'; // Adjust the import path as necessary +import { collection, getDocs } from 'firebase/firestore'; + +export const countItems = async () => { + let totalQuantity = 0; + + try { + // Query all orders (no userId filter) + const ordersRef = collection(db, 'orders'); + + // Fetch all orders from Firestore + const querySnapshot = await getDocs(ordersRef); + + const orders = querySnapshot.docs.map(doc => doc.data()); + + // Count item quantities for item ids 1 and 2 across all orders + orders.forEach(order => { + order.items.forEach(item => { + if (item.id === 1 || item.id === 2) { + totalQuantity += item.quantity; + } + }); + }); + + } catch (error) { + console.error('Error fetching orders:', error); + throw new Error('Failed to fetch orders.'); + } + + return totalQuantity; +}; diff --git a/next.config.mjs b/next.config.mjs index 8c1b65a..4678774 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - -}; +const nextConfig = {}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index d850d6b..960153f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "csv-parser": "^3.0.0", "dotenv": "^16.4.5", "feed": "^4.2.2", + "firebase": "^10.14.1", "flask": "^0.2.10", "framer-motion": "^11.0.8", "googleapis": "^142.0.0", @@ -31,7 +32,8 @@ "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", - "react-slick": "^0.30.2" + "react-slick": "^0.30.2", + "undici": "^6.20.1" }, "devDependencies": { "@types/papaparse": "^5.3.14", @@ -2469,6 +2471,594 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", + "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", + "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==" + }, + "node_modules/@firebase/app": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", + "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", + "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", + "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", + "dependencies": { + "@firebase/app-check": "0.8.8", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.43", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", + "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", + "dependencies": { + "@firebase/app": "0.10.13", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==" + }, + "node_modules/@firebase/auth": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", + "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", + "dependencies": { + "@firebase/auth": "1.7.9", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/auth/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "dependencies": { + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.0.tgz", + "integrity": "sha512-vSe5s8dY13ilhLnfY0eYRmQsdTbH7PUFZtBbqU6JVX/j8Qp9A6G5gG6//ulbX9/1JFOF1IWNOne9c8S/DOCJaQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/database": "1.0.8", + "@firebase/database-types": "1.0.5", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.3.tgz", + "integrity": "sha512-NwVU+JPZ/3bhvNSJMCSzfcBZZg8SUGyzZ2T0EW3/bkUeefCyzMISSt/TTIfEHc8cdyXGlMqfGe3/62u9s74UEg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "@firebase/webchannel-wrapper": "1.0.1", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", + "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/firestore/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@firebase/functions": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.8.tgz", + "integrity": "sha512-Lo2rTPDn96naFIlSZKVd1yvRRqqqwiJk7cf9TZhUerwnPKgBzXy+aHE22ry+6EjCaQusUoNai6mU6p+G8QZT1g==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.14.tgz", + "integrity": "sha512-dZ0PKOKQFnOlMfcim39XzaXonSuPPAVuzpqA4ONTIdyaJK/OnBaIEVs/+BH4faa1a2tLeR+Jy15PKqDRQoNIJw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/functions": "0.11.8", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==" + }, + "node_modules/@firebase/functions/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@firebase/installations": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.9.tgz", + "integrity": "sha512-hlT7AwCiKghOX3XizLxXOsTFiFCQnp/oj86zp1UxwDGmyzsyoxtX+UIZyVyH/oBF5+XtblFG9KZzZQ/h+dpy+Q==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.9.tgz", + "integrity": "sha512-2lfdc6kPXR7WaL4FCQSQUhXcPbI7ol3wF+vkgtU25r77OxPf8F/VmswQ7sgIkBBWtymn5ZF20TIKtnOj9rjb6w==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.12.tgz", + "integrity": "sha512-6q0pbzYBJhZEtUoQx7hnPhZvAbuMNuBXKQXOx2YlWhSrlv9N1m0ZzlNpBbu/ItTzrwNKTibdYzUyaaxdWLg+4w==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.12.tgz", + "integrity": "sha512-pKsiUVZrbmRgdImYqhBNZlkKJbqjlPkVdQRZGRbkTyX4OSGKR0F/oJeCt1a8jEg5UnBp4fdVwSWSp4DuCovvEQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/messaging": "0.12.12", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.9.tgz", + "integrity": "sha512-PnVaak5sqfz5ivhua+HserxTJHtCar/7zM0flCX6NkzBNzJzyzlH4Hs94h2Il0LQB99roBqoE5QT1JqWqcLJHQ==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.9.tgz", + "integrity": "sha512-dNl95IUnpsu3fAfYBZDCVhXNkASE0uo4HYaEPd2/PKscfTvsgqFAOxfAXzBEDOnynDWiaGUnb5M1O00JQ+3FXA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.9", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.9.tgz", + "integrity": "sha512-EO1NLCWSPMHdDSRGwZ73kxEEcTopAxX1naqLJFNApp4hO8WfKfmEpmjxmP5TrrnypjIf2tUkYaKsfbEA7+AMmA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.9.tgz", + "integrity": "sha512-AxzGpWfWFYejH2twxfdOJt5Cfh/ATHONegTd/a0p5flEzsD5JsxXgfkFToop+mypEL3gNwawxrxlZddmDoNxyA==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==" + }, + "node_modules/@firebase/storage": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.2.tgz", + "integrity": "sha512-fxuJnHshbhVwuJ4FuISLu+/76Aby2sh+44ztjF2ppoe0TELIDxPW6/r1KGlWYt//AD0IodDYYA8ZTN89q8YqUw==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.12.tgz", + "integrity": "sha512-hA4VWKyGU5bWOll+uwzzhEMMYGu9PlKQc1w4DWxB3aIErWYzonrZjF0icqNQZbwKNIdh8SHjZlFeB2w6OSsjfg==", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/storage/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.4.tgz", + "integrity": "sha512-EBSqyu9eg8frQlVU9/HjKtHN7odqbh9MtAcVz3WwHj4gLCLOoN9F/o+oxlq3CxvFrd3CNTZwu6d2mZtVlEInng==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", + "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2555,9 +3145,9 @@ } }, "node_modules/@next/env": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz", - "integrity": "sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==" + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.15.tgz", + "integrity": "sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.1.3", @@ -2569,9 +3159,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.4.tgz", - "integrity": "sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz", + "integrity": "sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==", "cpu": [ "arm64" ], @@ -2584,9 +3174,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.4.tgz", - "integrity": "sha512-b0Xo1ELj3u7IkZWAKcJPJEhBop117U78l70nfoQGo4xUSvv0PJSTaV4U9xQBLvZlnjsYkc8RwQN1HoH/oQmLlQ==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz", + "integrity": "sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==", "cpu": [ "x64" ], @@ -2599,9 +3189,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.4.tgz", - "integrity": "sha512-457G0hcLrdYA/u1O2XkRMsDKId5VKe3uKPvrKVOyuARa6nXrdhJOOYU9hkKKyQTMru1B8qEP78IAhf/1XnVqKA==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz", + "integrity": "sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==", "cpu": [ "arm64" ], @@ -2614,9 +3204,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.4.tgz", - "integrity": "sha512-l/kMG+z6MB+fKA9KdtyprkTQ1ihlJcBh66cf0HvqGP+rXBbOXX0dpJatjZbHeunvEHoBBS69GYQG5ry78JMy3g==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz", + "integrity": "sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==", "cpu": [ "arm64" ], @@ -2629,9 +3219,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.4.tgz", - "integrity": "sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz", + "integrity": "sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==", "cpu": [ "x64" ], @@ -2644,9 +3234,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.4.tgz", - "integrity": "sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz", + "integrity": "sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==", "cpu": [ "x64" ], @@ -2659,9 +3249,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.4.tgz", - "integrity": "sha512-xzxF4ErcumXjO2Pvg/wVGrtr9QQJLk3IyQX1ddAC/fi6/5jZCZ9xpuL9Tzc4KPWMFq8GGWFVDMshZOdHGdkvag==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz", + "integrity": "sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==", "cpu": [ "arm64" ], @@ -2674,9 +3264,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.4.tgz", - "integrity": "sha512-WZiz8OdbkpRw6/IU/lredZWKKZopUMhcI2F+XiMAcPja0uZYdMTZQRoQ0WZcvinn9xZAidimE7tN9W5v9Yyfyw==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz", + "integrity": "sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==", "cpu": [ "ia32" ], @@ -2689,9 +3279,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.4.tgz", - "integrity": "sha512-4Rto21sPfw555sZ/XNLqfxDUNeLhNYGO2dlPqsnuCg8N8a2a9u1ltqBOPQ4vj1Gf7eJC0W2hHG2eYUHuiXgY2w==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz", + "integrity": "sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==", "cpu": [ "x64" ], @@ -2767,6 +3357,60 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@rushstack/eslint-patch": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", @@ -3413,11 +4057,17 @@ "node": ">=16.0.0" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -3793,7 +4443,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3802,7 +4451,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4259,11 +4907,57 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4274,8 +4968,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color2k": { "version": "2.0.3", @@ -4683,6 +5376,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5187,6 +5888,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/feed": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", @@ -5243,6 +5955,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "10.14.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", + "integrity": "sha512-0KZxU+Ela9rUCULqFsUUOYYkjh7OM1EWdIfG6///MtXd0t2/uUIf0iNV5i0KariMhRQ5jve/OY985nrAXFaZeQ==", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-compat": "0.2.14", + "@firebase/app": "0.10.13", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-compat": "0.3.15", + "@firebase/app-compat": "0.2.43", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.9", + "@firebase/auth-compat": "0.5.14", + "@firebase/data-connect": "0.1.0", + "@firebase/database": "1.0.8", + "@firebase/database-compat": "1.0.8", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-compat": "0.3.38", + "@firebase/functions": "0.11.8", + "@firebase/functions-compat": "0.3.14", + "@firebase/installations": "0.6.9", + "@firebase/installations-compat": "0.2.9", + "@firebase/messaging": "0.12.12", + "@firebase/messaging-compat": "0.2.12", + "@firebase/performance": "0.6.9", + "@firebase/performance-compat": "0.2.9", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-compat": "0.2.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-compat": "0.3.12", + "@firebase/util": "1.10.0", + "@firebase/vertexai-preview": "0.0.4" + } + }, "node_modules/flask": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/flask/-/flask-0.2.10.tgz", @@ -5455,6 +6202,14 @@ "node": ">=14" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -5786,6 +6541,11 @@ "react-is": "^16.7.0" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/https-proxy-agent": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", @@ -5798,6 +6558,11 @@ "node": ">= 14" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5996,7 +6761,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -6429,6 +7193,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -6445,6 +7214,11 @@ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6564,12 +7338,12 @@ "dev": true }, "node_modules/next": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.4.tgz", - "integrity": "sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==", + "version": "14.2.15", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.15.tgz", + "integrity": "sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==", "dependencies": { - "@next/env": "14.1.4", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.15", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", @@ -6583,18 +7357,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.1.4", - "@next/swc-darwin-x64": "14.1.4", - "@next/swc-linux-arm64-gnu": "14.1.4", - "@next/swc-linux-arm64-musl": "14.1.4", - "@next/swc-linux-x64-gnu": "14.1.4", - "@next/swc-linux-x64-musl": "14.1.4", - "@next/swc-win32-arm64-msvc": "14.1.4", - "@next/swc-win32-ia32-msvc": "14.1.4", - "@next/swc-win32-x64-msvc": "14.1.4" + "@next/swc-darwin-arm64": "14.2.15", + "@next/swc-darwin-x64": "14.2.15", + "@next/swc-linux-arm64-gnu": "14.2.15", + "@next/swc-linux-arm64-musl": "14.2.15", + "@next/swc-linux-x64-gnu": "14.2.15", + "@next/swc-linux-x64-musl": "14.2.15", + "@next/swc-win32-arm64-msvc": "14.2.15", + "@next/swc-win32-ia32-msvc": "14.2.15", + "@next/swc-win32-x64-msvc": "14.2.15" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -6603,6 +7378,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -7054,6 +7832,29 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7303,6 +8104,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -7761,7 +8570,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8062,6 +8870,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -8151,6 +8967,27 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -8382,6 +9219,14 @@ "xml-js": "bin/cli.js" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -8396,6 +9241,49 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 6f3c81a..057d3ca 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "csv-parser": "^3.0.0", "dotenv": "^16.4.5", "feed": "^4.2.2", + "firebase": "^10.14.1", "flask": "^0.2.10", "framer-motion": "^11.0.8", "googleapis": "^142.0.0", @@ -32,7 +33,8 @@ "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", - "react-slick": "^0.30.2" + "react-slick": "^0.30.2", + "undici": "^6.20.1" }, "devDependencies": { "@types/papaparse": "^5.3.14", diff --git a/public/shop/bibs.jpg b/public/shop/bibs.jpg new file mode 100644 index 0000000..75778c5 Binary files /dev/null and b/public/shop/bibs.jpg differ diff --git a/public/shop/bibs.png b/public/shop/bibs.png new file mode 100644 index 0000000..c833b09 Binary files /dev/null and b/public/shop/bibs.png differ diff --git a/public/shop/coolingPacks.png b/public/shop/coolingPacks.png new file mode 100644 index 0000000..c6f8e36 Binary files /dev/null and b/public/shop/coolingPacks.png differ diff --git a/public/shop/coolingpacks.jpeg b/public/shop/coolingpacks.jpeg new file mode 100644 index 0000000..8af360f Binary files /dev/null and b/public/shop/coolingpacks.jpeg differ diff --git a/public/shop/socks.jpeg b/public/shop/socks.jpeg new file mode 100644 index 0000000..1817015 Binary files /dev/null and b/public/shop/socks.jpeg differ diff --git a/public/shop/socks.png b/public/shop/socks.png new file mode 100644 index 0000000..badc112 Binary files /dev/null and b/public/shop/socks.png differ diff --git a/public/shop/suit.jpg b/public/shop/suit.jpg new file mode 100644 index 0000000..7202fcd Binary files /dev/null and b/public/shop/suit.jpg differ diff --git a/tsconfig.json b/tsconfig.json index e7ff90f..f439e6e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "lib": ["dom", "dom.iterable", "esnext"], + "target": "ESNext", "allowJs": true, "skipLibCheck": true, "strict": true, @@ -21,6 +22,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "components/shop/countItems.js"], "exclude": ["node_modules"] }