Initial commit

This commit is contained in:
2026-02-03 10:38:46 +01:00
commit eb304f4b14
144 changed files with 22605 additions and 0 deletions

View 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>

View 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>