diff --git a/src/CLAUDE.md b/src/CLAUDE.md
index f14ba685..5cfcac39 100644
--- a/src/CLAUDE.md
+++ b/src/CLAUDE.md
@@ -69,6 +69,7 @@ export const FlowSessionRepo = { createFlowSession, /* other methods */ }
- Components → React Query Hooks → API Services → Repository Layer
- Never skip layers or access repositories directly from hooks
- Never call Tauri commands directly from hooks
+- **CRITICAL: Never call API services directly from components** - always use React Query hooks
### State Management Priority
@@ -86,8 +87,16 @@ const { data: flowSessions } = useGetFlowSessions()
// ✅ Good: UI state with Zustand
const { isMenuOpen, toggleMenu } = useUIStore()
+// ✅ Good: Mutations with React Query
+const updateMutation = useUpdatePreferences()
+updateMutation.mutate(data)
+
// ❌ Bad: Database state with Zustand
const { flowSessions, fetchFlowSessions } = useFlowSessionStore() // Don't do this
+
+// ❌ Bad: Direct API calls from components
+import { slackApi } from '@/api/ebbApi/slackApi'
+await slackApi.updatePreferences(data) // Don't do this - use React Query hook instead
```
## Tauri Command Patterns
@@ -238,13 +247,25 @@ export default function MyPage() {
### Data Fetching in Components
-Always use React Query hooks:
+Always use React Query hooks - never call API services directly:
```typescript
-import { useGetFlowSessions } from '../api/hooks/useFlowSession'
+import { useGetFlowSessions, useUpdateFlowSession } from '../api/hooks/useFlowSession'
export default function FlowSessionList() {
const { data: sessions, isLoading, error } = useGetFlowSessions()
+ const updateMutation = useUpdateFlowSession()
+
+ const handleUpdate = (id: string, data: UpdateData) => {
+ updateMutation.mutate({ id, data }, {
+ onSuccess: () => {
+ toast.success('Updated successfully')
+ },
+ onError: (error) => {
+ logAndToastError('Update failed', error)
+ }
+ })
+ }
if (isLoading) return
Loading...
if (error) return Error loading sessions
@@ -252,13 +273,31 @@ export default function FlowSessionList() {
return (
{sessions?.map(session => (
-
{session.objective}
+
+ {session.objective}
+
+
))}
)
}
```
+**❌ Never do this in components:**
+```typescript
+// DON'T import and call API services directly
+import { FlowSessionApi } from '../api/ebbApi/flowSessionApi'
+
+const handleUpdate = async () => {
+ await FlowSessionApi.updateFlowSession(id, data) // Wrong - violates layer pattern
+}
+```
+
## File Organization
- `src/api/hooks/`: React Query hooks (Layer 1)
diff --git a/src/api/hooks/useSlack.ts b/src/api/hooks/useSlack.ts
index 8f7074da..f1874c05 100644
--- a/src/api/hooks/useSlack.ts
+++ b/src/api/hooks/useSlack.ts
@@ -1,5 +1,5 @@
-import { useQuery } from '@tanstack/react-query'
-import { slackApi } from '../ebbApi/slackApi'
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
+import { slackApi, SlackPreferences } from '../ebbApi/slackApi'
const slackKeys = {
all: ['slack'] as const,
@@ -16,3 +16,15 @@ export const useSlackStatus = () => {
retry: false,
})
}
+
+export const useUpdateSlackPreferences = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (preferences: SlackPreferences) => slackApi.updatePreferences(preferences),
+ onSuccess: () => {
+ // Invalidate and refetch slack status to get updated preferences
+ queryClient.invalidateQueries({ queryKey: slackKeys.status() })
+ },
+ })
+}
diff --git a/src/components/SlackFocusToggle.tsx b/src/components/SlackFocusToggle.tsx
index 37b26cef..377124b5 100644
--- a/src/components/SlackFocusToggle.tsx
+++ b/src/components/SlackFocusToggle.tsx
@@ -1,7 +1,8 @@
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import { SlackIcon } from '@/components/icons/SlackIcon'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
import {
Dialog,
DialogContent,
@@ -9,7 +10,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
-import { useSlackStatus } from '@/api/hooks/useSlack'
+import { useSlackStatus, useUpdateSlackPreferences } from '@/api/hooks/useSlack'
import { logAndToastError } from '@/lib/utils/ebbError.util'
import { initiateSlackOAuth } from '@/lib/utils/slackAuth.util'
import { Skeleton } from '@/components/ui/skeleton'
@@ -29,7 +30,18 @@ export function SlackFocusToggle({ slackSettings, onSlackSettingsChange }: Slack
const navigate = useNavigate()
const [showDialog, setShowDialog] = useState(false)
+ const [customStatusText, setCustomStatusText] = useState('')
+ const [customStatusEmoji, setCustomStatusEmoji] = useState('')
const { data: slackStatus, isLoading: slackStatusLoading } = useSlackStatus()
+ const updatePreferencesMutation = useUpdateSlackPreferences()
+
+ // Load current preferences when dialog opens
+ useEffect(() => {
+ if (showDialog && slackStatus?.preferences) {
+ setCustomStatusText(slackStatus.preferences.custom_status_text || '')
+ setCustomStatusEmoji(slackStatus.preferences.custom_status_emoji || '')
+ }
+ }, [showDialog, slackStatus?.preferences])
const handleSlackToggle = async () => {
if (!user) {
@@ -60,6 +72,31 @@ export function SlackFocusToggle({ slackSettings, onSlackSettingsChange }: Slack
}
}
+ const validateEmoji = (emoji: string): boolean => {
+ if (!emoji) return true // Empty is valid
+ return emoji.startsWith(':') && emoji.endsWith(':') && emoji.length > 2
+ }
+
+ const handleSavePreferences = async () => {
+ if (!validateEmoji(customStatusEmoji)) {
+ toast.error('Emoji must be in format :emoji_name: (e.g., :brain:)')
+ return
+ }
+
+ updatePreferencesMutation.mutate({
+ custom_status_text: customStatusText,
+ custom_status_emoji: customStatusEmoji
+ }, {
+ onSuccess: () => {
+ toast.success('Slack preferences updated')
+ setShowDialog(false)
+ },
+ onError: (error) => {
+ logAndToastError('Failed to update Slack preferences', error)
+ }
+ })
+ }
+
const navigateToSettings = () => {
setShowDialog(false)
// Navigate to settings page - you may need to adjust this based on your routing
@@ -128,6 +165,45 @@ export function SlackFocusToggle({ slackSettings, onSlackSettingsChange }: Slack
/>
+
+
+
+
setCustomStatusText(e.target.value)}
+ placeholder="e.g., In a focus session"
+ maxLength={100}
+ />
+
+ Status message shown during focus sessions
+
+
+
+
+
+
setCustomStatusEmoji(e.target.value)}
+ placeholder="e.g., :brain:"
+ maxLength={50}
+ className={!validateEmoji(customStatusEmoji) ? 'border-red-500' : ''}
+ />
+
+ Emoji in format :emoji_name: (e.g., :brain:, :rocket:)
+
+
+
+
+
+