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 (
+
+
+
+
+ Members Login
+
+
+
+ setEmail(e.target.value)} />
+
+
+ setPassword(e.target.value)} />
+
+
+ {show ? 'Hide' : 'Show'}
+
+
+
+
+ Login
+
+
+ {/* 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}
+
+
+ Log Out
+
+ setEditProfileModalOpen(true)} ml={4}>
+ Edit Profile
+
+
+
+
+
+
+ {/* 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 ? (
+
+
+
+
+ Log Out
+
+
+
+ ) : (
+
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)}/>
-
-
- {show ? 'Hide' : 'Show'}
-
-
-
- Login
-
-
-
- );
-}
-
-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 && (
+
+ openEditModal(team)}>
+ Edit
+
+ handleDeleteTeam(team.id!)}>
+ Delete
+
+
+ )}
+
+ ))}
+
+ ))}
+
+
+ {/* 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
+
+ openRiderEditModal(rider)}>
+ Edit
+
+ handleDeleteRider(rider.id!)}>
+ Delete
+
+
+ )}
+
+
+ ))
+ ) : (
+ 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
+
+
+
+
+
+ Update Team
+
+ Close
+
+
+
+ );
+};
+
+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 (
+
+
+ Registrer Team
+
+
+
+
+ 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"
+ />
+
+
+
+
+ Register Team
+
+
+ Close
+
+
+
+
+
+ );
+};
+
+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 (
+
+
+ Efterspørg Team
+
+
+
+
+ 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"
+ />
+
+
+
+
+ Register Interest
+
+
+ Close
+
+
+
+
+
+ );
+};
+
+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"
+ />
+
+
+
+
+ Update Interest
+
+
+ Close
+
+
+
+ );
+};
+
+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}
+ />
+
+
+
+ Update
+
+
+ Delete Profile
+
+
+ Cancel
+
+
+
+
+ );
+};
+
+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 (
+ <>
+ Forgot Password?
+
+
+
+ Reset Password
+
+
+ setEmail(e.target.value)} // Update email state
+ mb={4} // Add margin below the input
+ />
+
+
+
+ Close
+
+
+ Send Reset Link
+
+
+
+
+ >
+ );
+};
+
+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
+
+
+
+
+ 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} }
+
+
+
+
+ Sign Up
+
+ Cancel
+
+
+
+ >
+ );
+}
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 ? (
+
+
+
+ Bemærk: Du har aktive bestillinger
+
+
+
+ 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
+
+ handleDeleteOrder(order.id)} mt={4}>
+ Slet bestilling
+
+
+
+ ))}
+
+
+
+
+
+ ) : (
+ <>>
+ )}
+
+ );
+};
+
+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
+
+
+ removeFromCart(item.id)}
+ alignContent={'center'}
+ >
+ Fjern
+
+
+
+
+
+ ))}
+
+
+ Ryd kurv
+
+
+ )}
+
+
+ );
+};
+
+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
+
+
+ Afgiv bestilling
+
+
+
+ );
+};
+
+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) => (
+
+ handleImageClick(product.imageUrl)} // Open modal on click
+ />
+
+
+ {product.name}
+ {product.description}
+
+
+ {product.price} kr
+
+
+
+ handleSizeChange(product.id, e.target.value)}
+ >
+ {product.availableSizes.map((size) => (
+
+ {size}
+
+ ))}
+
+ handleFitChange(product.id, e.target.value)}
+ >
+ {product.availableFit.map((fit) => (
+
+ {fit}
+
+ ))}
+
+
+
+ handleQuantityChange(product.id, e.target.value)}
+ />
+
+ addToCart(product, quantities[product.id] || 1, selectedSizes[product.id] || '', selectedFit[product.id] || '')
+ }
+ colorScheme="red"
+ isDisabled={!selectedSizes[product.id] || !selectedFit[product.id]}
+ >
+ Tilføj
+
+
+
+
+ ))}
+
+
+
+ {/* Modal for enlarged image */}
+ {selectedImage && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+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"]
}