301 lines
11 KiB
TypeScript
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>
|
|
);
|
|
}
|