betterstart-cli 0.0.107 → 0.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/assets/adapters/next/templates/init/admin-globals.css +2 -62
  2. package/dist/assets/adapters/next/templates/init/components/layouts/admin-header.tsx +2 -19
  3. package/dist/assets/adapters/next/templates/init/components/layouts/admin-providers.tsx +4 -7
  4. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu.tsx +0 -1
  5. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table-pagination.tsx +3 -1
  6. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/copyable-code-block.tsx +2 -5
  7. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-code-mirror.tsx +4 -5
  8. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +0 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +1 -0
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +3 -1
  11. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +89 -78
  12. package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +5 -1
  13. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +2 -11
  14. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor.ts +1 -2
  15. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +42 -37
  16. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +95 -71
  17. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +7 -7
  18. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +88 -69
  19. package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +1 -0
  20. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -8
  21. package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +8 -10
  22. package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +8 -5
  23. package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +1 -1
  24. package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +76 -33
  25. package/dist/assets/shared-assets/react-admin/custom/content-editor/main-toolbar-content.tsx +3 -3
  26. package/dist/assets/shared-assets/react-admin/custom/content-editor/mobile-toolbar-content.tsx +1 -1
  27. package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +1 -5
  28. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +3 -15
  29. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +0 -1
  30. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/blockquote-button.tsx +2 -1
  31. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/code-block-button.tsx +2 -1
  32. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/color-highlight-button.tsx +2 -1
  33. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +1 -1
  34. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +2 -1
  35. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +1 -1
  36. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/mark-button.tsx +2 -1
  37. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/undo-redo-button.tsx +2 -1
  38. package/dist/assets/shared-assets/react-admin/ui/avatar.tsx +1 -1
  39. package/dist/assets/shared-assets/react-admin/ui/badge.tsx +5 -3
  40. package/dist/assets/shared-assets/react-admin/ui/button.tsx +4 -4
  41. package/dist/assets/shared-assets/react-admin/ui/calendar.tsx +1 -1
  42. package/dist/assets/shared-assets/react-admin/ui/chart.tsx +5 -16
  43. package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
  44. package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +12 -2
  45. package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +1 -1
  46. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +1 -1
  47. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +24 -21
  48. package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
  49. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +9 -3
  50. package/dist/assets/shared-assets/react-admin/ui/input-otp.tsx +2 -2
  51. package/dist/assets/shared-assets/react-admin/ui/input.tsx +1 -1
  52. package/dist/assets/shared-assets/react-admin/ui/kbd.tsx +1 -1
  53. package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +1 -1
  54. package/dist/assets/shared-assets/react-admin/ui/native-select.tsx +1 -1
  55. package/dist/assets/shared-assets/react-admin/ui/radio-group.tsx +1 -1
  56. package/dist/assets/shared-assets/react-admin/ui/select.tsx +1 -1
  57. package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +2 -5
  58. package/dist/assets/shared-assets/react-admin/ui/switch.tsx +2 -2
  59. package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +3 -3
  60. package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
  61. package/dist/assets/shared-assets/react-admin/ui/toggle.tsx +1 -1
  62. package/dist/{chunk-OYU73Q2W.js → chunk-PSDUGFMD.js} +2 -2
  63. package/dist/{chunk-OYU73Q2W.js.map → chunk-PSDUGFMD.js.map} +1 -1
  64. package/dist/cli.js +240 -193
  65. package/dist/cli.js.map +1 -1
  66. package/dist/{read-namespaced-template-EIH2NPDJ.js → read-namespaced-template-TSATYQPP.js} +2 -2
  67. package/package.json +1 -1
  68. package/dist/assets/adapters/next/templates/init/hooks/use-admin-theme.tsx +0 -72
  69. package/dist/assets/adapters/next/templates/init/utils/theme/system.ts +0 -6
  70. /package/dist/{read-namespaced-template-EIH2NPDJ.js.map → read-namespaced-template-TSATYQPP.js.map} +0 -0
@@ -5,8 +5,6 @@
5
5
 
6
6
  @plugin "@tailwindcss/typography";
7
7
 
8
- @custom-variant dark (&:is(.dark *));
9
-
10
8
  @theme inline {
11
9
  --color-background: var(--background);
12
10
  --color-foreground: var(--foreground);
@@ -97,9 +95,10 @@
97
95
  --accent-foreground: oklch(0.15 0 0);
98
96
  --destructive: oklch(0.577 0.245 27.325);
99
97
  --warning: oklch(0.625 0.165 57.4);
98
+ --warning-foreground: oklch(0.625 0.165 57.4);
100
99
  --border: oklch(0.93 0 0);
101
100
  --highlight: oklch(0.9 0.004 286.32);
102
- --input: oklch(0.994 0 0);
101
+ --input: oklch(0.93 0 0);
103
102
  --ring: oklch(0.708 0 0);
104
103
  --chart-1: oklch(0.845 0.143 164.978);
105
104
  --chart-2: oklch(0.696 0.17 162.48);
@@ -174,65 +173,6 @@
174
173
  --tt-color-highlight-pink-contrast: rgba(225, 136, 179, 0.27);
175
174
  }
176
175
 
177
- .dark {
178
- --background: oklch(0.144 0 0);
179
- --foreground: oklch(0.985 0 0);
180
- --card: oklch(0.15 0 0);
181
- --card-foreground: oklch(0.985 0 0);
182
- --popover: oklch(0.15 0 0);
183
- --popover-foreground: oklch(0.985 0 0);
184
- --primary: oklch(0.922 0 0);
185
- --primary-foreground: oklch(0.15 0 0);
186
- --secondary: oklch(0.269 0 0);
187
- --secondary-foreground: oklch(0.985 0 0);
188
- --muted: oklch(0.269 0 0);
189
- --muted-foreground: oklch(0.708 0 0);
190
- --accent: oklch(0.269 0 0);
191
- --accent-foreground: oklch(0.985 0 0);
192
- --destructive: oklch(0.704 0.191 22.216);
193
- --warning: oklch(0.75 0.2 90);
194
- --warning-foreground: oklch(0.625 0.165 57.4);
195
- --border: oklch(1 0 0 / 10%);
196
- --highlight: oklch(1 0 0 / 10%);
197
- --input: oklch(1 0 0 / 15%);
198
- --ring: oklch(0.556 0 0);
199
- --chart-1: oklch(0.845 0.143 164.978);
200
- --chart-2: oklch(0.696 0.17 162.48);
201
- --chart-3: oklch(0.596 0.145 163.225);
202
- --chart-4: oklch(0.508 0.118 165.612);
203
- --chart-5: oklch(0.432 0.095 166.913);
204
- --sidebar: oklch(0.15 0 0);
205
- --sidebar-foreground: oklch(0.985 0 0);
206
- --sidebar-primary: oklch(0.488 0.243 264.376);
207
- --sidebar-primary-foreground: oklch(0.985 0 0);
208
- --sidebar-accent: oklch(0.269 0 0);
209
- --sidebar-accent-foreground: oklch(0.985 0 0);
210
- --sidebar-border: oklch(1 0 0 / 10%);
211
- --sidebar-ring: oklch(0.556 0 0);
212
- --tt-bg-color: var(--background);
213
- --tt-bg-color-contrast: var(--border);
214
- --tt-color-highlight-yellow: #6b6524;
215
- --tt-color-highlight-green: #509568;
216
- --tt-color-highlight-blue: #6e92aa;
217
- --tt-color-highlight-purple: #583e74;
218
- --tt-color-highlight-red: #743e42;
219
- --tt-color-highlight-gray: rgb(47, 47, 47);
220
- --tt-color-highlight-brown: rgb(74, 50, 40);
221
- --tt-color-highlight-orange: rgb(92, 59, 35);
222
- --tt-color-highlight-pink: rgb(78, 44, 60);
223
- --tt-color-highlight-yellow-contrast: #58531e;
224
- --tt-color-highlight-green-contrast: #47855d;
225
- --tt-color-highlight-blue-contrast: #5e86a1;
226
- --tt-color-highlight-purple-contrast: #4c3564;
227
- --tt-color-highlight-red-contrast: #643539;
228
- --tt-color-highlight-gray-contrast: rgba(255, 255, 255, 0.094);
229
- --tt-color-highlight-brown-contrast: rgba(184, 101, 69, 0.25);
230
- --tt-color-highlight-orange-contrast: rgba(233, 126, 37, 0.2);
231
- --tt-color-highlight-pink-contrast: rgba(220, 76, 145, 0.22);
232
- --shadow-small: 0px 1px 2px #00000029;
233
- --shadow-medium: 0px 2px 2px #00000052, 0px 8px 8px -8px #00000029;
234
- }
235
-
236
176
  @layer base {
237
177
  * {
238
178
  @apply border-border outline-ring/50;
@@ -1,31 +1,14 @@
1
1
  'use client'
2
2
 
3
- import { Button } from '@admin/components/ui/button'
4
3
  import { SidebarTrigger, useSidebar } from '@admin/components/ui/sidebar'
5
- import { useTheme } from '@admin/hooks/use-admin-theme'
6
- import { Moon, Sun } from 'lucide-react'
7
4
 
8
5
  export function AdminHeader() {
9
- const { theme, setTheme } = useTheme()
10
6
  const { state } = useSidebar()
11
7
 
12
8
  return (
13
9
  <header className="sticky top-0 z-50 flex h-14 w-full shrink-0 items-center gap-2 border-b border-border">
14
- <div className="flex w-full flex-1 items-center justify-between gap-1 px-5">
15
- <div className="flex w-full items-center gap-2">
16
- {state === 'collapsed' && <SidebarTrigger />}
17
- </div>
18
- <div className="ml-auto flex items-center gap-2">
19
- <Button
20
- variant="ghost"
21
- size="icon"
22
- onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
23
- >
24
- <Sun className="size-4 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
25
- <Moon className="absolute size-4 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
26
- <span className="sr-only">Toggle theme</span>
27
- </Button>
28
- </div>
10
+ <div className="flex w-full flex-1 items-center gap-2 px-5">
11
+ {state === 'collapsed' && <SidebarTrigger />}
29
12
  </div>
30
13
  </header>
31
14
  )
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { Toaster } from '@admin/components/ui/sonner'
4
4
  import { TooltipProvider } from '@admin/components/ui/tooltip'
5
- import { AdminThemeProvider } from '@admin/hooks/use-admin-theme'
6
5
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
7
6
  import { useState } from 'react'
8
7
 
@@ -22,12 +21,10 @@ export function AdminProviders({ children }: { children: React.ReactNode }) {
22
21
 
23
22
  return (
24
23
  <QueryClientProvider client={queryClient}>
25
- <AdminThemeProvider>
26
- <TooltipProvider>
27
- {children}
28
- <Toaster position="top-right" richColors />
29
- </TooltipProvider>
30
- </AdminThemeProvider>
24
+ <TooltipProvider>
25
+ {children}
26
+ <Toaster position="top-right" richColors />
27
+ </TooltipProvider>
31
28
  </QueryClientProvider>
32
29
  )
33
30
  }
@@ -60,7 +60,6 @@ export async function SidebarUserMenu() {
60
60
  render={
61
61
  <Link
62
62
  href="/admin/profile"
63
- prefetch={true}
64
63
  className="flex w-full items-center justify-between gap-2 py-2 pr-1"
65
64
  />
66
65
  }
@@ -70,7 +70,9 @@ export function DataTablePagination<TData>(props: DataTablePaginationProps<TData
70
70
  )
71
71
 
72
72
  const handlePageSizeChange = React.useCallback(
73
- (value: string) => {
73
+ (value: string | null) => {
74
+ if (value === null) return
75
+
74
76
  React.startTransition(() => {
75
77
  if (value === 'all') {
76
78
  setPageSize(-1)
@@ -5,7 +5,6 @@ import type { DevModeCodeLanguage, DevModeSnippetCodeTab } from './dev-mode-type
5
5
  import { Button } from '@admin/components/ui/button'
6
6
  import { Card, CardContent, CardHeader } from '@admin/components/ui/card'
7
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@admin/components/ui/tabs'
8
- import { useTheme } from '@admin/hooks/use-admin-theme'
9
8
  import { useCopyToClipboard } from '@admin/hooks/use-copy-to-clipboard'
10
9
  import { Check, Copy } from 'lucide-react'
11
10
  import { PlainCodeFallback } from './plain-code-fallback'
@@ -21,8 +20,6 @@ interface CopyableCodeBlockProps {
21
20
 
22
21
  export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeBlockProps) {
23
22
  const [copy, isCopied] = useCopyToClipboard()
24
- const { resolvedTheme } = useTheme()
25
- const codeTheme = resolvedTheme === 'dark' ? 'dark' : 'light'
26
23
  const fallbackTab: DevModeSnippetCodeTab = {
27
24
  id: 'code',
28
25
  label,
@@ -68,7 +65,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
68
65
  <TabsContent key={tab.id} value={tab.id} className="min-h-0 overflow-auto">
69
66
  <CardContent className="h-full overflow-auto p-0">
70
67
  <React.Suspense fallback={<PlainCodeFallback code={tab.code} />}>
71
- <LazyCodeMirror code={tab.code} language={tab.language} theme={codeTheme} />
68
+ <LazyCodeMirror code={tab.code} language={tab.language} />
72
69
  </React.Suspense>
73
70
  </CardContent>
74
71
  </TabsContent>
@@ -94,7 +91,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
94
91
  </CardHeader>
95
92
  <CardContent className="overflow-auto p-0">
96
93
  <React.Suspense fallback={<PlainCodeFallback code={code} />}>
97
- <LazyCodeMirror code={code} language={language} theme={codeTheme} />
94
+ <LazyCodeMirror code={code} language={language} />
98
95
  </React.Suspense>
99
96
  </CardContent>
100
97
  </>
@@ -1,11 +1,11 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import type { DevModeCodeLanguage, DevModeCodeTheme } from './dev-mode-types'
4
+ import type { DevModeCodeLanguage } from './dev-mode-types'
5
5
  import { javascript } from '@codemirror/lang-javascript'
6
6
  import { json } from '@codemirror/lang-json'
7
7
  import { EditorView } from '@codemirror/view'
8
- import { githubDark, githubLight } from '@uiw/codemirror-theme-github'
8
+ import { githubLight } from '@uiw/codemirror-theme-github'
9
9
  import CodeMirror from '@uiw/react-codemirror'
10
10
 
11
11
  const codeMirrorSetup = {
@@ -43,10 +43,9 @@ const compactCodeTheme = EditorView.theme({
43
43
  interface DevModeCodeMirrorProps {
44
44
  code: string
45
45
  language: DevModeCodeLanguage
46
- theme: DevModeCodeTheme
47
46
  }
48
47
 
49
- export default function DevModeCodeMirror({ code, language, theme }: DevModeCodeMirrorProps) {
48
+ export default function DevModeCodeMirror({ code, language }: DevModeCodeMirrorProps) {
50
49
  const extensions = React.useMemo(
51
50
  () =>
52
51
  language === 'json'
@@ -62,7 +61,7 @@ export default function DevModeCodeMirror({ code, language, theme }: DevModeCode
62
61
  readOnly
63
62
  basicSetup={codeMirrorSetup}
64
63
  extensions={extensions}
65
- theme={theme === 'dark' ? githubDark : githubLight}
64
+ theme={githubLight}
66
65
  className="flex-1 [&>.cm-editor]:h-full"
67
66
  />
68
67
  )
@@ -1,5 +1,4 @@
1
1
  export type DevModeCodeLanguage = 'tsx' | 'ts' | 'json'
2
- export type DevModeCodeTheme = 'light' | 'dark'
3
2
  export type DevModeSnippetIcon =
4
3
  | 'eye'
5
4
  | 'file-code'
@@ -102,6 +102,7 @@ export function EditMediaDialogContent({
102
102
  </DrawerHeader>
103
103
  <Form {...form}>
104
104
  <form
105
+ autoComplete="off"
105
106
  id={formId}
106
107
  onSubmit={form.handleSubmit(handleSave)}
107
108
  className="flex flex-1 flex-col gap-4 overflow-y-auto px-6 pt-px pb-4"
@@ -41,7 +41,9 @@ export function MediaGridPagination({
41
41
  const perPage = 'Rows per page'
42
42
 
43
43
  const handlePageSizeChange = React.useCallback(
44
- (value: string) => {
44
+ (value: string | null) => {
45
+ if (value === null) return
46
+
45
47
  React.startTransition(() => {
46
48
  onPageSizeChange(Number(value))
47
49
  onPageChange(1)
@@ -2,7 +2,9 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import type { UploadFileResult } from '@admin/types'
5
+ import type { FieldErrors } from 'react-hook-form'
5
6
  import { Card, CardContent } from '@admin/components/ui/card'
7
+ import { Form } from '@admin/components/ui/form'
6
8
  import {
7
9
  InputGroup,
8
10
  InputGroupAddon,
@@ -15,19 +17,29 @@ import { getMediaUrlPlaceholder } from '@admin/utils/media/get-media-url-placeho
15
17
  import { cn } from '@admin/utils/shared/cn'
16
18
  import { isValidUrl } from '@admin/utils/validation/is-valid-url'
17
19
  import { Upload } from 'lucide-react'
20
+ import { useForm } from 'react-hook-form'
21
+ import { toast } from 'sonner'
18
22
 
19
23
  interface MediaUrlImporterProps extends React.HTMLAttributes<HTMLDivElement> {
20
24
  onImportFromUrl: (url: string) => Promise<UploadFileResult>
21
25
  accept?: string
22
26
  }
23
27
 
28
+ interface MediaUrlFormValues {
29
+ url: string
30
+ }
31
+
24
32
  export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUrlImporterProps) {
25
- const [urlValue, setUrlValue] = React.useState('')
26
- const [urlError, setUrlError] = React.useState<string | null>(null)
27
33
  const [isImporting, startImportTransition] = React.useTransition()
28
34
  const mountedRef = React.useRef(true)
29
35
  const importInFlightRef = React.useRef(false)
30
36
  const importRequestIdRef = React.useRef(0)
37
+ const form = useForm<MediaUrlFormValues>({
38
+ defaultValues: { url: '' },
39
+ mode: 'onSubmit',
40
+ reValidateMode: 'onChange'
41
+ })
42
+ const urlField = form.register('url', { required: 'Enter a direct media URL to import.' })
31
43
 
32
44
  React.useEffect(() => {
33
45
  mountedRef.current = true
@@ -42,93 +54,92 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
42
54
  const urlHelpText = getMediaUrlHelpText(accept)
43
55
  const urlPlaceholder = getMediaUrlPlaceholder(accept)
44
56
 
45
- const handleImport = React.useCallback(() => {
46
- if (isImporting || importInFlightRef.current) return
47
-
48
- const nextUrl = urlValue.trim()
49
-
50
- if (!nextUrl) {
51
- setUrlError('Enter a direct media URL to import.')
52
- return
53
- }
54
-
55
- if (!isValidUrl(nextUrl)) {
56
- setUrlError('Enter a valid HTTP or HTTPS URL.')
57
- return
58
- }
59
-
60
- setUrlError(null)
61
- const importRequestId = importRequestIdRef.current + 1
62
- importRequestIdRef.current = importRequestId
63
- importInFlightRef.current = true
57
+ const handleImport = React.useCallback(
58
+ (values: MediaUrlFormValues) => {
59
+ if (isImporting || importInFlightRef.current) return
64
60
 
65
- startImportTransition(async () => {
66
- try {
67
- const result = await onImportFromUrl(nextUrl)
61
+ const nextUrl = values.url.trim()
68
62
 
69
- if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
70
- return
71
- }
72
-
73
- if (!result.success) {
74
- setUrlError(result.error ?? 'Failed to import media from URL.')
75
- return
76
- }
63
+ if (!isValidUrl(nextUrl)) {
64
+ toast.error('Enter a valid HTTP or HTTPS URL.')
65
+ return
66
+ }
77
67
 
78
- setUrlValue('')
79
- } catch (error) {
80
- if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
81
- return
68
+ const importRequestId = importRequestIdRef.current + 1
69
+ importRequestIdRef.current = importRequestId
70
+ importInFlightRef.current = true
71
+
72
+ startImportTransition(async () => {
73
+ try {
74
+ const result = await onImportFromUrl(nextUrl)
75
+
76
+ if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
77
+ return
78
+ }
79
+
80
+ if (!result.success) {
81
+ toast.error(result.error ?? 'Failed to import media from URL.')
82
+ return
83
+ }
84
+
85
+ form.reset()
86
+ } catch (error) {
87
+ if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
88
+ return
89
+ }
90
+
91
+ toast.error(error instanceof Error ? error.message : 'Failed to import media from URL.')
92
+ } finally {
93
+ if (importRequestIdRef.current === importRequestId) {
94
+ importInFlightRef.current = false
95
+ }
82
96
  }
97
+ })
98
+ },
99
+ [form, isImporting, onImportFromUrl]
100
+ )
83
101
 
84
- setUrlError(error instanceof Error ? error.message : 'Failed to import media from URL.')
85
- } finally {
86
- if (importRequestIdRef.current === importRequestId) {
87
- importInFlightRef.current = false
88
- }
89
- }
90
- })
91
- }, [isImporting, onImportFromUrl, urlValue])
102
+ const handleInvalid = (errors: FieldErrors<MediaUrlFormValues>) => {
103
+ const message = errors.url?.message
104
+ toast.error(typeof message === 'string' ? message : 'Enter a direct media URL to import.')
105
+ }
92
106
 
93
107
  return (
94
108
  <Card className={cn('flex items-center justify-center', className)}>
95
109
  <CardContent className="h-full w-full">
96
110
  <div className="flex h-full w-full flex-col items-center justify-center gap-3 text-center">
97
- <InputGroup className="mx-auto max-w-2xl bg-background">
98
- <InputGroupInput
99
- type="url"
100
- name="url"
101
- aria-label="Media URL"
102
- value={urlValue}
103
- placeholder={urlPlaceholder}
111
+ <Form {...form}>
112
+ <form
104
113
  autoComplete="off"
105
- spellCheck={false}
106
- onChange={(event) => {
107
- setUrlValue(event.target.value)
108
- if (urlError) setUrlError(null)
109
- }}
110
- onKeyDown={(event) => {
111
- if (event.key === 'Enter') {
112
- event.preventDefault()
113
- void handleImport()
114
- }
115
- }}
116
- disabled={isImporting}
117
- />
118
- <InputGroupAddon align="inline-end">
119
- <InputGroupButton
120
- type="button"
121
- variant="outline"
122
- onClick={() => void handleImport()}
123
- disabled={isImporting}
124
- aria-busy={isImporting}
125
- className="h-full px-5"
126
- >
127
- {isImporting ? <Spinner /> : <Upload />}
128
- {isImporting ? 'Importing…' : 'Import'}
129
- </InputGroupButton>
130
- </InputGroupAddon>
131
- </InputGroup>
114
+ noValidate
115
+ onSubmit={form.handleSubmit(handleImport, handleInvalid)}
116
+ className="w-full"
117
+ >
118
+ <InputGroup className="mx-auto max-w-2xl bg-background">
119
+ <InputGroupInput
120
+ type="url"
121
+ aria-label="Media URL"
122
+ placeholder={urlPlaceholder}
123
+ autoComplete="off"
124
+ spellCheck={false}
125
+ {...urlField}
126
+ disabled={isImporting}
127
+ />
128
+ <InputGroupAddon align="inline-end">
129
+ <InputGroupButton
130
+ type="submit"
131
+ variant="outline"
132
+ disabled={isImporting}
133
+ aria-busy={isImporting}
134
+ className="h-full px-5"
135
+ >
136
+ {isImporting ? <Spinner /> : <Upload />}
137
+ {isImporting ? 'Importing…' : 'Import'}
138
+ </InputGroupButton>
139
+ </InputGroupAddon>
140
+ </InputGroup>
141
+ </form>
142
+ </Form>
132
143
 
133
144
  <div className="flex w-full flex-col items-center text-center">
134
145
  <p className="text-sm text-muted-foreground">{urlHelpText}</p>
@@ -40,7 +40,11 @@ export function SearchInput({
40
40
 
41
41
  return (
42
42
  <Form {...form}>
43
- <form onSubmit={form.handleSubmit(onSubmit)} className="flex w-full items-center">
43
+ <form
44
+ autoComplete="off"
45
+ onSubmit={form.handleSubmit(onSubmit)}
46
+ className="flex w-full items-center"
47
+ >
44
48
  <FormField
45
49
  control={form.control}
46
50
  name="search"
@@ -9,7 +9,6 @@ import type { QueryClient } from '@tanstack/react-query'
9
9
  import type { ReactCodeMirrorRef } from '@uiw/react-codemirror'
10
10
  import type { Root } from 'react-dom/client'
11
11
  import { MediaField } from '@admin/components/custom/media-field'
12
- import { useTheme } from '@admin/hooks/use-admin-theme'
13
12
  import { applyCursorChange } from '@admin/utils/editor/apply-cursor-change'
14
13
  import { applyCursorChangeIfAvailable } from '@admin/utils/editor/apply-cursor-change-if-available'
15
14
  import { createMarkdownTableText } from '@admin/utils/editor/create-markdown-table-text'
@@ -33,11 +32,12 @@ import { redo, redoDepth, undo, undoDepth } from '@codemirror/commands'
33
32
  import { markdown } from '@codemirror/lang-markdown'
34
33
  import { Decoration, EditorView, WidgetType } from '@codemirror/view'
35
34
  import { QueryClientProvider, useQueryClient } from '@tanstack/react-query'
36
- import { githubDarkInit, githubLightInit } from '@uiw/codemirror-theme-github'
35
+ import { githubLightInit } from '@uiw/codemirror-theme-github'
37
36
  import { NuqsAdapter } from 'nuqs/adapters/react'
38
37
  import { createRoot } from 'react-dom/client'
39
38
 
40
39
  const ADMIN_MONO_FONT_FAMILY = 'var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace'
40
+ const editorTheme = githubLightInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } })
41
41
 
42
42
  type MarkdownMediaGalleryWidgetProps = {
43
43
  widgetId: number
@@ -150,15 +150,6 @@ export function useContentEditorSourceMode({
150
150
  const [isMarkdownLinkActive, setIsMarkdownLinkActive] = React.useState(false)
151
151
  const [canUndoMarkdownChange, setCanUndoMarkdownChange] = React.useState(false)
152
152
  const [canRedoMarkdownChange, setCanRedoMarkdownChange] = React.useState(false)
153
- const { resolvedTheme } = useTheme()
154
- const editorTheme = React.useMemo(
155
- () =>
156
- resolvedTheme === 'dark'
157
- ? githubDarkInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } })
158
- : githubLightInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } }),
159
- [resolvedTheme]
160
- )
161
-
162
153
  const valueRef = React.useRef(value)
163
154
  valueRef.current = value
164
155
  const onChangeRef = React.useRef(onChange)
@@ -99,8 +99,7 @@ export function useContentEditor({
99
99
  autocomplete: 'off',
100
100
  autocorrect: 'off',
101
101
  autocapitalize: 'off',
102
- class:
103
- 'simple-editor content-editor-prose prose prose-sm max-w-none dark:prose-invert focus:outline-none'
102
+ class: 'simple-editor content-editor-prose prose prose-sm max-w-none focus:outline-none'
104
103
  },
105
104
  handleKeyDown: (_view, event) => handleSlashKeyDown(event)
106
105
  },