diff --git a/.env b/.env index 63e92e2..7a5bc87 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ VITE_APP_API_URL=http://localhost:2021 -VITE_IS_LANDING_DEFAULT=false +VITE_IS_LANDING_DEFAULT=true VITE_OPENREPLAY_PROJECT_KEY= VITE_IS_SELF_HOSTED=false \ No newline at end of file diff --git a/.env.development b/.env.development index cc21ca0..e677275 100644 --- a/.env.development +++ b/.env.development @@ -2,3 +2,4 @@ VITE_APP_API_URL=http://localhost:2021 VITE_APP_REDIRECT_URL=http://localhost:5173 VITE_APP_GOOGLE_CLIENT_ID=USE_YOUR_OWN_CLIENT_ID VITE_IS_SELF_HOSTED=true +VITE_IS_LANDING_DEFAULT=true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9592487..6654637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "donetick", - "version": "0.1.110", + "version": "0.1.145", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "donetick", - "version": "0.1.110", + "version": "0.1.145", "dependencies": { "@capacitor-community/sqlite": "^7.0.0", "@capacitor/android": "^7.0.0", @@ -37,6 +37,7 @@ "@tanstack/react-query": "^5.17.0", "aos": "^2.3.4", "browser-image-compression": "^2.0.2", + "caniuse-lite": "^1.0.30001769", "capacitor-plugin-safe-area": "^4.0.0", "chrono-node": "^2.7.7", "dotenv": "^16.4.5", @@ -55,6 +56,7 @@ "react-dom": "^18.2.0", "react-easy-crop": "^5.4.2", "react-router-dom": "^6.21.1", + "react-swipeable-list": "^1.10.0", "react-transition-group": "^4.4.5", "reactjs-social-login": "^2.6.3", "recharts": "^2.15.0", @@ -64,14 +66,13 @@ "devDependencies": { "@capacitor/assets": "^3.0.5", "@capacitor/cli": "^7.0.0", - "@rollup/rollup-darwin-arm64": "^4.50.0", - "@swc/core-darwin-arm64": "^1.13.5", "@tanstack/eslint-plugin-query": "^5.14.6", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@vite-pwa/assets-generator": "^0.2.4", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.16", + "baseline-browser-mapping": "^2.9.19", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.2", @@ -92,12 +93,6 @@ "engines": { "node": ">=20.0.0", "npm": ">=9.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-darwin-arm64": "^4.46.1", - "@rollup/rollup-linux-x64-gnu": "^4.50.0", - "@swc/core-darwin-arm64": "^1.13.3", - "@swc/core-linux-x64-gnu": "^1.13.3" } }, "node_modules/@alloc/quick-lru": { @@ -3196,8 +3191,8 @@ "cpu": [ "arm64" ], - "devOptional": true, "license": "MIT", + "optional": true, "os": [ "darwin" ] @@ -3258,8 +3253,8 @@ "cpu": [ "arm64" ], - "devOptional": true, "license": "Apache-2.0 AND MIT", + "optional": true, "os": [ "darwin" ], @@ -4696,27 +4691,6 @@ "postcss": "^8.1.0" } }, - "node_modules/autoprefixer/node_modules/caniuse-lite": { - "version": "1.0.30001739", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", - "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4813,6 +4787,16 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/big-integer": { "version": "1.6.52", "dev": true, @@ -4938,26 +4922,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/browserslist/node_modules/caniuse-lite": { - "version": "1.0.30001739", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz", - "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/buffer": { "version": "5.7.1", "devOptional": true, @@ -5080,6 +5044,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/capacitor-plugin-safe-area": { "version": "4.0.0", "license": "MIT", @@ -10830,6 +10814,12 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-swipeable-list": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/react-swipeable-list/-/react-swipeable-list-1.10.0.tgz", + "integrity": "sha512-Ap85a3c5sgvglZBlaansfT6VfwuMad5U4puxFW4sJbUU6alE7epzW39CHvs5AohUZi7wjlP1KFunpXoD+iUyGQ==", + "license": "MIT" + }, "node_modules/react-transition-group": { "version": "4.4.5", "license": "BSD-3-Clause", diff --git a/src/App.jsx b/src/App.jsx index 32f888b..3fb77d6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,13 +2,13 @@ import NavBar from '@/views/components/NavBar' import { Button, Typography, useColorScheme } from '@mui/joy' import Tracker from '@openreplay/tracker' import { useCallback, useEffect } from 'react' -import { Outlet, useNavigate } from 'react-router-dom' +import { Outlet } from 'react-router-dom' import { useRegisterSW } from 'virtual:pwa-register/react' import { registerCapacitorListeners } from './CapacitorListener' import PageTransition from './components/animations/PageTransition' -import { AuthProvider } from './hooks/useAuth.jsx' import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext' import SSEProvider from './contexts/SSEContext' +import { AuthProvider } from './hooks/useAuth.jsx' import { useNotification } from './service/NotificationProvider' import NetworkBanner from './views/components/NetworkBanner' @@ -32,7 +32,6 @@ const startOpenReplay = () => { tracker.start() } - const AppContent = () => { const { showNotification } = useNotification() diff --git a/src/hooks/useSSE.js b/src/hooks/useSSE.js index 804d759..8a63f20 100644 --- a/src/hooks/useSSE.js +++ b/src/hooks/useSSE.js @@ -18,9 +18,9 @@ const MAX_RECONNECT_ATTEMPTS = 10 // Circuit breaker limit const CIRCUIT_BREAKER_RESET_TIME = 600000 // 10 minutes export const useSSE = () => { - const { data: userProfile } = useUserProfile() - const { isAuthenticated, token } = useAuth() + // Only fetch user profile if authenticated - prevents unnecessary API calls on landing page + const { data: userProfile } = useUserProfile() const [connectionState, setConnectionState] = useState(SSE_STATES.CLOSED) const [lastEvent, setLastEvent] = useState(null) const [error, setError] = useState(null) diff --git a/src/queries/ResourceQueries.jsx b/src/queries/ResourceQueries.jsx index 21a3679..8f79f31 100644 --- a/src/queries/ResourceQueries.jsx +++ b/src/queries/ResourceQueries.jsx @@ -1,6 +1,17 @@ import { useQuery } from '@tanstack/react-query' import { GetResource } from '../utils/Fetcher' +// Helper to check if we have a valid token +const isTokenValid = () => { + const token = localStorage.getItem('token') + if (!token) return false + + const expiry = localStorage.getItem('token_expiry') + if (!expiry) return true // No expiry set, assume valid + + return new Date() < new Date(expiry) +} + export const useResource = () => { const { data, isLoading, error } = useQuery({ queryKey: ['resource'], @@ -11,6 +22,7 @@ export const useResource = () => { staleTime: 6 * 60 * 60 * 1000, // 6 hours in milliseconds refetchOnWindowFocus: false, refetchOnReconnect: false, + enabled: isTokenValid(), // Only run query when we have a valid token }) return { data, isLoading, error } } diff --git a/src/queries/UserQueries.jsx b/src/queries/UserQueries.jsx index 9ffe727..160de07 100644 --- a/src/queries/UserQueries.jsx +++ b/src/queries/UserQueries.jsx @@ -54,6 +54,7 @@ export const useUserProfile = () => { }, staleTime: 30 * 60 * 1000, // 30 minutes in milliseconds gcTime: 30 * 60 * 1000, // 30 minutes in milliseconds + enabled: isTokenValid(), // Only run query when we have a valid token }) return { data, diff --git a/src/views/components/NavBar.jsx b/src/views/components/NavBar.jsx index deb83b8..c83e1d1 100644 --- a/src/views/components/NavBar.jsx +++ b/src/views/components/NavBar.jsx @@ -103,7 +103,18 @@ import Z_INDEX from '../../constants/zIndex' import { useResource } from '../../queries/ResourceQueries' import { apiClient } from '../../utils/ApiClient' -const publicPages = ['/landing', '/privacy', '/terms'] +const publicPages = [ + '/landing', + '/privacy', + '/terms', + 'login', + '/signup', + '/auth/oauth2', + '/forgot-password', + '/login/settings', + '/', +] + const NavBar = () => { const { data: resource } = useResource() @@ -165,15 +176,7 @@ const NavBar = () => { ) } - if ( - [ - '/signup', - '/login', - '/auth/oauth2', - '/forgot-password', - '/login/settings', - ].includes(location.pathname) - ) { + if (publicPages.includes(location.pathname)) { return ( // no navbar but show the safe area padding