Skip to content

Commit

Permalink
Limit the number of asked questions per visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
Isti01 committed Mar 18, 2024
1 parent 4b5b77c commit a9f7399
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 39 deletions.
4 changes: 3 additions & 1 deletion src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ export async function sendQuestion({
question,
slug,
recaptchaToken,
userId,
}: {
question: string;
slug: string;
recaptchaToken: string;
userId: string;
}) {
const isRecaptchaValid = await validateRecaptcha(recaptchaToken);
if (!isRecaptchaValid) {
Expand All @@ -54,7 +56,7 @@ export async function sendQuestion({
}
const res = await fetch(`https://konf-qna.kir-dev.hu/api/presentation/${slug}/question`, {
method: 'POST',
body: JSON.stringify({ content: question, userId: 'zokni' }),
body: JSON.stringify({ content: question, userId }),
});
if (res.status === 200) {
return 201;
Expand Down
18 changes: 2 additions & 16 deletions src/app/questions/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
import { RoomQuestion } from '@/components/tiles/question-tile';
import { QuestionPageBody } from '@/components/questions/question-page-body';
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>
<QuestionPageBody presentations={presentations} />
</div>
);
}
7 changes: 1 addition & 6 deletions src/components/presentation/PresentationGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import clsx from 'clsx';
import Link from 'next/link';
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';
Expand Down Expand Up @@ -144,11 +143,7 @@ export function PresentationTile({
{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>
)}
{preview && <PresentationQuestionForm slug={presentation.slug} />}
</div>
</Tile.Body>
</Tile>
Expand Down
57 changes: 41 additions & 16 deletions src/components/presentation/PresentationQuestion.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Dialog } from '@headlessui/react';
import { useState } from 'react';
import { useEffect, 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';
import { AllowedQuestionCount, getQuestionCount, getUserId, incrementQuestionCount } from '@/utils/questionHelpers';

interface PresentationQuestionFormProps {
slug: string;
Expand All @@ -17,16 +18,25 @@ export function PresentationQuestionForm({ slug }: PresentationQuestionFormProps
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [question, setQuestion] = useState('');
const [questionCount, setQuestionCount] = useState(0);

const canAskQuestions = questionCount < AllowedQuestionCount;

useEffect(() => {
setQuestionCount(getQuestionCount(slug));
}, []);

const onSend = async () => {
if (!executeRecaptcha) return;
const recaptchaToken = await executeRecaptcha('presentation_question');
if (question.trim()) {
if (question.trim() && canAskQuestions) {
setIsLoading(true);
const status = await sendQuestion({ question, slug, recaptchaToken });
const status = await sendQuestion({ question, slug, recaptchaToken, userId: getUserId() });
setIsLoading(false);
switch (status) {
case 201:
incrementQuestionCount(slug);
setQuestionCount(questionCount + 1);
setIsSuccessOpen(true);
setQuestion('');
break;
Expand All @@ -41,19 +51,34 @@ export function PresentationQuestionForm({ slug }: PresentationQuestionFormProps

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>
{canAskQuestions ? (
<>
<div className='relative'>
<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!'
/>
<p className='absolute right-0 bottom-0 p-4'>
{questionCount}/{AllowedQuestionCount} Kérdés feltéve
</p>
</div>
{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>
</>
) : (
<div className='w-full my-4 flex justify-center'>
<WhiteButton disabled={true} onClick={() => {}}>
Elfogytak a feltehető kérdések!
</WhiteButton>
</div>
)}
<Dialog open={isSuccessOpen} onClose={() => setIsSuccessOpen(false)} className='relative z-50'>
<div className='fixed inset-0 bg-black/80' aria-hidden='true' />

Expand Down
27 changes: 27 additions & 0 deletions src/components/questions/question-page-body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';

import { RoomQuestion } from '@/components/tiles/question-tile';
import { PresentationWithDates } from '@/models/models';

export function QuestionPageBody({ presentations }: { presentations: PresentationWithDates[] | undefined }) {
return (
<GoogleReCaptchaProvider reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? ''}>
<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 mt-16 sm:mt-0'>
<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>
</GoogleReCaptchaProvider>
);
}
31 changes: 31 additions & 0 deletions src/utils/questionHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';

export const AllowedQuestionCount = 3;

export function getUserId(): string {
let key = localStorage.getItem('user-id');
if (!key) {
key = generateRandomString(32);
localStorage.setItem('user-id', key);
}
return key;
}

export function getQuestionCount(slug: string) {
let amount = Number(localStorage.getItem(`question-${slug}`));
if (isNaN(amount)) {
amount = 0;
localStorage.setItem(`question-${slug}`, String(amount));
}
return amount;
}

export function incrementQuestionCount(slug: string) {
localStorage.setItem(`question-${slug}`, String(getQuestionCount(slug) + 1));
}

function generateRandomString(length: number): string {
return Array.from({ length })
.map(() => chars[Math.floor(Math.random() * chars.length)])
.join('');
}

0 comments on commit a9f7399

Please sign in to comment.