Initial commit
This commit is contained in:
189
upload/src/components/FileUploader.vue
Normal file
189
upload/src/components/FileUploader.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import Uppy from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
import XHRUpload from '@uppy/xhr-upload'
|
||||
import type { PublicEvent } from '../types/event'
|
||||
import { usePasswordVerification } from '../composables/usePasswordVerification'
|
||||
|
||||
const props = defineProps<{
|
||||
event: PublicEvent
|
||||
slug: string
|
||||
}>()
|
||||
|
||||
const { getPassword } = usePasswordVerification(props.slug)
|
||||
const uppyContainer = ref<HTMLElement | null>(null)
|
||||
const showSuccess = ref(false)
|
||||
let successTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
let uppy: Uppy | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (!uppyContainer.value || !props.event) return
|
||||
|
||||
const baseURL = import.meta.env.DEV ? '' : (import.meta.env.VITE_API_URL || '')
|
||||
const endpoint = `${baseURL}/api/events/${props.slug}/upload`
|
||||
|
||||
uppy = new Uppy({
|
||||
restrictions: {
|
||||
maxFileSize: props.event.max_file_size_mb * 1024 * 1024,
|
||||
allowedFileTypes: props.event.allowed_extensions.map((ext) => `.${ext}`),
|
||||
},
|
||||
autoProceed: false,
|
||||
})
|
||||
|
||||
uppy.use(Dashboard, {
|
||||
target: uppyContainer.value,
|
||||
inline: true,
|
||||
proudlyDisplayPoweredByUppy: false,
|
||||
showProgressDetails: true,
|
||||
height: 360,
|
||||
theme: 'light',
|
||||
})
|
||||
|
||||
uppy.use(XHRUpload, {
|
||||
endpoint,
|
||||
method: 'POST',
|
||||
formData: true,
|
||||
fieldName: 'file',
|
||||
withCredentials: true,
|
||||
getResponseData() {
|
||||
return {}
|
||||
},
|
||||
getRequestHeaders() {
|
||||
const headers: Record<string, string> = {}
|
||||
const password = getPassword()
|
||||
if (password) {
|
||||
headers['X-Upload-Password'] = password
|
||||
}
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('XSRF-TOKEN='))
|
||||
if (token) {
|
||||
headers['X-XSRF-TOKEN'] = decodeURIComponent(token.split('=')[1])
|
||||
}
|
||||
return headers
|
||||
},
|
||||
})
|
||||
|
||||
uppy.on('complete', () => {
|
||||
showSuccess.value = true
|
||||
if (successTimeout) clearTimeout(successTimeout)
|
||||
successTimeout = setTimeout(() => {
|
||||
showSuccess.value = false
|
||||
successTimeout = null
|
||||
}, 4000)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (successTimeout) clearTimeout(successTimeout)
|
||||
uppy?.close()
|
||||
uppy = null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.slug,
|
||||
() => {
|
||||
uppy?.close()
|
||||
uppy = null
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="file-uploader-wrapper">
|
||||
<Transition name="upload-success">
|
||||
<div v-if="showSuccess" class="upload-success-banner" role="status" aria-live="polite">
|
||||
<span class="upload-success-icon" aria-hidden="true">✓</span>
|
||||
<span>Upload complete</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div ref="uppyContainer" class="uppy-dashboard-container"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.file-uploader-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-success-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
border: 1px solid var(--upload-primary);
|
||||
border-radius: var(--upload-radius);
|
||||
color: var(--upload-primary);
|
||||
font-weight: 600;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.upload-success-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
background: var(--upload-primary);
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.upload-success-enter-active,
|
||||
.upload-success-leave-active {
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
.upload-success-enter-from,
|
||||
.upload-success-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.uppy-dashboard-container {
|
||||
--uppy-dashboard-accent-color: #6366f1;
|
||||
--uppy-dashboard-accent-color-hover: #4f46e5;
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-inner {
|
||||
border-radius: var(--upload-radius);
|
||||
border: 1px solid var(--upload-border);
|
||||
background: var(--upload-surface);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-AddFiles-info {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-size: 15px;
|
||||
color: var(--upload-muted);
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-AddFiles-title {
|
||||
font-family: "Plus Jakarta Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
color: var(--upload-heading);
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-browse {
|
||||
color: var(--upload-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-browse:hover {
|
||||
color: var(--upload-primary-hover);
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-Dashboard-Item-previewInnerWrap {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.uppy-dashboard-container .uppy-StatusBar.is-waiting .uppy-StatusBar-bar {
|
||||
background: linear-gradient(90deg, #6366f1 0%, #818cf8 100%);
|
||||
}
|
||||
</style>
|
||||
41
upload/src/components/HelloWorld.vue
Normal file
41
upload/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user