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,41 +0,0 @@
import { createFetch } from '@vueuse/core'
import { destr } from 'destr'
export const useApi = createFetch({
baseUrl: import.meta.env.VITE_API_BASE_URL || '/api',
fetchOptions: {
headers: {
Accept: 'application/json',
},
},
options: {
refetch: true,
async beforeFetch({ options }) {
const accessToken = useCookie('accessToken').value
if (accessToken) {
options.headers = {
...options.headers,
Authorization: `Bearer ${accessToken}`,
}
}
return { options }
},
afterFetch(ctx) {
const { data, response } = ctx
// Parse data if it's JSON
let parsedData = null
try {
parsedData = destr(data)
}
catch (error) {
console.error(error)
}
return { data: parsedData, response }
},
},
})

View File

@@ -1,177 +1,137 @@
import { ref, computed } from "vue";
import { $api } from "@/utils/api";
import type {
Event,
CreateEventData,
UpdateEventData,
InviteToEventData,
ApiResponse,
Pagination,
} from "@/types/events";
import { computed, ref } from 'vue'
import { apiClient } from '@/lib/api-client'
import { useCurrentOrganisationId } from '@/composables/useOrganisationContext'
import type { ApiResponse, CreateEventData, Event, Pagination, UpdateEventData } from '@/types/events'
/** Laravel paginated JSON resource response (no `success` wrapper). */
interface LaravelPaginatedEventsBody {
data: Event[]
meta: {
current_page: number
per_page: number
total: number
last_page: number
from: number | null
to: number | null
}
}
function requireOrganisationId(organisationId: string | null): string {
if (!organisationId) {
throw new Error('No organisation in session. Log in again or select an organisation.')
}
return organisationId
}
export function useEvents() {
const events = ref<Event[]>([]);
const currentEvent = ref<Event | null>(null);
const pagination = ref<Pagination | null>(null);
const isLoading = ref(false);
const error = ref<Error | null>(null);
const { organisationId } = useCurrentOrganisationId()
const events = ref<Event[]>([])
const currentEvent = ref<Event | null>(null)
const pagination = ref<Pagination | null>(null)
const isLoading = ref(false)
const error = ref<Error | null>(null)
function eventsPath(): string {
const id = requireOrganisationId(organisationId.value)
return `/organisations/${id}/events`
}
// Fetch all events
async function fetchEvents(params?: {
page?: number;
per_page?: number;
status?: string;
page?: number
per_page?: number
}) {
isLoading.value = true;
error.value = null;
isLoading.value = true
error.value = null
try {
const response = await $api<ApiResponse<Event[]>>("/events", {
method: "GET",
query: params,
});
events.value = response.data;
pagination.value = response.meta?.pagination || null;
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to fetch events");
throw error.value;
} finally {
isLoading.value = false;
const { data } = await apiClient.get<LaravelPaginatedEventsBody>(eventsPath(), { params })
events.value = data.data
pagination.value = {
current_page: data.meta.current_page,
per_page: data.meta.per_page,
total: data.meta.total,
last_page: data.meta.last_page,
from: data.meta.from,
to: data.meta.to,
}
}
catch (err) {
error.value = err instanceof Error ? err : new Error('Failed to fetch events')
throw error.value
}
finally {
isLoading.value = false
}
}
// Fetch single event
async function fetchEvent(id: string) {
isLoading.value = true;
error.value = null;
isLoading.value = true
error.value = null
try {
const response = await $api<ApiResponse<Event>>(`/events/${id}`, {
method: "GET",
});
currentEvent.value = response.data;
return response.data;
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to fetch event");
throw error.value;
} finally {
isLoading.value = false;
const { data } = await apiClient.get<ApiResponse<Event>>(`${eventsPath()}/${id}`)
currentEvent.value = data.data
return data.data
}
catch (err) {
error.value = err instanceof Error ? err : new Error('Failed to fetch event')
throw error.value
}
finally {
isLoading.value = false
}
}
// Create event
async function createEvent(eventData: CreateEventData) {
isLoading.value = true;
error.value = null;
isLoading.value = true
error.value = null
try {
const response = await $api<ApiResponse<Event>>("/events", {
method: "POST",
body: eventData,
});
events.value.unshift(response.data);
return response.data;
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to create event");
throw error.value;
} finally {
isLoading.value = false;
const { data } = await apiClient.post<ApiResponse<Event>>(eventsPath(), eventData)
events.value.unshift(data.data)
return data.data
}
catch (err) {
error.value = err instanceof Error ? err : new Error('Failed to create event')
throw error.value
}
finally {
isLoading.value = false
}
}
// Update event
async function updateEvent(id: string, eventData: UpdateEventData) {
isLoading.value = true;
error.value = null;
isLoading.value = true
error.value = null
try {
const response = await $api<ApiResponse<Event>>(`/events/${id}`, {
method: "PUT",
body: eventData,
});
const index = events.value.findIndex((e) => e.id === id);
if (index !== -1) {
events.value[index] = response.data;
}
if (currentEvent.value?.id === id) {
currentEvent.value = response.data;
}
return response.data;
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to update event");
throw error.value;
} finally {
isLoading.value = false;
const { data } = await apiClient.put<ApiResponse<Event>>(`${eventsPath()}/${id}`, eventData)
const index = events.value.findIndex(e => e.id === id)
if (index !== -1)
events.value[index] = data.data
if (currentEvent.value?.id === id)
currentEvent.value = data.data
return data.data
}
}
// Delete event
async function deleteEvent(id: string) {
isLoading.value = true;
error.value = null;
try {
await $api(`/events/${id}`, {
method: "DELETE",
});
events.value = events.value.filter((e) => e.id !== id);
if (currentEvent.value?.id === id) {
currentEvent.value = null;
}
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to delete event");
throw error.value;
} finally {
isLoading.value = false;
catch (err) {
error.value = err instanceof Error ? err : new Error('Failed to update event')
throw error.value
}
}
// Invite members to event
async function inviteToEvent(eventId: string, inviteData: InviteToEventData) {
isLoading.value = true;
error.value = null;
try {
const response = await $api<ApiResponse<Event["invitations"]>>(
`/events/${eventId}/invite`,
{
method: "POST",
body: inviteData,
}
);
// Refresh event to get updated invitations
if (currentEvent.value?.id === eventId) {
await fetchEvent(eventId);
}
return response.data;
} catch (err) {
error.value =
err instanceof Error ? err : new Error("Failed to invite members");
throw error.value;
} finally {
isLoading.value = false;
finally {
isLoading.value = false
}
}
return {
// State
organisationId: computed(() => organisationId.value),
events: computed(() => events.value),
currentEvent: computed(() => currentEvent.value),
pagination: computed(() => pagination.value),
isLoading: computed(() => isLoading.value),
error: computed(() => error.value),
// Actions
fetchEvents,
fetchEvent,
createEvent,
updateEvent,
deleteEvent,
inviteToEvent,
};
}
}

View File

@@ -0,0 +1,28 @@
import { useCookie } from '@core/composable/useCookie'
import { computed } from 'vue'
export interface AuthOrganisationSummary {
id: string
name: string
slug: string
role: string
}
export interface AuthUserCookie {
id: string
name: string
email: string
roles?: string[]
organisations?: AuthOrganisationSummary[]
}
/**
* First organisation from the session cookie (set at login). Super-admins still need an organisation context for nested event routes.
*/
export function useCurrentOrganisationId() {
const userData = useCookie<AuthUserCookie | null>('userData')
const organisationId = computed(() => userData.value?.organisations?.[0]?.id ?? null)
return { organisationId }
}