11import { Ionicons } from "@expo/vector-icons" ;
2- import { useRouter } from "expo-router" ;
3- import React , { useState , useMemo } from "react" ;
2+ import { Stack , useRouter } from "expo-router" ;
3+ import React , { useState , useMemo , Activity } from "react" ;
44import {
55 View ,
66 Text ,
@@ -10,27 +10,27 @@ import {
1010 ActionSheetIOS ,
1111 Alert ,
1212 Platform ,
13- Modal ,
1413 TextInput ,
15- KeyboardAvoidingView ,
1614 ScrollView ,
1715} from "react-native" ;
1816
19- import { CalComAPIService , Schedule } from "../../services/calcom" ;
20- import { Header } from "../../components/Header" ;
21- import { FullScreenModal } from "../../components/FullScreenModal" ;
22- import { LoadingSpinner } from "../../components/LoadingSpinner" ;
23- import { EmptyScreen } from "../../components/EmptyScreen" ;
24- import { showErrorAlert } from "../../utils/alerts" ;
25- import { offlineAwareRefresh } from "../../utils/network" ;
26- import { shadows } from "../../utils/shadows" ;
17+ import { CalComAPIService , Schedule } from "../../../ services/calcom" ;
18+ import { Header } from "../../../ components/Header" ;
19+ import { FullScreenModal } from "../../../ components/FullScreenModal" ;
20+ import { LoadingSpinner } from "../../../ components/LoadingSpinner" ;
21+ import { EmptyScreen } from "../../../ components/EmptyScreen" ;
22+ import { showErrorAlert } from "../../../ utils/alerts" ;
23+ import { offlineAwareRefresh } from "../../../ utils/network" ;
24+ import { shadows } from "../../../ utils/shadows" ;
2725import {
2826 useSchedules ,
2927 useCreateSchedule ,
3028 useDeleteSchedule ,
3129 useDuplicateSchedule ,
3230 useSetScheduleAsDefault ,
33- } from "../../hooks" ;
31+ } from "../../../hooks" ;
32+ import { isLiquidGlassAvailable } from "expo-glass-effect" ;
33+ import { AvailabilityListItem } from "../../../components/availability-list-item/AvailabilityListItem" ;
3434
3535export default function Availability ( ) {
3636 const router = useRouter ( ) ;
@@ -261,65 +261,6 @@ export default function Availability() {
261261 ) ;
262262 } ;
263263
264- const renderSchedule = ( { item : schedule , index } : { item : Schedule ; index : number } ) => {
265- return (
266- < TouchableOpacity
267- className = "border-b border-[#E5E5EA] bg-white active:bg-[#F8F9FA]"
268- onPress = { ( ) => handleSchedulePress ( schedule ) }
269- onLongPress = { ( ) => handleScheduleLongPress ( schedule ) }
270- style = { { paddingHorizontal : 16 , paddingVertical : 16 } }
271- >
272- < View className = "flex-row items-center justify-between" >
273- < View className = "mr-4 flex-1" >
274- < View className = "mb-1 flex-row flex-wrap items-center" >
275- < Text className = "text-base font-semibold text-[#333]" > { schedule . name } </ Text >
276- { schedule . isDefault ? (
277- < View className = "ml-2 rounded bg-[#666] px-2 py-0.5" >
278- < Text className = "text-xs font-semibold text-white" > Default</ Text >
279- </ View >
280- ) : null }
281- </ View >
282-
283- { schedule . availability && schedule . availability . length > 0 ? (
284- < View >
285- { schedule . availability . map ( ( slot , slotIndex ) => (
286- < View
287- key = { `${ schedule . id } -${ slot . days . join ( "-" ) } -${ slotIndex } ` }
288- className = { slotIndex > 0 ? "mt-2" : "" }
289- >
290- < Text className = "text-sm text-[#666]" >
291- { slot . days . join ( ", " ) } { slot . startTime } - { slot . endTime }
292- </ Text >
293- </ View >
294- ) ) }
295- </ View >
296- ) : (
297- < Text className = "text-sm text-[#666]" > No availability set</ Text >
298- ) }
299-
300- < View className = "mt-2 flex-row items-center" >
301- < Ionicons name = "globe-outline" size = { 14 } color = "#666" />
302- < Text className = "ml-1.5 text-sm text-[#666]" > { schedule . timeZone } </ Text >
303- </ View >
304- </ View >
305-
306- { /* Three dots button - vertically centered on the right */ }
307- < TouchableOpacity
308- className = "items-center justify-center rounded-lg border border-[#E5E5EA]"
309- style = { { width : 32 , height : 32 } }
310- onPress = { ( e ) => {
311- e . stopPropagation ( ) ;
312- setSelectedSchedule ( schedule ) ;
313- setShowActionsModal ( true ) ;
314- } }
315- >
316- < Ionicons name = "ellipsis-horizontal" size = { 18 } color = "#3C3F44" />
317- </ TouchableOpacity >
318- </ View >
319- </ TouchableOpacity >
320- ) ;
321- } ;
322-
323264 if ( loading ) {
324265 return (
325266 < View className = "flex-1 bg-[#f8f9fa]" >
@@ -356,93 +297,126 @@ export default function Availability() {
356297 const showList = ! showEmptyState && ! showSearchEmptyState ;
357298
358299 return (
359- < View className = "flex-1 bg-gray-100" >
360- < Header />
300+ < >
301+ < Stack . Header
302+ style = { { backgroundColor : "transparent" , shadowColor : "transparent" } }
303+ blurEffect = { isLiquidGlassAvailable ( ) ? undefined : "light" } // Only looks cool on iOS 18 and below
304+ hidden = { Platform . OS === "android" }
305+ >
306+ < Stack . Header . Title large > Availability</ Stack . Header . Title >
307+ < Stack . Header . Right >
308+ < Stack . Header . Button onPress = { handleCreateNew } tintColor = "#000" variant = "prominent" >
309+ New
310+ </ Stack . Header . Button >
311+ </ Stack . Header . Right >
312+ < Stack . Header . SearchBar
313+ placeholder = "Search schedules"
314+ onChangeText = { ( e ) => handleSearch ( e . nativeEvent . text ) }
315+ obscureBackground = { false }
316+ barTintColor = "#fff"
317+ />
318+ </ Stack . Header >
319+
320+ < Activity mode = { Platform . OS !== "ios" ? "visible" : "hidden" } >
321+ < Header />
322+ < View className = "flex-row items-center gap-3 border-b border-gray-300 bg-gray-100 px-4 py-2" >
323+ < TextInput
324+ className = "flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-[17px] text-black focus:border-black focus:ring-2 focus:ring-black"
325+ placeholder = "Search schedules"
326+ placeholderTextColor = "#9CA3AF"
327+ value = { searchQuery }
328+ onChangeText = { handleSearch }
329+ autoCapitalize = "none"
330+ autoCorrect = { false }
331+ clearButtonMode = "while-editing"
332+ />
333+ < TouchableOpacity
334+ className = "min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
335+ onPress = { handleCreateNew }
336+ >
337+ < Ionicons name = "add" size = { 18 } color = "#fff" />
338+ < Text className = "text-base font-semibold text-white" > New</ Text >
339+ </ TouchableOpacity >
340+ </ View >
341+ </ Activity >
361342
362343 { /* Empty state - no schedules */ }
363- { showEmptyState ? (
364- < View className = "flex-1 bg-gray-50" style = { { paddingBottom : 100 } } >
344+ < Activity mode = { showEmptyState ? "visible" : "hidden" } >
345+ < ScrollView
346+ style = { { backgroundColor : "white" } }
347+ contentContainerStyle = { {
348+ flexGrow : 1 ,
349+ justifyContent : "center" ,
350+ alignItems : "center" ,
351+ padding : 20 ,
352+ paddingBottom : 90 ,
353+ } }
354+ refreshControl = { < RefreshControl refreshing = { refreshing } onRefresh = { onRefresh } /> }
355+ contentInsetAdjustmentBehavior = "automatic"
356+ >
357+ < EmptyScreen
358+ icon = "time-outline"
359+ headline = "Create an availability schedule"
360+ description = "Creating availability schedules allows you to manage availability across event types. They can be applied to one or more event types."
361+ buttonText = "New"
362+ onButtonPress = { handleCreateNew }
363+ />
364+ </ ScrollView >
365+ </ Activity >
366+
367+ { /* Search bar and content for non-empty states */ }
368+ < Activity mode = { ! showEmptyState ? "visible" : "hidden" } >
369+ { /* Search empty state */ }
370+ < Activity mode = { showSearchEmptyState ? "visible" : "hidden" } >
365371 < ScrollView
372+ style = { { backgroundColor : "white" } }
366373 contentContainerStyle = { {
367374 flexGrow : 1 ,
368375 justifyContent : "center" ,
369376 alignItems : "center" ,
370377 padding : 20 ,
378+ paddingBottom : 90 ,
371379 } }
372380 refreshControl = { < RefreshControl refreshing = { refreshing } onRefresh = { onRefresh } /> }
373381 >
374382 < EmptyScreen
375- icon = "time-outline"
376- headline = "Create an availability schedule"
377- description = "Creating availability schedules allows you to manage availability across event types. They can be applied to one or more event types."
378- buttonText = "New"
379- onButtonPress = { handleCreateNew }
383+ icon = "search-outline"
384+ headline = { `No results found for "${ searchQuery } "` }
385+ description = "Try searching with different keywords"
380386 />
381387 </ ScrollView >
382- </ View >
383- ) : null }
388+ </ Activity >
384389
385- { /* Search bar and content for non-empty states */ }
386- { ! showEmptyState ? (
387- < >
388- < View className = "flex-row items-center gap-3 border-b border-gray-300 bg-gray-100 px-4 py-2" >
389- < TextInput
390- className = "flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-[17px] text-black focus:border-black focus:ring-2 focus:ring-black"
391- placeholder = "Search schedules"
392- placeholderTextColor = "#9CA3AF"
393- value = { searchQuery }
394- onChangeText = { handleSearch }
395- autoCapitalize = "none"
396- autoCorrect = { false }
397- clearButtonMode = "while-editing"
398- />
399- < TouchableOpacity
400- className = "min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
401- onPress = { handleCreateNew }
402- >
403- < Ionicons name = "add" size = { 18 } color = "#fff" />
404- < Text className = "text-base font-semibold text-white" > New</ Text >
405- </ TouchableOpacity >
406- </ View >
407-
408- { /* Search empty state */ }
409- { showSearchEmptyState ? (
410- < View className = "flex-1 bg-gray-50" style = { { paddingBottom : 100 } } >
411- < ScrollView
412- contentContainerStyle = { {
413- flexGrow : 1 ,
414- justifyContent : "center" ,
415- alignItems : "center" ,
416- padding : 20 ,
417- } }
418- refreshControl = { < RefreshControl refreshing = { refreshing } onRefresh = { onRefresh } /> }
419- >
420- < EmptyScreen
421- icon = "search-outline"
422- headline = { `No results found for "${ searchQuery } "` }
423- description = "Try searching with different keywords"
424- />
425- </ ScrollView >
426- </ View >
427- ) : null }
428-
429- { /* Schedules list */ }
430- { showList ? (
431- < View className = "flex-1 px-2 pt-4 md:px-4" >
432- < View className = "flex-1 overflow-hidden rounded-lg border border-[#E5E5EA] bg-white" >
433- < FlatList
434- data = { filteredSchedules }
435- keyExtractor = { ( item ) => item . id . toString ( ) }
436- renderItem = { renderSchedule }
437- contentContainerStyle = { { paddingBottom : 90 } }
438- refreshControl = { < RefreshControl refreshing = { refreshing } onRefresh = { onRefresh } /> }
439- showsVerticalScrollIndicator = { false }
440- />
441- </ View >
442- </ View >
443- ) : null }
444- </ >
445- ) : null }
390+ { /* Schedules list */ }
391+ < Activity mode = { showList ? "visible" : "hidden" } >
392+ < FlatList
393+ className = "flex-1 rounded-lg border border-[#E5E5EA] bg-white"
394+ contentContainerStyle = { {
395+ paddingBottom : 90 ,
396+ paddingHorizontal : 8 ,
397+ paddingVertical : 4 ,
398+ } }
399+ data = { filteredSchedules }
400+ keyExtractor = { ( item ) => item . id . toString ( ) }
401+ renderItem = { ( { item, index } ) => (
402+ < AvailabilityListItem
403+ item = { item }
404+ index = { index }
405+ handleSchedulePress = { handleSchedulePress }
406+ handleScheduleLongPress = { handleScheduleLongPress }
407+ setSelectedSchedule = { setSelectedSchedule }
408+ setShowActionsModal = { setShowActionsModal }
409+ onDuplicate = { handleDuplicate }
410+ onDelete = { handleDelete }
411+ onSetAsDefault = { handleSetAsDefault }
412+ />
413+ ) }
414+ refreshControl = { < RefreshControl refreshing = { refreshing } onRefresh = { onRefresh } /> }
415+ showsVerticalScrollIndicator = { false }
416+ contentInsetAdjustmentBehavior = "automatic"
417+ />
418+ </ Activity >
419+ </ Activity >
446420
447421 { /* Create Schedule Modal */ }
448422 < FullScreenModal
@@ -536,7 +510,9 @@ export default function Availability() {
536510 { /* Actions List */ }
537511 < View className = "p-2" >
538512 { /* Set as Default - only show if not already default */ }
539- { selectedSchedule && ! selectedSchedule . isDefault ? (
513+ < Activity
514+ mode = { selectedSchedule && ! selectedSchedule . isDefault ? "visible" : "hidden" }
515+ >
540516 < >
541517 < TouchableOpacity
542518 onPress = { ( ) => {
@@ -553,7 +529,7 @@ export default function Availability() {
553529
554530 < View className = "mx-4 my-2 h-px bg-gray-200" />
555531 </ >
556- ) : null }
532+ </ Activity >
557533
558534 { /* Duplicate */ }
559535 < TouchableOpacity
@@ -651,6 +627,6 @@ export default function Availability() {
651627 </ View >
652628 </ View >
653629 </ FullScreenModal >
654- </ View >
630+ </ >
655631 ) ;
656632}
0 commit comments