135 lines
4.0 KiB
TypeScript
135 lines
4.0 KiB
TypeScript
import React, { useCallback, useEffect, useState } from "react";
|
|
import { View, ActivityIndicator, FlatList, RefreshControl } from "react-native";
|
|
import { Text } from "@/components/ui/text";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { StandardHeader } from "@/components/StandardHeader";
|
|
import { api } from "@/lib/api";
|
|
import { EmptyState } from "@/components/EmptyState";
|
|
|
|
type NotificationItem = {
|
|
id: string;
|
|
title?: string;
|
|
body?: string;
|
|
message?: string;
|
|
createdAt?: string;
|
|
read?: boolean;
|
|
};
|
|
|
|
export default function NotificationsScreen() {
|
|
const [items, setItems] = useState<NotificationItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [page, setPage] = useState(1);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
|
|
const fetchNotifications = useCallback(
|
|
async (pageNum: number, mode: "initial" | "refresh" | "more") => {
|
|
try {
|
|
if (mode === "initial") setLoading(true);
|
|
if (mode === "refresh") setRefreshing(true);
|
|
if (mode === "more") setLoadingMore(true);
|
|
|
|
const res = await (api as any).notifications.getAll({
|
|
query: { page: pageNum, limit: 20 },
|
|
});
|
|
|
|
const next = (res?.data ?? []) as NotificationItem[];
|
|
if (mode === "more") {
|
|
setItems((prev) => [...prev, ...next]);
|
|
} else {
|
|
setItems(next);
|
|
}
|
|
|
|
setHasMore(Boolean(res?.meta?.hasNextPage));
|
|
setPage(pageNum);
|
|
} catch (e) {
|
|
setHasMore(false);
|
|
} finally {
|
|
setLoading(false);
|
|
setRefreshing(false);
|
|
setLoadingMore(false);
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
useEffect(() => {
|
|
fetchNotifications(1, "initial");
|
|
}, [fetchNotifications]);
|
|
|
|
const onRefresh = () => fetchNotifications(1, "refresh");
|
|
const onEndReached = () => {
|
|
if (!loading && !loadingMore && hasMore) fetchNotifications(page + 1, "more");
|
|
};
|
|
|
|
const renderItem = ({ item }: { item: NotificationItem }) => {
|
|
const message = item.body ?? item.message ?? "";
|
|
const time = item.createdAt
|
|
? new Date(item.createdAt).toLocaleString()
|
|
: "";
|
|
|
|
return (
|
|
<Card className="mb-2">
|
|
<CardContent className="py-3">
|
|
<Text className="font-semibold text-foreground">
|
|
{item.title ?? "Notification"}
|
|
</Text>
|
|
{message ? (
|
|
<Text className="text-muted-foreground mt-1 text-sm">{message}</Text>
|
|
) : null}
|
|
{time ? (
|
|
<Text className="text-muted-foreground mt-1 text-xs">{time}</Text>
|
|
) : null}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<StandardHeader
|
|
showBack
|
|
title="Notifications"
|
|
rightAction="notificationsSettings"
|
|
/>
|
|
|
|
{loading ? (
|
|
<View className="flex-1 items-center justify-center">
|
|
<ActivityIndicator />
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={items}
|
|
keyExtractor={(i) => i.id}
|
|
renderItem={renderItem}
|
|
contentContainerStyle={{ padding: 16, paddingBottom: 32 }}
|
|
onEndReached={onEndReached}
|
|
onEndReachedThreshold={0.4}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
}
|
|
ListEmptyComponent={
|
|
<View className="px-[16px] py-6">
|
|
<EmptyState
|
|
title="No notifications"
|
|
description="You don't have any notifications yet."
|
|
hint="Pull to refresh to check for new notifications."
|
|
previewLines={3}
|
|
/>
|
|
</View>
|
|
}
|
|
ListFooterComponent={
|
|
loadingMore ? (
|
|
<View className="py-4">
|
|
<ActivityIndicator />
|
|
</View>
|
|
) : null
|
|
}
|
|
/>
|
|
)}
|
|
</ScreenWrapper>
|
|
);
|
|
}
|