-
Notifications
You must be signed in to change notification settings - Fork 0
feat: integrate API into poll creation form & poll details #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
""" WalkthroughThis update refactors the wallet and World ID verification logic by splitting the original combined function into two distinct functions: one to generate and set a secure nonce cookie ( Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PollForm
participant usePollForm
participant Validation (zod)
participant API
User->>PollForm: Fill form & submit
PollForm->>usePollForm: handlePublish()
usePollForm->>Validation (zod): Validate form data
alt Validation passes
usePollForm->>API: createPoll (with form data)
API-->>usePollForm: Poll created (ID, data)
usePollForm-->>PollForm: Show PollCreatedModal
else Validation fails
Validation (zod)-->>usePollForm: Errors
usePollForm-->>PollForm: Display error messages
end
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
context/AuthContext.tsx (1)
62-68
:⚠️ Potential issue
storeToken
never updatesworldID
After login, the JWT is saved but
worldID
remainsnull
, so forms default to an empty string.const storeToken = (token: string) => { setTokenFromLib(token); setToken(token); + const retrievedWorldID = getWorldID(); + setWorldID(retrievedWorldID); router.push("/"); scheduleAutoLogout(token, logout); };Without this, any component relying on
worldID
(e.g. poll creation) will send an empty field until a page refresh.
♻️ Duplicate comments (1)
hooks/usePoll.ts (1)
14-22
:worldID
missing fromICreatePollData
The poll form submits
worldID
; typings should reflect that to avoid excessany
casting (see hook comment above).
🧹 Nitpick comments (5)
validation/pollSchemas.ts (1)
3-47
: Comprehensive validation schema with some opportunities for improvementThe validation schema is well-structured with appropriate constraints and helpful error messages for each field. The character limits and array size validations are particularly useful.
Consider these improvements to the validation schema:
- Add better validation for date fields:
- startDate: z.string().nonempty("Start date is required"), - endDate: z.string().nonempty("End date is required"), + startDate: z.string() + .nonempty("Start date is required") + .refine(val => !isNaN(Date.parse(val)), "Invalid date format"), + endDate: z.string() + .nonempty("End date is required") + .refine(val => !isNaN(Date.parse(val)), "Invalid date format") + .refine( + (val, ctx) => !ctx.data.startDate || new Date(val) > new Date(ctx.data.startDate), + "End date must be after start date" + ),
- Add validation for unique options and tags:
tags: z .array(z.string().nonempty("Tag cannot be empty")) + .refine( + (tags) => new Set(tags).size === tags.length, + "Tags must be unique" + ) .refine( (val) => val.length <= 5, (val) => ({ message: `${val.length}/5 Max tag limit reached` }) )
- Add maximum length validation for individual tags:
- .array(z.string().nonempty("Tag cannot be empty")) + .array( + z.string() + .nonempty("Tag cannot be empty") + .refine( + (val) => val.length <= 20, + (val) => ({ message: `${val.length}/20 Max character limit reached` }) + ) + )context/AuthContext.tsx (1)
48-54
: Variable shadowing hides state setter & may confuse readersconst worldID = getWorldID(); if (worldID) setWorldID(worldID);The local
worldID
constant shadows the outer state variable of the same name. While it works, it hurts readability and makes accidental misuse likely.-const worldID = getWorldID(); -if (worldID) setWorldID(worldID); +const retrievedWorldID = getWorldID(); +if (retrievedWorldID) setWorldID(retrievedWorldID);hooks/usePoll.ts (2)
45-46
: Left‑overconsole.log
pollutes production bundleDebug statements leak PII and hurt performance. Please remove.
- console.log(data);
63-71
: No authentication header when creating a poll
/poll
likely requires an auth token. Fetching withoutAuthorization
will fail for authenticated routes once server protection is enabled.headers: { "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, },
Make sure
getToken
is imported from@/lib/auth
.components/Poll/PollForm.tsx (1)
53-54
: Stray console logRemove or guard with
process.env.NODE_ENV !== "production"
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (12)
app/actions/verify.ts
(2 hunks)components/Login/SplashScreen.tsx
(1 hunks)components/Modals/PollCreatedModal.tsx
(2 hunks)components/Poll/PollForm.tsx
(6 hunks)components/ui/Modal.tsx
(2 hunks)context/AuthContext.tsx
(3 hunks)hooks/usePoll.ts
(4 hunks)hooks/usePollForm.ts
(1 hunks)lib/auth.ts
(1 hunks)package.json
(2 hunks)utils/time.ts
(1 hunks)validation/pollSchemas.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
components/Modals/PollCreatedModal.tsx (1)
utils/share.ts (1)
handleShare
(1-18)
context/AuthContext.tsx (1)
lib/auth.ts (2)
getToken
(15-19)getWorldID
(48-53)
hooks/usePoll.ts (1)
types/poll.ts (1)
IPoll
(1-14)
🔇 Additional comments (9)
package.json (1)
12-25
: Well-chosen form handling and validation librariesThe addition of react-hook-form, zod, and their resolver is appropriate for implementing robust form management and validation in the poll creation form.
utils/time.ts (2)
41-47
: LGTM: Clean date formatting implementationThe new
formatShortDate
function has proper null checking and uses appropriate locale formatting.
56-61
: Consider timezone handling in date-time combinationThe
combineDateTime
function works correctly but be aware it uses the local timezone when setting hours and minutes.If this application needs to handle specific timezones or UTC, this function might need additional timezone handling capabilities. Verify whether the current implementation aligns with your timezone requirements.
components/ui/Modal.tsx (1)
16-27
: Excellent focus management implementationThe focus trap logic enhances accessibility by focusing the first interactive element when the modal opens, which is a best practice for keyboard navigation.
components/Login/SplashScreen.tsx (1)
36-36
: Improved logical operator precedenceThe addition of parentheses around
(isLoggingIn || isLoggedIn)
clarifies the intended logic. This prevents potential operator precedence issues, as&&
has higher precedence than||
in JavaScript/TypeScript.components/Modals/PollCreatedModal.tsx (1)
12-12
: Type update correctly handles potential undefined stateUpdating the type to include
undefined
better reflects the component's usage pattern, especially during poll creation when the poll ID might not be available immediately.app/actions/verify.ts (2)
6-7
: Good environment variable validationAdding a check for the required environment variable before making the API request is a good defensive programming practice. This will prevent cryptic errors and provide clearer feedback when configuration is missing.
31-34
: Improved error handling for API responsesChecking for successful response status before attempting to parse the JSON prevents potential runtime errors. The detailed error message including status code and text will be helpful for debugging.
components/Poll/PollForm.tsx (1)
195-196
: FormonSubmit
is effectively a no‑opCurrently:
<form onSubmit={form.handleSubmit((data) => {})}>Pressing Enter triggers validation but never publishes. Either wire it to
handlePublish
or rely solely on the button and prevent default.-<form onSubmit={form.handleSubmit((data) => {})}> +<form onSubmit={form.handleSubmit(handlePublish)}>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🔭 Outside diff range comments (1)
hooks/useWorldAuth.ts (1)
64-94
:⚠️ Potential issue
AbortSignal.timeout
is Node‑only – causesTypeError
in browsersThe WHATWG fetch spec doesn’t include
AbortSignal.timeout
; only Node ≥20 implements it.
In Chrome/Firefox/Edge this will throw at runtime and the request will never fire.- signal: AbortSignal.timeout(15000), + // Abort after 15 s in all environments + signal: (() => { + const controller = new AbortController(); + setTimeout(() => controller.abort(), 15_000); + return controller.signal; + })(),
🧹 Nitpick comments (9)
app/actions/verify.ts (3)
10-15
: Add explicitpath
and environment–awaresecure
options for better cookie hygiene
path
defaults to/
in most runtimes, but being explicit avoids surprises (and makes intent clearer to reviewers).
Likewise, settingsecure: true
in development over HTTP will silently drop the cookie in most browsers – gating it behindprocess.env.NODE_ENV === "production"
makes local testing easier.- cookies().set("siwe_nonce", nonce, { - secure: true, + cookies().set("siwe_nonce", nonce, { + secure: process.env.NODE_ENV === "production", httpOnly: true, sameSite: "strict", + path: "/", maxAge: 600, });
17-21
: Normalise the success/error payload for predictable callers
getNonce
returns{ nonce?, success, error? }
, whileverifyNonceCookie
returns{ isValid, error? }
. Consistency eases consumption and type‑safety.
Consider returning{ success: boolean, data?: T, error?: string }
from both helpers (mirroringAuthResult
).
38-42
: Surface asuccess
field for parity withgetNonce
Returning
{ isValid }
only forces downstream callers to branch on two different field names.
Adding asuccess
boolean (alias ofisValid
) keeps the API symmetrical and self‑documenting.hooks/useWorldAuth.ts (2)
120-128
: Handle missingtoken
defensively
storeToken(data.token)
will throw if the backend ever omitstoken
(e.g. on partial failure).
Add a guard to propagate a meaningful error instead of crashing React.- storeToken(data.token); + if (!data?.token) { + setError(AUTH_ERRORS.SERVER_VERIFICATION_FAILED); + return { success: false, error: AUTH_ERRORS.SERVER_VERIFICATION_FAILED }; + } + storeToken(data.token);
148-197
:handleLogin
swallows granular errors – bubble them up for UX clarityInside each sub‑step you overwrite the specific error with a generic one (
WORLD_ID_VERIFICATION_FAILED
,WALLET_AUTH_ERROR
, etc.).
Surfacing the underlyingerr.message
(when it matches a knownAUTH_ERRORS
value) would enable the UI to present more actionable feedback.types/poll.ts (2)
16-20
: Interfaces look solid – but consider tightening the author relation
IPollDetails
embeds the fullIAuthor
object insidepoll
. This is convenient, but it also duplicates the fields that already exist at the top level ofIPoll
. If you know for sure that the backend will always embed the author object, you may want to omitauthorUserId
fromIPoll
(or mark it optional) to avoid two competing sources of truth. Otherwise keep both but document the precedence to avoid confusion for consumers.
34-41
: Nit: keep type names consistent with existing conventionsElsewhere in the codebase user‑related types appear to use
UserId
,userId
, etc. StoringworldID
inIAuthor
deviates from that pattern (ID
capitalised vs.Id
). A minor rename toworldId
will make autocomplete and mental grepping easier.components/Poll/PollVoteCard.tsx (2)
292-299
: Accessibility: the drag‑handle is not keyboard reachableThe draggable handle (
SlidingIcon
) can only be adjusted with a mouse/touch.
Provide keyboard equivalents (arrow keys or “+ / –” already exist) and addrole="slider"
+aria-valuemin/max/now
so screen‑reader users understand the control.
330-336
: Use optional chaining to silence runtime errors (Biome hint)
vote.count.toLocaleString()
will throw while the initial state is being computed becausevote.count
isundefined
.
After fixing the initialisation above, this goes away, but adding an optional chain keeps the UI resilient:-{vote.count.toLocaleString()}{" "} +{vote.count?.toLocaleString() ?? 0}{" "}🧰 Tools
🪛 Biome (1.9.4)
[error] 264-336: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
app/actions/verify.ts
(1 hunks)app/poll/[id]/page.tsx
(1 hunks)components/Poll/PollVoteCard.tsx
(7 hunks)components/icon-components/UserIcon.tsx
(1 hunks)hooks/usePoll.ts
(5 hunks)hooks/useUser.ts
(1 hunks)hooks/useWorldAuth.ts
(2 hunks)lib/constants/authErrors.ts
(1 hunks)types/poll.ts
(2 hunks)utils/time.ts
(2 hunks)
✅ Files skipped from review due to trivial changes (2)
- components/icon-components/UserIcon.tsx
- lib/constants/authErrors.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- utils/time.ts
- hooks/usePoll.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
hooks/useWorldAuth.ts (3)
context/AuthContext.tsx (1)
useAuth
(92-92)lib/constants/authErrors.ts (1)
AUTH_ERRORS
(19-36)app/actions/verify.ts (2)
verifyNonceCookie
(24-43)getNonce
(6-22)
components/Poll/PollVoteCard.tsx (6)
hooks/usePoll.ts (1)
usePoll
(29-101)hooks/useUser.ts (1)
useUser
(58-186)utils/time.ts (1)
getRelativeTimeString
(3-40)components/icon-components/SlidingIcon.tsx (1)
SlidingIcon
(3-20)components/icon-components/MinusRoundIcon.tsx (1)
MinusRoundIcon
(3-24)components/icon-components/PlusRoundIcon.tsx (1)
PlusRoundIcon
(3-24)
🪛 ESLint
hooks/useUser.ts
[error] 62-62: React Hook "useQuery" is called in function "getUserData" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 83-83: React Hook "useQuery" is called in function "getUserActivities" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 103-103: React Hook "useQuery" is called in function "getUserVotes" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 135-135: React Hook "useMutation" is called in function "setVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 160-160: React Hook "useMutation" is called in function "editVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
🪛 Biome (1.9.4)
components/Poll/PollVoteCard.tsx
[error] 264-336: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (1)
hooks/useWorldAuth.ts (1)
1-10
: Client ↔ server mix‑up: importing server actions directly into client hook may break Next.js bundling
getNonce
andverifyNonceCookie
live in a"use server"
file. Importing them into a hook that will run in the browser can trigger “Server Module imported from Client Component” build errors (Next 14).
If the build currently succeeds it’s only because the hook happens not to be consumed by ause client
component yet – a fragile constraint.Would you confirm that no
use client
component imports this hook? If it is imported, wrap the server calls behind a/api/*
route or leverageuseAction
instead of direct import.
coderabbit has suggested some security fixes related to cookies and jwt. @Meriem-BM could please confirm that they are already resolved or not valid? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (5)
components/Poll/PollVoteCard.tsx (3)
340-341
: 🛠️ Refactor suggestionReplace hardcoded voter count with actual data
The voter count is still hardcoded to "10" instead of using data from the API.
<div className="flex items-center gap-x-2"> - <span className="text-gray-900 font-medium">10</span> + <span className="text-gray-900 font-medium">{pollData?.totalVoters || 0}</span> <span className="text-gray-700 text-sm">voters participated</span> </div>Ensure that the
getPollDetails
API response includes the total voter count information.
379-381
: 🛠️ Refactor suggestionReplace hardcoded values in VotingSuccessModal
The VotingSuccessModal is using hardcoded values for the poll title and ID instead of the actual poll data.
<VotingSuccessModal setShowModal={setShowVotingSuccessModal} - pollTitle={"test"} - pollId={1} + pollTitle={pollDetails?.title || ""} + pollId={pollId} />
351-353
: 🛠️ Refactor suggestionUpdate share function with dynamic poll data
The share button is using hardcoded values instead of actual poll data.
<button className="rounded-full h-8 w-8" - onClick={() => handleShare("Test", 1)} + onClick={() => handleShare(pollDetails?.title || "", pollId)} > <ShareIcon /> </button>hooks/usePoll.ts (2)
68-86
:⚠️ Potential issueReact Hook usage violation in regular function.
The
createPoll
function uses React Hooks (useQueryClient
anduseMutation
) but doesn't follow the naming convention for custom hooks.Rename the function to follow React's naming convention:
-export const createPoll = () => { +export const useCreatePoll = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (data: ICreatePollData): Promise<IPoll> => { const res = await fetch("/poll", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) throw new Error("Failed to create poll"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["polls"] }); }, retry: false, }); };🧰 Tools
🪛 ESLint
[error] 69-69: React Hook "useQueryClient" is called in function "createPoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 71-71: React Hook "useMutation" is called in function "createPoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
88-104
:⚠️ Potential issueReact Hook usage violation in regular function.
The
deletePoll
function uses React Hooks (useQueryClient
anduseMutation
) but doesn't follow the naming convention for custom hooks.Rename the function to follow React's naming convention:
-export const deletePoll = () => { +export const useDeletePoll = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id }: { id: number }) => { const res = await fetch(`/poll/${id}`, { method: "DELETE", headers: { "Content-Type": "application/json" }, }); if (!res.ok) throw new Error("Failed to delete poll"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["polls"] }); }, }); };🧰 Tools
🪛 ESLint
[error] 89-89: React Hook "useQueryClient" is called in function "deletePoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 91-91: React Hook "useMutation" is called in function "deletePoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
♻️ Duplicate comments (8)
hooks/useWorldAuth.ts (1)
96-142
: Nonce verification should be kept server-sideThe client-side nonce verification is redundant since:
- The server endpoint
/auth/verifyWorldId
will also need to verify the nonce- Client-side verification adds an unnecessary step in the auth flow
- It introduces additional potential failure points
Consider removing the client-side nonce verification and let the server handle it, which would simplify the flow and reduce unnecessary round-trips.
hooks/useUser.ts (5)
36-46
:⚠️ Potential issueRename function to follow React Hooks convention
The
getUserData
function is using React hooks internally (useQuery
) but doesn't follow the naming convention for custom hooks, which must start with "use". This violates React's Rules of Hooks and will cause ESLint errors.Rename the function to follow React hooks naming convention:
-export const getUserData = (): UseQueryResult<IUser> => { +export const useUserData = (): UseQueryResult<IUser> => { return useQuery({ queryKey: ["user", "data"], queryFn: async () => { const res = await fetch("/user/getUserData"); if (!res.ok) throw new Error("Failed to fetch user data"); return res.json(); }, }); };🧰 Tools
🪛 ESLint
[error] 37-37: React Hook "useQuery" is called in function "getUserData" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
48-73
:⚠️ Potential issueRename function to follow React Hooks convention
Similar to
getUserData
, this function also uses React hooks internally but doesn't follow the naming convention. Functions that use React hooks must be named with the "use" prefix.-export const getUserActivities = ({ +export const useUserActivities = ({ filter, search, }: { filter: "active" | "inactive" | "created" | "participated"; search: string; }): UseQueryResult<{ activities: UserActivity[]; total: number; }> => { // Function implementation... };🧰 Tools
🪛 ESLint
[error] 58-58: React Hook "useQuery" is called in function "getUserActivities" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
75-98
:⚠️ Potential issueRename function to follow React Hooks convention and improve error handling
This function uses React hooks but doesn't follow the naming convention. Additionally, the retry callback doesn't safely handle non-Error objects when checking error messages.
-export const getUserVotes = ( +export const useUserVotes = ( pollId: number ): UseQueryResult<IGetUserVotesResponse> => { return useQuery({ queryKey: ["user", "votes", pollId], queryFn: async () => { const urlParams = new URLSearchParams({ pollId: String(pollId), }); const res = await fetch(`/user/getUserVotes?${urlParams.toString()}`); if (!res.ok) throw new Error("Failed to fetch user votes"); return res.json(); }, staleTime: 0, retry: (failureCount) => { + // Simple retry logic based only on count if (failureCount >= MAX_RETRIES) return false; return true; }, }); };🧰 Tools
🪛 ESLint
[error] 78-78: React Hook "useQuery" is called in function "getUserVotes" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
100-125
:⚠️ Potential issueRename function and fix duplicate query invalidation
This function needs to be renamed to follow React Hooks convention. Also, there's a duplicate call to invalidate the same query on success which is unnecessary.
-export const setVote = () => { +export const useSetVote = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (params: SetVoteParams) => { const res = await fetch("/user/setVote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); if (!res.ok) throw new Error("Failed to set vote"); return res.json(); }, onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: ["poll", variables.pollId], }); - queryClient.invalidateQueries({ - queryKey: ["poll", variables.pollId], - }); + // Also invalidate user votes for consistency + queryClient.invalidateQueries({ + queryKey: ["user", "votes", variables.pollId], + }); }, }); };🧰 Tools
🪛 ESLint
[error] 101-101: React Hook "useQueryClient" is called in function "setVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 103-103: React Hook "useMutation" is called in function "setVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
127-147
:⚠️ Potential issueRename function to follow React Hooks convention
The
editVote
function is using React hooks but doesn't follow the naming convention, which requires a "use" prefix.-export const editVote = () => { +export const useEditVote = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (params: SetVoteParams) => { const res = await fetch("/user/editVote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); if (!res.ok) throw new Error("Failed to edit vote"); return res.json(); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["user", "votes"] }); + queryClient.invalidateQueries({ + queryKey: ["user", "votes", params.pollId] + }); }, }); };Note: The current invalidation will clear all user votes queries regardless of poll ID. Adding the pollId to the invalidation key would be more targeted.
🧰 Tools
🪛 ESLint
[error] 128-128: React Hook "useQueryClient" is called in function "editVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 130-130: React Hook "useMutation" is called in function "editVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
components/Poll/PollVoteCard.tsx (2)
173-177
: Improve UX with feedback when total exceeds 100%Currently, the vote button is disabled when total percentage exceeds 100%, but there's no visual feedback explaining why to the user.
Consider adding a warning message that appears when the total exceeds 100%:
+const totalPercentage = votes?.reduce((acc, vote) => acc + vote.percentage, 0) || 0; +const exceedsLimit = totalPercentage > 100; const voteButtonDisabled = !votes || votes?.length === 0 || votes?.every((vote) => vote.percentage === 0) || - votes?.reduce((acc, vote) => acc + vote.percentage, 0) > 100; + exceedsLimit; +// Then in the JSX, before the Vote button: +{exceedsLimit && ( + <p className="text-red-500 text-sm text-center mb-2"> + Total voting power exceeds 100%. Please adjust your votes. + </p> +)}
117-127
:⚠️ Potential issueFix potential undefined access in poll options mapping
When the component mounts,
pollDetails
might still beundefined
while data is being fetched, which could cause runtime errors when trying to map overpollDetails?.options
.if (!userVotes) { + if (!pollDetails?.options) { + return; + } const defaultVotes = pollDetails?.options.map((option) => ({ option: option, percentage: 0, count: 0, isDragging: false, })); setVotes(defaultVotes); return; }
🧹 Nitpick comments (10)
hooks/useWorldAuth.ts (6)
17-21
: Improve type safety for AuthResultThe
data?: any
type is too permissive and could lead to type-related bugs.export type AuthResult = { success: boolean; - data?: any; + data?: { + token: string; + [key: string]: unknown; + }; error?: string; };
112-118
: Add timeout protection to server verification requestThe verification API call is missing timeout protection unlike the World ID proof verification which has a 15-second timeout.
const res = await fetch("/auth/verifyWorldId", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ walletPayload, worldIdProof, nonce }), + signal: AbortSignal.timeout(15000), });
135-141
: Preserve original error structure for debuggingThe current implementation converts all errors to strings, losing valuable debugging information.
} catch (err) { setError(AUTH_ERRORS.PAYLOAD_VERIFICATION_ERROR); return { success: false, - error: err instanceof Error ? err.message : String(err), + error: AUTH_ERRORS.PAYLOAD_VERIFICATION_ERROR, + details: err instanceof Error ? + { message: err.message, stack: err.stack } : + String(err), }; }
156-197
: Restructure login flow to reduce nestingThe login flow has multiple nested try-catch blocks that make the code harder to follow.
Consider extracting each step into its own function that returns an
AuthResult
, then chain them together. This would reduce nesting and improve readability:const executeWorldIDVerification = async (): Promise<AuthResult> => { try { const worldIdProof = await startWorldIDVerification(); await verifyWorldIDProof(worldIdProof); return { success: true, data: { worldIdProof } }; } catch (err) { return { success: false, error: AUTH_ERRORS.WORLD_ID_VERIFICATION_FAILED, }; } }; const executeWalletAuth = async (): Promise<AuthResult> => { try { const nonceResult = await getNonce(); if (!nonceResult.success || !nonceResult.nonce) { setError(AUTH_ERRORS.NONCE_ERROR); return { success: false, error: AUTH_ERRORS.NONCE_ERROR }; } const nonce = nonceResult.nonce; const walletPayload = await performWalletAuth(nonce); return { success: true, data: { walletPayload, nonce } }; } catch (err) { return { success: false, error: AUTH_ERRORS.WALLET_AUTH_ERROR, }; } }; // Then simplify handleLogin: const handleLogin = useCallback(async (): Promise<AuthResult> => { setError(null); if (!MiniKit.isInstalled()) { setError(AUTH_ERRORS.MINIKIT_NOT_INSTALLED); return { success: false, error: AUTH_ERRORS.MINIKIT_NOT_INSTALLED }; } try { setIsLoggingIn(true); // Step 1 const worldIDResult = await executeWorldIDVerification(); if (!worldIDResult.success) return worldIDResult; // Step 2 const walletResult = await executeWalletAuth(); if (!walletResult.success) return walletResult; // Step 3 return await verifyPayload( walletResult.data.walletPayload, worldIDResult.data.worldIdProof, walletResult.data.nonce ); } catch (err) { setError(AUTH_ERRORS.LOGIN_FAILED); return { success: false, error: AUTH_ERRORS.LOGIN_FAILED }; } finally { setIsLoggingIn(false); } }, []);
64-94
: Add response data validationThe
verifyWorldIDProof
function doesn't validate the structure of the response data before returning it.Add type checking or schema validation to ensure the response matches the expected structure:
const json = await res.json(); if (json.status !== 200) { setError(AUTH_ERRORS.PROOF_VERIFICATION_FAILED); throw AUTH_ERRORS.PROOF_VERIFICATION_FAILED; } + // Basic validation of response structure + if (!json || typeof json !== 'object') { + setError(AUTH_ERRORS.API_ERROR); + throw AUTH_ERRORS.API_ERROR; + } return json;
38-41
: Consider adding specific error handling for wallet authThe wallet authentication error handling is generic and doesn't distinguish between different types of errors.
} catch (err) { - setError(AUTH_ERRORS.WALLET_AUTH_FAILED); - throw AUTH_ERRORS.WALLET_AUTH_FAILED; + // Check for specific error types + if (err && typeof err === "object" && "code" in err) { + const errorCode = err.code; + // Handle specific error codes + if (errorCode === "user_rejected") { + setError(AUTH_ERRORS.WALLET_AUTH_REJECTED); + throw AUTH_ERRORS.WALLET_AUTH_REJECTED; + } + } + + // Default error handling + setError(AUTH_ERRORS.WALLET_AUTH_FAILED); + throw AUTH_ERRORS.WALLET_AUTH_FAILED; }Note: You would need to add the new error code to your AUTH_ERRORS constant.
components/Poll/PollVoteCard.tsx (3)
261-333
: Use optional chaining for conditional renderingThe static analysis tool suggests using optional chaining for more concise code here. This would help avoid potential issues with null/undefined values.
- votes && - votes.map((vote, index) => ( + votes?.map((vote, index) => ( <div key={index} className="space-y-1"> {/* Component content */} </div> ))🧰 Tools
🪛 Biome (1.9.4)
[error] 261-333: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
394-394
: Fix typo in className attributeThere's a typo in the className attribute of one of the skeleton loading elements.
-<div className="w-10 h-10 roundaria-labelledbyed-full bg-gray-200"></div> +<div className="w-10 h-10 rounded-full bg-gray-200"></div>
387-398
: Add accessibility attributes to loading skeletonThe loading skeleton should include ARIA attributes to improve accessibility for screen reader users.
-const UserLoadingSkeleton = () => { +const UserLoadingSkeleton = () => { return ( - <div className="flex items-center justify-center h-24"> + <div + className="flex items-center justify-center h-24" + role="status" + aria-label="Loading user votes" + > <div className="w-10 h-10 rounded-full bg-gray-200"></div> <div className="w-10 h-10 rounded-full bg-gray-200"></div> <div className="w-10 h-10 rounded-full bg-gray-200"></div> <div className="w-10 h-10 rounded-full bg-gray-200"></div> <div className="w-10 h-10 roundaria-labelledbyed-full bg-gray-200"></div> <div className="w-10 h-10 rounded-full bg-gray-200"></div> + <span className="sr-only">Loading user votes...</span> </div> ); };components/Poll/PollForm.tsx (1)
261-268
: Consider semantic button type for form submission.The publish button is currently using
type="button"
which doesn't trigger form submission. While it uses a separate click handler, it would be more semantically correct to usetype="submit"
if it's meant to submit the form.- <Button - type="button" - className="w-full mt-auto py-4" - onClick={handlePublish} - disabled={isCreatingPoll} - > + <Button + type="submit" + className="w-full mt-auto py-4" + disabled={isCreatingPoll} + >This change would require updating the form submission handler as mentioned in the previous comment.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
app/actions/verify.ts
(1 hunks)app/api/nonce/route.ts
(0 hunks)app/poll/[id]/page.tsx
(1 hunks)components/Modals/PollCreatedModal.tsx
(2 hunks)components/Poll/PollForm.tsx
(6 hunks)components/Poll/PollList.tsx
(1 hunks)components/Poll/PollVoteCard.tsx
(7 hunks)components/Poll/RecentPolls.tsx
(2 hunks)components/ui/Modal.tsx
(2 hunks)hooks/usePoll.ts
(3 hunks)hooks/usePollForm.ts
(1 hunks)hooks/useUser.ts
(1 hunks)hooks/useWorldAuth.ts
(2 hunks)lib/auth.ts
(1 hunks)utils/time.ts
(2 hunks)validation/pollSchemas.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- app/api/nonce/route.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- lib/auth.ts
- app/poll/[id]/page.tsx
- validation/pollSchemas.ts
- components/ui/Modal.tsx
- utils/time.ts
- app/actions/verify.ts
- components/Modals/PollCreatedModal.tsx
- hooks/usePollForm.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
components/Poll/PollVoteCard.tsx (3)
hooks/usePoll.ts (1)
getPollDetails
(56-66)hooks/useUser.ts (1)
getUserVotes
(75-98)utils/time.ts (1)
getRelativeTimeString
(3-40)
hooks/useWorldAuth.ts (3)
context/AuthContext.tsx (1)
useAuth
(92-92)lib/constants/authErrors.ts (1)
AUTH_ERRORS
(19-36)app/actions/verify.ts (2)
verifyNonceCookie
(24-43)getNonce
(6-22)
hooks/usePoll.ts (1)
types/poll.ts (2)
IPoll
(1-14)IPollDetails
(16-20)
🪛 Biome (1.9.4)
components/Poll/PollVoteCard.tsx
[error] 261-333: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 ESLint
hooks/useUser.ts
[error] 37-37: React Hook "useQuery" is called in function "getUserData" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 58-58: React Hook "useQuery" is called in function "getUserActivities" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 78-78: React Hook "useQuery" is called in function "getUserVotes" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 101-101: React Hook "useQueryClient" is called in function "setVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 103-103: React Hook "useMutation" is called in function "setVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 128-128: React Hook "useQueryClient" is called in function "editVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 130-130: React Hook "useMutation" is called in function "editVote" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
hooks/usePoll.ts
[error] 35-35: React Hook "useQuery" is called in function "getPolls" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 57-57: React Hook "useQuery" is called in function "getPollDetails" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 69-69: React Hook "useQueryClient" is called in function "createPoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 71-71: React Hook "useMutation" is called in function "createPoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 89-89: React Hook "useQueryClient" is called in function "deletePoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
[error] 91-91: React Hook "useMutation" is called in function "deletePoll" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
(react-hooks/rules-of-hooks)
🔇 Additional comments (11)
hooks/useWorldAuth.ts (2)
7-9
: Validates the auth workflow integrationThe addition of proper nonce management and centralized error handling is a good improvement to the authentication flow. The hook now provides a more structured approach to handling login.
144-146
: Good addition of error management capabilitiesAdding the
clearError
function and exposing the error state through the hook interface gives components better control over error handling and display.Also applies to: 199-205
components/Poll/PollList.tsx (1)
4-4
: LGTM! Updated import to use direct function reference.The import has been correctly updated to use the direct function reference from the refactored hook.
components/Poll/RecentPolls.tsx (2)
3-3
: LGTM! Updated import to use direct function reference.The import has been correctly updated to use the direct function reference from the refactored hook.
65-65
: LGTM! Simplified conditional rendering.The conditional rendering of the "Refreshing..." message has been simplified to a single-line expression without changing functionality.
components/Poll/PollVoteCard.tsx (2)
25-39
: LGTM! Implemented data-driven approach with API integration.Good job converting the component to use actual data fetched from APIs instead of hardcoded values, which aligns with the PR objective of integrating API into the poll creation form.
256-260
: LGTM! Added proper loading state.Good implementation of a loading skeleton while user votes are being fetched, providing visual feedback to users during data loading.
components/Poll/PollForm.tsx (4)
56-67
: Backspace handler implementation improved correctly.The previous issue with the backspace handler has been fixed correctly by accessing the last tag directly from
watchedTags
instead of using a separate array manipulation.
109-128
: Good form field implementation with validation.The poll options implementation using React Hook Form is well structured with proper error handling and styling based on validation state.
255-259
: Good error handling implementation.The error display for both general form errors and API-specific errors is well implemented with visual feedback.
217-226
:✅ Verification successful
Potential issue with description length validation.
The description length is checked in the UI (showing character count when ≤ 1400 and error message when > 1400), but this might not align with the validation schema used in
usePollForm
. If the form validation logic is in the hook, consider moving this display logic there too for consistency.Verify that the description length validation in the form's UI matches the validation schema in
usePollForm
. Look for any Zod schema definitions related to the description field length.
🏁 Script executed:
#!/bin/bash # Search for the Zod schema definition for the description field rg -A 5 -B 5 "description.*z\.(string|text)"Length of output: 46
I’ll locate the
usePollForm
hook to inspect its Zod schema for thedescription
field.
🏁 Script executed:
#!/bin/bash # Find files defining usePollForm echo "Looking for usePollForm definitions..." rg -l "usePollForm" # Inspect those files for Zod object schemas around description for file in $(rg -l "usePollForm"); do echo -e "\n===== $file =====" rg -n "z\.object" -C3 "$file" || echo " → No z.object found in $file" doneLength of output: 745
Let’s inspect the
usePollForm
hook for any Zod schema or validation rules arounddescription
.
🏁 Script executed:
#!/bin/bash file="hooks/usePollForm.ts" echo "→ Checking Zod imports in $file" rg -n "from.*zod" -C2 "$file" || echo " No Zod imports found." echo -e "\n→ Scanning for Zod usages (z.*) in $file" rg -n "z\." -C2 "$file" || echo " No direct z. usages found." echo -e "\n→ Looking for register calls in $file" rg -n "register" -C3 "$file" || echo " No register calls found." echo -e "\n→ Searching for any maxLength/length checks in $file" rg -n "maxLength" -C2 "$file" || rg -n "length" -C2 "$file" || echo " No maxLength/length validations found." echo -e "\n→ Inspecting description references in $file" rg -n "description" -C2 "$file" || echo " No description-specific logic found."Length of output: 2345
Let’s locate and inspect the
pollSchema
definition to verify if it enforces a max length ondescription
.
🏁 Script executed:
#!/bin/bash # Locate pollSchema in the form hook echo "Searching for pollSchema in hooks/usePollForm.ts..." rg -n "pollSchema" -C5 hooks/usePollForm.tsLength of output: 1205
Inspecting the actual Zod schema for
pollSchema
:
🏁 Script executed:
#!/bin/bash # Locate the pollSchemas file schema_file=$(rg -l "pollSchemas" --glob "*.ts") echo "→ Found schema file: $schema_file" # Show pollSchema definition around description rg -n "pollSchema" -C10 "$schema_file"Length of output: 1695
Let’s locate the actual
pollSchemas.ts
file and inspect thedescription
constraint inpollSchema
.
🏁 Script executed:
#!/bin/bash echo "Searching for pollSchema definitions across .ts files..." rg -l "export const pollSchema" -g "*.ts" echo -e "\nInspecting each schema for description rules:" for file in $(rg -l "export const pollSchema" -g "*.ts"); do echo -e "\n===== $file =====" rg -n "export const pollSchema" -C5 "$file" rg -n "description" -C5 "$file" doneLength of output: 1140
Description length validation is consistent
The UI check (≤1400 characters showing a counter, and error message afterward) matches the Zod schema invalidation/pollSchemas.ts
(refine ≤ 1400 with the same error message). No changes needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
components/Poll/PollVoteCard.tsx (1)
189-205
: 🛠️ Refactor suggestionVoting constraint allows > 100% yet blocks interaction.
Users can still set multiple options to 100%, resulting in a total over 100%. The Vote button will be disabled but with no visual indication of why. Consider clamping values so the total can't exceed 100%.
const increaseVote = (index: number) => { const vote = votes?.[index]; if (!vote) return; if (vote.percentage >= 100) return; + // Calculate total percentage excluding the current vote + const totalOtherPercentages = votes?.reduce( + (acc, v, i) => acc + (i === index ? 0 : v.percentage), + 0 + ) ?? 0; + + // Limit the increase to keep total at or below 100% + const maxAllowed = 100 - totalOtherPercentages; + const newPercentage = Math.min(vote.percentage + 1, maxAllowed); const newVotes = votes?.map((vote, i) => i === index - ? { ...vote, percentage: Math.min(100, vote.percentage + 1) } + ? { ...vote, percentage: newPercentage } : vote ); if (newVotes) { newVotes[index].count = Math.sqrt(newVotes[index].percentage); setVotes(newVotes); } };
🧹 Nitpick comments (5)
components/Poll/PollVoteCard.tsx (3)
39-41
: Improve date fallback handling.When
pollDetails
is undefined, this passes an empty string to theDate
constructor which creates an "Invalid Date" object. This could cause unexpected behavior in thegetRelativeTimeString
function.const { timeLeft } = getRelativeTimeString( - new Date(pollDetails?.endDate ?? "") + pollDetails?.endDate ? new Date(pollDetails.endDate) : new Date() );
319-391
: Use optional chaining for cleaner code.Use optional chaining for a more concise syntax when mapping over votes.
{isLoading ? ( <OptionsLoadingSkeleton /> ) : ( - votes && - votes.map((vote, index) => ( + votes?.map((vote, index) => ( <div key={index} className="space-y-1"> {/* Vote option UI */} </div> )) )}🧰 Tools
🪛 Biome (1.9.4)
[error] 319-391: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
421-422
: Add error handling for share functionality.The
handleShare
utility is called without any error handling. If sharing fails, users won't receive any feedback.onClick={() => { - handleShare(pollDetails?.title ?? "", pollId) + try { + handleShare(pollDetails?.title ?? "", pollId); + } catch (error) { + console.error("Failed to share:", error); + // Consider showing a toast or notification to the user + } }}hooks/useUser.ts (2)
102-106
: Enhance retry logic with error type checking.The current retry logic only checks failure count but doesn't consider error types. Some errors like 404 or 403 shouldn't be retried as they likely won't resolve on subsequent attempts.
retry: (failureCount, error) => { if (failureCount >= MAX_RETRIES) return false; + // Don't retry client or permission errors + if (error instanceof Error) { + const message = error.message; + if ( + message.includes("404") || + message.includes("403") || + message.includes("401") || + message.includes("Permission denied") + ) { + return false; + } + } return true; },
43-55
: Consider adding caching configuration.The
useUserData
hook doesn't specify caching behavior such asstaleTime
orcacheTime
. User data likely doesn't change frequently, so consider adding appropriate cache configuration.return useQuery({ queryKey: ["user", "data", worldID], queryFn: async () => { const res = await fetch("/user/getUserData"); if (!res.ok) throw new Error("Failed to fetch user data"); return res.json(); }, + staleTime: 5 * 60 * 1000, // 5 minutes + cacheTime: 10 * 60 * 1000, // 10 minutes });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
components/Poll/PollVoteCard.tsx
(6 hunks)hooks/useUser.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
hooks/useUser.ts (1)
context/AuthContext.tsx (1)
useAuth
(92-92)
🪛 Biome (1.9.4)
components/Poll/PollVoteCard.tsx
[error] 319-391: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
components/Poll/PollVoteCard.tsx (2)
121-134
: Success modal shown before the mutation resolvesThis was flagged previously and is still present. Displaying the “Success” modal immediately can mislead users if the network call fails. Move the modal trigger into the
onSuccess
callback of the mutation.
182-204
: MissingpollDetails
inuseEffect
deps may leave state staleSame observation as in an earlier review: the effect uses
pollDetails?.options
but does not listpollDetails
in the dependency array. Add it to ensure vote state updates when poll data arrives.
🧹 Nitpick comments (6)
app/poll/[id]/results/page.tsx (1)
1-22
: Clean implementation of poll results page using Next.js patterns.The component correctly handles route parameters with proper type checking and conversion. The structure follows modern Next.js client component patterns.
Consider enhancing the error state with better styling and user feedback:
if (!pollId) { - return <div>Poll not found</div>; + return ( + <main className="flex-1 bg-white rounded-t-3xl p-5"> + <Header backUrl="/polls" /> + <div className="flex flex-col items-center justify-center py-10"> + <p className="text-gray-700 text-lg font-medium mb-4">Poll not found</p> + <p className="text-gray-500 text-sm mb-6">The poll you're looking for doesn't exist or has been removed.</p> + </div> + </main> + ); }Also, consider adding a loading state while the poll data is being fetched in the PollResultsCard component.
utils/share.ts (1)
1-4
: UnusedpollTitle
parameter – consider pruning or using it in the share payload
handleSharePoll
andhandleShareResults
acceptpollTitle
but never use it.
Either remove the argument to avoid dead code / TS‑lint noise, or pass it tonavigator.share({ title, url })
so that recipients get a richer preview.-export const handleSharePoll = async (pollTitle: string, pollId: number) => { - const shareUrl = `${process.env.NEXT_PUBLIC_APP_URL}/poll/${pollId}`; - await handleShareLink(shareUrl); -}; +export const handleSharePoll = async (pollId: number) => { + const shareUrl = `${process.env.NEXT_PUBLIC_APP_URL}/poll/${pollId}`; + await handleShareLink(shareUrl); +}Same applies to
handleShareResults
.Also applies to: 6-9
components/Poll/PollResultsCard.tsx (3)
46-48
:getRelativeTimeString
called with an empty date – guard for undefined poll dataWhen the component first renders,
pollDetails
is stillundefined
; passing an empty string tonew Date()
yields an “Invalid Date”, which can break your time formatter.-const { timeLeft } = getRelativeTimeString( - new Date(pollDetails?.endDate ?? "") -); +const { timeLeft } = pollDetails + ? getRelativeTimeString(new Date(pollDetails.endDate)) + : { timeLeft: "" };
56-66
: Effect dependencies are incomplete – risk of stale vote data
useEffect
depends onpollOptions
andtotalVotes
, yet only listspollResults
. Add all referenced values to avoid subtle mismatches when any of them change.-}, [pollResults]); +}, [pollResults, pollOptions, totalVotes]);
68-76
: IncludedeletePollSuccess
in the dependency arrayThe effect reacts to both
deletePollPending
anddeletePollSuccess
, but only the former is declared. Add the latter so the redirect reliably fires after a successful delete.-}, [deletePollPending]); +}, [deletePollPending, deletePollSuccess]);components/Poll/PollVoteCard.tsx (1)
241-246
: UI allows > 100 % allocation without feedbackYou disable the “Vote” button when totals exceed 100 %, yet sliders still permit it and users get no hint why the action is blocked. Consider clamping at 100 % or showing an inline warning (see prior suggestion).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
app/poll/[id]/results/page.tsx
(1 hunks)components/Modals/ConfirmDeleteModal.tsx
(1 hunks)components/Modals/PollCreatedModal.tsx
(2 hunks)components/Modals/VotingSuccessModal.tsx
(2 hunks)components/Poll/PollList.tsx
(2 hunks)components/Poll/PollResultsCard.tsx
(1 hunks)components/Poll/PollVoteCard.tsx
(5 hunks)components/Poll/RecentPolls.tsx
(3 hunks)components/icon-components/TrashIcon.tsx
(1 hunks)components/icon-components/index.tsx
(2 hunks)hooks/usePoll.ts
(3 hunks)hooks/usePollForm.ts
(1 hunks)tailwind.config.ts
(1 hunks)types/poll.ts
(2 hunks)utils/number.ts
(1 hunks)utils/share.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (3)
- tailwind.config.ts
- components/icon-components/index.tsx
- components/icon-components/TrashIcon.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- components/Poll/RecentPolls.tsx
- components/Poll/PollList.tsx
- hooks/usePollForm.ts
- components/Modals/PollCreatedModal.tsx
- types/poll.ts
- hooks/usePoll.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/poll/[id]/results/page.tsx (1)
components/Header.tsx (1)
Header
(4-28)
components/Modals/ConfirmDeleteModal.tsx (2)
components/ui/BottomModal.tsx (1)
BottomModal
(9-125)components/ui/Button.tsx (1)
Button
(9-32)
components/Modals/VotingSuccessModal.tsx (1)
utils/share.ts (1)
handleSharePoll
(1-4)
🪛 Biome (1.9.4)
components/Poll/PollResultsCard.tsx
[error] 191-222: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
components/Poll/PollVoteCard.tsx
[error] 353-425: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (6)
utils/number.ts (1)
1-12
: Well-implemented number formatting utility.This utility function handles number formatting nicely with good edge cases (zeros, NaN, integers vs decimals). The implementation is clean and focuses on a single responsibility.
Consider adding explicit return type for better type safety:
-export const formatFloat = (num: number, maxDecimals: number = 2) => { +export const formatFloat = (num: number, maxDecimals: number = 2): string => {For future enhancement, you might consider adding locale support as an optional parameter to support internationalization requirements.
components/Modals/ConfirmDeleteModal.tsx (1)
1-46
: Well-structured confirmation modal with proper loading state handling.The modal implements good practices including:
- Conditional rendering based on modalOpen
- Disabling the delete button during loading state
- Clear warning about the irreversible nature of deletion
- Cancel option for user to back out
Consider these enhancements for better accessibility and reusability:
<Button className="text-sm font-semibold font-sora" onClick={onDelete} disabled={isLoading} + aria-busy={isLoading} > {isLoading ? "Deleting..." : "Delete"} </Button>You might also consider making the warning message customizable via props for different deletion contexts.
components/Modals/VotingSuccessModal.tsx (1)
3-3
: Updated share function import and usage correctly.The import and usage of the share function have been appropriately updated from
handleShare
tohandleSharePoll
while maintaining the same parameters.Also applies to: 47-47
app/poll/[id]/results/page.tsx (1)
18-18
:✅ Verification successful
Verify the backUrl path.
The Header component's backUrl is set to "/polls".
🏁 Script executed:
#!/bin/bash # Check if a /polls route exists in the codebase fd "app/polls" --type dLength of output: 365
Here’s a deeper check for any “polls” directory or route entry:
#!/bin/bash # Exact match for app/polls directory fd --full-path 'app/polls' --type d # Broad search for any directory named “polls” fd polls --type d
Let’s list your top‐level routes and check explicitly for a “polls” directory:
🏁 Script executed:
#!/bin/bash # List immediate subdirectories under app/ fd . -t d -d 1 app # Exact match for app/polls directory fd --full-path 'app/polls' --type dLength of output: 134
Confirmed
/polls
route exists – no changes needed.A directory at
app/polls/
was found, so the<Header backUrl="/polls" />
is correct.utils/share.ts (1)
11-16
: Droppedtitle
/text
fields may degrade share previewsThe previous version included
title
&text
; stripping them means some targets (e.g. Slack, iMessage) show only the raw URL. If you didn’t intend to remove rich previews, re‑introduce them:- await navigator.share({ url: link }); + await navigator.share({ title: "Worldview", url: link });(Feel free to substitute a dynamic title.)
components/Poll/PollVoteCard.tsx (1)
465-468
:handleSharePoll
dropspollTitle
– call signature mismatch
handleSharePoll
now expects onlypollId
, but you’re still passingpollDetails?.title
here. Update the call after you decide what to do with the unused parameter.-onClick={() => handleSharePoll(pollDetails?.title ?? "", pollId)} +onClick={() => handleSharePoll(pollId)}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
Summary by CodeRabbit
New Features
Bug Fixes
Chores
Documentation
Refactor