perf: preload events and CalDAV config to avoid empty screens
Add CaldavConfigStore and preloadAppData() to load events (current month) and CalDAV config into stores before dismissing the auth loading spinner. This prevents the brief empty flash when first navigating to Calendar or Settings tabs. Also applies Prettier formatting across codebase.
This commit is contained in:
@@ -1,17 +1,52 @@
|
||||
import { useEffect, ReactNode } from "react";
|
||||
import { useEffect, useState, ReactNode } from "react";
|
||||
import { View, ActivityIndicator } from "react-native";
|
||||
import { Redirect } from "expo-router";
|
||||
import { useAuthStore } from "../stores";
|
||||
import { useThemeStore } from "../stores/ThemeStore";
|
||||
import { useEventsStore } from "../stores/EventsStore";
|
||||
import { useCaldavConfigStore } from "../stores/CaldavConfigStore";
|
||||
import { EventService } from "../services";
|
||||
import { CaldavConfigService } from "../services/CaldavConfigService";
|
||||
|
||||
type AuthGuardProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Preloads app data (events + CalDAV config) into stores.
|
||||
* Called before the loading spinner is dismissed so screens have data immediately.
|
||||
*/
|
||||
export const preloadAppData = async () => {
|
||||
const now = new Date();
|
||||
const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const dayOfWeek = firstOfMonth.getDay();
|
||||
const daysFromPrevMonth = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
const startDate = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
1 - daysFromPrevMonth,
|
||||
);
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setDate(startDate.getDate() + 41);
|
||||
endDate.setHours(23, 59, 59);
|
||||
|
||||
const [eventsResult, configResult] = await Promise.allSettled([
|
||||
EventService.getByDateRange(startDate, endDate),
|
||||
CaldavConfigService.getConfig(),
|
||||
]);
|
||||
|
||||
if (eventsResult.status === "fulfilled") {
|
||||
useEventsStore.getState().setEvents(eventsResult.value);
|
||||
}
|
||||
if (configResult.status === "fulfilled") {
|
||||
useCaldavConfigStore.getState().setConfig(configResult.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps content that requires authentication.
|
||||
* - Loads stored user on mount
|
||||
* - Preloads app data (events, CalDAV config) before dismissing spinner
|
||||
* - Shows loading indicator while checking auth state
|
||||
* - Redirects to login if not authenticated
|
||||
* - Renders children if authenticated
|
||||
@@ -19,11 +54,14 @@ type AuthGuardProps = {
|
||||
export const AuthGuard = ({ children }: AuthGuardProps) => {
|
||||
const { theme } = useThemeStore();
|
||||
const { isAuthenticated, isLoading, loadStoredUser } = useAuthStore();
|
||||
const [dataReady, setDataReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
await loadStoredUser();
|
||||
if (!useAuthStore.getState().isAuthenticated) return;
|
||||
await preloadAppData();
|
||||
setDataReady(true);
|
||||
try {
|
||||
await CaldavConfigService.sync();
|
||||
} catch {
|
||||
@@ -33,7 +71,7 @@ export const AuthGuard = ({ children }: AuthGuardProps) => {
|
||||
init();
|
||||
}, [loadStoredUser]);
|
||||
|
||||
if (isLoading) {
|
||||
if (isLoading || (isAuthenticated && !dataReady)) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -9,7 +9,12 @@ export type BaseButtonProps = {
|
||||
solid?: boolean;
|
||||
};
|
||||
|
||||
const BaseButton = ({ className, children, onPress, solid = false }: BaseButtonProps) => {
|
||||
const BaseButton = ({
|
||||
className,
|
||||
children,
|
||||
onPress,
|
||||
solid = false,
|
||||
}: BaseButtonProps) => {
|
||||
const { theme } = useThemeStore();
|
||||
return (
|
||||
<Pressable
|
||||
|
||||
@@ -126,11 +126,11 @@ const DateTimePickerButton = ({
|
||||
|
||||
// Convenience wrappers for simpler usage
|
||||
export const DatePickerButton = (
|
||||
props: Omit<DateTimePickerButtonProps, "mode">
|
||||
props: Omit<DateTimePickerButtonProps, "mode">,
|
||||
) => <DateTimePickerButton {...props} mode="date" />;
|
||||
|
||||
export const TimePickerButton = (
|
||||
props: Omit<DateTimePickerButtonProps, "mode">
|
||||
props: Omit<DateTimePickerButtonProps, "mode">,
|
||||
) => <DateTimePickerButton {...props} mode="time" />;
|
||||
|
||||
export default DateTimePickerButton;
|
||||
|
||||
@@ -124,8 +124,12 @@ export const ProposedEventCard = ({
|
||||
color={theme.confirmButton}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={{ color: theme.confirmButton }} className="font-medium">
|
||||
Neue Ausnahme: {formatDate(new Date(proposedChange.occurrenceDate!))}
|
||||
<Text
|
||||
style={{ color: theme.confirmButton }}
|
||||
className="font-medium"
|
||||
>
|
||||
Neue Ausnahme:{" "}
|
||||
{formatDate(new Date(proposedChange.occurrenceDate!))}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -138,7 +142,10 @@ export const ProposedEventCard = ({
|
||||
color={theme.confirmButton}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={{ color: theme.confirmButton }} className="font-medium">
|
||||
<Text
|
||||
style={{ color: theme.confirmButton }}
|
||||
className="font-medium"
|
||||
>
|
||||
Neues Ende: {formatDate(newUntilDate)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user