Skip to content

Commit

Permalink
Merge pull request #84 from simonyiszk/feature/questions
Browse files Browse the repository at this point in the history
questions from the web
  • Loading branch information
Tschonti authored Mar 17, 2024
2 parents 7d61b40 + a04b808 commit 9435d49
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 57 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 41 additions & 0 deletions src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
26 changes: 26 additions & 0 deletions src/app/questions/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex flex-col px-4 sm:px-6 xl:px-0 max-w-6xl w-full overflow-hidden'>
<h1 className='mb-16 mt-8'>Kérdezz az elődóktól!</h1>

<div className='grid grid-cols-1 sm:grid-cols-2 gap-6'>
<div className='order-1'>
<h2 className='text-4xl text-center'>IB028</h2>
</div>
<div className='order-3 sm:order-2'>
<h2 className='text-4xl text-center'>IB025</h2>
</div>
<div className='order-2 sm:order-3'>
<RoomQuestion presentations={presentations ?? []} room='IB028' />
</div>
<div className='order-4'>
<RoomQuestion presentations={presentations ?? []} room='IB025' />
</div>
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions src/components/navbar/navbar-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const links = [
href: '/contact',
label: 'kapcsolat',
},
{
href: '/questions',
label: 'kérdések',
},
{
href: '/golya',
label: 'gólyáknak',
Expand Down
99 changes: 57 additions & 42 deletions src/components/presentation/PresentationGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -88,56 +90,69 @@ export function PresentationGrid({
);
}

function PresentationTile({ presentation }: { presentation: PresentationWithDates }) {
export function PresentationTile({
presentation,
preview = false,
}: {
presentation: PresentationWithDates;
preview?: boolean;
}) {
return (
<Tile clickable={!presentation.placeholder} className='w-full h-full' disableMinHeight={true}>
<Tile.Body lessPadding='5' className='flex flex-col'>
<span className='pb-2 text-xs'>
{presentation.room !== 'BOTH' && `${presentation.room} | `}
{dateToHourAndMinuteString(presentation.startDate)} - {dateToHourAndMinuteString(presentation.endDate)}
</span>
<div className='flex flex-col justify-center flex-1'>
<div className={clsx('flex', presentation.placeholder && 'justify-around')}>
<h2
className={clsx(
'text-lg lg:text-xl font-medium',
!presentation.presenter ? 'text-center pb-4' : 'pb-4 lg:pb-6'
)}
>
{presentation.title}
</h2>
{presentation.room === 'BOTH' && presentation.placeholder && (
<>
<Tile clickable={!presentation.placeholder && !preview} className='w-full h-full' disableMinHeight={true}>
<Tile.Body lessPadding='5' className='flex flex-col'>
<span className='pb-2 text-xs'>
{presentation.room !== 'BOTH' && `${presentation.room} | `}
{dateToHourAndMinuteString(presentation.startDate)} - {dateToHourAndMinuteString(presentation.endDate)}
</span>
<div className='flex flex-col justify-center flex-1'>
<div className={clsx('flex', presentation.placeholder && 'justify-around')}>
<h2
aria-hidden={true}
className={clsx(
'text-lg lg:text-xl pb-4 lg:pb-6 font-medium',
!presentation.presenter && 'text-center'
'text-lg lg:text-xl font-medium',
!presentation.presenter ? 'text-center pb-4' : 'pb-4 lg:pb-6'
)}
>
{presentation.title}
</h2>
{presentation.room === 'BOTH' && presentation.placeholder && (
<h2
aria-hidden={true}
className={clsx(
'text-lg lg:text-xl pb-4 lg:pb-6 font-medium',
!presentation.presenter && 'text-center'
)}
>
{presentation.title}
</h2>
)}
</div>
{!!presentation.presenter && (
<div className='flex gap-4'>
<img
src={presentation.presenter.pictureUrl}
className='object-cover rounded-3xl w-16 h-16'
alt='Presentation Image'
/>
<div>
<h3 className='text-lg lg:text-2xl font-bold text-[#FFE500]'>{presentation.presenter.name}</h3>
<div className='text-xs lg:text-sm'>{presentation.presenter.rank}</div>
<div className='hidden lg:block text-xs pt-0.5'>{presentation.presenter.company?.name}</div>
</div>
</div>
)}
{presentation.presenter?.company?.category === SponsorCategory.MAIN_SPONSOR && !preview && (
<p className='mt-2 text-base whitespace-pre-line'>{presentation.description.split('\n')[0]}</p>
)}
{preview && (
<GoogleReCaptchaProvider reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? ''}>
<PresentationQuestionForm slug={presentation.slug} />
</GoogleReCaptchaProvider>
)}
</div>
{!!presentation.presenter && (
<div className='flex gap-4'>
<img
src={presentation.presenter.pictureUrl}
className='object-cover rounded-3xl w-16 h-16'
alt='Presentation Image'
/>
<div>
<h3 className='text-lg lg:text-2xl font-bold text-[#FFE500]'>{presentation.presenter.name}</h3>
<div className='text-xs lg:text-sm'>{presentation.presenter.rank}</div>
<div className='hidden lg:block text-xs pt-0.5'>{presentation.presenter.company?.name}</div>
</div>
</div>
)}
{presentation.presenter?.company?.category === SponsorCategory.MAIN_SPONSOR && (
<p className='mt-2 text-base whitespace-pre-line'>{presentation.description.split('\n')[0]}</p>
)}
</div>
</Tile.Body>
</Tile>
</Tile.Body>
</Tile>
</>
);
}

Expand Down
75 changes: 75 additions & 0 deletions src/components/presentation/PresentationQuestion.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='mt-10 w-full'>
<textarea
className='w-full rounded-md p-2 bg-transparent border-white border-[0.5px]'
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={4}
placeholder='Ide írd a kérdésed!'
/>
{error && <p className='text-red-500 my-2'>{error}</p>}
<div className='w-full my-4 flex justify-center'>
<WhiteButton onClick={onSend} disabled={!question.trim() || isLoading || !executeRecaptcha}>
Kérdés küldése
</WhiteButton>
</div>
<Dialog open={isSuccessOpen} onClose={() => setIsSuccessOpen(false)} className='relative z-50'>
<div className='fixed inset-0 bg-black/80' aria-hidden='true' />

<div className='fixed inset-0 flex w-screen items-center justify-center p-4'>
<Dialog.Panel className='mx-auto max-w-lg rounded bg-[#0f181c] p-8 flex flex-col items-center gap-5'>
<div className='text-8xl text-white'>
<FaCheckCircle />
</div>
<Dialog.Title className='font-bold text-2xl mb-5 text-center'>
A kérdésed megkaptuk és moderálás után a felolvasandó kérdések közé kerül. Köszönjük!
</Dialog.Title>

<WhiteButton onClick={() => setIsSuccessOpen(false)}>Rendben</WhiteButton>
</Dialog.Panel>
</div>
</Dialog>
</div>
);
}
3 changes: 0 additions & 3 deletions src/components/tiles/mobil-app-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ export function MobilAppTile({ data }: Props) {
<a href={data.iosUrl} target='_blank'>
<img className='h-[60px] min-w-[180]' src='/img/appstore.svg' alt='App Store' />
</a>
<a href={data.androidUrl} className='h-fit' target='_blank'>
<img className='h-[57px] min-w-[190]' src='/img/androidapk.svg' alt='Play Store' />
</a>
</div>
<Link
href='/mobile'
Expand Down
20 changes: 10 additions & 10 deletions src/components/tiles/mobile/android-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import Image from 'next/image';
import Link from 'next/link';

import { Tile } from '@/components/tiles/tile';

interface AndroidTileProps {
androidUrl: string;
}

export function AndroidTile({ androidUrl }: AndroidTileProps) {
export function AndroidTile({}: AndroidTileProps) {
return (
<Tile>
<Tile.Body className='space-y-8 text-center'>
<h2>Android</h2>
<Link className='mx-auto w-fit block' href={androidUrl}>
<Image src='/img/androidapk.svg' alt='Letölthető Android APK' width={302} height={80} />
</Link>
<p>
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.
</p>
<Image
className='opacity-50 mx-auto'
src='/img/google-play.png'
alt='Letölthető Android APK'
width={250}
height={40}
/>
<p>Sajnáljuk, de az Android alkalmazás még nem érhető el a Google Play Áruházban.</p>
<p>További információkért látogass el a Kir-Dev standjához!</p>
</Tile.Body>
</Tile>
);
Expand Down
22 changes: 22 additions & 0 deletions src/components/tiles/question-tile.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<PresentationTile presentation={presentation} preview />
) : (
<p className='text-stone-200 text-base sm:text-[20px] text-center'>
Jelenleg nincs előadás ebben a teremben, nézz vissza később!
</p>
);
}
5 changes: 5 additions & 0 deletions src/utils/dateHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Loading

0 comments on commit 9435d49

Please sign in to comment.