mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat(companion): add dark mode support using NativeWind system preference - Set userInterfaceStyle to 'automatic' in app.json to follow system preference - Add useColorScheme hook from NativeWind to detect color scheme - Update root layout with dynamic colors for StatusBar, containers, and modals - Update tabs layout with dynamic colors for tab bar and icons - Update all Stack.Screen options to use dynamic background colors and blur effects - Support both iOS and Android with appropriate dark mode colors Co-Authored-By: [email protected] <[email protected]> * fix(companion): use NativeWind dark: variant classes for automatic dark mode Use dark: Tailwind variant classes instead of conditional logic based on useColorScheme for the container background. This ensures NativeWind automatically applies dark mode styles based on system preference. Co-Authored-By: [email protected] <[email protected]> * fix(companion): use darkMode 'media' for automatic dark mode detection - Changed darkMode from 'class' to 'media' in tailwind.config.js - Updated global.css to use @media (prefers-color-scheme: dark) instead of .dark selector - This ensures NativeWind properly detects system color scheme on iOS/Android Co-Authored-By: [email protected] <[email protected]> * dark mode starting * feat(companion): add dark mode support to core infrastructure (Phase 1) * feat(companion): add dark mode support to tab pages (Phase 2) * fix(companion): add dark mode support to booking list components for font visibility * fix(companion): add dark mode support to EventTypeListItem components * event types and bookings page * Availability list page * more page, and some ui fixes * feat(companion): add dark mode support to detail screens and modals - BookingDetailScreen (iOS & Android): dynamic colors for all UI elements - AvailabilityDetailScreen (iOS & Android): dark mode backgrounds and text - profile-sheet (iOS & Android): dark mode support for profile modal - RescheduleScreen (all platforms): dark mode for date/time pickers - EditLocationScreen (iOS & Android): dark mode for location editor - AddGuestsScreen: partial dark mode implementation (in progress) Uses iOS system colors: #000000 (black), #1C1C1E (secondary), #FFFFFF (white), #38383A (border), #8E8E93 (text secondary) * fix typecheck and lint * feat(companion): add dark mode support to remaining screens - ScreenWrapper: Fix hardcoded #800020 color with dynamic destructive color - EditAvailabilityDayScreen: Add dark mode for Android/Web and iOS - EditAvailabilityHoursScreen: Add dark mode for Android/Web and iOS - EditAvailabilityNameScreen: Add dark mode for Android/Web and iOS - EditAvailabilityOverrideScreen: Add dark mode for Web, iOS, and Android - MarkNoShowScreen: Add dark mode with dynamic colors for no-show states - MeetingSessionDetailsScreen: Add dark mode for session details - ViewRecordingsScreen: Add dark mode for recordings list Uses iOS system colors (#000000, #1C1C1E, #38383A, #8E8E93) and useColorScheme hook for automatic theme detection. * fix(companion): fix iOS modal dark mode backgrounds for transparent glass UI * fix(companion): fix dark mode for booking action screens in transparent background mode * fix(companion): fix Android dark mode for transparent background in EditAvailabilityOverrideScreen * refactor(companion): centralize dark mode colors and align with main website - Update tailwind.config.js with centralized color definitions - Create constants/colors.ts for inline style color references - Replace iOS system colors with main website neutral grays: - #1C1C1E -> #171717 (secondary dark background) - #38383A -> #4D4D4D (dark border) - #8E8E93 -> #A3A3A3 (secondary text dark) - Keep pure black (#000000) for main backgrounds (OLED friendly) - Add semantic color mappings for consistent UI styling * feat(companion): complete dark mode implementation across all priority files - Priority 1: Updated SettingsUI.tsx, BasicsTab.tsx, LimitsTab.tsx, AdvancedTab.tsx, RecurringTab.tsx, AvailabilityTab.tsx, event-type-detail.tsx - Priority 2: Updated edit-availability-hours/name/override/day.tsx, oauth/callback.tsx - Priority 3: Updated AvailabilityListScreen.tsx with dark mode for modals and list items - Priority 4: Updated BookingActionsModal.tsx, BookingActionsModal.ios.tsx, GlassModalHeader.tsx, LoginScreen.tsx, LocationsList.tsx, SearchHeader.tsx, toast.tsx Dark mode colors used: - Main bg: #000000 (cal.bg.dark) - Secondary bg: #171717 (cal.bg.secondaryDark) - Muted bg: #262626 (cal.bg.mutedDark) - Borders: #4D4D4D (cal.border.dark) - Secondary text: #A3A3A3 (cal.text.secondaryDark) * fix(companion): add dark mode support to iOS header, GlassView modals, and main content background * fix(companion): add dark mode support to toggle/switch components * fix(companion): use iOS green (#34C759) for toggle ON state in dark mode * fix(companion): use white track + black thumb for dark mode toggles * fix(companion): add dark mode support to login page logo and button * dark mode to many pages, centralize the colors * dark mode skelaton * Availability pages android * toggles * headers * red color for dark mode * copy button * white flash on bottom ui native ios sheet * bottom sheet buttons * bottom sheet in gray color * bottom sheet again.. yeahh * toggle green * toggle green * fix lint and typechecks * fix exntension error * Update companion/components/event-type-detail/tabs/LimitsTabDatePicker.android.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * address cubic comments --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
554 lines
19 KiB
TypeScript
554 lines
19 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
|
||
import {
|
||
Alert,
|
||
Platform,
|
||
ScrollView,
|
||
Switch,
|
||
Text,
|
||
TouchableOpacity,
|
||
useColorScheme,
|
||
View,
|
||
} from "react-native";
|
||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||
import { AppPressable } from "@/components/AppPressable";
|
||
import { FullScreenModal } from "@/components/FullScreenModal";
|
||
import { useUpdateSchedule } from "@/hooks/useSchedules";
|
||
import type { Schedule } from "@/services/calcom";
|
||
import type { ScheduleAvailability } from "@/services/types";
|
||
import { showErrorAlert, showSuccessAlert } from "@/utils/alerts";
|
||
import { shadows } from "@/utils/shadows";
|
||
|
||
const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||
|
||
// Map day names to numbers
|
||
const DAY_NAME_TO_NUMBER: Record<string, number> = {
|
||
Sunday: 0,
|
||
Monday: 1,
|
||
Tuesday: 2,
|
||
Wednesday: 3,
|
||
Thursday: 4,
|
||
Friday: 5,
|
||
Saturday: 6,
|
||
};
|
||
|
||
// Generate time options (15-minute intervals)
|
||
const generateTimeOptions = () => {
|
||
const options: string[] = [];
|
||
for (let hour = 0; hour < 24; hour++) {
|
||
for (let minute = 0; minute < 60; minute += 15) {
|
||
const h = hour.toString().padStart(2, "0");
|
||
const m = minute.toString().padStart(2, "0");
|
||
options.push(`${h}:${m}`);
|
||
}
|
||
}
|
||
return options;
|
||
};
|
||
|
||
const TIME_OPTIONS = generateTimeOptions();
|
||
|
||
// Convert 24-hour time to 12-hour format with AM/PM
|
||
const formatTime12Hour = (time24: string): string => {
|
||
const [hours, minutes] = time24.split(":");
|
||
const hour = parseInt(hours, 10);
|
||
const min = minutes || "00";
|
||
const period = hour >= 12 ? "PM" : "AM";
|
||
const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
||
return `${hour12}:${min} ${period}`;
|
||
};
|
||
|
||
// Parse availability from schedule for a specific day
|
||
const parseAvailabilityForDay = (
|
||
schedule: Schedule | null,
|
||
dayIndex: number
|
||
): ScheduleAvailability[] => {
|
||
if (!schedule) return [];
|
||
|
||
const slots: ScheduleAvailability[] = [];
|
||
const availabilityArray = schedule.availability;
|
||
|
||
if (availabilityArray && Array.isArray(availabilityArray)) {
|
||
availabilityArray.forEach((slot) => {
|
||
let days: number[] = [];
|
||
if (Array.isArray(slot.days)) {
|
||
days = slot.days
|
||
.map((day) => {
|
||
if (typeof day === "string" && DAY_NAME_TO_NUMBER[day] !== undefined) {
|
||
return DAY_NAME_TO_NUMBER[day];
|
||
}
|
||
if (typeof day === "string") {
|
||
const parsed = parseInt(day, 10);
|
||
if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 6) {
|
||
return parsed;
|
||
}
|
||
}
|
||
if (typeof day === "number" && day >= 0 && day <= 6) {
|
||
return day;
|
||
}
|
||
return null;
|
||
})
|
||
.filter((day): day is number => day !== null);
|
||
}
|
||
|
||
if (days.includes(dayIndex)) {
|
||
const startTime = slot.startTime ?? "09:00:00";
|
||
const endTime = slot.endTime ?? "17:00:00";
|
||
slots.push({
|
||
days: [dayIndex.toString()],
|
||
startTime,
|
||
endTime,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
return slots;
|
||
};
|
||
|
||
// Build full availability array from schedule, replacing the specific day
|
||
const buildFullAvailability = (
|
||
schedule: Schedule,
|
||
dayIndex: number,
|
||
newSlots: ScheduleAvailability[]
|
||
): { days: string[]; startTime: string; endTime: string }[] => {
|
||
const result: { days: string[]; startTime: string; endTime: string }[] = [];
|
||
|
||
const existingArray = schedule.availability;
|
||
if (existingArray && Array.isArray(existingArray)) {
|
||
existingArray.forEach((slot) => {
|
||
let days: number[] = [];
|
||
if (Array.isArray(slot.days)) {
|
||
days = slot.days
|
||
.map((day) => {
|
||
if (typeof day === "string" && DAY_NAME_TO_NUMBER[day] !== undefined) {
|
||
return DAY_NAME_TO_NUMBER[day];
|
||
}
|
||
if (typeof day === "string") {
|
||
const parsed = parseInt(day, 10);
|
||
if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 6) {
|
||
return parsed;
|
||
}
|
||
}
|
||
if (typeof day === "number" && day >= 0 && day <= 6) {
|
||
return day;
|
||
}
|
||
return null;
|
||
})
|
||
.filter((day): day is number => day !== null);
|
||
}
|
||
|
||
const otherDays = days.filter((d) => d !== dayIndex);
|
||
if (otherDays.length > 0) {
|
||
otherDays.forEach((d) => {
|
||
result.push({
|
||
days: [DAYS[d]],
|
||
startTime: (slot.startTime ?? "09:00:00").substring(0, 5),
|
||
endTime: (slot.endTime ?? "17:00:00").substring(0, 5),
|
||
});
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
newSlots.forEach((slot) => {
|
||
result.push({
|
||
days: [DAYS[dayIndex]],
|
||
startTime: slot.startTime.substring(0, 5),
|
||
endTime: slot.endTime.substring(0, 5),
|
||
});
|
||
});
|
||
|
||
return result;
|
||
};
|
||
|
||
export interface EditAvailabilityDayScreenProps {
|
||
schedule: Schedule | null;
|
||
dayIndex: number;
|
||
onSuccess: () => void;
|
||
onSavingChange?: (isSaving: boolean) => void;
|
||
transparentBackground?: boolean;
|
||
}
|
||
|
||
export interface EditAvailabilityDayScreenHandle {
|
||
submit: () => void;
|
||
}
|
||
|
||
export const EditAvailabilityDayScreen = forwardRef<
|
||
EditAvailabilityDayScreenHandle,
|
||
EditAvailabilityDayScreenProps
|
||
>(function EditAvailabilityDayScreen({ schedule, dayIndex, onSuccess, onSavingChange }, ref) {
|
||
const insets = useSafeAreaInsets();
|
||
const colorScheme = useColorScheme();
|
||
const isDark = colorScheme === "dark";
|
||
|
||
const colors = {
|
||
background: isDark ? "#000000" : "#FFFFFF",
|
||
backgroundSecondary: isDark ? "#171717" : "#F9FAFB",
|
||
cardBackground: isDark ? "#171717" : "#FFFFFF",
|
||
text: isDark ? "#FFFFFF" : "#000000",
|
||
textSecondary: isDark ? "#A3A3A3" : "#6B7280",
|
||
border: isDark ? "#4D4D4D" : "#E5E5EA",
|
||
iconColor: isDark ? "#FFFFFF" : "#A3A3A3",
|
||
};
|
||
|
||
// Use mutation hook for cache-synchronized updates
|
||
const { mutate: updateSchedule, isPending: isMutating } = useUpdateSchedule();
|
||
|
||
const [isEnabled, setIsEnabled] = useState(false);
|
||
const [slots, setSlots] = useState<{ startTime: string; endTime: string }[]>([]);
|
||
const [showTimePicker, setShowTimePicker] = useState<{
|
||
slotIndex: number;
|
||
type: "start" | "end";
|
||
} | null>(null);
|
||
|
||
const dayName = DAYS[dayIndex] || "Day";
|
||
|
||
// Render modal content
|
||
const renderTimePickerContent = () => (
|
||
<>
|
||
<View className="flex-row items-center justify-between border-b border-gray-200 px-4 py-4 dark:border-[#4D4D4D]">
|
||
<Text className="text-[17px] font-semibold text-black dark:text-white">
|
||
Select {showTimePicker?.type === "start" ? "Start" : "End"} Time
|
||
</Text>
|
||
<AppPressable onPress={() => setShowTimePicker(null)}>
|
||
<Ionicons name="close" size={24} color={colors.iconColor} />
|
||
</AppPressable>
|
||
</View>
|
||
<ScrollView className="px-4 py-3">
|
||
{TIME_OPTIONS.map((time) => {
|
||
const slotIndex = showTimePicker?.slotIndex ?? 0;
|
||
const currentTime =
|
||
showTimePicker?.type === "start"
|
||
? slots[slotIndex]?.startTime
|
||
: slots[slotIndex]?.endTime;
|
||
const isSelected = currentTime === time;
|
||
|
||
return (
|
||
<AppPressable key={time} onPress={() => handleTimeSelect(time)}>
|
||
<View
|
||
className={`mb-2.5 rounded-xl border-2 px-4 py-4 ${
|
||
isSelected
|
||
? "border-[#007AFF] bg-blue-50 shadow-md dark:bg-[#0A84FF]/20"
|
||
: "border-gray-200 bg-gray-50 dark:border-[#4D4D4D] dark:bg-[#171717]"
|
||
}`}
|
||
>
|
||
<View className="flex-row items-center justify-between">
|
||
<Text
|
||
className={`text-[17px] ${
|
||
isSelected
|
||
? "font-semibold text-[#007AFF]"
|
||
: "font-medium text-gray-900 dark:text-white"
|
||
}`}
|
||
>
|
||
{formatTime12Hour(time)}
|
||
</Text>
|
||
{isSelected && (
|
||
<View className="rounded-full bg-[#007AFF] p-1.5">
|
||
<Ionicons name="checkmark" size={14} color="#FFFFFF" />
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</AppPressable>
|
||
);
|
||
})}
|
||
</ScrollView>
|
||
</>
|
||
);
|
||
|
||
// Initialize from schedule
|
||
useEffect(() => {
|
||
if (schedule) {
|
||
const daySlots = parseAvailabilityForDay(schedule, dayIndex);
|
||
if (daySlots.length > 0) {
|
||
setIsEnabled(true);
|
||
setSlots(
|
||
daySlots.map((s) => ({
|
||
startTime: s.startTime.substring(0, 5),
|
||
endTime: s.endTime.substring(0, 5),
|
||
}))
|
||
);
|
||
} else {
|
||
setIsEnabled(false);
|
||
setSlots([{ startTime: "09:00", endTime: "17:00" }]);
|
||
}
|
||
}
|
||
}, [schedule, dayIndex]);
|
||
|
||
// Notify parent of saving state
|
||
useEffect(() => {
|
||
onSavingChange?.(isMutating);
|
||
}, [isMutating, onSavingChange]);
|
||
|
||
const handleToggle = useCallback(
|
||
(value: boolean) => {
|
||
setIsEnabled(value);
|
||
if (value && slots.length === 0) {
|
||
setSlots([{ startTime: "09:00", endTime: "17:00" }]);
|
||
}
|
||
},
|
||
[slots.length]
|
||
);
|
||
|
||
const handleAddSlot = useCallback(() => {
|
||
setSlots((prev) => [...prev, { startTime: "09:00", endTime: "17:00" }]);
|
||
}, []);
|
||
|
||
const handleRemoveSlot = useCallback((index: number) => {
|
||
setSlots((prev) => prev.filter((_, i) => i !== index));
|
||
}, []);
|
||
|
||
const handleTimeSelect = useCallback(
|
||
(time: string) => {
|
||
if (!showTimePicker) return;
|
||
|
||
setSlots((prev) => {
|
||
const newSlots = [...prev];
|
||
if (showTimePicker.type === "start") {
|
||
newSlots[showTimePicker.slotIndex] = {
|
||
...newSlots[showTimePicker.slotIndex],
|
||
startTime: time,
|
||
};
|
||
} else {
|
||
newSlots[showTimePicker.slotIndex] = {
|
||
...newSlots[showTimePicker.slotIndex],
|
||
endTime: time,
|
||
};
|
||
}
|
||
return newSlots;
|
||
});
|
||
setShowTimePicker(null);
|
||
},
|
||
[showTimePicker]
|
||
);
|
||
|
||
const handleSubmit = useCallback(() => {
|
||
if (!schedule || isMutating) return;
|
||
|
||
// Validate all slots have end time after start time
|
||
if (isEnabled) {
|
||
for (const slot of slots) {
|
||
if (slot.endTime <= slot.startTime) {
|
||
Alert.alert("Error", "End time must be after start time for all slots");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
const daySlots: ScheduleAvailability[] = isEnabled
|
||
? slots.map((s) => ({
|
||
days: [dayIndex.toString()],
|
||
startTime: `${s.startTime}:00`,
|
||
endTime: `${s.endTime}:00`,
|
||
}))
|
||
: [];
|
||
|
||
const fullAvailability = buildFullAvailability(schedule, dayIndex, daySlots);
|
||
|
||
updateSchedule(
|
||
{ id: schedule.id, updates: { availability: fullAvailability } },
|
||
{
|
||
onSuccess: () => {
|
||
showSuccessAlert("Success", `${dayName} updated successfully`);
|
||
onSuccess();
|
||
},
|
||
onError: () => {
|
||
showErrorAlert("Error", "Failed to update schedule. Please try again.");
|
||
},
|
||
}
|
||
);
|
||
}, [schedule, dayIndex, dayName, isEnabled, slots, onSuccess, isMutating, updateSchedule]);
|
||
|
||
useImperativeHandle(
|
||
ref,
|
||
() => ({
|
||
submit: handleSubmit,
|
||
}),
|
||
[handleSubmit]
|
||
);
|
||
|
||
if (!schedule) {
|
||
return (
|
||
<View className="flex-1 items-center justify-center bg-white dark:bg-black">
|
||
<Text className="text-gray-500 dark:text-[#A3A3A3]">No schedule data</Text>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<ScrollView
|
||
className="flex-1 bg-white dark:bg-black"
|
||
contentContainerStyle={{
|
||
padding: 16,
|
||
paddingBottom: insets.bottom + 16,
|
||
}}
|
||
>
|
||
<View className="mb-4 flex-row items-center justify-between rounded-xl border-2 border-gray-200 bg-gray-50 px-4 py-3.5 dark:border-[#4D4D4D] dark:bg-[#171717]">
|
||
<Text className="text-[17px] font-medium text-black dark:text-white">
|
||
Available on {dayName}
|
||
</Text>
|
||
<Switch
|
||
value={isEnabled}
|
||
onValueChange={handleToggle}
|
||
trackColor={{
|
||
false: isDark ? "#404040" : "#E5E5EA",
|
||
true: isDark ? "#34C759" : "#000000",
|
||
}}
|
||
thumbColor="#FFFFFF"
|
||
/>
|
||
</View>
|
||
|
||
{isEnabled && (
|
||
<>
|
||
<Text className="mb-2 text-[13px] font-medium text-gray-500 dark:text-[#A3A3A3]">
|
||
Time Slots
|
||
</Text>
|
||
|
||
{slots.map((slot, index) => (
|
||
<View
|
||
key={`slot-${slot.startTime}-${slot.endTime}-${index}`}
|
||
className="mb-3 rounded-xl border-2 border-gray-200 bg-white px-4 py-4 dark:border-[#4D4D4D] dark:bg-[#171717]"
|
||
>
|
||
<View className="flex-row items-center justify-between">
|
||
<Text className="text-[15px] font-medium text-gray-500 dark:text-[#A3A3A3]">
|
||
Slot {index + 1}
|
||
</Text>
|
||
{slots.length > 1 && (
|
||
<AppPressable
|
||
onPress={() => handleRemoveSlot(index)}
|
||
className="rounded-full bg-red-50 p-2 dark:bg-[#FF453A]/20"
|
||
>
|
||
<Ionicons name="trash-outline" size={18} color="#FF3B30" />
|
||
</AppPressable>
|
||
)}
|
||
</View>
|
||
|
||
<View className="mt-3 flex-row items-center">
|
||
{Platform.OS === "web" ? (
|
||
<View className="flex-1 rounded-lg bg-gray-50 px-3 py-2.5 border border-gray-100 dark:bg-[#2C2C2E] dark:border-[#4D4D4D]">
|
||
<Text className="text-[13px] text-gray-500 dark:text-[#A3A3A3]">Start</Text>
|
||
<input
|
||
type="time"
|
||
value={slot.startTime}
|
||
onChange={(e) => {
|
||
const newSlots = [...slots];
|
||
newSlots[index] = { ...newSlots[index], startTime: e.target.value };
|
||
setSlots(newSlots);
|
||
}}
|
||
style={{
|
||
fontSize: 17,
|
||
fontWeight: 500,
|
||
color: colors.text,
|
||
backgroundColor: "transparent",
|
||
border: "none",
|
||
outline: "none",
|
||
fontFamily: "inherit",
|
||
width: "100%",
|
||
}}
|
||
/>
|
||
</View>
|
||
) : (
|
||
<AppPressable
|
||
className="flex-1 rounded-lg bg-gray-50 px-3 py-2.5 border border-gray-100 dark:bg-[#2C2C2E] dark:border-[#4D4D4D]"
|
||
onPress={() => setShowTimePicker({ slotIndex: index, type: "start" })}
|
||
>
|
||
<Text className="text-[13px] text-gray-500 dark:text-[#A3A3A3]">Start</Text>
|
||
<Text className="text-[17px] font-medium text-black dark:text-white">
|
||
{formatTime12Hour(slot.startTime)}
|
||
</Text>
|
||
</AppPressable>
|
||
)}
|
||
|
||
<Text className="mx-3 text-[17px] text-gray-400 dark:text-[#A3A3A3]">–</Text>
|
||
|
||
{Platform.OS === "web" ? (
|
||
<View className="flex-1 rounded-lg bg-gray-50 px-3 py-2.5 border border-gray-100 dark:bg-[#2C2C2E] dark:border-[#4D4D4D]">
|
||
<Text className="text-[13px] text-gray-500 dark:text-[#A3A3A3]">End</Text>
|
||
<input
|
||
type="time"
|
||
value={slot.endTime}
|
||
onChange={(e) => {
|
||
const newSlots = [...slots];
|
||
newSlots[index] = { ...newSlots[index], endTime: e.target.value };
|
||
setSlots(newSlots);
|
||
}}
|
||
style={{
|
||
fontSize: 17,
|
||
fontWeight: 500,
|
||
color: colors.text,
|
||
backgroundColor: "transparent",
|
||
border: "none",
|
||
outline: "none",
|
||
fontFamily: "inherit",
|
||
width: "100%",
|
||
}}
|
||
/>
|
||
</View>
|
||
) : (
|
||
<AppPressable
|
||
className="flex-1 rounded-lg bg-gray-50 px-3 py-2.5 border border-gray-100 dark:bg-[#2C2C2E] dark:border-[#4D4D4D]"
|
||
onPress={() => setShowTimePicker({ slotIndex: index, type: "end" })}
|
||
>
|
||
<Text className="text-[13px] text-gray-500 dark:text-[#A3A3A3]">End</Text>
|
||
<Text className="text-[17px] font-medium text-black dark:text-white">
|
||
{formatTime12Hour(slot.endTime)}
|
||
</Text>
|
||
</AppPressable>
|
||
)}
|
||
</View>
|
||
</View>
|
||
))}
|
||
|
||
<AppPressable onPress={handleAddSlot}>
|
||
<View className="flex-row items-center justify-center rounded-xl border-2 border-dashed border-gray-300 bg-gray-50 py-4 dark:border-[#4D4D4D] dark:bg-[#171717]">
|
||
<Ionicons name="add-circle" size={22} color="#007AFF" />
|
||
<Text className="ml-2 text-[17px] font-medium text-[#007AFF]">Add Time Slot</Text>
|
||
</View>
|
||
</AppPressable>
|
||
</>
|
||
)}
|
||
|
||
{!isEnabled && (
|
||
<View className="items-center rounded-xl border-2 border-dashed border-gray-200 px-4 py-12 dark:border-[#4D4D4D] dark:bg-[#171717]">
|
||
<View className="items-center justify-center rounded-full bg-gray-50 p-4 dark:bg-[#2C2C2E]">
|
||
<Ionicons name="moon-outline" size={32} color={colors.textSecondary} />
|
||
</View>
|
||
<Text className="mt-4 text-center text-[17px] font-medium text-gray-900 dark:text-white">
|
||
Unavailable
|
||
</Text>
|
||
<Text className="mt-1 text-center text-[15px] text-gray-500 dark:text-[#A3A3A3]">
|
||
You are currently unavailable on {dayName}.{"\n"}Toggle the switch above to add hours.
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
<FullScreenModal
|
||
visible={!!showTimePicker}
|
||
animationType={Platform.OS === "web" ? "fade" : "slide"}
|
||
onRequestClose={() => setShowTimePicker(null)}
|
||
>
|
||
{Platform.OS === "web" ? (
|
||
<TouchableOpacity
|
||
className="flex-1 items-center justify-center bg-black/50 p-4"
|
||
activeOpacity={1}
|
||
onPress={() => setShowTimePicker(null)}
|
||
>
|
||
<TouchableOpacity
|
||
className="max-h-[80%] w-full max-w-[500px] overflow-hidden rounded-2xl bg-white p-2 dark:bg-[#171717]"
|
||
activeOpacity={1}
|
||
onPress={(e) => e.stopPropagation()}
|
||
style={shadows.xl()}
|
||
>
|
||
{renderTimePickerContent()}
|
||
</TouchableOpacity>
|
||
</TouchableOpacity>
|
||
) : (
|
||
<View className="flex-1 bg-white p-2 dark:bg-[#171717]">{renderTimePickerContent()}</View>
|
||
)}
|
||
</FullScreenModal>
|
||
</ScrollView>
|
||
);
|
||
});
|
||
|
||
export default EditAvailabilityDayScreen;
|