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.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.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 (
+
+ );
+}
diff --git a/src/components/tiles/mobil-app-tile.tsx b/src/components/tiles/mobil-app-tile.tsx
index 8664cd2..bd1c490 100644
--- a/src/components/tiles/mobil-app-tile.tsx
+++ b/src/components/tiles/mobil-app-tile.tsx
@@ -23,9 +23,6 @@ export function MobilAppTile({ data }: Props) {
-
-
-
Android
-
-
-
-
- Az APK fájlt letöltve és megnyitva telepítheted az alkalmazást az Androidos eszközödre. A telepítés
- előtt/közben az "Ismeretlen forrásból származó alkalmazások engedélyezése" opciót be kell kapcsolni a
- beállításokban.
-
+
+ Sajnáljuk, de az Android alkalmazás még nem érhető el a Google Play Áruházban.
+ További információkért látogass el a Kir-Dev standjához!
);
diff --git a/src/components/tiles/question-tile.tsx b/src/components/tiles/question-tile.tsx
new file mode 100644
index 0000000..74d05d4
--- /dev/null
+++ b/src/components/tiles/question-tile.tsx
@@ -0,0 +1,22 @@
+'use client';
+import { PresentationWithDates } from '@/models/models';
+import { getCurrentDate } from '@/utils/dateHelper';
+
+import { PresentationTile } from '../presentation/PresentationGrid';
+
+type Props = {
+ presentations: PresentationWithDates[];
+ room: 'IB028' | 'IB025';
+};
+
+export function RoomQuestion({ presentations, room }: Props) {
+ const now = getCurrentDate();
+ const presentation = presentations.find((p) => p.room === room && p.startDate < now && p.endDate > now);
+ return presentation ? (
+
+ ) : (
+
+ Jelenleg nincs előadás ebben a teremben, nézz vissza később!
+
+ );
+}
diff --git a/src/utils/dateHelper.ts b/src/utils/dateHelper.ts
index f7ada13..e6822b9 100644
--- a/src/utils/dateHelper.ts
+++ b/src/utils/dateHelper.ts
@@ -6,3 +6,8 @@ export function dateToHourAndMinuteString(date: Date): string {
timeZone: 'Europe/Budapest',
});
}
+
+export function getCurrentDate() {
+ return new Date(2024, 2, 19, 14, 12);
+ // return new Date();
+}
diff --git a/yarn.lock b/yarn.lock
index d8965aa..200b21c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1685,6 +1685,13 @@ hast-util-whitespace@^3.0.0:
dependencies:
"@types/hast" "^3.0.0"
+hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
html-url-attributes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.0.tgz#fc4abf0c3fb437e2329c678b80abb3c62cff6f08"
@@ -3056,12 +3063,19 @@ react-dom@^18:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-google-recaptcha-v3@^1.10.1:
+ version "1.10.1"
+ resolved "https://registry.yarnpkg.com/react-google-recaptcha-v3/-/react-google-recaptcha-v3-1.10.1.tgz#5b125bc0dec123206431860e8800e188fc735aff"
+ integrity sha512-K3AYzSE0SasTn+XvV2tq+6YaxM+zQypk9rbCgG4OVUt7Rh4ze9basIKefoBz9sC0CNslJj9N1uwTTgRMJQbQJQ==
+ dependencies:
+ hoist-non-react-statics "^3.3.2"
+
react-icons@^4.12.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.12.0.tgz#54806159a966961bfd5cdb26e492f4dafd6a8d78"
integrity sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==
-react-is@^16.13.1:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==