[READ-ONLY] One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 馃搮 calendar.xyehr.cn
nextjs
0

Configure Feed

Select the types of activity you want to include in your feed.

One-Calendar / components / providers / calendar-context.tsx
4.9 kB 163 lines
1'use client' 2 3import type { Dispatch, SetStateAction } from 'react' 4import type React from 'react' 5import { useEffect, useRef } from 'react' 6import { create } from 'zustand' 7import { 8 readEncryptedLocalStorage, 9 writeEncryptedLocalStorage, 10} from '@/hooks/useLocalStorage' 11 12export interface CalendarCategory { 13 id: string 14 name: string 15 color: string 16 keywords?: string[] 17} 18 19export interface CalendarEvent { 20 id: string 21 title: string 22 startDate: Date 23 endDate: Date 24 isAllDay: boolean 25 recurrence: 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly' 26 location?: string 27 participants: string[] 28 notification: number 29 description?: string 30 color: string 31 calendarId: string 32} 33 34interface CalendarContextType { 35 calendars: CalendarCategory[] 36 setCalendars: Dispatch<SetStateAction<CalendarCategory[]>> 37 events: CalendarEvent[] 38 setEvents: Dispatch<SetStateAction<CalendarEvent[]>> 39 addCategory: (category: CalendarCategory) => void 40 removeCategory: (id: string) => void 41 updateCategory: (id: string, category: Partial<CalendarCategory>) => void 42 moveCategory: (id: string, direction: 'up' | 'down') => void 43 addEvent: (newEvent: CalendarEvent) => void 44} 45 46interface CalendarState { 47 calendars: CalendarCategory[] 48 events: CalendarEvent[] 49 setCalendars: (value: SetStateAction<CalendarCategory[]>) => void 50 setEvents: (value: SetStateAction<CalendarEvent[]>) => void 51 addCategory: (category: CalendarCategory) => void 52 removeCategory: (id: string) => void 53 updateCategory: (id: string, category: Partial<CalendarCategory>) => void 54 moveCategory: (id: string, direction: 'up' | 'down') => void 55 addEvent: (newEvent: CalendarEvent) => void 56} 57 58const useCalendarStore = create<CalendarState>()((set) => ({ 59 calendars: [], 60 events: [], 61 setCalendars: (value) => 62 set((state) => ({ 63 calendars: typeof value === 'function' ? value(state.calendars) : value, 64 })), 65 setEvents: (value) => 66 set((state) => ({ 67 events: typeof value === 'function' ? value(state.events) : value, 68 })), 69 addCategory: (category) => 70 set((state) => ({ calendars: [...state.calendars, category] })), 71 removeCategory: (id) => 72 set((state) => ({ 73 calendars: state.calendars.filter((cal) => cal.id !== id), 74 })), 75 updateCategory: (id, category) => 76 set((state) => ({ 77 calendars: state.calendars.map((cal) => 78 cal.id === id ? { ...cal, ...category } : cal, 79 ), 80 })), 81 moveCategory: (id, direction) => 82 set((state) => { 83 const currentIndex = state.calendars.findIndex((cal) => cal.id === id) 84 if (currentIndex === -1) return { calendars: state.calendars } 85 86 const targetIndex = 87 direction === 'up' ? currentIndex - 1 : currentIndex + 1 88 89 if (targetIndex < 0 || targetIndex >= state.calendars.length) { 90 return { calendars: state.calendars } 91 } 92 93 const nextCalendars = [...state.calendars] 94 const [movedCalendar] = nextCalendars.splice(currentIndex, 1) 95 nextCalendars.splice(targetIndex, 0, movedCalendar) 96 97 return { calendars: nextCalendars } 98 }), 99 addEvent: (newEvent) => 100 set((state) => { 101 const eventExists = state.events.some((event) => event.id === newEvent.id) 102 103 if (eventExists) { 104 return { 105 events: state.events.map((event) => 106 event.id === newEvent.id ? newEvent : event, 107 ), 108 } 109 } 110 111 return { events: [...state.events, newEvent] } 112 }), 113})) 114 115export function CalendarProvider({ children }: { children: React.ReactNode }) { 116 const calendars = useCalendarStore((state) => state.calendars) 117 const events = useCalendarStore((state) => state.events) 118 const setCalendars = useCalendarStore((state) => state.setCalendars) 119 const setEvents = useCalendarStore((state) => state.setEvents) 120 const hydratedRef = useRef(false) 121 122 useEffect(() => { 123 const hydrate = async () => { 124 const storedCalendars = await readEncryptedLocalStorage< 125 CalendarCategory[] 126 >('calendar-categories', []) 127 const storedEvents = await readEncryptedLocalStorage<CalendarEvent[]>( 128 'calendar-events', 129 [], 130 ) 131 132 setCalendars(storedCalendars) 133 setEvents( 134 storedEvents.map((event) => ({ 135 ...event, 136 startDate: new Date(event.startDate), 137 endDate: new Date(event.endDate), 138 })), 139 ) 140 hydratedRef.current = true 141 } 142 143 void hydrate() 144 }, [setCalendars, setEvents]) 145 146 useEffect(() => { 147 if (!hydratedRef.current) return 148 void writeEncryptedLocalStorage('calendar-categories', calendars) 149 }, [calendars]) 150 151 useEffect(() => { 152 if (!hydratedRef.current) return 153 void writeEncryptedLocalStorage('calendar-events', events) 154 }, [events]) 155 156 return children 157} 158 159export function useCalendar(): CalendarContextType { 160 return useCalendarStore() 161} 162 163export const useCalendarContext = useCalendar