diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 803d4dd..62a3c92 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -8,8 +8,8 @@ export const viewport: Viewport = {
themeColor: "#2563EB",
width: "device-width",
initialScale: 1,
- maximumScale: 1,
- userScalable: false,
+ maximumScale: 5,
+ userScalable: true,
};
export const metadata = {
diff --git a/frontend/src/app/reminders/page.tsx b/frontend/src/app/reminders/page.tsx
index bd7e1cb..e7b168a 100644
--- a/frontend/src/app/reminders/page.tsx
+++ b/frontend/src/app/reminders/page.tsx
@@ -219,7 +219,7 @@ const RemindersPage = () => {
{showAdvanceReminder && (
-
+
{
{showBarkSettings && (
-
+
{t("barkSettingsDesc")}
diff --git a/frontend/src/app/todos/page.tsx b/frontend/src/app/todos/page.tsx
index 9e34f21..b7b4534 100644
--- a/frontend/src/app/todos/page.tsx
+++ b/frontend/src/app/todos/page.tsx
@@ -271,8 +271,8 @@ const TodosPage = () => {
{todos.length > 0 ? (
- {/* Table header */}
-
+ {/* Table header - desktop only */}
+
{t("title")}
{t("due")}
@@ -284,81 +284,87 @@ const TodosPage = () => {
return (
- {/* Checkbox / Check-in */}
-
+ {/* Mobile: Row 1 - Checkbox + Title */}
+
+ {/* Checkbox / Check-in */}
+
- {/* Title + badges */}
-
-
-
- {todo.title}
-
- {todo.recurrenceRule && (
-
-
- {getRecurrenceLabel(todo.recurrenceRule)}
+ {/* Title + badges */}
+
+
+
+ {todo.title}
- )}
- {todo.checkInCount > 0 && (
-
- {todo.checkInCount}x
-
- )}
+ {todo.recurrenceRule && (
+
+
+ {getRecurrenceLabel(todo.recurrenceRule)}
+
+ )}
+ {todo.checkInCount > 0 && (
+
+ {todo.checkInCount}x
+
+ )}
+
- {/* Due date */}
-
- {formatDueDate(todo.dueAt)}
-
+ {/* Mobile: Row 2 - Due date + Status + Delete */}
+
+ {/* Due date */}
+
+ {formatDueDate(todo.dueAt)}
+
- {/* Status pill */}
-
- {status.label}
-
+ {/* Status pill */}
+
+ {status.label}
+
- {/* Delete */}
-
-
-
-
-
-
-
- {t("confirmDelete")}
-
- {t("confirmDeleteDesc", { title: todo.title })}
-
-
-
- {t("cancel")}
- deleteTodo(todo.id)}
+ {/* Delete */}
+
+
+
+
+
+
+
+
+
+ {t("confirmDelete")}
+
+ {t("confirmDeleteDesc", { title: todo.title })}
+
+
+
+ {t("cancel")}
+ deleteTodo(todo.id)}
+ >
+ {t("delete")}
+
+
+
+
+
);
@@ -384,37 +390,37 @@ const TodosPage = () => {
{/* Right column - Stats & Upcoming */}
{/* Stats cards */}
-
-
-
-
+
+
+
+
-
-
{totalTasks}
-
{t("totalTasks")}
+
+
{totalTasks}
+
{t("totalTasks")}
-
-
-
+
+
+
-
-
{checkedInCount}
-
{t("checkedInToday")}
+
+
{checkedInCount}
+
{t("checkedInToday")}
-
-
-
+
+
+
-
-
{recurringCount}
-
{t("recurringTasks")}
+
+
{recurringCount}
+
{t("recurringTasks")}
diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx
index b2f1ad0..74ca27e 100644
--- a/frontend/src/components/AppShell.tsx
+++ b/frontend/src/components/AppShell.tsx
@@ -10,10 +10,10 @@ import { UserProvider } from "@/lib/user-context";
const AppShellContent = ({ children }: { children: ReactNode }) => {
return (
-
+
-
+
diff --git a/frontend/src/components/AppSidebar.tsx b/frontend/src/components/AppSidebar.tsx
index 1008ee9..591105f 100644
--- a/frontend/src/components/AppSidebar.tsx
+++ b/frontend/src/components/AppSidebar.tsx
@@ -16,6 +16,7 @@ import {
SidebarMenuButton,
SidebarMenuItem,
SidebarSeparator,
+ useSidebar,
} from "@/components/ui/sidebar";
import Avatar from "@/components/ui/avatar";
import LanguageSwitcher from "@/components/LanguageSwitcher";
@@ -37,6 +38,42 @@ const AppSidebar = () => {
const { unreadCount } = useNotification();
const { user } = useUser();
const t = useTranslation();
+ const { isMobile } = useSidebar();
+
+ if (isMobile) {
+ return (
+
+
+
+ );
+ }
return (
diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx
index 0d9a987..81a57cf 100644
--- a/frontend/src/components/ui/sidebar.tsx
+++ b/frontend/src/components/ui/sidebar.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
@@ -8,6 +10,7 @@ type SidebarContextValue = {
open: boolean;
setOpen: (value: boolean) => void;
toggle: () => void;
+ isMobile: boolean;
};
const SidebarContext = React.createContext(null);
@@ -20,6 +23,8 @@ const useSidebar = () => {
return context;
};
+const MOBILE_BREAKPOINT = 768;
+
const SidebarProvider = ({
children,
defaultOpen = true,
@@ -28,9 +33,21 @@ const SidebarProvider = ({
defaultOpen?: boolean;
}) => {
const [open, setOpen] = React.useState(defaultOpen);
+ const [isMobile, setIsMobile] = React.useState(false);
const toggle = React.useCallback(() => setOpen((prev) => !prev), []);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = (e: MediaQueryListEvent | MediaQueryList) => {
+ setIsMobile(e.matches);
+ };
+ onChange(mql);
+ mql.addEventListener("change", onChange);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
return (
-
+
{children}
);
@@ -57,8 +74,24 @@ const sidebarVariants = cva(
const Sidebar = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes & VariantProps
->(({ className, variant, style, ...props }, ref) => {
- const { open } = useSidebar();
+>(({ className, variant, style, children, ...props }, ref) => {
+ const { open, isMobile } = useSidebar();
+
+ if (isMobile) {
+ return (
+
+ {children}
+
+ );
+ }
+
return (
>(
- ({ className, ...props }, ref) => (
-
- )
+ ({ className, ...props }, ref) => {
+ const { isMobile } = useSidebar();
+ return (
+
+ );
+ }
);
SidebarInset.displayName = "SidebarInset";