This commit is contained in:
2025-12-02 17:02:20 -05:00
parent f82ba25160
commit 6423d1c1a2
49 changed files with 2320 additions and 349 deletions

View File

@@ -0,0 +1,24 @@
import { onMounted, onBeforeUnmount } from 'vue'
import { eventBus } from './eventBus'
export function useBackendEvents(userId: string) {
let eventSource: EventSource | null = null
onMounted(() => {
console.log('Connecting to backend events for user:', userId)
eventSource = new EventSource(`/events?user_id=${userId}`)
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data)
// Emit globally for any component that cares
eventBus.emit(data.type, data)
eventBus.emit('sse', data) // optional: catch-all channel
}
})
onBeforeUnmount(() => {
console.log('Disconnecting from backend events for user:', userId)
eventSource?.close()
})
}

View File

@@ -0,0 +1,29 @@
type Callback = (payload: any) => void
class EventBus {
private listeners: Map<string, Callback[]> = new Map()
on(event: string, callback: Callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, [])
}
this.listeners.get(event)!.push(callback)
}
off(event: string, callback: Callback) {
const list = this.listeners.get(event)
if (!list) return
const index = list.indexOf(callback)
if (index !== -1) list.splice(index, 1)
}
emit(event: string, payload?: any) {
const list = this.listeners.get(event)
if (!list) return
list.forEach((callback) => callback(payload))
}
}
export const eventBus = new EventBus()

View File

@@ -0,0 +1,120 @@
export interface Task {
id: string
name: string
points: number
is_good: boolean
image_id: string | null
image_url?: string | null // optional, for resolved URLs
}
export interface Child {
id: string | number
name: string
age: number
points?: number
image_id: string | null
image_url?: string | null // optional, for resolved URLs
}
export interface Reward {
id: string
name: string
cost: number
points_needed: number
image_id: string | null
image_url?: string | null // optional, for resolved URLs
}
export interface RewardStatus {
id: string
name: string
points_needed: number
cost: number
image_id: string | null
image_url?: string | null // optional, for resolved URLs
}
export interface Event {
type: string
payload:
| TaskUpdateEventPayload
| RewardUpdateEventPayload
| ChildUpdateEventPayload
| ChildDeleteEventPayload
| TaskCreatedEventPayload
| TaskDeletedEventPayload
| TaskEditedEventPayload
| RewardCreatedEventPayload
| RewardDeletedEventPayload
| RewardEditedEventPayload
| RewardSetEventPayload
| TaskSetEventPayload
| ChildAddEventPayload
}
export interface TaskUpdateEventPayload {
task_id: string
child_id: string
status: string
points: number
}
export interface TaskSetEventPayload {
child_id: string
status: string
}
export interface RewardUpdateEventPayload {
reward_id: string
child_id: string
status: string
points: number
}
export interface RewardSetEventPayload {
child_id: string
status: string
}
export interface ChildAddEventPayload {
child_id: string
status: string
}
export interface ChildUpdateEventPayload {
child_id: string
status: string
}
export interface ChildDeleteEventPayload {
child_id: string
status: string
}
export interface TaskCreatedEventPayload {
task_id: string
status: string
}
export interface TaskDeletedEventPayload {
task_id: string
status: string
}
export interface TaskEditedEventPayload {
task_id: string
status: string
}
export interface RewardCreatedEventPayload {
reward_id: string
}
export interface RewardDeletedEventPayload {
reward_id: string
status: string
}
export interface RewardEditedEventPayload {
reward_id: string
status: string
}