diff --git a/apps/frontend/src/components/hooks/use-mobile.tsx b/apps/frontend/src/components/hooks/use-mobile.tsx
new file mode 100644
index 0000000000..0bced26e0e
--- /dev/null
+++ b/apps/frontend/src/components/hooks/use-mobile.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+/**
+ * Hook to detect mobile viewport (≤768px)
+ * Returns true when viewport width is 768px or less
+ */
+export function useMobile() {
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ const checkMobile = () => {
+ setIsMobile(window.innerWidth <= 768);
+ };
+
+ // Initial check
+ checkMobile();
+
+ // Listen for resize events
+ window.addEventListener('resize', checkMobile);
+
+ return () => {
+ window.removeEventListener('resize', checkMobile);
+ };
+ }, []);
+
+ return isMobile;
+}
diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx
index 43ce7be502..7743a20ff8 100644
--- a/apps/frontend/src/components/launches/calendar.tsx
+++ b/apps/frontend/src/components/launches/calendar.tsx
@@ -14,6 +14,7 @@ import {
Integrations,
useCalendar,
} from '@gitroom/frontend/components/launches/calendar.context';
+import { useMobile } from '@gitroom/frontend/components/hooks/use-mobile';
import dayjs from 'dayjs';
import 'dayjs/locale/en';
import 'dayjs/locale/he';
@@ -318,9 +319,109 @@ export const DayView = () => {
);
};
+// Mobile-friendly week view that shows posts as cards grouped by day
+export const MobileWeekView = () => {
+ const { startDate, posts, integrations } = useCalendar();
+ const t = useT();
+ const { editPost, deletePost, openStatistics, openMissingRelease } = usePostActions();
+
+ // Generate days for the week
+ const weekDays = useMemo(() => {
+ const days = [];
+ const weekStart = newDayjs(startDate);
+ for (let i = 0; i < 7; i++) {
+ const day = weekStart.add(i, 'day');
+ days.push({
+ name: day.format('dddd'),
+ date: day.format('L'),
+ dayjs: day,
+ });
+ }
+ return days;
+ }, [startDate]);
+
+ // Group posts by day
+ const postsByDay = useMemo(() => {
+ const grouped: { [key: string]: any[] } = {};
+ weekDays.forEach((day) => {
+ const dateKey = day.dayjs.format('YYYY-MM-DD');
+ grouped[dateKey] = posts.filter((post) => {
+ const postDate = dayjs.utc(post.publishDate).local();
+ return postDate.format('YYYY-MM-DD') === dateKey;
+ });
+ });
+ return grouped;
+ }, [posts, weekDays]);
+
+ return (
+
+
+ {weekDays.map((day) => {
+ const dateKey = day.dayjs.format('YYYY-MM-DD');
+ const dayPosts = postsByDay[dateKey] || [];
+ const isToday = day.date === newDayjs().format('L');
+
+ return (
+
+ {/* Day header */}
+
+ {isToday && (
+
+ )}
+
{day.name}
+
+ {day.date}
+
+
+
+ {/* Posts for this day */}
+ {dayPosts.length === 0 ? (
+
+ {t('no_posts_scheduled', 'No posts scheduled')}
+
+ ) : (
+
+ {dayPosts.map((post) => (
+
+ ))}
+
+ )}
+
+ );
+ })}
+
+
+ );
+};
+
export const WeekView = () => {
const { startDate, endDate } = useCalendar();
const t = useT();
+ const isMobile = useMobile();
+
+ // Use mobile view on small screens
+ if (isMobile) {
+ return ;
+ }
// Use dayjs to get localized day names
const localizedDays = useMemo(() => {
@@ -390,9 +491,118 @@ export const WeekView = () => {
);
};
+// Mobile-friendly month view showing posts as list grouped by day
+export const MobileMonthView = () => {
+ const { startDate, posts, integrations } = useCalendar();
+ const t = useT();
+ const { editPost, deletePost, openStatistics, openMissingRelease } = usePostActions();
+
+ const calendarDays = useMemo(() => {
+ const monthStart = newDayjs(startDate);
+ const currentMonth = monthStart.month();
+ const currentYear = monthStart.year();
+
+ const startOfMonth = newDayjs(new Date(currentYear, currentMonth, 1));
+ const endOfMonth = startOfMonth.endOf('month');
+
+ const calendarDays = [];
+ let currentDay = startOfMonth;
+
+ while (currentDay.isSameOrBefore(endOfMonth)) {
+ calendarDays.push(currentDay);
+ currentDay = currentDay.add(1, 'day');
+ }
+
+ return calendarDays;
+ }, [startDate]);
+
+ // Group posts by day
+ const postsByDay = useMemo(() => {
+ const grouped: { [key: string]: any[] } = {};
+ calendarDays.forEach((day) => {
+ const dateKey = day.format('YYYY-MM-DD');
+ grouped[dateKey] = posts.filter((post) => {
+ const postDate = dayjs.utc(post.publishDate).local();
+ return postDate.format('YYYY-MM-DD') === dateKey;
+ });
+ });
+ return grouped;
+ }, [posts, calendarDays]);
+
+ return (
+
+
+ {calendarDays.map((day) => {
+ const dateKey = day.format('YYYY-MM-DD');
+ const dayPosts = postsByDay[dateKey] || [];
+ const isToday = day.format('L') === newDayjs().format('L');
+
+ // Only show days that have posts
+ if (dayPosts.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {/* Day header */}
+
+ {isToday && (
+
+ )}
+
{day.format('dddd')}
+
+ {day.format('L')}
+
+
+
+ {/* Posts for this day */}
+
+ {dayPosts.map((post) => (
+
+ ))}
+
+
+ );
+ })}
+
+ {calendarDays.every((day) => (postsByDay[day.format('YYYY-MM-DD')] || []).length === 0) && (
+
+ {t('no_posts_this_month', 'No posts scheduled this month')}
+
+ )}
+
+
+ );
+};
+
export const MonthView = () => {
const { startDate } = useCalendar();
const t = useT();
+ const isMobile = useMobile();
+
+ // Use mobile view on small screens
+ if (isMobile) {
+ return ;
+ }
// Use dayjs to get localized day names
const localizedDays = useMemo(() => {
@@ -564,6 +774,7 @@ export const CalendarColumn: FC<{
const { getDate, randomHour } = props;
const [num, setNum] = useState(0);
const user = useUser();
+ const isMobile = useMobile();
const {
integrations,
posts,
@@ -632,8 +843,9 @@ export const CalendarColumn: FC<{
}, []);
const [{ canDrop }, drop] = useDrop(() => ({
accept: 'post',
+ canDrop: () => !isMobile, // Disable drag-and-drop on mobile
drop: async (item: any) => {
- if (isBeforeNow) return;
+ if (isBeforeNow || isMobile) return;
// Find the post to check its state
const post = posts.find((p) => p.id === item.id);
@@ -964,6 +1176,7 @@ const CalendarItem: FC<{
};
}> = memo((props) => {
const t = useT();
+ const isMobile = useMobile();
const {
editPost,
statistics,
@@ -984,6 +1197,7 @@ const CalendarItem: FC<{
const [{ opacity }, dragRef] = useDrag(
() => ({
type: 'post',
+ canDrag: () => !isMobile, // Disable dragging on mobile
item: {
id: post.id,
interval: !!post.intervalInDays,
@@ -993,7 +1207,7 @@ const CalendarItem: FC<{
opacity: monitor.isDragging() ? 0 : 1,
}),
}),
- []
+ [isMobile]
);
return (
+
{/* Add Time Slot Section */}