diff --git a/src/app/auth/page.js b/src/app/auth/page.js index 5397547f..8179d575 100644 --- a/src/app/auth/page.js +++ b/src/app/auth/page.js @@ -6,11 +6,11 @@ import { authService } from '@/lib/service'; export default function AuthPage() { const router = useRouter(); - + useEffect(() => { async function fetchSession() { const { data: session } = await authService.getSessionUser(); - if (session.user) { + if (session.user) { router.push('/'); } } diff --git a/src/app/casos-activos/solicitudes/page.js b/src/app/casos-activos/solicitudes/page.js index 23379005..c1f78bdf 100644 --- a/src/app/casos-activos/solicitudes/page.js +++ b/src/app/casos-activos/solicitudes/page.js @@ -10,7 +10,9 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { tiposAyudaOptions } from '@/helpers/constants'; import Modal from '@/components/Modal'; import { useModal } from '@/context/EmergencyProvider'; -import { useTowns } from '../../../context/TownProvider'; +import { useTowns } from '@/context/TownProvider'; + +const MODAL_NAME = 'solicitudes'; export default function Solicitudes() { const towns = useTowns(); @@ -23,10 +25,10 @@ export default function Solicitudes() { const [data, setData] = useState([]); const [currentPage, setCurrentPage] = useState(Number(searchParams.get('page')) || 1); const [currentCount, setCurrentCount] = useState(0); - const { showModal, toggleModal } = useModal(); + const { toggleModal } = useModal(); const closeModal = () => { - toggleModal(false); + toggleModal(MODAL_NAME, false); }; const itemsPerPage = 10; const numPages = (count) => { @@ -172,12 +174,15 @@ export default function Solicitudes() { ) : ( @@ -187,11 +192,14 @@ export default function Solicitudes() {
- {showModal && ( - - - - )} + + + town.id === Number(filtroData.pueblo))?.name} + onClose={closeModal} + isModal={true} + /> + ); } diff --git a/src/app/voluntometro/page.js b/src/app/voluntometro/page.js index 1568a427..b9b228fd 100644 --- a/src/app/voluntometro/page.js +++ b/src/app/voluntometro/page.js @@ -1,7 +1,5 @@ import { supabase } from '@/lib/supabase/client'; import { HeartHandshake, Search, Thermometer } from 'lucide-react'; -import OfferHelp from '@/components/OfferHelp'; -import Modal from '@/components/Modal'; import TownCardInfo from '@/components/TownCardInfo'; const getCount = async () => { @@ -39,7 +37,6 @@ const getCount = async () => { ofertas: ofreceCount || 0, }; }; - const getVolunteers = async () => { const today = new Date().toISOString().split('T')[0]; @@ -87,7 +84,7 @@ const getVolunteers = async () => { export default async function Voluntometro() { const pueblos = await getVolunteers(); const count = await getCount(); - console.log(pueblos.ofertasButton); + const getFechaHoy = () => { const fecha = new Date(); return fecha.toLocaleDateString('es-ES', { @@ -96,6 +93,7 @@ export default async function Voluntometro() { year: 'numeric', }); }; + const getTopAndBottomPueblos = () => { const sortedPueblos = [...pueblos].sort((a, b) => { const volunteersDiffA = a.count - a.needHelp; diff --git a/src/components/AsignarSolicitudButton.tsx b/src/components/AsignarSolicitudButton.tsx index c12b4935..38da6778 100644 --- a/src/components/AsignarSolicitudButton.tsx +++ b/src/components/AsignarSolicitudButton.tsx @@ -8,14 +8,19 @@ import { Spinner } from '@/components/Spinner'; import Link from 'next/link'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; +import Modal from '@/components/Modal'; +import { useModal } from '@/context/EmergencyProvider'; type AsignarSolicitudButtonProps = { helpRequest: HelpRequestData; }; export default function AsignarSolicitudButton({ helpRequest }: AsignarSolicitudButtonProps) { + const { toggleModal } = useModal(); const session = useSession(); + const MODAL_NAME = `Solicitud-${helpRequest.id}`; + const { data: assignments, isLoading, @@ -60,10 +65,15 @@ export default function AsignarSolicitudButton({ helpRequest }: AsignarSolicitud }, }); - async function handleSubmit(e: MouseEvent) { + async function handleAcceptanceSubmit(e: MouseEvent) { e.preventDefault(); + toggleModal(MODAL_NAME, false); assignMutation.mutate(); } + async function handleSubmit(e: MouseEvent) { + e.preventDefault(); + toggleModal(MODAL_NAME, true); + } async function handleCancel(e: MouseEvent) { e.preventDefault(); unassignMutation.mutate(); @@ -105,6 +115,26 @@ export default function AsignarSolicitudButton({ helpRequest }: AsignarSolicitud Quiero ayudar )} + +
+

Quiero ayudar

+

¿Te comprometes a atender esta solicitud?

+
+ + +
+
+
); } diff --git a/src/components/CookieBanner/CookieBanner.tsx b/src/components/CookieBanner/CookieBanner.tsx new file mode 100644 index 00000000..d99de933 --- /dev/null +++ b/src/components/CookieBanner/CookieBanner.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { FC, useEffect, useState } from 'react'; +import Modal from '@/components/Modal'; +import { useModal } from '@/context/EmergencyProvider'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +const MODAL_NAME = 'cookie-banner'; +const POLICY_URL = '/politica-privacidad'; +const COOKIE_CONSENT_KEY = 'ajudaDanaCookieConsentAccepted'; + +const CookieBanner: FC = () => { + const { toggleModal } = useModal(); + const pathname = usePathname(); + const [hasConsent, setHasConsent] = useState(false); + + useEffect(() => { + // Check for consent in localStorage when the component mounts + const consent = localStorage.getItem(COOKIE_CONSENT_KEY); + setHasConsent(Boolean(consent)); + }, []); + + const handleAcceptCookies = () => { + // Set consent in localStorage and close the modal + localStorage.setItem(COOKIE_CONSENT_KEY, 'true'); + setHasConsent(true); + toggleModal(MODAL_NAME, false); + }; + + const handleRejectCookies = () => { + // Redirect to Google if cookies are rejected + window.location.href = 'https://www.google.com'; + }; + + useEffect(() => { + // Show the modal only if consent is not given and we're not on the policy page + if (!hasConsent && pathname !== POLICY_URL) { + toggleModal(MODAL_NAME, true); + } else { + toggleModal(MODAL_NAME, false); + } + }, [hasConsent, pathname]); + + if (hasConsent) return null; // Do not render the modal if consent is already given + + return ( + +
+

Política de Cookies

+

+ Usamos cookies para mejorar su experiencia. Al aceptar, usted está de acuerdo con nuestra{' '} + + política de cookies + + . +

+
+ + +
+
+
+ ); +}; + +export default CookieBanner; diff --git a/src/components/Modal.js b/src/components/Modal.tsx similarity index 53% rename from src/components/Modal.js rename to src/components/Modal.tsx index 10b6ae89..e7b8950e 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.tsx @@ -1,13 +1,39 @@ import { useModal } from '@/context/EmergencyProvider'; +import { MouseEvent, FC, ReactNode } from 'react'; -export const Modal = ({ children, maxWidth = 'max-w-2xl', allowClose = true }) => { - const { showModal, toggleModal } = useModal(); +type TailwindMaxWidth = + | 'max-w-xs' + | 'max-w-sm' + | 'max-w-md' + | 'max-w-lg' + | 'max-w-xl' + | 'max-w-2xl' + | 'max-w-3xl' + | 'max-w-4xl' + | 'max-w-5xl' + | 'max-w-6xl' + | 'max-w-7xl' + | 'max-w-full' + | 'max-w-screen-sm' + | 'max-w-screen-md' + | 'max-w-screen-lg' + | 'max-w-screen-xl' + | 'max-w-screen-2xl'; - if (!showModal) return null; +type ModalProps = { + id: string; + children: ReactNode; + maxWidth?: TailwindMaxWidth; + allowClose?: boolean; +}; + +const Modal: FC = ({ id, children, maxWidth = 'max-w-2xl', allowClose = true }) => { + const { isModalOpen, toggleModal } = useModal(); + if (!isModalOpen[id]) return null; - const handleBackdropClick = (e) => { + const handleBackdropClick = (e: MouseEvent) => { if (allowClose && e.target === e.currentTarget) { - toggleModal(); + toggleModal(id); } }; @@ -19,7 +45,7 @@ export const Modal = ({ children, maxWidth = 'max-w-2xl', allowClose = true }) =
{allowClose && (
+ {/* Política de privacidad */} + {!isPrivacyAccepted && ( +
+
+ setFormData({ ...formData, privacyPolicy: e.target.checked })} + className="min-w-4 min-h-4 cursor-pointer" + id="privacyPolicy" + required + /> + +
+
+ )}
{ const [phoneNumber, setPhoneNumber] = useState(''); @@ -79,7 +81,7 @@ const PhoneNumberDialog = () => { if (metadata.telefono) { return; } - toggleModal(); + toggleModal(MODAL_NAME); }; fetchNumber(); @@ -103,11 +105,11 @@ const PhoneNumberDialog = () => { throw new Error('Error a la hora de actualizar el usuario con un numero de telefono'); } - toggleModal(); + toggleModal(MODAL_NAME); }, []); return ( - + ); diff --git a/src/components/layout/EmergencyLayout.tsx b/src/components/layout/EmergencyLayout.tsx index 2d883b06..e835ee10 100644 --- a/src/components/layout/EmergencyLayout.tsx +++ b/src/components/layout/EmergencyLayout.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, PropsWithChildren } from 'react'; // @ts-ignore import Sidebar from './Sidebar'; import Footer from './Footer'; +import CookieBanner from '@/components/CookieBanner/CookieBanner'; export default function EmergencyLayout({ children }: PropsWithChildren) { const [isSidebarOpen, setIsSidebarOpen] = useState(true); // Por defecto abierto @@ -33,6 +34,7 @@ export default function EmergencyLayout({ children }: PropsWithChildren) {
{children}
+
); diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index c121fa78..956a2ba3 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -5,10 +5,11 @@ import ReactMap from 'react-map-gl/maplibre'; import 'maplibre-gl/dist/maplibre-gl.css'; import { Marker } from 'react-map-gl/maplibre'; import { useModal } from '@/context/EmergencyProvider'; -// @ts-ignore import Modal from '@/components/Modal'; import { MapPinFilled } from '@/components/icons/MapPinFilled'; +const MODAL_NAME = `map-marker`; + const urgencyToColor = { alta: 'text-red-500', media: 'text-amber-500', @@ -34,7 +35,7 @@ const DEFAULT_ZOOM = 12; const Map: FC = ({ markers = [] }) => { const [selectedMarker, setSelectedMarker] = useState(null); - const { showModal, toggleModal } = useModal(); + const { toggleModal } = useModal(); console.log(selectedMarker); return ( @@ -50,11 +51,11 @@ const Map: FC = ({ markers = [] }) => { {markers.map((m) => { return ( { - toggleModal(true); + toggleModal(MODAL_NAME, true); setSelectedMarker(m); }} anchor="bottom" @@ -63,7 +64,7 @@ const Map: FC = ({ markers = [] }) => { ); })} - {selectedMarker && showModal && {selectedMarker.popup}} + {selectedMarker && {selectedMarker.popup}} ); }; diff --git a/src/context/EmergencyProvider.tsx b/src/context/EmergencyProvider.tsx index fcd02baa..76a109fe 100644 --- a/src/context/EmergencyProvider.tsx +++ b/src/context/EmergencyProvider.tsx @@ -1,24 +1,32 @@ 'use client'; -import React, { createContext, ReactNode, useContext, useState } from 'react'; +import React, { createContext, FC, ReactNode, useContext, useState } from 'react'; -const EmergencyContext = createContext({ showModal: false, toggleModal: () => {} }); - -type EmergencyCtx = { - showModal: boolean; - toggleModal: (force: boolean) => void; +type EmergencyContextType = { + isModalOpen: { [key: string]: boolean }; + toggleModal: (id: string, force?: boolean) => void; }; -type SessionProviderProps = { +const EmergencyContext = createContext({ + isModalOpen: {}, + toggleModal: () => {}, +}); + +type EmergencyProviderProps = { children: ReactNode; }; -export const EmergencyProvider: React.FC = ({ children }) => { - const [showModal, setShowModal] = useState(false); +export const EmergencyProvider: FC = ({ children }) => { + const [isModalOpen, setIsModalOpen] = useState<{ [key: string]: boolean }>({}); - const toggleModal = (force: boolean) => setShowModal((prev) => (force !== undefined ? force : !prev)); + const toggleModal = (id: string, force?: boolean) => { + setIsModalOpen((prev) => ({ + ...prev, + [id]: force !== undefined ? force : !prev[id], + })); + }; - return {children}; + return {children}; }; export const useModal = () => {