UNPKG

omnipay-savings-sdk

Version:

Omnipay Savings SDK

316 lines (315 loc) 18.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const utils_1 = require("../../utils"); const useHandleChangeNotrim_1 = __importDefault(require("../../hooks/useHandleChangeNotrim")); const Input_1 = __importDefault(require("../Input")); const SDKConfigContext_1 = require("../../contexts/SDKConfigContext"); const react_query_1 = require("@tanstack/react-query"); const actions_1 = require("../../services/actions"); const ToastProvider_1 = require("../../contexts/ToastProvider"); const Notification_1 = __importDefault(require("../Feedback/Notification")); const savingsConfirmation_1 = __importDefault(require("../Modals/savingsConfirmation")); const SavingsFormLayout_1 = __importDefault(require("./shared/SavingsFormLayout")); const CommonSavingsFields_1 = __importDefault(require("./shared/CommonSavingsFields")); const InterestRateTiers_1 = __importDefault(require("../Common/InterestRateTiers")); const savingsFormUtils_1 = require("./shared/savingsFormUtils"); const saveAsYouCollectForm_1 = require("./saveAsYouCollectForm"); const LockedSavingsForm = (props) => { var _a, _b, _c; const { title, goBack, navigate, replace, savingsOption, suggestedPlan, editMode, existingSavingsData, } = props; const minimumLockDuration = (savingsOption === null || savingsOption === void 0 ? void 0 : savingsOption.minimumLockDuration) || 3; const minimumLockDurationDays = minimumLockDuration * 30; // Convert months to days for minimum const [switchInterest, setSwitchInterest] = (0, react_1.useState)(editMode ? (existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.isInterestDisabled) || false : false); const [lockDurationDays, setLockDurationDays] = (0, react_1.useState)(0); const [currentInterestRate, setCurrentInterestRate] = (0, react_1.useState)((savingsOption === null || savingsOption === void 0 ? void 0 : savingsOption.interestRate) || 0); const [selectedTier, setSelectedTier] = (0, react_1.useState)(null); const { showToast } = (0, ToastProvider_1.useToast)(); const { apiKey, primaryColor } = (0, SDKConfigContext_1.useSDKConfig)(); const queryClient = (0, react_query_1.useQueryClient)(); const [formStatus, setFormStatus] = (0, react_1.useState)('idle'); const [showConfirmation, setShowConfirmation] = (0, react_1.useState)(false); const [confirmationPayload, setConfirmationPayload] = (0, react_1.useState)(null); // Query for wallet balance const { data: walletBalanceData, isLoading: isLoadingBalance } = (0, react_query_1.useQuery)({ queryKey: ['walletBalance', apiKey], queryFn: () => (0, actions_1.getWalletBalance)(apiKey), retry: 2, enabled: !!apiKey, }); // Query for interest rate tiers const { data: interestRateTiersData, isLoading: isLoadingTiers } = (0, react_query_1.useQuery)({ queryKey: ['lockedSavingsInterestRateTiers', apiKey], queryFn: () => (0, actions_1.getLockedSavingsInterestRateTiers)(apiKey), retry: 2, enabled: !!apiKey, }); // Ref for scrolling and state for tracking section positions const scrollViewRef = (0, react_1.useRef)(null); const [sectionPositions, setSectionPositions] = (0, react_1.useState)({}); const [errors, setErrors] = (0, useHandleChangeNotrim_1.default)({ name: '', amount: '', narration: '', savingsColor: '', lockDurationDays: '', tierSelection: '', }); const [details, setDetails] = (0, useHandleChangeNotrim_1.default)({ name: editMode ? (existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.accountName) || '' : '', amount: editMode ? ((_a = existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.savingTarget) === null || _a === void 0 ? void 0 : _a.toString()) || '' : '', narration: editMode ? (existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.narration) || '' : '', savingsColor: (0, savingsFormUtils_1.getValidSavingsColor)(existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.savingsPlanColor, editMode), }); const { name, amount, narration, savingsColor } = details; // Get wallet balance values const useableWalletBalance = Number((_c = (_b = walletBalanceData === null || walletBalanceData === void 0 ? void 0 : walletBalanceData.data) === null || _b === void 0 ? void 0 : _b.availableBalance) !== null && _c !== void 0 ? _c : 0); const lockAmountValue = Number(amount); // Get interest rate tiers const interestRateTiers = interestRateTiersData || []; // Function to calculate current interest rate based on selected days const calculateCurrentInterestRate = (days) => { if (!interestRateTiers.length) return (savingsOption === null || savingsOption === void 0 ? void 0 : savingsOption.interestRate) || 0; const matchingTier = interestRateTiers.find(tier => days >= tier.minDays && days <= tier.maxDays && tier.isActive); return matchingTier ? matchingTier.interestRate : (savingsOption === null || savingsOption === void 0 ? void 0 : savingsOption.interestRate) || 0; }; // Update interest rate when days change react_1.default.useEffect(() => { if (lockDurationDays > 0) { const newRate = calculateCurrentInterestRate(lockDurationDays); setCurrentInterestRate(newRate); } }, [lockDurationDays, interestRateTiers]); // Handle tier selection from InterestRateTiers component const handleTierSelection = (tier, isManualSelection = true) => { setSelectedTier(tier); setErrors('tierSelection', ''); // Only reset lock duration when manually selecting from dropdown // Don't reset when auto-selecting based on entered days if (isManualSelection && (lockDurationDays === 0 || lockDurationDays < tier.minDays || lockDurationDays > tier.maxDays)) { setLockDurationDays(0); setErrors('lockDurationDays', ''); } }; function updateSavingsColor(value) { setDetails('savingsColor', value); setErrors('savingsColor', ''); } function updateName(value) { setDetails('name', value); setErrors('name', ''); } function updateAmount(value) { setDetails('amount', value); setErrors('amount', ''); // Validate against wallet balance if (value > useableWalletBalance) { setErrors('amount', `Your balance is ${(0, utils_1.formatAsCurrency)(useableWalletBalance)}`); } else { setErrors('amount', ''); } } function updateNarration(value) { setDetails('narration', value); setErrors('narration', ''); } // Helper function to capture section positions const handleSectionLayout = (sectionKey, event) => { const { y } = event.nativeEvent.layout; setSectionPositions(prev => (Object.assign(Object.assign({}, prev), { [sectionKey]: y }))); }; // Helper function to scroll to first error const scrollToError = (errorField) => { const targetPosition = sectionPositions[errorField]; if (targetPosition !== undefined && scrollViewRef.current) { scrollViewRef.current.scrollTo({ y: Math.max(0, targetPosition - 50), animated: true, }); } }; // Validation function const validateForm = () => { const commonErrors = (0, savingsFormUtils_1.validateCommonFields)(name, amount, savingsColor); // Calculate maximum days from all tiers const maxDaysAvailable = interestRateTiers.length > 0 ? Math.max(...interestRateTiers.map(tier => tier.maxDays)) : 365; const lockedErrors = (0, savingsFormUtils_1.validateLockedSavings)(lockDurationDays, minimumLockDurationDays, editMode, selectedTier, maxDaysAvailable); // Add wallet balance validation if (!editMode && lockAmountValue > useableWalletBalance) { commonErrors.push({ field: 'amount', message: `Insufficient balance. Maximum amount is ${(0, utils_1.formatAsCurrency)(useableWalletBalance)}`, }); } return [...commonErrors, ...lockedErrors]; }; const { mutate: savingsMutate, isPending: isLoading, data, } = (0, react_query_1.useMutation)({ mutationFn: (variables) => { if (editMode) { return (0, actions_1.updateSavingsAccount)(apiKey, variables); } else { return (0, actions_1.createSavingsAccount)(apiKey, variables); } }, onSuccess: res => { var _a; if (res.error) { showToast({ type: 'error', message: (_a = res === null || res === void 0 ? void 0 : res.error) !== null && _a !== void 0 ? _a : `An error occurred while ${editMode ? 'updating' : 'creating'} your savings account. Please try again.`, duration: 3000, }); return; } if (res === null || res === void 0 ? void 0 : res.success) { // Invalidate relevant queries to fetch latest data queryClient.invalidateQueries({ queryKey: ['savingsListByCustomerId', apiKey], }); queryClient.invalidateQueries({ queryKey: ['totalAccountTransactionsByCustomerId', apiKey], }); queryClient.invalidateQueries({ queryKey: ['customerAccountSummary', apiKey], }); queryClient.invalidateQueries({ queryKey: ['allSavingsCategories', apiKey], }); // Close the confirmation modal and clear payload setShowConfirmation(false); setConfirmationPayload(null); setFormStatus('success'); } }, onError: err => { console.error('Mutation failed:', err); }, }); function onSuccessNotificationClose() { setFormStatus('idle'); replace('plan', { planId: data === null || data === void 0 ? void 0 : data.responseData.savingsId, planName: data === null || data === void 0 ? void 0 : data.responseData.accountName, }); } const handleSubmit = async () => { var _a, _b; react_native_1.Keyboard.dismiss(); // Validate form and get all errors const validationErrors = validateForm(); if (validationErrors.length > 0) { // Set all errors validationErrors.forEach(({ field, message }) => { setErrors(field, message); }); // Scroll to first error after a brief delay to allow error state to update setTimeout(() => { scrollToError(validationErrors[0].field); }, 100); return; } // Form is valid, proceed with submission let payload; if (editMode) { payload = { savingsId: (existingSavingsData === null || existingSavingsData === void 0 ? void 0 : existingSavingsData.savingsId) || '', planId: (_a = suggestedPlan === null || suggestedPlan === void 0 ? void 0 : suggestedPlan.id) !== null && _a !== void 0 ? _a : 0, accountName: name.trim(), savingTarget: (0, utils_1.correctFloatPoint)(Number(amount) * 100), savingsCategoryId: savingsOption.id, narration: narration.trim(), autoSave: false, isInterestDisabled: switchInterest, savingsPlanColor: savingsColor, }; } else { // Calculate end date based on lock duration in days const startDate = new Date(); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + lockDurationDays); payload = { planId: (_b = suggestedPlan === null || suggestedPlan === void 0 ? void 0 : suggestedPlan.id) !== null && _b !== void 0 ? _b : 0, accountName: name.trim(), savingTarget: (0, utils_1.correctFloatPoint)(Number(amount) * 100), savingsCategoryId: savingsOption.id, narration: narration.trim(), autoSave: false, isInterestDisabled: switchInterest, savingsPlanColor: savingsColor, startDate: startDate.toISOString(), endDate: endDate.toISOString(), }; } if ((suggestedPlan === null || suggestedPlan === void 0 ? void 0 : suggestedPlan.name) === name) { payload.planId = suggestedPlan === null || suggestedPlan === void 0 ? void 0 : suggestedPlan.id; } // In edit mode, check if there are any actual changes if (editMode && existingSavingsData) { const hasChanges = (0, savingsFormUtils_1.checkForChanges)(payload, existingSavingsData); if (!hasChanges) { // No changes detected, show a toast and return showToast({ type: 'info', message: 'No changes detected. Your savings account is already up to date.', duration: 3000, }); return; } } // Show confirmation modal instead of directly submitting setConfirmationPayload(payload); setShowConfirmation(true); }; const handleConfirmSubmission = async () => { if (confirmationPayload) { savingsMutate(confirmationPayload); } }; const handleCancelConfirmation = () => { setShowConfirmation(false); setConfirmationPayload(null); }; return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(SavingsFormLayout_1.default, Object.assign({ title: title, onBack: goBack, onSubmit: handleSubmit, scrollViewRef: scrollViewRef, suggestedPlanName: suggestedPlan === null || suggestedPlan === void 0 ? void 0 : suggestedPlan.name }, { children: [(0, jsx_runtime_1.jsx)(CommonSavingsFields_1.default, { savingsColor: savingsColor, onColorChange: updateSavingsColor, colorError: errors.savingsColor, primaryColor: primaryColor, name: name, onNameChange: updateName, nameError: errors.name, amount: amount, onAmountChange: updateAmount, amountError: errors.amount, amountLabel: "Lock Amount", showAmountField: true, walletBalance: useableWalletBalance, showBalance: true, isLoadingBalance: isLoadingBalance, onColorLayout: event => handleSectionLayout('savingsColor', event), onNameLayout: event => handleSectionLayout('name', event), onAmountLayout: event => handleSectionLayout('amount', event) }), (0, jsx_runtime_1.jsx)(InterestRateTiers_1.default, { tiers: interestRateTiers, selectedDays: lockDurationDays, currentRate: switchInterest ? 0 : currentInterestRate, onTierSelect: handleTierSelection, onDaysChange: (days) => { setLockDurationDays(days); setErrors('lockDurationDays', ''); }, primaryColor: primaryColor, isLoading: isLoadingTiers, minimumLockDurationDays: minimumLockDurationDays, durationError: errors.lockDurationDays, tierSelectionError: errors.tierSelection, onSectionLayout: event => handleSectionLayout('lockDurationDays', event), lockAmount: lockAmountValue, isInterestDisabled: switchInterest, selectedTier: selectedTier }), (0, jsx_runtime_1.jsx)(Input_1.default, { value: narration, label: "Narration", placeholder: "Describe", keyboardType: "default", autoCompleteType: "off", returnKeyType: "done", onChange: (value) => { updateNarration(String(value)); }, errorMsg: errors.narration || '', onEndEditing: () => { }, onFocus: () => { }, containerStyle: { marginTop: (0, utils_1.ms)(-24) } }), (0, jsx_runtime_1.jsx)(saveAsYouCollectForm_1.SwitchFormInput, { title: "Switch off Interest", onPress: () => setSwitchInterest(!switchInterest), onValueChange: () => setSwitchInterest(!switchInterest), check: switchInterest, setCheck: setSwitchInterest })] })), formStatus === 'success' && ((0, jsx_runtime_1.jsx)(Notification_1.default, { title: editMode ? 'Savings Updated Successfully' : 'Savings Created Successfully', description: editMode ? 'Your savings account has been updated successfully' : 'Your savings account has been created successfully', image: utils_1.Images.successIcon, buttonText: "View Details", onButtonPress: onSuccessNotificationClose })), confirmationPayload && ((0, jsx_runtime_1.jsx)(savingsConfirmation_1.default, { visible: showConfirmation, editMode: editMode || false, newData: confirmationPayload, existingData: existingSavingsData, onConfirm: handleConfirmSubmission, onCancel: handleCancelConfirmation, isLoading: isLoading, savingsCategoryName: savingsOption.name, isLockedCategory: true }))] })); }; exports.default = LockedSavingsForm;