refactor: align codebase with EventCrew domain and trim legacy band stack

- Update API: events, users, policies, routes, resources, migrations
- Remove deprecated models/resources (customers, setlists, invitations, etc.)
- Refresh admin app and docs; remove apps/band

Made-with: Cursor
This commit is contained in:
2026-03-29 23:19:06 +02:00
parent 34e12e00b3
commit 1cb7674d52
1034 changed files with 7453 additions and 8743 deletions

View File

@@ -1,176 +0,0 @@
<script setup lang="ts">
import { $api } from '@/utils/api'
import type { User, ApiResponse } from '@/types/events'
const props = defineProps<{
isDialogOpen: boolean
eventId: string
}>()
const emit = defineEmits<{
(e: 'update:isDialogOpen', val: boolean): void
(e: 'invited'): void
}>()
const selectedUserIds = ref<string[]>([])
const availableUsers = ref<User[]>([])
const isLoading = ref(false)
const isInviting = ref(false)
const searchQuery = ref('')
const isDialogOpenModel = computed({
get: () => props.isDialogOpen,
set: (val) => emit('update:isDialogOpen', val),
})
// Fetch available users (members)
async function fetchUsers() {
isLoading.value = true
try {
// TODO: Replace with actual users/members endpoint when available
// For now, this will fail gracefully and show an empty list
// Expected endpoint: GET /users or GET /members with filters for type=member
const response = await $api<ApiResponse<User[]>>('/users', {
method: 'GET',
query: {
type: 'member',
status: 'active',
},
})
availableUsers.value = response.data
} catch (err) {
console.error('Failed to fetch users. Make sure a /users endpoint exists:', err)
// Set empty array on error so UI doesn't break
availableUsers.value = []
} finally {
isLoading.value = false
}
}
// Watch dialog open to fetch users
watch(() => props.isDialogOpen, (isOpen) => {
if (isOpen) {
fetchUsers()
selectedUserIds.value = []
}
})
async function handleInvite() {
if (selectedUserIds.value.length === 0) {
return
}
isInviting.value = true
try {
await $api(`/events/${props.eventId}/invite`, {
method: 'POST',
body: {
user_ids: selectedUserIds.value,
},
})
emit('invited')
isDialogOpenModel.value = false
} catch (err) {
console.error('Failed to invite members:', err)
} finally {
isInviting.value = false
}
}
const filteredUsers = computed(() => {
if (!searchQuery.value) {
return availableUsers.value
}
const query = searchQuery.value.toLowerCase()
return availableUsers.value.filter(
user =>
user.name.toLowerCase().includes(query) ||
user.email.toLowerCase().includes(query)
)
})
</script>
<template>
<VDialog
v-model="isDialogOpenModel"
max-width="600"
>
<VCard>
<VCardTitle>Invite Members to Event</VCardTitle>
<VDivider />
<VCardText>
<AppTextField
v-model="searchQuery"
placeholder="Search members..."
prepend-inner-icon="tabler-search"
class="mb-4"
/>
<div
v-if="isLoading"
class="text-center py-8"
>
<VProgressCircular
indeterminate
color="primary"
/>
</div>
<div
v-else
class="member-list"
style="max-height: 400px; overflow-y: auto;"
>
<VCheckbox
v-for="user in filteredUsers"
:key="user.id"
v-model="selectedUserIds"
:value="user.id"
class="mb-2"
>
<template #label>
<div>
<div class="text-body-1">
{{ user.name }}
</div>
<div class="text-body-2 text-medium-emphasis">
{{ user.email }}
</div>
</div>
</template>
</VCheckbox>
<VAlert
v-if="filteredUsers.length === 0"
type="info"
>
No members found
</VAlert>
</div>
</VCardText>
<VDivider />
<VCardActions>
<VSpacer />
<VBtn
variant="text"
@click="isDialogOpenModel = false"
>
Cancel
</VBtn>
<VBtn
color="primary"
:disabled="selectedUserIds.length === 0"
:loading="isInviting"
@click="handleInvite"
>
Invite {{ selectedUserIds.length }} Member(s)
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>