enh
This commit is contained in:
parent
7291bfe7ce
commit
496faffe0a
8 changed files with 209 additions and 249 deletions
|
|
@ -117,6 +117,10 @@ export default function Complaints() {
|
|||
contentContainerStyle={tw`px-4 py-4`}
|
||||
data={complaints}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
initialNumToRender={10}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
removeClippedSubviews
|
||||
onEndReached={handleLoadMore}
|
||||
onEndReachedThreshold={0.5}
|
||||
renderItem={({ item }) => (
|
||||
|
|
@ -146,6 +150,20 @@ export default function Complaints() {
|
|||
{item.text}
|
||||
</MyText>
|
||||
|
||||
{item.response && (
|
||||
<View style={tw`mb-3 bg-blue-50 border border-blue-100 rounded-xl p-3`}>
|
||||
<View style={tw`flex-row items-center mb-1`}>
|
||||
<MaterialIcons name="reply" size={16} color="#2563EB" />
|
||||
<MyText style={tw`ml-1 text-xs font-bold text-blue-700`}>
|
||||
Admin Reply
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-sm text-gray-700 leading-5`}>
|
||||
{item.response}
|
||||
</MyText>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{item.images && item.images.length > 0 && (
|
||||
<View style={tw`mb-3`}>
|
||||
<MyText style={tw`text-sm font-semibold text-gray-700 mb-2`}>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export default function CreateCoupon() {
|
|||
)
|
||||
|
||||
const handleCreateCoupon = async (values: any) => {
|
||||
console.log('Form values:', values); // Debug log
|
||||
const { isReservedCoupon, couponCodes, ...rest } = values;
|
||||
// Transform targetUsers array to targetUser for backend compatibility
|
||||
const payload = {
|
||||
|
|
@ -33,43 +32,29 @@ export default function CreateCoupon() {
|
|||
targetUser: rest.targetUsers?.[0] || undefined,
|
||||
};
|
||||
delete payload.targetUsers;
|
||||
console.log('Payload:', payload); // Debug log
|
||||
|
||||
if (isReservedCoupon && Array.isArray(couponCodes) && couponCodes.length > 0) {
|
||||
// Create one reserved coupon per code, same config
|
||||
const codes = couponCodes.map((c: string) => c.trim()).filter(Boolean);
|
||||
try {
|
||||
for (const code of codes) {
|
||||
await createReservedCoupon.mutateAsync({ ...payload, couponCode: code });
|
||||
}
|
||||
await refetchCoupons()
|
||||
await refetchReservedCoupons()
|
||||
Alert.alert('Success', `${codes.length} reserved coupon${codes.length > 1 ? 's' : ''} created successfully`, [
|
||||
{ text: 'OK', onPress: () => router.back() }
|
||||
]);
|
||||
} catch (error: any) {
|
||||
Alert.alert('Error', error.message || 'Failed to create reserved coupons');
|
||||
// For flat discounts, send the discount value as maxValue too
|
||||
if (payload.flatDiscount !== undefined) {
|
||||
payload.maxValue = payload.flatDiscount;
|
||||
}
|
||||
|
||||
const codes = (couponCodes || []).map((c: string) => c.trim()).filter(Boolean);
|
||||
if (codes.length === 0) {
|
||||
Alert.alert('Error', 'At least one coupon code is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const mutation = isReservedCoupon ? createReservedCoupon : createCoupon;
|
||||
const isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending;
|
||||
|
||||
if (isLoading) return; // Prevent double submission
|
||||
|
||||
mutation.mutate(payload, {
|
||||
onSuccess: async () => {
|
||||
try {
|
||||
await mutation.mutateAsync({ ...payload, couponCodes: codes });
|
||||
await refetchCoupons()
|
||||
await refetchReservedCoupons()
|
||||
Alert.alert('Success', `${isReservedCoupon ? 'Reserved coupon' : 'Coupon'} created successfully`, [
|
||||
Alert.alert('Success', `${codes.length} coupon${codes.length > 1 ? 's' : ''} created successfully`, [
|
||||
{ text: 'OK', onPress: () => router.back() }
|
||||
]);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
Alert.alert('Error', error.message || 'Failed to create coupon');
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
Alert.alert('Error', error.message || 'Failed to create coupons');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ export default function EditCoupon() {
|
|||
};
|
||||
delete updates.targetUsers;
|
||||
|
||||
// For flat discounts, send the discount value as maxValue too
|
||||
if (updates.flatDiscount !== undefined) {
|
||||
updates.maxValue = updates.flatDiscount;
|
||||
}
|
||||
|
||||
updateCoupon.mutate({ id: couponId, updates }, {
|
||||
onSuccess: async () => {
|
||||
await refetch()
|
||||
|
|
@ -74,7 +79,7 @@ export default function EditCoupon() {
|
|||
|
||||
// Transform coupon data for form (targetUser to targetUsers array)
|
||||
const initialValues: Partial<CreateCouponPayload & { isReservedCoupon?: boolean }> = {
|
||||
couponCode: coupon.couponCode,
|
||||
couponCodes: coupon.couponCode ? [coupon.couponCode] : [''],
|
||||
isUserBased: coupon.isUserBased,
|
||||
isApplyForAll: coupon.isApplyForAll,
|
||||
targetUsers: coupon.targetUser ? [coupon.targetUser.id] : [],
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const USERS_PAGE_SIZE = 10;
|
|||
|
||||
interface CouponFormValues extends CreateCouponPayload {
|
||||
isReservedCoupon?: boolean;
|
||||
couponCodes?: string[];
|
||||
couponCodes: string[];
|
||||
skuIds?: number[];
|
||||
}
|
||||
|
||||
|
|
@ -23,11 +23,6 @@ interface CouponFormProps {
|
|||
|
||||
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'),
|
||||
couponCodes: Yup.array().of(
|
||||
Yup.string()
|
||||
.required('Code is required')
|
||||
|
|
@ -62,21 +57,17 @@ const couponValidationSchema = Yup.object().shape({
|
|||
return this.createError({ message: 'Must provide either percentage or flat discount' });
|
||||
}
|
||||
return true;
|
||||
}).test('reserved-codes-required', 'At least one coupon code is required', function(value: any) {
|
||||
if (value.isReservedCoupon) {
|
||||
}).test('codes-required', 'At least one coupon code is required', function(value: any) {
|
||||
const codes = (value.couponCodes || []).filter((c: string) => c && c.trim());
|
||||
if (codes.length === 0) {
|
||||
return this.createError({ path: 'couponCodes', message: 'Add at least one coupon code' });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).test('reserved-codes-unique', 'Coupon codes must be unique', function(value: any) {
|
||||
if (value.isReservedCoupon) {
|
||||
}).test('codes-unique', 'Coupon codes must be unique', function(value: any) {
|
||||
const codes = (value.couponCodes || []).map((c: string) => c.trim()).filter(Boolean);
|
||||
if (new Set(codes).size !== codes.length) {
|
||||
return this.createError({ path: 'couponCodes', message: 'Coupon codes must be unique' });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
@ -124,8 +115,7 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
// User search functionality will be inside Formik
|
||||
|
||||
const defaultValues: CouponFormValues = {
|
||||
couponCode: '',
|
||||
couponCodes: [],
|
||||
couponCodes: [''],
|
||||
isUserBased: false,
|
||||
isApplyForAll: false,
|
||||
targetUsers: [],
|
||||
|
|
@ -173,24 +163,11 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
<MyText style={tw`ml-2 text-sm font-medium text-gray-700`}>Is Reserved Coupon</MyText>
|
||||
</View>
|
||||
|
||||
{/* Coupon Code (single, non-reserved) OR Coupon Codes (array, reserved) */}
|
||||
{!isReserved ? (
|
||||
<View style={tw`mb-4`}>
|
||||
<MyTextInput
|
||||
topLabel="Coupon Code"
|
||||
placeholder="e.g., SAVE20"
|
||||
value={values.couponCode || ''}
|
||||
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
|
||||
keyboardType="default"
|
||||
autoCapitalize="characters"
|
||||
error={!!(touched.couponCode && errors.couponCode)}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
{/* Coupon Codes */}
|
||||
<View style={tw`mb-4`}>
|
||||
<Text style={tw`text-base font-bold mb-1`}>Coupon Codes</Text>
|
||||
<Text style={tw`text-xs text-gray-500 mb-2`}>
|
||||
Each code creates a separate reserved coupon with the same settings.
|
||||
Each code creates a separate coupon with the same settings.
|
||||
</Text>
|
||||
{(values.couponCodes || []).map((code: string, index: number) => {
|
||||
const codeError = Array.isArray(errors.couponCodes)
|
||||
|
|
@ -217,7 +194,7 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
const next = (values.couponCodes || []).filter((_: string, i: number) => i !== index);
|
||||
setFieldValue('couponCodes', next);
|
||||
setFieldValue('couponCodes', next.length > 0 ? next : ['']);
|
||||
}}
|
||||
style={tw`ml-2 p-2`}
|
||||
>
|
||||
|
|
@ -236,7 +213,6 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
<Text style={tw`text-blue-600 font-medium`}>+ Add Code</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Discount Type Selection */}
|
||||
<Text style={tw`text-base font-bold mb-2`}>
|
||||
|
|
@ -308,7 +284,8 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
/>
|
||||
</View>
|
||||
|
||||
{/* Maximum Discount Value */}
|
||||
{/* Maximum Discount Value - only for percentage discounts */}
|
||||
{values.flatDiscount === undefined && (
|
||||
<View style={tw`mb-4`}>
|
||||
<MyTextInput
|
||||
topLabel="Maximum Discount Value"
|
||||
|
|
@ -319,6 +296,7 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
error={!!(touched.maxValue && errors.maxValue)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Validity Period */}
|
||||
<View style={tw`mb-4`}>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export const complaintRouter = router({
|
|||
status: string;
|
||||
createdAt: Date;
|
||||
images: string[];
|
||||
response: string | null;
|
||||
}>;
|
||||
nextCursor?: number;
|
||||
}> => {
|
||||
|
|
@ -83,6 +84,7 @@ export const complaintRouter = router({
|
|||
status: c.isResolved ? 'resolved' : 'pending',
|
||||
createdAt: c.createdAt,
|
||||
images: signedImages,
|
||||
response: c.response,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type { Coupon, CouponValidationResult, UserMiniInfo } from '@packages/sha
|
|||
|
||||
const createCouponBodySchema = z.object({
|
||||
couponCode: z.string().optional(),
|
||||
couponCodes: z.array(z.string()).optional(),
|
||||
isUserBased: z.boolean().optional(),
|
||||
discountPercent: z.number().optional(),
|
||||
flatDiscount: z.number().optional(),
|
||||
|
|
@ -48,8 +49,8 @@ const validateCouponBodySchema = z.object({
|
|||
export const couponRouter = router({
|
||||
create: protectedProcedure
|
||||
.input(createCouponBodySchema)
|
||||
.mutation(async ({ input, ctx }): Promise<Coupon> => {
|
||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, skuIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
.mutation(async ({ input, ctx }): Promise<Coupon[]> => {
|
||||
const { couponCode, couponCodes, isUserBased, discountPercent, flatDiscount, minOrder, skuIds, applicableUsers, applicableProducts, maxValue, isApplyForAll, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
|
||||
// Validation: ensure at least one discount type is provided
|
||||
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
||||
|
|
@ -72,18 +73,15 @@ export const couponRouter = router({
|
|||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Generate coupon code if not provided
|
||||
let finalCouponCode = couponCode;
|
||||
if (!finalCouponCode) {
|
||||
// Support both single code (legacy) and array of codes
|
||||
let codes: string[] = couponCodes?.filter(Boolean) || [];
|
||||
if (codes.length === 0 && couponCode) {
|
||||
codes = [couponCode];
|
||||
}
|
||||
if (codes.length === 0) {
|
||||
const timestamp = Date.now().toString().slice(-6);
|
||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
finalCouponCode = `MF${timestamp}${random}`;
|
||||
}
|
||||
|
||||
// Using dbService helper (new implementation)
|
||||
const codeExists = await checkCouponExists(finalCouponCode);
|
||||
if (codeExists) {
|
||||
throw new Error("Coupon code already exists");
|
||||
codes = [`MF${timestamp}${random}`];
|
||||
}
|
||||
|
||||
// If applicableUsers is provided, verify users exist
|
||||
|
|
@ -94,6 +92,14 @@ export const couponRouter = router({
|
|||
}
|
||||
}
|
||||
|
||||
const createdCoupons: Coupon[] = [];
|
||||
for (const code of codes) {
|
||||
const finalCouponCode = code.trim().toUpperCase();
|
||||
const codeExists = await checkCouponExists(finalCouponCode);
|
||||
if (codeExists) {
|
||||
throw new Error(`Coupon code already exists: ${finalCouponCode}`);
|
||||
}
|
||||
|
||||
const coupon = await createCouponWithRelations(
|
||||
{
|
||||
couponCode: finalCouponCode,
|
||||
|
|
@ -113,47 +119,10 @@ export const couponRouter = router({
|
|||
applicableProducts
|
||||
);
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query with transaction:
|
||||
const result = await db.insert(coupons).values({
|
||||
couponCode: finalCouponCode,
|
||||
isUserBased: isUserBased || false,
|
||||
discountPercent: discountPercent?.toString(),
|
||||
flatDiscount: flatDiscount?.toString(),
|
||||
minOrder: minOrder?.toString(),
|
||||
skuIds: skuIds || null,
|
||||
createdBy: staffUserId,
|
||||
maxValue: maxValue?.toString(),
|
||||
isApplyForAll: isApplyForAll || false,
|
||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
||||
maxLimitForUser,
|
||||
exclusiveApply: exclusiveApply || false,
|
||||
}).returning();
|
||||
|
||||
const coupon = result[0];
|
||||
|
||||
// Insert applicable users
|
||||
if (applicableUsers && applicableUsers.length > 0) {
|
||||
await db.insert(couponApplicableUsers).values(
|
||||
applicableUsers.map(userId => ({
|
||||
couponId: coupon.id,
|
||||
userId,
|
||||
}))
|
||||
);
|
||||
createdCoupons.push(coupon);
|
||||
}
|
||||
|
||||
// Insert applicable products
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await db.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(skuId => ({
|
||||
couponId: coupon.id,
|
||||
skuId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
return coupon;
|
||||
return createdCoupons;
|
||||
}),
|
||||
|
||||
getAll: protectedProcedure
|
||||
|
|
@ -210,7 +179,8 @@ export const couponRouter = router({
|
|||
|
||||
// Prepare update data
|
||||
const updateData: any = {};
|
||||
if (updates.couponCode !== undefined) updateData.couponCode = updates.couponCode;
|
||||
const updatedCode = updates.couponCodes?.[0]?.trim() || updates.couponCode;
|
||||
if (updatedCode !== undefined && updatedCode !== '') updateData.couponCode = updatedCode.trim().toUpperCase();
|
||||
if (updates.isUserBased !== undefined) updateData.isUserBased = updates.isUserBased;
|
||||
if (updates.discountPercent !== undefined) updateData.discountPercent = updates.discountPercent?.toString();
|
||||
if (updates.flatDiscount !== undefined) updateData.flatDiscount = updates.flatDiscount?.toString();
|
||||
|
|
@ -405,7 +375,7 @@ export const couponRouter = router({
|
|||
createReservedCoupon: protectedProcedure
|
||||
.input(createCouponBodySchema)
|
||||
.mutation(async ({ input, ctx }): Promise<any> => {
|
||||
const { couponCode, isUserBased, discountPercent, flatDiscount, minOrder, skuIds, applicableProducts, maxValue, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
const { couponCode, couponCodes, isUserBased, discountPercent, flatDiscount, minOrder, skuIds, applicableProducts, maxValue, validTill, maxLimitForUser, exclusiveApply } = input;
|
||||
|
||||
// Validation: ensure at least one discount type is provided
|
||||
if ((!discountPercent && !flatDiscount) || (discountPercent && flatDiscount)) {
|
||||
|
|
@ -418,19 +388,29 @@ export const couponRouter = router({
|
|||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
// Generate secret code if not provided
|
||||
let secretCode = couponCode || `SECRET${Date.now().toString().slice(-6)}${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
|
||||
// Support both single code (legacy) and array of codes
|
||||
let codes: string[] = couponCodes?.filter(Boolean) || [];
|
||||
if (codes.length === 0 && couponCode) {
|
||||
codes = [couponCode];
|
||||
}
|
||||
if (codes.length === 0) {
|
||||
const timestamp = Date.now().toString().slice(-6);
|
||||
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
codes = [`SECRET${timestamp}${random}`];
|
||||
}
|
||||
|
||||
// Using dbService helper (new implementation)
|
||||
const createdCoupons = [];
|
||||
for (const code of codes) {
|
||||
const secretCode = code.trim().toUpperCase();
|
||||
const codeExists = await checkReservedCouponExists(secretCode);
|
||||
if (codeExists) {
|
||||
throw new Error("Secret code already exists");
|
||||
throw new Error(`Secret code already exists: ${secretCode}`);
|
||||
}
|
||||
|
||||
const coupon = await createReservedCouponWithProducts(
|
||||
{
|
||||
secretCode,
|
||||
couponCode: couponCode || `RESERVED${Date.now().toString().slice(-6)}`,
|
||||
couponCode: secretCode,
|
||||
discountPercent: discountPercent?.toString(),
|
||||
flatDiscount: flatDiscount?.toString(),
|
||||
minOrder: minOrder?.toString(),
|
||||
|
|
@ -444,36 +424,10 @@ export const couponRouter = router({
|
|||
applicableProducts
|
||||
);
|
||||
|
||||
/*
|
||||
// Old implementation - direct DB query:
|
||||
const result = await db.insert(reservedCoupons).values({
|
||||
secretCode,
|
||||
couponCode: couponCode || RESERVED${Date.now().toString().slice(-6)},
|
||||
discountPercent: discountPercent?.toString(),
|
||||
flatDiscount: flatDiscount?.toString(),
|
||||
minOrder: minOrder?.toString(),
|
||||
skuIds,
|
||||
maxValue: maxValue?.toString(),
|
||||
validTill: validTill ? dayjs(validTill).toDate() : undefined,
|
||||
maxLimitForUser,
|
||||
exclusiveApply: exclusiveApply || false,
|
||||
createdBy: staffUserId,
|
||||
}).returning();
|
||||
|
||||
const coupon = result[0];
|
||||
|
||||
// Insert applicable products if provided
|
||||
if (applicableProducts && applicableProducts.length > 0) {
|
||||
await db.insert(couponApplicableProducts).values(
|
||||
applicableProducts.map(skuId => ({
|
||||
couponId: coupon.id,
|
||||
skuId,
|
||||
}))
|
||||
);
|
||||
createdCoupons.push(coupon);
|
||||
}
|
||||
*/
|
||||
|
||||
return coupon;
|
||||
return createdCoupons;
|
||||
}),
|
||||
|
||||
getUsersMiniInfo: protectedProcedure
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { View, ScrollView, Alert, Dimensions } from 'react-native';
|
|||
import { Image } from 'expo-image';
|
||||
import { useRouter, usePathname } from 'expo-router';
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier } from 'common-ui';
|
||||
import { tw, theme, MyText, MyTouchableOpacity, MyFlatList, AppContainer, MiniQuantifier, SearchBar } from 'common-ui';
|
||||
import { trpc } from '@/src/trpc-client';
|
||||
import { useAllProducts, useStores, useSlots } from '@/src/hooks/prominent-api-hooks';
|
||||
import { AllProductsApiType } from '@backend/trpc/router';
|
||||
|
|
@ -445,6 +445,7 @@ interface FlashDeliveryProductsProps {
|
|||
export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProductPress }: FlashDeliveryProductsProps) {
|
||||
useHideTabNav('quick_delivery');
|
||||
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = React.useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const router = useRouter();
|
||||
const storeId = storeIdParent;
|
||||
const storeIdNum = storeId;
|
||||
|
|
@ -506,11 +507,28 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
|
|||
}) || [];
|
||||
}
|
||||
|
||||
// Apply search filter (searches within this store's products only)
|
||||
const trimmedQuery = searchQuery.trim().toLowerCase();
|
||||
if (trimmedQuery) {
|
||||
flashProducts = flashProducts.filter(p =>
|
||||
p.name?.toLowerCase().includes(trimmedQuery)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={tw`flex-1`}>
|
||||
<MyFlatList
|
||||
data={flashProducts}
|
||||
numColumns={2}
|
||||
ListHeaderComponent={
|
||||
<View style={tw`px-4 pt-2`}>
|
||||
<SearchBar
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search products in this store..."
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<CompactProductCard
|
||||
item={item}
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ export interface Coupon {
|
|||
}
|
||||
|
||||
export interface CreateCouponPayload {
|
||||
couponCode: string;
|
||||
couponCodes: string[];
|
||||
discountPercent?: number;
|
||||
flatDiscount?: number;
|
||||
minOrder?: number;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue