Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,11 @@
"prettier-plugin-tailwindcss": "^0.1.8",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.27.0",
"react-hot-toast": "^2.2.0",
"react-infinite-scroll-hook": "^4.0.1",
"react-intersection-observer-hook": "^2.0.4",
"react-router-dom": "^6.2.1",
"react-router-dom": "6.4.0-pre.13",
"react-swipeable": "^7.0.0",
"tailwindcss": "^3.0.11",
"tsc-files": "^1.1.3",
Expand Down
29 changes: 12 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions src/App/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { act, fireEvent, render, screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react';
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { App } from './App';
Expand Down Expand Up @@ -33,11 +32,9 @@ const queryClient = new QueryClient({

const TestApp = (): JSX.Element => {
return (
<BrowserRouter basename={process.env.PUBLIC_URL}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
};

Expand Down
70 changes: 32 additions & 38 deletions src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { ErrorFallback } from './ErrorFallback';
import { DataBrowserRouter, Navigate, Route } from 'react-router-dom';
import { ErrorPage } from './ErrorPage';
import { Layout, NotFound } from '../routes';
import { useScrollToTop } from './useScrollToTop';
import { LoadingIndicator } from './LoadingIndicator';

const Tasks = lazy(async () => import('../routes/tasks').then((module) => ({ default: module.Tasks })));
Expand All @@ -13,42 +11,38 @@ const Analytics = lazy(async () => import('../routes/analytics').then((module) =
const Settings = lazy(async () => import('../routes/settings').then((module) => ({ default: module.Settings })));

const App = (): JSX.Element => {
useScrollToTop();

// TODO: animated routes (no good story for v6 atm, see https://github.com/remix-run/react-router/issues/7117#issuecomment-949096628)
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="tasks" />} />
<Route
path="tasks/*"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Tasks />
</Suspense>
}
/>
<Route
path="analytics"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Analytics />
</Suspense>
}
/>
<Route
path="settings"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Settings />
</Suspense>
}
/>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</ErrorBoundary>
<DataBrowserRouter basename={import.meta.env.BASE_URL}>
<Route path="/" element={<Layout />} errorElement={<ErrorPage />}>
<Route index element={<Navigate to="tasks" />} />
<Route
path="tasks/*"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Tasks />
</Suspense>
}
/>
<Route
path="analytics"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Analytics />
</Suspense>
}
/>
<Route
path="settings"
element={
<Suspense fallback={<LoadingIndicator className="text-primary" />}>
<Settings />
</Suspense>
}
/>
<Route path="*" element={<NotFound />} />
</Route>
</DataBrowserRouter>
);
};

Expand Down
28 changes: 0 additions & 28 deletions src/App/ErrorFallback.tsx

This file was deleted.

19 changes: 19 additions & 0 deletions src/App/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useErrorMessage } from './useErrorMessage';

const ErrorPage = (): JSX.Element => {
const errorMessage = useErrorMessage();

return (
<main className="mx-auto flex min-h-screen max-w-2xl items-center justify-center p-3">
<div role="alert">
<div className="rounded-t bg-error px-4 py-2 font-bold text-on-error">Oops!</div>
<div className="flex flex-col rounded-b border border-t-0 border-error bg-error-container px-4 py-3 text-on-error-container">
<h2>Something went wrong:</h2>
<pre className="whitespace-pre-wrap break-all">{errorMessage}</pre>
</div>
</div>
</main>
);
};

export { ErrorPage };
18 changes: 18 additions & 0 deletions src/App/useErrorMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isRouteErrorResponse, useRouteError } from 'react-router-dom';

const useErrorMessage = (): string => {
const error = useRouteError();
let message;

if (isRouteErrorResponse(error)) {
message = `${error.status} ${error.statusText}`;
} else if (error instanceof Error) {
message = error.message;
} else {
message = JSON.stringify(error);
}

return message;
};

export { useErrorMessage };
13 changes: 5 additions & 8 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
import '@reach/dialog/styles.css';
import './index.css';
import { BrowserRouter } from 'react-router-dom';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Toaster } from 'react-hot-toast';
Expand Down Expand Up @@ -54,13 +53,11 @@ const main = async (): Promise<void> => {
const root = createRoot(container);
root.render(
<React.StrictMode>
<BrowserRouter basename={import.meta.env.BASE_URL}>
<QueryClientProvider client={queryClient}>
<App />
<Toaster position="top-center" />
<ReactQueryDevtools />
</QueryClientProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools />
</QueryClientProvider>
<Toaster position="top-center" />
</React.StrictMode>,
);

Expand Down
3 changes: 3 additions & 0 deletions src/routes/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Outlet } from 'react-router-dom';
import { useScrollToTop } from '../../App/useScrollToTop';
import { Headroom, Legroom } from '../../components/Headroom';
import { Header } from './Header';
import { BottomNavigation } from './Navigation';

const Layout = (): JSX.Element => {
useScrollToTop();

return (
// NOTE: cannot use Fragment here as it will break the sticky header
<div className="min-h-screen bg-background">
Expand Down