Amba-Agent-App/app/(root)/(screens)/sendnotification.tsx
2026-01-16 00:22:35 +03:00

301 lines
11 KiB
TypeScript

import React, { useMemo, useRef, useState } from "react";
import { View, ScrollView, TouchableOpacity } from "react-native";
import { Text } from "~/components/ui/text";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import BackButton from "~/components/ui/backButton";
import { Input } from "~/components/ui/input";
import ModalToast from "~/components/ui/toast";
import { useRecipientsStore } from "~/lib/stores";
import {
BellIcon,
MessageCircle,
LucideSlidersHorizontal,
} from "lucide-react-native";
import BottomSheet from "~/components/ui/bottomSheet";
const getInitials = (name: string) => {
return name
.split(" ")
.map((word) => word.charAt(0).toUpperCase())
.slice(0, 2)
.join("");
};
export default function SendNotificationScreen() {
const { recipients } = useRecipientsStore();
const [searchQuery, setSearchQuery] = useState("");
const [toastVisible, setToastVisible] = useState(false);
const [toastTitle, setToastTitle] = useState("");
const [toastDescription, setToastDescription] = useState<string | undefined>(
undefined
);
const [toastVariant, setToastVariant] = useState<
"success" | "error" | "warning" | "info"
>("info");
const toastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [sheetVisible, setSheetVisible] = useState(false);
const [selectedRecipient, setSelectedRecipient] = useState<any | null>(null);
const showToast = (
title: string,
description?: string,
variant: "success" | "error" | "warning" | "info" = "info"
) => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
setToastTitle(title);
setToastDescription(description);
setToastVariant(variant);
setToastVisible(true);
toastTimeoutRef.current = setTimeout(() => {
setToastVisible(false);
toastTimeoutRef.current = null;
}, 2500);
};
const normalizedSearch = searchQuery.trim().toLowerCase();
const filteredRecipients = useMemo(() => {
const base = recipients || [];
const bySearch = !normalizedSearch
? base
: base.filter((recipient) => {
const name = recipient.fullName.toLowerCase();
const phone = recipient.phoneNumber.toLowerCase();
return (
name.includes(normalizedSearch) || phone.includes(normalizedSearch)
);
});
return bySearch;
}, [recipients, normalizedSearch]);
const handleSendInApp = (name: string) => {
showToast(
"In-app notification",
`We'd notify ${name} in the app (stub).`,
"success"
);
};
const handleSendWhatsApp = (name: string) => {
showToast(
"WhatsApp notification",
`We'd open WhatsApp to message ${name} (stub).`,
"info"
);
};
return (
<ScreenWrapper edges={[]}>
<BackButton />
<View className="flex-1 bg-white">
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 32 }}
showsVerticalScrollIndicator={false}
>
<View className="px-5 pt-4">
<Text className="text-lg font-dmsans-bold text-primary mb-1">
Send notification
</Text>
<Text className="text-sm font-dmsans text-gray-500 mb-4">
Pick who you want to notify about upcoming or recent payments.
</Text>
<View className="w-full mb-4">
<Input
value={searchQuery}
onChangeText={setSearchQuery}
placeholderText="Search clients by name or phone"
containerClassName="w-full"
borderClassName="border-[#D9DBE9] bg-white"
placeholderColor="#7E7E7E"
textClassName="text-[#000] text-sm"
rightIcon={
<LucideSlidersHorizontal color="#9CA3AF" size={18} />
}
/>
</View>
{filteredRecipients.length === 0 && (
<View className="items-center justify-center py-10">
<Text className="text-sm font-dmsans text-gray-400">
No clients found. Add recipients first to send notifications.
</Text>
</View>
)}
{filteredRecipients.length > 0 && (
<View className="space-y-4 mb-4">
{filteredRecipients.map((recipient, index) => {
const initials = getInitials(recipient.fullName);
const lowerName = recipient.fullName.toLowerCase();
const isBusiness =
lowerName.includes("ltd") ||
lowerName.includes("plc") ||
lowerName.includes("inc") ||
lowerName.includes("company");
const clientType = isBusiness ? "Business" : "Individual";
const hasSchedule = index % 2 === 1;
return (
<View
key={recipient.id}
className="bg-white rounded-3xl mb-2 border border-gray-100"
style={{
shadowColor: "#000",
shadowOpacity: 0.03,
shadowRadius: 32,
shadowOffset: { width: 0, height: 10 },
elevation: 3,
}}
>
<View className="px-4 py-4">
<View className="flex-row items-center mb-3">
<View className="w-11 h-11 rounded-full bg-primary items-center justify-center mr-3">
<Text className="text-white text-sm font-dmsans-medium">
{initials}
</Text>
</View>
<View className="flex-1">
<View className="flex-row items-center justify-between">
<Text className="text-[14px] font-dmsans-bold text-gray-900">
{recipient.fullName}
</Text>
<View className="px-2 py-[2px] rounded-md bg-[#FFB668] ml-2">
<Text className="text-[10px] font-dmsans-medium text-white">
{clientType}
</Text>
</View>
</View>
<Text className="text-[11px] font-dmsans text-gray-500 mt-1">
{recipient.phoneNumber}
</Text>
</View>
</View>
<View className="flex-row items-center justify-between mb-3">
<View>
<Text className="text-[11px] font-dmsans text-gray-500">
Notification context
</Text>
<Text className="text-[12px] font-dmsans-medium text-gray-900">
{hasSchedule
? "Upcoming scheduled payment this week"
: "One-off payment reminder"}
</Text>
</View>
{hasSchedule && (
<View className="px-2 py-[2px] rounded-full bg-primary/10">
<Text className="text-[10px] font-dmsans-medium text-primary">
Has schedules
</Text>
</View>
)}
</View>
<View className="mt-2">
<TouchableOpacity
activeOpacity={0.9}
className="flex-row items-center justify-center rounded-2xl bg-primary py-2.5"
onPress={() => {
setSelectedRecipient(recipient);
setSheetVisible(true);
}}
>
<BellIcon color="#FFFFFF" size={18} />
<Text className="text-[13px] font-dmsans-medium text-white ml-1.5">
Send notification
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
})}
</View>
)}
</View>
</ScrollView>
</View>
<BottomSheet
visible={sheetVisible}
onClose={() => {
setSheetVisible(false);
setSelectedRecipient(null);
}}
maxHeightRatio={0.4}
>
{selectedRecipient && (
<View className="w-full px-5 pt-4 pb-6">
<Text className="text-base font-dmsans-bold text-primary mb-1 text-center">
Choose notification type
</Text>
<Text className="text-[13px] font-dmsans text-gray-600 mb-4 text-center">
{`Who: ${selectedRecipient.fullName}`}
</Text>
<View className="space-y-3">
<TouchableOpacity
activeOpacity={0.9}
className="flex-row items-center justify-between rounded-2xl border border-gray-200 bg-white px-4 py-3"
onPress={() => {
handleSendInApp(selectedRecipient.fullName);
setSheetVisible(false);
setSelectedRecipient(null);
}}
>
<View className="flex-row items-center">
<BellIcon color="#105D38" size={20} />
<View className="ml-3">
<Text className="text-[13px] font-dmsans-medium text-gray-900">
In-app notification
</Text>
<Text className="text-[11px] font-dmsans text-gray-500">
Show inside Amba when they open the app.
</Text>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.9}
className="flex-row items-center justify-between rounded-2xl bg-[#25D366]/10 border border-[#25D366]/40 px-4 py-3"
onPress={() => {
handleSendWhatsApp(selectedRecipient.fullName);
setSheetVisible(false);
setSelectedRecipient(null);
}}
>
<View className="flex-row items-center">
<MessageCircle color="#25D366" size={20} />
<View className="ml-3">
<Text className="text-[13px] font-dmsans-medium text-[#128C7E]">
WhatsApp message
</Text>
<Text className="text-[11px] font-dmsans text-[#128C7E]/80">
Open WhatsApp to send them a quick update.
</Text>
</View>
</View>
</TouchableOpacity>
</View>
</View>
)}
</BottomSheet>
<ModalToast
visible={toastVisible}
title={toastTitle}
description={toastDescription}
variant={toastVariant}
/>
</ScreenWrapper>
);
}