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:
@@ -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 }
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
28
apps/admin/src/composables/useOrganisationContext.ts
Normal file
28
apps/admin/src/composables/useOrganisationContext.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user