forked from BRETT/BRETT
Adds a reusable shadcn-style Spinner (lucide loader-circle icon, localized aria-label) and wires it into every button that has a pending state: comment submit, delete, tag save, profile save, admin saves, upload, login and register. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
99 lines
3.1 KiB
Vue
99 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import FileDropzone from '@/components/FileDropzone.vue'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverDescription,
|
|
PopoverHeader,
|
|
PopoverTitle,
|
|
PopoverTrigger,
|
|
} from '@/components/ui/popover'
|
|
import { Spinner } from '@/components/ui/spinner'
|
|
|
|
const { user } = useAuth()
|
|
const { t } = useI18n()
|
|
const open = ref(false)
|
|
const file = ref<File | null>(null)
|
|
const url = ref('')
|
|
const errorMessage = ref('')
|
|
const loading = ref(false)
|
|
|
|
async function onSubmit() {
|
|
if (loading.value || (!file.value && !url.value.trim())) return
|
|
loading.value = true
|
|
errorMessage.value = ''
|
|
try {
|
|
if (url.value.trim()) {
|
|
await $fetch('/api/uploads', {
|
|
method: 'POST',
|
|
body: { url: url.value.trim() },
|
|
})
|
|
}
|
|
else {
|
|
const formData = new FormData()
|
|
formData.append('file', file.value!)
|
|
await $fetch('/api/uploads', { method: 'POST', body: formData })
|
|
}
|
|
file.value = null
|
|
url.value = ''
|
|
open.value = false
|
|
await refreshNuxtData('uploads')
|
|
await navigateTo('/')
|
|
}
|
|
catch (err) {
|
|
const e = err as { data?: { statusMessage?: string, message?: string } }
|
|
errorMessage.value = e.data?.statusMessage ?? e.data?.message ?? t('upload.failed')
|
|
}
|
|
finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Popover v-model:open="open">
|
|
<PopoverTrigger as-child>
|
|
<Button variant="ghost">
|
|
<Icon name="ph:cloud-arrow-up-fill" class="size-4" />
|
|
{{ $t('upload.upload') }}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent align="start" class="w-160">
|
|
<template v-if="user">
|
|
<PopoverHeader>
|
|
<PopoverTitle>{{ $t('upload.upload') }}</PopoverTitle>
|
|
<PopoverDescription>{{ $t('upload.desc') }}</PopoverDescription>
|
|
</PopoverHeader>
|
|
<form class="space-y-4" @submit.prevent="onSubmit">
|
|
<div class="space-y-1.5">
|
|
<Label>{{ $t('upload.file') }}</Label>
|
|
<FileDropzone v-model="file" :disabled="!!url.trim()" />
|
|
</div>
|
|
<div class="space-y-1.5">
|
|
<Label for="upload-url">{{ $t('upload.orUrl') }}</Label>
|
|
<Input id="upload-url" v-model="url" type="url" placeholder="https://…" :disabled="!!file" />
|
|
<p class="text-xs text-muted-foreground">
|
|
{{ $t('upload.urlHint') }}
|
|
</p>
|
|
</div>
|
|
<p v-if="errorMessage" class="text-sm text-destructive">
|
|
{{ errorMessage }}
|
|
</p>
|
|
<Button type="submit" class="w-full" :disabled="loading || (!file && !url.trim())">
|
|
<Spinner v-if="loading" />
|
|
{{ loading ? $t('upload.uploading') : $t('upload.upload') }}
|
|
</Button>
|
|
</form>
|
|
</template>
|
|
<p v-else class="text-sm text-muted-foreground">
|
|
<NuxtLink to="/login" class="text-primary underline" @click="open = false">
|
|
{{ $t('common.loginLink') }}
|
|
</NuxtLink>{{ $t('upload.loginToUpload') }}
|
|
</p>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</template>
|