mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat(companion): add configurable landing page feature - Add useUserPreferences hook for persistent storage of landing page preference - Add LandingPagePicker component for both iOS and Android/web platforms - Update tabs index to redirect based on user preference - Update bookings index to accept initial filter from URL params - Add App Settings section in More screen with landing page selector - Clear user preferences on logout for fresh state * fix(companion): remove try-finally for React Compiler compatibility * fix(companion): use router.replace for dynamic landing page redirect Replace Redirect component with router.replace() to fix TypeScript strict typing issue with expo-router's Href type for dynamic routes. * fix(companion): use literal route strings for TypeScript strict typing Use switch statement with literal route strings instead of dynamic string variable to satisfy expo-router's strict Href type checking. * working fix * better css
270 lines
9.5 KiB
TypeScript
270 lines
9.5 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
|
import { useState } from "react";
|
|
import {
|
|
Alert,
|
|
Platform,
|
|
ScrollView,
|
|
Text,
|
|
TouchableOpacity,
|
|
useColorScheme,
|
|
View,
|
|
} from "react-native";
|
|
import { Header } from "@/components/Header";
|
|
import { LandingPagePicker } from "@/components/LandingPagePicker";
|
|
import { LogoutConfirmModal } from "@/components/LogoutConfirmModal";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
import { useQueryContext } from "@/contexts/QueryContext";
|
|
import { type LandingPage, useUserPreferences } from "@/hooks/useUserPreferences";
|
|
import { showErrorAlert, showSuccessAlert } from "@/utils/alerts";
|
|
import { openInAppBrowser } from "@/utils/browser";
|
|
import { getColors } from "@/constants/colors";
|
|
|
|
interface MoreMenuItem {
|
|
name: string;
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
isExternal?: boolean;
|
|
href?: string;
|
|
onPress?: () => void;
|
|
}
|
|
|
|
export default function More() {
|
|
const { logout } = useAuth();
|
|
const { clearCache } = useQueryContext();
|
|
const { preferences, setLandingPage, landingPageLabel } = useUserPreferences();
|
|
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
|
const [showLandingPagePicker, setShowLandingPagePicker] = useState(false);
|
|
const colorScheme = useColorScheme();
|
|
const isDark = colorScheme === "dark";
|
|
const theme = getColors(isDark);
|
|
|
|
const handleLandingPageSelect = async (value: LandingPage) => {
|
|
try {
|
|
await setLandingPage(value);
|
|
showSuccessAlert("Saved", "First page updated");
|
|
} catch {
|
|
showErrorAlert("Error", "Failed to save preference. Please try again.");
|
|
}
|
|
};
|
|
|
|
const performLogout = async () => {
|
|
try {
|
|
// Clear in-memory cache before logout
|
|
await clearCache();
|
|
await logout();
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
console.error("Logout error", message);
|
|
if (__DEV__) {
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
console.debug("[More] logout failed", { message, stack });
|
|
}
|
|
showErrorAlert("Error", "Failed to sign out. Please try again.");
|
|
}
|
|
};
|
|
|
|
const handleSignOut = () => {
|
|
if (Platform.OS === "web") {
|
|
// Use modal for web/extension since Alert.alert doesn't work
|
|
setShowLogoutModal(true);
|
|
} else {
|
|
// Use native Alert for Android
|
|
Alert.alert("Sign Out", "Are you sure you want to sign out?", [
|
|
{ text: "Cancel", style: "cancel" },
|
|
{
|
|
text: "Sign Out",
|
|
style: "destructive",
|
|
onPress: performLogout,
|
|
},
|
|
]);
|
|
}
|
|
};
|
|
|
|
const menuItems: MoreMenuItem[] = [
|
|
{
|
|
name: "Apps",
|
|
icon: "grid-outline",
|
|
isExternal: true,
|
|
onPress: () => openInAppBrowser("https://app.cal.com/apps", "Apps page"),
|
|
},
|
|
{
|
|
name: "Routing",
|
|
icon: "git-branch-outline",
|
|
isExternal: true,
|
|
onPress: () => openInAppBrowser("https://app.cal.com/routing", "Routing page"),
|
|
},
|
|
{
|
|
name: "Workflows",
|
|
icon: "flash-outline",
|
|
isExternal: true,
|
|
onPress: () => openInAppBrowser("https://app.cal.com/workflows", "Workflows page"),
|
|
},
|
|
{
|
|
name: "Insights",
|
|
icon: "bar-chart-outline",
|
|
isExternal: true,
|
|
onPress: () => openInAppBrowser("https://app.cal.com/insights", "Insights page"),
|
|
},
|
|
{
|
|
name: "Support",
|
|
icon: "help-circle-outline",
|
|
isExternal: true,
|
|
onPress: () => openInAppBrowser("https://go.cal.com/support", "Support"),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<View className="flex-1" style={{ backgroundColor: theme.background }}>
|
|
<Header />
|
|
<ScrollView
|
|
contentContainerStyle={{ padding: 16, paddingBottom: 90 }}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<View
|
|
className="overflow-hidden rounded-lg border border-[#E5E5EA] bg-white"
|
|
style={{ borderColor: theme.border, backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
{menuItems.map((item, index) => (
|
|
<TouchableOpacity
|
|
key={item.name}
|
|
onPress={item.onPress}
|
|
className={`flex-row items-center justify-between bg-white px-5 py-5 active:bg-[#F8F9FA]`}
|
|
style={{
|
|
backgroundColor: theme.backgroundSecondary,
|
|
borderBottomWidth: index < menuItems.length - 1 ? 1 : 0,
|
|
borderBottomColor: theme.border,
|
|
}}
|
|
>
|
|
<View className="flex-1 flex-row items-center">
|
|
<Ionicons name={item.icon} size={20} color={theme.text} />
|
|
<Text className="ml-3 text-base font-semibold" style={{ color: theme.text }}>
|
|
{item.name}
|
|
</Text>
|
|
</View>
|
|
{item.isExternal ? (
|
|
<Ionicons name="open-outline" size={20} color={theme.textSecondary} />
|
|
) : (
|
|
<Ionicons name="chevron-forward" size={20} color={theme.textSecondary} />
|
|
)}
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
|
|
{/* App Settings */}
|
|
<View
|
|
className="mt-6 overflow-hidden rounded-lg border border-[#E5E5EA] bg-white"
|
|
style={{ borderColor: theme.border, backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<View
|
|
className="border-b border-[#E5E5EA] bg-gray-50 px-4 py-2"
|
|
style={{ borderBottomColor: theme.border, backgroundColor: isDark ? "#2C2C2E" : undefined }}
|
|
>
|
|
<Text
|
|
className="text-xs font-semibold uppercase text-gray-500"
|
|
style={{ color: theme.textSecondary }}
|
|
>
|
|
App Settings
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={() => setShowLandingPagePicker(true)}
|
|
className="flex-row items-center justify-between bg-white px-5 py-4 active:bg-[#F8F9FA]"
|
|
style={{ backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<View className="flex-row items-center">
|
|
<Ionicons name="home-outline" size={20} color={theme.text} />
|
|
<Text className="ml-3 text-base font-semibold" style={{ color: theme.text }}>
|
|
First Page
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row items-center">
|
|
<View
|
|
className="ml-3 rounded-md bg-gray-100 px-2.5 py-1"
|
|
style={{ backgroundColor: isDark ? "#3A3A3C" : undefined }}
|
|
>
|
|
<Text
|
|
className="text-sm font-medium text-gray-600"
|
|
style={{ color: theme.textSecondary }}
|
|
>
|
|
{landingPageLabel}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Delete Account Link */}
|
|
<View
|
|
className="mt-6 overflow-hidden rounded-lg border border-[#E5E5EA] bg-white"
|
|
style={{ borderColor: theme.border, backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<TouchableOpacity
|
|
onPress={() =>
|
|
openInAppBrowser("https://app.cal.com/settings/my-account/profile", "Delete Account")
|
|
}
|
|
className="flex-row items-center justify-between bg-white px-5 py-4 active:bg-red-50"
|
|
style={{ backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<View className="flex-1 flex-row items-center">
|
|
<Ionicons name="trash-outline" size={20} color={theme.destructive} />
|
|
<Text className="ml-3 text-base font-medium" style={{ color: theme.destructive }}>
|
|
Delete Account
|
|
</Text>
|
|
</View>
|
|
<Ionicons name="open-outline" size={20} color={theme.textSecondary} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Sign Out Button */}
|
|
<View
|
|
className="mt-4 overflow-hidden rounded-lg border border-[#E5E5EA] bg-white"
|
|
style={{ borderColor: theme.border, backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<TouchableOpacity
|
|
onPress={handleSignOut}
|
|
className="flex-row items-center justify-center bg-white px-5 py-4 active:bg-red-50"
|
|
style={{ backgroundColor: theme.backgroundSecondary }}
|
|
>
|
|
<Ionicons name="log-out-outline" size={20} color={theme.destructive} />
|
|
<Text className="ml-2 text-base font-medium" style={{ color: theme.destructive }}>
|
|
Sign Out
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Footer Note */}
|
|
<Text
|
|
className="mt-6 px-1 text-center text-xs text-gray-400"
|
|
style={{ color: theme.textMuted }}
|
|
>
|
|
The companion app is an extension of the web application.{"\n"}
|
|
For advanced features, visit{" "}
|
|
<Text
|
|
style={{ color: theme.text }}
|
|
onPress={() => openInAppBrowser("https://app.cal.com", "Cal.com")}
|
|
>
|
|
app.cal.com
|
|
</Text>
|
|
</Text>
|
|
</ScrollView>
|
|
|
|
{/* Logout Confirmation Modal for Web */}
|
|
<LogoutConfirmModal
|
|
visible={showLogoutModal}
|
|
onConfirm={() => {
|
|
setShowLogoutModal(false);
|
|
performLogout();
|
|
}}
|
|
onCancel={() => setShowLogoutModal(false)}
|
|
/>
|
|
|
|
{/* Landing Page Picker Modal */}
|
|
<LandingPagePicker
|
|
visible={showLandingPagePicker}
|
|
currentValue={preferences.landingPage}
|
|
onSelect={handleLandingPageSelect}
|
|
onClose={() => setShowLandingPagePicker(false)}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|