Skip to content

Commit 5253fc3

Browse files
feat: Enhance dynamic Zod schema generation, improve CRUD error handling, and update user menu with theme selection.
1 parent a2e6dce commit 5253fc3

31 files changed

+130
-141
lines changed

playground/app/app.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export default defineAppConfig({
22
ui: {
33
colors: {
44
primary: 'green',
5-
neutral: 'zinc',
6-
},
7-
},
5+
neutral: 'zinc'
6+
}
7+
}
88
})

playground/app/app.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ useHead({
77
meta: [
88
{ charset: 'utf-8' },
99
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
10-
{ key: 'theme-color', name: 'theme-color', content: color },
10+
{ key: 'theme-color', name: 'theme-color', content: color }
1111
],
1212
link: [
13-
{ rel: 'icon', href: '/favicon.ico' },
13+
{ rel: 'icon', href: '/favicon.ico' }
1414
],
1515
htmlAttrs: {
16-
lang: 'en',
17-
},
16+
lang: 'en'
17+
}
1818
})
1919
2020
const title = 'Nuxt Dashboard Template'
@@ -27,7 +27,7 @@ useSeoMeta({
2727
ogDescription: description,
2828
ogImage: 'https://ui.nuxt.com/assets/templates/nuxt/dashboard-light.png',
2929
twitterImage: 'https://ui.nuxt.com/assets/templates/nuxt/dashboard-light.png',
30-
twitterCard: 'summary_large_image',
30+
twitterCard: 'summary_large_image'
3131
})
3232
3333
const { user } = useUserSession()

playground/app/components/LoginModal.vue

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const props = withDefaults(defineProps<{
1010
color: 'primary',
1111
variant: 'solid',
1212
size: 'lg',
13-
class: '',
13+
class: ''
1414
})
1515
1616
const isOpen = ref(false)
@@ -19,7 +19,7 @@ const { fetch: refreshSession } = useUserSession()
1919
2020
const state = reactive({
2121
email: 'admin@example.com',
22-
password: '$1Password',
22+
password: '$1Password'
2323
})
2424
2525
const loading = ref(false)
@@ -29,18 +29,17 @@ async function onSubmit() {
2929
try {
3030
await $fetch('/api/auth/login', {
3131
method: 'POST',
32-
body: state,
32+
body: state
3333
})
3434
await refreshSession()
3535
isOpen.value = false
3636
// Redirect to dashboard after successful login
3737
await navigateTo('/dashboard')
38-
}
39-
catch (err: unknown) {
38+
} catch (err: unknown) {
4039
toast.add({
4140
title: 'Error',
4241
description: (err as { data?: { message?: string } }).data?.message || 'Invalid credentials',
43-
color: 'error',
42+
color: 'error'
4443
})
4544
loading.value = false
4645
}

playground/app/components/UserMenu.vue

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
1818
label: user.value?.name || undefined,
1919
avatar: {
2020
src: user.value?.avatar || '',
21-
alt: user.value?.name || '',
22-
},
21+
alt: user.value?.name || ''
22+
}
2323
}], [{
2424
label: 'Theme',
2525
icon: 'i-lucide-palette',
@@ -29,7 +29,7 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
2929
chip: appConfig.ui.colors.primary,
3030
content: {
3131
align: 'center',
32-
collisionPadding: 16,
32+
collisionPadding: 16
3333
},
3434
children: colors.map(color => ({
3535
label: color,
@@ -41,15 +41,15 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
4141
e.preventDefault()
4242
4343
appConfig.ui.colors.primary = color
44-
},
45-
})),
44+
}
45+
}))
4646
}, {
4747
label: 'Neutral',
4848
slot: 'chip',
4949
chip: appConfig.ui.colors.neutral === 'neutral' ? 'old-neutral' : appConfig.ui.colors.neutral,
5050
content: {
5151
align: 'end',
52-
collisionPadding: 16,
52+
collisionPadding: 16
5353
},
5454
children: neutrals.map(color => ({
5555
label: color,
@@ -61,9 +61,9 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
6161
e.preventDefault()
6262
6363
appConfig.ui.colors.neutral = color
64-
},
65-
})),
66-
}],
64+
}
65+
}))
66+
}]
6767
}, {
6868
label: 'Appearance',
6969
icon: 'i-lucide-sun-moon',
@@ -76,7 +76,7 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
7676
e.preventDefault()
7777
7878
colorMode.preference = 'light'
79-
},
79+
}
8080
}, {
8181
label: 'Dark',
8282
icon: 'i-lucide-moon',
@@ -89,26 +89,26 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
8989
},
9090
onSelect(e: Event) {
9191
e.preventDefault()
92-
},
93-
}],
92+
}
93+
}]
9494
}], [{
9595
label: 'Documentation',
9696
icon: 'i-lucide-book-open',
9797
to: 'https://auto-crud.clifland.in/',
98-
target: '_blank',
98+
target: '_blank'
9999
}, {
100100
label: 'GitHub repository',
101101
icon: 'i-simple-icons-github',
102102
to: 'https://github.com/clifordpereira/nuxt-auto-crud',
103-
target: '_blank',
103+
target: '_blank'
104104
}, {
105105
label: 'Log out',
106106
icon: 'i-lucide-log-out',
107107
onSelect: async () => {
108108
await $fetch('/api/auth/logout', { method: 'POST' })
109109
// Force a reload to ensure layout changes (admin -> guest) are applied
110110
window.location.href = '/'
111-
},
111+
}
112112
}]]))
113113
</script>
114114

@@ -124,15 +124,15 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
124124
name: user?.name || undefined,
125125
avatar: user?.avatar ? { src: user.avatar, alt: user?.name || undefined } : undefined,
126126
label: collapsed ? undefined : (user?.name || undefined),
127-
trailingIcon: collapsed ? undefined : 'i-lucide-chevrons-up-down',
127+
trailingIcon: collapsed ? undefined : 'i-lucide-chevrons-up-down'
128128
}"
129129
color="neutral"
130130
variant="ghost"
131131
block
132132
:square="collapsed"
133133
class="data-[state=open]:bg-elevated"
134134
:ui="{
135-
trailingIcon: 'text-dimmed',
135+
trailingIcon: 'text-dimmed'
136136
}"
137137
/>
138138

@@ -142,7 +142,7 @@ const items = computed<DropdownMenuItem[][]>(() => ([[{
142142
class="rounded-full ring ring-bg bg-(--chip-light) dark:bg-(--chip-dark) size-2"
143143
:style="{
144144
'--chip-light': `var(--color-${(item as any).chip}-500)`,
145-
'--chip-dark': `var(--color-${(item as any).chip}-400)`,
145+
'--chip-dark': `var(--color-${(item as any).chip}-400)`
146146
}"
147147
/>
148148
</div>

playground/app/components/common/Pagination.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const searchedItems = computed(() => {
2121
Object.values(row)
2222
.join(' ')
2323
.toLowerCase()
24-
.includes(search.value.toLowerCase()),
24+
.includes(search.value.toLowerCase())
2525
)
2626
})
2727
@@ -37,7 +37,7 @@ watch(
3737
() => {
3838
emit('update:paginated', paginatedItems.value)
3939
},
40-
{ immediate: true },
40+
{ immediate: true }
4141
)
4242
4343
watch([search], () => {

playground/app/components/common/Password.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ function checkStrength(str: string) {
88
{ regex: /.{8,}/, text: 'At least 8 characters' },
99
{ regex: /\d/, text: 'At least 1 number' },
1010
{ regex: /[a-z]/, text: 'At least 1 lowercase letter' },
11-
{ regex: /[A-Z]/, text: 'At least 1 uppercase letter' },
11+
{ regex: /[A-Z]/, text: 'At least 1 uppercase letter' }
1212
]
1313
1414
return requirements.map(req => ({
1515
met: req.regex.test(str),
16-
text: req.text,
16+
text: req.text
1717
}))
1818
}
1919

playground/app/components/crud/EditRow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const state = computed(() => {
1212
if (!props.schema) return {}
1313
// exclude system fields
1414
const filteredFields = props.schema.fields.filter(
15-
field => field.name !== 'created_at' && field.name !== 'updated_at' && field.name !== 'id',
15+
field => field.name !== 'created_at' && field.name !== 'updated_at' && field.name !== 'id'
1616
)
1717
1818
return useFormState(filteredFields, props.row)

playground/app/components/crud/Form.vue

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const emit = defineEmits<{
2828
2929
// filter out system fields
3030
const filteredFields = props.schema.fields.filter(
31-
field => field.name !== 'created_at' && field.name !== 'updated_at' && field.name !== 'id',
31+
field => field.name !== 'created_at' && field.name !== 'updated_at' && field.name !== 'id'
3232
)
3333
3434
// dynamically build zod schema
@@ -44,9 +44,7 @@ const state = reactive<Record<string, unknown>>(
4444
// Case 1: value is an object with id (e.g. customer_id: { id: 1, ... })
4545
if ((field.name.endsWith('_id') || field.name.endsWith('Id')) && value && typeof value === 'object' && 'id' in value) {
4646
value = (value as { id: unknown }).id
47-
}
48-
// Case 2: value is undefined, but there might be a relation object (e.g. field is customer_id, but props has customer: { id: 1 })
49-
else if ((field.name.endsWith('_id') || field.name.endsWith('Id')) && value === undefined) {
47+
} else if ((field.name.endsWith('_id') || field.name.endsWith('Id')) && value === undefined) {
5048
const relationName = field.name.endsWith('_id') ? field.name.replace('_id', '') : field.name.replace('Id', '')
5149
const relationValue = props.initialState?.[relationName]
5250
if (relationValue && typeof relationValue === 'object' && 'id' in relationValue) {
@@ -57,8 +55,8 @@ const state = reactive<Record<string, unknown>>(
5755
acc[field.name] = value ?? (field.type === 'boolean' ? false : undefined)
5856
return acc
5957
},
60-
{} as Record<string, unknown>,
61-
),
58+
{} as Record<string, unknown>
59+
)
6260
)
6361
6462
// processedFields with capitalized label for display
@@ -67,16 +65,15 @@ const processedFields = computed(() =>
6765
let label = field.name
6866
if (label.endsWith('_id')) {
6967
label = label.replace('_id', '')
70-
}
71-
else if (label.endsWith('Id')) {
68+
} else if (label.endsWith('Id')) {
7269
label = label.replace('Id', '')
7370
}
7471
label = useChangeCase(label, 'capitalCase').value
7572
return {
7673
...field,
77-
label,
74+
label
7875
}
79-
}),
76+
})
8077
)
8178
8279
function handleSubmit(event: FormSubmitEvent<Record<string, unknown>>) {

playground/app/components/crud/NameList.vue

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ const emit = defineEmits(['update:modelValue'])
1212
let urlPath = ''
1313
if (props.tableName) {
1414
urlPath = props.tableName
15-
}
16-
else if (props.fieldName) {
15+
} else if (props.fieldName) {
1716
const baseName = props.fieldName.replace(/(_id|Id)$/, '')
1817
urlPath = pluralize(baseName) // e.g., user_id → users
1918
}
@@ -29,18 +28,18 @@ const { data: options } = await useFetch(() => `${crudBaseUrl}/${urlPath}`, {
2928
return {
3029
label: r.name || r.title || `#${r.id}`,
3130
value: r.id,
32-
extra: r.email,
31+
extra: r.email
3332
}
3433
}),
3534
lazy: true,
36-
headers: crudHeaders(),
35+
headers: crudHeaders()
3736
})
3837
3938
const selected = computed({
4039
get: () => options.value?.find(opt => opt.value == props.modelValue),
4140
set: (val: { value: string | number } | null) => {
4241
emit('update:modelValue', val?.value)
43-
},
42+
}
4443
})
4544
</script>
4645

playground/app/components/crud/Table.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const config = useRuntimeConfig().public
1414
const crudBaseUrl = config.crudBaseUrl || '/api'
1515
1616
const { data } = await useFetch(`${crudBaseUrl}/${props.resource}`, {
17-
headers: crudHeaders(),
17+
headers: crudHeaders()
1818
})
1919
2020
// Fetch relations
@@ -37,7 +37,7 @@ const paginatedItems = ref<Record<string, unknown>[]>([])
3737
root: 'divide-y divide-gray-200 dark:divide-gray-700',
3838
header: 'px-4 py-5',
3939
body: 'divide-y divide-gray-200 dark:divide-gray-700',
40-
footer: 'p-4',
40+
footer: 'p-4'
4141
}"
4242
>
4343
<!-- Filters / Pagination Area -->

0 commit comments

Comments
 (0)