This commit is contained in:
shafi54 2026-02-08 03:01:33 +05:30
parent 55c41fa0af
commit d599c2e004
11 changed files with 437 additions and 11 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -17,6 +17,13 @@ export default function Layout() {
headerShown: false, headerShown: false,
}} }}
/> />
<Stack.Screen
name="all-items-order"
options={{
title: "All Items Order",
headerShown: false,
}}
/>
</Stack> </Stack>
); );
} }

View file

@ -0,0 +1,391 @@
import React, { useState, useEffect, useCallback } from "react";
import {
View,
Alert,
ActivityIndicator,
Dimensions,
StyleSheet,
} from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Image } from "expo-image";
import DraggableFlatList, {
ScaleDecorator,
} from "react-native-draggable-flatlist";
import {
AppContainer,
MyText,
tw,
MyTouchableOpacity,
} from "common-ui";
import { useRouter } from "expo-router";
import { trpc } from "../../../src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { useQueryClient } from "@tanstack/react-query";
const { width: screenWidth } = Dimensions.get("window");
// Item takes full width minus padding
const itemWidth = screenWidth - 48; // 24px padding each side
const itemHeight = 80;
interface Product {
id: number;
name: string;
images: string[];
isOutOfStock: boolean;
}
interface ProductItemProps {
item: Product;
drag: () => void;
isActive: boolean;
}
const ProductItem: React.FC<ProductItemProps> = ({
item,
drag,
isActive,
}) => {
return (
<ScaleDecorator>
<TouchableOpacity
onLongPress={drag}
activeOpacity={1}
style={[
styles.item,
isActive && styles.activeItem,
item.isOutOfStock && styles.outOfStock,
]}
>
{/* Drag Handle */}
<View style={styles.dragHandle}>
<MaterialIcons
name="drag-indicator"
size={24}
color={isActive ? "#3b82f6" : "#9ca3af"}
/>
</View>
{/* Product Image */}
{item.images?.[0] ? (
<Image
source={{ uri: item.images[0] }}
style={styles.image}
resizeMode="cover"
/>
) : (
<View style={styles.placeholderImage}>
<MaterialIcons name="image" size={24} color="#9ca3af" />
</View>
)}
{/* Product Info */}
<View style={styles.info}>
<MyText style={styles.name} numberOfLines={1}>
{item.name.length > 30 ? item.name.substring(0, 30) + '...' : item.name}
</MyText>
{item.isOutOfStock && (
<MaterialIcons name="remove-circle" size={16} color="#dc2626" />
)}
</View>
</TouchableOpacity>
</ScaleDecorator>
);
};
export default function AllItemsOrder() {
const router = useRouter();
const queryClient = useQueryClient();
const [products, setProducts] = useState<Product[]>([]);
const [hasChanges, setHasChanges] = useState(false);
// Get current order from constants
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
const { data: allProducts, isLoading: isLoadingProducts, error: productsError } = trpc.common.product.getAllProductsSummary.useQuery({});
const updateConstants = trpc.admin.const.updateConstants.useMutation();
// Initialize products from constants
useEffect(() => {
if (allProducts?.products) {
const allItemsOrderConstant = constants?.find(c => c.key === 'allItemsOrder');
let orderedIds: number[] = [];
if (allItemsOrderConstant) {
const value = allItemsOrderConstant.value;
if (Array.isArray(value)) {
orderedIds = value.map((id: any) => parseInt(id));
} else if (typeof value === 'string') {
orderedIds = value.split(',').map((id: string) => parseInt(id.trim())).filter(id => !isNaN(id));
}
}
// Create product map for quick lookup
const productMap = new Map(allProducts.products.map(p => [p.id, p]));
// Sort products based on order, products not in order go to end
const sortedProducts: Product[] = [];
// First add products in the specified order
for (const id of orderedIds) {
const product = productMap.get(id);
if (product) {
sortedProducts.push({
id: product.id,
name: product.name,
images: product.images || [],
isOutOfStock: product.isOutOfStock || false,
});
productMap.delete(id);
}
}
// Then add remaining products (not in order yet)
for (const product of productMap.values()) {
sortedProducts.push({
id: product.id,
name: product.name,
images: product.images || [],
isOutOfStock: product.isOutOfStock || false,
});
}
setProducts(sortedProducts);
}
}, [constants, allProducts]);
const handleDragEnd = useCallback(({ data }: { data: Product[] }) => {
setProducts(data);
setHasChanges(true);
}, []);
const renderItem = useCallback(({ item, drag, isActive }: { item: Product; drag: () => void; isActive: boolean }) => {
return (
<ProductItem
item={item}
drag={drag}
isActive={isActive}
/>
);
}, []);
const handleSave = () => {
const productIds = products.map(p => p.id);
updateConstants.mutate(
{
constants: [{
key: 'allItemsOrder',
value: productIds
}]
},
{
onSuccess: () => {
setHasChanges(false);
Alert.alert('Success', 'All items order updated successfully!');
queryClient.invalidateQueries({ queryKey: ['const.getConstants'] });
},
onError: (error) => {
Alert.alert('Error', 'Failed to update items order. Please try again.');
console.error('Update all items order error:', error);
}
}
);
};
// Show loading state while data is being fetched
if (isLoadingConstants || isLoadingProducts) {
return (
<AppContainer>
<View style={tw`flex-1 bg-gray-50`}>
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
<TouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</TouchableOpacity>
<MyText style={tw`text-xl font-bold text-gray-900`}>All Items Order</MyText>
<View style={tw`w-16`} />
</View>
<View style={tw`flex-1 justify-center items-center p-8`}>
<ActivityIndicator size="large" color="#3b82f6" />
<MyText style={tw`text-gray-500 mt-4 text-center`}>
{isLoadingConstants ? 'Loading order...' : 'Loading products...'}
</MyText>
</View>
</View>
</AppContainer>
);
}
// Show error state if queries failed
if (constantsError || productsError) {
return (
<AppContainer>
<View style={tw`flex-1 bg-gray-50`}>
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
<TouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</TouchableOpacity>
<MyText style={tw`text-xl font-bold text-gray-900`}>All Items Order</MyText>
<View style={tw`w-16`} />
</View>
<View style={tw`flex-1 justify-center items-center p-8`}>
<MaterialIcons name="error-outline" size={64} color="#ef4444" />
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Error</MyText>
<MyText style={tw`text-gray-500 mt-2 text-center`}>
{constantsError ? 'Failed to load order' : 'Failed to load products'}
</MyText>
<TouchableOpacity
onPress={() => router.back()}
style={tw`mt-6 bg-blue-600 px-6 py-3 rounded-full`}
>
<MyText style={tw`text-white font-semibold`}>Go Back</MyText>
</TouchableOpacity>
</View>
</View>
</AppContainer>
);
}
return (
<View style={tw`flex-1 bg-gray-50`}>
{/* Header */}
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
<MyTouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</MyTouchableOpacity>
<MyText style={tw`text-xl font-bold text-gray-900`}>All Items Order</MyText>
<MyTouchableOpacity
onPress={handleSave}
disabled={!hasChanges || updateConstants.isPending}
style={tw`px-4 py-2 rounded-lg ${
hasChanges && !updateConstants.isPending
? 'bg-blue-600'
: 'bg-gray-300'
}`}
>
<MyText style={tw`${
hasChanges && !updateConstants.isPending
? 'text-white'
: 'text-gray-500'
} font-semibold`}>
{updateConstants.isPending ? 'Saving...' : 'Save'}
</MyText>
</MyTouchableOpacity>
</View>
{/* Content */}
{products.length === 0 ? (
<View style={tw`flex-1 justify-center items-center p-8`}>
<MaterialIcons name="inventory" size={64} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center text-lg`}>
No products available
</MyText>
</View>
) : (
<View style={tw`flex-1`}>
<View style={tw`bg-blue-50 px-4 py-2 mb-2 mt-2 mx-4 rounded-lg`}>
<MyText style={tw`text-blue-700 text-xs text-center`}>
Long press and drag to reorder {products.length} items
</MyText>
</View>
<View style={tw`flex-1 px-3`}>
<DraggableFlatList
data={products}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
onDragEnd={handleDragEnd}
showsVerticalScrollIndicator={true}
contentContainerStyle={{ paddingBottom: 20 }}
containerStyle={tw`flex-1`}
keyboardShouldPersistTaps="handled"
// Enable auto-scroll during drag
activationDistance={10}
/>
</View>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
item: {
width: itemWidth,
height: 60,
backgroundColor: 'white',
borderRadius: 8,
borderWidth: 1,
borderColor: '#e5e7eb',
padding: 10,
flexDirection: 'row',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
marginVertical: 4,
},
activeItem: {
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
borderColor: '#3b82f6',
transform: [{ scale: 1.02 }],
},
outOfStock: {
opacity: 0.6,
},
dragHandle: {
marginRight: 8,
padding: 2,
},
image: {
width: 30,
height: 30,
borderRadius: 6,
marginRight: 10,
},
placeholderImage: {
width: 30,
height: 30,
borderRadius: 6,
backgroundColor: '#f3f4f6',
marginRight: 10,
alignItems: 'center',
justifyContent: 'center',
},
info: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
name: {
fontSize: 13,
color: '#111827',
fontWeight: '500',
flex: 1,
marginRight: 4,
},
orderNumber: {
fontSize: 11,
color: '#9ca3af',
marginLeft: 8,
},
});

View file

@ -31,6 +31,7 @@ const CONST_LABELS: Record<string, string> = {
playStoreUrl: 'Play Store URL', playStoreUrl: 'Play Store URL',
appStoreUrl: 'App Store URL', appStoreUrl: 'App Store URL',
popularItems: 'Popular Items', popularItems: 'Popular Items',
allItemsOrder: 'All Items Order',
isFlashDeliveryEnabled: 'Enable Flash Delivery', isFlashDeliveryEnabled: 'Enable Flash Delivery',
supportMobile: 'Support Mobile', supportMobile: 'Support Mobile',
supportEmail: 'Support Email', supportEmail: 'Support Email',
@ -48,6 +49,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constant, setFieldValue,
// Special handling for popularItems - show navigation button instead of input // Special handling for popularItems - show navigation button instead of input
if (constant.key === 'popularItems') { if (constant.key === 'popularItems') {
console.log('key is allItemsOrder')
return ( return (
<View> <View>
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}> <MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
@ -67,6 +69,28 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constant, setFieldValue,
); );
} }
// Special handling for allItemsOrder - show navigation button instead of input
if (constant.key === 'allItemsOrder') {
return (
<View>
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
{CONST_LABELS[constant.key] || constant.key}
</MyText>
<MyTouchableOpacity
onPress={() => router.push('/(drawer)/customize-app/all-items-order')}
style={tw`bg-green-50 border-2 border-dashed border-green-200 p-4 rounded-lg flex-row items-center justify-center`}
>
<MaterialIcons name="reorder" size={20} color="#16a34a" style={tw`mr-2`} />
<MyText style={tw`text-green-700 font-medium`}>
Manage All Visible Items ({Array.isArray(constant.value) ? constant.value.length : 0} items)
</MyText>
<MaterialIcons name="chevron-right" size={20} color="#16a34a" style={tw`ml-2`} />
</MyTouchableOpacity>
</View>
);
}
// Handle boolean values - show checkbox // Handle boolean values - show checkbox
if (typeof constant.value === 'boolean') { if (typeof constant.value === 'boolean') {
return ( return (
@ -134,6 +158,7 @@ export default function CustomizeApp() {
const { data: constants, isLoading: isLoadingConstants, refetch } = trpc.admin.const.getConstants.useQuery(); const { data: constants, isLoading: isLoadingConstants, refetch } = trpc.admin.const.getConstants.useQuery();
const { mutate: updateConstants, isPending: isUpdating } = trpc.admin.const.updateConstants.useMutation(); const { mutate: updateConstants, isPending: isUpdating } = trpc.admin.const.updateConstants.useMutation();
const handleSubmit = (values: ConstantFormData) => { const handleSubmit = (values: ConstantFormData) => {
// Filter out constants that haven't changed // Filter out constants that haven't changed
const changedConstants = values.constants.filter((constant, index) => { const changedConstants = values.constants.filter((constant, index) => {

View file

@ -1,7 +1,7 @@
ENV_MODE=PROD ENV_MODE=PROD
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy # DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/ PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090 PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
PHONE_PE_CLIENT_VERSION=1 PHONE_PE_CLIENT_VERSION=1

View file

@ -19,7 +19,7 @@ import { seed } from 'src/db/seed';
import './src/jobs/jobs-index'; import './src/jobs/jobs-index';
import { startAutomatedJobs } from './src/lib/automatedJobs'; import { startAutomatedJobs } from './src/lib/automatedJobs';
// seed() seed()
initFunc() initFunc()
startAutomatedJobs() startAutomatedJobs()

View file

@ -113,6 +113,7 @@ export async function seed() {
{ key: CONST_KEYS.flashFreeDeliveryThreshold, value: 500 }, { key: CONST_KEYS.flashFreeDeliveryThreshold, value: 500 },
{ key: CONST_KEYS.flashDeliveryCharge, value: 69 }, { key: CONST_KEYS.flashDeliveryCharge, value: 69 },
{ key: CONST_KEYS.popularItems, value: [] }, { key: CONST_KEYS.popularItems, value: [] },
{ key: CONST_KEYS.allItemsOrder, value: [] },
{ key: CONST_KEYS.versionNum, value: '1.1.0' }, { key: CONST_KEYS.versionNum, value: '1.1.0' },
{ key: CONST_KEYS.playStoreUrl, value: 'https://play.google.com/store/apps/details?id=in.freshyo.app' }, { key: CONST_KEYS.playStoreUrl, value: 'https://play.google.com/store/apps/details?id=in.freshyo.app' },
{ key: CONST_KEYS.appStoreUrl, value: 'https://play.google.com/store/apps/details?id=in.freshyo.app' }, { key: CONST_KEYS.appStoreUrl, value: 'https://play.google.com/store/apps/details?id=in.freshyo.app' },

View file

@ -6,6 +6,7 @@ export const CONST_KEYS = {
flashDeliveryCharge: 'flashDeliveryCharge', flashDeliveryCharge: 'flashDeliveryCharge',
platformFeePercent: 'platformFeePercent', platformFeePercent: 'platformFeePercent',
taxRate: 'taxRate', taxRate: 'taxRate',
tester: 'tester',
minOrderAmountForCoupon: 'minOrderAmountForCoupon', minOrderAmountForCoupon: 'minOrderAmountForCoupon',
maxCouponDiscount: 'maxCouponDiscount', maxCouponDiscount: 'maxCouponDiscount',
flashDeliverySlotId: 'flashDeliverySlotId', flashDeliverySlotId: 'flashDeliverySlotId',
@ -14,6 +15,7 @@ export const CONST_KEYS = {
playStoreUrl: 'playStoreUrl', playStoreUrl: 'playStoreUrl',
appStoreUrl: 'appStoreUrl', appStoreUrl: 'appStoreUrl',
popularItems: 'popularItems', popularItems: 'popularItems',
allItemsOrder: 'allItemsOrder',
isFlashDeliveryEnabled: 'isFlashDeliveryEnabled', isFlashDeliveryEnabled: 'isFlashDeliveryEnabled',
supportMobile: 'supportMobile', supportMobile: 'supportMobile',
supportEmail: 'supportEmail', supportEmail: 'supportEmail',
@ -27,6 +29,7 @@ export const CONST_LABELS: Record<ConstKey, string> = {
flashDeliveryCharge: 'Flash Delivery Charge', flashDeliveryCharge: 'Flash Delivery Charge',
platformFeePercent: 'Platform Fee Percent', platformFeePercent: 'Platform Fee Percent',
taxRate: 'Tax Rate', taxRate: 'Tax Rate',
tester: 'Tester',
minOrderAmountForCoupon: 'Minimum Order Amount for Coupon', minOrderAmountForCoupon: 'Minimum Order Amount for Coupon',
maxCouponDiscount: 'Maximum Coupon Discount', maxCouponDiscount: 'Maximum Coupon Discount',
flashDeliverySlotId: 'Flash Delivery Slot ID', flashDeliverySlotId: 'Flash Delivery Slot ID',
@ -35,6 +38,7 @@ export const CONST_LABELS: Record<ConstKey, string> = {
playStoreUrl: 'Play Store URL', playStoreUrl: 'Play Store URL',
appStoreUrl: 'App Store URL', appStoreUrl: 'App Store URL',
popularItems: 'Popular Items', popularItems: 'Popular Items',
allItemsOrder: 'All Items Order',
isFlashDeliveryEnabled: 'Enable Flash Delivery', isFlashDeliveryEnabled: 'Enable Flash Delivery',
supportMobile: 'Support Mobile', supportMobile: 'Support Mobile',
supportEmail: 'Support Email', supportEmail: 'Support Email',

View file

@ -115,8 +115,6 @@ export const userRouter = router({
const userId = ctx.user.userId; const userId = ctx.user.userId;
const { token } = input; const { token } = input;
console.log({token})
if (!userId) { if (!userId) {
throw new ApiError('User not authenticated', 401); throw new ApiError('User not authenticated', 401);
} }

View file

@ -53,13 +53,13 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
useEffect(() => { useEffect(() => {
registerForPushNotificationsAsync() registerForPushNotificationsAsync()
.then((token) => { .then((token) => {
console.log({token})
setExpoPushToken(token); setExpoPushToken(token);
setNotifPermission("granted"); setNotifPermission("granted");
}) })
.catch((errorRaw) => { .catch((errorRaw) => {
console.log({errorRaw})
const err = String(errorRaw).slice(7); //remove the "Error: " string component in beginning const err = String(errorRaw).slice(7); //remove the "Error: " string component in beginning