betterstart-cli 0.0.19 → 0.0.20
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.
- package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +1 -1
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
- package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +14 -42
- package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +0 -1
- package/dist/assets/shared-assets/react-admin/schema.json +2 -64
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +18 -2
- package/dist/cli.js +22 -23
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AdminNavigationItem } from '@admin/types/navigation'
|
|
2
2
|
import { House, ImagePlay } from 'lucide-react'
|
|
3
|
+
import { contactNav } from './navigation/contact'
|
|
3
4
|
import { menusNav } from './navigation/menus'
|
|
4
5
|
import { portfolioNav } from './navigation/portfolio'
|
|
5
6
|
import { postsNav } from './navigation/posts'
|
|
@@ -30,7 +31,7 @@ function comparePositions(a?: number, b?: number): number {
|
|
|
30
31
|
return 0
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const combinedNavigation = [...coreNav, ...menusNav, ...portfolioNav, ...postsNav]
|
|
34
|
+
const combinedNavigation = [...coreNav, ...contactNav, ...menusNav, ...portfolioNav, ...postsNav]
|
|
34
35
|
|
|
35
36
|
const dashboard = combinedNavigation.find((item) => item.href === '/admin')
|
|
36
37
|
const others = combinedNavigation.filter((item) => item.href !== '/admin')
|
|
@@ -195,7 +195,7 @@ export function GalleryField({
|
|
|
195
195
|
<CarouselContent className="-ml-3">
|
|
196
196
|
{canAdd && !disabled ? (
|
|
197
197
|
<CarouselItem className="basis-[168px] pl-3 sm:basis-[184px]">
|
|
198
|
-
<Card className="h-full">
|
|
198
|
+
<Card className="h-full" onClick={() => setOpen(true)}>
|
|
199
199
|
<CardContent className="p-2 flex flex-col cursor-pointer">
|
|
200
200
|
<div className="flex size-full bg-background border border-border rounded-sm aspect-square overflow-hidden text-muted-foreground hover:text-foreground transition-colors items-center flex-col justify-center gap-1">
|
|
201
201
|
<Plus className="size-6" strokeWidth={1.5} />
|
|
@@ -6,39 +6,27 @@ import { Card, CardContent } from '@admin/components/ui/card'
|
|
|
6
6
|
import type { AdminMedia } from '@admin/types'
|
|
7
7
|
import { cn } from '@admin/utils/shared/cn'
|
|
8
8
|
import { ImageIcon } from 'lucide-react'
|
|
9
|
-
import type * as React from 'react'
|
|
10
9
|
|
|
11
10
|
interface MediaFieldEmptyStateProps {
|
|
12
11
|
accept?: string
|
|
13
|
-
addButtonLabel?: string
|
|
14
12
|
className?: string
|
|
15
13
|
disabled?: boolean
|
|
16
|
-
isRemovable?: boolean
|
|
17
14
|
mode?: 'single' | 'multi'
|
|
18
15
|
open: boolean
|
|
19
16
|
onOpenChange: (open: boolean) => void
|
|
20
|
-
onRemove?: React.MouseEventHandler<HTMLButtonElement>
|
|
21
17
|
onSelect: (selected: AdminMedia | AdminMedia[]) => void
|
|
22
|
-
showAddButton?: boolean
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
export function MediaFieldEmptyState({
|
|
26
21
|
accept,
|
|
27
|
-
addButtonLabel = 'Add Image',
|
|
28
22
|
className,
|
|
29
23
|
disabled,
|
|
30
|
-
isRemovable = false,
|
|
31
24
|
mode = 'single',
|
|
32
25
|
open,
|
|
33
26
|
onOpenChange,
|
|
34
|
-
|
|
35
|
-
onSelect,
|
|
36
|
-
showAddButton = false
|
|
27
|
+
onSelect
|
|
37
28
|
}: MediaFieldEmptyStateProps) {
|
|
38
|
-
|
|
39
|
-
const shouldShowActions = !disabled && (shouldShowAddButton || onRemove)
|
|
40
|
-
|
|
41
|
-
function handleAddImage(e: React.MouseEvent) {
|
|
29
|
+
function handleAddImage(e: React.MouseEvent<HTMLButtonElement>) {
|
|
42
30
|
e.preventDefault()
|
|
43
31
|
e.stopPropagation()
|
|
44
32
|
onOpenChange(true)
|
|
@@ -54,36 +42,20 @@ export function MediaFieldEmptyState({
|
|
|
54
42
|
>
|
|
55
43
|
<CardContent className="relative p-0 justify-center items-center flex flex-col h-40 hover:bg-muted/50 transition-colors duration-200">
|
|
56
44
|
<ImageIcon
|
|
57
|
-
className=
|
|
58
|
-
'group-hover:opacity-0 group-focus-within:opacity-0': shouldShowActions
|
|
59
|
-
})}
|
|
45
|
+
className="text-muted-foreground transition-opacity group-hover:opacity-0 group-focus-within:opacity-0"
|
|
60
46
|
strokeWidth={1}
|
|
61
47
|
/>
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</Button>
|
|
74
|
-
) : null}
|
|
75
|
-
{onRemove ? (
|
|
76
|
-
<Button
|
|
77
|
-
type="button"
|
|
78
|
-
data-media-field-action="true"
|
|
79
|
-
variant="default"
|
|
80
|
-
onClick={onRemove}
|
|
81
|
-
>
|
|
82
|
-
Remove
|
|
83
|
-
</Button>
|
|
84
|
-
) : null}
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
48
|
+
<div className="absolute inset-0 flex items-center justify-center gap-1 opacity-0 transition-opacity duration-200 ease-out group-hover:opacity-100 group-focus-within:opacity-100">
|
|
49
|
+
<Button
|
|
50
|
+
type="button"
|
|
51
|
+
data-media-field-action="true"
|
|
52
|
+
variant="outline"
|
|
53
|
+
onClick={handleAddImage}
|
|
54
|
+
className="border-primary/10"
|
|
55
|
+
>
|
|
56
|
+
Add Image
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
87
59
|
</CardContent>
|
|
88
60
|
</Card>
|
|
89
61
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "BetterStart Schema Definition",
|
|
5
5
|
"description": "JSON Schema for validating custom schema files (courses.json, testimonials.json, instructors.json, events.json, etc.)",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["
|
|
7
|
+
"required": ["name", "label"],
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
10
|
"$schema": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"type": {
|
|
32
32
|
"type": "string",
|
|
33
|
-
"description": "Schema type: 'entity' for CRUD list, 'single' for singleton record (site settings, about page), or 'form' for public submission forms",
|
|
33
|
+
"description": "Schema type: 'entity' for CRUD list (default), 'single' for singleton record (site settings, about page), or 'form' for public submission forms",
|
|
34
34
|
"enum": ["entity", "single", "form"]
|
|
35
35
|
},
|
|
36
36
|
"fields": {
|
|
@@ -157,9 +157,6 @@
|
|
|
157
157
|
{
|
|
158
158
|
"$ref": "#/$defs/stringField"
|
|
159
159
|
},
|
|
160
|
-
{
|
|
161
|
-
"$ref": "#/$defs/emailField"
|
|
162
|
-
},
|
|
163
160
|
{
|
|
164
161
|
"$ref": "#/$defs/textField"
|
|
165
162
|
},
|
|
@@ -426,61 +423,6 @@
|
|
|
426
423
|
}
|
|
427
424
|
}
|
|
428
425
|
},
|
|
429
|
-
"emailField": {
|
|
430
|
-
"type": "object",
|
|
431
|
-
"description": "Email input field",
|
|
432
|
-
"required": ["name", "type"],
|
|
433
|
-
"additionalProperties": false,
|
|
434
|
-
"properties": {
|
|
435
|
-
"name": {
|
|
436
|
-
"type": "string"
|
|
437
|
-
},
|
|
438
|
-
"type": {
|
|
439
|
-
"const": "email"
|
|
440
|
-
},
|
|
441
|
-
"label": {
|
|
442
|
-
"type": "string"
|
|
443
|
-
},
|
|
444
|
-
"required": {
|
|
445
|
-
"type": "boolean"
|
|
446
|
-
},
|
|
447
|
-
"length": {
|
|
448
|
-
"type": "integer",
|
|
449
|
-
"description": "Maximum character length",
|
|
450
|
-
"minimum": 1,
|
|
451
|
-
"maximum": 65535
|
|
452
|
-
},
|
|
453
|
-
"hint": {
|
|
454
|
-
"type": "string"
|
|
455
|
-
},
|
|
456
|
-
"placeholder": {
|
|
457
|
-
"type": "string",
|
|
458
|
-
"description": "Placeholder text displayed in the email input"
|
|
459
|
-
},
|
|
460
|
-
"default": {
|
|
461
|
-
"type": "string"
|
|
462
|
-
},
|
|
463
|
-
"defaultValueFrom": {
|
|
464
|
-
"$ref": "#/$defs/defaultValueFrom"
|
|
465
|
-
},
|
|
466
|
-
"hasIcon": {
|
|
467
|
-
"type": "boolean",
|
|
468
|
-
"description": "Whether this field has an associated icon picker"
|
|
469
|
-
},
|
|
470
|
-
"hidden": {
|
|
471
|
-
"type": "boolean",
|
|
472
|
-
"description": "Whether this field is hidden from the form UI but still exists in the database"
|
|
473
|
-
},
|
|
474
|
-
"showWhen": {
|
|
475
|
-
"$ref": "#/$defs/showWhenCondition"
|
|
476
|
-
},
|
|
477
|
-
"height": {
|
|
478
|
-
"enum": ["auto", "fill"],
|
|
479
|
-
"default": "auto",
|
|
480
|
-
"description": "Generated field height behavior: auto size or fill remaining flexible space."
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
426
|
"markdownField": {
|
|
485
427
|
"type": "object",
|
|
486
428
|
"description": "Rich text editor with markdown support",
|
|
@@ -1279,10 +1221,6 @@
|
|
|
1279
1221
|
"type": "string",
|
|
1280
1222
|
"description": "Tab display label"
|
|
1281
1223
|
},
|
|
1282
|
-
"icon": {
|
|
1283
|
-
"type": "string",
|
|
1284
|
-
"description": "Lucide icon name rendered before the tab label"
|
|
1285
|
-
},
|
|
1286
1224
|
"fields": {
|
|
1287
1225
|
"type": "array",
|
|
1288
1226
|
"description": "Fields within this tab",
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { cn } from '@admin/utils/shared/cn'
|
|
4
|
-
import
|
|
4
|
+
import * as React from 'react'
|
|
5
5
|
import { Drawer as DrawerPrimitive } from 'vaul'
|
|
6
6
|
|
|
7
7
|
function Drawer({
|
|
8
|
+
children,
|
|
9
|
+
container,
|
|
8
10
|
handleOnly = true,
|
|
9
11
|
...props
|
|
10
12
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
11
|
-
|
|
13
|
+
const portalContainerRef = React.useRef<HTMLDivElement>(null)
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<div ref={portalContainerRef} data-slot="drawer-container" className="contents" />
|
|
18
|
+
<DrawerPrimitive.Root
|
|
19
|
+
data-slot="drawer"
|
|
20
|
+
container={container ?? portalContainerRef.current}
|
|
21
|
+
handleOnly={handleOnly}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</DrawerPrimitive.Root>
|
|
26
|
+
</>
|
|
27
|
+
)
|
|
12
28
|
}
|
|
13
29
|
|
|
14
30
|
function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
package/dist/cli.js
CHANGED
|
@@ -15366,7 +15366,7 @@ export type TabValue = (typeof TAB_VALUES)[number]
|
|
|
15366
15366
|
const metadataStrip = showMetadataWidget ? ` {initialData?.id != null && (
|
|
15367
15367
|
<div
|
|
15368
15368
|
className={cn(
|
|
15369
|
-
'sticky z-
|
|
15369
|
+
'sticky z-100 flex flex-col gap-1 px-4 bg-background h-14 xl:flex-row xl:items-center xl:justify-between top-14 transition-shadow overflow-x-clip',
|
|
15370
15370
|
{
|
|
15371
15371
|
'shadow-modal/2': hasScrolled,
|
|
15372
15372
|
'border-b border-border/80': !hasScrolled
|
|
@@ -15449,11 +15449,31 @@ ${relState}` : ""}${creatableSelectState ? `
|
|
|
15449
15449
|
${creatableSelectState}` : ""}${staticMultiSelectState ? `
|
|
15450
15450
|
${staticMultiSelectState}` : ""}${hasDraft ? "\n const [submitIntent, setSubmitIntent] = React.useState<'draft' | 'publish' | null>(null)" : ""}
|
|
15451
15451
|
|
|
15452
|
+
const defaultValues: FormValues = {
|
|
15453
|
+
${defaultValues}
|
|
15454
|
+
}
|
|
15455
|
+
|
|
15456
|
+
const form = useForm<FormValues>({
|
|
15457
|
+
resolver: standardSchemaResolver(formSchema),
|
|
15458
|
+
mode: 'onBlur',
|
|
15459
|
+
reValidateMode: 'onChange',
|
|
15460
|
+
defaultValues
|
|
15461
|
+
})
|
|
15462
|
+
const defaultValuesRef = React.useRef(defaultValues)
|
|
15463
|
+
defaultValuesRef.current = defaultValues
|
|
15464
|
+
React.useEffect(
|
|
15465
|
+
() => () => {
|
|
15466
|
+
form.reset(defaultValuesRef.current)
|
|
15467
|
+
},
|
|
15468
|
+
[form]
|
|
15469
|
+
)
|
|
15470
|
+
|
|
15452
15471
|
const createMutation = useMutation({
|
|
15453
15472
|
mutationFn: (data: ${Singular}CreateInput) => create${Singular}(data),
|
|
15454
15473
|
onSuccess: async () => {
|
|
15455
15474
|
toast.success('${Singular} created successfully')
|
|
15456
15475
|
await queryClient.invalidateQueries({ queryKey: ['${plural}'] })
|
|
15476
|
+
form.reset(defaultValues)
|
|
15457
15477
|
router.push('${adminRoutePath}/${schema.name}')
|
|
15458
15478
|
},
|
|
15459
15479
|
onError: (error: Error) => {
|
|
@@ -15474,25 +15494,6 @@ ${staticMultiSelectState}` : ""}${hasDraft ? "\n const [submitIntent, setSubmit
|
|
|
15474
15494
|
})
|
|
15475
15495
|
|
|
15476
15496
|
const isPending = createMutation.isPending || updateMutation.isPending
|
|
15477
|
-
|
|
15478
|
-
const defaultValues: FormValues = {
|
|
15479
|
-
${defaultValues}
|
|
15480
|
-
}
|
|
15481
|
-
|
|
15482
|
-
const form = useForm<FormValues>({
|
|
15483
|
-
resolver: standardSchemaResolver(formSchema),
|
|
15484
|
-
mode: 'onBlur',
|
|
15485
|
-
reValidateMode: 'onChange',
|
|
15486
|
-
defaultValues
|
|
15487
|
-
})
|
|
15488
|
-
const defaultValuesRef = React.useRef(defaultValues)
|
|
15489
|
-
defaultValuesRef.current = defaultValues
|
|
15490
|
-
React.useEffect(
|
|
15491
|
-
() => () => {
|
|
15492
|
-
form.reset(defaultValuesRef.current)
|
|
15493
|
-
},
|
|
15494
|
-
[form]
|
|
15495
|
-
)
|
|
15496
15497
|
const { isDirty } = form.formState
|
|
15497
15498
|
${hasDraft ? " const canPublishPristineDraft = Boolean(initialData && !initialData.published)\n const canSubmitDraftAction = Boolean(initialData?.published || isDirty)\n const canSubmitPublishAction = isDirty || canPublishPristineDraft\n" : ""}
|
|
15498
15499
|
${defaultValueFromSyncFunctions ? `
|
|
@@ -15607,9 +15608,7 @@ ${topLevelTabsField ? `import type { TabValue } from '${hookImportPath}'
|
|
|
15607
15608
|
import { ${hookName} } from '${hookImportPath}'
|
|
15608
15609
|
import { ${contentComponentName} } from './${schema.name}-form-content'
|
|
15609
15610
|
|
|
15610
|
-
${metadataOffsetConst}
|
|
15611
|
-
|
|
15612
|
-
interface ${Singular}FormProps {
|
|
15611
|
+
${metadataOffsetConst}interface ${Singular}FormProps {
|
|
15613
15612
|
initialData?: ${Singular}
|
|
15614
15613
|
pageTitle: string
|
|
15615
15614
|
}
|