feat: UX improvements, security hardening, and code cleanup

- Extract shared extractError utility (lib/extractError.ts), remove 3 duplicate copies
- Export DEFAULT_BASE_URL from PostizContext, remove duplicate in settings
- Add 401 interceptor in axios client: fires UnauthorizedHandler in _layout → alert + redirect to Settings
- Calendar day items now tappable: tap opens context menu (Copy / Edit / Repost)
- Persist sort order (newest/oldest) across sessions via AsyncStorage
- Filter chips show post count per status (Queue 3, Error 1, etc.)
- Copy text action now shows a brief "Copied" toast + haptic feedback
- PostCard: swipe right → Reschedule action (QUEUE posts only, amber color)
- Compose: per-network char limit (Twitter 280, Instagram 2200…) with color warning at 90%
- Compose: local draft save/restore via AsyncStorage with restore banner on open
- Compose: prefillImagePath/prefillImageId params allow Edit/Repost to carry over existing media

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 22:20:56 +02:00
parent bc0973ccaa
commit 7aacb9a53e
8 changed files with 419 additions and 112 deletions
@@ -15,29 +15,9 @@ import {
} from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { usePostiz } from "@/context/PostizContext";
import { usePostiz, DEFAULT_BASE_URL } from "@/context/PostizContext";
import { useColors } from "@/hooks/useColors";
const DEFAULT_BASE_URL = "https://postiz.gyozamancave.fr/api/public/v1";
function extractAxiosError(err: unknown): string {
if (axios.isAxiosError(err)) {
const status = err.response?.status;
const data = err.response?.data;
if (data) {
const body =
typeof data === "string"
? data.slice(0, 200)
: (data?.message ?? data?.error ?? JSON.stringify(data)).toString().slice(0, 200);
return status ? `HTTP ${status}: ${body}` : body;
}
if (status) return `HTTP ${status}${err.message}`;
if (err.code === "ECONNABORTED") return "Request timed out (10s). Check that the URL is reachable.";
if (err.message) return err.message;
}
if (err instanceof Error) return err.message;
return "Unknown error";
}
import { extractError } from "@/lib/extractError";
export default function SettingsScreen() {
const colors = useColors();
@@ -98,7 +78,7 @@ export default function SettingsScreen() {
continue;
}
}
lastError = extractAxiosError(err);
lastError = extractError(err);
}
}
@@ -119,7 +99,7 @@ export default function SettingsScreen() {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
Alert.alert("Saved", "Settings saved successfully.");
} catch (err: unknown) {
Alert.alert("Error", `Failed to save settings.\n${extractAxiosError(err)}`);
Alert.alert("Error", `Failed to save settings.\n${extractError(err)}`);
} finally {
setSaving(false);
}