diff --git a/app/root.tsx b/app/root.tsx index 0724225..4858898 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,5 +1,5 @@ -import type { MetaFunction, LoaderArgs, LinksFunction } from '@remix-run/node' -import { json } from '@remix-run/node' +import { MetaFunction, LoaderArgs, LinksFunction } from '@remix-run/node'; +import { json } from '@remix-run/node'; import { Links, LiveReload, @@ -7,38 +7,82 @@ import { Outlet, Scripts, ScrollRestoration, + Link, useCatch, useMatches, - useTransition -} from '@remix-run/react' -import { useEffect } from 'react' -import NProgress from 'nprogress' -import nProgressStylesUrl from 'nprogress/nprogress.css' - -import { getUser } from './session.server' + useTransition, + Form, + useLocation +} from '@remix-run/react'; +import { useEffect, useState } from 'react'; +import { + MantineProvider, + createEmotionCache, + useMantineTheme, + ColorScheme, + ColorSchemeProvider, + AppShell, + Navbar, + Header, + Text, + MediaQuery, + Burger, + ActionIcon, + useMantineColorScheme, + Button, + Box, + Group, + UnstyledButton, + ThemeIcon +} from '@mantine/core'; +import { useColorScheme, useLocalStorage } from '@mantine/hooks'; +import { StylesPlaceholder } from '@mantine/remix'; +import { + NavigationProgress, + startNavigationProgress, + completeNavigationProgress +} from '@mantine/nprogress'; +import { getUser } from './session.server'; +import { + Sun, + Moon, + Clock, + LogOut, + LogIn, + Home, + Briefcase, + BarChart2, + FileText, + Upload +} from 'react-feather'; +import { NotificationsProvider } from '@mantine/notifications'; export const meta: MetaFunction = () => ({ charset: 'utf-8', - title: 'New Remix App', + title: 'WorkTimer', + description: + 'WorkTimer is a time tracking app. Helps you track your time spent on projects.', viewport: 'width=device-width,initial-scale=1' -}) +}); export let links: LinksFunction = () => { - return [{ rel: 'stylesheet', href: nProgressStylesUrl }] -} + return []; +}; export async function loader({ request }: LoaderArgs) { return json({ user: await getUser(request) - }) + }); } +createEmotionCache({ key: 'mantine' }); + export default function App() { - let transition = useTransition() + let transition = useTransition(); useEffect(() => { - if (transition.state === 'idle') NProgress.done() - else NProgress.start() - }, [transition.state]) + if (transition.state === 'idle') completeNavigationProgress(); + else startNavigationProgress(); + }, [transition.state]); return ( @@ -46,44 +90,79 @@ export default function App() { - ) + ); } -function Document({ children, title }: { children: React.ReactNode; title?: string }) { +function Document({ + children, + title +}: { + children: React.ReactNode; + title?: string; +}) { + const preferredColorScheme = useColorScheme(); + const toggleColorScheme = (value?: ColorScheme) => { + console.log(value); + setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark')); + }; + + const [colorScheme, setColorScheme] = useLocalStorage({ + key: 'mantine-color-scheme', + defaultValue: preferredColorScheme, + getInitialValueInEffect: true + }); + return ( - - - - {title ? {title} : null} - - - - - {children} - - - {process.env.NODE_ENV === 'development' && } - - - ) + + + + + + + {title ? {title} : null} + + + + + + + {children} + + + {process.env.NODE_ENV === 'development' && } + + + + + + ); } export function CatchBoundary() { - let caught = useCatch() + let caught = useCatch(); - let message + let message; switch (caught.status) { case 401: - message = 'Oops! Looks like you tried to visit a page that you do not have access to.' + message = + 'Oops! Looks like you tried to visit a page that you do not have access to.'; - break + break; case 404: - message = 'Oops! Looks like you tried to visit a page that does not exist.' + message = + 'Oops! Looks like you tried to visit a page that does not exist.'; - break + break; default: - throw new Error(caught.data || caught.statusText) + throw new Error(caught.data || caught.statusText); } return ( @@ -97,23 +176,354 @@ export function CatchBoundary() { - ) + ); } function Layout({ children }: React.PropsWithChildren<{}>) { - let version = useMatches().find((m) => m.id === 'root')?.data?.version + let user = useMatches().find((m) => m.id === 'root')?.data?.user; + const [opened, setOpened] = useState(false); + const location = useLocation(); + const theme = useMantineTheme(); + + const { colorScheme, toggleColorScheme } = useMantineColorScheme(); + + useEffect(() => { + setOpened(false); + }, [location]); return ( - - Timer - {children} - - - ) + + + + ({ + paddingLeft: theme.spacing.xs, + paddingRight: theme.spacing.xs, + paddingBottom: theme.spacing.lg, + borderBottom: `1px solid ${ + theme.colorScheme === 'dark' + ? theme.colors.dark[4] + : theme.colors.gray[2] + }` + })} + > + + toggleColorScheme()} + size={30} + title="Toggle color scheme" + > + {colorScheme === 'dark' ? ( + + ) : ( + + )} + + + + + + {user && ( + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Home + + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Time entries + + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Projects + + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Report + + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Import + + + ({ + display: 'block', + width: '100%', + padding: theme.spacing.xs, + borderRadius: theme.radius.sm, + color: + theme.colorScheme === 'dark' + ? theme.colors.dark[0] + : theme.black, + + '&:hover': { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[6] + : theme.colors.gray[0] + } + })} + > + + + + + + Statistics + + + + )} + {user && ( + + + + + + + + + {user.email.split('@')[0]} + + Click to logout + + + + + + + )} + + ) + } + header={ + + + + setOpened((o) => !o)} + size="sm" + color={theme.colors.gray[6]} + mr="xl" + /> + + + + + + + WorkTimer + + + + + + toggleColorScheme(colorScheme === 'dark' ? 'light' : 'dark') + } + title="Toggle color scheme" + > + {colorScheme === 'dark' ? ( + + ) : ( + + )} + + + {!user && ( + } + component={Link} + to="/login" + > + Login + + )} + + + + } + styles={(theme) => ({ + main: { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.colors.dark[8] + : theme.colors.gray[0] + } + })} + > + {children} + + ); } export function ErrorBoundary({ error }: { error: Error }) { - console.error(error) + console.error(error); return ( @@ -121,9 +531,12 @@ export function ErrorBoundary({ error }: { error: Error }) { There was an error {error.message} - Hey, developer, you should replace this with what you want your users to see. + + Hey, developer, you should replace this with what you want your + users to see. + - ) + ); }
{error.message}
Hey, developer, you should replace this with what you want your users to see.
+ Hey, developer, you should replace this with what you want your + users to see. +