-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #291 from Vandivier/287-prestige-subpage
feat: honor type pages
- Loading branch information
Showing
7 changed files
with
234 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |