From dea7c387ea66d694a276d6a6a966879b4794b5b0 Mon Sep 17 00:00:00 2001 From: mARTin Date: Fri, 27 Feb 2026 13:03:25 +0100 Subject: [PATCH] v1.7.0: Responsive mobile layout + boot update script - Add responsive CSS breakpoints (480px/768px/1024px) - Auto-adjust viewDays based on screen width - Keep time column visible on mobile (compact) - Add boot-update.sh for auto-update on reboot - Fix Suspense boundary in verify-email page - Bump version to 1.7.0 --- package.json | 4 +- scripts/boot-update.sh | 50 ++++++++ src/app/auth/verify-email/page.tsx | 19 ++- src/app/globals.css | 185 ++++++++++++++++++++++++----- src/components/WeeklyView.tsx | 30 ++++- 5 files changed, 251 insertions(+), 37 deletions(-) create mode 100755 scripts/boot-update.sh diff --git a/package.json b/package.json index 501f0cc..1036662 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.6.0", + "version": "1.7.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { @@ -58,4 +58,4 @@ "ts-jest": "^29.0.0", "typescript": "^5.0.0" } -} +} \ No newline at end of file diff --git a/scripts/boot-update.sh b/scripts/boot-update.sh new file mode 100755 index 0000000..3d21c6a --- /dev/null +++ b/scripts/boot-update.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Boot Update Script: Automatically pull latest main, build, and prepare for PM2 +# This script runs on LXC container boot BEFORE PM2 starts. +# It ensures the app is always up-to-date with the main branch. + +set -e + +APP_DIR="/root/My-Weekly-ToDo-List" +LOG_FILE="/var/log/app-boot-update.log" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "=== Boot update started ===" + +cd "$APP_DIR" + +# Check if there are remote changes +log "Fetching from origin..." +git fetch origin main 2>&1 | tee -a "$LOG_FILE" + +LOCAL=$(git rev-parse HEAD) +REMOTE=$(git rev-parse origin/main) + +if [ "$LOCAL" = "$REMOTE" ]; then + log "Already up-to-date. No rebuild needed." + log "=== Boot update finished (no changes) ===" + exit 0 +fi + +log "Changes detected. Updating from $LOCAL to $REMOTE" + +log "Pulling latest changes..." +git checkout main 2>&1 | tee -a "$LOG_FILE" +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +log "Installing dependencies..." +npm install 2>&1 | tee -a "$LOG_FILE" + +log "Updating database schema..." +npx prisma db push 2>&1 | tee -a "$LOG_FILE" + +log "Regenerating Prisma client..." +npx prisma generate 2>&1 | tee -a "$LOG_FILE" + +log "Building application..." +npm run build 2>&1 | tee -a "$LOG_FILE" + +log "=== Boot update finished (rebuilt successfully) ===" diff --git a/src/app/auth/verify-email/page.tsx b/src/app/auth/verify-email/page.tsx index dc26efb..38d17d8 100644 --- a/src/app/auth/verify-email/page.tsx +++ b/src/app/auth/verify-email/page.tsx @@ -1,10 +1,10 @@ 'use client'; -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, Suspense } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; -export default function VerifyEmailPage() { +function VerifyEmailContent() { const router = useRouter(); const searchParams = useSearchParams(); const email = searchParams.get('email') || ''; @@ -262,4 +262,19 @@ export default function VerifyEmailPage() { ); +} + +export default function VerifyEmailPage() { + return ( + +
+
My Weekly ToDo's
+

Loading...

+
+ + }> + +
+ ); } \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 61d374f..0299435 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1757,18 +1757,137 @@ h3 { background-color: transparent; } -/* Responsive */ +/* ============================================ + RESPONSIVE BREAKPOINTS + ============================================ */ + +/* === Tablet (≤ 1024px): 5-day view === */ +@media (max-width: 1024px) { + .weekly-days-grid { + grid-template-columns: repeat(5, 1fr) !important; + } + + .all-day-events-grid { + grid-template-columns: repeat(5, 1fr) !important; + } + + .weekly-someday-lists-grid { + grid-template-columns: repeat(5, 1fr) !important; + } +} + +/* === Phone landscape / small tablet (≤ 768px): 3-day view === */ @media (max-width: 768px) { .weekly-days-grid { - grid-template-columns: 1fr !important; + grid-template-columns: repeat(3, 1fr) !important; + } + + .all-day-events-grid { + grid-template-columns: repeat(3, 1fr) !important; + } + + .weekly-someday-lists-grid { + grid-template-columns: repeat(3, 1fr) !important; } .weekly-someday-lists { grid-template-columns: 1fr; } + /* Compact time column */ .time-column { - display: none; + width: 40px; + } + + .time-slot-label { + font-size: 0.55rem; + padding: 0 0.25rem; + } + + /* Compact day headers */ + .weekly-day-header { + padding: 0.5rem 0.25rem; + } + + .weekly-day-name { + font-size: 0.8rem; + } + + .weekly-day-date { + font-size: 0.7rem; + } + + /* Smaller text in time slots */ + .time-slot-task { + font-size: 0.7rem; + } + + .time-slot-event .event-title { + font-size: 0.75rem; + } + + .time-slot-event .event-time-row { + font-size: 0.65rem; + } + + /* Touch-friendly: taller minimum slot height */ + .time-slot { + min-height: 28px; + } +} + +/* === Phone portrait (≤ 480px): 1-day view === */ +@media (max-width: 480px) { + .weekly-days-grid { + grid-template-columns: 1fr !important; + } + + .all-day-events-grid { + grid-template-columns: 1fr !important; + } + + .weekly-someday-lists-grid { + grid-template-columns: 1fr !important; + } + + /* Narrower time column on phone */ + .time-column { + width: 36px; + } + + .time-slot-label { + font-size: 0.5rem; + padding: 0 2px; + } + + /* Full-width header on phone */ + .weekly-header { + padding: 0.5rem 0.75rem; + } + + .weekly-day-header { + padding: 0.5rem; + } + + /* Larger tap targets */ + .time-slot { + min-height: 32px; + padding: 0px 4px; + } + + .time-slot-task { + font-size: 0.75rem; + padding: 3px 0; + } + + /* Settings panel full-width on phone */ + .weekly-settings-sidebar { + width: 100% !important; + max-width: 100vw; + } + + .preferences-panel { + width: 100%; } } @@ -3119,55 +3238,59 @@ h3 { display: none; /* Chrome, Safari, Opera */ } -/* Responsive Design */ +/* Responsive Design - Header & Layout */ @media (max-width: 768px) { - /* Mobile/Tablet adjustment */ + /* Mobile header: stack and compact */ .weekly-header { - flex-direction: column; + flex-direction: row; + flex-wrap: wrap; height: auto; - padding: 10px; - gap: 10px; + padding: 0.5rem 0.75rem; + gap: 6px; } - + .weekly-header > div { - width: 100%; - justify-content: center; + flex-shrink: 0; } - + /* Fix overlap on mobile by removing absolute positioning */ .weekly-header .absolute { position: static !important; transform: none !important; left: auto !important; - margin: 5px 0; - } - - .weekly-btn-icon { - width: 36px; - height: 36px; + margin: 0; } - /* Stack days vertically on mobile */ + .weekly-btn-icon { + width: 32px; + height: 32px; + } + + /* Stack days vertically in list view on mobile */ .list-view .weekly-days-grid { grid-template-columns: 1fr !important; overflow-y: auto; } - .time-grid-wrapper { - flex-direction: column; - height: calc(100vh - 120px); /* Adjust for header */ - } - .weekly-day-column { min-height: auto; - border-right: none !important; - border-bottom: 1px solid #ccc; } - - /* Ensure sidebar is scrollable and accessible */ - .weekly-settings-sidebar { - width: 100%; - max-width: 100vw; +} + +@media (max-width: 480px) { + /* Phone: make header even more compact */ + .weekly-header { + padding: 0.4rem 0.5rem; + gap: 4px; + } + + .weekly-btn-icon { + width: 28px; + height: 28px; + } + + .weekly-logo { + font-size: 1rem; } } diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 5a4e8cf..b3328af 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -488,7 +488,29 @@ export default function WeeklyView() { return d; }); const [viewDays, setViewDays] = useState(7); + const savedViewDaysRef = useRef(7); // Track user's saved preference for restoring on resize const [isLoading, setIsLoading] = useState(true); + + // Responsive: auto-adjust viewDays based on screen width + useEffect(() => { + const getResponsiveViewDays = (width: number): number => { + if (width <= 480) return 1; + if (width <= 768) return 3; + if (width <= 1024) return Math.min(savedViewDaysRef.current, 5); + return savedViewDaysRef.current; + }; + + const handleResize = () => { + const responsiveDays = getResponsiveViewDays(window.innerWidth); + setViewDays(responsiveDays); + }; + + // Set initial value + handleResize(); + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); // savedViewDaysRef is a ref, so no dependency needed const [isSyncing, setIsSyncing] = useState(false); const [syncError, setSyncError] = useState(null); const syncCountRef = useRef(0); @@ -1361,7 +1383,10 @@ export default function WeeklyView() { setViewStyle(data.user.viewStyle as ViewStyle); setShowTimeGrid(data.user.showTimeGrid ?? true); } - if (data.user.viewDays !== undefined) setViewDays(data.user.viewDays); + if (data.user.viewDays !== undefined) { + setViewDays(data.user.viewDays); + savedViewDaysRef.current = data.user.viewDays; + } if (data.user.cellDuration !== undefined) setCellDuration(data.user.cellDuration as CellDuration); setShowNextTask(data.user.showNextTask || false); @@ -3481,6 +3506,7 @@ export default function WeeklyView() { key={num} onClick={() => { setViewDays(num); + savedViewDaysRef.current = num; saveSetting("viewDays", num); }} className={`px-2 py-0.5 text-xs rounded transition-colors ${viewDays === num ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`} @@ -9851,7 +9877,7 @@ function SettingsSidebar({ color: "var(--weekly-settings-label)", }} > - Version {process.env.NEXT_PUBLIC_APP_VERSION || "1.0.0"} + Version {process.env.NEXT_PUBLIC_APP_VERSION || "1.7.0"}