Skip to content

Commit

Permalink
Centralize slug handling
Browse files Browse the repository at this point in the history
- Add utility function for generating poll path from poll ID,
  and another one from getting poll ID from url params,
  abstracting away all the meddling with slug.
- proposalIdToSlug: accept both the pure and the 0x hex form of id
  • Loading branch information
csillag committed Oct 11, 2024
1 parent 19bbd3b commit f272997
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 19 deletions.
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const router = createHashRouter([
element: <CreatePollPage />,
},
{
path: ':pollId',
path: ':slug',
element: <PollPage />,
},
],
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/components/PollCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { AnimatePresence } from 'framer-motion'
import { Button } from '../Button'
import { MotionDiv } from '../Animations'
import { MaybeWithTooltip } from '../Tooltip/MaybeWithTooltip'
import { proposalIdToSlug } from '../../utils/slug'
import { getPollPath } from '../../utils/path.utils'

const Arrow: FC<{ className: string }> = ({ className }) => (
<svg
Expand Down Expand Up @@ -94,8 +94,6 @@ export const PollCard: FC<{
onDashboard: true,
})

const slug = proposalIdToSlug(proposalId!);

const {
id: pollId,
proposal: { params },
Expand Down Expand Up @@ -175,7 +173,7 @@ export const PollCard: FC<{
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.5 }}
>
<Link to={`/${slug}`} style={{ textDecoration: 'none' }}>
<Link to={getPollPath(pollId!)} style={{ textDecoration: 'none' }}>
<div className={classes.pollCard}>
<div className={classes.pollCardTop}>
{error && (
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/pages/CreatePollPage/useCreatePollForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { designDecisions, MIN_COMPLETION_TIME_MINUTES, nativeTokenName } from '.

import { useNavigate } from 'react-router-dom'
import { acls } from '../../components/ACLs'
import { proposalIdToSlug } from '../../utils/slug'
import { getPollPath } from '../../utils/path.utils'

// The steps / pages of the wizard
const StepTitles = {
Expand Down Expand Up @@ -356,8 +356,7 @@ export const useCreatePollForm = () => {
const newId = await doCreatePoll(daoSigner, eth.state.address, pollProps, logger)

if (newId) {
const slug = proposalIdToSlug(newId);
navigate(`/${slug}`)
navigate(getPollPath(newId))
}
} catch (ex) {
let exString = `${ex}`
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/DashboardPage/useDashboardData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useBooleanField, useOneOfField, useTextField } from '../../components/I
import { useNavigate } from 'react-router-dom'
import { dashboard, designDecisions } from '../../constants/config'
import classes from './index.module.css'
import { proposalIdToSlug } from '../../utils/slug'
import { getPollPath } from '../../utils/path.utils'

const FETCH_BATCH_SIZE = 100

Expand Down Expand Up @@ -241,8 +241,8 @@ export const useDashboardData = () => {
onEnter: () => {
const cards = allVisiblePollIds
if (cards.length !== 1) return // We can only do this is there is exactly one matching card
const slug = proposalIdToSlug(Array.from(cards.values())[0]);
navigate(`/${slug}`)
const pollId = Array.from(cards.values())[0]
navigate(getPollPath(pollId))
},
})

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/LandingPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { Button, ButtonSize } from '../../components/Button'
import { useNavigate } from 'react-router-dom'
import { useAppState } from '../../hooks/useAppState'
import { appName } from '../../constants/config'
import { getPollPath } from '../../utils/path.utils'

export const LandingPage: FC = () => {
const navigate = useNavigate()
const {
state: { isMobileScreen },
} = useAppState()
const openDemo = useCallback(() => navigate('/demo'), [])
const openDemo = useCallback(() => navigate(getPollPath('demo')), [])
const buttonSize: ButtonSize = isMobileScreen ? 'small' : 'medium'

return (
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/PollPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ActivePoll } from './ActivePoll'
import { ThanksForVote } from './ThanksForVoting'
import { Helmet } from 'react-helmet-async'
import { appName, appNameAndTagline, appRootUrl } from '../../constants/config'
import { slugToProposalId } from '../../utils/slug'
import { getPollIdFromRouter, getPollPath } from '../../utils/path.utils'

const PollLoading: FC = () => {
return (
Expand Down Expand Up @@ -83,8 +83,8 @@ export const PollUI: FC<PollData> = props => {
}

export const PollPage: FC = () => {
const { pollId: slug } = useParams()
const pollData = usePollData(slugToProposalId(slug!))
const pollId = getPollIdFromRouter(useParams())
const pollData = usePollData(pollId)
const { poll } = pollData
const params = poll?.ipfsParams
if (params) {
Expand All @@ -96,7 +96,7 @@ export const PollPage: FC = () => {
<title>{title}</title>
<meta name="twitter:title" content={title} />
<meta property="og:title" content={title} />,
<meta property="og:url" content={`${appRootUrl}/#${slug}`} />
<meta property="og:url" content={`${appRootUrl}/#${getPollPath(pollId)}`} />
{description && (
<>
<meta name="twitter:description" content={description} />
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/utils/path.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { proposalIdToSlug, slugToProposalId } from './slug'
import { Params } from 'react-router-dom'

/**
* Get the poll path from a poll or proposal id.
*/
export const getPollPath = (pollId: string): string => `/${proposalIdToSlug(pollId!)}`

/**
* Get the poll ID from the parameters found in the URL, coming from the router.
*/
export const getPollIdFromRouter = (params: Params): string => {
const { slug } = params
if (!slug) throw new Error("Slug should be among router parameters, but it isn't!")
return slugToProposalId(slug)
}
6 changes: 3 additions & 3 deletions frontend/src/utils/slug.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BytesLike, getBytes, hexlify } from 'ethers'
import { getBytes, hexlify } from 'ethers'

// https://en.wikipedia.org/wiki/Base32#z-base-32
const BASE32_ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
Expand Down Expand Up @@ -65,8 +65,8 @@ function removeTrailingZeros(arr: Uint8Array): Uint8Array {
return arr.slice(0, lastNonZeroIndex + 1)
}

export function proposalIdToSlug(proposalId: BytesLike) {
const bytes = removeTrailingZeros(getBytes(proposalId))
export function proposalIdToSlug(proposalId: string) {
const bytes = removeTrailingZeros(getBytes(proposalId.startsWith('0x') ? proposalId : `0x${proposalId}`))
return base32Encode(ensureMinLength(bytes, SLUG_BYTES))
}

Expand Down

0 comments on commit f272997

Please sign in to comment.