206 lines
No EOL
6 KiB
TypeScript
206 lines
No EOL
6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, ScrollView, Alert } from 'react-native';
|
|
import { AppContainer, MyText, tw, useMarkDataFetchers, MyTouchableOpacity, MyTextInput } from 'common-ui';
|
|
import { trpc } from '@/src/trpc-client';
|
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
import dayjs from 'dayjs';
|
|
|
|
interface CouponCardProps {
|
|
coupon: {
|
|
id: number;
|
|
code: string;
|
|
discountType: 'percentage' | 'flat';
|
|
discountValue: number;
|
|
maxValue?: number;
|
|
minOrder?: number;
|
|
description: string;
|
|
validTill?: string | Date;
|
|
usageCount: number;
|
|
maxLimitForUser?: number;
|
|
isExpired: boolean;
|
|
isUsedUp: boolean;
|
|
};
|
|
}
|
|
|
|
function CouponCard({ coupon }: CouponCardProps) {
|
|
const getStatusColor = () => {
|
|
if (coupon.isExpired) return 'text-red-500';
|
|
if (coupon.isUsedUp) return 'text-orange-500';
|
|
return 'text-green-600';
|
|
};
|
|
|
|
const getStatusText = () => {
|
|
if (coupon.isExpired) return 'Expired';
|
|
if (coupon.isUsedUp) return 'Used';
|
|
return 'Available';
|
|
};
|
|
|
|
const formatDate = (date?: string | Date) => {
|
|
if (!date) return 'No expiry';
|
|
return dayjs(date).format('DD MMM YYYY');
|
|
};
|
|
|
|
return (
|
|
<View style={tw`bg-white rounded-lg p-4 mb-3 shadow-sm border border-gray-100`}>
|
|
<View style={tw`flex-row justify-between items-start mb-2`}>
|
|
<View style={tw`flex-1`}>
|
|
<MyText weight="semibold" style={tw`text-lg text-gray-800 mb-1`}>
|
|
{coupon.code}
|
|
</MyText>
|
|
<MyText style={tw`text-sm text-gray-600 mb-2`}>
|
|
{coupon.description}
|
|
</MyText>
|
|
</View>
|
|
<View style={tw`items-end`}>
|
|
<MyText style={tw`${getStatusColor()} text-sm font-medium mb-1`}>
|
|
{getStatusText()}
|
|
</MyText>
|
|
{coupon.maxLimitForUser && (
|
|
<MyText style={tw`text-xs text-gray-500`}>
|
|
Used: {coupon.usageCount}/{coupon.maxLimitForUser}
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
<View style={tw`flex-row justify-between items-center`}>
|
|
<MyText style={tw`text-xs text-gray-500`}>
|
|
Valid till: {formatDate(coupon.validTill)}
|
|
</MyText>
|
|
<MyTouchableOpacity
|
|
style={tw`bg-blue-500 px-3 py-1 rounded-full`}
|
|
disabled={coupon.isExpired || coupon.isUsedUp}
|
|
>
|
|
<MyText style={tw`text-white text-xs font-medium`}>
|
|
{coupon.isExpired || coupon.isUsedUp ? 'Unavailable' : 'Apply'}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function CouponSection({
|
|
title,
|
|
coupons,
|
|
emptyMessage
|
|
}: {
|
|
title: string;
|
|
coupons: CouponCardProps['coupon'][];
|
|
emptyMessage: string;
|
|
}) {
|
|
return (
|
|
<View style={tw`mb-6`}>
|
|
<MyText weight="semibold" style={tw`text-lg text-gray-800 mb-3`}>
|
|
{title}
|
|
</MyText>
|
|
|
|
{coupons.length === 0 ? (
|
|
<View style={tw`bg-gray-50 rounded-lg p-6 items-center`}>
|
|
<MaterialIcons name="local-offer" size={48} color="#9CA3AF" />
|
|
<MyText style={tw`text-gray-500 text-center mt-2`}>
|
|
{emptyMessage}
|
|
</MyText>
|
|
</View>
|
|
) : (
|
|
coupons.map(coupon => (
|
|
<CouponCard key={coupon.id} coupon={coupon} />
|
|
))
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function Coupons() {
|
|
const { data, isLoading, error, refetch } = trpc.user.coupon.getMyCoupons.useQuery();
|
|
const [couponCode, setCouponCode] = useState('');
|
|
|
|
const redeemCouponMutation = trpc.user.coupon.redeemReservedCoupon.useMutation({
|
|
onSuccess: () => {
|
|
setCouponCode('');
|
|
refetch();
|
|
Alert.alert('Success', 'Coupon added successfully!');
|
|
},
|
|
onError: (error: any) => {
|
|
Alert.alert('Error', error.message || 'Failed to add coupon. Please check the code and try again.');
|
|
}
|
|
});
|
|
|
|
useMarkDataFetchers(() => {
|
|
refetch();
|
|
});
|
|
|
|
console.log({data, error})
|
|
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<AppContainer>
|
|
<View style={tw`flex-1 justify-center items-center`}>
|
|
<MyText style={tw`text-gray-600`}>Loading coupons...</MyText>
|
|
</View>
|
|
</AppContainer>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<AppContainer>
|
|
<View style={tw`flex-1 justify-center items-center p-4`}>
|
|
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
|
<MyText style={tw`text-red-600 text-center mt-2`}>
|
|
Failed to load coupons. Please try again.
|
|
</MyText>
|
|
</View>
|
|
</AppContainer>
|
|
);
|
|
}
|
|
|
|
const personalCoupons = data?.data?.personal || [];
|
|
const generalCoupons = data?.data?.general || [];
|
|
|
|
return (
|
|
<AppContainer>
|
|
<View style={tw`flex-1 p-4`}>
|
|
<View style={tw`bg-white rounded-lg p-4 mb-6 shadow-sm border border-gray-100`}>
|
|
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>
|
|
Add a Coupon
|
|
</MyText>
|
|
<MyTextInput
|
|
placeholder="Enter coupon code"
|
|
value={couponCode}
|
|
onChangeText={setCouponCode}
|
|
style={tw`mb-4`}
|
|
/>
|
|
<MyTouchableOpacity
|
|
style={tw`bg-blue-500 px-4 py-3 rounded-lg items-center ${
|
|
redeemCouponMutation.isPending || couponCode.trim().length < 4 ? 'opacity-50' : ''
|
|
}`}
|
|
disabled={redeemCouponMutation.isPending || couponCode.trim().length < 4}
|
|
onPress={() => {
|
|
if (couponCode.trim().length >= 4) {
|
|
redeemCouponMutation.mutate({ secretCode: couponCode.trim().toUpperCase() });
|
|
}
|
|
}}
|
|
>
|
|
<MyText style={tw`text-white font-semibold`}>
|
|
{redeemCouponMutation.isPending ? 'Adding...' : 'Add Coupon'}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
|
|
<CouponSection
|
|
title="Only for Me"
|
|
coupons={personalCoupons}
|
|
emptyMessage="No personal coupons available"
|
|
/>
|
|
|
|
<CouponSection
|
|
title="Apply to All"
|
|
coupons={generalCoupons}
|
|
emptyMessage="No general coupons available"
|
|
/>
|
|
</View>
|
|
</AppContainer>
|
|
);
|
|
} |