diff --git a/app/components/EventDetailsTopInfoSection.tsx b/app/components/EventDetailsTopInfoSection.tsx new file mode 100644 index 0000000..f96543a --- /dev/null +++ b/app/components/EventDetailsTopInfoSection.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import BondscapeButton from "@/components/BondscapeButton"; + +interface Props { + visible: boolean; + title: string; + description: string; + buttonText: string; + onButtonClick: () => void; +} + +const EventDetailsTopInfoSection = ({ + visible, + title, + description, + buttonText, + onButtonClick, +}: Props) => { + return ( +
+
+ {title} +
+
+
+ {description} +
+ {visible && ( + + )} +
+
+ ); +}; + +export default EventDetailsTopInfoSection; diff --git a/app/components/EventQRCodeDialog.tsx b/app/components/EventQRCodeDialog.tsx new file mode 100644 index 0000000..3545b2c --- /dev/null +++ b/app/components/EventQRCodeDialog.tsx @@ -0,0 +1,134 @@ +"use client"; +import React, { useCallback } from "react"; +import Image from "next/image"; +import { PuffLoader } from "react-spinners"; +import Skeleton from "react-loading-skeleton"; +import { Button } from "primereact/button"; +import { toast } from "react-toastify"; +import { Dialog } from "primereact/dialog"; + +interface Props { + /** + * Name of the event to which the QR code is associated. + */ + eventName: string | undefined; + + /** + * Link associated to the QR code. If undefined, the button to copy the link will not be disabled. + */ + linkUrl: string | undefined; + + /** + * The QR code image source. + */ + qrCodeImageSrc: string | undefined; + + /** + * Whether the dialog is visible or not. + */ + visible: boolean; + + /** + * Callback to be called when the dialog is hidden. + */ + onHide: () => void; +} + +const EventQRCodeDialog = ({ + eventName, + linkUrl, + qrCodeImageSrc, + visible, + onHide, +}: Props) => { + const [isLoading, setIsLoading] = React.useState(false); + + const saveQrCode = useCallback(async () => { + if (!qrCodeImageSrc) return; + + setIsLoading(true); + try { + // Get the QR Code image data + const response = await fetch(qrCodeImageSrc); + const block = await response.blob(); + const url = URL.createObjectURL(block); + + // Create a link that contains the image data + const a = document.createElement("a"); + a.href = url; + a.download = `bondscape_${eventName}_qr_code.png`; + document.body.appendChild(a); + + // Click the link to download the image + a.click(); + + // Remove the link + document.body.removeChild(a); + } catch (error) { + console.error(error); + setIsLoading(false); + } + }, [eventName, qrCodeImageSrc]); + + return ( + +
+
+ {qrCodeImageSrc ? ( + {"Qr + ) : ( + + )} +
+
+
+ {eventName ?? } +
+
+ {linkUrl && ( +
+
+
+
+ ); +}; + +export default EventQRCodeDialog; diff --git a/app/creator/events/[id]/page.tsx b/app/creator/events/[id]/page.tsx index 3bfc86b..f5f9f3d 100644 --- a/app/creator/events/[id]/page.tsx +++ b/app/creator/events/[id]/page.tsx @@ -1,70 +1,59 @@ "use client"; -import BondscapeButton from "@/components/BondscapeButton"; import useCustomLazyQuery from "@/hooks/graphql/useCustomLazyQuery"; import useBreakpoints from "@/hooks/layout/useBreakpoints"; import useFormatDateToTZ from "@/hooks/timeformat/useFormatDateToTZ"; import MainLayout from "@/layouts/MainLayout"; -import { - extractTimezoneOffset, - serializeTimezoneOffset, -} from "@/lib/DateUtils"; +import {extractTimezoneOffset, serializeTimezoneOffset,} from "@/lib/DateUtils"; import GetEventJoinLink from "@/services/axios/requests/GetEventJoinLink"; import GetQrCode from "@/services/axios/requests/GetQrCode"; import GetEventById from "@/services/graphql/queries/bondscape/GetEventById"; -import { Event, GQLEventsResult } from "@/types/event"; +import {Event, GQLEventsResult} from "@/types/event"; import Image from "next/image"; import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { Button } from "primereact/button"; -import { Dialog } from "primereact/dialog"; -import { ProgressBar } from "primereact/progressbar"; -import { classNames } from "primereact/utils"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import {useRouter} from "next/navigation"; +import {ProgressBar} from "primereact/progressbar"; +import {classNames} from "primereact/utils"; +import React, {useCallback, useEffect, useMemo, useState} from "react"; import Skeleton from "react-loading-skeleton"; -import { PuffLoader } from "react-spinners"; -import { toast } from "react-toastify"; +import GetEventDetailsLink from "@/services/axios/requests/GetEventDetailsLink"; +import EventDetailsTopInfoSection from "@/components/EventDetailsTopInfoSection"; +import EventQRCodeDialog from "@/components/EventQRCodeDialog"; +/** + * Event details page. + * @constructor + */ export default function EventDetails({ params }: { params: any }) { - // States + // ------------------------------------------------------------------------------------------------------------------ + // --- States + // ------------------------------------------------------------------------------------------------------------------ + const [selectedEvent, setSelectedEvent] = useState(); - const [eventQrCode, setEventQrCode] = useState(""); - const [generatingQr, setGeneratingQr] = useState(false); - const [qrCodeVisible, setQrCodeVisible] = useState(false); - // Hooks + const [eventQRCodeDialogURL, setEventQRCodeDialogURL] = useState< + string | undefined + >(undefined); + const [eventQRCodeDialogImageSrc, setEventQRCodeDialogImageSrc] = useState< + string | undefined + >(undefined); + const [eventQRCodeDialogVisible, setEventQRCodeDialogVisible] = + useState(false); + + const [eventShareQRCode, setEventShareQRCode] = useState(""); + const [eventJoinQRCode, setEventJoinQRCode] = useState(""); + + // ------------------------------------------------------------------------------------------------------------------ + // --- Hooks + // ------------------------------------------------------------------------------------------------------------------ + const [isMobile, isMd] = useBreakpoints(); const router = useRouter(); const [getEventById] = useCustomLazyQuery(GetEventById); const { getEventPeriodExtended } = useFormatDateToTZ(); - // Callbacks - - const generateQrCode = useCallback(async (url: string) => { - const result = await GetQrCode(url, "url"); - if (result.isOk()) { - setEventQrCode(result.value.url); - } - }, []); - - const toDataURL = useCallback(async () => { - setGeneratingQr(true); - try { - const response = await fetch(eventQrCode); - const blob = await response.blob(); - return URL.createObjectURL(blob); - } finally { - setGeneratingQr(false); - } - }, [eventQrCode]); - - const saveQrCode = useCallback(async () => { - const a = document.createElement("a"); - a.href = await toDataURL(); - a.download = `bondscape_${selectedEvent?.name}_qr_code.png`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - }, [selectedEvent?.name, toDataURL]); + // ------------------------------------------------------------------------------------------------------------------ + // --- Callbacks + // ------------------------------------------------------------------------------------------------------------------ const loadEvent = useCallback( async (eventId: string) => { @@ -80,17 +69,41 @@ export default function EventDetails({ params }: { params: any }) { [getEventById], ); - // Effects + const showEventJoinQRCode = useCallback(() => { + setEventQRCodeDialogImageSrc(eventJoinQRCode); + setEventQRCodeDialogURL(undefined); + setEventQRCodeDialogVisible(true); + }, [eventJoinQRCode]); + + const showEventShareQRCode = useCallback(() => { + setEventQRCodeDialogImageSrc(eventShareQRCode); + setEventQRCodeDialogURL(selectedEvent?.detailsLink); + setEventQRCodeDialogVisible(true); + }, [eventShareQRCode, selectedEvent?.detailsLink]); + + // ------------------------------------------------------------------------------------------------------------------ + // --- Effects + // ------------------------------------------------------------------------------------------------------------------ useEffect(() => { if (selectedEvent) { - GetEventJoinLink(selectedEvent.id).then((result) => { - if (result.isOk()) { - generateQrCode(result.value); - } - }); + GetEventJoinLink(selectedEvent.id) + .andThen((url) => GetQrCode(url, "url")) + .then((result) => { + if (result.isOk()) { + setEventJoinQRCode(result.value.url); + } + }); + + GetEventDetailsLink(selectedEvent.id) + .andThen((url) => GetQrCode(url, "url")) + .then((result) => { + if (result.isOk()) { + setEventShareQRCode(result.value.url); + } + }); } - }, [generateQrCode, selectedEvent]); + }, [selectedEvent]); useEffect(() => { const eventId = params.id as string; @@ -110,6 +123,10 @@ export default function EventDetails({ params }: { params: any }) { } }, [selectedEvent]); + // ------------------------------------------------------------------------------------------------------------------ + // --- Screen rendering + // ------------------------------------------------------------------------------------------------------------------ + if (isMobile || isMd) { return (
@@ -128,26 +145,24 @@ export default function EventDetails({ params }: { params: any }) { editButtonHref={`/creator/create/${params.id}`} >
-
-
- Event Check-in -
-
-
- Save this QR and shows it to attendees at the event to let them - join the memories creation! -
- {selectedEvent && ( - setQrCodeVisible(true)} - /> - )} -
-
+ +
{selectedEvent ? ( @@ -435,63 +450,13 @@ export default function EventDetails({ params }: { params: any }) {
- setQrCodeVisible(false)} - > -
-
- {eventQrCode ? ( - {"Qr - ) : ( - - )} -
-
-
- {selectedEvent?.name ?? } -
- {selectedEvent && ( -
-
- )} -
-
-
+ setEventQRCodeDialogVisible(false)} + /> ); } diff --git a/app/services/axios/requests/GetEventDetailsLink/index.ts b/app/services/axios/requests/GetEventDetailsLink/index.ts new file mode 100644 index 0000000..92189f1 --- /dev/null +++ b/app/services/axios/requests/GetEventDetailsLink/index.ts @@ -0,0 +1,11 @@ +import { ResultAsync } from "neverthrow"; +import axiosInstance from "../../index"; + +const GetEventDetailsLink = (eventId: string): ResultAsync => { + return ResultAsync.fromPromise( + axiosInstance.get(`/events/${eventId}/links/details`), + (e: any) => e ?? Error("Error getting the link"), + ).map((response) => response.data); +}; + +export default GetEventDetailsLink;