← back to davidaoliver__Imperial-Sporthorses

Function bodies 104 total

All specs Real LLM only Function bodies
SetupScreen function · javascript · L8-L76 (69 LOC)
src/App.jsx
function SetupScreen() {
  return (
    <div className="min-h-screen flex flex-col items-center justify-center bg-neutral-950 px-6">
      <div className="bg-neutral-900 border border-neutral-800 rounded-2xl shadow-2xl p-8 w-full max-w-md animate-scale-in">
        <div className="flex justify-center mb-6">
          <img src="/logo.png" alt="Imperial Sporthorses" className="h-20 w-20 object-contain" />
        </div>
        <h1 className="text-2xl font-bold text-amber-400 mb-1 text-center">
          Imperial Sporthorses
        </h1>
        <p className="text-neutral-400 text-sm mb-6 text-center">
          Almost ready! Connect your Supabase project to get started.
        </p>

        <div className="space-y-4 text-sm">
          <div className="flex gap-3 items-start bg-neutral-800 rounded-xl p-3">
            <Database className="w-5 h-5 text-amber-400 shrink-0 mt-0.5" />
            <div>
              <p className="font-semibold text-neutral-100">1. Create a Supabase Project
App function · javascript · L78-L109 (32 LOC)
src/App.jsx
export default function App() {
  const { session, profile, loading } = useAuth()

  if (!isConfigured) {
    return <SetupScreen />
  }

  if (loading) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-neutral-950">
        <div className="text-center animate-fade-in">
          <img src="/logo.png" alt="Imperial Sporthorses" className="h-16 w-16 object-contain mx-auto mb-4 opacity-80" />
          <RefreshCw className="w-8 h-8 text-amber-400 animate-spin mx-auto mb-3" />
          <p className="text-sm text-neutral-400">Loading...</p>
        </div>
      </div>
    )
  }

  // Not logged in
  if (!session) {
    return <LoginPage />
  }

  // Logged in but no display name -> complete profile
  if (!profile?.display_name) {
    return <CompleteProfilePage />
  }

  // Fully authenticated
  return <Layout />
}
HandOffModal function · javascript · L5-L38 (34 LOC)
src/components/HandOffModal.jsx
export default function HandOffModal({ shift, dateStr, users, currentUserId, onClose, onSubmit }) {
  const otherUsers = users.filter((u) => u.id !== currentUserId)

  return (
    <div className="fixed inset-0 bg-black/40 z-50 flex items-end justify-center animate-fade-in">
      <div className="bg-neutral-900 border-t border-neutral-700 rounded-t-2xl w-full max-w-lg p-5 pb-8 animate-slide-up safe-area-bottom">
        <div className="flex items-center justify-between mb-4">
          <div className="flex items-center gap-2">
            <ArrowRightLeft className="w-4 h-4 text-amber-400" />
            <h3 className="font-semibold text-neutral-100">Hand Off Shift</h3>
          </div>
          <button onClick={onClose} className="p-2">
            <X className="w-5 h-5 text-neutral-500" />
          </button>
        </div>
        <p className="text-sm text-neutral-400 mb-4">
          Hand off your <span className="font-medium text-neutral-200">{SHIFT_LABELS[shift] || shift}</span>
Layout function · javascript · L28-L139 (112 LOC)
src/components/Layout.jsx
export default function Layout() {
  const [activeTab, setActiveTab] = useState('tasks')
  const [tasksView, setTasksView] = useState('weekly') // 'weekly' or 'board'
  const { isAdmin } = useAuth()

  function renderTab() {
    switch (activeTab) {
      case 'tasks':
        return (
          <div>
            {/* Sub-tabs for Tasks */}
            <div className="flex items-center gap-2 px-4 pt-5 pb-2">
              <button
                onClick={() => setTasksView('weekly')}
                className={`flex items-center gap-1.5 text-sm font-semibold px-4 py-2 rounded-xl transition ${
                  tasksView === 'weekly'
                    ? 'bg-amber-500/20 text-amber-400'
                    : 'bg-neutral-800/60 text-neutral-400 hover:text-neutral-300'
                }`}
              >
                <CalendarDays className="w-4 h-4" />
                Weekly
              </button>
              <button
                onClick={() => setTasksView('board')}
           
renderTab function · javascript · L33-L88 (56 LOC)
src/components/Layout.jsx
  function renderTab() {
    switch (activeTab) {
      case 'tasks':
        return (
          <div>
            {/* Sub-tabs for Tasks */}
            <div className="flex items-center gap-2 px-4 pt-5 pb-2">
              <button
                onClick={() => setTasksView('weekly')}
                className={`flex items-center gap-1.5 text-sm font-semibold px-4 py-2 rounded-xl transition ${
                  tasksView === 'weekly'
                    ? 'bg-amber-500/20 text-amber-400'
                    : 'bg-neutral-800/60 text-neutral-400 hover:text-neutral-300'
                }`}
              >
                <CalendarDays className="w-4 h-4" />
                Weekly
              </button>
              <button
                onClick={() => setTasksView('board')}
                className={`flex items-center gap-1.5 text-sm font-semibold px-4 py-2 rounded-xl transition ${
                  tasksView === 'board'
                    ? 'bg-amber-500/20 text-amber-400'
     
NoteEditor function · javascript · L5-L192 (188 LOC)
src/components/NoteEditor.jsx
export default function NoteEditor({ note, onBack, onDelete, onSave, onTogglePin }) {
  const [title, setTitle] = useState(note?.title || '')
  const [body, setBody] = useState(note?.body || '')
  const [saveStatus, setSaveStatus] = useState('saved') // 'saved' | 'saving' | 'unsaved'
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
  const bodyRef = useRef(null)
  const titleRef = useRef(null)
  const isNew = !note?.title && !note?.body

  // Auto-focus title if new note
  useEffect(() => {
    if (isNew && titleRef.current) {
      titleRef.current.focus()
    }
  }, [])

  function handleChange(newTitle, newBody) {
    setTitle(newTitle)
    setBody(newBody)
    setSaveStatus('saving')
    onSave(note.id, { title: newTitle, body: newBody }).then(() => {
      setSaveStatus('saved')
    })
  }

  function handleBack() {
    onBack()
  }

  function insertChecklist() {
    if (!bodyRef.current) return
    const ta = bodyRef.current
    const start = ta.selectionStart
handleChange function · javascript · L21-L28 (8 LOC)
src/components/NoteEditor.jsx
  function handleChange(newTitle, newBody) {
    setTitle(newTitle)
    setBody(newBody)
    setSaveStatus('saving')
    onSave(note.id, { title: newTitle, body: newBody }).then(() => {
      setSaveStatus('saved')
    })
  }
Repobility (the analyzer behind this table) · https://repobility.com
handleBack function · javascript · L30-L32 (3 LOC)
src/components/NoteEditor.jsx
  function handleBack() {
    onBack()
  }
insertChecklist function · javascript · L34-L53 (20 LOC)
src/components/NoteEditor.jsx
  function insertChecklist() {
    if (!bodyRef.current) return
    const ta = bodyRef.current
    const start = ta.selectionStart
    const before = body.slice(0, start)
    const after = body.slice(ta.selectionEnd)
    // If at start of line or start of text, just insert. Otherwise add newline first.
    const needsNewline = start > 0 && before[before.length - 1] !== '\n'
    const insert = (needsNewline ? '\n' : '') + '- [ ] '
    const newBody = before + insert + after
    setBody(newBody)
    setSaveStatus('saving')
    onSave(note.id, { title, body: newBody }).then(() => setSaveStatus('saved'))
    // Set cursor after the inserted text
    requestAnimationFrame(() => {
      const pos = start + insert.length
      ta.setSelectionRange(pos, pos)
      ta.focus()
    })
  }
handleBodyKeyDown function · javascript · L56-L90 (35 LOC)
src/components/NoteEditor.jsx
  function handleBodyKeyDown(e) {
    if (e.key === 'Enter') {
      const ta = bodyRef.current
      const pos = ta.selectionStart
      const lines = body.slice(0, pos).split('\n')
      const currentLine = lines[lines.length - 1]
      // If current line is a checklist item, auto-continue
      if (/^- \[[ x]\] /.test(currentLine)) {
        e.preventDefault()
        const before = body.slice(0, pos)
        const after = body.slice(ta.selectionEnd)
        // If the current checklist line is empty (just the prefix), remove it instead
        if (/^- \[[ x]\] $/.test(currentLine)) {
          const newBody = body.slice(0, pos - currentLine.length) + after
          setBody(newBody)
          setSaveStatus('saving')
          onSave(note.id, { title, body: newBody }).then(() => setSaveStatus('saved'))
          requestAnimationFrame(() => {
            const newPos = pos - currentLine.length
            ta.setSelectionRange(newPos, newPos)
          })
        } else {
          con
AuthProvider function · javascript · L8-L218 (211 LOC)
src/contexts/AuthContext.jsx
export function AuthProvider({ children }) {
  const [session, setSession] = useState(null)
  const [profile, setProfile] = useState(null)
  const [loading, setLoading] = useState(true)
  const [authReady, setAuthReady] = useState(false)
  const fetchingRef = useRef(false)

  // Safety timeout: never stay loading for more than 8 seconds
  useEffect(() => {
    const timer = setTimeout(() => {
      if (loading) {
        console.warn('[Auth] Safety timeout — forcing loading=false')
        setLoading(false)
      }
    }, 8000)
    return () => clearTimeout(timer)
  }, [loading])

  // Effect 1: Track auth session ONLY. No database calls here.
  useEffect(() => {
    if (!isConfigured) {
      setLoading(false)
      return
    }

    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (event, currentSession) => {
        console.log(`[Auth] event: ${event}`)
        // Ignore SIGNED_OUT from failed token refreshes (network blips)
        // Only clear session if 
handleVisibilityChange function · javascript · L60-L71 (12 LOC)
src/contexts/AuthContext.jsx
    function handleVisibilityChange() {
      if (document.visibilityState === 'visible') {
        supabase.auth.startAutoRefresh()
        // Explicitly refresh session when app comes back to foreground
        supabase.auth.getSession().then(({ data, error }) => {
          if (data?.session) setSession(data.session)
          if (error) console.warn('[Auth] Visibility refresh error:', error.message)
        })
      } else {
        supabase.auth.stopAutoRefresh()
      }
    }
loadProfile function · javascript · L106-L165 (60 LOC)
src/contexts/AuthContext.jsx
    async function loadProfile(attempt = 1) {
      if (fetchingRef.current) return
      fetchingRef.current = true

      console.log(`[Auth] Fetching profile for: ${userId} (attempt ${attempt})`)
      try {
        const { data, error } = await supabase
          .from('users')
          .select('*')
          .eq('id', userId)
          .single()

        if (error && error.code === 'PGRST116') {
          console.log('[Auth] No profile found, upserting...')
          // Use upsert to handle race condition with handle_new_user trigger
          const { data: newProfile, error: upsertErr } = await supabase
            .from('users')
            .upsert({ id: userId, email: userEmail }, { onConflict: 'id' })
            .select()
            .single()

          if (upsertErr) {
            console.error('[Auth] Failed to upsert profile:', upsertErr.message)
            // Retry: the trigger might have created it, try fetching again
            if (attempt < 3) {
              fetch
updateDisplayName function · javascript · L170-L187 (18 LOC)
src/contexts/AuthContext.jsx
  async function updateDisplayName(displayName) {
    if (!session?.user) throw new Error('No session')

    const { data, error } = await supabase
      .from('users')
      .update({ display_name: displayName })
      .eq('id', session.user.id)
      .select()
      .single()

    if (error) {
      console.error('[Auth] updateDisplayName error:', error)
      throw error
    }
    // Update local profile state to instantly reflect the change
    setProfile(data)
    return data
  }
signInWithGoogle function · javascript · L189-L197 (9 LOC)
src/contexts/AuthContext.jsx
  async function signInWithGoogle() {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: window.location.origin,
      },
    })
    if (error) throw error
  }
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
signOut function · javascript · L199-L203 (5 LOC)
src/contexts/AuthContext.jsx
  async function signOut() {
    await supabase.auth.signOut()
    // The onAuthStateChange listener will handle clearing session/profile
    window.location.reload() // Force a full reload to clear all state
  }
useHandoffs function · javascript · L4-L125 (122 LOC)
src/hooks/useHandoffs.js
export default function useHandoffs() {
  const [handoffs, setHandoffs] = useState([])

  const fetchHandoffs = useCallback(async () => {
    // Try with joins first, fall back to plain select
    const { data, error } = await supabase
      .from('shift_handoffs')
      .select(
        '*, from_user:users!shift_handoffs_from_user_id_fkey(display_name), to_user:users!shift_handoffs_to_user_id_fkey(display_name)'
      )

    if (error) {
      console.warn('[Handoffs] Join query failed, trying fallback:', error.message)
      const { data: fallback } = await supabase
        .from('shift_handoffs')
        .select('*')
      setHandoffs(fallback || [])
    } else {
      setHandoffs(data || [])
    }
  }, [])

  useEffect(() => {
    fetchHandoffs()

    const channel = supabase
      .channel('handoffs-realtime')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'shift_handoffs' },
        () => fetchHandoffs()
      )
      .subscribe()

    retur
getHandoff function · javascript · L42-L49 (8 LOC)
src/hooks/useHandoffs.js
  function getHandoff(dateStr, shift, userId) {
    return handoffs.find(
      (h) =>
        h.handoff_date === dateStr &&
        h.shift === shift &&
        h.from_user_id === userId
    )
  }
getShiftHandoffs function · javascript · L52-L56 (5 LOC)
src/hooks/useHandoffs.js
  function getShiftHandoffs(dateStr, shift) {
    return handoffs.filter(
      (h) => h.handoff_date === dateStr && h.shift === shift
    )
  }
createHandoff function · javascript · L59-L82 (24 LOC)
src/hooks/useHandoffs.js
  async function createHandoff(dateStr, shift, fromUserId, toUserId) {
    const existing = handoffs.find(
      (h) =>
        h.handoff_date === dateStr &&
        h.shift === shift &&
        h.from_user_id === fromUserId &&
        h.status === 'pending'
    )
    if (existing) return { error: 'A hand-off is already pending' }

    const { data: insertData, error } = await supabase.from('shift_handoffs').insert({
      handoff_date: dateStr,
      shift,
      from_user_id: fromUserId,
      to_user_id: toUserId,
    }).select()

    if (error) {
      console.error('[Handoffs] Error creating handoff:', error)
      return { error: error.message }
    }
    await fetchHandoffs()
    return { error: null }
  }
acceptHandoff function · javascript · L85-L122 (38 LOC)
src/hooks/useHandoffs.js
  async function acceptHandoff(handoffId) {
    // Fetch the handoff details so we know which shift/date/user to reassign
    const handoff = handoffs.find((h) => h.id === handoffId)
    if (!handoff) {
      console.error('[Handoffs] Handoff not found:', handoffId)
      return { error: 'Handoff not found' }
    }

    const { error } = await supabase
      .from('shift_handoffs')
      .update({
        status: 'accepted',
        resolved_at: new Date().toISOString(),
      })
      .eq('id', handoffId)
      .select()

    if (error) {
      console.error('[Handoffs] Error accepting:', error)
      return { error: error.message }
    }

    // Reassign ALL tasks for this shift+date from the original user to the accepting user
    const { error: reassignError } = await supabase
      .from('tasks')
      .update({ assigned_to: handoff.to_user_id })
      .eq('task_date', handoff.handoff_date)
      .eq('shift', handoff.shift)
      .eq('assigned_to', handoff.from_user_id)

    if (r
useScheduleNotes function · javascript · L4-L96 (93 LOC)
src/hooks/useScheduleNotes.js
export default function useScheduleNotes(profileId) {
  const [notes, setNotes] = useState([])
  const [loading, setLoading] = useState(true)
  const saveTimerRef = useRef(null)
  const pendingSaveRef = useRef(null)

  const fetchNotes = useCallback(async () => {
    const { data, error } = await supabase
      .from('schedule_notes')
      .select('*, creator:created_by(display_name), updater:updated_by(display_name)')
      .order('pinned', { ascending: false })
      .order('updated_at', { ascending: false })
    if (error) {
      console.error('Error fetching notes:', error)
    } else {
      setNotes(data || [])
    }
    setLoading(false)
  }, [])

  useEffect(() => {
    fetchNotes()

    const channel = supabase
      .channel('schedule-notes-realtime')
      .on('postgres_changes', { event: '*', schema: 'public', table: 'schedule_notes' }, () =>
        fetchNotes()
      )
      .subscribe()

    return () => supabase.removeChannel(channel)
  }, [fetchNotes])

  async funct
createNote function · javascript · L37-L48 (12 LOC)
src/hooks/useScheduleNotes.js
  async function createNote() {
    const { data, error } = await supabase
      .from('schedule_notes')
      .insert({ title: '', body: '', created_by: profileId, updated_by: profileId })
      .select()
      .single()
    if (error) {
      console.error('Error creating note:', error)
      return null
    }
    return data
  }
Open data scored by Repobility · https://repobility.com
saveNote function · javascript · L50-L66 (17 LOC)
src/hooks/useScheduleNotes.js
  function saveNote(id, fields) {
    // Cancel any pending save
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current)
    pendingSaveRef.current = { id, fields }

    return new Promise((resolve) => {
      saveTimerRef.current = setTimeout(async () => {
        const { error } = await supabase
          .from('schedule_notes')
          .update({ ...fields, updated_by: profileId, updated_at: new Date().toISOString() })
          .eq('id', id)
        pendingSaveRef.current = null
        if (error) console.error('Error saving note:', error)
        resolve(!error)
      }, 500)
    })
  }
flushSave function · javascript · L68-L79 (12 LOC)
src/hooks/useScheduleNotes.js
  async function flushSave() {
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current)
    if (pendingSaveRef.current) {
      const { id, fields } = pendingSaveRef.current
      pendingSaveRef.current = null
      const { error } = await supabase
        .from('schedule_notes')
        .update({ ...fields, updated_by: profileId, updated_at: new Date().toISOString() })
        .eq('id', id)
      if (error) console.error('Error flushing save:', error)
    }
  }
deleteNote function · javascript · L81-L85 (5 LOC)
src/hooks/useScheduleNotes.js
  async function deleteNote(id) {
    const { error } = await supabase.from('schedule_notes').delete().eq('id', id)
    if (error) console.error('Error deleting note:', error)
    return !error
  }
togglePin function · javascript · L87-L93 (7 LOC)
src/hooks/useScheduleNotes.js
  async function togglePin(id, currentPinned) {
    const { error } = await supabase
      .from('schedule_notes')
      .update({ pinned: !currentPinned })
      .eq('id', id)
    if (error) console.error('Error toggling pin:', error)
  }
urlBase64ToUint8Array function · javascript · L5-L14 (10 LOC)
src/lib/pushSubscription.js
function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
  const rawData = atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; i++) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}
subscribeToPush function · javascript · L17-L78 (62 LOC)
src/lib/pushSubscription.js
export async function subscribeToPush(userId) {
  if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
    console.log('[Push] Not supported in this browser')
    return null
  }

  try {
    const registration = await navigator.serviceWorker.ready

    // Check for existing subscription
    let subscription = await registration.pushManager.getSubscription()

    // Unsubscribe old subscription if it exists (VAPID key may have changed)
    if (subscription) {
      try {
        await subscription.unsubscribe()
        console.log('[Push] Cleared old subscription')
      } catch (e) {
        console.warn('[Push] Failed to unsubscribe old:', e)
      }
      subscription = null
    }

    if (!subscription) {
      // Request permission and subscribe
      const permission = await Notification.requestPermission()
      if (permission !== 'granted') {
        console.log('[Push] Permission denied')
        return null
      }

      subscription = await registration.push
sendPushAlert function · javascript · L81-L116 (36 LOC)
src/lib/pushSubscription.js
export async function sendPushAlert(title, message) {
  // Fetch all push subscriptions from Supabase
  const { data: subs, error } = await supabase
    .from('push_subscriptions')
    .select('endpoint, p256dh, auth')

  if (error || !subs || subs.length === 0) {
    console.warn('[Push] No subscriptions found:', error)
    return { sent: 0 }
  }

  // Format subscriptions for web-push
  const subscriptions = subs.map((s) => ({
    endpoint: s.endpoint,
    keys: { p256dh: s.p256dh, auth: s.auth },
  }))

  // Call Netlify Function
  const response = await fetch('/.netlify/functions/send-push', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, message, subscriptions }),
  })

  const result = await response.json()
  console.log('[Push] Send result:', result)

  // Clean up expired subscriptions
  if (result.expired && result.expired.length > 0) {
    for (const endpoint of result.expired) {
      await supabase.from('push_subs
ErrorBoundary class · javascript · L7-L40 (34 LOC)
src/main.jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }
  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }
  componentDidCatch(error, info) {
    console.error('[ErrorBoundary]', error, info)
  }
  render() {
    if (this.state.hasError) {
      return (
        <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 16, padding: 24, textAlign: 'center', background: '#0a0a0a' }}>
          <img src="/logo.png" alt="Imperial Sporthorses" style={{ height: 80, width: 80, objectFit: 'contain', opacity: 0.8 }} />
          <div style={{ color: '#fbbf24', fontSize: 16, fontWeight: 'bold' }}>Something went wrong</div>
          <div style={{ color: '#a3a3a3', fontSize: 12, maxWidth: 280 }}>{this.state.error?.message || 'Unknown error'}</div>
          <button
            onClick={() => {
              try 
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
constructor method · javascript · L8-L11 (4 LOC)
src/main.jsx
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }
getDerivedStateFromError method · javascript · L12-L14 (3 LOC)
src/main.jsx
  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }
componentDidCatch method · javascript · L15-L17 (3 LOC)
src/main.jsx
  componentDidCatch(error, info) {
    console.error('[ErrorBoundary]', error, info)
  }
render method · javascript · L18-L39 (22 LOC)
src/main.jsx
  render() {
    if (this.state.hasError) {
      return (
        <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 16, padding: 24, textAlign: 'center', background: '#0a0a0a' }}>
          <img src="/logo.png" alt="Imperial Sporthorses" style={{ height: 80, width: 80, objectFit: 'contain', opacity: 0.8 }} />
          <div style={{ color: '#fbbf24', fontSize: 16, fontWeight: 'bold' }}>Something went wrong</div>
          <div style={{ color: '#a3a3a3', fontSize: 12, maxWidth: 280 }}>{this.state.error?.message || 'Unknown error'}</div>
          <button
            onClick={() => {
              try { localStorage.clear() } catch(e) {}
              try { sessionStorage.clear() } catch(e) {}
              window.location.href = window.location.origin
            }}
            style={{ background: '#f59e0b', color: '#000', border: 'none', padding: '10px 24px', borderRadius: 8, fontWeight: 'bold', fontSize: 
toggleUserRole function · javascript · L112-L116 (5 LOC)
src/pages/AdminSettings.jsx
  async function toggleUserRole(user) {
    const newRole = user.role === 'Admin' ? 'Staff' : 'Admin'
    await supabase.from('users').update({ role: newRole }).eq('id', user.id)
    fetchAll()
  }
addLocation function · javascript · L119-L128 (10 LOC)
src/pages/AdminSettings.jsx
  async function addLocation(e) {
    e.preventDefault()
    if (!newLocation.name.trim()) return
    await supabase.from('locations').insert({
      name: newLocation.name.trim(),
      type: newLocation.type,
    })
    setNewLocation({ name: '', type: 'Stall' })
    fetchAll()
  }
deleteLocation function · javascript · L130-L134 (5 LOC)
src/pages/AdminSettings.jsx
  async function deleteLocation(id) {
    if (!confirm('Delete this location?')) return
    await supabase.from('locations').delete().eq('id', id)
    fetchAll()
  }
syncMapLocations function · javascript · L141-L156 (16 LOC)
src/pages/AdminSettings.jsx
  async function syncMapLocations() {
    const needed = [
      ...MAP_STALLS.map((name) => ({ name, type: 'Stall' })),
      ...MAP_PASTURES.map((name) => ({ name, type: 'Pasture' })),
    ]
    const existing = locations.map((l) => l.name)
    const toCreate = needed.filter((n) => !existing.includes(n.name))
    if (toCreate.length === 0) return
    const { error } = await supabase.from('locations').insert(toCreate)
    if (error) {
      console.error('Sync error:', error)
      alert('Failed to sync: ' + error.message)
    } else {
      fetchAll()
    }
  }
Repobility (the analyzer behind this table) · https://repobility.com
addHorse function · javascript · L159-L179 (21 LOC)
src/pages/AdminSettings.jsx
  async function addHorse(e) {
    e.preventDefault()
    if (!newHorse.name.trim()) return
    await supabase.from('horses').insert({
      name: newHorse.name.trim(),
      owner_info: newHorse.owner_info || null,
      home_stall: newHorse.home_stall || null,
      assigned_pasture: newHorse.assigned_pasture || null,
      current_location: newHorse.home_stall || null,
      am_grain: newHorse.am_grain || null,
      pm_grain: newHorse.pm_grain || null,
      hay_type: newHorse.hay_type || null,
      supplements: newHorse.supplements || null,
      meds_notes: newHorse.meds_notes || null,
    })
    setNewHorse({
      name: '', owner_info: '', home_stall: '', assigned_pasture: '',
      am_grain: '', pm_grain: '', hay_type: '', supplements: '', meds_notes: '',
    })
    fetchAll()
  }
deleteHorse function · javascript · L181-L185 (5 LOC)
src/pages/AdminSettings.jsx
  async function deleteHorse(id) {
    if (!confirm('Delete this horse?')) return
    await supabase.from('horses').delete().eq('id', id)
    fetchAll()
  }
addTemplate function · javascript · L188-L198 (11 LOC)
src/pages/AdminSettings.jsx
  async function addTemplate(e) {
    e.preventDefault()
    if (!newTemplate.title.trim()) return
    await supabase.from('task_templates').insert({
      title: newTemplate.title.trim(),
      shift: newTemplate.shift,
      sort_order: newTemplate.sort_order,
    })
    setNewTemplate({ title: '', shift: 'AM', sort_order: 0 })
    fetchAll()
  }
deleteTemplate function · javascript · L200-L203 (4 LOC)
src/pages/AdminSettings.jsx
  async function deleteTemplate(id) {
    await supabase.from('task_templates').delete().eq('id', id)
    fetchAll()
  }
addScheduleEntry function · javascript · L206-L216 (11 LOC)
src/pages/AdminSettings.jsx
  async function addScheduleEntry(e) {
    e.preventDefault()
    if (!newScheduleEntry.user_id) return
    await supabase.from('weekly_schedule').insert({
      user_id: newScheduleEntry.user_id,
      day_of_week: parseInt(newScheduleEntry.day_of_week),
      shift: newScheduleEntry.shift,
    })
    setNewScheduleEntry({ user_id: '', day_of_week: 0, shift: 'AM' })
    fetchAll()
  }
deleteScheduleEntry function · javascript · L218-L221 (4 LOC)
src/pages/AdminSettings.jsx
  async function deleteScheduleEntry(id) {
    await supabase.from('weekly_schedule').delete().eq('id', id)
    fetchAll()
  }
addBoardTask function · javascript · L224-L243 (20 LOC)
src/pages/AdminSettings.jsx
  async function addBoardTask(e) {
    e.preventDefault()
    if (!newBoardTask.title.trim()) return
    const { format } = await import('date-fns')
    const { error } = await supabase.from('tasks').insert({
      title: newBoardTask.title.trim(),
      shift: null,
      status: 'Pending',
      task_date: format(new Date(), 'yyyy-MM-dd'),
      assigned_to: newBoardTask.assigned_to || null,
      sort_order: 0,
    })
    if (error) {
      console.error('[Admin] Failed to create board task:', error)
      alert('Failed to create task: ' + error.message)
      return
    }
    setNewBoardTask({ title: '', assigned_to: '' })
    fetchAll()
  }
deleteBoardTask function · javascript · L245-L249 (5 LOC)
src/pages/AdminSettings.jsx
  async function deleteBoardTask(id) {
    if (!confirm('Delete this board task?')) return
    await supabase.from('tasks').delete().eq('id', id)
    fetchAll()
  }
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
Chat function · javascript · L8-L261 (254 LOC)
src/pages/Chat.jsx
export default function Chat() {
  const { profile, isAdmin } = useAuth()
  const [messages, setMessages] = useState([])
  const [newMessage, setNewMessage] = useState('')
  const [loading, setLoading] = useState(true)
  const [sending, setSending] = useState(false)
  const messagesEndRef = useRef(null)
  const inputRef = useRef(null)

  const fetchMessages = useCallback(async () => {
    try {
      const { data, error } = await supabase
        .from('messages')
        .select('*, sender:users!messages_user_id_fkey(display_name)')
        .order('created_at', { ascending: true })
        .limit(200)

      if (error) {
        console.error('Error fetching messages:', error)
        const { data: fallback } = await supabase
          .from('messages')
          .select('*')
          .order('created_at', { ascending: true })
          .limit(200)
        setMessages(fallback || [])
      } else {
        setMessages(data || [])
      }
    } catch (err) {
      console.error('fetchM
sendNotification function · javascript · L52-L66 (15 LOC)
src/pages/Chat.jsx
  function sendNotification(title, body, tag) {
    if ('Notification' in window && Notification.permission === 'granted') {
      if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
        navigator.serviceWorker.controller.postMessage({
          type: 'SHOW_NOTIFICATION',
          title,
          body,
          tag,
        })
      } else {
        // Fallback to basic Notification API
        new Notification(title, { body, icon: '/notif-icon.png', tag })
      }
    }
  }
scrollToBottom function · javascript · L97-L99 (3 LOC)
src/pages/Chat.jsx
  function scrollToBottom() {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  }
page 1 / 3next ›