Skip to content

Commit

Permalink
add interactive for lighthase's panel
Browse files Browse the repository at this point in the history
  • Loading branch information
docimin committed Jun 23, 2024
1 parent 71d0f96 commit 46c974a
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 0 deletions.
181 changes: 181 additions & 0 deletions src/app/[locale]/(interactive)/interactive/voting/page.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use client'
import { client } from '@/app/appwrite'
import { createVote } from '@/utils/actions/interactive/createVote'
import { useEffect, useState } from 'react'
import { RealtimeResponseEvent } from 'appwrite'
import { Interactive } from '@/utils/types/models'

export default function VotingClient({
questionId,
votes,
forwardedFor,
}: {
questionId: number
votes: Interactive.VotesAnswersType
forwardedFor: string
}) {
const [votedQuestions, setVotedQuestions] = useState({})
const [selectedQuestionIndex, setSelectedQuestionIndex] = useState(questionId)
const [selectedOptionIndex, setSelectedOptionIndex] = useState(null)

const loadVotedQuestions = () => {
let newVotedQuestions = { ...votedQuestions }
votes.documents.forEach((vote) => {
if (vote.ipAddress === forwardedFor) {
newVotedQuestions[vote.questionId] = Number(vote.optionId)
}
})
setVotedQuestions(newVotedQuestions)
}

console.log(votedQuestions)

// Call this function when the component mounts
useEffect(() => {
loadVotedQuestions()
}, [])

Check warning on line 36 in src/app/[locale]/(interactive)/interactive/voting/page.client.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: React Hook useEffect has a missing dependency: 'loadVotedQuestions'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)

useEffect(() => {
if (votedQuestions[selectedQuestionIndex]) {
setSelectedOptionIndex(votedQuestions[selectedQuestionIndex])
}
}, [selectedQuestionIndex, votedQuestions])

interface QuestionType extends RealtimeResponseEvent<any> {
questionId: string
}

client.subscribe(
['databases.interactive.collections.questions.documents.main'],
(response: QuestionType) => {
setSelectedQuestionIndex(response.payload.questionId)
}
)

const questions = [
{
question: 'You come across a fork in the road. Do you:',
options: [
'Take the left path, which seems well-traveled.',
'Take the right path, which appears less traveled but possibly more interesting.',
'Consult your map or ask a local for advice.',
],
},
{
question: 'You encounter a wounded traveler on the roadside. Do you:',
options: [
'Offer immediate assistance, tending to their wounds.',
"Approach cautiously, unsure if it's a trap.",
'Ignore them and continue on your journey, not wanting to get involved.',
],
},
{
question: 'A merchant offers to sell you a mysterious potion. Do you:',
options: [
'Haggle for a lower price before buying it.',
"Ask for more information about the potion's effects.",
'Decline the offer, wary of unknown substances.',
],
},
{
question: 'You stumble upon a hidden cave entrance. Do you:',
options: [
'Investigate further, curious about what treasures it might hold.',
'Proceed cautiously, wary of potential dangers lurking inside.',
'Mark the location on your map for later exploration and continue on your current path.',
],
},
{
question:
'A group of bandits blocks your path, demanding payment for safe passage. Do you:',
options: [
'Negotiate with them to find a peaceful resolution.',
'Refuse to pay and prepare to fight if necessary.',
'Attempt to sneak past them unnoticed.',
],
},
{
question: 'You discover an ancient tome in a forgotten library. Do you:',
options: [
'Spend time deciphering its contents to gain knowledge or power.',
'Leave it undisturbed, fearing potential consequences.',
'Take the tome with you to study later.',
],
},
{
question: 'You encounter a magical creature in distress. Do you:',
options: [
'Rush to aid the creature, believing it may reward you for your kindness.',
'Approach cautiously, unsure of its intentions.',
'Ignore the creature and continue on your journey, not wanting to risk danger.',
],
},
]

const handleVote = async (questionIndex, optionIndex) => {
const voteKey = `${questionIndex}-${optionIndex}`

Check warning on line 116 in src/app/[locale]/(interactive)/interactive/voting/page.client.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant voteKey

if (votedQuestions[questionIndex]) {
alert('You have already voted on this question.')
return
}

// Check if the user has already voted on this question
const userHasVoted = votes.documents.some(
(vote) =>
vote.questionId === questionIndex && vote.ipAddress === forwardedFor
)

if (userHasVoted) {
alert('You have already voted on this question.')
return
}

// Update the state immediately
setVotedQuestions((prevState) => {
return { ...prevState, [selectedQuestionIndex]: optionIndex }
})

// Update the selected option immediately
setSelectedOptionIndex(optionIndex)

// Then send the vote to the server
const vote = await createVote(selectedQuestionIndex, optionIndex)

Check warning on line 143 in src/app/[locale]/(interactive)/interactive/voting/page.client.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant vote
}

console.log(selectedOptionIndex)

//console.log(votedQuestions)

return (
<section className="w-full py-12 md:py-24 lg:py-32">
<div className="container grid gap-8 px-4 md:px-6">
{questions[selectedQuestionIndex].question && (
<h2 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl">
{questions[selectedQuestionIndex].question}
</h2>
)}
{questions[selectedQuestionIndex].options.map((option, optionIndex) => (
<button
key={option}
onClick={() => handleVote(selectedQuestionIndex, optionIndex)}
className={
selectedOptionIndex === optionIndex &&
votedQuestions[selectedQuestionIndex] === optionIndex
? 'bg-gray-500'
: ''
}
>
<div className="rounded-lg border p-4 shadow-sm">
<div className="space-y-2">
<div className={'flex justify-between'}>
<p className="text-lg font-semibold">{option}</p>
</div>
</div>
</div>
</button>
))}
</div>
</section>
)
}
23 changes: 23 additions & 0 deletions src/app/[locale]/(interactive)/interactive/voting/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import PageLayout from '@/components/pageLayout'
import VotingClient from '@/app/[locale]/(interactive)/interactive/voting/page.client'
import { getVotes } from '@/utils/server-api/interactive/votes/getVotes'
import { headers } from 'next/headers'
import { getQuestionId } from '@/utils/server-api/interactive/votes/getQuestionId'

export const runtime = 'edge'

export default async function VotingPage() {
const questionId = await getQuestionId()
const votes = await getVotes()
const forwardedFor = headers().get('x-forwarded-for')

return (
<PageLayout title={'Voting'}>
<VotingClient
questionId={questionId}
votes={votes}
forwardedFor={forwardedFor}
/>
</PageLayout>
)
}
9 changes: 9 additions & 0 deletions src/app/[locale]/(interactive)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Header from '@/components/header/header-server'

export default function LocaleLayout({ children, params: { locale } }) {
return (
<div>
<Header locale={locale}>{children}</Header>
</div>
)
}
15 changes: 15 additions & 0 deletions src/utils/actions/interactive/createVote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use server'
import { createAdminClient } from '@/app/appwrite-session'
import { ID } from 'node-appwrite'
import { headers } from 'next/headers'

export async function createVote(questionId: number, optionId: number) {
const forwardedFor = headers().get('x-forwarded-for')

const { databases } = await createAdminClient()
return await databases.createDocument('interactive', 'answers', ID.unique(), {
questionId: `${questionId}`,
optionId: `${optionId}`,
ipAddress: forwardedFor || null,
})
}
18 changes: 18 additions & 0 deletions src/utils/server-api/interactive/votes/getQuestionId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createAdminClient } from '@/app/appwrite-session'
import { Interactive } from '@/utils/types/models'

/**
* This function is used to get the answers of the votes of Lighthase's EF Panel.
* @returns The question ID.
* @example
* const questionId = await getQuestionId()
*/
export async function getQuestionId(): Promise<number> {
const { databases } = await createAdminClient()
const data: Interactive.VotesQuestionId = await databases
.getDocument('interactive', 'questions', 'main')
.catch((error) => {
return error
})
return data.questionId
}
17 changes: 17 additions & 0 deletions src/utils/server-api/interactive/votes/getVotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createAdminClient } from '@/app/appwrite-session'
import { Interactive } from '@/utils/types/models'

/**
* This function is used to get the answers of the votes of Lighthase's EF Panel.
* @returns The answers from the votes.
* @example
* const votes = await getVotes()
*/
export async function getVotes(): Promise<Interactive.VotesAnswersType> {
const { databases } = await createAdminClient()
return await databases
.listDocuments('interactive', 'answers')
.catch((error) => {
return error
})
}
29 changes: 29 additions & 0 deletions src/utils/types/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,32 @@ export namespace Gallery {
mimeType: string
}
}

export namespace Interactive {
/**
* This data is returned from the API by calling the interactive endpoint.
* @see InteractiveDocumentsType
* @interface
* @since 2.0.0
*/
export interface VotesAnswersType {
total: number
documents: VotesAnswersDocumentsType[]
}

/**
* This data is returned from the API within the `documents` array.
* @see InteractiveType
* @interface
* @since 2.0.0
*/
export interface VotesAnswersDocumentsType extends Models.Document {
ipAddress: string | null
questionId: number
optionId: number
}

export interface VotesQuestionId extends Models.Document {
questionId: number
}
}

0 comments on commit 46c974a

Please sign in to comment.