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 */}