391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
import React, { useEffect, useRef, useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
Image,
|
|
ScrollView,
|
|
Share,
|
|
TouchableOpacity,
|
|
TextInput,
|
|
} from "react-native";
|
|
import { Button } from "~/components/ui/button";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import { router, useLocalSearchParams } from "expo-router";
|
|
import { ROUTES } from "~/lib/routes";
|
|
import { SuccessIcon } from "~/components/ui/icons";
|
|
import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile";
|
|
import ScreenWrapper from "~/components/ui/ScreenWrapper";
|
|
import ModalToast from "~/components/ui/toast";
|
|
import { useTranslation } from "react-i18next";
|
|
import LottieView from "lottie-react-native";
|
|
import { Icons } from "~/assets/icons";
|
|
|
|
export default function TaskComp() {
|
|
const { t } = useTranslation();
|
|
const params = useLocalSearchParams<{
|
|
message?: string;
|
|
amount?: string;
|
|
recipientName?: string;
|
|
recipientPhoneNumber?: string;
|
|
flowType?: string;
|
|
}>();
|
|
|
|
const { wallet } = useAuthWithProfile();
|
|
|
|
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 isEventTicketFlow = params.flowType === "event_ticket";
|
|
|
|
const [ratingSheetVisible, setRatingSheetVisible] = useState(false);
|
|
const [rating, setRating] = useState(0);
|
|
const [selectedIssues, setSelectedIssues] = useState<string[]>([]);
|
|
const [comments, setComments] = useState("");
|
|
const [selectedPurpose, setSelectedPurpose] = useState<string | null>(null);
|
|
const [isPurposeDropdownOpen, setIsPurposeDropdownOpen] = useState(false);
|
|
const [otherPurpose, setOtherPurpose] = useState("");
|
|
|
|
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);
|
|
};
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (toastTimeoutRef.current) {
|
|
clearTimeout(toastTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
const toggleIssue = (key: string) => {
|
|
setSelectedIssues((prev) =>
|
|
prev.includes(key) ? prev.filter((i) => i !== key) : [...prev, key]
|
|
);
|
|
};
|
|
|
|
const handleSendAgain = () => {
|
|
const balance = wallet?.balance;
|
|
if (balance === undefined) {
|
|
showToast(
|
|
t("taskcomp.toastErrorTitle"),
|
|
t("taskcomp.toastNoBalance"),
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
if (balance < 1000) {
|
|
showToast(
|
|
t("taskcomp.toastErrorTitle"),
|
|
t("taskcomp.toastMinError"),
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Navigate to home first, then to send money to prevent going back to success page
|
|
router.replace(ROUTES.HOME);
|
|
// Use setTimeout to ensure home navigation completes before send money navigation
|
|
router.push(ROUTES.SEND_OR_REQUEST_MONEY);
|
|
};
|
|
|
|
const handleSubmitRating = () => {
|
|
setRatingSheetVisible(false);
|
|
router.replace(ROUTES.HOME);
|
|
};
|
|
|
|
const handleShare = async () => {
|
|
try {
|
|
const shareMessage = params.message
|
|
? t("taskcomp.shareMessageWithParam", { message: params.message })
|
|
: t("taskcomp.shareMessageDefault");
|
|
|
|
const result = await Share.share({
|
|
message: shareMessage,
|
|
title: t("taskcomp.shareTitle"),
|
|
});
|
|
|
|
if (result.action === Share.sharedAction) {
|
|
// Content was shared
|
|
console.log("Content shared successfully");
|
|
} else if (result.action === Share.dismissedAction) {
|
|
// Share dialog was dismissed
|
|
console.log("Share dialog dismissed");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error sharing:", error);
|
|
showToast(
|
|
t("taskcomp.toastErrorTitle"),
|
|
t("taskcomp.toastShareError"),
|
|
"error"
|
|
);
|
|
}
|
|
};
|
|
return (
|
|
<ScreenWrapper edges={[]}>
|
|
<View className="flex-1 w-full">
|
|
<View className="w-full flex-1">
|
|
<View className="flex-1 justify-center w-full">
|
|
{/* <View className="h-24" /> */}
|
|
<View className="flex items-center space-y-10">
|
|
<LottieView
|
|
source={require("../../../assets/lottie/Success.json")}
|
|
autoPlay
|
|
loop={false}
|
|
style={{ width: 260, height: 260 }}
|
|
/>
|
|
{/* <View className="h-10" /> */}
|
|
{params.amount ? (
|
|
<View className="px-5">
|
|
<Text className="text-secondary font-dmsans-bold text-3xl">
|
|
{params.amount}
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<></>
|
|
)}
|
|
<View className="h-4" />
|
|
<View className="px-5">
|
|
<Text className="text-lg font-regular text-gray-400 font-dmsans text-center">
|
|
{params.message || t("taskcomp.successDescription")}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Bottom action buttons */}
|
|
<View className="w-full px-5 pb-8 pt-4" style={{ gap: 12 }}>
|
|
{isEventTicketFlow ? (
|
|
<>
|
|
<Button
|
|
className="bg-primary rounded-full"
|
|
onPress={() => router.replace(ROUTES.HOME)}
|
|
>
|
|
<Text className="font-dmsans text-white">
|
|
{t("taskcomp.goHomeButton")}
|
|
</Text>
|
|
</Button>
|
|
|
|
<Button
|
|
className="bg-secondary rounded-full"
|
|
onPress={handleShare}
|
|
>
|
|
<Text className="font-dmsans text-white">
|
|
{t("taskcomp.shareButton")}
|
|
</Text>
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Button
|
|
className="bg-primary rounded-full"
|
|
onPress={handleSendAgain}
|
|
>
|
|
<Text className="font-dmsans text-white">
|
|
{t("taskcomp.sendAgainButton")}
|
|
</Text>
|
|
</Button>
|
|
|
|
<Button
|
|
className="bg-secondary rounded-full"
|
|
onPress={handleShare}
|
|
>
|
|
<Text className="font-dmsans text-white">
|
|
{t("taskcomp.shareButton")}
|
|
</Text>
|
|
</Button>
|
|
|
|
<Button
|
|
className="bg-white border border-dashed border-[#FBBF77] rounded-full"
|
|
onPress={() => setRatingSheetVisible(true)}
|
|
>
|
|
<Text className="font-dmsans text-black">
|
|
{t("taskcomp.goHomeButton")}
|
|
</Text>
|
|
</Button>
|
|
</>
|
|
)}
|
|
</View>
|
|
</View>
|
|
{!isEventTicketFlow && ratingSheetVisible && (
|
|
<View className="absolute inset-0 justify-end bg-black/40">
|
|
<TouchableOpacity
|
|
className="flex-1"
|
|
activeOpacity={1}
|
|
onPress={() => setRatingSheetVisible(false)}
|
|
/>
|
|
<View className="bg-white rounded-t-3xl px-5 pt-8 pb-6 relative">
|
|
<View className="absolute -top-7 self-center w-14 h-14 rounded-full bg-[#105D38] items-center justify-center">
|
|
<Image
|
|
source={Icons.bill}
|
|
style={{
|
|
width: 32,
|
|
height: 32,
|
|
resizeMode: "contain",
|
|
tintColor: "#fff",
|
|
}}
|
|
/>
|
|
</View>
|
|
<View className="items-center mb-4">
|
|
<Text className="text-xl font-dmsans-bold text-primary mt-3">
|
|
{t("taskcomp.ratingTitle")}
|
|
</Text>
|
|
<Text className="text-sm text-gray-500 mt-1 text-center">
|
|
{t("taskcomp.ratingSubtitle")}
|
|
</Text>
|
|
</View>
|
|
|
|
<View className="mb-4">
|
|
<Text className="text-sm font-dmsans-medium text-gray-700 mb-2">
|
|
{t("taskcomp.ratingOverallLabel")}
|
|
</Text>
|
|
<View className="flex-row justify-center gap-3">
|
|
{[1, 2, 3, 4, 5].map((star) => (
|
|
<TouchableOpacity
|
|
key={star}
|
|
onPress={() => setRating(star)}
|
|
activeOpacity={0.8}
|
|
>
|
|
<Text
|
|
className={`text-3xl font-dmsans ${
|
|
star <= rating ? "text-primary" : "text-gray-300"
|
|
}`}
|
|
>
|
|
★
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
<View className="mb-4">
|
|
<Text className="text-sm font-dmsans-medium text-gray-700 mb-2">
|
|
{t("taskcomp.ratingPurposeLabel")}
|
|
</Text>
|
|
<View>
|
|
<TouchableOpacity
|
|
activeOpacity={0.8}
|
|
onPress={() => setIsPurposeDropdownOpen((prev) => !prev)}
|
|
className="border border-gray-300 rounded-2xl px-3 py-2 flex-row items-center justify-between"
|
|
>
|
|
<Text className="text-sm text-gray-900 font-dmsans">
|
|
{selectedPurpose
|
|
? t(
|
|
{
|
|
family: "taskcomp.ratingPurposeFamily",
|
|
medical: "taskcomp.ratingPurposeMedical",
|
|
loan: "taskcomp.ratingPurposeLoan",
|
|
purchase: "taskcomp.ratingPurposePurchase",
|
|
other: "taskcomp.ratingPurposeOther",
|
|
}[
|
|
selectedPurpose as
|
|
| "family"
|
|
| "medical"
|
|
| "loan"
|
|
| "purchase"
|
|
| "other"
|
|
]
|
|
)
|
|
: t("taskcomp.ratingPurposePlaceholder")}
|
|
</Text>
|
|
<Text className="text-lg text-gray-400">▾</Text>
|
|
</TouchableOpacity>
|
|
{isPurposeDropdownOpen && (
|
|
<View className="mt-2 border border-gray-200 rounded-2xl bg-white overflow-hidden">
|
|
{[
|
|
{
|
|
key: "family",
|
|
labelKey: "taskcomp.ratingPurposeFamily",
|
|
},
|
|
{
|
|
key: "medical",
|
|
labelKey: "taskcomp.ratingPurposeMedical",
|
|
},
|
|
{ key: "loan", labelKey: "taskcomp.ratingPurposeLoan" },
|
|
{
|
|
key: "purchase",
|
|
labelKey: "taskcomp.ratingPurposePurchase",
|
|
},
|
|
{ key: "other", labelKey: "taskcomp.ratingPurposeOther" },
|
|
].map((option) => (
|
|
<TouchableOpacity
|
|
key={option.key}
|
|
activeOpacity={0.8}
|
|
className="px-3 py-2"
|
|
onPress={() => {
|
|
setSelectedPurpose(option.key);
|
|
setIsPurposeDropdownOpen(false);
|
|
}}
|
|
>
|
|
<Text className="text-sm text-gray-800 font-dmsans">
|
|
{t(option.labelKey as any)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{selectedPurpose === "other" && (
|
|
<View className="mb-4">
|
|
<Text className="text-sm font-dmsans-medium text-gray-700 mb-2">
|
|
{t("taskcomp.ratingOtherLabel")}
|
|
</Text>
|
|
<View className="border border-gray-300 rounded-2xl px-3 py-2 min-h-[60px]">
|
|
<TextInput
|
|
value={otherPurpose}
|
|
onChangeText={setOtherPurpose}
|
|
placeholder={t("taskcomp.ratingOtherPlaceholder")}
|
|
placeholderTextColor="#9CA3AF"
|
|
multiline
|
|
className="text-sm text-gray-900 font-dmsans"
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
<Button
|
|
className="bg-primary rounded-full"
|
|
onPress={handleSubmitRating}
|
|
>
|
|
<Text className="font-dmsans text-white">
|
|
{t("taskcomp.ratingSubmitButton")}
|
|
</Text>
|
|
</Button>
|
|
</View>
|
|
</View>
|
|
)}
|
|
<ModalToast
|
|
visible={toastVisible}
|
|
title={toastTitle}
|
|
description={toastDescription}
|
|
variant={toastVariant}
|
|
/>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|