From 4c9d343bd7131463b09f1ae3b958f83192f18f4d Mon Sep 17 00:00:00 2001 From: Martin Gerritsen Date: Wed, 6 Nov 2024 23:52:45 +0100 Subject: [PATCH 1/8] feat(CookieConsent) Added Cookie consent banner --- src/app/casos-activos/solicitudes/page.js | 19 ++--- src/app/voluntometro/page.js | 17 ++--- src/components/CookieBanner/CookieBanner.tsx | 77 ++++++++++++++++++++ src/components/{Modal.js => Modal.tsx} | 38 ++++++++-- src/components/auth/PhoneNumberDialog.js | 8 +- src/components/layout/EmergencyLayout.tsx | 2 + src/components/map/map.tsx | 11 +-- src/context/EmergencyProvider.tsx | 30 +++++--- src/helpers/constants.ts | 2 + 9 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 src/components/CookieBanner/CookieBanner.tsx rename src/components/{Modal.js => Modal.tsx} (53%) diff --git a/src/app/casos-activos/solicitudes/page.js b/src/app/casos-activos/solicitudes/page.js index cc41c002..0120dd32 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,7 +174,7 @@ export default function Solicitudes() { + + + + + ); +}; + +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 && (
); diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index 2f123efb..9b28e071 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -6,9 +6,10 @@ import 'maplibre-gl/dist/maplibre-gl.css'; import { Marker } from 'react-map-gl/maplibre'; import { MapPin } from 'lucide-react'; import { useModal } from '@/context/EmergencyProvider'; -// @ts-ignore import Modal from '@/components/Modal'; +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 = () => { diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 431ddc2f..d0eb3d6e 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -18,3 +18,5 @@ export const mapToIdAndLabel = (data: any) => { label: data[key], })); }; + +export const COOKIE_CONSENT_KEY = 'ajudana_cookie_consent'; From 2e70a972637e21987791322f8fafea991c352d7b Mon Sep 17 00:00:00 2001 From: Martin Gerritsen Date: Wed, 6 Nov 2024 23:56:03 +0100 Subject: [PATCH 2/8] More clear name on the cookie consent key --- src/components/CookieBanner/CookieBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CookieBanner/CookieBanner.tsx b/src/components/CookieBanner/CookieBanner.tsx index b7f79932..edffe6be 100644 --- a/src/components/CookieBanner/CookieBanner.tsx +++ b/src/components/CookieBanner/CookieBanner.tsx @@ -8,7 +8,7 @@ import { usePathname } from 'next/navigation'; const MODAL_NAME = 'cookie-banner'; const POLICY_URL = '/politica-privacidad'; -const COOKIE_CONSENT_KEY = 'cookieConsentAccepted'; +const COOKIE_CONSENT_KEY = 'ajudadanaCookieConsentAccepted'; const CookieBanner: FC = () => { const { toggleModal } = useModal(); From a2268d96c8ac281f650dce911c7ea8dee40b5a58 Mon Sep 17 00:00:00 2001 From: PaolaEstefania Date: Thu, 7 Nov 2024 00:58:04 +0100 Subject: [PATCH 3/8] feat: check at login if user privacyPolicy is set --- .env.example | 2 - package-lock.json | 105 ----------------------------------- src/app/auth/page.js | 4 +- src/components/auth/Login.js | 81 ++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 110 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index c4c451c5..00000000 --- a/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -NEXT_PUBLIC_SUPABASE_URL= -NEXT_PUBLIC_SUPABASE_ANON_KEY= diff --git a/package-lock.json b/package-lock.json index 1f4454a1..c69c8949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7457,111 +7457,6 @@ "version": "0.1.5", "license": "MIT", "optional": true - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.2.tgz", - "integrity": "sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.2.tgz", - "integrity": "sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.2.tgz", - "integrity": "sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.2.tgz", - "integrity": "sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.2.tgz", - "integrity": "sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.2.tgz", - "integrity": "sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.2.tgz", - "integrity": "sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } 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/components/auth/Login.js b/src/components/auth/Login.js index 752170ff..06095af1 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js @@ -10,28 +10,85 @@ export default function Login({ onSuccessCallback }) { const [formData, setFormData] = useState({ email: '', password: '', + privacyPolicy: '', }); const [status, setStatus] = useState({ isSubmitting: false, error: null, success: false, }); + const [isPrivacyAccepted, setPrivacyAccepted] = useState(true); + + /** + * Updates privacy policy of user + * @param {boolean} value + */ + const updatePrivacyPolicy = async (value) => { + const { data: session, error: errorGettingUser } = await authService.getSessionUser(); + + if (!session.user || errorGettingUser) { + throw new Error('Error a la hora de obtener el usuario'); + } + + const metadata = session.user.user_metadata; + const metadataUpdated = { ...metadata, privacyPolicy: value }; + + const { error: updateUserError } = await authService.updateUser({ + data: metadataUpdated, + }); + + if (updateUserError) { + throw new Error('Error a la hora de actualizar el usuario'); + } + }; + + const getUserPrivacyPolicy = async () => { + const { data: session, error: errorGettingUser } = await authService.getSessionUser(); + if (!session.user || errorGettingUser) { + throw new Error('Error a la hora de obtener el usuario'); + } + return session.user.user_metadata.privacyPolicy; + + }; const handleSubmit = async (e) => { e.preventDefault(); + setStatus({ isSubmitting: true, error: null, success: false }); + if (!isPrivacyAccepted) { + if (!formData.privacyPolicy) { + setError('Para continuar, debes aceptar la Política de Privacidad.'); + return; + } + } + if (!formData.email || !formData.password) { setStatus({ isSubmitting: false, error: 'Rellena el email y contraseña', success: false }); return; } - const response = await authService.signIn(formData.email, formData.password); + const response = await authService.signIn(formData.email, formData.password); + if (response.error) { setStatus({ isSubmitting: false, error: 'El email o contraseña son inválidos', success: false }); return; } + + + if (formData.privacyPolicy && !isPrivacyAccepted) { + await updatePrivacyPolicy(true); + setPrivacyAccepted(true); + } + + const privacyPolicy = await getUserPrivacyPolicy(); + + if (!privacyPolicy) { + setPrivacyAccepted(false); + setStatus({ isSubmitting: false, error: null, success: false }); + return; + } setStatus({ isSubmitting: false, error: null, success: true }); @@ -69,6 +126,28 @@ export default function Login({ onSuccessCallback }) { /> + {/* Política de privacidad */} + {!isPrivacyAccepted && ( +
+
+ setFormData({ ...formData, privacyPolicy: e.target.checked })} + className="min-w-4 min-h-4 cursor-pointer" + id="privacyPolicy" + required + /> + +
+
+ )}
Date: Thu, 7 Nov 2024 01:04:46 +0100 Subject: [PATCH 4/8] fix: logout if no privacyPolicy setted --- src/components/auth/Login.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index 06095af1..4f16c40a 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js @@ -71,6 +71,7 @@ export default function Login({ onSuccessCallback }) { const response = await authService.signIn(formData.email, formData.password); + if (response.error) { setStatus({ isSubmitting: false, error: 'El email o contraseña son inválidos', success: false }); return; @@ -86,7 +87,9 @@ export default function Login({ onSuccessCallback }) { if (!privacyPolicy) { setPrivacyAccepted(false); + await authService.signOut(); setStatus({ isSubmitting: false, error: null, success: false }); + return; } From 078fd941c89976a40b42271e45c8254308f03607 Mon Sep 17 00:00:00 2001 From: Martin Gerritsen Date: Thu, 7 Nov 2024 08:14:50 +0100 Subject: [PATCH 5/8] Removed Cookie key in constant --- src/components/CookieBanner/CookieBanner.tsx | 2 +- src/helpers/constants.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/CookieBanner/CookieBanner.tsx b/src/components/CookieBanner/CookieBanner.tsx index edffe6be..d99de933 100644 --- a/src/components/CookieBanner/CookieBanner.tsx +++ b/src/components/CookieBanner/CookieBanner.tsx @@ -8,7 +8,7 @@ import { usePathname } from 'next/navigation'; const MODAL_NAME = 'cookie-banner'; const POLICY_URL = '/politica-privacidad'; -const COOKIE_CONSENT_KEY = 'ajudadanaCookieConsentAccepted'; +const COOKIE_CONSENT_KEY = 'ajudaDanaCookieConsentAccepted'; const CookieBanner: FC = () => { const { toggleModal } = useModal(); diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 5d9b87cf..f89eded4 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -20,5 +20,3 @@ export const mapToIdAndLabel = (data: any) => { label: data[key], })); }; - -export const COOKIE_CONSENT_KEY = 'ajudana_cookie_consent'; From 0442042f7f0dd06ef8d46fabeaa905fd26fc6c52 Mon Sep 17 00:00:00 2001 From: PaolaEstefania Date: Thu, 7 Nov 2024 09:45:43 +0100 Subject: [PATCH 6/8] fix: do not delete .env.example --- .env.example | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c4c451c5 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_ANON_KEY= From 85f4c20503904b484df091e37f43c0854f427dbc Mon Sep 17 00:00:00 2001 From: patrickwebsdev Date: Thu, 7 Nov 2024 07:56:48 -0300 Subject: [PATCH 7/8] fix: towns id --- src/app/casos-activos/solicitudes/page.js | 11 +++++++++-- src/components/OfferCard.js | 3 ++- src/components/PhoneInfo.js | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/casos-activos/solicitudes/page.js b/src/app/casos-activos/solicitudes/page.js index 23379005..b3731a03 100644 --- a/src/app/casos-activos/solicitudes/page.js +++ b/src/app/casos-activos/solicitudes/page.js @@ -177,7 +177,10 @@ export default function Solicitudes() { className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 flex items-center gap-2 whitespace-nowrap" > - Ofrecer ayuda {filtroData.pueblo === 'todos' ? '' : ' a ' + towns[filtroData.pueblo - 1]?.name} + Ofrecer ayuda{' '} + {filtroData.pueblo === 'todos' + ? '' + : ' a ' + towns.find((town) => town.id === Number(filtroData.pueblo))?.name}
) : ( @@ -189,7 +192,11 @@ export default function Solicitudes() {
{showModal && ( - + town.id === Number(filtroData.pueblo))?.name} + onClose={closeModal} + isModal={true} + /> )} diff --git a/src/components/OfferCard.js b/src/components/OfferCard.js index 61d37539..b58cf373 100644 --- a/src/components/OfferCard.js +++ b/src/components/OfferCard.js @@ -59,7 +59,8 @@ export default function OfferCard({
- Pueblo: {towns[caso.town_id - 1]?.name || ''} + Pueblo:{' '} + {towns.find((town) => town.id === caso.town_id)?.name || ''}
)} diff --git a/src/components/PhoneInfo.js b/src/components/PhoneInfo.js index e1ff465b..62d4deef 100644 --- a/src/components/PhoneInfo.js +++ b/src/components/PhoneInfo.js @@ -21,7 +21,11 @@ export default function PhoneInfo({ caseInfo }) { return ( Contacto:{' '} - {!!userAssignment ? caseInfo.contact_info : 'Ayuda a esta persona para ver sus datos de contacto'} + {session && session.user + ? !!userAssignment + ? caseInfo.contact_info + : 'Dale al botón "Quiero ayudar" para ver sus datos de contacto.' + : 'Inicia sesion para ver este dato'} ); } From 4a80117e9295b682ca9cf98eb3da35f26a27a4b2 Mon Sep 17 00:00:00 2001 From: Martin Gerritsen Date: Thu, 7 Nov 2024 14:04:13 +0100 Subject: [PATCH 8/8] feature(Modal) added Quiero ayudar modal --- src/components/AsignarSolicitudButton.tsx | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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?

+
+ + +
+
+
); }