Repository: GeneralMagicio/worldview-fe
Pull request #30: Fix: tags & options validation issues
Author: login: Meriem-BM
Created: 2025-04-24T21:38:34Z | Merged: 2025-04-28T14:54:11Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - The "Add option" button in poll creation is now disabled at 20 options, with a clear visual indicator and message.
  - Tag input in poll forms provides real-time validation feedback for length requirements.
  - Date selection in the date-time picker is now restricted to future dates only, with the start date fixed and non-editable.
  - Added an error page informing users that the mini-app is accessible only within the World App, with a download link.
  - Client-side navigation redirects to the error page if MiniKit installation fails.

- **Improvements**
  - Enhanced relative time display for polls, showing more precise countdowns using both start and end dates.
  - Increased number of polls displayed per page from 10 to 20 for improved browsing.
  - Poll filters now handle combined active/inactive states more consistently.
  - Poll list fetching bypasses network requests when filters indicate no active polls.
  - Date-time picker disables navigation to past months and prevents selecting past dates.
  - Login button background updated for improved styling consistency.

- **Style**
  - Updated styling for disabled buttons and informational messages in the poll form.
  - Disabled states and cursor styles added for calendar days and buttons in date-time and time pickers.
  - Added button type attributes for accessibility in time picker and date-time picker components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/error/page.tsx b/app/error/page.tsx
new file mode 100644
index 0000000..5a80ca5
--- /dev/null
+++ b/app/error/page.tsx
@@ -0,0 +1,16 @@
+import Link from "next/link";
+
+export default function ErrorPage() {
+  return (
+    <div className="flex flex-col items-center justify-center h-screen">
+      <p className="text-gray-500 text-center text-lg font-medium  w-4/5">
+        Ooops! This mini-app can only be accessed inside of the World App.
+        Download the{" "}
+        <Link href="https://worldcoin.org/download" className="text-blue-500">
+          World App
+        </Link>{" "}
+        on your mobile device and find the WorldView mini-app
+      </p>
+    </div>
+  );
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 05b5549..86c4799 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -27,12 +27,12 @@ export default function RootLayout({
       <body>
         <ErudaProvider>
           <MiniKitProvider>
-            <ReactQueryClientProvider>
-              <AuthProvider>
+            <AuthProvider>
+              <ReactQueryClientProvider>
                 <FetchPatchProvider />
                 {children}
-              </AuthProvider>
-            </ReactQueryClientProvider>
+              </ReactQueryClientProvider>
+            </AuthProvider>
           </MiniKitProvider>
         </ErudaProvider>
       </body>
diff --git a/components/DateTimePicker/CalendarDay.tsx b/components/DateTimePicker/CalendarDay.tsx
index b736caf..c48d37d 100644
--- a/components/DateTimePicker/CalendarDay.tsx
+++ b/components/DateTimePicker/CalendarDay.tsx
@@ -9,10 +9,18 @@ export const CalendarDay = ({
   isRangeEnd,
   isFirstInWeek,
   isLastInWeek,
+  isDisabled,
   onClick,
 }: CalendarDayProps) => {
   const { day, month, year } = dateObj;
 
+  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
+    e.preventDefault();
+    if (!isDisabled) {
+      onClick(day, month, year);
+    }
+  };
+
   return (
     <div
       className={cn(
@@ -25,12 +33,15 @@ export const CalendarDay = ({
       )}
     >
       <button
+        type="button"
         className={cn(
           "flex items-center justify-center w-12 h-12 relative rounded-full transition-colors",
           (isRangeStart || isRangeEnd) && "bg-gray-900 text-white",
-          !isCurrentMonth && "text-gray-300"
+          !isCurrentMonth && "text-gray-300",
+          isDisabled && "cursor-not-allowed"
         )}
-        onClick={() => onClick(day, month, year)}
+        onClick={handleClick}
+        disabled={isDisabled}
         aria-label={`${day} ${new Intl.DateTimeFormat("en-US", {
           month: "long",
         }).format(new Date(year, month, 1))}, ${year}`}
diff --git a/components/DateTimePicker/DateTimePicker.tsx b/components/DateTimePicker/DateTimePicker.tsx
index 83e503d..11e8a74 100644
--- a/components/DateTimePicker/DateTimePicker.tsx
+++ b/components/DateTimePicker/DateTimePicker.tsx
@@ -1,5 +1,9 @@
 import { useState } from "react";
-import { ChevronLeftIcon, ChevronRightIcon } from "../icon-components";
+import {
+  ChevronLeftIcon,
+  ChevronRightIcon,
+  ClockIcon,
+} from "../icon-components";
 import { Button } from "../ui/Button";
 import { Modal } from "../ui/Modal";
 import {
@@ -21,6 +25,7 @@ export default function DateTimePicker({
   initialStartTime = "13:00",
   initialEndTime = "18:00",
 }: DateTimePickerProps) {
+  const today = new Date();
   const [currentDate, setCurrentDate] = useState(new Date());
 
   // Selected date range
@@ -31,15 +36,8 @@ export default function DateTimePicker({
     endTime: initialEndTime,
   });
 
-  // Selection mode (start or end date)
-  const [selectionMode, setSelectionMode] = useState<"start" | "end" | null>(
-    null
-  );
-
   // Time picker state
-  const [timePickerOpen, setTimePickerOpen] = useState<"start" | "end" | null>(
-    null
-  );
+  const [timePickerOpen, setTimePickerOpen] = useState<"end" | null>(null);
 
   // Navigate to previous month
   const previousMonth = () => {
@@ -63,50 +61,25 @@ export default function DateTimePicker({
   const handleDateClick = (day: number, month: number, year: number) => {
     const clickedDate = new Date(year, month, day);
 
-    if (
-      !dateRange.startDate ||
-      selectionMode === "start" ||
-      (dateRange.startDate && dateRange.endDate)
-    ) {
-      // Start new selection
-      setDateRange({
-        ...dateRange,
-        startDate: clickedDate,
-        endDate: null,
-      });
-      setSelectionMode("end");
-    } else {
-      // Complete selection
-      if (clickedDate < dateRange.startDate) {
-        // If clicked date is before start date, swap them
-        setDateRange({
-          ...dateRange,
-          startDate: clickedDate,
-          endDate: dateRange.startDate,
-        });
-      } else {
-        setDateRange({
-          ...dateRange,
-          endDate: clickedDate,
-        });
-      }
-      setSelectionMode(null);
+    if (clickedDate <= today) {
+      return;
     }
+
+    setDateRange({
+      ...dateRange,
+      endDate: clickedDate,
+    });
   };
 
   // Handle time selection
-  const handleTimeChange = (
-    type: "start" | "end",
-    hours: number,
-    minutes: number
-  ) => {
+  const handleTimeChange = (hours: number, minutes: number) => {
     const formattedHours = hours.toString().padStart(2, "0");
     const formattedMinutes = minutes.toString().padStart(2, "0");
     const timeString = `${formattedHours}:${formattedMinutes}`;
 
     setDateRange((prev) => ({
       ...prev,
-      [type === "start" ? "startTime" : "endTime"]: timeString,
+      endTime: timeString,
     }));
   };
 
@@ -192,6 +165,17 @@ export default function DateTimePicker({
     return date.getTime() === compareDate.getTime();
   };
 
+  // Check if a date should be disabled (current date or past dates)
+  const isDateDisabled = (
+    day: number,
+    month: number,
+    year: number
+  ): boolean => {
+    const date = new Date(year, month, day);
+    date.setHours(0, 0, 0, 0);
+    return date <= today;
+  };
+
   // Get current month and year for display
   const currentMonthName = new Intl.DateTimeFormat("en-US", {
     month: "long",
@@ -208,8 +192,10 @@ export default function DateTimePicker({
         {/* Month navigation */}
         <div className="flex items-center justify-between mb-6">
           <button
+            type="button"
             onClick={previousMonth}
-            className="p-2 rounded-full"
+            className="p-2 rounded-full disabled:opacity-25"
+            disabled={currentDate.getMonth() === today.getMonth()}
             aria-label="Previous month"
           >
             <ChevronLeftIcon />
@@ -218,6 +204,7 @@ export default function DateTimePicker({
             {currentMonthName} {currentYear}
           </h2>
           <button
+            type="button"
             onClick={nextMonth}
             className="p-2 rounded-full"
             aria-label="Next month"
@@ -229,24 +216,23 @@ export default function DateTimePicker({
         {/* Date range inputs */}
         <div className="flex items-center mb-6">
           <div className="flex-1">
-            <button
-              className="w-full border border-gray-300 rounded-lg p-4 bg-transparent text-left"
-              onClick={() => setSelectionMode("start")}
-            >
-              <div className="text-gray-900 text-sm">
-                {dateRange.startDate ? formatDate(dateRange.startDate) : ""}
+            <div className="w-full border border-gray-300 rounded-lg p-4 bg-gray-100 text-left cursor-not-allowed">
+              <div className="text-gray-500 text-sm">
+                {formatDate(dateRange.startDate)}
               </div>
-            </button>
+            </div>
           </div>
           <div className="mx-2 text-gray-400">—</div>
           <div className="flex-1">
             <button
+              type="button"
               className="w-full border border-gray-300 rounded-lg p-4 bg-transparent text-left"
-              onClick={() => dateRange.startDate && setSelectionMode("end")}
               disabled={!dateRange.startDate}
             >
               <div className="text-gray-900 text-sm">
-                {dateRange.endDate ? formatDate(dateRange.endDate) : ""}
+                {dateRange.endDate
+                  ? formatDate(dateRange.endDate)
+                  : "Select end date"}
               </div>
             </button>
           </div>
@@ -254,25 +240,19 @@ export default function DateTimePicker({
 
         {/* Time range inputs */}
         <div className="flex items-center mb-8">
-          <TimePicker
-            time={dateRange.startTime}
-            isOpen={timePickerOpen === "start"}
-            onTimeChange={(hours, minutes) =>
-              handleTimeChange("start", hours, minutes)
-            }
-            onToggle={() =>
-              setTimePickerOpen((prev) => (prev === "start" ? null : "start"))
-            }
-          />
+          <div className="flex-1">
+            <div className="w-full border border-gray-300 rounded-lg p-4 bg-gray-100 text-left cursor-not-allowed flex items-center gap-2">
+              <ClockIcon />
+              <div className="text-gray-500 text-sm">{dateRange.startTime}</div>
+            </div>
+          </div>
 
           <div className="mx-2 text-gray-400">—</div>
 
           <TimePicker
             time={dateRange.endTime}
             isOpen={timePickerOpen === "end"}
-            onTimeChange={(hours, minutes) =>
-              handleTimeChange("end", hours, minutes)
-            }
+            onTimeChange={(hours, minutes) => handleTimeChange(hours, minutes)}
             onToggle={() =>
               setTimePickerOpen((prev) => (prev === "end" ? null : "end"))
             }
@@ -320,6 +300,11 @@ export default function DateTimePicker({
                   )}
                   isFirstInWeek={dayIndex === 0}
                   isLastInWeek={dayIndex === 6}
+                  isDisabled={isDateDisabled(
+                    dateObj.day,
+                    dateObj.month,
+                    dateObj.year
+                  )}
                   onClick={handleDateClick}
                 />
               ))}
@@ -331,6 +316,7 @@ export default function DateTimePicker({
       {/* Footer */}
       <div className="flex border-t border-gray-300 justify-evenly items-center py-4">
         <Button
+          type="button"
           variant="outline"
           className="w-5/12"
           onClick={() => onOpenChange(false)}
@@ -338,9 +324,10 @@ export default function DateTimePicker({
           Cancel
         </Button>
         <Button
-          className="w-5/12"
+          type="button"
+          className="w-5/12 disabled:opacity-50"
           onClick={handleApply}
-          disabled={!dateRange.startDate || !dateRange.endDate}
+          disabled={!dateRange.endDate}
         >
           Apply
         </Button>
diff --git a/components/DateTimePicker/TimePicker.tsx b/components/DateTimePicker/TimePicker.tsx
index 425001c..53dcd6e 100644
--- a/components/DateTimePicker/TimePicker.tsx
+++ b/components/DateTimePicker/TimePicker.tsx
@@ -37,6 +37,7 @@ export const TimePicker = ({
   return (
     <div className={cn("flex-1 relative", className)} ref={timePickerRef}>
       <button
+        type="button"
         className={cn(
           "w-full border rounded-lg p-4 bg-transparent flex items-center gap-2",
           isOpen ? "border-gray-500" : "border-gray-300"
@@ -58,6 +59,7 @@ export const TimePicker = ({
               <div className="h-48 overflow-y-auto flex flex-col items-center">
                 {HOURS.map((hour) => (
                   <button
+                    type="button"
                     key={hour}
                     className={cn(
                       "w-full py-2 text-center rounded-md",
@@ -84,6 +86,7 @@ export const TimePicker = ({
               <div className="h-48 overflow-y-auto flex flex-col items-center">
                 {MINUTES.map((minute) => (
                   <button
+                    type="button"
                     key={minute}
                     className={cn(
                       "w-full py-2 text-center rounded-md",
diff --git a/components/Login/SplashScreen.tsx b/components/Login/SplashScreen.tsx
index 27d38d7..aa5f227 100644
--- a/components/Login/SplashScreen.tsx
+++ b/components/Login/SplashScreen.tsx
@@ -25,7 +25,7 @@ export function SplashScreen() {
           </div>
           <div className="w-full max-w-md px-6 mt-auto">
             <button
-              className="w-full bg-[#161c1f] text-white py-4 rounded-xl text-lg font-semibold"
+              className="w-full bg-gray-900 text-white py-4 rounded-xl text-lg font-semibold"
               onClick={handleLogin}
             >
               Login with World
diff --git a/components/Modals/FilterModal.tsx b/components/Modals/FilterModal.tsx
index c1f839b..deae315 100644
--- a/components/Modals/FilterModal.tsx
+++ b/components/Modals/FilterModal.tsx
@@ -1,7 +1,7 @@
+import { useEffect, useState } from "react";
+import { IPollFilters } from "@/types/poll";
 import CustomCheckbox from "../ui/CustomCheckbox";
 import BottomModal from "../ui/BottomModal";
-import { IPollFilters } from "@/types/poll";
-import { useState } from "react";
 
 interface FilterModalProps {
   filters: IPollFilters;
@@ -34,6 +34,10 @@ export default function FilterModal({
     setTempFilters(DEFAULT_FILTERS);
   };
 
+  useEffect(() => {
+    setTempFilters(filters);
+  }, [filters]);
+
   return (
     <BottomModal modalOpen={filtersOpen} setModalOpen={setFiltersOpen}>
       <h2 className="text-xl font-medium text-center mb-8">Select filters</h2>
diff --git a/components/Poll/PollCard.tsx b/components/Poll/PollCard.tsx
index b17d30f..2bec87b 100644
--- a/components/Poll/PollCard.tsx
+++ b/components/Poll/PollCard.tsx
@@ -8,7 +8,10 @@ import { useRouter } from "next/navigation";
 export default function PollCard({ poll }: { poll: IPoll }) {
   const router = useRouter();
 
-  const { timeLeft, isEnded } = getRelativeTimeString(new Date(poll.endDate));
+  const { timeLeft, isEnded } = getRelativeTimeString(
+    poll.startDate ?? "",
+    poll.endDate ?? ""
+  );
 
   const navigateToPoll = () => {
     router.push(`/poll/${poll.pollId}`);
diff --git a/components/Poll/PollForm.tsx b/components/Poll/PollForm.tsx
index f4dddbd..27f520e 100644
--- a/components/Poll/PollForm.tsx
+++ b/components/Poll/PollForm.tsx
@@ -131,7 +131,8 @@ export default function PollForm() {
       <button
         type="button"
         onClick={addOption}
-        className="flex items-center gap-2 bg-gray-200 text-gray-900 px-4 py-2 text-sm rounded-lg"
+        disabled={watchedOptions.length >= 20}
+        className="flex items-center gap-2 bg-gray-200 text-gray-900 px-4 py-2 text-sm rounded-lg disabled:opacity-25 disabled:cursor-not-allowed"
       >
         <PlusIcon size={16} color="#191C20" />
         Add option
@@ -140,6 +141,11 @@ export default function PollForm() {
       {errors.options &&
         !Array.isArray(errors.options) &&
         renderErrorMessage(errors.options.message as string)}
+      {watchedOptions.length >= 20 && (
+        <p className="flex items-center justify-end text-gray-500 text-sm mt-1">
+          Maximum of 20 options reached
+        </p>
+      )}
     </div>
   );
 
diff --git a/components/Poll/PollList.tsx b/components/Poll/PollList.tsx
index 0a97371..dc8eb66 100644
--- a/components/Poll/PollList.tsx
+++ b/components/Poll/PollList.tsx
@@ -10,7 +10,7 @@ import NoPollsView from "./NoPollsView";
 import { Toaster } from "../Toaster";
 import { useToast } from "@/hooks/useToast";
 
-const POLLS_PER_PAGE = 10;
+const POLLS_PER_PAGE = 20;
 
 interface PollListProps {
   filters: IPollFilters;
@@ -36,10 +36,10 @@ export default function PollList({ filters, filterParam }: PollListProps) {
   }, [filters]);
 
   const checkIsActive = () => {
-    if (filters.livePolls && filters.finishedPolls) return undefined;
+    if (filters.livePolls && filters.finishedPolls) return "none";
+    if (!filters.livePolls && !filters.finishedPolls) return undefined;
     if (filters.livePolls && !filters.finishedPolls) return true;
     if (!filters.livePolls && filters.finishedPolls) return false;
-    if (!filters.livePolls && !filters.finishedPolls) return undefined;
     return undefined;
   };
 
diff --git a/components/Poll/PollResultsCard.tsx b/components/Poll/PollResultsCard.tsx
index 320ebfa..7569a7d 100644
--- a/components/Poll/PollResultsCard.tsx
+++ b/components/Poll/PollResultsCard.tsx
@@ -40,7 +40,8 @@ export default function PollVoteCard({ pollId }: { pollId: number }) {
   const isAuthor = worldID === pollDetails?.author?.worldID;
 
   const { timeLeft } = getRelativeTimeString(
-    new Date(pollDetails?.endDate ?? "")
+    pollDetails?.startDate ?? "",
+    pollDetails?.endDate ?? ""
   );
 
   const [votes, setVotes] = useState<VoteState[]>();
diff --git a/components/Poll/PollVoteCard.tsx b/components/Poll/PollVoteCard.tsx
index 3696795..11d13d9 100644
--- a/components/Poll/PollVoteCard.tsx
+++ b/components/Poll/PollVoteCard.tsx
@@ -50,7 +50,8 @@ export default function PollVoteCard({ pollId }: { pollId: number }) {
   const isAuthor = worldID === pollDetails?.author?.worldID;
 
   const { timeLeft } = getRelativeTimeString(
-    new Date(pollDetails?.endDate ?? "")
+    pollDetails?.startDate ?? "",
+    pollDetails?.endDate ?? ""
   );
 
   const navigateToUserProfile = () => {
diff --git a/components/minikit-provider.tsx b/components/minikit-provider.tsx
index 315d5c6..bb905dd 100644
--- a/components/minikit-provider.tsx
+++ b/components/minikit-provider.tsx
@@ -2,10 +2,17 @@
 
 import { MiniKit } from "@worldcoin/minikit-js";
 import { ReactNode, useEffect } from "react";
+import { useRouter } from "next/navigation";
 
 export default function MiniKitProvider({ children }: { children: ReactNode }) {
+  const router = useRouter();
+
   useEffect(() => {
     MiniKit.install();
+
+    if (!MiniKit.isInstalled()) {
+      router.push("/error");
+    }
   }, []);
 
   return <>{children}</>;
diff --git a/hooks/usePoll.ts b/hooks/usePoll.ts
index be3f533..805aa6c 100644
--- a/hooks/usePoll.ts
+++ b/hooks/usePoll.ts
@@ -11,7 +11,7 @@ export const POLLS_LIMIT = 10;
 interface IUsePollParams {
   page?: number;
   limit?: number;
-  isActive?: boolean;
+  isActive?: boolean | "none";
   userVoted?: boolean;
   userCreated?: boolean;
   sortBy?: "creationDate" | "endDate" | "participantCount";
@@ -37,6 +37,13 @@ export const useGetPolls = (
   return useQuery({
     queryKey: ["polls", filters],
     queryFn: async () => {
+      if (filters.isActive === "none") {
+        return {
+          polls: [],
+          total: 0,
+        };
+      }
+
       const params: IUsePollParams = {
         page: filters.page || 1,
         limit: filters.limit || POLLS_LIMIT,
diff --git a/hooks/usePollForm.ts b/hooks/usePollForm.ts
index f165242..01b33f0 100644
--- a/hooks/usePollForm.ts
+++ b/hooks/usePollForm.ts
@@ -29,7 +29,7 @@ export function usePollForm() {
   const currentMonth = new Date().getMonth();
   const currentDay = new Date().getDate();
 
-  const tomorrow = new Date(currentYear, currentMonth, currentDay + 1);
+  const today = new Date(currentYear, currentMonth, currentDay);
   const nextWeek = new Date(currentYear, currentMonth, currentDay + 7);
 
   // Form setup
@@ -39,7 +39,7 @@ export function usePollForm() {
       title: "",
       description: "",
       options: ["", ""],
-      startDate: tomorrow.toISOString(),
+      startDate: today.toISOString(),
       endDate: nextWeek.toISOString(),
       tags: [],
       isAnonymous: false,
@@ -55,6 +55,8 @@ export function usePollForm() {
     formState: { errors },
     getValues,
     trigger,
+    setError,
+    clearErrors,
   } = form;
 
   // Watch for changes to form values
@@ -68,13 +70,19 @@ export function usePollForm() {
   const [draftModalOpen, setDraftModalOpen] = useState(false);
   const [pollCreatedModalOpen, setPollCreatedModalOpen] = useState(false);
 
+  const initialStartTime = new Date().toLocaleTimeString("en-EU", {
+    hour: "2-digit",
+    minute: "2-digit",
+    hour12: false,
+  });
+
   // Date time state
   const [duration, setDuration] = useState<24 | 48 | "custom">(24);
   const [datePickerOpen, setDatePickerOpen] = useState(false);
   const [selectedDateTime, setSelectedDateTime] = useState<DateTimeValues>({
-    startDate: tomorrow,
+    startDate: today,
     endDate: nextWeek,
-    startTime: "13:00",
+    startTime: initialStartTime,
     endTime: "18:00",
   });
   const [customDateRange, setCustomDateRange] = useState<string | null>(null);
@@ -160,31 +168,18 @@ export function usePollForm() {
     let end: Date;
     let start: Date;
 
+    start = now;
+
     if (duration === 24) {
-      start = now;
       end = new Date(now.getTime() + 24 * 60 * 60 * 1000);
     } else if (duration === 48) {
-      start = now;
       end = new Date(now.getTime() + 48 * 60 * 60 * 1000);
     } else {
-      if (startDate < now) {
-        setGeneralError("Start date cannot be in the past");
-        return { formattedStartDate: "", formattedEndDate: "" };
-      }
-      if (endDate < startDate) {
-        setGeneralError("End date cannot be before start date");
-        return { formattedStartDate: "", formattedEndDate: "" };
-      }
       if (startDate === endDate) {
         setGeneralError("Start date and end date cannot be the same");
         return { formattedStartDate: "", formattedEndDate: "" };
       }
 
-      if (startDate === now) {
-        start = now;
-      } else {
-        start = startDate;
-      }
       end = endDate;
     }
 
@@ -230,6 +225,20 @@ export function usePollForm() {
     });
   };
 
+  useEffect(() => {
+    if (tagInput.length > 20) {
+      setError("tags", {
+        message: `${tagInput.length}/20 Max tag character limit reached`,
+      });
+    } else if (tagInput.length < 3 && tagInput.length > 0) {
+      setError("tags", {
+        message: "Must be at least 3 characters",
+      });
+    } else {
+      clearErrors("tags");
+    }
+  }, [tagInput]);
+
   return {
     form,
     register,
diff --git a/types/dateTimePicker.ts b/types/dateTimePicker.ts
index 20d7861..96e10e7 100644
--- a/types/dateTimePicker.ts
+++ b/types/dateTimePicker.ts
@@ -37,5 +37,6 @@ export type CalendarDayProps = {
   isRangeEnd: boolean;
   isFirstInWeek: boolean;
   isLastInWeek: boolean;
+  isDisabled: boolean;
   onClick: (day: number, month: number, year: number) => void;
 };
diff --git a/utils/time.ts b/utils/time.ts
index 627b9a1..51eac12 100644
--- a/utils/time.ts
+++ b/utils/time.ts
@@ -1,42 +1,58 @@
 import { MONTHS } from "@/lib/constants";
 
 export const getRelativeTimeString = (
-  targetDate: string | Date
+  startDate: string,
+  endDate: string
 ): { timeLeft: string; isEnded: boolean } => {
+  if (!startDate || !endDate) {
+    return {
+      timeLeft: "Ended",
+      isEnded: true,
+    };
+  }
+
   const now = new Date();
-  const date = new Date(targetDate);
-  const diff = date.getTime() - now.getTime(); // in ms
+  const start = new Date(startDate);
+  const end = new Date(endDate);
+  const isOutOfRange = start > now || end < now;
+
+  if (isOutOfRange) {
+    return {
+      timeLeft: "Ended",
+      isEnded: true,
+    };
+  }
+
+  const diff = end.getTime() - now.getTime();
   const absDiff = Math.abs(diff);
 
   const minutes = Math.floor(absDiff / (1000 * 60));
-  const hours = Math.floor(minutes / 60);
-  const days = Math.floor(hours / 24);
+  const hours = Math.floor(absDiff / (1000 * 60 * 60));
+  const days = Math.floor(absDiff / (1000 * 60 * 60 * 24));
   const months = Math.floor(days / 30);
-  const years = Math.floor(months / 12);
+  // const years = Math.floor(months / 12);
 
-  let result = "";
+  let timeLeft: string = "";
 
-  if (diff > 0) {
-    if (days === 0) {
-      result = `${hours % 24}h`;
-    } else if (hours === 0) {
-      result = `${minutes % 60}m`;
-    } else {
-      result = `${days}d ${hours % 24}h`;
-    }
-  } else if (years >= 1) {
-    result = `${years}y`;
-  } else if (months >= 1) {
-    result = `${months}mo`;
+  if (days >= 60) {
+    const remainingDays = days % 30;
+    timeLeft =
+      remainingDays > 0 ? `${months}m ${remainingDays}d` : `${months}m`;
   } else if (days >= 1) {
-    result = `${days}d ${hours % 24}h`;
+    const remainingHours = hours % 24;
+    timeLeft = remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
   } else if (hours >= 1) {
-    result = `${hours}h ${minutes % 60}m`;
+    const remainingMinutes = minutes % 60;
+    timeLeft =
+      remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
   } else {
-    result = `${minutes}m`;
+    timeLeft = `${minutes}m`;
   }
 
-  return { timeLeft: result, isEnded: diff < 0 };
+  return {
+    timeLeft,
+    isEnded: isOutOfRange,
+  };
 };
 
 export const formatDate = (date: Date | null): string => {
diff --git a/validation/pollSchemas.ts b/validation/pollSchemas.ts
index 750fdc4..dc74ff0 100644
--- a/validation/pollSchemas.ts
+++ b/validation/pollSchemas.ts
@@ -32,7 +32,7 @@ export const pollSchema = z.object({
         )
         .refine(
           (val) => val.length >= 3,
-          (val) => ({ message: "Must be at least 3 characters" })
+          () => ({ message: "Must be at least 3 characters" })
         )
     )
     .refine(


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>hooks/usePollForm.ts (1)</summary><blockquote>

`235-247`: **Implemented real-time validation for tag input length.**

This is an excellent improvement that provides immediate feedback to users as they type tags. The validation correctly enforces the minimum (3 characters) and maximum (20 characters) length requirements, matching the schema validation rules.


However, there's a potential issue with clearing errors too aggressively. If there are other tag-related errors (like maximum tag count reached), they will also be cleared when the input length is valid.


Consider using a more specific error key to avoid clearing other tag errors:

```diff
-setError("tags", {
+setError("tags.length", {
  message: `${tagInput.length}/20 Max tag character limit reached`,
});

// And similarly for clearing
-clearErrors("tags");
+clearErrors("tags.length");
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4ed4fdfa5c76940eddec612ce70444ed618ea472 and fa355a2076a1d818c6f9e6aeeb076894859111dc.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `components/Poll/PollForm.tsx` (2 hunks)
* `hooks/usePollForm.ts` (2 hunks)
* `utils/time.ts` (1 hunks)
* `validation/pollSchemas.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>validation/pollSchemas.ts (1)</summary>

`34-36`: **Improved code clarity by removing unused parameter.**

The error message callback for the tag length refinement has been simplified by removing the unused `val` parameter. This is a small but positive change that reduces unnecessary code.

</details>
<details>
<summary>components/Poll/PollForm.tsx (2)</summary>

`134-135`: **Good UX improvement - "Add option" button now properly disabled when limit reached.**

The button is now disabled when the maximum of 20 options is reached, with appropriate styling to visually indicate the disabled state.

---

`144-148`: **Clear user feedback added for maximum options limit.**

Adding an explanatory message when the maximum number of options is reached provides clear feedback to users about why they can't add more options. This is good UX practice.

</details>
<details>
<summary>hooks/usePollForm.ts (1)</summary>

`58-60`: **Added form validation tools for tag validation.**

Correctly importing the necessary form handlers from react-hook-form to support the new tag validation functionality.

</details>
<details>
<summary>utils/time.ts (3)</summary>

`9-10`: **Improved time calculation with explicit isPast flag.**

Using a boolean flag to track whether the date is in the past makes the code more readable and intention-clear than relying on negative diff values throughout the code.

---

`13-16`: **Simplified time unit calculations.**

The calculation of hours and days directly from milliseconds is more efficient than the cascading approach that was likely used before.

---

`38-41`: **Updated return value with meaningful property names.**

The return object now uses more descriptive property names (`timeLeft` and `isEnded`), making the API more intuitive.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T21:40:35Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>utils/time.ts (1)</summary><blockquote>

`37-50`: **Handle the edge case of 0 minutes remaining.**

When minutes is 0, the function will return "0m" which isn't very user-friendly. Consider implementing the suggestion from the previous review to handle zero minutes more explicitly.



You could improve the user experience by adding a special case:

```diff
  } else {
-   timeLeft = `${minutes}m`;
+   timeLeft = minutes === 0 ? "0 minutes remaining" : `${minutes}m`;
  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fa355a2076a1d818c6f9e6aeeb076894859111dc and dee58a4b51b1514024ad289ef10aaa18e7cf9c78.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `components/Modals/FilterModal.tsx` (2 hunks)
* `components/Poll/PollCard.tsx` (1 hunks)
* `components/Poll/PollList.tsx` (1 hunks)
* `components/Poll/PollResultsCard.tsx` (1 hunks)
* `components/Poll/PollVoteCard.tsx` (1 hunks)
* `utils/time.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>components/Poll/PollCard.tsx (1)</summary><blockquote>

<details>
<summary>utils/time.ts (1)</summary>

* `getRelativeTimeString` (3-56)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>utils/time.ts (4)</summary>

`3-6`: **Improved function signature for more accurate time calculations.**

The function now accepts both start and end dates, allowing for more comprehensive time range validation. This is a better approach as it enables checking if the current time is outside the entire poll range.

---

`7-24`: **Enhanced error handling with meaningful defaults.**

The function now properly handles missing date parameters and out-of-range scenarios by immediately returning "Ended" with the appropriate flag. This will prevent downstream errors and provides clear feedback about poll status.

---

`26-31`: **Improved time calculations for more accurate results.**

The calculations now directly convert the millisecond difference to minutes, hours, and days instead of cascading calculations, which could introduce rounding errors.

---

`52-55`: **Consistent return structure with clear status flag.**

The function now returns both the formatted time string and a boolean flag indicating whether the poll has ended. This is a good pattern as it allows components to easily determine the poll status.

</details>
<details>
<summary>components/Poll/PollCard.tsx (1)</summary>

`11-14`: **Updated function call to match new signature.**

The code correctly adapts to the updated `getRelativeTimeString` function by passing both start and end dates with null handling, ensuring that the component works properly with the new time utility.

</details>
<details>
<summary>components/Poll/PollResultsCard.tsx (1)</summary>

`42-45`: **Updated function call to match new signature.**

The code correctly adapts to the updated `getRelativeTimeString` function by passing both start and end dates with null handling, ensuring that the component works properly with the new time utility.

</details>
<details>
<summary>components/Modals/FilterModal.tsx (2)</summary>

`1-2`: **Improved import organization.**

The imports are now better organized by grouping related React hooks together and positioning type imports before component imports.

---

`37-39`: **Added synchronization between props and internal state.**

This is a critical fix that ensures the internal filter state stays in sync with external filters. Without this `useEffect`, if the parent component updates `filters` prop, the modal's `tempFilters` state wouldn't reflect those changes unless manually reset.

</details>
<details>
<summary>components/Poll/PollList.tsx (1)</summary>

`13-13`: **Increased poll pagination from 10 to 20 per page.**

This change doubles the number of polls loaded in each page request, which provides users with more content before needing to click "Load More".

</details>
<details>
<summary>components/Poll/PollVoteCard.tsx (1)</summary>

`52-55`: **Updated time calculation to use both start and end dates.**

The function call now correctly passes both start and end dates to the refactored `getRelativeTimeString` utility, improving accuracy of the displayed time remaining.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T22:53:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>hooks/usePoll.ts (1)</summary><blockquote>

`46-46`: **Remove unnecessary empty line.**

This empty line after the conditional block is redundant since there's already a blank line after it. Consider removing it for cleaner code.

```diff
      if (filters.isActive === "none") {
        return {
          polls: [],
          total: 0,
        };
      }
-

      const params: IUsePollParams = {
```

</blockquote></details>
<details>
<summary>components/DateTimePicker/CalendarDay.tsx (2)</summary><blockquote>

`17-23`: **Minor: `preventDefault` is unnecessary on a `type="button"` element**

Because the button is already `type="button"` (line 36) it has no default form-submission behaviour, so `e.preventDefault()` is redundant. Removing it simplifies the handler.

```diff
-    e.preventDefault();
-    if (!isDisabled) {
-      onClick(day, month, year);
-    }
+    if (!isDisabled) {
+      onClick(day, month, year);
+    }
```

---

`35-45`: **Improve accessibility by exposing disabled state to assistive tech**

Currently visual and functional disabling is correct, but screen-readers don’t know _why_ the day cannot be chosen. Add `aria-disabled` so the semantic state is conveyed:

```diff
-        onClick={handleClick}
-        disabled={isDisabled}
+        onClick={handleClick}
+        disabled={isDisabled}
+        aria-disabled={isDisabled}
```

</blockquote></details>
<details>
<summary>components/DateTimePicker/DateTimePicker.tsx (1)</summary><blockquote>

`168-177`: **Duplicate “is date in the past” logic – centralise & normalise time**

`handleDateClick` and `isDateDisabled` both compare against `today`, but only
`isDateDisabled` normalises the tested date to midnight.  
For consistency (and to avoid edge-cases around time-of-day) move the normalisation to a single helper and reuse it in both places.

```ts
const isPastOrToday = (d: Date) => {
  const todayMidnight = new Date();
  todayMidnight.setHours(0,0,0,0);
  d.setHours(0,0,0,0);
  return d <= todayMidnight;
};
```

Then:

```diff
-  if (clickedDate <= today) return;
+  if (isPastOrToday(clickedDate)) return;
```

and

```diff
-return date <= today;
+return isPastOrToday(date);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between dee58a4b51b1514024ad289ef10aaa18e7cf9c78 and 212802cbf9fa5084100a5a77bc3073c632de4be2.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `components/DateTimePicker/CalendarDay.tsx` (2 hunks)
* `components/DateTimePicker/DateTimePicker.tsx` (10 hunks)
* `components/DateTimePicker/TimePicker.tsx` (3 hunks)
* `components/Poll/PollList.tsx` (2 hunks)
* `hooks/usePoll.ts` (2 hunks)
* `hooks/usePollForm.ts` (6 hunks)
* `types/dateTimePicker.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* components/DateTimePicker/TimePicker.tsx

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* components/Poll/PollList.tsx
* hooks/usePollForm.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>components/DateTimePicker/CalendarDay.tsx (2)</summary><blockquote>

<details>
<summary>types/dateTimePicker.ts (1)</summary>

* `CalendarDayProps` (32-42)

</details>
<details>
<summary>utils/index.ts (1)</summary>

* `cn` (8-10)

</details>

</blockquote></details>
<details>
<summary>components/DateTimePicker/DateTimePicker.tsx (5)</summary><blockquote>

<details>
<summary>utils/time.ts (1)</summary>

* `formatDate` (58-61)

</details>
<details>
<summary>components/icon-components/index.tsx (1)</summary>

* `ClockIcon` (37-37)

</details>
<details>
<summary>components/icon-components/ClockIcon.tsx (1)</summary>

* `ClockIcon` (3-25)

</details>
<details>
<summary>components/DateTimePicker/TimePicker.tsx (1)</summary>

* `TimePicker` (10-112)

</details>
<details>
<summary>components/ui/Button.tsx (1)</summary>

* `Button` (9-32)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>hooks/usePoll.ts (3)</summary>

`14-14`: **Good type extension for filter enhancement.**

Adding the `"none"` value to the `isActive` filter type is a clean way to support the new filtering behavior. This allows explicit indication of when no polls should be shown, rather than using workarounds or special boolean values.

---

`40-45`: **Efficient short-circuit optimization for the "none" filter case.**

This early return pattern correctly implements the logic to return empty results when both active and inactive polls are filtered out. This approach prevents unnecessary network requests, which improves performance and user experience.

---

`9-9`: <details>
<summary>❌ Incorrect review comment</summary>

**Consider updating POLLS_LIMIT constant.**

The AI summary mentioned that the polls fetched per page was doubled from 10 to 20, but this constant still shows 10. If the limit has indeed been changed elsewhere in the codebase, consider updating this constant for consistency.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for any references to POLLS_LIMIT and if it's being overridden elsewhere
rg "POLLS_LIMIT" --type ts
```

Length of output: 136

---



</details>

**No update needed for POLLS_LIMIT constant**

A search of the codebase confirms `POLLS_LIMIT` is only defined here and never overridden to 20. The default of 10 is correct, so you can ignore the earlier suggestion. 

> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>types/dateTimePicker.ts (1)</summary>

`40-42`: **Consider making `isDisabled` optional for backwards-compatibility**

`isDisabled` is now mandatory for every `<CalendarDay />` call-site.  
If any older code (tests, storybook, admin panels, etc.) still renders `CalendarDay` you will get a compile-time error.

```diff
-  isDisabled: boolean;
+  /** Marks day as not selectable (e.g. today & past) – defaults to `false` */
+  isDisabled?: boolean;
```

The component can safely default to `false`:

```ts
export const CalendarDay = ({ isDisabled = false, ...rest }: CalendarDayProps) => {/* … */}
```

This keeps existing renders compiling while still enforcing the new rule where required.  
  
Would you like a patch that updates the component to apply this default?

</details>
<details>
<summary>components/DateTimePicker/DateTimePicker.tsx (1)</summary>

`75-84`: **Minor: rename `handleTimeChange` parameter order for clarity**

The callback signature `(hours, minutes)` differs from the natural “time” order `(hours, minutes)`? Wait – order is correct, but adapt typed names: they are hours then minutes; Provide vantage.

(This is more nitpick but not necessary; ignore)

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T09:27:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/error/page.tsx (1)</summary><blockquote>

`1-16`: **New error page for MiniKit installation requirement.**

The error page provides a clear message about environment requirements and directs users to download the World App.


Consider adding these attributes to the external link for better security and user experience:

```diff
-        <Link href="https://worldcoin.org/download" className="text-blue-500">
+        <Link href="https://worldcoin.org/download" className="text-blue-500" target="_blank" rel="noopener noreferrer">
```

Also, consider adding a period at the end of the last sentence for consistent punctuation.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 212802cbf9fa5084100a5a77bc3073c632de4be2 and 5064b708ce6c9bde4d4c32974eb7f0f7e4ff8bb3.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `app/error/page.tsx` (1 hunks)
* `app/layout.tsx` (1 hunks)
* `components/Login/SplashScreen.tsx` (1 hunks)
* `components/minikit-provider.tsx` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* components/Login/SplashScreen.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>app/layout.tsx (3)</summary><blockquote>

<details>
<summary>context/AuthContext.tsx (1)</summary>

* `AuthProvider` (38-90)

</details>
<details>
<summary>components/react-query-client-provider.tsx (1)</summary>

* `ReactQueryClientProvider` (6-13)

</details>
<details>
<summary>lib/FetchPatchProvider.ts (1)</summary>

* `FetchPatchProvider` (6-12)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/layout.tsx (1)</summary>

`30-35`: **Provider order changed to ensure correct context access.**

The order of `AuthProvider` and `ReactQueryClientProvider` has been swapped. This is a good change as it ensures the authentication context is available to any components making API requests through React Query.

</details>
<details>
<summary>components/minikit-provider.tsx (2)</summary>

`5-9`: **Added router for navigation to error page.**

Good addition of router import and instantiation to enable error page redirection when the MiniKit validation fails.

---

`12-15`: **Added validation to ensure MiniKit is installed.**

This validation check is important to ensure users are accessing the app in the proper environment, with an appropriate redirect to the error page if requirements aren't met.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T21:40:43Z
user: login: RamRamez
body: Thanks
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-28T14:53:50Z

Review comments:
diff_hunk: @@ -6,37 +6,39 @@ export const getRelativeTimeString = (
   const now = new Date();
   const date = new Date(targetDate);
   const diff = date.getTime() - now.getTime(); // in ms
+  const isPast = diff < 0;
   const absDiff = Math.abs(diff);
 
   const minutes = Math.floor(absDiff / (1000 * 60));
-  const hours = Math.floor(minutes / 60);
-  const days = Math.floor(hours / 24);
+  const hours = Math.floor(absDiff / (1000 * 60 * 60));
+  const days = Math.floor(absDiff / (1000 * 60 * 60 * 24));
   const months = Math.floor(days / 30);
-  const years = Math.floor(months / 12);
+  // const years = Math.floor(months / 12);
 
-  let result = "";
+  let timeLeft: string = "";
 
-  if (diff > 0) {
-    if (days === 0) {
-      result = `${hours % 24}h`;
-    } else if (hours === 0) {
-      result = `${minutes % 60}m`;
+  if (!isPast) {
+    if (days >= 60) {
+      const remainingDays = days % 30;
+      timeLeft =
+        remainingDays > 0 ? `${months}m ${remainingDays}d` : `${months}m`;
+    } else if (days >= 1) {
+      const remainingHours = hours % 24;
+      timeLeft =
+        remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
+    } else if (hours >= 1) {
+      const remainingMinutes = minutes % 60;
+      timeLeft =
+        remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
     } else {
-      result = `${days}d ${hours % 24}h`;
+      timeLeft = `${minutes}m`;
     }
-  } else if (years >= 1) {
-    result = `${years}y`;
-  } else if (months >= 1) {
-    result = `${months}mo`;
-  } else if (days >= 1) {
-    result = `${days}d ${hours % 24}h`;
-  } else if (hours >= 1) {
-    result = `${hours}h ${minutes % 60}m`;
-  } else {
-    result = `${minutes}m`;
   }
path: utils/time.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Enhanced time formatting with more granular display.**

The new implementation provides more detailed and human-readable time formats based on the time difference magnitude. This creates a better user experience with more precise time information.


One edge case to consider - what happens when minutes is 0? The current code would return "0m" which might not be ideal.

---

🌐 Web query:

```web
What is the best practice for displaying time when there are 0 minutes remaining?
```

💡 Result:

## Best Practice for Displaying Time When There Are 0 Minutes Remaining

**Clear, unambiguous, and user-centered communication is essential when displaying time remaining, particularly when the countdown reaches “0 minutes.”** Best practices, drawn from usability, design systems, and data formatting guidelines, include:

### How to Display 0 Minutes Remaining

- **State “0 minutes remaining” clearly.** This provides explicit feedback to the user that the countdown has reached zero but is not negative, preventing confusion about completion timing[4][8].
- **Avoid negative or ambiguous displays.** Do not show “-1 minutes,” “expired,” or leave the field blank unless the context specifically demands it; this can mislead or confuse users about the process status or whether an action is overdue[4][8].
- **Transition to completion status if appropriate.** After 0 minutes, if the process is complete or the time is up, switch the display to a completion indicator such as “Completed,” “Time’s up,” or similar, depending on context[4][8].
- **Consistent formatting.** Use your application's standard time format (preferably ISO 8601 for digital systems), and be consistent in how time is displayed across states, so users always know what to expect[1][2][3].

### Visual and Textual Approach

- **Progress Bars:** If using visual indicators (like progress bars), ensure the bar is fully filled, and optionally add a label stating “0 minutes remaining” or “Complete”[8].
- **Textual Timers:** Use clear and readable typography for “0 minutes remaining.” If using pluralization logic, ensure it handles zero correctly (i.e., “0 minutes” not “0 minute”)[4].
- **Accessibility:** Ensure screen readers and other assistive technologies announce “0 minutes remaining” or “completed” to users who rely on them.

### Examples

- `0 minutes remaining`
- `Time’s up` (if the event or action should now cease)
- `Completed` (if the process is now done)

### Rationale

These practices ensure that users are not left wondering if time is still remaining, if the timer has malfunctioned, or if the action should be complete. Clarity at the zero-minute mark is critical for trust and a good user experience[4][8].

---

> “To clearly indicate progress to users of complex applications: Communicate percentage of work done or time remaining for any wait that exceeds 10 seconds.”  
> — Nielsen Norman Group[8]

---

**Summary:**  
Display “0 minutes remaining” when the timer hits zero, then promptly switch to a “Completed” or “Time’s up” status if appropriate. Avoid negative time values and always format time consistently[4][8][1].

Citations:

- 1: https://dataoneorg.github.io/Education/bestpractices/describe-formats-for
- 2: https://www.tinybird.co/blog-posts/database-timestamps-timezones
- 3: https://m2.material.io/design/communication/data-formats.html
- 4: https://blog.prototypr.io/expressing-time-in-ui-ux-design-5-rules-and-a-few-other-things-eda5531a41a7
- 5: https://www.w3.org/TR/timezone/
- 6: https://community.glideapps.com/t/displaying-hours-and-minute-remaining-today/55449
- 7: https://www.sitepoint.com/community/t/most-user-friendly-way-of-writing-a-date-and-time/379623
- 8: https://www.nngroup.com/articles/designing-for-waits-and-interruptions/
- 9: https://help.sumologic.com/docs/send-data/reference-information/time-reference/
- 10: https://www.secretprojects.co.uk/threads/time-and-date-display-options-poll.41284/

---



</details>

**Handle 0 minutes remaining explicitly**

Rather than falling through to `"0m"`, it’s clearer to display “0 minutes remaining” (and then switch to “Time’s up”/“Completed” once the countdown is truly over). Please update `utils/time.ts` (lines 18–36) to include a zero-minute branch. For example:

```ts
if (!isPast) {
  if (days >= 60) {
    …
  } else if (days >= 1) {
    …
  } else if (hours >= 1) {
    …
  } else if (minutes === 0) {
    timeLeft = "0 minutes remaining";
  } else {
    timeLeft = `${minutes}m`;
  }
}
```

Or, if you prefer to transition immediately to a completion state, replace `"0 minutes remaining"` with `"Time’s up"` or `"Completed"`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T21:40:34Z
author_association: NONE
diff_hunk: @@ -63,50 +61,25 @@ export default function DateTimePicker({
   const handleDateClick = (day: number, month: number, year: number) => {
     const clickedDate = new Date(year, month, day);
 
-    if (
-      !dateRange.startDate ||
-      selectionMode === "start" ||
-      (dateRange.startDate && dateRange.endDate)
-    ) {
-      // Start new selection
-      setDateRange({
-        ...dateRange,
-        startDate: clickedDate,
-        endDate: null,
-      });
-      setSelectionMode("end");
-    } else {
-      // Complete selection
-      if (clickedDate < dateRange.startDate) {
-        // If clicked date is before start date, swap them
-        setDateRange({
-          ...dateRange,
-          startDate: clickedDate,
-          endDate: dateRange.startDate,
-        });
-      } else {
-        setDateRange({
-          ...dateRange,
-          endDate: clickedDate,
-        });
-      }
-      setSelectionMode(null);
+    if (clickedDate <= today) {
+      return;
     }
+
+    setDateRange({
+      ...dateRange,
+      endDate: clickedDate,
+    });
path: components/DateTimePicker/DateTimePicker.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Potential stale-state bug when setting `endDate`**

`setDateRange` relies on the captured `dateRange` value which can be stale if multiple rapid clicks occur before React processes a re-render.  
Using the functional update form is safer:

```diff
-    setDateRange({
-      ...dateRange,
-      endDate: clickedDate,
-    });
+    setDateRange(prev => ({
+      ...prev,
+      endDate: clickedDate,
+    }));
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    if (clickedDate <= today) {
      return;
    }

    setDateRange(prev => ({
      ...prev,
      endDate: clickedDate,
    }));
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T09:27:38Z
author_association: NONE
diff_hunk: @@ -208,8 +192,10 @@ export default function DateTimePicker({
         {/* Month navigation */}
         <div className="flex items-center justify-between mb-6">
           <button
+            type="button"
             onClick={previousMonth}
-            className="p-2 rounded-full"
+            className="p-2 rounded-full disabled:opacity-25"
+            disabled={currentDate.getMonth() === today.getMonth()}
             aria-label="Previous month"
path: components/DateTimePicker/DateTimePicker.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`Previous month` button incorrectly disabled for future years**

The button becomes disabled when the displayed month number equals today’s
month, regardless of the year (e.g. April 2026 while today is April 2025).
Add a year check so only the _current_ month of the _current_ year is blocked.

```diff
-disabled={currentDate.getMonth() === today.getMonth()}
+disabled={
+  currentDate.getMonth() === today.getMonth() &&
+  currentDate.getFullYear() === today.getFullYear()
+}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            onClick={previousMonth}
            className="p-2 rounded-full disabled:opacity-25"
            disabled={
              currentDate.getMonth() === today.getMonth() &&
              currentDate.getFullYear() === today.getFullYear()
            }
            aria-label="Previous month"
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T09:27:38Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-04-24T21:38:39Z
body: [vc]: #Y5MddA5fZgXMjC/sR1ySk/bftxctRXns9bOiXCH5eGM=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ3b3JsZHZpZXctZmUiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoid29ybGR2aWV3LWZlLWdpdC1maXgtcG9zdC1xYS1pc3N1ZXMtZ2VuZXJhbC1tYWdpYy52ZXJjZWwuYXBwIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9nZW5lcmFsLW1hZ2ljL3dvcmxkdmlldy1mZS9VVmg4UnpvbWlrdzJVVkhnajRoQUE0OFlwMkxZIiwicHJldmlld1VybCI6Indvcmxkdmlldy1mZS1naXQtZml4LXBvc3QtcWEtaXNzdWVzLWdlbmVyYWwtbWFnaWMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **worldview-fe** | ✅ Ready ([Inspect](https://vercel.com/general-magic/worldview-fe/UVh8Rzomikw2UVHgj4hAA48Yp2LY)) | [Visit Preview](https://worldview-fe-git-fix-post-qa-issues-general-magic.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/worldview-fe-git-fix-post-qa-issues-general-magic.vercel.app?via=pr-comment-feedback-link) | Apr 25, 2025 9:40pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-24T21:38:41Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce new user interface restrictions and validation logic for poll creation and tag input. The poll form disables the "Add option" button and displays a message when the maximum of 20 options is reached. Tag input receives dynamic validation feedback, setting errors if tags are too short or too long. The time utility function for relative time is refactored to accept both start and end dates, improving formatting and removing past date handling. Calls to this function in poll components are updated accordingly. The number of polls fetched per page in the poll list is doubled from 10 to 20. The filter modal synchronizes its internal state with updated incoming props. The DateTimePicker component is refactored to restrict date selection to future dates only, disabling start date changes and start time picker. CalendarDay and TimePicker components add button type attributes and disabled styling. The `usePoll` hook adds a new filter state `"none"` to short-circuit fetching. A new error page component is added to handle MiniKit installation failures with redirection logic implemented in the minikit provider. The provider nesting order in the app layout is swapped for context correctness.

## Changes

| File(s)                                                          | Change Summary                                                                                                            |
|------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| components/Poll/PollForm.tsx                                      | Disables "Add option" button at 20 options, adds a message indicating max reached, updates button styling for disabled state. |
| hooks/usePollForm.ts                                              | Sets default start date/time to today/current time, adds dynamic tag input validation with error setting/clearing via useEffect. |
| utils/time.ts                                                    | Refactors `getRelativeTimeString` to accept start and end dates, changes time formatting logic, removes past date handling, updates return structure. |
| components/Poll/PollCard.tsx, PollResultsCard.tsx, PollVoteCard.tsx | Updates calls to `getRelativeTimeString` to pass both start and end dates instead of a single end date.                    |
| components/Poll/PollList.tsx                                      | Doubles `POLLS_PER_PAGE` from 10 to 20; modifies `checkIsActive` to return `"none"` when both live and finished filters are true. |
| validation/pollSchemas.ts                                         | Simplifies error message callback for tag length refinement without changing validation logic.                            |
| components/Modals/FilterModal.tsx                                | Adds useEffect to sync internal `tempFilters` state with updated incoming `filters` prop.                                 |
| components/DateTimePicker/DateTimePicker.tsx                     | Refactors to allow only future end date selection; disables start date changes and start time picker; disables past date selection and month navigation to past. |
| components/DateTimePicker/CalendarDay.tsx                        | Adds `isDisabled` prop; disables button interaction and styles cursor accordingly; adds type="button" to button.          |
| components/DateTimePicker/TimePicker.tsx                         | Adds explicit `type="button"` attributes to all buttons in the component.                                                |
| hooks/usePoll.ts                                                 | Expands `isActive` filter type to `boolean | "none"`; short-circuits poll fetching returning empty results when `"none"`. |
| types/dateTimePicker.ts                                          | Adds `isDisabled` boolean property to `CalendarDayProps` type.                                                           |
| app/error/page.tsx                                               | Adds new `ErrorPage` component displaying a message and link for users accessing the mini-app outside the World App.     |
| components/minikit-provider.tsx                                  | Adds MiniKit installation check and redirects to error page if installation fails using `useRouter`.                     |
| app/layout.tsx                                                  | Swaps the nesting order of `AuthProvider` and `ReactQueryClientProvider` components.                                     |
| components/Login/SplashScreen.tsx                               | Changes login button background color from a hex code to Tailwind CSS class `bg-gray-900`.                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant PollForm
    participant usePollForm
    participant Validation

    User->>PollForm: Click "Add option"
    PollForm->>PollForm: Check number of options
    alt Options < 20
        PollForm->>PollForm: Enable "Add option" button
    else Options >= 20
        PollForm->>PollForm: Disable "Add option" button
        PollForm->>User: Show "Maximum of 20 options reached"
    end

    User->>PollForm: Input tag
    PollForm->>usePollForm: Update tagInput
    usePollForm->>Validation: Validate tag length
    alt Tag too short or too long
        Validation->>usePollForm: Set error on "tags"
        usePollForm->>PollForm: Display error message
    else Tag valid
        Validation->>usePollForm: Clear error on "tags"
    end
```

## Possibly related PRs

- GeneralMagicio/worldview-fe#29: Modifies the provider nesting order in `app/layout.tsx`, directly related to the provider order swap in this PR.
- GeneralMagicio/worldview-fe#21: Refactors `PollForm` to use a custom hook `usePollForm` for form state and validation; related at the component level but addressing different aspects than this PR.

## Suggested reviewers

- RamRamez

## Poem

> In the warren where polls are born,  
> Twenty options—no more can adorn!  
> Tags now checked for length and might,  
> Time ticks by in granules bright.  
> Dates move forward, past is barred,  
> Buttons disabled, no clicks marred.  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=GeneralMagicio/worldview-fe&utm_content=30):

- 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](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in 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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: Meriem-BM
created_at: 2025-04-25T09:27:04Z
body: - https://github.com/GeneralMagicio/worldview-fe/issues/8#issuecomment-2824689389
- https://github.com/GeneralMagicio/worldview-fe/issues/6#issuecomment-2827957578
- https://github.com/GeneralMagicio/worldview-fe/issues/27
author_association: COLLABORATOR