Skip to content

Commit

Permalink
upgrade to latest epic stack with vite, dep bump
Browse files Browse the repository at this point in the history
  • Loading branch information
thadk committed Aug 17, 2024
1 parent 2741f8f commit 37981d6
Show file tree
Hide file tree
Showing 99 changed files with 12,594 additions and 9,360 deletions.
2 changes: 1 addition & 1 deletion heat-stack/app/components/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function GeneralErrorBoundary({
</p>
),
statusHandlers,
unexpectedErrorHandler = error => <p>{getErrorMessage(error)}</p>,
unexpectedErrorHandler = (error) => <p>{getErrorMessage(error)}</p>,
}: {
defaultStatusHandler?: StatusHandler
statusHandlers?: Record<number, StatusHandler>
Expand Down
59 changes: 55 additions & 4 deletions heat-stack/app/components/forms.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { useInputControl } from '@conform-to/react'
import { REGEXP_ONLY_DIGITS_AND_CHARS, type OTPInputProps } from 'input-otp'
import React, { useId } from 'react'
import { Checkbox, type CheckboxProps } from './ui/checkbox.tsx'
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from './ui/input-otp.tsx'
import { Input } from './ui/input.tsx'
import { Label } from './ui/label.tsx'
import { Textarea } from './ui/textarea.tsx'
Expand All @@ -18,7 +25,7 @@ export function ErrorList({
if (!errorsToRender?.length) return null
return (
<ul id={id} className="flex flex-col gap-1">
{errorsToRender.map(e => (
{errorsToRender.map((e) => (
<li key={e} className="text-[10px] text-foreground-destructive">
{e}
</li>
Expand Down Expand Up @@ -57,6 +64,50 @@ export function Field({
)
}

export function OTPField({
labelProps,
inputProps,
errors,
className,
}: {
labelProps: React.LabelHTMLAttributes<HTMLLabelElement>
inputProps: Partial<OTPInputProps & { render: never }>
errors?: ListOfErrors
className?: string
}) {
const fallbackId = useId()
const id = inputProps.id ?? fallbackId
const errorId = errors?.length ? `${id}-error` : undefined
return (
<div className={className}>
<Label htmlFor={id} {...labelProps} />
<InputOTP
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
maxLength={6}
id={id}
aria-invalid={errorId ? true : undefined}
aria-describedby={errorId}
{...inputProps}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<div className="min-h-[32px] px-4 pb-3 pt-1">
{errorId ? <ErrorList id={errorId} errors={errors} /> : null}
</div>
</div>
)
}

export function TextareaField({
labelProps,
textareaProps,
Expand Down Expand Up @@ -123,15 +174,15 @@ export function CheckboxField({
aria-invalid={errorId ? true : undefined}
aria-describedby={errorId}
checked={input.value === checkedValue}
onCheckedChange={state => {
onCheckedChange={(state) => {
input.change(state.valueOf() ? checkedValue : '')
buttonProps.onCheckedChange?.(state)
}}
onFocus={event => {
onFocus={(event) => {
input.focus()
buttonProps.onFocus?.(event)
}}
onBlur={event => {
onBlur={(event) => {
input.blur()
buttonProps.onBlur?.(event)
}}
Expand Down
2 changes: 1 addition & 1 deletion heat-stack/app/components/progress-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function EpicProgress() {
.getAnimations()
.map(({ finished }) => finished)

Promise.allSettled(animationPromises).then(() => {
void Promise.allSettled(animationPromises).then(() => {
if (!delayedPending) setAnimationComplete(true)
})
}, [delayedPending])
Expand Down
2 changes: 1 addition & 1 deletion heat-stack/app/components/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function SearchBar({
method="GET"
action="/users"
className="flex flex-wrap items-center justify-center gap-2"
onChange={e => autoSubmit && handleFormChange(e.currentTarget)}
onChange={(e) => autoSubmit && handleFormChange(e.currentTarget)}
>
<div className="flex-1">
<Label htmlFor={id} className="sr-only">
Expand Down
2 changes: 1 addition & 1 deletion heat-stack/app/components/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Some components in this directory are downloaded via the
[shadcn/ui](https://ui.shadcn.com) [CLI](https://ui.shadcn.com/docs/cli). Feel
free to customize them to your needs. It's important to know that shadcn/ui is
not a library of components you install, but instead it's a registry of prebuilt
components which you can download and customize.
components which you can download and customize.
8 changes: 3 additions & 5 deletions heat-stack/app/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { Check } from 'lucide-react'
import * as React from 'react'

import { cn } from '#app/utils/misc.tsx'

Expand All @@ -26,15 +25,14 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<Check className="h-4 w-4" />
{/* <svg viewBox="0 0 8 8">
<svg viewBox="0 0 8 8">
<path
d="M1,4 L3,6 L7,2"
stroke="currentcolor"
strokeWidth="1"
fill="none"
/>
</svg> */}
</svg>
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Expand Down
16 changes: 14 additions & 2 deletions heat-stack/app/components/ui/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type SVGProps } from 'react'
import { cn } from '#app/utils/misc.tsx'
import { type IconName } from '@/icon-name'
import href from './icons/sprite.svg'
import { type IconName } from '@/icon-name'

export { href }
export { IconName }
Expand Down Expand Up @@ -33,23 +33,34 @@ const childrenSizeClassName = {
* Alternatively, if you're not ok with the icon being to the left of the text,
* you need to wrap the icon and text in a common parent and set the parent to
* display "flex" (or "inline-flex") with "items-center" and a reasonable gap.
*
* Pass `title` prop to the `Icon` component to get `<title>` element rendered
* in the SVG container, providing this way for accessibility.
*/
export function Icon({
name,
size = 'font',
className,
title,
children,
...props
}: SVGProps<SVGSVGElement> & {
name: IconName
size?: Size
title?: string
}) {
if (children) {
return (
<span
className={`inline-flex items-center ${childrenSizeClassName[size]}`}
>
<Icon name={name} size={size} className={className} {...props} />
<Icon
name={name}
size={size}
className={className}
title={title}
{...props}
/>
{children}
</span>
)
Expand All @@ -59,6 +70,7 @@ export function Icon({
{...props}
className={cn(sizeClassName[size], 'inline self-center', className)}
>
{title ? <title>{title}</title> : null}
<use href={`${href}#${name}`} />
</svg>
)
Expand Down
70 changes: 70 additions & 0 deletions heat-stack/app/components/ui/input-otp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { OTPInput, OTPInputContext } from 'input-otp'
import * as React from 'react'

import { cn } from '#app/utils/misc.tsx'

const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-[:disabled]:opacity-50',
containerClassName,
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
))
InputOTP.displayName = 'InputOTP'

const InputOTPGroup = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center', className)} {...props} />
))
InputOTPGroup.displayName = 'InputOTPGroup'

const InputOTPSlot = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext)
const slot = inputOTPContext.slots[index]
if (!slot) throw new Error('Invalid slot index')
const { char, hasFakeCaret, isActive } = slot

return (
<div
ref={ref}
className={cn(
'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'z-10 ring-2 ring-ring ring-offset-background',
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
})
InputOTPSlot.displayName = 'InputOTPSlot'

const InputOTPSeparator = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
-
</div>
))
InputOTPSeparator.displayName = 'InputOTPSeparator'

export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
25 changes: 19 additions & 6 deletions heat-stack/app/components/ui/status-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,31 @@ export const StatusButton = React.forwardRef<
})
const companion = {
pending: delayedPending ? (
<div className="inline-flex h-6 w-6 items-center justify-center">
<Icon name="update" className="animate-spin" />
<div
role="status"
className="inline-flex h-6 w-6 items-center justify-center"
>
<Icon name="update" className="animate-spin" title="loading" />
</div>
) : null,
success: (
<div className="inline-flex h-6 w-6 items-center justify-center">
<Icon name="check" />
<div
role="status"
className="inline-flex h-6 w-6 items-center justify-center"
>
<Icon name="check" title="success" />
</div>
),
error: (
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-destructive">
<Icon name="cross-1" className="text-destructive-foreground" />
<div
role="status"
className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-destructive"
>
<Icon
name="cross-1"
className="text-destructive-foreground"
title="error"
/>
</div>
),
idle: null,
Expand Down
2 changes: 1 addition & 1 deletion heat-stack/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'

if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
import('./utils/monitoring.client.tsx').then(({ init }) => init())
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
}

startTransition(() => {
Expand Down
31 changes: 21 additions & 10 deletions heat-stack/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
} from '@remix-run/node'
import { RemixServer } from '@remix-run/react'
import * as Sentry from '@sentry/remix'
import chalk from 'chalk'
import { isbot } from 'isbot'
import { getInstanceInfo } from 'litefs-js'
import { renderToPipeableStream } from 'react-dom/server'
import { getEnv, init } from './utils/env.server.ts'
import { getInstanceInfo } from './utils/litefs.server.ts'
import { NonceProvider } from './utils/nonce-provider.ts'
import { makeTimings } from './utils/timing.server.ts'

Expand All @@ -19,10 +20,6 @@ const ABORT_DELAY = 5000
init()
global.ENV = getEnv()

if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
import('./utils/monitoring.server.ts').then(({ init }) => init())
}

type DocRequestArgs = Parameters<HandleDocumentRequestFunction>

export default async function handleRequest(...args: DocRequestArgs) {
Expand All @@ -39,11 +36,15 @@ export default async function handleRequest(...args: DocRequestArgs) {
responseHeaders.set('fly-primary-instance', primaryInstance)
responseHeaders.set('fly-instance', currentInstance)

if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) {
responseHeaders.append('Document-Policy', 'js-profiling')
}

const callbackName = isbot(request.headers.get('user-agent'))
? 'onAllReady'
: 'onShellReady'

const nonce = String(loadContext.cspNonce) ?? undefined
const nonce = loadContext.cspNonce?.toString() ?? ''
return new Promise(async (resolve, reject) => {
let didError = false
// NOTE: this timing will only include things that are rendered in the shell
Expand All @@ -70,10 +71,8 @@ export default async function handleRequest(...args: DocRequestArgs) {
onShellError: (err: unknown) => {
reject(err)
},
onError: (error: unknown) => {
onError: () => {
didError = true

console.error(error)
},
nonce,
},
Expand All @@ -97,9 +96,21 @@ export function handleError(
error: unknown,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
// Skip capturing if the request is aborted as Remix docs suggest
// Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
if (request.signal.aborted) {
return
}
if (error instanceof Error) {
Sentry.captureRemixServerException(error, 'remix.server', request)
console.error(chalk.red(error.stack))
void Sentry.captureRemixServerException(
error,
'remix.server',
request,
true,
)
} else {
console.error(chalk.red(error))
Sentry.captureException(error)
}
}
Loading

0 comments on commit 37981d6

Please sign in to comment.