Skip to content

Commit

Permalink
Merge pull request #291 from Vandivier/287-prestige-subpage
Browse files Browse the repository at this point in the history
feat: honor type pages
  • Loading branch information
Vandivier authored Aug 14, 2024
2 parents 96db165 + dab239d commit 3814eba
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 108 deletions.
28 changes: 28 additions & 0 deletions src/app/top-honors/[type]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// File: app/top-honors/[type]/page.tsx

import { Suspense } from 'react'
import { LadderlyPageWrapper } from 'src/core/components/page-wrapper/LadderlyPageWrapper'
import { isValidVotableType, slugToType } from 'src/votable/utils'
import TopHonorsContent from '../components/TopHonorsContent'

export default function TopHonorsPageWithTypeParam({
params,
}: {
params: { type: string }
}) {
const votableType = slugToType(params.type)

return (
<LadderlyPageWrapper title="Top Honors">
<main className="m-4 flex flex-col items-center justify-center">
{isValidVotableType(votableType) ? (
<Suspense fallback={<div>Loading...</div>}>
<TopHonorsContent initialType={votableType} />
</Suspense>
) : (
<p>Invalid votable type</p>
)}
</main>
</LadderlyPageWrapper>
)
}
72 changes: 72 additions & 0 deletions src/app/top-honors/components/TopHonorsContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client'

import { useQuery } from '@blitzjs/rpc'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React, { useState } from 'react'
import { VotableTypeSelector } from '../../../votable/VotableTypeSelector'
import getVotableLeaders from '../queries/getVotableLeaders'
import { typeToSlug } from 'src/votable/utils'

const toTitleCase = (str) =>
str
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')

const VotableLeaders = ({ votableType }) => {
const [leaders] = useQuery(getVotableLeaders, { type: votableType })

return (
<div>
<h2 className="mb-4 text-center text-2xl font-bold">
Top {`${toTitleCase(votableType)}`} Honors
</h2>
<p className="my-4 max-w-[400px] text-sm">
These are the top 10 {`${toTitleCase(votableType)}`} leaders based on
their prestige score and registered user votes. Ties are sorted by name.
</p>
<Link
href={`/vote/${typeToSlug(votableType)}`}
className="text-blue-500 underline"
>
Click here to vote!
</Link>
<ul className="mt-4">
{leaders.map((leader) => (
<li key={leader.id}>
<span className="font-bold">{leader.name}</span> -{' '}
{leader.prestigeScore} points ({leader.registeredUserVotes}{' '}
registered votes)
</li>
))}
</ul>
</div>
)
}

export default function TopHonorsContent({
initialType,
}: {
initialType?: string
}) {
const router = useRouter()
const [votableType, setVotableType] = useState(initialType || 'COMPANY')

const handleVotableTypeChange = (newType) => {
setVotableType(newType)
router.push(`/top-honors/${typeToSlug(newType)}`)
}

return (
<>
<div className="m-auto my-4">
<VotableTypeSelector
value={votableType}
onChange={handleVotableTypeChange}
/>
</div>
<VotableLeaders votableType={votableType} />
</>
)
}
46 changes: 4 additions & 42 deletions src/app/top-honors/page.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,15 @@
'use client'
// File: app/top-honors/page.tsx

import { useQuery } from '@blitzjs/rpc'
import React, { Suspense, useState } from 'react'
import { Suspense } from 'react'
import { LadderlyPageWrapper } from 'src/core/components/page-wrapper/LadderlyPageWrapper'
import { VotableTypeSelector } from 'src/votable/VotableTypeSelector'
import getVotableLeaders from './queries/getVotableLeaders'

const toTitleCase = (str) =>
str
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')

const VotableLeaders = ({ type }) => {
const [leaders] = useQuery(getVotableLeaders, { type })

return (
<div>
<h2 className="mb-4 text-center text-2xl font-bold">
Top {`${toTitleCase(type)}`} Honors
</h2>
<p className="my-4 max-w-[400px] text-sm">
These are the top 10 {`${toTitleCase(type)}`} leaders based on their
prestige score and registered user votes. Ties are sorted by name.
</p>
<ul>
{leaders.map((leader) => (
<li key={leader.id}>
<span className="font-bold">{leader.name}</span> -{' '}
{leader.prestigeScore} points ({leader.registeredUserVotes}{' '}
registered votes)
</li>
))}
</ul>
</div>
)
}
import TopHonorsContent from './components/TopHonorsContent'

export default function TopHonorsPage() {
const [votableType, setVotableType] = useState('COMPANY')

return (
<LadderlyPageWrapper title="Top Honors">
<main className="m-4 flex flex-col items-center justify-center">
<div className="m-auto my-4">
<VotableTypeSelector value={votableType} onChange={setVotableType} />
</div>
<Suspense fallback={<div>Loading...</div>}>
<VotableLeaders type={votableType} />
<TopHonorsContent />
</Suspense>
</main>
</LadderlyPageWrapper>
Expand Down
26 changes: 26 additions & 0 deletions src/app/vote/[type]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// File: app/vote/[type]/page.tsx

import { Suspense } from 'react'
import { LadderlyPageWrapper } from 'src/core/components/page-wrapper/LadderlyPageWrapper'
import { isValidVotableType, slugToType } from 'src/votable/utils'
import VotePageContent from '../components/VotePageContent'

export default function VotePageWithTypeParam({
params,
}: {
params: { type: string }
}) {
const votableType = slugToType(params.type)

return (
<LadderlyPageWrapper title="Vote">
{isValidVotableType(votableType) ? (
<Suspense fallback={<div>Loading...</div>}>
<VotePageContent initialType={votableType} />
</Suspense>
) : (
<p>Invalid votable type</p>
)}
</LadderlyPageWrapper>
)
}
88 changes: 88 additions & 0 deletions src/app/vote/components/VotePageContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client'

import { useMutation, useQuery } from '@blitzjs/rpc'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React, { useState } from 'react'
import { VotableTypeSelector } from 'src/votable/VotableTypeSelector'
import createVote from '../mutations/createVote'
import getRandomVotablePair from '../queries/getRandomVotablePair'
import { typeToSlug } from 'src/votable/utils'

const VotePageContent = ({ initialType }: { initialType?: string }) => {
const router = useRouter()
const [votableType, setVotableType] = useState(initialType || 'COMPANY')
const [votablePair, { refetch }] = useQuery(getRandomVotablePair, {
type: votableType,
})
const [castVote] = useMutation(createVote)

const votableA = votablePair?.[0]
const votableB = votablePair?.[1]

const handleVote = async (winnerId: number) => {
if (votableA && votableB) {
try {
await castVote({
winnerId,
votableAId: votableA.id,
votableBId: votableB.id,
})
await refetch()
} catch (error) {
console.error('Error casting vote:', error)
}
} else {
console.error('Invalid votable pair.')
}
}

const handleVotableTypeChange = (newType) => {
setVotableType(newType)
router.push(`/vote/${typeToSlug(newType)}`)
}

if (!votableA || !votableB) {
return <p>Loading votable items...</p>
}

return (
<div className="flex flex-col items-center justify-center">
<h1 className="m-4 text-2xl font-bold">
Vote for the more prestigious option!
</h1>

<VotableTypeSelector
value={votableType}
onChange={handleVotableTypeChange}
/>

<Link
href={`/top-honors/${typeToSlug(votableType)}`}
className="my-4 text-blue-500 underline"
>
View current leaders here!
</Link>

<div className="flex space-x-4">
<button
onClick={() => handleVote(votableA.id)}
className="rounded border bg-blue-500 p-4 text-white"
>
{votableA.name}
</button>
<button
onClick={() => handleVote(votableB.id)}
className="rounded border bg-blue-500 p-4 text-white"
>
{votableB.name}
</button>
</div>
<button onClick={() => refetch()} className="m-4 text-blue-500 underline">
Skip
</button>
</div>
)
}

export default VotePageContent
69 changes: 3 additions & 66 deletions src/app/vote/page.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,8 @@
'use client'
// File: app/vote/page.tsx

import { useMutation, useQuery } from '@blitzjs/rpc'
import React, { Suspense, useState } from 'react'
import { Suspense } from 'react'
import { LadderlyPageWrapper } from 'src/core/components/page-wrapper/LadderlyPageWrapper'
import { VotableTypeSelector } from 'src/votable/VotableTypeSelector'
import createVote from './mutations/createVote'
import getRandomVotablePair from './queries/getRandomVotablePair'

const VotePageContent = () => {
const [votableType, setVotableType] = useState('COMPANY')
const [votablePair, { refetch }] = useQuery(getRandomVotablePair, {
type: votableType,
})
const [castVote] = useMutation(createVote)

const votableA = votablePair?.[0]
const votableB = votablePair?.[1]

const handleVote = async (winnerId: number) => {
if (votableA && votableB) {
try {
await castVote({
winnerId,
votableAId: votableA.id,
votableBId: votableB.id,
})
await refetch()
} catch (error) {
console.error('Error casting vote:', error)
}
} else {
console.error('Invalid votable pair.')
}
}

if (!votableA || !votableB) {
return <p>Loading votable items...</p>
}

return (
<div className="flex flex-col items-center justify-center">
<h1 className="m-4 text-2xl font-bold">
Vote for the more prestigious option!
</h1>

<VotableTypeSelector value={votableType} onChange={setVotableType} />

<div className="flex space-x-4">
<button
onClick={() => handleVote(votableA.id)}
className="rounded border bg-blue-500 p-4 text-white"
>
{votableA.name}
</button>
<button
onClick={() => handleVote(votableB.id)}
className="rounded border bg-blue-500 p-4 text-white"
>
{votableB.name}
</button>
</div>
<button onClick={() => refetch()} className="m-4 text-blue-500 underline">
Skip
</button>
</div>
)
}
import VotePageContent from './components/VotePageContent'

export default function VotePage() {
return (
Expand Down
13 changes: 13 additions & 0 deletions src/votable/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { VotableType } from 'db'

export function slugToType(slug: string): string {
return slug.toUpperCase().replace(/-/g, '_')
}

export function typeToSlug(type: string): string {
return type.toLowerCase().replace(/_/g, '-')
}

export function isValidVotableType(type: string): boolean {
return Object.values(VotableType).includes(type as VotableType)
}

0 comments on commit 3814eba

Please sign in to comment.