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
This commit is contained in:
mARTin 2026-02-27 13:03:25 +01:00
parent f4ae9f85aa
commit dea7c387ea
5 changed files with 251 additions and 37 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "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", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

50
scripts/boot-update.sh Executable file
View File

@ -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) ==="

View File

@ -1,10 +1,10 @@
'use client'; 'use client';
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
export default function VerifyEmailPage() { function VerifyEmailContent() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const email = searchParams.get('email') || ''; const email = searchParams.get('email') || '';
@ -263,3 +263,18 @@ export default function VerifyEmailPage() {
</div> </div>
); );
} }
export default function VerifyEmailPage() {
return (
<Suspense fallback={
<div className="weekly-auth-container">
<div className="weekly-auth-card">
<div className="weekly-auth-logo">My Weekly ToDo&apos;s</div>
<p style={{ textAlign: 'center', color: '#666' }}>Loading...</p>
</div>
</div>
}>
<VerifyEmailContent />
</Suspense>
);
}

View File

@ -1757,18 +1757,137 @@ h3 {
background-color: transparent; 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) { @media (max-width: 768px) {
.weekly-days-grid { .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 { .weekly-someday-lists {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
/* Compact time column */
.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,19 +3238,19 @@ h3 {
display: none; /* Chrome, Safari, Opera */ display: none; /* Chrome, Safari, Opera */
} }
/* Responsive Design */ /* Responsive Design - Header & Layout */
@media (max-width: 768px) { @media (max-width: 768px) {
/* Mobile/Tablet adjustment */ /* Mobile header: stack and compact */
.weekly-header { .weekly-header {
flex-direction: column; flex-direction: row;
flex-wrap: wrap;
height: auto; height: auto;
padding: 10px; padding: 0.5rem 0.75rem;
gap: 10px; gap: 6px;
} }
.weekly-header > div { .weekly-header > div {
width: 100%; flex-shrink: 0;
justify-content: center;
} }
/* Fix overlap on mobile by removing absolute positioning */ /* Fix overlap on mobile by removing absolute positioning */
@ -3139,35 +3258,39 @@ h3 {
position: static !important; position: static !important;
transform: none !important; transform: none !important;
left: auto !important; left: auto !important;
margin: 5px 0; margin: 0;
} }
.weekly-btn-icon { .weekly-btn-icon {
width: 36px; width: 32px;
height: 36px; height: 32px;
} }
/* Stack days vertically on mobile */ /* Stack days vertically in list view on mobile */
.list-view .weekly-days-grid { .list-view .weekly-days-grid {
grid-template-columns: 1fr !important; grid-template-columns: 1fr !important;
overflow-y: auto; overflow-y: auto;
} }
.time-grid-wrapper {
flex-direction: column;
height: calc(100vh - 120px); /* Adjust for header */
}
.weekly-day-column { .weekly-day-column {
min-height: auto; min-height: auto;
border-right: none !important; }
border-bottom: 1px solid #ccc;
} }
/* Ensure sidebar is scrollable and accessible */ @media (max-width: 480px) {
.weekly-settings-sidebar { /* Phone: make header even more compact */
width: 100%; .weekly-header {
max-width: 100vw; padding: 0.4rem 0.5rem;
gap: 4px;
}
.weekly-btn-icon {
width: 28px;
height: 28px;
}
.weekly-logo {
font-size: 1rem;
} }
} }

View File

@ -488,7 +488,29 @@ export default function WeeklyView() {
return d; return d;
}); });
const [viewDays, setViewDays] = useState(7); const [viewDays, setViewDays] = useState(7);
const savedViewDaysRef = useRef(7); // Track user's saved preference for restoring on resize
const [isLoading, setIsLoading] = useState(true); 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 [isSyncing, setIsSyncing] = useState(false);
const [syncError, setSyncError] = useState<string | null>(null); const [syncError, setSyncError] = useState<string | null>(null);
const syncCountRef = useRef(0); const syncCountRef = useRef(0);
@ -1361,7 +1383,10 @@ export default function WeeklyView() {
setViewStyle(data.user.viewStyle as ViewStyle); setViewStyle(data.user.viewStyle as ViewStyle);
setShowTimeGrid(data.user.showTimeGrid ?? true); 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) if (data.user.cellDuration !== undefined)
setCellDuration(data.user.cellDuration as CellDuration); setCellDuration(data.user.cellDuration as CellDuration);
setShowNextTask(data.user.showNextTask || false); setShowNextTask(data.user.showNextTask || false);
@ -3481,6 +3506,7 @@ export default function WeeklyView() {
key={num} key={num}
onClick={() => { onClick={() => {
setViewDays(num); setViewDays(num);
savedViewDaysRef.current = num;
saveSetting("viewDays", 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"}`} 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)", 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"}
</p> </p>
</div> </div>
</div> </div>