Skip to content

Commit 9c68083

Browse files
PeerRichdevin-ai-integration[bot]dhairyashiil
authored
fix(companion): improve code clarity and type safety in components (calcom#26062)
- Remove 'as any' type assertion in LoginScreen.tsx, use Platform.OS check - Fix malformed className string in LogoutButton.tsx (extra quotes) - Remove @ts-ignore comments in Tooltip.tsx with proper web-specific types - Replace 'any' type with Schedule type in AvailabilityTab.tsx - Replace 'any' type with 'string' for SF Symbol icons in EventTypeListItem.ios.tsx - Remove console.log error handling in SvgImage.tsx - Extract duplicated formatDuration function to use shared utility from formatters.ts - Extract repeated inline styles to sectionDividerStyle constant in AvailabilityTab.tsx Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Dhairyashil <dhairyashil10101010@gmail.com>
1 parent 6650fdb commit 9c68083

8 files changed

Lines changed: 99 additions & 136 deletions

File tree

companion/components/LoginScreen.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import React from "react";
2-
import { View, Text, TouchableOpacity, Platform, StyleSheet } from "react-native";
3-
import { useSafeAreaInsets } from "react-native-safe-area-context";
41
import { useAuth } from "../contexts/AuthContext";
5-
import { CalComLogo } from "./CalComLogo";
62
import { showErrorAlert } from "../utils/alerts";
73
import { openInAppBrowser } from "../utils/browser";
4+
import { CalComLogo } from "./CalComLogo";
5+
import React from "react";
6+
import { View, Text, TouchableOpacity, Platform, StyleSheet } from "react-native";
7+
import { useSafeAreaInsets } from "react-native-safe-area-context";
88

99
export function LoginScreen() {
1010
const { loginWithOAuth, loading } = useAuth();
@@ -66,7 +66,7 @@ export function LoginScreen() {
6666
<TouchableOpacity
6767
onPress={handleSignUp}
6868
className="mt-3 items-center justify-center py-1"
69-
style={{ cursor: "pointer" } as any}
69+
style={Platform.OS === "web" ? { cursor: "pointer" } : undefined}
7070
activeOpacity={0.7}
7171
>
7272
<View>

companion/components/LogoutButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import React, { useState } from "react";
2-
import { TouchableOpacity, Text, Alert, Platform } from "react-native";
31
import { useAuth } from "../contexts/AuthContext";
42
import { LogoutConfirmModal } from "./LogoutConfirmModal";
3+
import React, { useState } from "react";
4+
import { TouchableOpacity, Text, Alert, Platform } from "react-native";
55

66
interface LogoutButtonProps {
77
className?: string;
@@ -62,7 +62,7 @@ export function LogoutButton({ className = "" }: LogoutButtonProps) {
6262
<>
6363
<TouchableOpacity
6464
onPress={handleLogoutPress}
65-
className={`"px-4 rounded-lg" bg-gray-600 py-2 ${className}`}
65+
className={`rounded-lg bg-gray-600 px-4 py-2 ${className}`}
6666
style={Platform.OS === "web" ? { cursor: "pointer" } : undefined}
6767
activeOpacity={0.7}
6868
>

companion/components/SvgImage.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ export const SvgImage: React.FC<SvgImageProps> = ({ uri, width, height, style })
2828
source={{ uri }}
2929
style={{ width, height }}
3030
resizeMode="contain"
31-
onError={() => console.log("Image load error:", uri)}
31+
onError={() => {
32+
// Image load errors are expected for invalid/missing URLs - silently handled
33+
}}
3234
/>
3335
</View>
3436
);
@@ -41,8 +43,8 @@ export const SvgImage: React.FC<SvgImageProps> = ({ uri, width, height, style })
4143
uri={uri}
4244
width={width}
4345
height={height}
44-
onError={(error) => {
45-
console.log("SVG load error, falling back to Image:", error);
46+
onError={() => {
47+
// SVG load errors trigger fallback to regular Image component
4648
setUseFallback(true);
4749
}}
4850
/>

companion/components/Tooltip.tsx

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,10 @@
1-
import React, { useState, useRef, useEffect } from "react";
2-
import { View, Text, Platform } from "react-native";
3-
41
interface TooltipProps {
52
text: string;
63
children: React.ReactElement;
74
}
85

96
export function Tooltip({ text, children }: TooltipProps) {
10-
const [showTooltip, setShowTooltip] = useState(false);
11-
const [position, setPosition] = useState({ top: 0, left: 0 });
12-
const containerRef = useRef<any>(null);
13-
14-
if (Platform.OS !== "web") {
15-
return children;
16-
}
17-
18-
const handleMouseEnter = (e: any) => {
19-
if (containerRef.current) {
20-
const rect = containerRef.current.getBoundingClientRect();
21-
setPosition({
22-
top: rect.top - 40,
23-
left: rect.left + rect.width / 2 + 20,
24-
});
25-
setShowTooltip(true);
26-
}
27-
};
28-
29-
const handleMouseLeave = () => {
30-
setShowTooltip(false);
31-
};
32-
33-
return (
34-
<View
35-
ref={containerRef}
36-
// @ts-ignore - web-only props
37-
onMouseEnter={handleMouseEnter}
38-
onMouseLeave={handleMouseLeave}
39-
style={{ position: "relative" }}
40-
>
41-
{children}
42-
{showTooltip ? (
43-
<View
44-
// @ts-ignore - web-only styles
45-
style={{
46-
position: "fixed",
47-
top: position.top,
48-
left: position.left,
49-
transform: "translate(-50%, -100%)",
50-
backgroundColor: "#1a1a1a",
51-
paddingLeft: 10,
52-
paddingRight: 10,
53-
paddingTop: 6,
54-
paddingBottom: 6,
55-
borderRadius: 6,
56-
zIndex: 10002,
57-
pointerEvents: "none",
58-
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
59-
}}
60-
>
61-
{/* @ts-ignore - web-only styles */}
62-
<Text style={{ color: "white", fontSize: 13, fontWeight: "600", whiteSpace: "nowrap" }}>
63-
{text}
64-
</Text>
65-
</View>
66-
) : null}
67-
</View>
68-
);
7+
// Native Tooltip is a no-op; web implementation lives in Tooltip.web.tsx
8+
void text;
9+
return children;
6910
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useState } from "react";
2+
3+
interface TooltipProps {
4+
text: string;
5+
children: React.ReactElement;
6+
}
7+
8+
export function Tooltip({ text, children }: TooltipProps) {
9+
const [showTooltip, setShowTooltip] = useState(false);
10+
11+
const handleMouseEnter = () => {
12+
setShowTooltip(true);
13+
};
14+
15+
const handleMouseLeave = () => {
16+
setShowTooltip(false);
17+
};
18+
19+
return (
20+
<span
21+
onMouseEnter={handleMouseEnter}
22+
onMouseLeave={handleMouseLeave}
23+
style={{ position: "relative", display: "inline-flex" }}
24+
>
25+
{children}
26+
{showTooltip ? (
27+
<span
28+
style={{
29+
position: "absolute",
30+
left: "50%",
31+
bottom: "100%",
32+
transform: "translate(-50%, -8px)",
33+
backgroundColor: "#1a1a1a",
34+
padding: "6px 10px",
35+
borderRadius: 6,
36+
zIndex: 10002,
37+
pointerEvents: "none",
38+
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
39+
color: "white",
40+
fontSize: 13,
41+
fontWeight: 600,
42+
whiteSpace: "nowrap",
43+
}}
44+
>
45+
{text}
46+
</span>
47+
) : null}
48+
</span>
49+
);
50+
}

companion/components/event-type-detail/tabs/AvailabilityTab.tsx

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { Schedule } from "../../../services/calcom";
2+
import { Ionicons } from "@expo/vector-icons";
13
import React from "react";
24
import { View, Text, TouchableOpacity } from "react-native";
3-
import { Ionicons } from "@expo/vector-icons";
4-
import { Schedule } from "../../../services/calcom";
55

66
interface DaySchedule {
77
day: string;
@@ -15,12 +15,21 @@ interface AvailabilityTabProps {
1515
setShowScheduleDropdown: (show: boolean) => void;
1616
schedulesLoading: boolean;
1717
scheduleDetailsLoading: boolean;
18-
selectedScheduleDetails: any;
18+
selectedScheduleDetails: Schedule | null;
1919
getDaySchedules: () => DaySchedule[];
2020
formatTime: (time: string) => string;
2121
selectedTimezone: string;
2222
}
2323

24+
const sectionDividerStyle = {
25+
borderTopWidth: 1,
26+
borderTopColor: "#E5E5EA",
27+
marginLeft: -20,
28+
marginRight: -20,
29+
paddingLeft: 20,
30+
paddingRight: 20,
31+
};
32+
2433
export function AvailabilityTab(props: AvailabilityTabProps) {
2534
return (
2635
<View className="rounded-2xl bg-white p-5">
@@ -44,17 +53,7 @@ export function AvailabilityTab(props: AvailabilityTabProps) {
4453

4554
{props.selectedSchedule ? (
4655
<>
47-
<View
48-
className="mt-5 pt-5"
49-
style={{
50-
borderTopWidth: 1,
51-
borderTopColor: "#E5E5EA",
52-
marginLeft: -20,
53-
marginRight: -20,
54-
paddingLeft: 20,
55-
paddingRight: 20,
56-
}}
57-
>
56+
<View className="mt-5 pt-5" style={sectionDividerStyle}>
5857
{props.scheduleDetailsLoading ? (
5958
<View className="items-center py-4">
6059
<Text className="text-sm italic text-[#8E8E93]">Loading schedule details...</Text>
@@ -71,7 +70,9 @@ export function AvailabilityTab(props: AvailabilityTabProps) {
7170
</Text>
7271
<Text className="mr-4 text-right text-[15px] text-[#666]">
7372
{dayInfo.available && dayInfo.startTime && dayInfo.endTime
74-
? `${props.formatTime(dayInfo.startTime)} - ${props.formatTime(dayInfo.endTime)}`
73+
? `${props.formatTime(dayInfo.startTime)} - ${props.formatTime(
74+
dayInfo.endTime
75+
)}`
7576
: "Unavailable"}
7677
</Text>
7778
</View>
@@ -85,17 +86,7 @@ export function AvailabilityTab(props: AvailabilityTabProps) {
8586
)}
8687
</View>
8788

88-
<View
89-
className="mt-5 pt-5"
90-
style={{
91-
borderTopWidth: 1,
92-
borderTopColor: "#E5E5EA",
93-
marginLeft: -20,
94-
marginRight: -20,
95-
paddingLeft: 20,
96-
paddingRight: 20,
97-
}}
98-
>
89+
<View className="mt-5 pt-5" style={sectionDividerStyle}>
9990
<Text className="mb-1.5 text-base font-semibold text-[#333]">Timezone</Text>
10091
<View className="items-center rounded-lg bg-[#F8F9FA] px-3 py-3">
10192
<Text className="text-center text-base text-[#666]">

companion/components/event-type-list-item/EventTypeListItem.ios.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Ionicons } from "@expo/vector-icons";
2-
import React from "react";
3-
import { View, Text, Pressable } from "react-native";
1+
import { formatDuration } from "../../utils/formatters";
2+
import { getEventDuration } from "../../utils/getEventDuration";
3+
import { normalizeMarkdown } from "../../utils/normalizeMarkdown";
4+
import { EventTypeListItemProps } from "./types";
45
import { Host, ContextMenu, Button, Image, HStack } from "@expo/ui/swift-ui";
5-
66
import { buttonStyle, frame, padding } from "@expo/ui/swift-ui/modifiers";
7-
import { EventTypeListItemProps } from "./types";
7+
import { Ionicons } from "@expo/vector-icons";
88
import { isLiquidGlassAvailable } from "expo-glass-effect";
9-
import { getEventDuration } from "../../utils/getEventDuration";
10-
import { normalizeMarkdown } from "../../utils/normalizeMarkdown";
9+
import React from "react";
10+
import { View, Text, Pressable } from "react-native";
1111

1212
export const EventTypeListItem = ({
1313
item,
@@ -25,9 +25,12 @@ export const EventTypeListItem = ({
2525
const duration = getEventDuration(item);
2626
const isLast = index === filteredEventTypes.length - 1;
2727

28+
type ButtonSystemImage = React.ComponentProps<typeof Button>["systemImage"];
29+
type EventTypeIcon = Exclude<ButtonSystemImage, undefined>;
30+
2831
const eventTypes: {
2932
label: string;
30-
icon: any;
33+
icon: EventTypeIcon;
3134
onPress: () => void;
3235
role: "default" | "destructive";
3336
}[] = [
@@ -63,18 +66,6 @@ export const EventTypeListItem = ({
6366
},
6467
];
6568

66-
const formatDuration = (minutes: number | undefined) => {
67-
if (!minutes || minutes <= 0) {
68-
return "0m";
69-
}
70-
if (minutes < 60) {
71-
return `${minutes}m`;
72-
}
73-
const hours = Math.floor(minutes / 60);
74-
const remainingMinutes = minutes % 60;
75-
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
76-
};
77-
7869
return (
7970
<View className={`bg-white active:bg-[#F8F9FA] ${!isLast ? "border-b border-[#E5E5EA]" : ""}`}>
8071
<View className="flex-shrink-1 flex-row items-center justify-between">

companion/components/event-type-list-item/EventTypeListItem.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
import { Tooltip } from "../../components/Tooltip";
2+
import { formatDuration } from "../../utils/formatters";
3+
import { getEventDuration } from "../../utils/getEventDuration";
4+
import { normalizeMarkdown } from "../../utils/normalizeMarkdown";
5+
import { EventTypeListItemProps } from "./types";
16
import { Ionicons } from "@expo/vector-icons";
27
import React from "react";
38
import { View, Text, TouchableOpacity } from "react-native";
49
import Svg, { Path } from "react-native-svg";
510

6-
import { Tooltip } from "../../components/Tooltip";
7-
import { EventTypeListItemProps } from "./types";
8-
import { getEventDuration } from "../../utils/getEventDuration";
9-
import { normalizeMarkdown } from "../../utils/normalizeMarkdown";
10-
1111
export const EventTypeListItem = ({
1212
item,
1313
index,
@@ -21,18 +21,6 @@ export const EventTypeListItem = ({
2121
const duration = getEventDuration(item);
2222
const isLast = index === filteredEventTypes.length - 1;
2323

24-
const formatDuration = (minutes: number | undefined) => {
25-
if (!minutes || minutes <= 0) {
26-
return "0m";
27-
}
28-
if (minutes < 60) {
29-
return `${minutes}m`;
30-
}
31-
const hours = Math.floor(minutes / 60);
32-
const remainingMinutes = minutes % 60;
33-
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
34-
};
35-
3624
return (
3725
<TouchableOpacity
3826
className={`bg-white active:bg-[#F8F9FA] ${!isLast ? "border-b border-[#E5E5EA]" : ""}`}

0 commit comments

Comments
 (0)