Files
cal.diy/companion/app/oauth/callback.tsx
T
Dhairyashil Shinde 49ae10149f feat(companion): iOS native UI components and simplified event type creation (#26240)
ios:


https://github.com/user-attachments/assets/efdfd3e3-239f-4cbf-be1a-7784d1df1851


android:



https://github.com/user-attachments/assets/a8e22a5c-60f3-4fb6-8923-b8a786bceb0c




## Overview

This PR introduces native iOS UI components, refactors the profile menu, and simplifies the event type creation flow for a more native iOS experience.

## Key Changes

### iOS Native UI Components
- **iOS-specific Event Types Page**: Added native Stack.Header, glass UI, and context menus for iOS
- **Platform-specific Profile Sheet**: Created separate implementations for iOS (formSheet) and Android/Web (modal)
- **Bottom Glass UI Navbar**: Updated bottom navigation bar with glass UI styling for iOS
- **Native iOS Alerts**: Replaced custom modals with native `Alert.prompt` for event types and availability pages

### Profile Menu Refactor
- Restructured More page with folder layout and native iOS header support
- Added profile button to More page header on iOS with glass UI
- Added "Copy public page link" and "Roadmap" options to profile sheet
- Refactored Header component: removed inline profile modal, now uses route-based navigation
- Updated Availability page with New menu and profile button in header
- Fixed "My Settings" URL to use `/general` endpoint
- Removed "Profile" item from More section menu
- Updated tab layout to support `(more)` folder structure

### Simplified Event Type Creation
- Reduced event type creation to only require title input
- Auto-generate slug from title using slugify utility
- Set default duration to 15 minutes
- Leave description empty (users can edit later)
- Applied to both iOS and Android/Web platforms

### UI Improvements
- Updated login button styling: changed background color to pure black (#000000) with white text for better contrast
2025-12-28 18:48:48 -03:00

109 lines
3.2 KiB
TypeScript

import { useLocalSearchParams, useRouter } from "expo-router";
import { useEffect } from "react";
import { ActivityIndicator, Platform, Text, View } from "react-native";
import { useAuth } from "@/contexts";
import { safeLogError } from "@/utils/safeLogger";
export default function OAuthCallback() {
const router = useRouter();
const params = useLocalSearchParams();
const auth = useAuth();
useEffect(() => {
if (Platform.OS === "android") {
if (auth.userInfo) {
router.replace("/");
}
}
if (Platform.OS !== "web") return;
// Extract code, state, and error parameters from URL
const code = params.code as string;
const state = params.state as string;
const error = params.error as string;
const errorDescription = params.error_description as string;
// Handle OAuth error response
if (error) {
const errorMessage = errorDescription || error || "OAuth authorization failed";
safeLogError("OAuth error occurred", { error, errorDescription });
if (typeof window !== "undefined") {
if (window.opener) {
window.opener.postMessage(
{
type: "OAUTH_ERROR",
error: errorMessage,
errorCode: error,
},
"*"
);
window.close();
} else {
// Store error for main app to pick up
if (state) {
window.localStorage.setItem(`oauth_callback_error_${state}`, errorMessage);
window.localStorage.setItem(`oauth_callback_error_code_${state}`, error);
}
router.replace("/");
}
}
return;
}
if (code && state) {
// Store the parameters temporarily using state-specific keys to prevent race conditions
// The OAuth service should be able to pick these up
if (typeof window !== "undefined") {
window.localStorage.setItem(`oauth_callback_code_${state}`, code);
window.localStorage.setItem(`oauth_callback_state_${state}`, state);
// Close the OAuth popup/tab if it exists
if (window.opener) {
window.opener.postMessage(
{
type: "OAUTH_SUCCESS",
code,
state,
},
"*"
);
window.close();
} else {
// Redirect to main app
router.replace("/");
}
}
} else {
safeLogError("No authorization code or state in OAuth callback", { code, state });
// Handle error case
if (typeof window !== "undefined") {
if (window.opener) {
window.opener.postMessage(
{
type: "OAUTH_ERROR",
error: "No authorization code received",
},
"*"
);
window.close();
} else {
router.replace("/");
}
}
}
}, [params, router, auth.userInfo]);
return (
<View className="flex-1 items-center justify-center bg-white">
<ActivityIndicator size="large" color="#2563eb" />
<Text className="mt-4 text-lg text-gray-700">Processing OAuth callback...</Text>
<Text className="mt-2 text-sm text-gray-500">
Please wait while we complete the authentication.
</Text>
</View>
);
}