Skip to content

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

Merged
merged 8 commits into from
Apr 21, 2025

Conversation

Meriem-BM
Copy link
Member

@Meriem-BM Meriem-BM commented Apr 18, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a centralized poll creation form with integrated validation and improved error handling.
    • Added a custom hook for poll form state management.
    • Included new date and time utility functions for formatting and combining dates.
    • Added accessibility improvements to modal dialogs, including keyboard focus and ARIA attributes.
    • Added support for storing and accessing a user's world ID in authentication context.
    • Added nonce-based verification for enhanced security during authentication.
    • Introduced user-related data fetching and mutation hooks for activities and votes.
    • Enhanced poll voting component with dynamic data fetching and improved interaction handling.
    • Added comprehensive error handling and state management to World ID authentication flow.
    • Added new icon component for delete actions and integrated it into the icon set.
    • Added confirmation modal for delete actions with loading state support.
    • Added new poll results page with dynamic routing and detailed results display.
  • Bug Fixes

    • Improved error handling for missing configuration and unsuccessful responses during wallet and World ID verification.
    • Clarified conditional rendering logic on the splash screen to prevent unintended display issues.
    • Ensured poll sharing always uses a valid poll ID, even if undefined.
    • Fixed SVG attribute naming in user icon for JSX compatibility.
  • Chores

    • Added new dependencies for form handling and validation: react-hook-form, zod, and @hookform/resolvers.
    • Updated Tailwind CSS configuration with additional success and error color shades.
  • Documentation

    • Introduced a comprehensive validation schema for poll data using zod.
  • Refactor

    • Refactored poll form to use a custom hook and unified validation, reducing local state complexity.
    • Improved type safety and clarity in polling hooks and related interfaces.
    • Converted poll voting component to be fully data-driven and asynchronous with type-safe state management.
    • Simplified polling hooks by exporting individual functions instead of a bundled hook.
    • Enhanced authentication flow with explicit error codes and centralized error messages.
    • Updated nonce handling to use secure, HTTP-only cookies with refined verification logic.
    • Streamlined poll list and recent polls components to use direct data fetching functions.
    • Renamed and simplified poll sharing functions for clarity and reuse.

Copy link

vercel bot commented Apr 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
worldview-fe ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 21, 2025 2:14am

Copy link

coderabbitai bot commented Apr 18, 2025

"""

Walkthrough

This 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 (getNonce), and another to verify a submitted nonce against the stored cookie (verifyNonceCookie). The backend verification fetch call was removed. The poll creation form was converted to use a new custom hook usePollForm with integrated validation via react-hook-form and zod. The authentication context now manages a worldID state extracted from the token. The useUser hook was added to handle user data and voting with error handling. Accessibility improvements were made to the modal component. Additional utility functions for date and time handling were added. The PollVoteCard component was refactored to be fully data-driven and asynchronous. Package dependencies for form handling and validation were added. Error handling and error state management were enhanced in the useWorldAuth hook with centralized error codes and messages.

Changes

File(s) Change Summary
app/actions/verify.ts Removed verifyWalletAndWorldID function; added getNonce to generate and set nonce cookie; added verifyNonceCookie to verify nonce from cookie; removed backend verification fetch logic.
components/Poll/PollForm.tsx, hooks/usePollForm.ts, validation/pollSchemas.ts, hooks/usePoll.ts, utils/time.ts, package.json Refactored poll form to use usePollForm hook with react-hook-form and zod validation; added poll schema; improved type safety in poll hooks; added date/time utility functions; added dependencies for form handling and validation.
context/AuthContext.tsx, lib/auth.ts Extended AuthContext to include worldID state and setter; added getWorldID utility to extract worldID from token.
components/ui/Modal.tsx Added focus management on modal open and ARIA attributes for accessibility.
components/Modals/PollCreatedModal.tsx Changed pollId prop type to `number
components/Login/SplashScreen.tsx Added explicit parentheses to clarify conditional rendering logic involving login state.
components/Poll/PollVoteCard.tsx Renamed component; added pollId prop; refactored to fetch poll details and user votes asynchronously; improved vote state management and UI rendering.
components/icon-components/UserIcon.tsx Updated SVG attribute names to camelCase for JSX compliance.
hooks/useUser.ts Added useUser hook to fetch user data, activities, votes, and handle vote mutations with error handling and cache invalidation.
hooks/useWorldAuth.ts Enhanced error handling with try-catch blocks; added explicit error state and clearing method; refactored login flow to return structured results; replaced FormData with JSON in payload verification; added centralized error codes.
lib/constants/authErrors.ts Added centralized authentication error codes and descriptive messages.
types/poll.ts Added IPollDetails and IAuthor interfaces for enriched poll and author data typing.

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
Loading

Possibly related PRs

Suggested reviewers

  • RamRamez

Poem

🐇 Hopping through code with a nonce in my paw,
Cookies set safe with a strict same-site law.
Poll forms now dance with zod’s guiding light,
Modals focus sharp, accessibility bright.
World IDs join the auth’s grand parade,
A bunny’s delight in the code we have made! 🌸✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 updates worldID

After login, the JWT is saved but worldID remains null, 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 from ICreatePollData

The poll form submits worldID; typings should reflect that to avoid excess any casting (see hook comment above).

🧹 Nitpick comments (5)
validation/pollSchemas.ts (1)

3-47: Comprehensive validation schema with some opportunities for improvement

The 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:

  1. 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"
+   ),
  1. 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` })
  )
  1. 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 readers

const 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‑over console.log pollutes production bundle

Debug 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 without Authorization 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 log

Remove or guard with process.env.NODE_ENV !== "production".

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb2f90 and 9929dd3.

⛔ 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 libraries

The 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 implementation

The new formatShortDate function has proper null checking and uses appropriate locale formatting.


56-61: Consider timezone handling in date-time combination

The 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 implementation

The 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 precedence

The 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 state

Updating 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 validation

Adding 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 responses

Checking 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: Form onSubmit is effectively a no‑op

Currently:

<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)}>

@Meriem-BM Meriem-BM changed the title feat: intgrate API into poll creation form feat: integrate API into poll creation form Apr 18, 2025
Copy link

@coderabbitai coderabbitai bot left a 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 – causes TypeError in browsers

The 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 explicit path and environment–aware secure options for better cookie hygiene

path defaults to / in most runtimes, but being explicit avoids surprises (and makes intent clearer to reviewers).
Likewise, setting secure: true in development over HTTP will silently drop the cookie in most browsers – gating it behind process.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? }, while verifyNonceCookie returns { isValid, error? }. Consistency eases consumption and type‑safety.
Consider returning { success: boolean, data?: T, error?: string } from both helpers (mirroring AuthResult).


38-42: Surface a success field for parity with getNonce

Returning { isValid } only forces downstream callers to branch on two different field names.
Adding a success boolean (alias of isValid) keeps the API symmetrical and self‑documenting.

hooks/useWorldAuth.ts (2)

120-128: Handle missing token defensively

storeToken(data.token) will throw if the backend ever omits token (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 clarity

Inside each sub‑step you overwrite the specific error with a generic one (WORLD_ID_VERIFICATION_FAILED, WALLET_AUTH_ERROR, etc.).
Surfacing the underlying err.message (when it matches a known AUTH_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 full IAuthor object inside poll. This is convenient, but it also duplicates the fields that already exist at the top level of IPoll. If you know for sure that the backend will always embed the author object, you may want to omit authorUserId from IPoll (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 conventions

Elsewhere in the codebase user‑related types appear to use UserId, userId, etc. Storing worldID in IAuthor deviates from that pattern (ID capitalised vs. Id). A minor rename to worldId will make autocomplete and mental grepping easier.

components/Poll/PollVoteCard.tsx (2)

292-299: Accessibility: the drag‑handle is not keyboard reachable

The draggable handle (SlidingIcon) can only be adjusted with a mouse/touch.
Provide keyboard equivalents (arrow keys or “+ / –” already exist) and add role="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 because vote.count is undefined.
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

📥 Commits

Reviewing files that changed from the base of the PR and between 9929dd3 and 49c9af4.

📒 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 and verifyNonceCookie 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 a use 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 leverage useAction instead of direct import.

@RamRamez
Copy link
Collaborator

coderabbit has suggested some security fixes related to cookies and jwt. @Meriem-BM could please confirm that they are already resolved or not valid?

@Meriem-BM Meriem-BM changed the title feat: integrate API into poll creation form feat: integrate API into poll creation form & poll details Apr 20, 2025
Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Replace 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 suggestion

Replace 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 suggestion

Update 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 issue

React Hook usage violation in regular function.

The createPoll function uses React Hooks (useQueryClient and useMutation) 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 issue

React Hook usage violation in regular function.

The deletePoll function uses React Hooks (useQueryClient and useMutation) 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-side

The client-side nonce verification is redundant since:

  1. The server endpoint /auth/verifyWorldId will also need to verify the nonce
  2. Client-side verification adds an unnecessary step in the auth flow
  3. 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 issue

Rename 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 issue

Rename 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 issue

Rename 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 issue

Rename 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 issue

Rename 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 issue

Fix potential undefined access in poll options mapping

When the component mounts, pollDetails might still be undefined while data is being fetched, which could cause runtime errors when trying to map over pollDetails?.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 AuthResult

The 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 request

The 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 debugging

The 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 nesting

The 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 validation

The 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 auth

The 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 rendering

The 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 attribute

There'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 skeleton

The 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 use type="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

📥 Commits

Reviewing files that changed from the base of the PR and between 1719cbd and 1e2c4a8.

📒 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 integration

The 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 capabilities

Adding 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 the description 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"
done

Length of output: 745


Let’s inspect the usePollForm hook for any Zod schema or validation rules around description.


🏁 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 on description.


🏁 Script executed:

#!/bin/bash
# Locate pollSchema in the form hook
echo "Searching for pollSchema in hooks/usePollForm.ts..."
rg -n "pollSchema" -C5 hooks/usePollForm.ts

Length 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 the description constraint in pollSchema.


🏁 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"
done

Length of output: 1140


Description length validation is consistent
The UI check (≤1400 characters showing a counter, and error message afterward) matches the Zod schema in validation/pollSchemas.ts (refine ≤ 1400 with the same error message). No changes needed.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Voting 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 the Date constructor which creates an "Invalid Date" object. This could cause unexpected behavior in the getRelativeTimeString 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 as staleTime or cacheTime. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2c4a8 and f4c109b.

📒 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)

Copy link

@coderabbitai coderabbitai bot left a 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 resolves

This 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: Missing pollDetails in useEffect deps may leave state stale

Same observation as in an earlier review: the effect uses pollDetails?.options but does not list pollDetails 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: Unused pollTitle parameter – consider pruning or using it in the share payload

handleSharePoll and handleShareResults accept pollTitle but never use it.
Either remove the argument to avoid dead code / TS‑lint noise, or pass it to navigator.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 data

When the component first renders, pollDetails is still undefined; passing an empty string to new 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 on pollOptions and totalVotes, yet only lists pollResults. Add all referenced values to avoid subtle mismatches when any of them change.

-}, [pollResults]);
+}, [pollResults, pollOptions, totalVotes]);

68-76: Include deletePollSuccess in the dependency array

The effect reacts to both deletePollPending and deletePollSuccess, 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 feedback

You 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

📥 Commits

Reviewing files that changed from the base of the PR and between f4c109b and 287b1d0.

📒 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 to handleSharePoll 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 d

Length 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 d

Length 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: Dropped title/text fields may degrade share previews

The 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 drops pollTitle – call signature mismatch

handleSharePoll now expects only pollId, but you’re still passing pollDetails?.title here. Update the call after you decide what to do with the unused parameter.

-onClick={() => handleSharePoll(pollDetails?.title ?? "", pollId)}
+onClick={() => handleSharePoll(pollId)}

Copy link
Collaborator

@RamRamez RamRamez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@Meriem-BM Meriem-BM merged commit 75809de into staging Apr 21, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants