Sign in required
Use Google sign-in to manage your team members and access the authenticated workspace.
Find Time
Share this scheduler link with your team so everyone can open the same meeting group and planning view.
// DEPLOY_CONFIG: {"triggers": [{"name": "meeting_time_confirmed_email_participants", "on": "collection.add", "collection": "meeting_times", "actions": [{"type": "email", "to": "{{participants_emails}}", "subject": "Meeting time confirmed", "body": "The meeting time has been confirmed for {{confirmed_time_localized}}. Each recipient should see this time in their own local time zone."}]}]} import { useEffect, useMemo, useState } from 'react'; import { Timeline } from './components/Timeline.jsx'; import { MemberManager } from './components/MemberManager.jsx'; import { OverlapFinder } from './components/OverlapFinder.jsx'; import { useAI, useAuth, useCollection, usePresence } from '@deplixo/sdk'; function App() { const [activeTab, setActiveTab] = useState('timeline'); const [selectedGroupId, setSelectedGroupId] = useState(''); const [shareFeedback, setShareFeedback] = useState(''); const { items: members, loading: membersLoading, add, update, remove } = useCollection('members', { personal: true }); const { items: meetingGroups, loading: groupsLoading, add: addGroup, update: updateGroup, remove: removeGroup } = useCollection('meeting_groups', { personal: true }); const { items: meetings, loading: meetingsLoading, add: addMeeting, update: updateMeeting, remove: removeMeeting } = useCollection('meetings', { personal: true }); const { user, loading: authLoading, login, logout } = useAuth(); const { users: onlineUsers, update: updatePresence } = usePresence({ status: 'online' }); const { generate, loading: aiLoading, error: aiError } = useAI(); const [meetingSuggestion, setMeetingSuggestion] = useState(null); const [suggestionFeedback, setSuggestionFeedback] = useState(''); const [suggestionLoading, setSuggestionLoading] = useState(false); const tabs = [ { id: 'timeline', label: '⏱ Timeline', icon: '⏱' }, { id: 'members', label: '👥 Members', icon: '👥' }, { id: 'overlap', label: '✦ Find Time', icon: '✦' }, { id: 'calendar', label: '🗓 Calendar', icon: '🗓' }, ]; const getSchedulerShareLink = (groupId = '') => { const url = new URL(window.location.href); url.searchParams.set('view', 'overlap'); if (groupId) { url.searchParams.set('group', groupId); } else { url.searchParams.delete('group'); } return url.toString(); }; const syncStateFromUrl = () => { if (typeof window === 'undefined') return; const params = new URLSearchParams(window.location.search); const view = params.get('view'); const group = params.get('group') || ''; if (view && ['timeline', 'members', 'overlap', 'calendar'].includes(view)) { setActiveTab(view); } setSelectedGroupId(group); }; const handleAuthAction = async () => { if (user) { await logout(); return; } await login('google'); }; const isAuthenticated = !!user; const isLoading = authLoading || membersLoading || groupsLoading || meetingsLoading; const selectedGroup = useMemo(() => { return meetingGroups?.find(group => group.id === selectedGroupId) || null; }, [meetingGroups, selectedGroupId]); const selectedGroupShareLink = useMemo(() => getSchedulerShareLink(selectedGroupId), [selectedGroupId]); const onlineMemberIds = useMemo(() => { return new Set((onlineUsers || []).map(u => u.id)); }, [onlineUsers]); const onlineMembers = useMemo(() => { return (members || []).filter(member => { if (member?.id && onlineMemberIds.has(member.id)) return true; if (member?.userId && onlineMemberIds.has(member.userId)) return true; return false; }); }, [members, onlineMemberIds]); useEffect(() => { if (!isAuthenticated || !user) return; updatePresence({ id: user.id, name: user.name || 'Signed in user', avatar: user.avatar || '', status: 'online', activeTab, groupId: selectedGroupId, updatedAt: new Date().toISOString(), }); }, [isAuthenticated, user, activeTab, selectedGroupId, updatePresence]); useEffect(() => { if (typeof window === 'undefined') return undefined; const markActive = () => { if (!user) return; updatePresence({ id: user.id, name: user.name || 'Signed in user', avatar: user.avatar || '', status: 'online', activeTab, groupId: selectedGroupId, updatedAt: new Date().toISOString(), }); }; const events = ['focus', 'visibilitychange', 'mousemove', 'keydown', 'click', 'touchstart']; events.forEach(eventName => window.addEventListener(eventName, markActive, { passive: true })); const intervalId = window.setInterval(markActive, 30000); return () => { events.forEach(eventName => window.removeEventListener(eventName, markActive)); window.clearInterval(intervalId); }; }, [user, activeTab, selectedGroupId, updatePresence]); const handleSelectGroup = (groupId) => { setSelectedGroupId(groupId); const url = new URL(window.location.href); url.searchParams.set('view', 'overlap'); if (groupId) { url.searchParams.set('group', groupId); } else { url.searchParams.delete('group'); } window.history.replaceState({}, '', url.toString()); }; const handleCopyShareLink = async () => { const shareLink = selectedGroupId ? selectedGroupShareLink : getSchedulerShareLink(''); try { await navigator.clipboard.writeText(shareLink); setShareFeedback('Scheduler link copied to clipboard'); } catch { setShareFeedback('Copy failed — share this link manually'); } window.clearTimeout(handleCopyShareLink._timer); handleCopyShareLink._timer = window.setTimeout(() => setShareFeedback(''), 2500); }; const handleSaveGroup = async (groupData) => { const payload = { ...groupData, memberIds: Array.isArray(groupData.memberIds) ? groupData.memberIds : [], minDuration: Number(groupData.minDuration) || 1, displayTimezone: groupData.displayTimezone || 'UTC', updatedAt: new Date().toISOString(), }; if (groupData.id) { await updateGroup(groupData.id, payload); return groupData.id; } const created = await addGroup({ ...payload, createdAt: new Date().toISOString(), }); return created?.id || ''; }; const handleDeleteGroup = async (groupId) => { await removeGroup(groupId); if (selectedGroupId === groupId) { setSelectedGroupId(''); const url = new URL(window.location.href); url.searchParams.delete('group'); window.history.replaceState({}, '', url.toString()); } }; const handleSaveMeeting = async (meetingData) => { const payload = { ...meetingData, memberIds: Array.isArray(meetingData.memberIds) ? meetingData.memberIds : [], timezone: meetingData.timezone || 'UTC', startAt: meetingData.startAt || new Date().toISOString(), endAt: meetingData.endAt || new Date(Date.now() + 60 * 60 * 1000).toISOString(), updatedAt: new Date().toISOString(), }; if (meetingData.id) { await updateMeeting(meetingData.id, payload); return meetingData.id; } const created = await addMeeting({ ...payload, createdAt: new Date().toISOString(), }); return created?.id || ''; }; const handleDeleteMeeting = async (meetingId) => { await removeMeeting(meetingId); }; const buildSuggestedMeetingPrompt = (group, groupMembers, existingMeetings) => { const memberLines = (groupMembers || []).map(member => { const hours = `${member?.workingHoursStart ?? 9}:00-${member?.workingHoursEnd ?? 17}:00`; return `- ${member?.name || 'Unknown'} | timezone: ${member?.timezone || 'UTC'} | working hours: ${hours}`; }).join('\n'); const meetingLines = (existingMeetings || []).slice(0, 40).map(meeting => { return `- ${meeting?.title || 'Untitled'} | start: ${meeting?.startAt || ''} | end: ${meeting?.endAt || ''} | timezone: ${meeting?.timezone || 'UTC'}`; }).join('\n'); return { system: 'You are a scheduling assistant. Propose the best meeting time based on member working hours, existing meetings, and the requested duration. Return JSON only.', user: `Meeting group: ${group?.name || 'Selected group'}\nMinimum duration (minutes): ${Number(group?.minDuration) || 60}\nDisplay timezone: ${group?.displayTimezone || 'UTC'}\n\nMembers:\n${memberLines || '- No members selected'}\n\nExisting meetings:\n${meetingLines || '- None'}\n\nReturn JSON with this exact shape: {"startAt":"ISO string","endAt":"ISO string","timezone":"IANA/UTC timezone string","confidence":"high|medium|low","reason":"short explanation","memberAvailability":"short summary"}. Choose the earliest high-quality slot within the next 14 days if possible. Prefer times that maximize overlap and avoid conflicts.`, }; }; const handleGenerateMeetingSuggestion = async () => { const groupMembers = selectedGroup ? (members || []).filter(member => (selectedGroup.memberIds || []).includes(member.id) || (selectedGroup.memberIds || []).includes(member.userId)) : (members || []); const durationMinutes = Number(selectedGroup?.minDuration) || 60; if (!groupMembers.length) { setMeetingSuggestion(null); setSuggestionFeedback('Select a meeting group with members before asking for an AI suggestion.'); return; } setSuggestionLoading(true); setSuggestionFeedback(''); try { const response = await generate(buildSuggestedMeetingPrompt(selectedGroup, groupMembers, meetings || [])); const parsed = typeof response === 'string' ? JSON.parse(response) : response; if (!parsed?.startAt || !parsed?.endAt) { setMeetingSuggestion(null); setSuggestionFeedback('AI suggestion was incomplete. Try again.'); } else { setMeetingSuggestion({ ...parsed, durationMinutes, groupId: selectedGroupId, }); } } catch { setMeetingSuggestion(null); setSuggestionFeedback('AI suggestion failed. Please try again.'); } finally { setSuggestionLoading(false); } }; const handleApplySuggestion = () => { if (!meetingSuggestion) return; window.dispatchEvent(new CustomEvent('synczone-open-meeting-draft', { detail: meetingSuggestion })); setActiveTab('calendar'); }; if (typeof window !== 'undefined') { const expectedView = new URLSearchParams(window.location.search).get('view'); const expectedGroup = new URLSearchParams(window.location.search).get('group') || ''; if (expectedView || expectedGroup) { queueMicrotask(syncStateFromUrl); } } return (
Distributed Team Scheduler
Use Google sign-in to manage your team members and access the authenticated workspace.
Share this scheduler link with your team so everyone can open the same meeting group and planning view.
Use Google sign-in to access team management and the authenticated workspace.
Time zone-aware meeting planning with local rendering for each member.
Generate an optimal time from existing members, selected group settings, and current calendar conflicts.
Create a meeting to see it appear in this calendar grid and list with time zone-aware rendering.
Generate an optimal time from existing members, selected group settings, and current calendar conflicts.