From 31be0f41e91c30e3946bf139b13210dbd51e5aa1 Mon Sep 17 00:00:00 2001 From: Samuel Jensen <44519206+nichtsam@users.noreply.github.com> Date: Sat, 28 Dec 2024 18:17:55 +0800 Subject: [PATCH] basic setting --- .env.example | 5 + app/components/form.tsx | 26 ++ app/components/status-button.tsx | 34 +++ app/components/ui/badge.tsx | 36 +++ app/components/ui/command.tsx | 151 +++++++++++ app/components/ui/dialog.tsx | 120 +++++++++ app/components/ui/label.tsx | 24 ++ app/components/ui/popover.tsx | 31 +++ app/components/ui/select.tsx | 157 ++++++++++++ app/root.tsx | 2 +- app/routes/settings.tsx | 340 +++++++++++++++++++++++++ app/routes/translate.tsx | 119 +++++---- app/utils/request.server.ts | 3 + app/utils/translation.ts | 105 ++++++-- app/utils/translator.server.ts | 52 +++- package.json | 12 +- pnpm-lock.yaml | 413 ++++++++++++++++++++++++++++--- 17 files changed, 1513 insertions(+), 117 deletions(-) create mode 100644 app/components/form.tsx create mode 100644 app/components/status-button.tsx create mode 100644 app/components/ui/badge.tsx create mode 100644 app/components/ui/command.tsx create mode 100644 app/components/ui/dialog.tsx create mode 100644 app/components/ui/label.tsx create mode 100644 app/components/ui/popover.tsx create mode 100644 app/components/ui/select.tsx create mode 100644 app/routes/settings.tsx create mode 100644 app/utils/request.server.ts diff --git a/.env.example b/.env.example index aa95197..ed4b770 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,6 @@ +# generate with `openssl rand -hex 32` +SESSION_SECRET=SESSION_SECRET + +# DeepL > Account > API Keys > Create key +# https://www.deepl.com/en/your-account/keys DEEPL_KEY=MOCK_DEEPL_KEY diff --git a/app/components/form.tsx b/app/components/form.tsx new file mode 100644 index 0000000..779b29b --- /dev/null +++ b/app/components/form.tsx @@ -0,0 +1,26 @@ +import { cn } from '#app/utils/ui.ts' +import { Badge } from './ui/badge.tsx' + +export type Errors = (string | null | undefined)[] | null | undefined +export interface ErrorListProps { + className?: string + errors?: Errors + errorId?: string +} +export const ErrorList = ({ errorId, errors, className }: ErrorListProps) => { + const errorsToRender = errors?.filter(Boolean) + if (!errorsToRender?.length) return null + + return ( + + ) +} diff --git a/app/components/status-button.tsx b/app/components/status-button.tsx new file mode 100644 index 0000000..9470d0c --- /dev/null +++ b/app/components/status-button.tsx @@ -0,0 +1,34 @@ +import { CheckCircle, CircleX, LoaderCircle } from 'lucide-react' +import { forwardRef } from 'react' +import { cn } from '#app/utils/ui.ts' +import { type ButtonProps, Button } from './ui/button.tsx' + +export interface StatusButtonProps extends ButtonProps { + status: 'success' | 'pending' | 'error' | 'idle' +} +export const StatusButton = forwardRef( + ({ status, className, children, ...props }, ref) => { + const statusIcon = { + success: , + pending: , + error: ( + + ), + idle: null, + }[status] + + return ( + + ) + }, +) + +StatusButton.displayName = 'StatusButton' diff --git a/app/components/ui/badge.tsx b/app/components/ui/badge.tsx new file mode 100644 index 0000000..40b721b --- /dev/null +++ b/app/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '#app/utils/ui.ts' + +const badgeVariants = cva( + 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', + outline: 'text-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/app/components/ui/command.tsx b/app/components/ui/command.tsx new file mode 100644 index 0000000..8619f69 --- /dev/null +++ b/app/components/ui/command.tsx @@ -0,0 +1,151 @@ +import { type DialogProps } from '@radix-ui/react-dialog' +import { Command as CommandPrimitive } from 'cmdk' +import { Search } from 'lucide-react' +import * as React from 'react' + +import { Dialog, DialogContent } from '#app/components/ui/dialog' +import { cn } from '#app/utils/ui.ts' + +const Command = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = 'CommandShortcut' + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/app/components/ui/dialog.tsx b/app/components/ui/dialog.tsx new file mode 100644 index 0000000..e91b5c6 --- /dev/null +++ b/app/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as DialogPrimitive from '@radix-ui/react-dialog' +import { X } from 'lucide-react' +import * as React from 'react' + +import { cn } from '#app/utils/ui.ts' + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = 'DialogHeader' + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = 'DialogFooter' + +const DialogTitle = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx new file mode 100644 index 0000000..307e5b9 --- /dev/null +++ b/app/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as LabelPrimitive from '@radix-ui/react-label' +import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '#app/utils/ui.ts' + +const labelVariants = cva( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', +) + +const Label = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/app/components/ui/popover.tsx b/app/components/ui/popover.tsx new file mode 100644 index 0000000..7df3a2a --- /dev/null +++ b/app/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as PopoverPrimitive from '@radix-ui/react-popover' +import * as React from 'react' + +import { cn } from '#app/utils/ui.ts' + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/app/components/ui/select.tsx b/app/components/ui/select.tsx new file mode 100644 index 0000000..03824a4 --- /dev/null +++ b/app/components/ui/select.tsx @@ -0,0 +1,157 @@ +import * as SelectPrimitive from '@radix-ui/react-select' +import { Check, ChevronDown, ChevronUp } from 'lucide-react' +import * as React from 'react' + +import { cn } from '#app/utils/ui.ts' + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1', + className, + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = 'popper', ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/app/root.tsx b/app/root.tsx index 88d1989..ce64fd1 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -35,7 +35,7 @@ export function Layout({ children }: { children: React.ReactNode }) { export default function App() { return ( -
+
diff --git a/app/routes/settings.tsx b/app/routes/settings.tsx new file mode 100644 index 0000000..3d1969f --- /dev/null +++ b/app/routes/settings.tsx @@ -0,0 +1,340 @@ +import { + type FieldMetadata, + getFormProps, + getSelectProps, + useForm, + useInputControl, +} from '@conform-to/react' +import { getZodConstraint, parseWithZod } from '@conform-to/zod' +import { + type LoaderFunctionArgs, + type ActionFunctionArgs, + data, + HeadersFunction, + redirect, +} from '@remix-run/node' +import { + Form, + useActionData, + useFetcher, + useLoaderData, +} from '@remix-run/react' +import { Check, ChevronsUpDown } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import { z } from 'zod' +import { ErrorList } from '#app/components/form.tsx' +import { Button } from '#app/components/ui/button.tsx' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '#app/components/ui/command.tsx' +import { Label } from '#app/components/ui/label.tsx' +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '#app/components/ui/popover.tsx' +import { + SourceLangCode, + sourceLangConfig, + TargetLangCode, + targetLangConfig, +} from '#app/utils/translation.ts' +import { + getSettingsSession, + settingsSessionStorage, +} from '#app/utils/translator.server.ts' +import { cn } from '#app/utils/ui.ts' + +const schema = z.object({ + sourceLanguage: SourceLangCode.default('detect'), + targetLanguages: z + .array(TargetLangCode) + .min(1, 'Target Languages must contain at least 1 language'), +}) + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const settingsSession = await getSettingsSession(request) + const settings = settingsSession.data + + return { + settings, + } +} + +export const action = async ({ request }: ActionFunctionArgs) => { + const formData = await request.formData() + const submission = parseWithZod(formData, { schema }) + if (submission.status !== 'success') { + return data( + { result: submission.reply() }, + { status: submission.status === 'error' ? 400 : 200 }, + ) + } + + const { sourceLanguage, targetLanguages } = submission.value + const settingsSession = await getSettingsSession(request) + settingsSession.set('sourceLanguage', sourceLanguage) + settingsSession.set('targetLanguages', targetLanguages) + + return redirect('/translate', { + headers: { + 'Set-Cookie': await settingsSessionStorage.commitSession(settingsSession), + }, + }) +} + +export default function Setting() { + const data = useLoaderData() + const actionData = useActionData() + const [form, fields] = useForm({ + defaultValue: data.settings, + lastResult: actionData?.result, + constraint: getZodConstraint(schema), + onValidate: ({ formData }) => parseWithZod(formData, { schema }), + }) + + console.log({ + data, + actionData, + formValue: form.value, + dirty: form.dirty, + }) + + return ( +
+

Translation Configuration

+ +
+ + + + + +
+ ) +} + +function SourceLanguageField({ + field, +}: { + field: FieldMetadata['sourceLanguage']> +}) { + const [open, setOpen] = useState(false) + const inputRef = useRef(null) + const control = useInputControl(field) + const value = control.value + const labelId = `label-${field.id}` + + return ( +
+ + + + + + + + + + + + No language found. + + {SourceLangCode.options + .sort((a, b) => { + if (a === SourceLangCode.enum.detect) { + return -1 + } else if (b === SourceLangCode.enum.detect) { + return 1 + } + const A = sourceLangConfig[a].label + const B = sourceLangConfig[b].label + return A > B ? 1 : -1 + }) + .map((code) => ( + { + control.change(code) + setOpen(false) + }} + > + {sourceLangConfig[code].label} + + + ))} + + + + + +
+ ) +} + +function TargetLanguagesField({ + field, +}: { + field: FieldMetadata['targetLanguages']> +}) { + const [open, setOpen] = useState(false) + const inputRef = useRef(null) + const control = useInputControl(field) + const value = + typeof control.value === 'string' ? [control.value] : control.value + const labelId = `label-${field.id}` + + return ( +
+ + + + + + + + + + + + + No results found. + + {TargetLangCode.options + .sort((a, b) => { + const A = targetLangConfig[a].label + const B = targetLangConfig[b].label + return A > B ? 1 : -1 + }) + .map((code) => ( + { + if (!value) { + control.change([code]) + return + } + if (value.includes(code)) { + control.change(value.filter((v) => v !== code)) + } else { + control.change([...value, code]) + } + }} + > + {targetLangConfig[code].label} + + + ))} + + + + + +
+ ) +} diff --git a/app/routes/translate.tsx b/app/routes/translate.tsx index f2401d6..020b1b1 100644 --- a/app/routes/translate.tsx +++ b/app/routes/translate.tsx @@ -1,15 +1,13 @@ -import { - getCollectionProps, - getFormProps, - getTextareaProps, - useForm, -} from '@conform-to/react' +import { getFormProps, getTextareaProps, useForm } from '@conform-to/react' import { getZodConstraint, parseWithZod } from '@conform-to/zod' -import { data, type LoaderFunctionArgs } from '@remix-run/node' -import { Form, useActionData } from '@remix-run/react' -import { AlertCircle, ArrowUp, LoaderCircle } from 'lucide-react' +import { + type LoaderFunctionArgs, + type ActionFunctionArgs, + data, +} from '@remix-run/node' +import { Form, Link, useActionData, useLoaderData } from '@remix-run/react' +import { AlertCircle, ArrowUp, LoaderCircle, Settings2 } from 'lucide-react' import { z } from 'zod' -import { Toggle } from '#app/components/toggle.tsx' import { Alert, AlertDescription, @@ -18,8 +16,8 @@ import { import { Button } from '#app/components/ui/button.tsx' import { Card, CardContent } from '#app/components/ui/card.tsx' import { ScrollArea } from '#app/components/ui/scroll-area.tsx' -import { targetLangConfigs, targetLangs } from '#app/utils/translation.ts' -import { Translator } from '#app/utils/translator.server.ts' +import { targetLangConfig } from '#app/utils/translation.ts' +import { getSettingsSession, Translator } from '#app/utils/translator.server.ts' import { useIsPending } from '#app/utils/ui.ts' export const schema = z.object({ @@ -28,12 +26,25 @@ export const schema = z.object({ required_error: 'Expression is required for an polyglotization!', }) .max(120, 'Expression is limited to 120 characters.'), - languages: z - .array(z.enum(targetLangs)) - .min(1, 'At least one of the target languages must be selected!'), }) -export const action = async ({ request }: LoaderFunctionArgs) => { +export const loader = async ({ request }: LoaderFunctionArgs) => { + const settings = await getSettingsSession(request) + + return { + valid: !!settings.data.targetLanguages, + } +} + +export const action = async ({ request }: ActionFunctionArgs) => { + const translationSession = await getSettingsSession(request) + const sourceLanguage = translationSession.get('sourceLanguage') + const targetLanguages = translationSession.get('targetLanguages') + + if (!targetLanguages) { + throw new Error('TODO') + } + const formData = await request.formData() const submission = parseWithZod(formData, { schema }) @@ -44,16 +55,20 @@ export const action = async ({ request }: LoaderFunctionArgs) => { ) } - const { expression, languages } = submission.value + const { expression } = submission.value const translator = new Translator(process.env.DEEPL_KEY!) - const translation = await translator.translate(expression, languages) + const translation = await translator.translate( + expression, + sourceLanguage, + targetLanguages, + ) if (!translation) { return data({ result: submission.reply(), data: null }, { status: 400 }) } return data({ - result: submission.reply(), + result: submission.reply({ resetForm: true }), data: { expression, translation, @@ -62,13 +77,13 @@ export const action = async ({ request }: LoaderFunctionArgs) => { } export default function Page() { + const data = useLoaderData() const actionData = useActionData() const isPending = useIsPending() const [form, fields] = useForm({ lastResult: actionData?.result, constraint: getZodConstraint(schema), - shouldValidate: 'onInput', onValidate: ({ formData }) => parseWithZod(formData, { schema }), }) @@ -77,32 +92,6 @@ export default function Page() { return (
-
- Target Languages - - -
- {getCollectionProps(fields.languages, { - type: 'checkbox', - options: targetLangs, - }).map(({ key, ...props }) => ( - - { - targetLangConfigs[props.value as (typeof targetLangs)[number]] - .label - } - - ))} -
-
-
- {isPending && ( -
- -
- )} {actionData?.data && (
@@ -120,7 +109,7 @@ export default function Page() { {actionData.data.translation.map( ({ language, expressions }) => (
-

{targetLangConfigs[language].label}

+

{targetLangConfig[language].label}

{expressions.map((expr) => (

{expr}

))} @@ -136,6 +125,26 @@ export default function Page() {
+ {!data.valid && ( + + + Missing Settings + + You're missing something in your{' '} + + settings + + ! + + + )} {allErrors.length > 0 && (
+
+ +
+
diff --git a/app/utils/request.server.ts b/app/utils/request.server.ts new file mode 100644 index 0000000..3c83d9a --- /dev/null +++ b/app/utils/request.server.ts @@ -0,0 +1,3 @@ +export const getCookieHeader = (request: Request) => { + return request.headers.get('Cookie') +} diff --git a/app/utils/translation.ts b/app/utils/translation.ts index 369e00b..7680bba 100644 --- a/app/utils/translation.ts +++ b/app/utils/translation.ts @@ -1,27 +1,94 @@ -import { type SourceLanguageCode, type TargetLanguageCode } from 'deepl-node' +import { + type SourceLanguageCode as DeeplSourceLangCode, + type TargetLanguageCode as DeeplTargetLangCode, +} from 'deepl-node' +import { z } from 'zod' -export const sourceLangs = [] as const satisfies SourceLanguageCode[] -export const targetLangs = [ +// prettier-ignore +const commonLangCodes = ['ar', 'bg', 'cs', 'da', 'de', 'el', 'es', 'et', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nb', 'nl', 'pl', 'ro', 'ru', 'sk', 'sl', 'sv', 'tr', 'uk', 'zh'] as const +export const SourceLangCode = z.enum([ + ...commonLangCodes, + 'en', + 'pt', + 'detect', +] as const satisfies (DeeplSourceLangCode | 'detect')[]) +export const TargetLangCode = z.enum([ + ...commonLangCodes, + 'en-GB', 'en-US', - 'zh', - 'de', - 'fr', -] as const satisfies TargetLanguageCode[] + 'pt-BR', + 'pt-PT', +] as const satisfies DeeplTargetLangCode[]) -export const sourceLangConfigs: Record< - Extract, - LanguageConfig -> = {} -export const targetLangConfigs: Record< - Extract, - LanguageConfig -> = { - 'en-US': { label: 'English' }, - zh: { label: 'Chinese' }, +export type SourceLangCode = z.infer +export type TargetLangCode = z.infer +type LanguageConfig = { label: string } + +export const sourceLangConfig: Record = { + detect: { label: 'Detect Language' }, + ar: { label: 'Arabic' }, + bg: { label: 'Bulgarian' }, + cs: { label: 'Czech' }, + da: { label: 'Danish' }, de: { label: 'German' }, + el: { label: 'Greek' }, + en: { label: 'English' }, + es: { label: 'Spanish' }, + et: { label: 'Estonian' }, + fi: { label: 'Finnish' }, fr: { label: 'French' }, + hu: { label: 'Hungarian' }, + id: { label: 'Indonesian' }, + it: { label: 'Italian' }, + ja: { label: 'Japanese' }, + ko: { label: 'Korean' }, + lt: { label: 'Lithuanian' }, + lv: { label: 'Latvian' }, + nb: { label: 'Norwegian (Bokmål)' }, + nl: { label: 'Dutch' }, + pl: { label: 'Polish' }, + pt: { label: 'Portuguese' }, + ro: { label: 'Romanian' }, + ru: { label: 'Russian' }, + sk: { label: 'Slovak' }, + sl: { label: 'Slovenian' }, + sv: { label: 'Swedish' }, + tr: { label: 'Turkish' }, + uk: { label: 'Ukrainian' }, + zh: { label: 'Chinese (simplified)' }, } -export type LanguageConfig = { - label: string +export const targetLangConfig: Record = { + ar: { label: 'Arabic' }, + bg: { label: 'Bulgarian' }, + cs: { label: 'Czech' }, + da: { label: 'Danish' }, + de: { label: 'German' }, + el: { label: 'Greek' }, + 'en-GB': { label: 'English (British)' }, + 'en-US': { label: 'English (American)' }, + es: { label: 'Spanish' }, + et: { label: 'Estonian' }, + fi: { label: 'Finnish' }, + fr: { label: 'French' }, + hu: { label: 'Hungarian' }, + id: { label: 'Indonesian' }, + it: { label: 'Italian' }, + ja: { label: 'Japanese' }, + ko: { label: 'Korean' }, + lt: { label: 'Lithuanian' }, + lv: { label: 'Latvian' }, + nb: { label: 'Norwegian (Bokmål)' }, + nl: { label: 'Dutch' }, + pl: { label: 'Polish' }, + 'pt-BR': { label: 'Portuguese (Brazilian)' }, + 'pt-PT': { label: 'Portuguese (European)' }, + ro: { label: 'Romanian' }, + ru: { label: 'Russian' }, + sk: { label: 'Slovak' }, + sl: { label: 'Slovenian' }, + sv: { label: 'Swedish' }, + tr: { label: 'Turkish' }, + uk: { label: 'Ukrainian' }, + zh: { label: 'Chinese (simplified)' }, } diff --git a/app/utils/translator.server.ts b/app/utils/translator.server.ts index 50d2af6..aca8a7b 100644 --- a/app/utils/translator.server.ts +++ b/app/utils/translator.server.ts @@ -1,13 +1,35 @@ -import { - Translator as DeeplTranslator, - type TargetLanguageCode, -} from 'deepl-node' +import { createCookieSessionStorage } from '@remix-run/node' +import { Translator as DeeplTranslator } from 'deepl-node' +import { createTypedSessionStorage } from 'remix-utils/typed-session' import { z } from 'zod' -import { targetLangs } from './translation.ts' +import { getCookieHeader } from './request.server' +import { SourceLangCode, TargetLangCode } from './translation.ts' + +export const settingsSessionStorage = createTypedSessionStorage({ + sessionStorage: createCookieSessionStorage({ + cookie: { + name: '_translation', + sameSite: 'lax', + path: '/', + httpOnly: true, + secrets: process.env.SESSION_SECRET!.split(','), + secure: process.env.NODE_ENV === 'production', + }, + }), + schema: z.object({ + sourceLanguage: SourceLangCode.default('detect'), + targetLanguages: z.array(TargetLangCode).min(1).optional(), + }), +}) + +export const getSettingsSession = async (request: Request) => { + const cookie = getCookieHeader(request) + return await settingsSessionStorage.getSession(cookie) +} const polyglotizationSchema = z.array( z.object({ - language: z.enum(targetLangs), + language: TargetLangCode, expressions: z.array(z.string()), }), ) @@ -19,16 +41,24 @@ export class Translator { this.translator = new DeeplTranslator(authKey) } - public async translate(string: string, targets: TargetLanguageCode[]) { - const promises = targets.map(async (target) => { + public async translate( + string: string, + sourceLang: SourceLangCode | null | undefined, + targetLangs: TargetLangCode[], + ) { + if (!sourceLang || sourceLang === 'detect') { + sourceLang = null + } + + const promises = targetLangs.map(async (targetLang) => { const translation = await this.translator.translateText( string, - null, - target, + sourceLang, + targetLang, ) return { - language: target, + language: targetLang, expressions: [translation.text], } }) diff --git a/package.json b/package.json index dbdd0e6..6ae0093 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,13 @@ "dependencies": { "@conform-to/react": "1.2.2", "@conform-to/zod": "1.2.2", - "@radix-ui/react-dialog": "1.1.3", - "@radix-ui/react-navigation-menu": "1.2.2", + "@radix-ui/react-collapsible": "1.1.2", + "@radix-ui/react-dialog": "1.1.4", + "@radix-ui/react-label": "2.1.1", + "@radix-ui/react-navigation-menu": "1.2.3", + "@radix-ui/react-popover": "1.1.4", "@radix-ui/react-scroll-area": "1.2.2", + "@radix-ui/react-select": "2.1.4", "@radix-ui/react-slot": "1.1.1", "@react-router/remix-config-routes-adapter": "0.0.0-nightly-bf7ecb711-20240911", "@remix-run/express": "2.15.1", @@ -38,17 +42,19 @@ "class-variance-authority": "0.7.1", "close-with-grace": "2.1.0", "clsx": "2.1.1", + "cmdk": "1.0.4", "cross-env": "7.0.3", "deepl-node": "1.15.0", "dotenv": "16.4.7", "express": "4.21.2", "get-port": "7.1.0", "isbot": "4", - "lucide-react": "0.468.0", + "lucide-react": "0.469.0", "morgan": "1.10.0", "react": "19.0.0", "react-dom": "19.0.0", "remix-flat-routes": "0.6.5", + "remix-utils": "7.7.0", "tailwind-merge": "2.5.5", "tailwindcss-animate": "1.0.7", "vaul": "1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b151589..e658396 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,27 @@ importers: '@conform-to/zod': specifier: 1.2.2 version: 1.2.2(zod@3.24.1) + '@radix-ui/react-collapsible': + specifier: 1.1.2 + version: 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-dialog': - specifier: 1.1.3 - version: 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: 1.1.4 + version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-label': + specifier: 2.1.1 + version: 2.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-navigation-menu': - specifier: 1.2.2 - version: 1.2.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: 1.2.3 + version: 1.2.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-popover': + specifier: 1.1.4 + version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-scroll-area': specifier: 1.2.2 version: 1.2.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-select': + specifier: 2.1.4 + version: 2.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-slot': specifier: 1.1.1 version: 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -59,6 +71,9 @@ importers: clsx: specifier: 2.1.1 version: 2.1.1 + cmdk: + specifier: 1.0.4 + version: 1.0.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) cross-env: specifier: 7.0.3 version: 7.0.3 @@ -78,8 +93,8 @@ importers: specifier: '4' version: 4.4.0 lucide-react: - specifier: 0.468.0 - version: 0.468.0(react@19.0.0) + specifier: 0.469.0 + version: 0.469.0(react@19.0.0) morgan: specifier: 1.10.0 version: 1.10.0 @@ -92,6 +107,9 @@ importers: remix-flat-routes: specifier: 0.6.5 version: 0.6.5(@remix-run/dev@2.15.1(@remix-run/react@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@remix-run/serve@2.15.1(typescript@5.7.2))(@types/node@22.10.1)(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))) + remix-utils: + specifier: 7.7.0 + version: 7.7.0(@remix-run/node@2.15.1(typescript@5.7.2))(@remix-run/react@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@19.0.0)(zod@3.24.1) tailwind-merge: specifier: 2.5.5 version: 2.5.5 @@ -757,6 +775,21 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -846,6 +879,32 @@ packages: '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.2': + resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.1': resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} peerDependencies: @@ -877,8 +936,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.3': - resolution: {integrity: sha512-ujGvqQNkZ0J7caQyl8XuZRj2/TIrYcOGwqz5TeD1OMcCdfBuEMP0D12ve+8J5F9XuNUth3FAKFWo/wt0E/GJrQ==} + '@radix-ui/react-dialog@1.1.4': + resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -899,8 +958,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.2': - resolution: {integrity: sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg==} + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -943,8 +1002,47 @@ packages: '@types/react': optional: true - '@radix-ui/react-navigation-menu@1.2.2': - resolution: {integrity: sha512-7wHxgyNzOjsexOHFTXGJK/RDhKgrqj0siWJpm5i+sb7h+A6auY7efph6eMg0kOU4sVCLcbhHK7ZVueAXxOzvZA==} + '@radix-ui/react-label@2.1.1': + resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.3': + resolution: {integrity: sha512-IQWAsQ7dsLIYDrn0WqPU+cdM7MONTv9nqrLVYoie3BPiabSfUVDe6Fr+oEt0Cofsr9ONDcDe9xhmJbL1Uq1yKg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.4': + resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1008,6 +1106,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-select@2.1.4': + resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.1.1': resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} peerDependencies: @@ -1062,6 +1173,24 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.1.1': resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} peerDependencies: @@ -1075,6 +1204,9 @@ packages: '@types/react-dom': optional: true + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@react-router/dev@7.0.2': resolution: {integrity: sha512-uT9OVTGJAtOHGSvAlES4Y2HLqLQ7pENffUhlS7Is7eEVWQeTfZei/1RXTnNwpLbwAuDEf7DHbINDeVLDdjP92w==} engines: {node: '>=20.0.0'} @@ -1720,6 +1852,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.0.4: + resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2725,10 +2863,10 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@0.468.0: - resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} @@ -3420,12 +3558,12 @@ packages: '@types/react': optional: true - react-remove-scroll@2.6.0: - resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} + react-remove-scroll@2.6.2: + resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -3508,6 +3646,39 @@ packages: peerDependencies: '@remix-run/dev': ^1.15.0 || ^2 + remix-utils@7.7.0: + resolution: {integrity: sha512-J8NhP044nrNIam/xOT1L9a4RQ9FSaA2wyrUwmN8ZT+c/+CdAAf70yfaLnvMyKcV5U+8BcURQ/aVbth77sT6jGA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@remix-run/cloudflare': ^2.0.0 + '@remix-run/node': ^2.0.0 + '@remix-run/react': ^2.0.0 + '@remix-run/router': ^1.7.2 + crypto-js: ^4.1.1 + intl-parse-accept-language: ^1.0.0 + is-ip: ^5.0.1 + react: ^18.0.0 + zod: ^3.22.4 + peerDependenciesMeta: + '@remix-run/cloudflare': + optional: true + '@remix-run/node': + optional: true + '@remix-run/react': + optional: true + '@remix-run/router': + optional: true + crypto-js: + optional: true + intl-parse-accept-language: + optional: true + is-ip: + optional: true + react: + optional: true + zod: + optional: true + require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} @@ -3823,6 +3994,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.31.0: + resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + engines: {node: '>=16'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3920,12 +4095,12 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-callback-ref@1.3.2: - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -3940,6 +4115,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4652,6 +4832,23 @@ snapshots: dependencies: levn: 0.4.1 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@floating-ui/utils@0.2.8': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -4769,6 +4966,31 @@ snapshots: '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + + '@radix-ui/react-collapsible@1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/react-collection@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -4793,12 +5015,12 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-dialog@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-dialog@1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-dismissable-layer': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0) '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) @@ -4810,7 +5032,7 @@ snapshots: aria-hidden: 1.2.4 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - react-remove-scroll: 2.6.0(@types/react@19.0.1)(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.1)(react@19.0.0) optionalDependencies: '@types/react': 19.0.1 '@types/react-dom': 19.0.2(@types/react@19.0.1) @@ -4821,7 +5043,7 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-dismissable-layer@1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -4858,14 +5080,23 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - '@radix-ui/react-navigation-menu@1.2.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-label@2.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + + '@radix-ui/react-navigation-menu@1.2.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) '@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-dismissable-layer': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -4880,6 +5111,47 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/react-popover@1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.1)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + + '@radix-ui/react-popper@1.2.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/react-portal@1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -4926,6 +5198,35 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/react-select@2.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.1)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/react-slot@1.1.1(@types/react@19.0.1)(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0) @@ -4965,6 +5266,20 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 + '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.1)(react@19.0.0)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + + '@radix-ui/react-use-size@1.1.0(@types/react@19.0.1)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -4974,6 +5289,8 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.2(@types/react@19.0.1) + '@radix-ui/rect@1.1.0': {} + '@react-router/dev@7.0.2(@types/node@22.10.1)(react-router@6.28.0(react@19.0.0))(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))': dependencies: '@babel/core': 7.26.0 @@ -5812,6 +6129,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.0.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + use-sync-external-store: 1.4.0(react@19.0.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -6967,7 +7296,7 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.468.0(react@19.0.0): + lucide-react@0.469.0(react@19.0.0): dependencies: react: 19.0.0 @@ -7780,13 +8109,13 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 - react-remove-scroll@2.6.0(@types/react@19.0.1)(react@19.0.0): + react-remove-scroll@2.6.2(@types/react@19.0.1)(react@19.0.0): dependencies: react: 19.0.0 react-remove-scroll-bar: 2.3.8(@types/react@19.0.1)(react@19.0.0) react-style-singleton: 2.2.3(@types/react@19.0.1)(react@19.0.0) tslib: 2.8.1 - use-callback-ref: 1.3.2(@types/react@19.0.1)(react@19.0.0) + use-callback-ref: 1.3.3(@types/react@19.0.1)(react@19.0.0) use-sidecar: 1.1.3(@types/react@19.0.1)(react@19.0.0) optionalDependencies: '@types/react': 19.0.1 @@ -7902,6 +8231,16 @@ snapshots: fs-extra: 11.2.0 minimatch: 5.1.6 + remix-utils@7.7.0(@remix-run/node@2.15.1(typescript@5.7.2))(@remix-run/react@2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@19.0.0)(zod@3.24.1): + dependencies: + type-fest: 4.31.0 + optionalDependencies: + '@remix-run/node': 2.15.1(typescript@5.7.2) + '@remix-run/react': 2.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@remix-run/router': 1.21.0 + react: 19.0.0 + zod: 3.24.1 + require-like@0.1.2: {} requireindex@1.2.0: {} @@ -8296,6 +8635,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.31.0: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -8425,7 +8766,7 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.2(@types/react@19.0.1)(react@19.0.0): + use-callback-ref@1.3.3(@types/react@19.0.1)(react@19.0.0): dependencies: react: 19.0.0 tslib: 2.8.1 @@ -8440,6 +8781,10 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 + use-sync-external-store@1.4.0(react@19.0.0): + dependencies: + react: 19.0.0 + util-deprecate@1.0.2: {} util@0.12.5: @@ -8474,7 +8819,7 @@ snapshots: vaul@1.1.2(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@radix-ui/react-dialog': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.1))(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) transitivePeerDependencies: