diff --git a/CLAUDE.md b/CLAUDE.md
index 7c233f3..4f5c0a8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -76,7 +76,7 @@ src/
│ ├── (tabs)/ # Tab navigation group
│ │ ├── _layout.tsx # Tab bar configuration (themed)
│ │ ├── chat.tsx # Chat screen (AI conversation)
-│ │ ├── calendar.tsx # Calendar overview
+│ │ ├── calendar.tsx # Calendar overview (with CalendarToolbar: sync + logout)
│ │ └── settings.tsx # Settings screen (theme switcher, logout, CalDAV config with feedback)
│ ├── editEvent.tsx # Event edit screen (dual-mode: calendar/chat)
│ ├── event/
@@ -637,6 +637,7 @@ NODE_ENV=development # development = pretty logs, production = JSON
- `EventCard`: Uses EventCardBase + edit/delete buttons (TouchableOpacity with delayPressIn for scroll-friendly touch handling)
- `ProposedEventCard`: Uses EventCardBase + confirm/reject/edit buttons for chat proposals, displays green highlighted text for new changes ("Neue Ausnahme: [date]" for single delete, "Neues Ende: [date]" for UNTIL updates), shows yellow conflict warnings when proposed time overlaps with existing events. Edit button allows modifying proposals before confirming.
- `DeleteEventModal`: Delete confirmation modal using ModalBase - shows three options for recurring events (single/future/all), simple confirm for non-recurring
+- `CalendarToolbar` (in calendar.tsx): Toolbar between header and weekdays with Sync button (CalDAV sync with spinner/green checkmark/red X feedback, disabled without config) and Logout button
- `EventOverlay` (in calendar.tsx): Day events overlay using ModalBase - shows EventCards for selected day
- `Themes.tsx`: Theme definitions with THEMES object (defaultLight, defaultDark) including all color tokens (textPrimary, borderPrimary, eventIndicator, secondaryBg, shadowColor, etc.)
- `EventsStore`: Zustand store with setEvents(), addEvent(), updateEvent(), deleteEvent() - stores ExpandedEvent[], preloaded by AuthGuard
diff --git a/apps/client/src/app/(tabs)/calendar.tsx b/apps/client/src/app/(tabs)/calendar.tsx
index ce0453b..c72ff14 100644
--- a/apps/client/src/app/(tabs)/calendar.tsx
+++ b/apps/client/src/app/(tabs)/calendar.tsx
@@ -1,4 +1,4 @@
-import { Pressable, Text, View } from "react-native";
+import { ActivityIndicator, Pressable, Text, View } from "react-native";
import {
DAYS,
MONTHS,
@@ -16,9 +16,11 @@ import { router, useFocusEffect } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { useThemeStore } from "../../stores/ThemeStore";
import BaseBackground from "../../components/BaseBackground";
-import { EventService } from "../../services";
+import { AuthService, EventService } from "../../services";
import { useEventsStore } from "../../stores";
import { useDropdownPosition } from "../../hooks/useDropdownPosition";
+import { CaldavConfigService } from "../../services/CaldavConfigService";
+import { useCaldavConfigStore } from "../../stores/CaldavConfigStore";
// MonthSelector types and helpers
type MonthItem = {
@@ -239,6 +241,7 @@ const Calendar = () => {
setMonthIndex={setMonthIndex}
setYear={setCurrentYear}
/>
+
{
);
};
+type CalendarToolbarProps = {
+ loadEvents: () => Promise;
+};
+
+const CalendarToolbar = ({ loadEvents }: CalendarToolbarProps) => {
+ const { theme } = useThemeStore();
+ const { config } = useCaldavConfigStore();
+ const [isSyncing, setIsSyncing] = useState(false);
+ const [syncResult, setSyncResult] = useState<"success" | "error" | null>(
+ null,
+ );
+
+ const handleSync = async () => {
+ if (!config || isSyncing) return;
+ setSyncResult(null);
+ setIsSyncing(true);
+ try {
+ await CaldavConfigService.sync();
+ await loadEvents();
+ setSyncResult("success");
+ } catch (error) {
+ console.error("CalDAV sync failed:", error);
+ setSyncResult("error");
+ } finally {
+ setIsSyncing(false);
+ }
+ };
+
+ useEffect(() => {
+ if (!syncResult) return;
+ const timer = setTimeout(() => setSyncResult(null), 3000);
+ return () => clearTimeout(timer);
+ }, [syncResult]);
+
+ const handleLogout = async () => {
+ await AuthService.logout();
+ router.replace("/login");
+ };
+
+ const syncIcon = () => {
+ if (isSyncing) {
+ return ;
+ }
+ if (syncResult === "success") {
+ return (
+
+ );
+ }
+ if (syncResult === "error") {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ };
+
+ const buttonStyle = {
+ backgroundColor: theme.chatBot,
+ borderColor: theme.borderPrimary,
+ borderWidth: 1,
+ shadowColor: theme.shadowColor,
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.25,
+ shadowRadius: 4,
+ elevation: 4,
+ };
+
+ const className = "flex flex-row items-center gap-2 px-3 py-1 rounded-lg"
+
+ return (
+
+
+ {syncIcon()}
+
+ Sync
+
+
+
+
+
+
+ Logout
+
+
+
+ );
+};
+
const WeekDaysLine = () => {
const { theme } = useThemeStore();
return (