Added song loading state

This commit is contained in:
lucast committed 2026-10-05 03:46:47 +02:00
1 parent 869ca7d728
commit e908ae27ad
3 files changed
+51 -23

No files matched your search

+21 -1
View File
@@ -47,6 +47,8 @@ interface ModalFormProps<T> {
fields: Field<T>[] fields: Field<T>[]
title?: string title?: string
onSave: (value: any) => Promise<boolean> onSave: (value: any) => Promise<boolean>
loading?: boolean
loadingText?: string
} }
const props = defineProps<ModalFormProps<any>>() const props = defineProps<ModalFormProps<any>>()
@@ -150,7 +152,10 @@ watch(
/> />
</template> </template>
<Button type="submit" full center-text>Save</Button> <Button type="submit" full center-text :disabled="props.loading">
<span v-if="props.loading" class="spinner" aria-hidden="true" />
{{ props.loading ? props.loadingText || "Saving…" : "Save" }}
</Button>
</form> </form>
</Modal> </Modal>
</template> </template>
@@ -169,4 +174,19 @@ watch(
flex-direction: column; flex-direction: column;
gap: 0.8rem; gap: 0.8rem;
} }
.spinner {
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style> </style>
+28 -22
View File
@@ -106,6 +106,7 @@ const getOptionId = (option: string | Option) =>
const showModal = ref<boolean>(false) const showModal = ref<boolean>(false)
const item = ref<Track | undefined>(undefined) const item = ref<Track | undefined>(undefined)
const addingSong = ref(false)
const save = async (value: any) => { const save = async (value: any) => {
if (value._id) { if (value._id) {
@@ -127,24 +128,31 @@ const save = async (value: any) => {
return false return false
} }
} else { } else {
if (!value.name || !value.file || !value.artists?.length) {
toast.show(
"Name, audio file, and at least one artist are required",
"error",
)
return false
}
if (value.createSingle && (!value.cover || !value.genre)) {
toast.show(
"A cover image and genre are required for a single",
"error",
)
return false
}
if (!value.createSingle && !value.album) {
toast.show("Choose an album or create a single album", "error")
return false
}
addingSong.value = true
await nextTick()
try { try {
if (!value.name || !value.file || !value.artists?.length) {
toast.show(
"Name, audio file, and at least one artist are required",
"error",
)
return false
}
if (value.createSingle) { if (value.createSingle) {
if (!value.cover || !value.genre) {
toast.show(
"A cover image and genre are required for a single",
"error",
)
return false
}
const albumResponse = await cfetch<{ _id: string }>("/admin/album", { const albumResponse = await cfetch<{ _id: string }>("/admin/album", {
method: "POST", method: "POST",
data: { data: {
@@ -155,12 +163,6 @@ const save = async (value: any) => {
}, },
}) })
value.album = albumResponse._id value.album = albumResponse._id
} else if (!value.album) {
toast.show(
"Choose an album or create a single album",
"error",
)
return false
} }
await cfetch("/admin/track", { await cfetch("/admin/track", {
@@ -178,6 +180,8 @@ const save = async (value: any) => {
toast.show("Failed to add song", "error") toast.show("Failed to add song", "error")
return false return false
} finally {
addingSong.value = false
} }
} }
@@ -241,6 +245,8 @@ useHead({ title: "Admin - Songs" })
:fields="modalFields" :fields="modalFields"
:item="item" :item="item"
:title="item ? 'Edit song' : 'Add new song'" :title="item ? 'Edit song' : 'Add new song'"
:loading="addingSong"
:loading-text="'Adding song…'"
/> />
</template> </template>
+2
View File
@@ -8,6 +8,7 @@ interface ButtonProps {
full?: boolean full?: boolean
iconName?: string iconName?: string
centerText?: boolean centerText?: boolean
disabled?: boolean
} }
const props = withDefaults(defineProps<ButtonProps>(), { const props = withDefaults(defineProps<ButtonProps>(), {
@@ -22,6 +23,7 @@ const props = withDefaults(defineProps<ButtonProps>(), {
props.type == 'link' && props.to != undefined ? NuxtLink : 'button' props.type == 'link' && props.to != undefined ? NuxtLink : 'button'
" "
:to="props.to" :to="props.to"
:disabled="props.disabled"
:class="[ :class="[
`button type-${props.variant}`, `button type-${props.variant}`,
{ {