Files
calendar/companion/components/CacheStatusIndicator.tsx
T
Dhairyashil ShindeandGitHub 9203cb262c feat(companion): component improvements, migrate to path aliases, expo-haptics, expo-image (#26226)
* react compiler

* remove compilation mode 'all', it will use infer by default

* feat(companion): add theme tokens, path aliases, and component improvements

- Extend tailwind.config.js with Cal.com brand color tokens (cal.text, cal.bg, cal.border, cal.accent, cal.brand)
- Create theme/colors.ts for JS usage where Tailwind classes can't be used
- Configure path aliases in tsconfig.json (@/components, @/hooks, @/utils, etc.)
- Add expo-image and expo-haptics dependencies
- Enhance AppPressable with Reanimated animations (opacity + scale) and haptic feedback
- Extract shared logic from BookingListItem into useBookingListItemData hook and BookingListItemParts
- Extract shared logic from EventTypeListItem into useEventTypeListItemData hook and EventTypeListItemParts
- Extract shared logic from AvailabilityListItem into AvailabilityListItemParts
- Update all list item components to use new theme tokens instead of hardcoded hex values

* fix react compiler non memo issue

* feat(companion): migrate to path aliases and expo-image

- Remove theme/colors.ts and theme/index.ts (defer dark mode to future PR)
- Replace colors import with hardcoded hex values in components
- Migrate all files to use path aliases (@/components/*, @/hooks/*, etc.)
- Migrate SvgImage to use expo-image for better performance
- Update tsconfig.json to remove @/theme/* alias

* fix(companion): add path aliases to extension tsconfig

Add baseUrl and paths configuration to tsconfig.extension.json to support
path aliases in shared types used by the extension build.
2025-12-28 08:08:33 -03:00

146 lines
3.5 KiB
TypeScript

/**
* CacheStatusIndicator Component
*
* A subtle, non-intrusive component that displays cache status information:
* - "Last updated: X minutes ago" when online
* - "Offline - showing cached data" when offline
*
* This component is designed to be placed in screen headers or footers
* to provide users with transparency about data freshness.
*/
import { Ionicons } from "@expo/vector-icons";
import { StyleSheet, Text, View } from "react-native";
import { useQueryContext } from "@/contexts/QueryContext";
/**
* Props for the CacheStatusIndicator component
*/
interface CacheStatusIndicatorProps {
/** Timestamp when the data was last fetched (from query.dataUpdatedAt) */
dataUpdatedAt?: number;
/** Whether data is currently being fetched */
isFetching?: boolean;
/** Optional custom style */
style?: object;
/** Show compact version (icon only when online) */
compact?: boolean;
}
/**
* Format the time difference in a human-readable way
*/
const formatTimeAgo = (timestamp: number): string => {
const now = Date.now();
const diffMs = now - timestamp;
const diffSeconds = Math.floor(diffMs / 1000);
const diffMinutes = Math.floor(diffSeconds / 60);
const diffHours = Math.floor(diffMinutes / 60);
if (diffSeconds < 10) {
return "Just now";
}
if (diffSeconds < 60) {
return `${diffSeconds}s ago`;
}
if (diffMinutes < 60) {
return `${diffMinutes}m ago`;
}
if (diffHours < 24) {
return `${diffHours}h ago`;
}
return "Over a day ago";
};
/**
* CacheStatusIndicator component
*
* @example
* ```tsx
* const { dataUpdatedAt, isFetching } = useBookings();
*
* <CacheStatusIndicator
* dataUpdatedAt={dataUpdatedAt}
* isFetching={isFetching}
* />
* ```
*/
export function CacheStatusIndicator({
dataUpdatedAt,
isFetching = false,
style,
compact = false,
}: CacheStatusIndicatorProps) {
const { isOnline } = useQueryContext();
// Don't show anything if no data has been fetched yet
if (!dataUpdatedAt && !isFetching && isOnline) {
return null;
}
// Offline state
if (!isOnline) {
return (
<View style={[styles.container, styles.offlineContainer, style]}>
<Ionicons name="cloud-offline-outline" size={12} color="#FF9500" />
<Text style={styles.offlineText}>Offline - showing cached data</Text>
</View>
);
}
// Fetching state
if (isFetching) {
return (
<View style={[styles.container, style]}>
<Ionicons name="sync-outline" size={12} color="#8E8E93" />
{!compact && <Text style={styles.text}>Updating...</Text>}
</View>
);
}
// Normal state with last updated time
if (dataUpdatedAt) {
if (compact) {
return (
<View style={[styles.container, style]}>
<Ionicons name="time-outline" size={12} color="#8E8E93" />
</View>
);
}
return (
<View style={[styles.container, style]}>
<Ionicons name="time-outline" size={12} color="#8E8E93" />
<Text style={styles.text}>Updated {formatTimeAgo(dataUpdatedAt)}</Text>
</View>
);
}
return null;
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
gap: 4,
paddingVertical: 4,
paddingHorizontal: 8,
},
offlineContainer: {
backgroundColor: "rgba(255, 149, 0, 0.1)",
borderRadius: 4,
},
text: {
fontSize: 11,
color: "#8E8E93",
},
offlineText: {
fontSize: 11,
color: "#FF9500",
fontWeight: "500",
},
});
export default CacheStatusIndicator;