betterstart-cli 0.0.17 → 0.0.19

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 (28) hide show
  1. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -0
  2. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -0
  3. package/dist/assets/adapters/next/plugins/portfolio/plugin.ts +17 -0
  4. package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +128 -0
  5. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +2 -2
  6. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -0
  7. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  8. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +1 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +1 -1
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +17 -7
  11. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +1 -0
  12. package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
  13. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +3 -3
  14. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +2 -1
  15. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +3 -3
  16. package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +2 -2
  17. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +2 -2
  18. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +219 -40
  19. package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +99 -0
  20. package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +181 -0
  21. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +38 -38
  22. package/dist/assets/shared-assets/react-admin/schema.json +64 -2
  23. package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +1 -1
  24. package/dist/cli.js +474 -143
  25. package/dist/cli.js.map +1 -1
  26. package/package.json +1 -1
  27. package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +0 -39
  28. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +0 -182
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "menus",
3
+ "type": "entity",
3
4
  "label": "Menus",
4
5
  "description": "Manage Menus",
5
6
  "icon": "Menu",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "posts",
3
3
  "label": "Posts",
4
+ "type": "entity",
4
5
  "description": "Manage blog posts",
5
6
  "icon": "FileText",
6
7
  "navGroup": {
@@ -0,0 +1,17 @@
1
+ import type { BetterstartPluginDefinition } from '@plugin-engine/types.js'
2
+
3
+ export const portfolioPlugin: BetterstartPluginDefinition = {
4
+ id: 'portfolio',
5
+ title: 'Portfolio',
6
+ description: 'Portfolio content module',
7
+ kind: 'content',
8
+ version: '1.0.0',
9
+ dependencies: [],
10
+ conflicts: [],
11
+ schemaFiles: ['portfolio.json'],
12
+ files: [],
13
+ packageDependencies: [],
14
+ devDependencies: [],
15
+ envSections: [],
16
+ configure: 'none'
17
+ }
@@ -0,0 +1,128 @@
1
+ {
2
+ "name": "portfolio",
3
+ "type": "entity",
4
+ "label": "Portfolio",
5
+ "description": "Manage portfolio items",
6
+ "icon": "Briefcase",
7
+ "navGroup": {
8
+ "label": "Collections",
9
+ "position": 0
10
+ },
11
+ "navPosition": 1,
12
+ "slot": {
13
+ "main": {
14
+ "fields": [
15
+ {
16
+ "name": "title",
17
+ "type": "string",
18
+ "label": "Title",
19
+ "required": true
20
+ },
21
+ {
22
+ "name": "eyebrow",
23
+ "type": "string",
24
+ "label": "Eyebrow",
25
+ "required": true
26
+ },
27
+ {
28
+ "name": "brandLink",
29
+ "type": "string",
30
+ "label": "Brand Link",
31
+ "required": false
32
+ },
33
+ {
34
+ "name": "gallery",
35
+ "type": "gallery",
36
+ "label": "Gallery"
37
+ },
38
+ {
39
+ "name": "description",
40
+ "type": "richtext",
41
+ "label": "Description",
42
+ "required": false,
43
+ "output": "html",
44
+ "height": "fill"
45
+ }
46
+ ]
47
+ },
48
+ "sidebar": {
49
+ "fields": [
50
+ {
51
+ "name": "generalSidebar",
52
+ "type": "section",
53
+ "heading": {
54
+ "icon": "bolt",
55
+ "title": "General"
56
+ },
57
+ "fields": [
58
+ {
59
+ "name": "coverImage",
60
+ "type": "image",
61
+ "label": "Cover Image"
62
+ },
63
+ {
64
+ "name": "categories",
65
+ "type": "select",
66
+ "label": "Categories",
67
+ "placeholder": "Select categories",
68
+ "hint": "Assign one or more categories",
69
+ "default": "category-1",
70
+ "creatable": true,
71
+ "multiple": true,
72
+ "previewLimit": 1,
73
+ "options": [
74
+ {
75
+ "label": "Category 1",
76
+ "value": "category-1"
77
+ },
78
+ {
79
+ "label": "Category 2",
80
+ "value": "category-2"
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ ]
87
+ }
88
+ },
89
+ "columns": [
90
+ {
91
+ "accessorKey": "title",
92
+ "header": "Title",
93
+ "type": "text",
94
+ "sortable": true,
95
+ "size": "xl"
96
+ },
97
+ {
98
+ "accessorKey": "published",
99
+ "header": "Status",
100
+ "type": "boolean",
101
+ "sortable": true,
102
+ "size": "sm"
103
+ },
104
+ {
105
+ "accessorKey": "updatedAt",
106
+ "header": "Updated",
107
+ "type": "date",
108
+ "sortable": true,
109
+ "size": "sm"
110
+ },
111
+ {
112
+ "accessorKey": "createdAt",
113
+ "header": "Created",
114
+ "type": "date",
115
+ "sortable": true,
116
+ "size": "sm"
117
+ }
118
+ ],
119
+ "actions": {
120
+ "create": true,
121
+ "edit": true,
122
+ "delete": true,
123
+ "draft": true
124
+ },
125
+ "search": {
126
+ "fields": ["title", "excerpt"]
127
+ }
128
+ }
@@ -10,7 +10,7 @@ export type DevModeSnippetIcon =
10
10
  | 'save'
11
11
  | 'send'
12
12
  | 'trash'
13
- export type DevModeSnippetKind = 'query' | 'mutation'
13
+ export type DevModeSnippetKind = 'jsx' | 'query' | 'mutation'
14
14
  export type DevModeLifecycleHookPhase = 'before' | 'after'
15
15
  export type DevModeLifecycleHookEvent =
16
16
  | 'beforeCreate'
@@ -33,7 +33,7 @@ export interface DevModeSnippet {
33
33
  code: string
34
34
  codeTabs?: DevModeSnippetCodeTab[]
35
35
  payloadExample?: DevModeSnippetExample
36
- responseExample: DevModeSnippetExample
36
+ responseExample?: DevModeSnippetExample
37
37
  }
38
38
 
39
39
  export interface DevModeSnippetCodeTab {
@@ -40,6 +40,7 @@ const SNIPPET_ICONS = {
40
40
  } satisfies Record<DevModeSnippetIcon, LucideIcon>
41
41
 
42
42
  const SNIPPET_KIND_LABELS = {
43
+ jsx: 'JSX',
43
44
  query: 'Query',
44
45
  mutation: 'Mutation'
45
46
  } satisfies Record<DevModeSnippetKind, string>
@@ -49,7 +49,7 @@ export function DevModeIntegrateButton({
49
49
  <Drawer direction="right" handleOnly onOpenChange={handleOpenChange}>
50
50
  <DrawerTrigger asChild>
51
51
  <Button type="button" variant="outline">
52
- <Code2 data-icon="inline-start" />
52
+ <Code2 />
53
53
  <span className="hidden sm:inline">Dev Mode</span>
54
54
  </Button>
55
55
  </DrawerTrigger>
@@ -53,7 +53,7 @@ export function MediaDeleteDialog({ media, disabled = false, onDeleted }: MediaD
53
53
  <AlertDialog>
54
54
  <AlertDialogTrigger asChild>
55
55
  <Button variant="destructive" disabled={disabled || isPending}>
56
- <Trash2 data-icon="inline-start" />
56
+ <Trash2 />
57
57
  Delete media
58
58
  </Button>
59
59
  </AlertDialogTrigger>
@@ -97,7 +97,7 @@ export function MediaDeleteDrawer({
97
97
  >
98
98
  <DrawerPrimitive.Trigger asChild>
99
99
  <Button variant="destructive" disabled={disabled || isPending || !container}>
100
- <Trash2 data-icon="inline-start" />
100
+ <Trash2 />
101
101
  Delete File
102
102
  </Button>
103
103
  </DrawerPrimitive.Trigger>
@@ -11,7 +11,7 @@ interface MediaGridItemProps {
11
11
  item: AdminMedia
12
12
  selected?: boolean
13
13
  isSelectMode?: boolean
14
- onClick: (item: AdminMedia) => void
14
+ onClick?: (item: AdminMedia) => void
15
15
  }
16
16
 
17
17
  export const MediaGridItem = React.memo(function MediaGridItem({
@@ -21,10 +21,16 @@ export const MediaGridItem = React.memo(function MediaGridItem({
21
21
  onClick
22
22
  }: MediaGridItemProps) {
23
23
  return (
24
- <Card className="relative group cursor-pointer" onClick={() => onClick(item)}>
24
+ <Card
25
+ className={cn('relative group', {
26
+ 'cursor-pointer': Boolean(onClick)
27
+ })}
28
+ onClick={onClick ? () => onClick(item) : undefined}
29
+ >
25
30
  <CardContent
26
- aria-pressed={isSelectMode ? selected : undefined}
27
- className={cn('p-2 flex flex-col hover:border-highlight', {
31
+ aria-pressed={isSelectMode && onClick ? selected : undefined}
32
+ className={cn('p-2 flex flex-col', {
33
+ 'hover:border-highlight': Boolean(onClick),
28
34
  'border-highlight': selected
29
35
  })}
30
36
  >
@@ -32,7 +38,7 @@ export const MediaGridItem = React.memo(function MediaGridItem({
32
38
  <MediaPreview media={item} isFilled />
33
39
  </div>
34
40
 
35
- {isSelectMode && (
41
+ {isSelectMode && onClick ? (
36
42
  <Checkbox
37
43
  aria-label={selected ? `Deselect ${item.filename}` : `Select ${item.filename}`}
38
44
  tabIndex={-1}
@@ -42,10 +48,14 @@ export const MediaGridItem = React.memo(function MediaGridItem({
42
48
  checked={selected}
43
49
  onCheckedChange={() => onClick(item)}
44
50
  />
45
- )}
51
+ ) : null}
46
52
  </CardContent>
47
53
  <CardFooter>
48
- <span className="w-full truncate text-sm text-muted-foreground group-hover:text-foreground">
54
+ <span
55
+ className={cn('w-full truncate text-sm text-muted-foreground', {
56
+ 'group-hover:text-foreground': Boolean(onClick)
57
+ })}
58
+ >
49
59
  {item.filename}
50
60
  </span>
51
61
  </CardFooter>
@@ -27,6 +27,7 @@ export function MediaPreview({ media, isFilled = false, showMetadata = false }:
27
27
  })}
28
28
  fill
29
29
  sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 200px"
30
+ unoptimized
30
31
  loading="lazy"
31
32
  decoding="async"
32
33
  draggable={false}
@@ -1,6 +1,7 @@
1
1
  import type { AdminNavigationItem } from '@admin/types/navigation'
2
2
  import { House, ImagePlay } from 'lucide-react'
3
3
  import { menusNav } from './navigation/menus'
4
+ import { portfolioNav } from './navigation/portfolio'
4
5
  import { postsNav } from './navigation/posts'
5
6
 
6
7
  export type { AdminNavigationItem } from '@admin/types/navigation'
@@ -29,7 +30,7 @@ function comparePositions(a?: number, b?: number): number {
29
30
  return 0
30
31
  }
31
32
 
32
- const combinedNavigation = [...coreNav, ...menusNav, ...postsNav]
33
+ const combinedNavigation = [...coreNav, ...menusNav, ...portfolioNav, ...postsNav]
33
34
 
34
35
  const dashboard = combinedNavigation.find((item) => item.href === '/admin')
35
36
  const others = combinedNavigation.filter((item) => item.href !== '/admin')
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'
3
+ import { MediaField } from '@admin/components/custom/media-field'
4
4
  import { useTheme } from '@admin/hooks/use-admin-theme'
5
5
  import type { ContentEditorSelectionRequest } from '@admin/utils/editor/content-editor'
6
6
  import {
@@ -73,7 +73,7 @@ class MarkdownMediaGalleryWidget extends WidgetType {
73
73
  root.render(
74
74
  <QueryClientProvider client={this.props.queryClient}>
75
75
  <NuqsAdapter>
76
- <MediaGalleryField
76
+ <MediaField
77
77
  value=""
78
78
  valueMode="url"
79
79
  accept="image/*"
@@ -95,7 +95,7 @@ class MarkdownMediaGalleryWidget extends WidgetType {
95
95
  root.render(
96
96
  <QueryClientProvider client={this.props.queryClient}>
97
97
  <NuqsAdapter>
98
- <MediaGalleryField
98
+ <MediaField
99
99
  value=""
100
100
  valueMode="url"
101
101
  accept="image/*"
@@ -97,6 +97,7 @@ export function useMediaByIds(ids: string[]) {
97
97
  return useQuery<(AdminMedia | null)[]>({
98
98
  queryKey: ['media', 'batch', ids],
99
99
  queryFn: () => (ids.length > 0 ? getMediaByIds(ids) : []),
100
- enabled: ids.length > 0
100
+ enabled: ids.length > 0,
101
+ placeholderData: (previousData) => previousData
101
102
  })
102
103
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { invalidateUsersCache, isEmailConfigured, updateEmail } from '@admin/actions/profile'
4
4
  import { authClient } from '@admin/auth/client'
5
- import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'
5
+ import { MediaField } from '@admin/components/custom/media-field'
6
6
  import { Avatar, AvatarFallback, AvatarImage } from '@admin/components/ui/avatar'
7
7
  import { Button } from '@admin/components/ui/button'
8
8
  import {
@@ -157,7 +157,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
157
157
  </div>
158
158
  </div>
159
159
  </CardHeader>
160
- <CardContent className="space-y-6">
160
+ <CardContent className="space-y-5">
161
161
  <FormField
162
162
  control={profileForm.control}
163
163
  name="image"
@@ -165,7 +165,7 @@ export function ProfileForm({ user }: ProfileFormProps) {
165
165
  <FormItem>
166
166
  <FormLabel>Profile Picture</FormLabel>
167
167
  <FormControl>
168
- <MediaGalleryField
168
+ <MediaField
169
169
  value={formField.value}
170
170
  onChange={formField.onChange}
171
171
  onBlur={formField.onBlur}
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'
3
+ import { MediaField } from '@admin/components/custom/media-field'
4
4
 
5
5
  export interface ContentEditorMediaGalleryBlockProps {
6
6
  accept?: string
@@ -16,7 +16,7 @@ export function ContentEditorMediaGalleryBlock({
16
16
  onRemove
17
17
  }: ContentEditorMediaGalleryBlockProps) {
18
18
  return (
19
- <MediaGalleryField
19
+ <MediaField
20
20
  value=""
21
21
  onChange={(url) => {
22
22
  if (url) onMediaSelected(url)
@@ -24,7 +24,7 @@ export function TableAddControls({ controls }: TableAddControlsProps) {
24
24
  variant="outline"
25
25
  >
26
26
  <span className="pointer-events-none inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-input text-foreground transition-colors group-hover:bg-secondary group-focus-visible:bg-secondary">
27
- <PlusIcon aria-hidden="true" data-icon="inline-start" />
27
+ <PlusIcon aria-hidden="true" />
28
28
  </span>
29
29
  </Button>
30
30
  <Button
@@ -37,7 +37,7 @@ export function TableAddControls({ controls }: TableAddControlsProps) {
37
37
  variant="outline"
38
38
  >
39
39
  <span className="pointer-events-none inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-input text-foreground transition-colors group-hover:bg-secondary group-focus-visible:bg-secondary">
40
- <PlusIcon aria-hidden="true" data-icon="inline-start" />
40
+ <PlusIcon aria-hidden="true" />
41
41
  </span>
42
42
  </Button>
43
43
  </div>,