mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
refactor: enhance type safety and improve code readability across multiple files
- Updated hooks to return specific types using UseQueryResult for better type safety. - Refactored various components to explicitly define return types for functions and callbacks. - Improved type annotations for variables and function parameters in screens and hooks. - Enhanced readability by restructuring code and ensuring consistent formatting. - Added missing type imports and ensured proper usage of types from the API. - Cleaned up unnecessary type assertions and improved overall code clarity.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -17,6 +18,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="name"
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"Project"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -41,6 +43,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
<ModelDelete
|
||||
modelType={Project}
|
||||
modelId={modelId}
|
||||
modelAPI={AdminModelAPI}
|
||||
onDeleteSuccess={() => {
|
||||
Navigation.navigate(RouteMap[PageMap.PROJECTS] as Route);
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
@@ -19,6 +20,7 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="name"
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"Project"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -43,6 +45,7 @@ const Projects: FunctionComponent = (): ReactElement => {
|
||||
<div>
|
||||
<CardModelDetail<Project>
|
||||
name="Project"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "Project",
|
||||
description: "Project details",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -17,6 +18,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -39,6 +41,7 @@ const DeletePage: FunctionComponent = (): ReactElement => {
|
||||
<ModelDelete
|
||||
modelType={User}
|
||||
modelId={modelId}
|
||||
modelAPI={AdminModelAPI}
|
||||
onDeleteSuccess={() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS] as Route);
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
@@ -19,6 +20,7 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -41,6 +43,7 @@ const Users: FunctionComponent = (): ReactElement => {
|
||||
<div>
|
||||
<CardModelDetail<User>
|
||||
name="User"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "User",
|
||||
description: "User details",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
@@ -19,6 +20,7 @@ const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
modelId={modelId}
|
||||
modelNameField="email"
|
||||
modelType={User}
|
||||
modelAPI={AdminModelAPI}
|
||||
title={"User"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
@@ -52,6 +54,7 @@ const UserSettings: FunctionComponent = (): ReactElement => {
|
||||
>
|
||||
<CardModelDetail<User>
|
||||
name="user-master-admin-settings"
|
||||
modelAPI={AdminModelAPI}
|
||||
cardProps={{
|
||||
title: "Master Admin Access",
|
||||
description:
|
||||
|
||||
@@ -12,6 +12,7 @@ import React, { ReactElement, useState } from "react";
|
||||
export interface ComponentProps<TBaseModel extends BaseModel> {
|
||||
modelType: { new (): TBaseModel };
|
||||
modelId: ObjectID;
|
||||
modelAPI?: typeof ModelAPI | undefined;
|
||||
onDeleteSuccess: () => void;
|
||||
}
|
||||
|
||||
@@ -29,7 +30,9 @@ const ModelDelete: <TBaseModel extends BaseModel>(
|
||||
const deleteItem: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await ModelAPI.deleteItem<TBaseModel>({
|
||||
const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI;
|
||||
|
||||
await modelAPI.deleteItem<TBaseModel>({
|
||||
modelType: props.modelType,
|
||||
id: props.modelId,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
|
||||
import PermissionUtil from "../../Utils/Permission";
|
||||
import User from "../../Utils/User";
|
||||
import Navigation from "../../Utils/Navigation";
|
||||
@@ -33,6 +34,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
|
||||
formFields?: undefined | Fields<TBaseModel>;
|
||||
className?: string | undefined;
|
||||
name: string;
|
||||
modelAPI?: typeof ModelAPI | undefined;
|
||||
createEditModalWidth?: ModalWidth | undefined;
|
||||
refresher?: boolean;
|
||||
createOrUpdateApiUrl?: URL | undefined;
|
||||
@@ -130,6 +132,7 @@ const CardModelDetail: <TBaseModel extends BaseModel>(
|
||||
<ModelDetail
|
||||
refresher={refresher}
|
||||
{...props.modelDetailProps}
|
||||
modelAPI={props.modelAPI}
|
||||
onItemLoaded={(item: TBaseModel) => {
|
||||
setItem(item);
|
||||
if (props.modelDetailProps.onItemLoaded) {
|
||||
@@ -144,6 +147,7 @@ const CardModelDetail: <TBaseModel extends BaseModel>(
|
||||
<ModelFormModal<TBaseModel>
|
||||
title={`Edit ${model.singularName}`}
|
||||
modalWidth={props.createEditModalWidth}
|
||||
modelAPI={props.modelAPI}
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
|
||||
fields: Array<Field<TBaseModel>>;
|
||||
onLoadingChange?: undefined | ((isLoading: boolean) => void);
|
||||
modelId: ObjectID;
|
||||
modelAPI?: typeof ModelAPI | undefined;
|
||||
onError?: ((error: string) => void) | undefined;
|
||||
onItemLoaded?: (item: TBaseModel) => void | undefined;
|
||||
refresher?: undefined | boolean;
|
||||
@@ -179,7 +180,9 @@ const ModelDetail: <TBaseModel extends BaseModel>(
|
||||
setOnBeforeFetchData(model);
|
||||
}
|
||||
|
||||
const item: TBaseModel | null = await ModelAPI.getItem({
|
||||
const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI;
|
||||
|
||||
const item: TBaseModel | null = await modelAPI.getItem({
|
||||
modelType: props.modelType,
|
||||
id: props.modelId,
|
||||
select: {
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
|
||||
modelType: { new (): TBaseModel };
|
||||
modelId: ObjectID;
|
||||
modelNameField: string;
|
||||
modelAPI?: typeof ModelAPI | undefined;
|
||||
}
|
||||
|
||||
const ModelPage: <TBaseModel extends BaseModel>(
|
||||
@@ -54,7 +55,9 @@ const ModelPage: <TBaseModel extends BaseModel>(
|
||||
} as Select<TBaseModel>;
|
||||
}
|
||||
|
||||
const item: TBaseModel | null = await ModelAPI.getItem({
|
||||
const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI;
|
||||
|
||||
const item: TBaseModel | null = await modelAPI.getItem({
|
||||
modelType: props.modelType,
|
||||
id: props.modelId,
|
||||
select: select as Select<TBaseModel>,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from "react";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import { View, StyleSheet, ViewStyle } from "react-native";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
||||
import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
|
||||
import type { Persister } from "@tanstack/query-persist-client-core";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { ThemeProvider, useTheme } from "./theme";
|
||||
import { AuthProvider } from "./hooks/useAuth";
|
||||
@@ -11,7 +12,7 @@ import { ProjectProvider } from "./hooks/useProject";
|
||||
import RootNavigator from "./navigation/RootNavigator";
|
||||
import OfflineBanner from "./components/OfflineBanner";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
const queryClient: QueryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
@@ -19,7 +20,7 @@ const queryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
const asyncStoragePersister = createAsyncStoragePersister({
|
||||
const asyncStoragePersister: Persister = createAsyncStoragePersister({
|
||||
storage: AsyncStorage,
|
||||
throttleTime: 1000,
|
||||
});
|
||||
@@ -58,7 +59,7 @@ export default function App(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: { container: ViewStyle } = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
@@ -28,15 +28,15 @@ export default function AddNoteModal({
|
||||
const { theme } = useTheme();
|
||||
const [noteText, setNoteText] = useState("");
|
||||
|
||||
const handleSubmit = (): void => {
|
||||
const trimmed = noteText.trim();
|
||||
const handleSubmit: () => void = (): void => {
|
||||
const trimmed: string = noteText.trim();
|
||||
if (trimmed) {
|
||||
onSubmit(trimmed);
|
||||
setNoteText("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (): void => {
|
||||
const handleClose: () => void = (): void => {
|
||||
setNoteText("");
|
||||
onClose();
|
||||
};
|
||||
@@ -151,7 +151,7 @@ export default function AddNoteModal({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
|
||||
@@ -3,7 +3,11 @@ import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
|
||||
import { useTheme } from "../theme";
|
||||
import { rgbToHex } from "../utils/color";
|
||||
import { formatRelativeTime } from "../utils/date";
|
||||
import type { IncidentEpisodeItem, AlertEpisodeItem } from "../api/types";
|
||||
import type {
|
||||
IncidentEpisodeItem,
|
||||
AlertEpisodeItem,
|
||||
NamedEntityWithColor,
|
||||
} from "../api/types";
|
||||
|
||||
type EpisodeCardProps =
|
||||
| {
|
||||
@@ -23,30 +27,30 @@ export default function EpisodeCard(
|
||||
const { episode, type, onPress } = props;
|
||||
const { theme } = useTheme();
|
||||
|
||||
const state =
|
||||
const state: NamedEntityWithColor =
|
||||
type === "incident"
|
||||
? (episode as IncidentEpisodeItem).currentIncidentState
|
||||
: (episode as AlertEpisodeItem).currentAlertState;
|
||||
|
||||
const severity =
|
||||
const severity: NamedEntityWithColor =
|
||||
type === "incident"
|
||||
? (episode as IncidentEpisodeItem).incidentSeverity
|
||||
: (episode as AlertEpisodeItem).alertSeverity;
|
||||
|
||||
const childCount =
|
||||
const childCount: number =
|
||||
type === "incident"
|
||||
? (episode as IncidentEpisodeItem).incidentCount
|
||||
: (episode as AlertEpisodeItem).alertCount;
|
||||
|
||||
const stateColor = state?.color
|
||||
const stateColor: string = state?.color
|
||||
? rgbToHex(state.color)
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
const severityColor = severity?.color
|
||||
const severityColor: string = severity?.color
|
||||
? rgbToHex(severity.color)
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
const timeString = formatRelativeTime(
|
||||
const timeString: string = formatRelativeTime(
|
||||
(episode as IncidentEpisodeItem).declaredAt || episode.createdAt,
|
||||
);
|
||||
|
||||
@@ -121,7 +125,7 @@ export default function EpisodeCard(
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
card: {
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -6,9 +6,9 @@ import { useNetworkStatus } from "../hooks/useNetworkStatus";
|
||||
export default function OfflineBanner(): React.JSX.Element | null {
|
||||
const { theme } = useTheme();
|
||||
const { isConnected, isInternetReachable } = useNetworkStatus();
|
||||
const slideAnim = useRef(new Animated.Value(-60)).current;
|
||||
const slideAnim: Animated.Value = useRef(new Animated.Value(-60)).current;
|
||||
|
||||
const isOffline = !isConnected || isInternetReachable === false;
|
||||
const isOffline: boolean = !isConnected || isInternetReachable === false;
|
||||
|
||||
useEffect(() => {
|
||||
Animated.spring(slideAnim, {
|
||||
@@ -43,7 +43,7 @@ export default function OfflineBanner(): React.JSX.Element | null {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
container: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
|
||||
@@ -208,20 +208,22 @@ export default function SkeletonCard({
|
||||
/>
|
||||
</View>
|
||||
{/* Body lines */}
|
||||
{Array.from({ length: Math.max(lines - 1, 1) }).map((_: unknown, index: number) => {
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.line,
|
||||
{
|
||||
backgroundColor: theme.colors.backgroundTertiary,
|
||||
width: lineWidths[index % lineWidths.length],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{Array.from({ length: Math.max(lines - 1, 1) }).map(
|
||||
(_: unknown, index: number) => {
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.line,
|
||||
{
|
||||
backgroundColor: theme.colors.backgroundTertiary,
|
||||
width: lineWidths[index % lineWidths.length],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,15 +36,21 @@ export default function SwipeableCard({
|
||||
const translateX: Animated.Value = useRef(new Animated.Value(0)).current;
|
||||
const hasTriggeredHaptic: React.MutableRefObject<boolean> = useRef(false);
|
||||
|
||||
const panResponder = useRef(
|
||||
const panResponder: PanResponderInstance = useRef(
|
||||
PanResponder.create({
|
||||
onMoveShouldSetPanResponder: (_, gestureState) => {
|
||||
onMoveShouldSetPanResponder: (
|
||||
_: GestureResponderEvent,
|
||||
gestureState: PanResponderGestureState,
|
||||
) => {
|
||||
return Math.abs(gestureState.dx) > 10 && Math.abs(gestureState.dy) < 20;
|
||||
},
|
||||
onPanResponderMove: (_, gestureState) => {
|
||||
onPanResponderMove: (
|
||||
_: GestureResponderEvent,
|
||||
gestureState: PanResponderGestureState,
|
||||
) => {
|
||||
// Limit swipe range
|
||||
const maxSwipe = 120;
|
||||
let dx = gestureState.dx;
|
||||
const maxSwipe: number = 120;
|
||||
let dx: number = gestureState.dx;
|
||||
if (!rightAction && dx < 0) {
|
||||
dx = 0;
|
||||
}
|
||||
@@ -62,7 +68,10 @@ export default function SwipeableCard({
|
||||
hasTriggeredHaptic.current = false;
|
||||
}
|
||||
},
|
||||
onPanResponderRelease: (_, gestureState) => {
|
||||
onPanResponderRelease: (
|
||||
_: GestureResponderEvent,
|
||||
gestureState: PanResponderGestureState,
|
||||
) => {
|
||||
if (gestureState.dx > SWIPE_THRESHOLD && leftAction) {
|
||||
leftAction.onAction();
|
||||
} else if (gestureState.dx < -SWIPE_THRESHOLD && rightAction) {
|
||||
@@ -124,7 +133,7 @@ export default function SwipeableCard({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
container: {
|
||||
overflow: "hidden",
|
||||
borderRadius: 12,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchAlertById,
|
||||
fetchAlertStates,
|
||||
fetchAlertStateTimeline,
|
||||
} from "../api/alerts";
|
||||
import type { AlertItem, AlertState, StateTimelineItem } from "../api/types";
|
||||
|
||||
export function useAlertDetail(projectId: string, alertId: string) {
|
||||
export function useAlertDetail(
|
||||
projectId: string,
|
||||
alertId: string,
|
||||
): UseQueryResult<AlertItem, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert", projectId, alertId],
|
||||
queryFn: () => {
|
||||
@@ -15,7 +19,9 @@ export function useAlertDetail(projectId: string, alertId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlertStates(projectId: string) {
|
||||
export function useAlertStates(
|
||||
projectId: string,
|
||||
): UseQueryResult<AlertState[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-states", projectId],
|
||||
queryFn: () => {
|
||||
@@ -25,7 +31,10 @@ export function useAlertStates(projectId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlertStateTimeline(projectId: string, alertId: string) {
|
||||
export function useAlertStateTimeline(
|
||||
projectId: string,
|
||||
alertId: string,
|
||||
): UseQueryResult<StateTimelineItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-state-timeline", projectId, alertId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchAlertEpisodeById,
|
||||
fetchAlertEpisodeStates,
|
||||
fetchAlertEpisodeStateTimeline,
|
||||
fetchAlertEpisodeNotes,
|
||||
} from "../api/alertEpisodes";
|
||||
import type {
|
||||
AlertEpisodeItem,
|
||||
AlertState,
|
||||
StateTimelineItem,
|
||||
NoteItem,
|
||||
} from "../api/types";
|
||||
|
||||
export function useAlertEpisodeDetail(projectId: string, episodeId: string) {
|
||||
export function useAlertEpisodeDetail(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
): UseQueryResult<AlertEpisodeItem, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-episode", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
@@ -16,7 +25,9 @@ export function useAlertEpisodeDetail(projectId: string, episodeId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlertEpisodeStates(projectId: string) {
|
||||
export function useAlertEpisodeStates(
|
||||
projectId: string,
|
||||
): UseQueryResult<AlertState[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-states", projectId],
|
||||
queryFn: () => {
|
||||
@@ -29,7 +40,7 @@ export function useAlertEpisodeStates(projectId: string) {
|
||||
export function useAlertEpisodeStateTimeline(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
) {
|
||||
): UseQueryResult<StateTimelineItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-episode-state-timeline", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
@@ -39,7 +50,10 @@ export function useAlertEpisodeStateTimeline(
|
||||
});
|
||||
}
|
||||
|
||||
export function useAlertEpisodeNotes(projectId: string, episodeId: string) {
|
||||
export function useAlertEpisodeNotes(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
): UseQueryResult<NoteItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-episode-notes", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchAlertEpisodes } from "../api/alertEpisodes";
|
||||
import type { ListResponse, AlertEpisodeItem } from "../api/types";
|
||||
|
||||
export function useAlertEpisodes(
|
||||
projectId: string,
|
||||
skip: number = 0,
|
||||
limit: number = 20,
|
||||
) {
|
||||
): UseQueryResult<ListResponse<AlertEpisodeItem>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-episodes", projectId, skip, limit],
|
||||
queryFn: () => {
|
||||
@@ -15,15 +16,18 @@ export function useAlertEpisodes(
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnresolvedAlertEpisodeCount(projectId: string) {
|
||||
export function useUnresolvedAlertEpisodeCount(
|
||||
projectId: string,
|
||||
): UseQueryResult<number, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-episodes", "unresolved-count", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await fetchAlertEpisodes(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
});
|
||||
const response: ListResponse<AlertEpisodeItem> =
|
||||
await fetchAlertEpisodes(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
});
|
||||
return response.count;
|
||||
},
|
||||
enabled: Boolean(projectId),
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchAlertNotes } from "../api/alertNotes";
|
||||
import type { NoteItem } from "../api/types";
|
||||
|
||||
export function useAlertNotes(projectId: string, alertId: string) {
|
||||
export function useAlertNotes(
|
||||
projectId: string,
|
||||
alertId: string,
|
||||
): UseQueryResult<NoteItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alert-notes", projectId, alertId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchAlerts } from "../api/alerts";
|
||||
import type { ListResponse, AlertItem } from "../api/types";
|
||||
|
||||
export function useAlerts(
|
||||
projectId: string,
|
||||
skip: number = 0,
|
||||
limit: number = 20,
|
||||
) {
|
||||
): UseQueryResult<ListResponse<AlertItem>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alerts", projectId, skip, limit],
|
||||
queryFn: () => {
|
||||
@@ -15,11 +16,13 @@ export function useAlerts(
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnresolvedAlertCount(projectId: string) {
|
||||
export function useUnresolvedAlertCount(
|
||||
projectId: string,
|
||||
): UseQueryResult<number, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["alerts", "unresolved-count", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await fetchAlerts(projectId, {
|
||||
const response: ListResponse<AlertItem> = await fetchAlerts(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
|
||||
@@ -27,7 +27,8 @@ interface AuthContextValue {
|
||||
setIsAuthenticated: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||
const AuthContext: React.Context<AuthContextValue | undefined> =
|
||||
createContext<AuthContextValue | undefined>(undefined);
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -36,22 +37,23 @@ interface AuthProviderProps {
|
||||
export function AuthProvider({
|
||||
children,
|
||||
}: AuthProviderProps): React.JSX.Element {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [needsServerUrl, setNeedsServerUrl] = useState(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [needsServerUrl, setNeedsServerUrl] = useState<boolean>(false);
|
||||
const [user, setUser] = useState<LoginResponse["user"] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
const checkAuth = async (): Promise<void> => {
|
||||
try {
|
||||
const hasUrl = await hasServerUrl();
|
||||
const hasUrl: boolean = await hasServerUrl();
|
||||
if (!hasUrl) {
|
||||
setNeedsServerUrl(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const tokens = await getTokens();
|
||||
const tokens: { accessToken: string; refreshToken: string } | null =
|
||||
await getTokens();
|
||||
if (tokens?.accessToken) {
|
||||
setIsAuthenticated(true);
|
||||
}
|
||||
@@ -66,8 +68,8 @@ export function AuthProvider({
|
||||
}, []);
|
||||
|
||||
// Register auth failure handler for 401 interceptor
|
||||
useEffect(() => {
|
||||
setOnAuthFailure(() => {
|
||||
useEffect((): void => {
|
||||
setOnAuthFailure((): void => {
|
||||
setIsAuthenticated(false);
|
||||
setUser(null);
|
||||
});
|
||||
@@ -75,7 +77,7 @@ export function AuthProvider({
|
||||
|
||||
const login = useCallback(
|
||||
async (email: string, password: string): Promise<LoginResponse> => {
|
||||
const response = await apiLogin(email, password);
|
||||
const response: LoginResponse = await apiLogin(email, password);
|
||||
|
||||
if (!response.twoFactorRequired && response.accessToken) {
|
||||
setIsAuthenticated(true);
|
||||
@@ -113,7 +115,7 @@ export function AuthProvider({
|
||||
}
|
||||
|
||||
export function useAuth(): AuthContextValue {
|
||||
const context = useContext(AuthContext);
|
||||
const context: AuthContextValue | undefined = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
|
||||
@@ -14,18 +14,18 @@ interface BiometricState {
|
||||
}
|
||||
|
||||
export function useBiometric(): BiometricState {
|
||||
const [isAvailable, setIsAvailable] = useState(false);
|
||||
const [isEnabled, setIsEnabled] = useState(false);
|
||||
const [biometricType, setBiometricType] = useState("Biometrics");
|
||||
const [isAvailable, setIsAvailable] = useState<boolean>(false);
|
||||
const [isEnabled, setIsEnabled] = useState<boolean>(false);
|
||||
const [biometricType, setBiometricType] = useState<string>("Biometrics");
|
||||
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
const check = async (): Promise<void> => {
|
||||
const compatible = await LocalAuthentication.hasHardwareAsync();
|
||||
const enrolled = await LocalAuthentication.isEnrolledAsync();
|
||||
const compatible: boolean = await LocalAuthentication.hasHardwareAsync();
|
||||
const enrolled: boolean = await LocalAuthentication.isEnrolledAsync();
|
||||
setIsAvailable(compatible && enrolled);
|
||||
|
||||
if (compatible) {
|
||||
const types =
|
||||
const types: LocalAuthentication.AuthenticationType[] =
|
||||
await LocalAuthentication.supportedAuthenticationTypesAsync();
|
||||
if (
|
||||
types.includes(
|
||||
@@ -40,7 +40,7 @@ export function useBiometric(): BiometricState {
|
||||
}
|
||||
}
|
||||
|
||||
const enabled = await getBiometricEnabled();
|
||||
const enabled: boolean = await getBiometricEnabled();
|
||||
setIsEnabled(enabled);
|
||||
};
|
||||
|
||||
@@ -48,7 +48,8 @@ export function useBiometric(): BiometricState {
|
||||
}, []);
|
||||
|
||||
const authenticate = useCallback(async (): Promise<boolean> => {
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
const result: LocalAuthentication.LocalAuthenticationResult =
|
||||
await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: "Authenticate to access OneUptime",
|
||||
fallbackLabel: "Use passcode",
|
||||
disableDeviceFallback: false,
|
||||
@@ -58,7 +59,8 @@ export function useBiometric(): BiometricState {
|
||||
|
||||
const setEnabled = useCallback(async (enabled: boolean): Promise<void> => {
|
||||
if (enabled) {
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
const result: LocalAuthentication.LocalAuthenticationResult =
|
||||
await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: "Confirm to enable biometric unlock",
|
||||
fallbackLabel: "Use passcode",
|
||||
disableDeviceFallback: false,
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import * as Haptics from "expo-haptics";
|
||||
|
||||
export function useHaptics() {
|
||||
interface HapticsResult {
|
||||
successFeedback: () => Promise<void>;
|
||||
errorFeedback: () => Promise<void>;
|
||||
lightImpact: () => Promise<void>;
|
||||
mediumImpact: () => Promise<void>;
|
||||
selectionFeedback: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useHaptics(): HapticsResult {
|
||||
const successFeedback = async (): Promise<void> => {
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchIncidentById,
|
||||
fetchIncidentStates,
|
||||
fetchIncidentStateTimeline,
|
||||
} from "../api/incidents";
|
||||
import type {
|
||||
IncidentItem,
|
||||
IncidentState,
|
||||
StateTimelineItem,
|
||||
} from "../api/types";
|
||||
|
||||
export function useIncidentDetail(projectId: string, incidentId: string) {
|
||||
export function useIncidentDetail(
|
||||
projectId: string,
|
||||
incidentId: string,
|
||||
): UseQueryResult<IncidentItem, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident", projectId, incidentId],
|
||||
queryFn: () => {
|
||||
@@ -15,7 +23,9 @@ export function useIncidentDetail(projectId: string, incidentId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useIncidentStates(projectId: string) {
|
||||
export function useIncidentStates(
|
||||
projectId: string,
|
||||
): UseQueryResult<IncidentState[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-states", projectId],
|
||||
queryFn: () => {
|
||||
@@ -28,7 +38,7 @@ export function useIncidentStates(projectId: string) {
|
||||
export function useIncidentStateTimeline(
|
||||
projectId: string,
|
||||
incidentId: string,
|
||||
) {
|
||||
): UseQueryResult<StateTimelineItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-state-timeline", projectId, incidentId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import {
|
||||
fetchIncidentEpisodeById,
|
||||
fetchIncidentEpisodeStates,
|
||||
fetchIncidentEpisodeStateTimeline,
|
||||
fetchIncidentEpisodeNotes,
|
||||
} from "../api/incidentEpisodes";
|
||||
import type {
|
||||
IncidentEpisodeItem,
|
||||
IncidentState,
|
||||
StateTimelineItem,
|
||||
NoteItem,
|
||||
} from "../api/types";
|
||||
|
||||
export function useIncidentEpisodeDetail(projectId: string, episodeId: string) {
|
||||
export function useIncidentEpisodeDetail(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
): UseQueryResult<IncidentEpisodeItem, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-episode", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
@@ -16,7 +25,9 @@ export function useIncidentEpisodeDetail(projectId: string, episodeId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useIncidentEpisodeStates(projectId: string) {
|
||||
export function useIncidentEpisodeStates(
|
||||
projectId: string,
|
||||
): UseQueryResult<IncidentState[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-states", projectId],
|
||||
queryFn: () => {
|
||||
@@ -29,7 +40,7 @@ export function useIncidentEpisodeStates(projectId: string) {
|
||||
export function useIncidentEpisodeStateTimeline(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
) {
|
||||
): UseQueryResult<StateTimelineItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-episode-state-timeline", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
@@ -39,7 +50,10 @@ export function useIncidentEpisodeStateTimeline(
|
||||
});
|
||||
}
|
||||
|
||||
export function useIncidentEpisodeNotes(projectId: string, episodeId: string) {
|
||||
export function useIncidentEpisodeNotes(
|
||||
projectId: string,
|
||||
episodeId: string,
|
||||
): UseQueryResult<NoteItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-episode-notes", projectId, episodeId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchIncidentEpisodes } from "../api/incidentEpisodes";
|
||||
import type { ListResponse, IncidentEpisodeItem } from "../api/types";
|
||||
|
||||
export function useIncidentEpisodes(
|
||||
projectId: string,
|
||||
skip: number = 0,
|
||||
limit: number = 20,
|
||||
) {
|
||||
): UseQueryResult<ListResponse<IncidentEpisodeItem>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-episodes", projectId, skip, limit],
|
||||
queryFn: () => {
|
||||
@@ -15,15 +16,18 @@ export function useIncidentEpisodes(
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnresolvedIncidentEpisodeCount(projectId: string) {
|
||||
export function useUnresolvedIncidentEpisodeCount(
|
||||
projectId: string,
|
||||
): UseQueryResult<number, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-episodes", "unresolved-count", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await fetchIncidentEpisodes(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
});
|
||||
const response: ListResponse<IncidentEpisodeItem> =
|
||||
await fetchIncidentEpisodes(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
});
|
||||
return response.count;
|
||||
},
|
||||
enabled: Boolean(projectId),
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchIncidentNotes } from "../api/incidentNotes";
|
||||
import type { NoteItem } from "../api/types";
|
||||
|
||||
export function useIncidentNotes(projectId: string, incidentId: string) {
|
||||
export function useIncidentNotes(
|
||||
projectId: string,
|
||||
incidentId: string,
|
||||
): UseQueryResult<NoteItem[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incident-notes", projectId, incidentId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryResult } from "@tanstack/react-query";
|
||||
import { fetchIncidents } from "../api/incidents";
|
||||
import type { ListResponse, IncidentItem } from "../api/types";
|
||||
|
||||
export function useIncidents(
|
||||
projectId: string,
|
||||
skip: number = 0,
|
||||
limit: number = 20,
|
||||
) {
|
||||
): UseQueryResult<ListResponse<IncidentItem>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incidents", projectId, skip, limit],
|
||||
queryFn: () => {
|
||||
@@ -15,15 +16,20 @@ export function useIncidents(
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnresolvedIncidentCount(projectId: string) {
|
||||
export function useUnresolvedIncidentCount(
|
||||
projectId: string,
|
||||
): UseQueryResult<number, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["incidents", "unresolved-count", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await fetchIncidents(projectId, {
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
});
|
||||
const response: ListResponse<IncidentItem> = await fetchIncidents(
|
||||
projectId,
|
||||
{
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
unresolvedOnly: true,
|
||||
},
|
||||
);
|
||||
return response.count;
|
||||
},
|
||||
enabled: Boolean(projectId),
|
||||
|
||||
@@ -12,15 +12,17 @@ export function useNetworkStatus(): NetworkStatus {
|
||||
isInternetReachable: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = NetInfo.addEventListener((state: NetInfoState) => {
|
||||
setStatus({
|
||||
isConnected: state.isConnected ?? true,
|
||||
isInternetReachable: state.isInternetReachable,
|
||||
});
|
||||
});
|
||||
useEffect((): (() => void) => {
|
||||
const unsubscribe: (() => void) = NetInfo.addEventListener(
|
||||
(state: NetInfoState): void => {
|
||||
setStatus({
|
||||
isConnected: state.isConnected ?? true,
|
||||
isInternetReachable: state.isInternetReachable,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
return (): void => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -21,9 +21,8 @@ interface ProjectContextValue {
|
||||
clearProject: () => Promise<void>;
|
||||
}
|
||||
|
||||
const ProjectContext = createContext<ProjectContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const ProjectContext: React.Context<ProjectContextValue | undefined> =
|
||||
createContext<ProjectContextValue | undefined>(undefined);
|
||||
|
||||
interface ProjectProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -36,20 +35,23 @@ export function ProjectProvider({
|
||||
null,
|
||||
);
|
||||
const [projectList, setProjectList] = useState<ProjectItem[]>([]);
|
||||
const [isLoadingProjects, setIsLoadingProjects] = useState(true);
|
||||
const [isLoadingProjects, setIsLoadingProjects] = useState<boolean>(true);
|
||||
|
||||
const loadProjects = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoadingProjects(true);
|
||||
const response = await fetchProjects();
|
||||
const response: { data: ProjectItem[] } = await fetchProjects();
|
||||
setProjectList(response.data);
|
||||
|
||||
// Try to restore previously selected project
|
||||
const savedId = await AsyncStorage.getItem(PROJECT_STORAGE_KEY);
|
||||
const savedId: string | null =
|
||||
await AsyncStorage.getItem(PROJECT_STORAGE_KEY);
|
||||
if (savedId) {
|
||||
const saved = response.data.find((p: ProjectItem) => {
|
||||
return p._id === savedId;
|
||||
});
|
||||
const saved: ProjectItem | undefined = response.data.find(
|
||||
(p: ProjectItem): boolean => {
|
||||
return p._id === savedId;
|
||||
},
|
||||
);
|
||||
if (saved) {
|
||||
setSelectedProject(saved);
|
||||
}
|
||||
@@ -57,7 +59,7 @@ export function ProjectProvider({
|
||||
|
||||
// Auto-select if only one project
|
||||
if (!savedId && response.data.length === 1) {
|
||||
const project = response.data[0]!;
|
||||
const project: ProjectItem = response.data[0]!;
|
||||
setSelectedProject(project);
|
||||
await AsyncStorage.setItem(PROJECT_STORAGE_KEY, project._id);
|
||||
}
|
||||
@@ -68,7 +70,7 @@ export function ProjectProvider({
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
loadProjects();
|
||||
}, [loadProjects]);
|
||||
|
||||
@@ -102,7 +104,7 @@ export function ProjectProvider({
|
||||
}
|
||||
|
||||
export function useProject(): ProjectContextValue {
|
||||
const context = useContext(ProjectContext);
|
||||
const context: ProjectContextValue | undefined = useContext(ProjectContext);
|
||||
if (!context) {
|
||||
throw new Error("useProject must be used within a ProjectProvider");
|
||||
}
|
||||
|
||||
@@ -18,34 +18,35 @@ import { useProject } from "./useProject";
|
||||
const PUSH_TOKEN_KEY = "oneuptime_expo_push_token";
|
||||
|
||||
export function usePushNotifications(navigationRef: unknown): void {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { projectList } = useProject();
|
||||
const { isAuthenticated }: { isAuthenticated: boolean } = useAuth();
|
||||
const { projectList }: { projectList: Array<{ _id: string }> } =
|
||||
useProject();
|
||||
const responseListenerRef = useRef<Subscription | null>(null);
|
||||
const receivedListenerRef = useRef<Subscription | null>(null);
|
||||
|
||||
// Set up channels and categories on mount
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
setupNotificationChannels();
|
||||
setupNotificationCategories();
|
||||
}, []);
|
||||
|
||||
// Set navigation ref for deep linking
|
||||
useEffect(() => {
|
||||
useEffect((): void => {
|
||||
if (navigationRef) {
|
||||
setNavigationRef(navigationRef);
|
||||
}
|
||||
}, [navigationRef]);
|
||||
|
||||
// Register push token when authenticated and projects loaded
|
||||
useEffect(() => {
|
||||
useEffect((): (() => void) | undefined => {
|
||||
if (!isAuthenticated || projectList.length === 0) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
let cancelled: boolean = false;
|
||||
|
||||
const register = async (): Promise<void> => {
|
||||
const token = await requestPermissionsAndGetToken();
|
||||
const token: string | null = await requestPermissionsAndGetToken();
|
||||
if (!token || cancelled) {
|
||||
return;
|
||||
}
|
||||
@@ -70,18 +71,19 @@ export function usePushNotifications(navigationRef: unknown): void {
|
||||
|
||||
register();
|
||||
|
||||
return () => {
|
||||
return (): void => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [isAuthenticated, projectList]);
|
||||
|
||||
// Set up notification listeners
|
||||
useEffect(() => {
|
||||
receivedListenerRef.current = Notifications.addNotificationReceivedListener(
|
||||
(_notification) => {
|
||||
// Foreground notification received — handler in setup.ts shows it
|
||||
},
|
||||
);
|
||||
useEffect((): (() => void) => {
|
||||
receivedListenerRef.current =
|
||||
Notifications.addNotificationReceivedListener(
|
||||
(_notification: Notifications.Notification): void => {
|
||||
// Foreground notification received — handler in setup.ts shows it
|
||||
},
|
||||
);
|
||||
|
||||
responseListenerRef.current =
|
||||
Notifications.addNotificationResponseReceivedListener(
|
||||
@@ -89,13 +91,15 @@ export function usePushNotifications(navigationRef: unknown): void {
|
||||
);
|
||||
|
||||
// Handle cold-start: check if app was opened via notification
|
||||
Notifications.getLastNotificationResponseAsync().then((response) => {
|
||||
if (response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
});
|
||||
Notifications.getLastNotificationResponseAsync().then(
|
||||
(response: Notifications.NotificationResponse | null): void => {
|
||||
if (response) {
|
||||
handleNotificationResponse(response);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
return (): void => {
|
||||
if (receivedListenerRef.current) {
|
||||
receivedListenerRef.current.remove();
|
||||
}
|
||||
@@ -108,7 +112,7 @@ export function usePushNotifications(navigationRef: unknown): void {
|
||||
|
||||
export async function unregisterPushToken(): Promise<void> {
|
||||
try {
|
||||
const token = await AsyncStorage.getItem(PUSH_TOKEN_KEY);
|
||||
const token: string | null = await AsyncStorage.getItem(PUSH_TOKEN_KEY);
|
||||
if (token) {
|
||||
await unregisterPushDevice(token);
|
||||
await AsyncStorage.removeItem(PUSH_TOKEN_KEY);
|
||||
|
||||
@@ -5,7 +5,7 @@ import AlertEpisodesScreen from "../screens/AlertEpisodesScreen";
|
||||
import AlertEpisodeDetailScreen from "../screens/AlertEpisodeDetailScreen";
|
||||
import type { AlertEpisodesStackParamList } from "./types";
|
||||
|
||||
const Stack = createNativeStackNavigator<AlertEpisodesStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<AlertEpisodesStackParamList>> = createNativeStackNavigator<AlertEpisodesStackParamList>();
|
||||
|
||||
export default function AlertEpisodesStackNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -5,7 +5,7 @@ import AlertsScreen from "../screens/AlertsScreen";
|
||||
import AlertDetailScreen from "../screens/AlertDetailScreen";
|
||||
import type { AlertsStackParamList } from "./types";
|
||||
|
||||
const Stack = createNativeStackNavigator<AlertsStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<AlertsStackParamList>> = createNativeStackNavigator<AlertsStackParamList>();
|
||||
|
||||
export default function AlertsStackNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -5,7 +5,7 @@ import ServerUrlScreen from "../screens/auth/ServerUrlScreen";
|
||||
import LoginScreen from "../screens/auth/LoginScreen";
|
||||
import { useTheme } from "../theme";
|
||||
|
||||
const Stack = createNativeStackNavigator<AuthStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<AuthStackParamList>> = createNativeStackNavigator<AuthStackParamList>();
|
||||
|
||||
interface AuthStackNavigatorProps {
|
||||
initialRoute: keyof AuthStackParamList;
|
||||
|
||||
@@ -5,7 +5,7 @@ import IncidentEpisodesScreen from "../screens/IncidentEpisodesScreen";
|
||||
import IncidentEpisodeDetailScreen from "../screens/IncidentEpisodeDetailScreen";
|
||||
import type { IncidentEpisodesStackParamList } from "./types";
|
||||
|
||||
const Stack = createNativeStackNavigator<IncidentEpisodesStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<IncidentEpisodesStackParamList>> = createNativeStackNavigator<IncidentEpisodesStackParamList>();
|
||||
|
||||
export default function IncidentEpisodesStackNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -5,7 +5,7 @@ import IncidentsScreen from "../screens/IncidentsScreen";
|
||||
import IncidentDetailScreen from "../screens/IncidentDetailScreen";
|
||||
import type { IncidentsStackParamList } from "./types";
|
||||
|
||||
const Stack = createNativeStackNavigator<IncidentsStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<IncidentsStackParamList>> = createNativeStackNavigator<IncidentsStackParamList>();
|
||||
|
||||
export default function IncidentsStackNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -9,7 +9,8 @@ import AlertEpisodesStackNavigator from "./AlertEpisodesStackNavigator";
|
||||
import SettingsStackNavigator from "./SettingsStackNavigator";
|
||||
import { useTheme } from "../theme";
|
||||
|
||||
const Tab: ReturnType<typeof createBottomTabNavigator<MainTabParamList>> = createBottomTabNavigator<MainTabParamList>();
|
||||
const Tab: ReturnType<typeof createBottomTabNavigator<MainTabParamList>> =
|
||||
createBottomTabNavigator<MainTabParamList>();
|
||||
|
||||
export default function MainTabNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -17,9 +17,9 @@ import ProjectSelectionScreen from "../screens/ProjectSelectionScreen";
|
||||
import BiometricLockScreen from "../screens/BiometricLockScreen";
|
||||
import { View, ActivityIndicator, StyleSheet } from "react-native";
|
||||
|
||||
const prefix = Linking.createURL("/");
|
||||
const prefix: string = Linking.createURL("/");
|
||||
|
||||
const linking = {
|
||||
const linking: React.ComponentProps<typeof NavigationContainer>["linking"] = {
|
||||
prefixes: [prefix, "oneuptime://"],
|
||||
config: {
|
||||
screens: {
|
||||
@@ -52,8 +52,8 @@ export default function RootNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
const { isAuthenticated, isLoading, needsServerUrl } = useAuth();
|
||||
const { selectedProject, isLoadingProjects } = useProject();
|
||||
const navigationRef = useNavigationContainerRef();
|
||||
const biometric = useBiometric();
|
||||
const navigationRef: ReturnType<typeof useNavigationContainerRef> = useNavigationContainerRef();
|
||||
const biometric: ReturnType<typeof useBiometric> = useBiometric();
|
||||
|
||||
const [biometricPassed, setBiometricPassed] = useState(false);
|
||||
const [biometricChecked, setBiometricChecked] = useState(false);
|
||||
@@ -62,7 +62,7 @@ export default function RootNavigator(): React.JSX.Element {
|
||||
|
||||
// Check biometric on app launch
|
||||
useEffect(() => {
|
||||
const checkBiometric = async (): Promise<void> => {
|
||||
const checkBiometric: () => Promise<void> = async (): Promise<void> => {
|
||||
if (!isAuthenticated || !biometric.isEnabled) {
|
||||
setBiometricPassed(true);
|
||||
setBiometricChecked(true);
|
||||
@@ -106,7 +106,7 @@ export default function RootNavigator(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const renderContent = (): React.JSX.Element => {
|
||||
const renderContent: () => React.JSX.Element = (): React.JSX.Element => {
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<AuthStackNavigator
|
||||
@@ -158,7 +158,7 @@ export default function RootNavigator(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
|
||||
@@ -5,7 +5,7 @@ import SettingsScreen from "../screens/SettingsScreen";
|
||||
import NotificationPreferencesScreen from "../screens/NotificationPreferencesScreen";
|
||||
import type { SettingsStackParamList } from "./types";
|
||||
|
||||
const Stack = createNativeStackNavigator<SettingsStackParamList>();
|
||||
const Stack: ReturnType<typeof createNativeStackNavigator<SettingsStackParamList>> = createNativeStackNavigator<SettingsStackParamList>();
|
||||
|
||||
export default function SettingsStackNavigator(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
@@ -52,9 +52,9 @@ function navigateToEntity(data: NotificationData): void {
|
||||
export function handleNotificationResponse(
|
||||
response: NotificationResponse,
|
||||
): void {
|
||||
const data =
|
||||
const data: NotificationData =
|
||||
(response.notification.request.content.data as NotificationData) || {};
|
||||
const actionId = response.actionIdentifier;
|
||||
const actionId: string = response.actionIdentifier;
|
||||
|
||||
if (actionId === "ACKNOWLEDGE") {
|
||||
// Background acknowledge — could call API here in the future
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as Notifications from "expo-notifications";
|
||||
import type { ExpoPushToken } from "expo-notifications";
|
||||
import * as Device from "expo-device";
|
||||
import Constants from "expo-constants";
|
||||
import { Platform } from "react-native";
|
||||
import { PermissionStatus } from "expo-modules-core";
|
||||
|
||||
// Show notifications when app is in foreground
|
||||
Notifications.setNotificationHandler({
|
||||
@@ -82,7 +84,7 @@ export async function requestPermissionsAndGetToken(): Promise<string | null> {
|
||||
}
|
||||
|
||||
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
||||
let finalStatus = existingStatus;
|
||||
let finalStatus: PermissionStatus = existingStatus;
|
||||
|
||||
if (existingStatus !== "granted") {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
@@ -93,7 +95,7 @@ export async function requestPermissionsAndGetToken(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projectId =
|
||||
const projectId: string | undefined =
|
||||
Constants.expoConfig?.extra?.eas?.projectId ??
|
||||
Constants.easConfig?.projectId;
|
||||
|
||||
@@ -101,7 +103,7 @@ export async function requestPermissionsAndGetToken(): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenData = await Notifications.getExpoPushTokenAsync({
|
||||
const tokenData: ExpoPushToken = await Notifications.getExpoPushTokenAsync({
|
||||
projectId,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,11 +24,7 @@ import { rgbToHex } from "../utils/color";
|
||||
import { formatDateTime } from "../utils/date";
|
||||
import type { AlertsStackParamList } from "../navigation/types";
|
||||
import { QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
AlertState,
|
||||
StateTimelineItem,
|
||||
NoteItem,
|
||||
} from "../api/types";
|
||||
import type { AlertState, StateTimelineItem, NoteItem } from "../api/types";
|
||||
import AddNoteModal from "../components/AddNoteModal";
|
||||
import SkeletonCard from "../components/SkeletonCard";
|
||||
import { useHaptics } from "../hooks/useHaptics";
|
||||
@@ -66,7 +62,10 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
|
||||
await Promise.all([refetchAlert(), refetchTimeline(), refetchNotes()]);
|
||||
}, [refetchAlert, refetchTimeline, refetchNotes]);
|
||||
|
||||
const handleStateChange: (stateId: string, stateName: string) => Promise<void> = useCallback(
|
||||
const handleStateChange: (
|
||||
stateId: string,
|
||||
stateName: string,
|
||||
) => Promise<void> = useCallback(
|
||||
async (stateId: string, stateName: string) => {
|
||||
if (!alert) {
|
||||
return;
|
||||
@@ -166,9 +165,11 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
// Find acknowledge and resolve states from fetched state definitions
|
||||
const acknowledgeState: AlertState | undefined = states?.find((s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
});
|
||||
const acknowledgeState: AlertState | undefined = states?.find(
|
||||
(s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
},
|
||||
);
|
||||
const resolveState: AlertState | undefined = states?.find((s: AlertState) => {
|
||||
return s.isResolvedState;
|
||||
});
|
||||
|
||||
@@ -26,11 +26,7 @@ import { rgbToHex } from "../utils/color";
|
||||
import { formatDateTime } from "../utils/date";
|
||||
import type { AlertEpisodesStackParamList } from "../navigation/types";
|
||||
import { QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
AlertState,
|
||||
StateTimelineItem,
|
||||
NoteItem,
|
||||
} from "../api/types";
|
||||
import type { AlertState, StateTimelineItem, NoteItem } from "../api/types";
|
||||
import AddNoteModal from "../components/AddNoteModal";
|
||||
import SkeletonCard from "../components/SkeletonCard";
|
||||
import { useHaptics } from "../hooks/useHaptics";
|
||||
@@ -71,7 +67,10 @@ export default function AlertEpisodeDetailScreen({
|
||||
await Promise.all([refetchEpisode(), refetchTimeline(), refetchNotes()]);
|
||||
}, [refetchEpisode, refetchTimeline, refetchNotes]);
|
||||
|
||||
const handleStateChange: (stateId: string, stateName: string) => Promise<void> = useCallback(
|
||||
const handleStateChange: (
|
||||
stateId: string,
|
||||
stateName: string,
|
||||
) => Promise<void> = useCallback(
|
||||
async (stateId: string, stateName: string) => {
|
||||
if (!episode) {
|
||||
return;
|
||||
@@ -172,9 +171,11 @@ export default function AlertEpisodeDetailScreen({
|
||||
? rgbToHex(episode.alertSeverity.color)
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
const acknowledgeState: AlertState | undefined = states?.find((s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
});
|
||||
const acknowledgeState: AlertState | undefined = states?.find(
|
||||
(s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
},
|
||||
);
|
||||
const resolveState: AlertState | undefined = states?.find((s: AlertState) => {
|
||||
return s.isResolvedState;
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ListRenderItemInfo,
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
@@ -139,7 +140,7 @@ export default function AlertEpisodesScreen(): React.JSX.Element {
|
||||
contentContainerStyle={
|
||||
episodes.length === 0 ? styles.emptyContainer : styles.list
|
||||
}
|
||||
renderItem={({ item }: { item: AlertEpisodeItem }) => {
|
||||
renderItem={({ item }: ListRenderItemInfo<AlertEpisodeItem>) => {
|
||||
return (
|
||||
<EpisodeCard
|
||||
episode={item}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ListRenderItemInfo,
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
@@ -45,9 +46,11 @@ export default function AlertsScreen(): React.JSX.Element {
|
||||
const { successFeedback, errorFeedback, lightImpact } = useHaptics();
|
||||
const queryClient: QueryClient = useQueryClient();
|
||||
|
||||
const acknowledgeState: AlertState | undefined = states?.find((s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
});
|
||||
const acknowledgeState: AlertState | undefined = states?.find(
|
||||
(s: AlertState) => {
|
||||
return s.isAcknowledgedState;
|
||||
},
|
||||
);
|
||||
|
||||
const alerts: AlertItem[] = data?.data ?? [];
|
||||
const totalCount: number = data?.count ?? 0;
|
||||
@@ -168,7 +171,7 @@ export default function AlertsScreen(): React.JSX.Element {
|
||||
contentContainerStyle={
|
||||
alerts.length === 0 ? styles.emptyContainer : styles.list
|
||||
}
|
||||
renderItem={({ item }: { item: AlertItem }) => {
|
||||
renderItem={({ item }: ListRenderItemInfo<AlertItem>) => {
|
||||
return (
|
||||
<SwipeableCard
|
||||
rightAction={
|
||||
|
||||
@@ -14,8 +14,8 @@ export default function BiometricLockScreen({
|
||||
}: BiometricLockScreenProps): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const authenticate = async (): Promise<void> => {
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
const authenticate: () => Promise<void> = async (): Promise<void> => {
|
||||
const result: LocalAuthentication.LocalAuthenticationResult = await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: "Unlock OneUptime",
|
||||
fallbackLabel: "Use passcode",
|
||||
disableDeviceFallback: false,
|
||||
@@ -100,7 +100,7 @@ export default function BiometricLockScreen({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
|
||||
@@ -38,7 +38,7 @@ function StatCard({
|
||||
const { theme } = useTheme();
|
||||
const { lightImpact } = useHaptics();
|
||||
|
||||
const handlePress = (): void => {
|
||||
const handlePress: () => void = (): void => {
|
||||
lightImpact();
|
||||
onPress();
|
||||
};
|
||||
@@ -101,8 +101,8 @@ function QuickLink({ label, onPress }: QuickLinkProps): React.JSX.Element {
|
||||
export default function HomeScreen(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
const { selectedProject } = useProject();
|
||||
const projectId = selectedProject?._id ?? "";
|
||||
const navigation = useNavigation<HomeNavProp>();
|
||||
const projectId: string = selectedProject?._id ?? "";
|
||||
const navigation: HomeNavProp = useNavigation<HomeNavProp>();
|
||||
|
||||
const {
|
||||
data: incidentCount,
|
||||
@@ -130,7 +130,7 @@ export default function HomeScreen(): React.JSX.Element {
|
||||
|
||||
const { lightImpact } = useHaptics();
|
||||
|
||||
const onRefresh = async (): Promise<void> => {
|
||||
const onRefresh: () => Promise<void> = async (): Promise<void> => {
|
||||
lightImpact();
|
||||
await Promise.all([
|
||||
refetchIncidents(),
|
||||
@@ -250,7 +250,7 @@ export default function HomeScreen(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
content: {
|
||||
padding: 20,
|
||||
paddingBottom: 40,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { rgbToHex } from "../utils/color";
|
||||
import { formatDateTime } from "../utils/date";
|
||||
import type { IncidentEpisodesStackParamList } from "../navigation/types";
|
||||
import type { IncidentState, StateTimelineItem, NoteItem } from "../api/types";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import AddNoteModal from "../components/AddNoteModal";
|
||||
import SkeletonCard from "../components/SkeletonCard";
|
||||
@@ -41,8 +42,8 @@ export default function IncidentEpisodeDetailScreen({
|
||||
const { episodeId } = route.params;
|
||||
const { theme } = useTheme();
|
||||
const { selectedProject } = useProject();
|
||||
const projectId = selectedProject?._id ?? "";
|
||||
const queryClient = useQueryClient();
|
||||
const projectId: string = selectedProject?._id ?? "";
|
||||
const queryClient: ReturnType<typeof useQueryClient> = useQueryClient();
|
||||
|
||||
const {
|
||||
data: episode,
|
||||
@@ -62,18 +63,18 @@ export default function IncidentEpisodeDetailScreen({
|
||||
const [noteModalVisible, setNoteModalVisible] = useState(false);
|
||||
const [submittingNote, setSubmittingNote] = useState(false);
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
const onRefresh: () => Promise<void> = useCallback(async (): Promise<void> => {
|
||||
await Promise.all([refetchEpisode(), refetchTimeline(), refetchNotes()]);
|
||||
}, [refetchEpisode, refetchTimeline, refetchNotes]);
|
||||
|
||||
const handleStateChange = useCallback(
|
||||
async (stateId: string, stateName: string) => {
|
||||
const handleStateChange: (stateId: string, stateName: string) => Promise<void> = useCallback(
|
||||
async (stateId: string, stateName: string): Promise<void> => {
|
||||
if (!episode) {
|
||||
return;
|
||||
}
|
||||
const queryKey = ["incident-episode", projectId, episodeId];
|
||||
const previousData = queryClient.getQueryData(queryKey);
|
||||
const newState = states?.find((s) => {
|
||||
const queryKey: string[] = ["incident-episode", projectId, episodeId];
|
||||
const previousData: unknown = queryClient.getQueryData(queryKey);
|
||||
const newState: IncidentState | undefined = states?.find((s: IncidentState) => {
|
||||
return s._id === stateId;
|
||||
});
|
||||
if (newState) {
|
||||
@@ -113,8 +114,8 @@ export default function IncidentEpisodeDetailScreen({
|
||||
],
|
||||
);
|
||||
|
||||
const handleAddNote = useCallback(
|
||||
async (noteText: string) => {
|
||||
const handleAddNote: (noteText: string) => Promise<void> = useCallback(
|
||||
async (noteText: string): Promise<void> => {
|
||||
setSubmittingNote(true);
|
||||
try {
|
||||
await createIncidentEpisodeNote(projectId, episodeId, noteText);
|
||||
@@ -159,24 +160,24 @@ export default function IncidentEpisodeDetailScreen({
|
||||
);
|
||||
}
|
||||
|
||||
const stateColor = episode.currentIncidentState?.color
|
||||
const stateColor: string = episode.currentIncidentState?.color
|
||||
? rgbToHex(episode.currentIncidentState.color)
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
const severityColor = episode.incidentSeverity?.color
|
||||
const severityColor: string = episode.incidentSeverity?.color
|
||||
? rgbToHex(episode.incidentSeverity.color)
|
||||
: theme.colors.textTertiary;
|
||||
|
||||
const acknowledgeState = states?.find((s) => {
|
||||
const acknowledgeState: IncidentState | undefined = states?.find((s: IncidentState) => {
|
||||
return s.isAcknowledgedState;
|
||||
});
|
||||
const resolveState = states?.find((s) => {
|
||||
const resolveState: IncidentState | undefined = states?.find((s: IncidentState) => {
|
||||
return s.isResolvedState;
|
||||
});
|
||||
|
||||
const currentStateId = episode.currentIncidentState?._id;
|
||||
const isResolved = resolveState?._id === currentStateId;
|
||||
const isAcknowledged = acknowledgeState?._id === currentStateId;
|
||||
const currentStateId: string | undefined = episode.currentIncidentState?._id;
|
||||
const isResolved: boolean = resolveState?._id === currentStateId;
|
||||
const isAcknowledged: boolean = acknowledgeState?._id === currentStateId;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -395,8 +396,8 @@ export default function IncidentEpisodeDetailScreen({
|
||||
>
|
||||
State Timeline
|
||||
</Text>
|
||||
{timeline.map((entry) => {
|
||||
const entryColor = entry.incidentState?.color
|
||||
{timeline.map((entry: StateTimelineItem) => {
|
||||
const entryColor: string = entry.incidentState?.color
|
||||
? rgbToHex(entry.incidentState.color)
|
||||
: theme.colors.textTertiary;
|
||||
return (
|
||||
@@ -472,7 +473,7 @@ export default function IncidentEpisodeDetailScreen({
|
||||
</View>
|
||||
|
||||
{notes && notes.length > 0
|
||||
? notes.map((note) => {
|
||||
? notes.map((note: NoteItem) => {
|
||||
return (
|
||||
<View
|
||||
key={note._id}
|
||||
@@ -541,7 +542,7 @@ export default function IncidentEpisodeDetailScreen({
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
centered: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
|
||||
@@ -19,7 +19,7 @@ import EmptyState from "../components/EmptyState";
|
||||
import type { IncidentEpisodesStackParamList } from "../navigation/types";
|
||||
import type { IncidentEpisodeItem } from "../api/types";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
const PAGE_SIZE: number = 20;
|
||||
|
||||
type NavProp = NativeStackNavigationProp<
|
||||
IncidentEpisodesStackParamList,
|
||||
@@ -29,12 +29,12 @@ type NavProp = NativeStackNavigationProp<
|
||||
export default function IncidentEpisodesScreen(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
const { selectedProject } = useProject();
|
||||
const projectId = selectedProject?._id ?? "";
|
||||
const navigation = useNavigation<NavProp>();
|
||||
const projectId: string = selectedProject?._id ?? "";
|
||||
const navigation: NavProp = useNavigation<NavProp>();
|
||||
|
||||
const { lightImpact } = useHaptics();
|
||||
const [page, setPage] = useState(0);
|
||||
const skip = page * PAGE_SIZE;
|
||||
const skip: number = page * PAGE_SIZE;
|
||||
|
||||
const { data, isLoading, isError, refetch } = useIncidentEpisodes(
|
||||
projectId,
|
||||
@@ -42,25 +42,25 @@ export default function IncidentEpisodesScreen(): React.JSX.Element {
|
||||
PAGE_SIZE,
|
||||
);
|
||||
|
||||
const episodes = data?.data ?? [];
|
||||
const totalCount = data?.count ?? 0;
|
||||
const hasMore = skip + PAGE_SIZE < totalCount;
|
||||
const episodes: IncidentEpisodeItem[] = data?.data ?? [];
|
||||
const totalCount: number = data?.count ?? 0;
|
||||
const hasMore: boolean = skip + PAGE_SIZE < totalCount;
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
const onRefresh: () => Promise<void> = useCallback(async (): Promise<void> => {
|
||||
lightImpact();
|
||||
setPage(0);
|
||||
await refetch();
|
||||
}, [refetch, lightImpact]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
const loadMore: () => void = useCallback((): void => {
|
||||
if (hasMore && !isLoading) {
|
||||
setPage((prev) => {
|
||||
setPage((prev: number) => {
|
||||
return prev + 1;
|
||||
});
|
||||
}
|
||||
}, [hasMore, isLoading]);
|
||||
|
||||
const handlePress = useCallback(
|
||||
const handlePress: (episode: IncidentEpisodeItem) => void = useCallback(
|
||||
(episode: IncidentEpisodeItem) => {
|
||||
navigation.navigate("IncidentEpisodeDetail", {
|
||||
episodeId: episode._id,
|
||||
@@ -133,13 +133,13 @@ export default function IncidentEpisodesScreen(): React.JSX.Element {
|
||||
>
|
||||
<FlatList
|
||||
data={episodes}
|
||||
keyExtractor={(item) => {
|
||||
keyExtractor={(item: IncidentEpisodeItem) => {
|
||||
return item._id;
|
||||
}}
|
||||
contentContainerStyle={
|
||||
episodes.length === 0 ? styles.emptyContainer : styles.list
|
||||
}
|
||||
renderItem={({ item }) => {
|
||||
renderItem={({ item }: { item: IncidentEpisodeItem }) => {
|
||||
return (
|
||||
<EpisodeCard
|
||||
episode={item}
|
||||
@@ -167,7 +167,7 @@ export default function IncidentEpisodesScreen(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StyleSheet,
|
||||
ListRenderItemInfo,
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
@@ -181,7 +182,7 @@ export default function IncidentsScreen(): React.JSX.Element {
|
||||
contentContainerStyle={
|
||||
incidents.length === 0 ? styles.emptyContainer : styles.list
|
||||
}
|
||||
renderItem={({ item }: { item: IncidentItem }) => {
|
||||
renderItem={({ item }: ListRenderItemInfo<IncidentItem>) => {
|
||||
return (
|
||||
<SwipeableCard
|
||||
rightAction={
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { View, Text, ScrollView, Switch, StyleSheet, ViewStyle, TextStyle } from "react-native";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
Switch,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
} from "react-native";
|
||||
import { useTheme } from "../theme";
|
||||
import { useHaptics } from "../hooks/useHaptics";
|
||||
import {
|
||||
@@ -78,7 +86,10 @@ export default function NotificationPreferencesScreen(): React.JSX.Element {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updatePref: (key: keyof NotificationPreferences, value: boolean) => void = useCallback(
|
||||
const updatePref: (
|
||||
key: keyof NotificationPreferences,
|
||||
value: boolean,
|
||||
) => void = useCallback(
|
||||
(key: keyof NotificationPreferences, value: boolean) => {
|
||||
selectionFeedback();
|
||||
const updated: NotificationPreferences = { ...prefs, [key]: value };
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function ProjectSelectionScreen(): React.JSX.Element {
|
||||
const { projectList, isLoadingProjects, selectProject, refreshProjects } =
|
||||
useProject();
|
||||
|
||||
const handleSelect = async (project: ProjectItem): Promise<void> => {
|
||||
const handleSelect: (project: ProjectItem) => Promise<void> = async (project: ProjectItem): Promise<void> => {
|
||||
await selectProject(project);
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function ProjectSelectionScreen(): React.JSX.Element {
|
||||
},
|
||||
]}
|
||||
>
|
||||
You don't have access to any projects.
|
||||
{"You don't have access to any projects."}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
@@ -117,11 +117,11 @@ export default function ProjectSelectionScreen(): React.JSX.Element {
|
||||
|
||||
<FlatList
|
||||
data={projectList}
|
||||
keyExtractor={(item) => {
|
||||
keyExtractor={(item: ProjectItem) => {
|
||||
return item._id;
|
||||
}}
|
||||
contentContainerStyle={styles.list}
|
||||
renderItem={({ item }) => {
|
||||
renderItem={({ item }: { item: ProjectItem }) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
@@ -170,7 +170,7 @@ export default function ProjectSelectionScreen(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const styles: ReturnType<typeof StyleSheet.create> = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ type SettingsNavProp = NativeStackNavigationProp<
|
||||
"SettingsList"
|
||||
>;
|
||||
|
||||
const APP_VERSION = "1.0.0";
|
||||
const APP_VERSION: string = "1.0.0";
|
||||
|
||||
interface SettingsRowProps {
|
||||
label: string;
|
||||
@@ -43,7 +43,7 @@ function SettingsRow({
|
||||
}: SettingsRowProps): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const content = (
|
||||
const content: React.JSX.Element = (
|
||||
<View
|
||||
style={[
|
||||
styles.row,
|
||||
@@ -95,16 +95,16 @@ export default function SettingsScreen(): React.JSX.Element {
|
||||
const { theme, themeMode, setThemeMode } = useTheme();
|
||||
const { logout } = useAuth();
|
||||
const { selectedProject, clearProject } = useProject();
|
||||
const biometric = useBiometric();
|
||||
const biometric: ReturnType<typeof useBiometric> = useBiometric();
|
||||
const { selectionFeedback } = useHaptics();
|
||||
const navigation = useNavigation<SettingsNavProp>();
|
||||
const navigation: SettingsNavProp = useNavigation<SettingsNavProp>();
|
||||
const [serverUrl, setServerUrlState] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getServerUrl().then(setServerUrlState);
|
||||
}, []);
|
||||
|
||||
const handleChangeProject = async (): Promise<void> => {
|
||||
const handleChangeProject: () => Promise<void> = async (): Promise<void> => {
|
||||
await clearProject();
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ type ServerUrlNavigationProp = NativeStackNavigationProp<
|
||||
export default function ServerUrlScreen(): React.JSX.Element {
|
||||
const { theme } = useTheme();
|
||||
const { setNeedsServerUrl } = useAuth();
|
||||
const navigation: ServerUrlNavigationProp = useNavigation<ServerUrlNavigationProp>();
|
||||
const navigation: ServerUrlNavigationProp =
|
||||
useNavigation<ServerUrlNavigationProp>();
|
||||
const [url, setUrl] = useState("https://oneuptime.com");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -23,9 +23,10 @@ export async function storeTokens(tokens: StoredTokens): Promise<void> {
|
||||
}
|
||||
|
||||
export async function getTokens(): Promise<StoredTokens | null> {
|
||||
const credentials: false | Keychain.UserCredentials = await Keychain.getGenericPassword({
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
const credentials: false | Keychain.UserCredentials =
|
||||
await Keychain.getGenericPassword({
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
|
||||
if (!credentials || typeof credentials === "boolean") {
|
||||
cachedAccessToken = null;
|
||||
|
||||
@@ -40,7 +40,9 @@ export async function setThemeMode(mode: ThemeMode): Promise<void> {
|
||||
}
|
||||
|
||||
export async function getBiometricEnabled(): Promise<boolean> {
|
||||
const stored: string | null = await AsyncStorage.getItem(KEYS.BIOMETRIC_ENABLED);
|
||||
const stored: string | null = await AsyncStorage.getItem(
|
||||
KEYS.BIOMETRIC_ENABLED,
|
||||
);
|
||||
return stored === "true";
|
||||
}
|
||||
|
||||
@@ -49,7 +51,9 @@ export async function setBiometricEnabled(enabled: boolean): Promise<void> {
|
||||
}
|
||||
|
||||
export async function getNotificationPreferences(): Promise<NotificationPreferences> {
|
||||
const stored: string | null = await AsyncStorage.getItem(KEYS.NOTIFICATION_PREFS);
|
||||
const stored: string | null = await AsyncStorage.getItem(
|
||||
KEYS.NOTIFICATION_PREFS,
|
||||
);
|
||||
if (stored) {
|
||||
try {
|
||||
return { ...DEFAULT_NOTIFICATION_PREFS, ...JSON.parse(stored) };
|
||||
|
||||
@@ -31,7 +31,8 @@ interface ThemeContextValue {
|
||||
setThemeMode: (mode: ThemeMode) => void;
|
||||
}
|
||||
|
||||
const ThemeContext: React.Context<ThemeContextValue | undefined> = createContext<ThemeContextValue | undefined>(undefined);
|
||||
const ThemeContext: React.Context<ThemeContextValue | undefined> =
|
||||
createContext<ThemeContextValue | undefined>(undefined);
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -40,7 +41,8 @@ interface ThemeProviderProps {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
}: ThemeProviderProps): React.JSX.Element {
|
||||
const systemColorScheme: "light" | "dark" | null | undefined = useColorScheme();
|
||||
const systemColorScheme: "light" | "dark" | null | undefined =
|
||||
useColorScheme();
|
||||
const [themeMode, setThemeModeState] = useState<ThemeMode>("dark");
|
||||
|
||||
// Load persisted theme on mount
|
||||
|
||||
Reference in New Issue
Block a user