Compare commits
8 Commits
298ebc5fb9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| da593e052b | |||
| 37955f95ab | |||
| 466bce862e | |||
| 3043293fa7 | |||
| 1838502b8b | |||
| 6508e27391 | |||
| 15b9f196f5 | |||
| 1c5e623c7b |
@@ -3,7 +3,6 @@ import './globals.css'
|
||||
import ServiceWorkerRegistration from '@/components/ServiceWorkerRegistration'
|
||||
import OfflineIndicator from '@/components/OfflineIndicator'
|
||||
import NotificationPrompt from '@/components/NotificationPrompt'
|
||||
import NotificationDebug from '@/components/NotificationDebug'
|
||||
import { ToastProvider } from '@/components/ToastContainer'
|
||||
import ErrorBoundary from '@/components/ErrorBoundary'
|
||||
|
||||
@@ -78,7 +77,6 @@ export default function RootLayout({
|
||||
<ServiceWorkerRegistration />
|
||||
<OfflineIndicator />
|
||||
<NotificationPrompt />
|
||||
<NotificationDebug />
|
||||
{children}
|
||||
</ToastProvider>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { createItem } from '@/lib/actions'
|
||||
import { Plus } from 'lucide-react'
|
||||
@@ -18,6 +18,20 @@ export default function InlineItemCreate({ listId }: InlineItemCreateProps) {
|
||||
const [title, setTitle] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [keepFormOpen, setKeepFormOpen] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
// Focus input when form becomes visible
|
||||
useEffect(() => {
|
||||
if ((isCreating || keepFormOpen) && inputRef.current) {
|
||||
// Small delay to ensure the input is rendered
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
}, [isCreating, keepFormOpen])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -40,14 +54,21 @@ export default function InlineItemCreate({ listId }: InlineItemCreateProps) {
|
||||
// Clear form immediately for better UX
|
||||
const itemTitle = sanitizeString(title)
|
||||
setTitle('')
|
||||
setIsCreating(false)
|
||||
setKeepFormOpen(true) // Mark that we want to keep the form open
|
||||
|
||||
// Create item and refresh
|
||||
await createItem(listId, itemTitle)
|
||||
showToast('Task added successfully', 'success')
|
||||
|
||||
// Force immediate refresh
|
||||
router.refresh()
|
||||
// The revalidatePath in createItem will refresh the data
|
||||
// but we'll keep the form open due to keepFormOpen state
|
||||
|
||||
// Refocus the input after a short delay to keep keyboard open
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, 100)
|
||||
} catch (error) {
|
||||
console.error('Error creating item:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to add task'
|
||||
@@ -63,6 +84,7 @@ export default function InlineItemCreate({ listId }: InlineItemCreateProps) {
|
||||
const handleCancel = () => {
|
||||
setTitle('')
|
||||
setIsCreating(false)
|
||||
setKeepFormOpen(false) // Reset the keep form open state
|
||||
// Reset zoom on iOS Safari
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur()
|
||||
@@ -75,12 +97,16 @@ export default function InlineItemCreate({ listId }: InlineItemCreateProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
// Force viewport reset on iOS Safari
|
||||
window.scrollTo(0, 0)
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
// Only handle blur if we're not keeping the form open
|
||||
// This prevents the keyboard from closing when we want it to stay open
|
||||
if (!keepFormOpen) {
|
||||
// Force viewport reset on iOS Safari
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCreating) {
|
||||
if (!isCreating && !keepFormOpen) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setIsCreating(true)}
|
||||
@@ -96,6 +122,7 @@ export default function InlineItemCreate({ listId }: InlineItemCreateProps) {
|
||||
<form onSubmit={handleSubmit} className="bg-white border-2 border-blue-400 rounded-lg p-4 sm:p-5 shadow-sm">
|
||||
<div className="mb-3 sm:mb-4">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function NotificationDebug() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [debug, setDebug] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const info = {
|
||||
notificationSupported: 'Notification' in window,
|
||||
serviceWorkerSupported: 'serviceWorker' in navigator,
|
||||
@@ -16,6 +18,10 @@ export default function NotificationDebug() {
|
||||
setDebug(info);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 left-4 bg-black text-white text-xs p-3 rounded-lg max-w-xs z-50 font-mono">
|
||||
<div className="font-bold mb-2">Push Debug:</div>
|
||||
|
||||
@@ -8,8 +8,15 @@ export default function NotificationPrompt() {
|
||||
const { permission, isSubscribed, subscribe } = usePushNotifications();
|
||||
const [showPrompt, setShowPrompt] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) return;
|
||||
|
||||
// Check if we should show the prompt
|
||||
const shouldShow =
|
||||
'Notification' in window &&
|
||||
@@ -27,7 +34,7 @@ export default function NotificationPrompt() {
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [permission, isSubscribed, dismissed]);
|
||||
}, [mounted, permission, isSubscribed, dismissed]);
|
||||
|
||||
const handleEnable = async () => {
|
||||
const success = await subscribe();
|
||||
@@ -42,12 +49,14 @@ export default function NotificationPrompt() {
|
||||
};
|
||||
|
||||
const handleDismissForever = () => {
|
||||
localStorage.setItem('notification-prompt-dismissed', 'true');
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('notification-prompt-dismissed', 'true');
|
||||
}
|
||||
setShowPrompt(false);
|
||||
setDismissed(true);
|
||||
};
|
||||
|
||||
if (!showPrompt) {
|
||||
if (!mounted || !showPrompt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "Ultimate Lists",
|
||||
"short_name": "Lists",
|
||||
"description": "The ultimate todo list app with advanced task management",
|
||||
"description": "The ultimate todo list app with advanced task management and continuous task input",
|
||||
"version": "1.0.2",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Service Worker for Ultimate Lists App
|
||||
// Implements caching strategies for offline functionality
|
||||
|
||||
const CACHE_NAME = 'ultimate-lists-v5';
|
||||
const RUNTIME_CACHE = 'ultimate-lists-runtime-v5';
|
||||
const CACHE_NAME = 'ultimate-lists-v8';
|
||||
const RUNTIME_CACHE = 'ultimate-lists-runtime-v8';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
Reference in New Issue
Block a user