omnipay-savings-sdk
Version:
Omnipay Savings SDK
316 lines (315 loc) • 18.7 kB
JavaScript
;
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;