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:
parent
f4ae9f85aa
commit
dea7c387ea
@ -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": {
|
||||||
@ -58,4 +58,4 @@
|
|||||||
"ts-jest": "^29.0.0",
|
"ts-jest": "^29.0.0",
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
50
scripts/boot-update.sh
Executable file
50
scripts/boot-update.sh
Executable 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) ==="
|
||||||
@ -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') || '';
|
||||||
@ -262,4 +262,19 @@ export default function VerifyEmailPage() {
|
|||||||
</footer>
|
</footer>
|
||||||
</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's</div>
|
||||||
|
<p style={{ textAlign: 'center', color: '#666' }}>Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<VerifyEmailContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@ -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,55 +3238,59 @@ 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 */
|
||||||
.weekly-header .absolute {
|
.weekly-header .absolute {
|
||||||
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 {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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 {
|
.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 */
|
|
||||||
.weekly-settings-sidebar {
|
@media (max-width: 480px) {
|
||||||
width: 100%;
|
/* Phone: make header even more compact */
|
||||||
max-width: 100vw;
|
.weekly-header {
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekly-btn-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekly-logo {
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user