diff --git a/package.json b/package.json index cc71a4a..cf23f25 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "react-markdown": "^9.0.1", "react-snap-carousel": "^0.4.0", "remark-gfm": "^4.0.0", - "yet-another-react-lightbox": "^3.16.0" + "yet-another-react-lightbox": "^3.16.0", + "react-google-recaptcha-v3": "^1.10.1" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/actions.ts b/src/app/actions.ts index e7833e9..d554a73 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -34,3 +34,44 @@ export async function addToGroup({ email }: { email: string }) { return 500; } } + +export async function sendQuestion({ + question, + slug, + recaptchaToken, +}: { + question: string; + slug: string; + recaptchaToken: string; +}) { + const isRecaptchaValid = await validateRecaptcha(recaptchaToken); + if (!isRecaptchaValid) { + console.error('Recaptcha validation failed'); + return 400; + } + if (!question || !slug) { + return 400; + } + const res = await fetch(`https://konf-qna.kir-dev.hu/api/presentation/${slug}/question`, { + method: 'POST', + body: JSON.stringify({ content: question, userId: 'zokni' }), + }); + if (res.status === 200) { + return 201; + } else if (res.status === 400) { + return 400; + } + return 500; +} + +async function validateRecaptcha(token: string) { + const res = await fetch('https://www.google.com/recaptcha/api/siteverify', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `secret=${process.env.RECAPTCHA_SECRET}&response=${token}`, + }); + const data = await res.json(); + return data.success; +} diff --git a/src/app/questions/page.tsx b/src/app/questions/page.tsx new file mode 100644 index 0000000..5a7b9e0 --- /dev/null +++ b/src/app/questions/page.tsx @@ -0,0 +1,26 @@ +import { RoomQuestion } from '@/components/tiles/question-tile'; +import { getPresentationData } from '@/models/get-presentation-data'; + +export default async function questionsPage() { + const presentations = await getPresentationData(); + return ( +
+

Kérdezz az elődóktól!

+ +
+
+

IB028

+
+
+

IB025

+
+
+ +
+
+ +
+
+
+ ); +} diff --git a/src/components/navbar/navbar-items.tsx b/src/components/navbar/navbar-items.tsx index 68f8fd4..86461ca 100644 --- a/src/components/navbar/navbar-items.tsx +++ b/src/components/navbar/navbar-items.tsx @@ -18,6 +18,10 @@ const links = [ href: '/contact', label: 'kapcsolat', }, + { + href: '/questions', + label: 'kérdések', + }, { href: '/golya', label: 'gólyáknak', diff --git a/src/components/presentation/PresentationGrid.tsx b/src/components/presentation/PresentationGrid.tsx index 10e4c79..3dcda05 100644 --- a/src/components/presentation/PresentationGrid.tsx +++ b/src/components/presentation/PresentationGrid.tsx @@ -2,8 +2,10 @@ import clsx from 'clsx'; import Link from 'next/link'; -import { CSSProperties, useRef } from 'react'; +import React, { CSSProperties, useRef } from 'react'; +import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; +import { PresentationQuestionForm } from '@/components/presentation/PresentationQuestion'; import { Tile } from '@/components/tiles/tile'; import { PresentationWithDates, SponsorCategory } from '@/models/models'; import { dateToHourAndMinuteString } from '@/utils/dateHelper'; @@ -88,56 +90,69 @@ export function PresentationGrid({ ); } -function PresentationTile({ presentation }: { presentation: PresentationWithDates }) { +export function PresentationTile({ + presentation, + preview = false, +}: { + presentation: PresentationWithDates; + preview?: boolean; +}) { return ( - - - - {presentation.room !== 'BOTH' && `${presentation.room} | `} - {dateToHourAndMinuteString(presentation.startDate)} - {dateToHourAndMinuteString(presentation.endDate)} - -
-
-

- {presentation.title} -

- {presentation.room === 'BOTH' && presentation.placeholder && ( + <> + + + + {presentation.room !== 'BOTH' && `${presentation.room} | `} + {dateToHourAndMinuteString(presentation.startDate)} - {dateToHourAndMinuteString(presentation.endDate)} + +
+

{presentation.title}

+ {presentation.room === 'BOTH' && presentation.placeholder && ( +

+ {presentation.title} +

+ )} +
+ {!!presentation.presenter && ( +
+ Presentation Image +
+

{presentation.presenter.name}

+
{presentation.presenter.rank}
+
{presentation.presenter.company?.name}
+
+
+ )} + {presentation.presenter?.company?.category === SponsorCategory.MAIN_SPONSOR && !preview && ( +

{presentation.description.split('\n')[0]}

+ )} + {preview && ( + + + )}
- {!!presentation.presenter && ( -
- Presentation Image -
-

{presentation.presenter.name}

-
{presentation.presenter.rank}
-
{presentation.presenter.company?.name}
-
-
- )} - {presentation.presenter?.company?.category === SponsorCategory.MAIN_SPONSOR && ( -

{presentation.description.split('\n')[0]}

- )} -
- - + + + ); } diff --git a/src/components/presentation/PresentationQuestion.tsx b/src/components/presentation/PresentationQuestion.tsx new file mode 100644 index 0000000..7ceb16f --- /dev/null +++ b/src/components/presentation/PresentationQuestion.tsx @@ -0,0 +1,75 @@ +import { Dialog } from '@headlessui/react'; +import { useState } from 'react'; +import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'; +import { FaCheckCircle } from 'react-icons/fa'; + +import { sendQuestion } from '@/app/actions'; +import { WhiteButton } from '@/components/white-button'; + +interface PresentationQuestionFormProps { + slug: string; +} + +export function PresentationQuestionForm({ slug }: PresentationQuestionFormProps) { + const { executeRecaptcha } = useGoogleReCaptcha(); + const [isSuccessOpen, setIsSuccessOpen] = useState(false); + + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [question, setQuestion] = useState(''); + + const onSend = async () => { + if (!executeRecaptcha) return; + const recaptchaToken = await executeRecaptcha('presentation_question'); + if (question.trim()) { + setIsLoading(true); + const status = await sendQuestion({ question, slug, recaptchaToken }); + setIsLoading(false); + switch (status) { + case 201: + setIsSuccessOpen(true); + setQuestion(''); + break; + case 400: + setError('Hibás formátum!'); + break; + default: + setError('Ismeretlen hiba!'); + } + } + }; + + return ( +
+