From f2729973fc908ddee8121db8869d721df8ca0a64 Mon Sep 17 00:00:00 2001 From: Csillag Kristof Date: Fri, 11 Oct 2024 23:59:34 +0200 Subject: [PATCH] Centralize slug handling - 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 --- frontend/src/App.tsx | 2 +- frontend/src/components/PollCard/index.tsx | 6 ++---- .../pages/CreatePollPage/useCreatePollForm.ts | 5 ++--- .../src/pages/DashboardPage/useDashboardData.ts | 6 +++--- frontend/src/pages/LandingPage/index.tsx | 3 ++- frontend/src/pages/PollPage/index.tsx | 8 ++++---- frontend/src/utils/path.utils.ts | 16 ++++++++++++++++ frontend/src/utils/slug.ts | 6 +++--- 8 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 frontend/src/utils/path.utils.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fcd258b..38731fa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,7 +25,7 @@ const router = createHashRouter([ element: , }, { - path: ':pollId', + path: ':slug', element: , }, ], diff --git a/frontend/src/components/PollCard/index.tsx b/frontend/src/components/PollCard/index.tsx index 35229cc..501b039 100644 --- a/frontend/src/components/PollCard/index.tsx +++ b/frontend/src/components/PollCard/index.tsx @@ -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 }) => ( - +
{error && ( diff --git a/frontend/src/pages/CreatePollPage/useCreatePollForm.ts b/frontend/src/pages/CreatePollPage/useCreatePollForm.ts index c0e6622..1254182 100644 --- a/frontend/src/pages/CreatePollPage/useCreatePollForm.ts +++ b/frontend/src/pages/CreatePollPage/useCreatePollForm.ts @@ -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 = { @@ -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}` diff --git a/frontend/src/pages/DashboardPage/useDashboardData.ts b/frontend/src/pages/DashboardPage/useDashboardData.ts index 1bbc953..0975032 100644 --- a/frontend/src/pages/DashboardPage/useDashboardData.ts +++ b/frontend/src/pages/DashboardPage/useDashboardData.ts @@ -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 @@ -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)) }, }) diff --git a/frontend/src/pages/LandingPage/index.tsx b/frontend/src/pages/LandingPage/index.tsx index de9319e..6d9561c 100644 --- a/frontend/src/pages/LandingPage/index.tsx +++ b/frontend/src/pages/LandingPage/index.tsx @@ -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 ( diff --git a/frontend/src/pages/PollPage/index.tsx b/frontend/src/pages/PollPage/index.tsx index 333338e..63e1ac7 100644 --- a/frontend/src/pages/PollPage/index.tsx +++ b/frontend/src/pages/PollPage/index.tsx @@ -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 ( @@ -83,8 +83,8 @@ export const PollUI: FC = 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) { @@ -96,7 +96,7 @@ export const PollPage: FC = () => { {title} , - + {description && ( <> diff --git a/frontend/src/utils/path.utils.ts b/frontend/src/utils/path.utils.ts new file mode 100644 index 0000000..53eb7fb --- /dev/null +++ b/frontend/src/utils/path.utils.ts @@ -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) +} diff --git a/frontend/src/utils/slug.ts b/frontend/src/utils/slug.ts index 9719840..b6cd41b 100644 --- a/frontend/src/utils/slug.ts +++ b/frontend/src/utils/slug.ts @@ -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' @@ -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)) }