Refactor frontend components and enhance API integration

- Updated frontend-nuxt.mdc to specify usage of composables for API calls.
- Added new AuthCard and ConfirmModal components for improved UI consistency.
- Introduced UserAvatar component for user profile display, replacing previous Gravatar implementation.
- Implemented useFormSubmit composable for handling form submissions with loading and error states.
- Enhanced vitest.config.ts to include coverage reporting for composables and components.
- Removed deprecated useAdminApi and useAuth composables to streamline API interactions.
- Updated login and setup pages to utilize new components and composables for better user experience.
This commit is contained in:
2026-03-04 00:14:05 +03:00
parent f6375e7d0f
commit 8f769543f4
34 changed files with 878 additions and 379 deletions

View File

@@ -253,44 +253,37 @@
</div>
</div>
<dialog ref="wipeModalRef" class="modal" aria-labelledby="wipe-modal-title">
<div class="modal-box">
<h2 id="wipe-modal-title" class="font-bold text-lg mb-2">Confirm wipe</h2>
<p>Wipe all grids, markers, tiles and maps? This cannot be undone.</p>
<div class="modal-action">
<form method="dialog">
<button class="btn">Cancel</button>
</form>
<button class="btn btn-error" :disabled="wiping" @click="doWipe">Wipe</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button aria-label="Close">Close</button></form>
</dialog>
<ConfirmModal
v-model="showWipeModal"
title="Confirm wipe"
message="Wipe all grids, markers, tiles and maps? This cannot be undone."
confirm-label="Wipe"
:danger="true"
:loading="wiping"
@confirm="doWipe"
/>
<dialog ref="rebuildModalRef" class="modal" aria-labelledby="rebuild-modal-title">
<div class="modal-box">
<h2 id="rebuild-modal-title" class="font-bold text-lg mb-2">Rebuild zooms</h2>
<p>Rebuild tile zoom levels for all maps? This may take a while.</p>
<div class="modal-action">
<form method="dialog">
<button class="btn">Cancel</button>
</form>
<button class="btn btn-primary" :disabled="rebuilding" @click="doRebuildZooms">Rebuild</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button aria-label="Close">Close</button></form>
</dialog>
<ConfirmModal
v-model="showRebuildModal"
title="Rebuild zooms"
message="Rebuild tile zoom levels for all maps? This may take a while."
confirm-label="Rebuild"
:loading="rebuilding"
@confirm="doRebuildZooms"
/>
</template>
</div>
</template>
<script setup lang="ts">
import type { MapInfoAdmin } from '~/types/api'
definePageMeta({ middleware: 'admin' })
const api = useMapApi()
const toast = useToast()
const users = ref<string[]>([])
const maps = ref<Array<{ ID: number; Name: string; Hidden: boolean; Priority: boolean }>>([])
const maps = ref<MapInfoAdmin[]>([])
const settings = ref({ prefix: '', defaultHide: false, title: '' })
const savingSettings = ref(false)
const rebuilding = ref(false)
@@ -298,8 +291,8 @@ const wiping = ref(false)
const merging = ref(false)
const mergeFile = ref<File | null>(null)
const mergeFileRef = ref<HTMLInputElement | null>(null)
const wipeModalRef = ref<HTMLDialogElement | null>(null)
const rebuildModalRef = ref<HTMLDialogElement | null>(null)
const showWipeModal = ref(false)
const showRebuildModal = ref(false)
const loading = ref(true)
const userSearch = ref('')
const mapSearch = ref('')
@@ -386,17 +379,17 @@ async function saveSettings() {
}
function confirmRebuildZooms() {
rebuildModalRef.value?.showModal()
showRebuildModal.value = true
}
const { markRebuildDone } = useRebuildZoomsInvalidation()
async function doRebuildZooms() {
rebuildModalRef.value?.close()
rebuilding.value = true
try {
await api.adminRebuildZooms()
markRebuildDone()
showRebuildModal.value = false
toast.success('Zooms rebuilt.')
} catch (e) {
toast.error((e as Error)?.message ?? 'Failed to rebuild zooms.')
@@ -406,14 +399,14 @@ async function doRebuildZooms() {
}
function confirmWipe() {
wipeModalRef.value?.showModal()
showWipeModal.value = true
}
async function doWipe() {
wiping.value = true
try {
await api.adminWipe()
wipeModalRef.value?.close()
showWipeModal.value = false
await loadMaps()
toast.success('All data wiped.')
} catch (e) {

View File

@@ -37,13 +37,15 @@
</template>
<script setup lang="ts">
import type { MapInfoAdmin } from '~/types/api'
definePageMeta({ middleware: 'admin' })
const route = useRoute()
const router = useRouter()
const api = useMapApi()
const id = computed(() => parseInt(route.params.id as string, 10))
const map = ref<{ ID: number; Name: string; Hidden: boolean; Priority: boolean } | null>(null)
const map = ref<MapInfoAdmin | null>(null)
const mapsLoaded = ref(false)
const form = ref({ name: '', hidden: false, priority: false })
const loading = ref(false)

View File

@@ -47,18 +47,15 @@
</div>
</form>
<dialog ref="deleteModalRef" class="modal">
<div class="modal-box">
<p>Delete user {{ form.user }}?</p>
<div class="modal-action">
<form method="dialog">
<button class="btn">Cancel</button>
</form>
<button class="btn btn-error" :disabled="deleting" @click="doDelete">Delete</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button aria-label="Close">Close</button></form>
</dialog>
<ConfirmModal
v-model="showDeleteModal"
title="Delete user"
:message="`Delete user ${form.user}?`"
confirm-label="Delete"
:danger="true"
:loading="deleting"
@confirm="doDelete"
/>
</div>
</template>
@@ -76,7 +73,7 @@ const authOptions = ['admin', 'map', 'markers', 'upload']
const loading = ref(false)
const deleting = ref(false)
const error = ref('')
const deleteModalRef = ref<HTMLDialogElement | null>(null)
const showDeleteModal = ref(false)
onMounted(async () => {
if (!isNew.value) {
@@ -108,14 +105,14 @@ async function submit() {
}
function confirmDelete() {
deleteModalRef.value?.showModal()
showDeleteModal.value = true
}
async function doDelete() {
deleting.value = true
try {
await api.adminUserDelete(form.value.user)
deleteModalRef.value?.close()
showDeleteModal.value = false
await router.push('/admin')
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Delete failed'