1
- import { useForm , getFormProps } from '@conform-to/react'
2
- import { parseWithZod } from '@conform-to/zod'
3
- import { invariantResponse } from '@epic-web/invariant'
4
1
import {
5
2
json ,
6
3
type LoaderFunctionArgs ,
7
- type ActionFunctionArgs ,
8
4
type HeadersFunction ,
9
5
type LinksFunction ,
10
6
type MetaFunction ,
@@ -17,16 +13,12 @@ import {
17
13
Outlet ,
18
14
Scripts ,
19
15
ScrollRestoration ,
20
- useFetcher ,
21
- useFetchers ,
22
16
useLoaderData ,
23
- // useMatches,
24
17
useSubmit ,
25
18
} from '@remix-run/react'
26
19
import { withSentry } from '@sentry/remix'
27
20
import { useRef , useState } from 'react'
28
21
import { HoneypotProvider } from 'remix-utils/honeypot/react'
29
- import { z } from 'zod'
30
22
import { LogoIcon , MenuIcon , XIcon } from '#app/components/icons'
31
23
import SiteFooter from '#app/components/site-footer.js'
32
24
import {
@@ -35,32 +27,32 @@ import {
35
27
SheetContent ,
36
28
Sheet ,
37
29
} from '#app/components/ui/sheet'
38
- import { GeneralErrorBoundary } from '. /components/error-boundary.tsx'
39
- import { EpicProgress } from '. /components/progress-bar.tsx'
40
- import { useToast } from '. /components/toaster.tsx'
41
- import { Button } from '. /components/ui/button.tsx'
30
+ import { GeneralErrorBoundary } from '#app /components/error-boundary.tsx'
31
+ import { EpicProgress } from '#app /components/progress-bar.tsx'
32
+ import { useToast } from '#app /components/toaster.tsx'
33
+ import { Button } from '#app /components/ui/button.tsx'
42
34
import {
43
35
DropdownMenu ,
44
36
DropdownMenuContent ,
45
37
DropdownMenuItem ,
46
38
DropdownMenuPortal ,
47
39
DropdownMenuTrigger ,
48
- } from '. /components/ui/dropdown-menu.tsx'
49
- import { Icon , href as iconsHref } from '. /components/ui/icon.tsx'
50
- import { EpicToaster } from '. /components/ui/sonner.tsx'
40
+ } from '#app /components/ui/dropdown-menu.tsx'
41
+ import { Icon , href as iconsHref } from '#app /components/ui/icon.tsx'
42
+ import { EpicToaster } from '#app /components/ui/sonner.tsx'
51
43
import tailwindStyleSheetUrl from './styles/tailwind.css?url'
52
- import { getUserId , logout } from '. /utils/auth.server.ts'
53
- import { ClientHintCheck , getHints , useHints } from '. /utils/client-hints.tsx'
54
- import { prisma } from '. /utils/db.server.ts'
55
- import { getEnv } from '. /utils/env.server.ts'
56
- import { honeypot } from '. /utils/honeypot.server.ts'
57
- import { combineHeaders , getDomainUrl , getUserImgSrc } from '. /utils/misc.tsx'
58
- import { useNonce } from '. /utils/nonce-provider.ts'
59
- import { useRequestInfo } from '. /utils/request-info .ts'
60
- import { type Theme , setTheme , getTheme } from '. /utils/theme .server.ts'
61
- import { makeTimings , time } from '. /utils/timing .server.ts'
62
- import { getToast } from '. /utils/toast.server .ts'
63
- import { /* useOptionalUser, */ useUser } from './utils/user.ts '
44
+ import { getUserId , logout } from '#app /utils/auth.server.ts'
45
+ import { ClientHintCheck , getHints } from '#app /utils/client-hints.tsx'
46
+ import { prisma } from '#app /utils/db.server.ts'
47
+ import { getEnv } from '#app /utils/env.server.ts'
48
+ import { honeypot } from '#app /utils/honeypot.server.ts'
49
+ import { combineHeaders , getDomainUrl , getUserImgSrc } from '#app /utils/misc.tsx'
50
+ import { useNonce } from '#app /utils/nonce-provider.ts'
51
+ import { type Theme , getTheme } from '#app /utils/theme.server .ts'
52
+ import { makeTimings , time } from '#app /utils/timing .server.ts'
53
+ import { getToast } from '#app /utils/toast .server.ts'
54
+ import { useUser } from '#app /utils/user .ts'
55
+ import { ThemeSwitch , useTheme } from '#app/routes/resources+/theme '
64
56
65
57
export const links : LinksFunction = ( ) => {
66
58
return [
@@ -166,26 +158,6 @@ export const headers: HeadersFunction = ({ loaderHeaders }) => {
166
158
return headers
167
159
}
168
160
169
- const ThemeFormSchema = z . object ( {
170
- theme : z . enum ( [ 'system' , 'light' , 'dark' ] ) ,
171
- } )
172
-
173
- export async function action ( { request } : ActionFunctionArgs ) {
174
- const formData = await request . formData ( )
175
- const submission = parseWithZod ( formData , {
176
- schema : ThemeFormSchema ,
177
- } )
178
-
179
- invariantResponse ( submission . status === 'success' , 'Invalid theme received' )
180
-
181
- const { theme } = submission . value
182
-
183
- const responseInit = {
184
- headers : { 'set-cookie' : setTheme ( theme ) } ,
185
- }
186
- return json ( { result : submission . reply ( ) } , responseInit )
187
- }
188
-
189
161
function Document ( {
190
162
children,
191
163
nonce,
@@ -396,88 +368,6 @@ export function UserDropdown() {
396
368
)
397
369
}
398
370
399
- /**
400
- * @returns the user's theme preference, or the client hint theme if the user
401
- * has not set a preference.
402
- */
403
- export function useTheme ( ) {
404
- const hints = useHints ( )
405
- const requestInfo = useRequestInfo ( )
406
- const optimisticMode = useOptimisticThemeMode ( )
407
- if ( optimisticMode ) {
408
- return optimisticMode === 'system' ? hints . theme : optimisticMode
409
- }
410
- return requestInfo . userPrefs . theme ?? hints . theme
411
- }
412
-
413
- /**
414
- * If the user's changing their theme mode preference, this will return the
415
- * value it's being changed to.
416
- */
417
- export function useOptimisticThemeMode ( ) {
418
- const fetchers = useFetchers ( )
419
- const themeFetcher = fetchers . find ( f => f . formAction === '/' )
420
-
421
- if ( themeFetcher && themeFetcher . formData ) {
422
- const submission = parseWithZod ( themeFetcher . formData , {
423
- schema : ThemeFormSchema ,
424
- } )
425
-
426
- if ( submission . status === 'success' ) {
427
- return submission . value . theme
428
- }
429
- }
430
- }
431
-
432
- export function ThemeSwitch ( {
433
- userPreference,
434
- } : {
435
- userPreference ?: Theme | null
436
- } ) {
437
- const fetcher = useFetcher < typeof action > ( )
438
-
439
- const [ form ] = useForm ( {
440
- id : 'theme-switch' ,
441
- lastResult : fetcher . data ?. result ,
442
- } )
443
-
444
- const optimisticMode = useOptimisticThemeMode ( )
445
- const mode = optimisticMode ?? userPreference ?? 'system'
446
- const nextMode =
447
- mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
448
- const modeLabel = {
449
- light : (
450
- < Icon name = "sun" >
451
- < span className = "sr-only" > Light</ span >
452
- </ Icon >
453
- ) ,
454
- dark : (
455
- < Icon name = "moon" >
456
- < span className = "sr-only" > Dark</ span >
457
- </ Icon >
458
- ) ,
459
- system : (
460
- < Icon name = "laptop" >
461
- < span className = "sr-only" > System</ span >
462
- </ Icon >
463
- ) ,
464
- }
465
-
466
- return (
467
- < fetcher . Form method = "POST" { ...getFormProps ( form ) } >
468
- < input type = "hidden" name = "theme" value = { nextMode } />
469
- < div className = "flex gap-2" >
470
- < button
471
- type = "submit"
472
- className = "flex h-8 w-8 cursor-pointer items-center justify-center"
473
- >
474
- { modeLabel [ mode ] }
475
- </ button >
476
- </ div >
477
- </ fetcher . Form >
478
- )
479
- }
480
-
481
371
export function ErrorBoundary ( ) {
482
372
// the nonce doesn't rely on the loader so we can access that
483
373
const nonce = useNonce ( )
0 commit comments