329 lines
11 KiB
Vue
329 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useEvents } from '../composables/useEvents'
|
|
import { useGoogleDrive } from '../composables/useGoogleDrive'
|
|
import type { EventFormData } from '../types/event'
|
|
|
|
const router = useRouter()
|
|
const { createEvent, loading: saving, error } = useEvents()
|
|
const { fetchStatus, getAuthUrl, listSharedDrives, listFolders, createFolder } = useGoogleDrive()
|
|
|
|
const driveConnected = ref(false)
|
|
const sharedDrives = ref<{ id: string; name: string; type: string }[]>([])
|
|
const folders = ref<{ id: string; name: string }[]>([])
|
|
const selectedDriveId = ref('')
|
|
const selectedDriveName = ref('')
|
|
const selectedFolderId = ref('')
|
|
const selectedFolderName = ref('')
|
|
const viewMode = ref<'my-drive' | 'shared-drives'>('my-drive')
|
|
|
|
const form = reactive<Partial<EventFormData>>({
|
|
name: '',
|
|
description: '',
|
|
slug: '',
|
|
google_drive_folder_id: '',
|
|
google_drive_folder_name: '',
|
|
is_active: true,
|
|
upload_start_at: '',
|
|
upload_end_at: '',
|
|
max_file_size_mb: 500,
|
|
allowed_extensions: ['mp4', 'mov', 'avi', 'mkv', 'webm'],
|
|
require_password: false,
|
|
upload_password: '',
|
|
})
|
|
|
|
const EXT_OPTIONS = ['mp4', 'mov', 'avi', 'mkv', 'webm', 'jpg', 'jpeg', 'png']
|
|
|
|
onMounted(async () => {
|
|
const s = await fetchStatus().catch(() => ({ connected: false }))
|
|
driveConnected.value = s?.connected ?? false
|
|
if (driveConnected.value) {
|
|
await loadMyDrive()
|
|
}
|
|
})
|
|
|
|
async function connectDrive() {
|
|
const url = await getAuthUrl()
|
|
window.location.href = url
|
|
}
|
|
|
|
async function loadMyDrive() {
|
|
viewMode.value = 'my-drive'
|
|
selectedDriveId.value = ''
|
|
selectedDriveName.value = ''
|
|
folders.value = await listFolders().catch(() => [])
|
|
}
|
|
|
|
async function loadSharedDrives() {
|
|
viewMode.value = 'shared-drives'
|
|
selectedDriveId.value = ''
|
|
selectedDriveName.value = ''
|
|
selectedFolderId.value = ''
|
|
selectedFolderName.value = ''
|
|
folders.value = []
|
|
sharedDrives.value = await listSharedDrives().catch(() => [])
|
|
}
|
|
|
|
function selectDrive(id: string, name: string) {
|
|
selectedDriveId.value = id
|
|
selectedDriveName.value = name
|
|
selectedFolderId.value = ''
|
|
selectedFolderName.value = ''
|
|
loadFoldersInDrive(id)
|
|
}
|
|
|
|
async function loadFoldersInDrive(driveId: string, parentId?: string) {
|
|
folders.value = await listFolders(parentId, driveId)
|
|
}
|
|
|
|
async function loadFolders(parentId?: string) {
|
|
if (selectedDriveId.value) {
|
|
folders.value = await listFolders(parentId, selectedDriveId.value)
|
|
} else {
|
|
folders.value = await listFolders(parentId)
|
|
}
|
|
}
|
|
|
|
function selectFolder(id: string, name: string) {
|
|
selectedFolderId.value = id
|
|
selectedFolderName.value = name
|
|
form.google_drive_folder_id = id
|
|
form.google_drive_folder_name = name
|
|
}
|
|
|
|
async function handleCreateFolder() {
|
|
const name = prompt('Folder name')
|
|
if (!name) return
|
|
const created = await createFolder(
|
|
name,
|
|
selectedFolderId.value || undefined,
|
|
selectedDriveId.value || undefined
|
|
)
|
|
await loadFolders(selectedFolderId.value || undefined)
|
|
selectFolder(created.id, created.name)
|
|
}
|
|
|
|
async function onSubmit() {
|
|
try {
|
|
const payload = {
|
|
...form,
|
|
google_drive_folder_id: form.google_drive_folder_id || null,
|
|
google_drive_folder_name: form.google_drive_folder_name || null,
|
|
upload_password: form.require_password ? form.upload_password : null,
|
|
}
|
|
const event = await createEvent(payload)
|
|
router.push({ name: 'event-uploads', params: { id: event.id } })
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="admin-page admin-form-page">
|
|
<router-link to="/" class="back-link mb-4">
|
|
← Back to Events
|
|
</router-link>
|
|
|
|
<div class="admin-card admin-card-body mb-4">
|
|
<h2 class="section-title mb-4">Event details</h2>
|
|
<form @submit.prevent="onSubmit" class="row g-3">
|
|
<div v-if="error" class="col-12 alert alert-danger py-2">{{ error }}</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Name</label>
|
|
<input v-model="form.name" type="text" class="form-control" required placeholder="e.g. Summer Conference 2025" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Description</label>
|
|
<textarea v-model="form.description" class="form-control" rows="2" placeholder="Optional description for attendees"></textarea>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">URL slug <span class="text-muted">(optional, auto-generated from name)</span></label>
|
|
<input v-model="form.slug" type="text" class="form-control" placeholder="my-event" />
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="admin-card admin-card-body mb-4">
|
|
<h2 class="section-title mb-3">Google Drive folder</h2>
|
|
<p class="text-muted small mb-3">Uploads will be saved to the selected folder.</p>
|
|
<div v-if="!driveConnected" class="drive-connect-box p-4 rounded-3 bg-light border">
|
|
<p class="mb-3 text-muted small">Connect your Google account to choose a destination folder.</p>
|
|
<button type="button" class="btn btn-primary" @click="connectDrive">
|
|
Connect Google Drive
|
|
</button>
|
|
</div>
|
|
<div v-else>
|
|
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button
|
|
type="button"
|
|
class="btn"
|
|
:class="viewMode === 'my-drive' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="loadMyDrive"
|
|
>
|
|
My Drive
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn"
|
|
:class="viewMode === 'shared-drives' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="loadSharedDrives"
|
|
>
|
|
Shared Drives
|
|
</button>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" @click="loadFolders()">
|
|
Refresh
|
|
</button>
|
|
<button
|
|
v-if="viewMode === 'my-drive' || selectedDriveId"
|
|
type="button"
|
|
class="btn btn-sm btn-outline-primary"
|
|
@click="handleCreateFolder"
|
|
>
|
|
Create folder
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="viewMode === 'shared-drives' && !selectedDriveId">
|
|
<ul class="folder-list list-group">
|
|
<li
|
|
v-for="d in sharedDrives"
|
|
:key="d.id"
|
|
class="list-group-item list-group-item-action"
|
|
@click="selectDrive(d.id, d.name)"
|
|
>
|
|
📁 {{ d.name }}
|
|
</li>
|
|
</ul>
|
|
<p v-if="sharedDrives.length === 0" class="text-muted small mt-2 mb-0">No shared drives found</p>
|
|
</div>
|
|
|
|
<div v-if="viewMode === 'my-drive' || selectedDriveId">
|
|
<p v-if="selectedDriveName" class="text-muted small mb-2">
|
|
📁 {{ selectedDriveName }}
|
|
<button type="button" class="btn btn-sm btn-link p-0 ms-2" @click="loadSharedDrives">(change)</button>
|
|
</p>
|
|
<ul class="folder-list list-group">
|
|
<li
|
|
v-for="f in folders"
|
|
:key="f.id"
|
|
class="list-group-item list-group-item-action"
|
|
:class="{ active: selectedFolderId === f.id }"
|
|
@click="selectFolder(f.id, f.name)"
|
|
>
|
|
{{ f.name }}
|
|
</li>
|
|
</ul>
|
|
<p v-if="selectedFolderName" class="mt-2 text-muted small mb-0">
|
|
Selected: <strong>{{ selectedFolderName }}</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-card admin-card-body mb-4">
|
|
<h2 class="section-title mb-4">Settings</h2>
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-6">
|
|
<div class="form-check form-switch">
|
|
<input v-model="form.is_active" type="checkbox" class="form-check-input" id="is_active" />
|
|
<label class="form-check-label" for="is_active">Event is active (accepting uploads)</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Max file size (MB)</label>
|
|
<input v-model.number="form.max_file_size_mb" type="number" class="form-control" min="1" max="2000" />
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Upload window start</label>
|
|
<input v-model="form.upload_start_at" type="datetime-local" class="form-control" />
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Upload window end</label>
|
|
<input v-model="form.upload_end_at" type="datetime-local" class="form-control" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Allowed file extensions</label>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<label v-for="ext in EXT_OPTIONS" :key="ext" class="form-check form-check-inline">
|
|
<input
|
|
type="checkbox"
|
|
class="form-check-input"
|
|
:value="ext"
|
|
:checked="form.allowed_extensions?.includes(ext)"
|
|
@change="(e: globalThis.Event) => {
|
|
const arr = form.allowed_extensions || []
|
|
const target = (e.target as HTMLInputElement)
|
|
if (target.checked) form.allowed_extensions = [...arr, ext]
|
|
else form.allowed_extensions = arr.filter(x => x !== ext)
|
|
}"
|
|
/>
|
|
<span class="form-check-label">.{{ ext }}</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="form-check form-switch mb-2">
|
|
<input v-model="form.require_password" type="checkbox" class="form-check-input" id="require_password" />
|
|
<label class="form-check-label" for="require_password">Require upload password</label>
|
|
</div>
|
|
<input
|
|
v-if="form.require_password"
|
|
v-model="form.upload_password"
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Password for attendees"
|
|
minlength="4"
|
|
style="max-width: 280px"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-card admin-card-body">
|
|
<button type="submit" class="btn btn-primary btn-lg" :disabled="saving" @click="onSubmit">
|
|
{{ saving ? 'Creating...' : 'Create Event' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.admin-form-page {
|
|
max-width: 720px;
|
|
}
|
|
|
|
.back-link {
|
|
color: var(--admin-muted);
|
|
text-decoration: none;
|
|
font-size: 0.9375rem;
|
|
font-weight: 500;
|
|
display: inline-block;
|
|
}
|
|
|
|
.back-link:hover {
|
|
color: var(--admin-primary);
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: var(--admin-heading);
|
|
margin: 0;
|
|
}
|
|
|
|
.folder-list {
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
border-radius: var(--admin-radius);
|
|
}
|
|
|
|
.drive-connect-box {
|
|
border-color: var(--admin-border) !important;
|
|
}
|
|
</style>
|