import React, { useState, useEffect } from 'react'; import { View, Text, TouchableOpacity, FlatList } from 'react-native'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { MyTextInput, MyButton, tw, AppContainer , DateTimePickerMod, Checkbox, BottomDialog, MyText } from 'common-ui'; import { trpc } from '../trpc-client'; import { CreateCouponPayload } from 'common-ui/shared-types'; const USERS_PAGE_SIZE = 10; interface CouponFormProps { onSubmit: (values: CreateCouponPayload & { isReservedCoupon?: boolean }) => void; isLoading: boolean; initialValues?: Partial; } const couponValidationSchema = Yup.object().shape({ isReservedCoupon: Yup.boolean().optional(), couponCode: Yup.string() .required('Coupon code is required') .min(3, 'Coupon code must be at least 3 characters') .max(50, 'Coupon code cannot exceed 50 characters') .matches(/^[A-Z0-9_-]+$/, 'Coupon code can only contain uppercase letters, numbers, underscores, and hyphens'), discountPercent: Yup.number() .min(0, 'Must be positive') .max(100, 'Cannot exceed 100%') .optional(), flatDiscount: Yup.number() .min(0, 'Must be positive') .optional(), minOrder: Yup.number().min(0, 'Must be positive').optional(), maxValue: Yup.number().min(0, 'Must be positive').optional(), validTill: Yup.date().optional(), maxLimitForUser: Yup.number().min(1, 'Must be at least 1').optional(), exclusiveApply: Yup.boolean().optional(), isUserBased: Yup.boolean(), isApplyForAll: Yup.boolean(), applicableUsers: Yup.array().of(Yup.number()).optional(), }).test('discount-validation', 'Must provide exactly one discount type with valid value', function(value) { const { discountPercent, flatDiscount } = value; const hasPercent = discountPercent !== undefined && discountPercent > 0; const hasFlat = flatDiscount !== undefined && flatDiscount > 0; if (hasPercent && hasFlat) { return this.createError({ message: 'Cannot have both percentage and flat discount' }); } if (!hasPercent && !hasFlat) { return this.createError({ message: 'Must provide either percentage or flat discount' }); } return true; }); export default function CouponForm({ onSubmit, isLoading, initialValues }: CouponFormProps) { // User dropdown states const [userSearchQuery, setUserSearchQuery] = useState(''); const [userOffset, setUserOffset] = useState(0); const [allUsers, setAllUsers] = useState<{ id: number; name: string; mobile: string | null }[]>([]); const [hasMoreUsers, setHasMoreUsers] = useState(true); const [usersDropdownOpen, setUsersDropdownOpen] = useState(false); const { data: usersData, isFetching: isFetchingUsers } = trpc.admin.coupon.getUsersMiniInfo.useQuery( { search: userSearchQuery, limit: USERS_PAGE_SIZE, offset: userOffset }, { enabled: usersDropdownOpen } ); useEffect(() => { if (usersData?.users) { if (userOffset === 0) { setAllUsers(usersData.users); } else { setAllUsers(prev => [...prev, ...usersData.users]); } setHasMoreUsers(usersData.users.length === USERS_PAGE_SIZE); } }, [usersData, userOffset]); useEffect(() => { setUserOffset(0); setHasMoreUsers(true); }, [userSearchQuery]); useEffect(() => { if (usersDropdownOpen) { setUserOffset(0); setAllUsers([]); setHasMoreUsers(true); setUserSearchQuery(''); } }, [usersDropdownOpen]); // User search functionality will be inside Formik const defaultValues: CreateCouponPayload & { isReservedCoupon?: boolean } = { couponCode: '', isUserBased: false, isApplyForAll: false, targetUsers: [], discountPercent: undefined, flatDiscount: undefined, minOrder: undefined, maxValue: undefined, validTill: undefined, maxLimitForUser: undefined, productIds: undefined, applicableUsers: [], applicableProducts: [], exclusiveApply: false, isReservedCoupon: false, }; return ( {({ values, errors, touched, setFieldValue, handleSubmit }) => { const toggleUserSelection = (userId: number) => { const current = values.applicableUsers || []; const newSelection = current.includes(userId) ? current.filter(id => id !== userId) : [...current, userId]; console.log('Toggling user:', userId, 'New selection:', newSelection); setFieldValue('applicableUsers', newSelection); }; const isReserved = (values as any).isReservedCoupon; return ( {/* Is Reserved Coupon Checkbox */} setFieldValue('isReservedCoupon', !(values as any).isReservedCoupon)} /> Is Reserved Coupon {/* Coupon Code */} setFieldValue('couponCode', text.toUpperCase())} keyboardType="default" autoCapitalize="characters" error={!!(touched.couponCode && errors.couponCode)} /> {/* Discount Type Selection */} Discount Type * { setFieldValue('discountPercent', values.discountPercent || 0); setFieldValue('flatDiscount', undefined); }} style={tw`flex-1 p-3 border rounded-lg mr-2 ${ values.discountPercent !== undefined ? 'border-blue-500' : 'border-gray-300' }`} > Percentage { setFieldValue('flatDiscount', values.flatDiscount || 0); setFieldValue('discountPercent', undefined); }} style={tw`flex-1 p-3 border rounded-lg ${ values.flatDiscount !== undefined ? 'border-blue-500' : 'border-gray-300' }`} > Flat Amount {/* Discount Value */} {values.discountPercent !== undefined && ( setFieldValue('discountPercent', parseFloat(text) || 0)} keyboardType="numeric" error={!!(touched.discountPercent && errors.discountPercent)} /> )} {values.flatDiscount !== undefined && ( setFieldValue('flatDiscount', parseFloat(text) || 0)} keyboardType="numeric" error={!!(touched.flatDiscount && errors.flatDiscount)} /> )} {/* Minimum Order */} setFieldValue('minOrder', parseFloat(text) || undefined)} keyboardType="numeric" error={!!(touched.minOrder && errors.minOrder)} /> {/* Maximum Discount Value */} setFieldValue('maxValue', parseFloat(text) || undefined)} keyboardType="numeric" error={!!(touched.maxValue && errors.maxValue)} /> {/* Validity Period */} Valid Till { setFieldValue('validTill', date?.toISOString()) }} /> {/* Usage Limit */} setFieldValue('maxLimitForUser', parseInt(text) || undefined)} keyboardType="numeric" error={!!(touched.maxLimitForUser && errors.maxLimitForUser)} /> {/* Exclusive Apply */} Exclusive Apply setFieldValue('exclusiveApply', !values.exclusiveApply)} style={tw`flex-row items-center`} > {values.exclusiveApply && } Exclusive coupon (cannot be combined with other coupons) {/* Target Audience */} Target Audience {isReserved ? '(Disabled for Reserved Coupons)' : ''} { setFieldValue('isApplyForAll', true); setFieldValue('isUserBased', false); setFieldValue('targetUsers', []); }} style={tw`flex-1 p-3 border rounded-lg mr-2 ${ values.isApplyForAll ? 'border-blue-500' : 'border-gray-300' } ${isReserved ? 'opacity-50' : ''}`} > All Users { setFieldValue('isUserBased', true); setFieldValue('isApplyForAll', false); }} style={tw`flex-1 p-3 border rounded-lg ${ values.isUserBased ? 'border-blue-500' : 'border-gray-300' } ${isReserved ? 'opacity-50' : ''}`} > Specific User {/* Applicable User Selection */} Applicable Users (Optional) setUsersDropdownOpen(true)} style={tw`border border-gray-300 rounded p-3 bg-white ${isReserved ? 'opacity-50' : ''}`} > {values.applicableUsers?.length ? `${values.applicableUsers.length} users selected` : 'Select users...'} setUsersDropdownOpen(false)}> Select Applicable Users item.id.toString()} renderItem={({ item }) => ( toggleUserSelection(item.id)} style={tw`flex-row items-center p-3 border-b border-gray-200`} > toggleUserSelection(item.id)} /> {item.mobile} - {item.name} )} onEndReached={() => { if (hasMoreUsers && !isFetchingUsers) { setUserOffset(prev => prev + USERS_PAGE_SIZE); } }} onEndReachedThreshold={0.5} ListFooterComponent={isFetchingUsers ? Loading... : null} style={tw`max-h-60`} /> setUsersDropdownOpen(false)} style={tw`mt-4 bg-blue-500 p-3 rounded`} > Done {/* Submit Button */} handleSubmit()} loading={isLoading} disabled={isLoading} > Create Coupon ); }} ); }