Make scheduling and dates timezone-safe

This commit is contained in:
2026-08-17 18:15:39 -04:00
parent 1853eaa963
commit 70c08054fb
63 changed files with 2515 additions and 779 deletions
+51 -12
View File
@@ -3,11 +3,22 @@ import DateTimePicker, {
type DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import { useState } from "react";
import { Modal, Platform, Pressable, StyleSheet, Text, View } from "react-native";
import {
Modal,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import { fonts, radii, spacing } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
import { formatDate, formatDateTime } from "@/lib/format";
import {
calendarDateFromLocalDate,
calendarDateToLocalDate,
} from "@beenvoice/domain/time-zone";
type DateTimeFieldProps = {
label: string;
@@ -31,17 +42,18 @@ export function DateTimeField({
const [draft, setDraft] = useState(value);
function openPicker() {
setDraft(value);
setDraft(mode === "date" ? calendarDateToLocalDate(value) : value);
setOpen(true);
}
function applyDate(next: Date) {
const normalized = mode === "date" ? calendarDateFromLocalDate(next) : next;
const clamped =
next.getTime() > maximumDate.getTime()
normalized.getTime() > maximumDate.getTime()
? maximumDate
: minimumDate && next.getTime() < minimumDate.getTime()
: minimumDate && normalized.getTime() < minimumDate.getTime()
? minimumDate
: next;
: normalized;
onChange(clamped);
}
@@ -60,7 +72,9 @@ export function DateTimeField({
return (
<View style={styles.wrapper}>
<Text style={[styles.label, { color: colors.mutedForeground }]}>{label}</Text>
<Text style={[styles.label, { color: colors.mutedForeground }]}>
{label}
</Text>
<Pressable
accessible
accessibilityLabel={`${label}, ${
@@ -81,28 +95,53 @@ export function DateTimeField({
<Text style={[styles.value, { color: colors.foreground }]}>
{mode === "date" ? formatDate(value) : formatDateTime(value)}
</Text>
<Ionicons name="calendar-outline" size={18} color={colors.mutedForeground} />
<Ionicons
name="calendar-outline"
size={18}
color={colors.mutedForeground}
/>
</Pressable>
{Platform.OS === "ios" ? (
<Modal visible={open} transparent animationType="slide" onRequestClose={() => setOpen(false)}>
<Modal
visible={open}
transparent
animationType="slide"
onRequestClose={() => setOpen(false)}
>
<Pressable style={styles.backdrop} onPress={() => setOpen(false)}>
<Pressable
style={[styles.sheet, { backgroundColor: colors.card }]}
onPress={(event) => event.stopPropagation()}
>
<View style={[styles.sheetHeader, { borderBottomColor: colors.border }]}>
<View
style={[
styles.sheetHeader,
{ borderBottomColor: colors.border },
]}
>
<Pressable onPress={() => setOpen(false)}>
<Text style={[styles.sheetAction, { color: colors.mutedForeground }]}>Cancel</Text>
<Text
style={[
styles.sheetAction,
{ color: colors.mutedForeground },
]}
>
Cancel
</Text>
</Pressable>
<Text style={[styles.sheetTitle, { color: colors.foreground }]}>{label}</Text>
<Text style={[styles.sheetTitle, { color: colors.foreground }]}>
{label}
</Text>
<Pressable
onPress={() => {
applyDate(draft);
setOpen(false);
}}
>
<Text style={[styles.sheetAction, { color: colors.primary }]}>Done</Text>
<Text style={[styles.sheetAction, { color: colors.primary }]}>
Done
</Text>
</Pressable>
</View>
<DateTimePicker