Compare commits

...

4 commits

Author SHA1 Message Date
shafi54
d599c2e004 enh 2026-02-08 03:01:33 +05:30
shafi54
55c41fa0af enh 2026-02-08 01:45:50 +05:30
shafi54
d1d7db55a0 enh 2026-02-08 00:50:03 +05:30
shafi54
78e90fd398 enh 2026-02-08 00:10:47 +05:30
34 changed files with 599 additions and 82 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,
}}
/>
<Stack.Screen
name="all-items-order"
options={{
title: "All Items Order",
headerShown: false,
}}
/>
</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',
appStoreUrl: 'App Store URL',
popularItems: 'Popular Items',
allItemsOrder: 'All Items Order',
isFlashDeliveryEnabled: 'Enable Flash Delivery',
supportMobile: 'Support Mobile',
supportEmail: 'Support Email',
@ -48,6 +49,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constant, setFieldValue,
// Special handling for popularItems - show navigation button instead of input
if (constant.key === 'popularItems') {
console.log('key is allItemsOrder')
return (
<View>
<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
if (typeof constant.value === 'boolean') {
return (
@ -134,6 +158,7 @@ export default function CustomizeApp() {
const { data: constants, isLoading: isLoadingConstants, refetch } = trpc.admin.const.getConstants.useQuery();
const { mutate: updateConstants, isPending: isUpdating } = trpc.admin.const.updateConstants.useMutation();
const handleSubmit = (values: ConstantFormData) => {
// Filter out constants that haven't changed
const changedConstants = values.constants.filter((constant, index) => {

View file

@ -1,10 +1,11 @@
import React, { useState, useEffect, useMemo } from "react";
import {
View,
TouchableOpacity,
Alert,
ActivityIndicator,
ScrollView,
} from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Image } from "expo-image";
import DraggableFlatList, {
RenderItemParams,
@ -16,6 +17,7 @@ import {
tw,
BottomDialog,
BottomDropdown,
MyTouchableOpacity,
} from "common-ui";
import ProductsSelector from "../../../components/ProductsSelector";
import { useRouter } from "expo-router";
@ -27,8 +29,8 @@ interface PopularProduct {
id: number;
name: string;
shortDescription: string | null;
price: string;
marketPrice: string | null;
price: number;
marketPrice: number | null;
unit: string;
incrementStep: number;
productQuantity: number;
@ -119,7 +121,7 @@ export default function CustomizePopularItems() {
const [popularProducts, setPopularProducts] = useState<PopularProduct[]>([]);
const [hasChanges, setHasChanges] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false);
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
const [selectedProductIds, setSelectedProductIds] = useState<number[]>([]);
// Get current popular items from constants
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
@ -182,14 +184,20 @@ export default function CustomizePopularItems() {
};
const handleAddProduct = () => {
if (selectedProductId) {
const product = allProducts?.products.find(p => p.id === selectedProductId);
if (product && !popularProducts.find(p => p.id === product.id)) {
setPopularProducts(prev => [...prev, product as PopularProduct]);
if (selectedProductIds.length > 0) {
const newProducts = selectedProductIds
.map(id => allProducts?.products.find(p => p.id === id))
.filter((product): product is NonNullable<typeof product> =>
product !== undefined && !popularProducts.find(p => p.id === product.id)
);
if (newProducts.length > 0) {
setPopularProducts(prev => [...prev, ...newProducts as PopularProduct[]]);
setHasChanges(true);
setSelectedProductId(null);
setShowAddDialog(false);
}
setSelectedProductIds([]);
setShowAddDialog(false);
}
};
@ -293,20 +301,19 @@ export default function CustomizePopularItems() {
}
return (
<AppContainer>
<View style={tw`flex-1 bg-gray-50`}>
<View style={[tw`flex-1 bg-gray-50 relative`]}>
{/* Header */}
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
<TouchableOpacity
<MyTouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</TouchableOpacity>
</MyTouchableOpacity>
<MyText style={tw`text-xl font-bold text-gray-900`}>Popular Items</MyText>
<TouchableOpacity
<MyTouchableOpacity
onPress={handleSave}
disabled={!hasChanges || updateConstants.isPending}
style={tw`px-4 py-2 rounded-lg ${
@ -322,7 +329,7 @@ export default function CustomizePopularItems() {
} font-semibold`}>
{updateConstants.isPending ? 'Saving...' : 'Save'}
</MyText>
</TouchableOpacity>
</MyTouchableOpacity>
</View>
{/* Content */}
@ -356,35 +363,41 @@ export default function CustomizePopularItems() {
)}
keyExtractor={(item) => item.id.toString()}
onDragEnd={handleDragEnd}
showsVerticalScrollIndicator={false}
contentContainerStyle={tw`pb-8`}
showsVerticalScrollIndicator={true}
scrollEnabled={true}
contentContainerStyle={{ paddingBottom: 80 }}
containerStyle={tw`flex-1`}
keyboardShouldPersistTaps="handled"
/>
</View>
)}
{/* FAB for Add Product */}
<View style={tw`absolute bottom-4 right-4`}>
<TouchableOpacity
{/* FAB for Add Product - Fixed position */}
<View style={tw`absolute bottom-12 right-6 z-50`}>
<MyTouchableOpacity
onPress={() => setShowAddDialog(true)}
style={tw`bg-blue-600 p-4 rounded-full shadow-lg`}
style={tw`bg-blue-600 p-4 rounded-full shadow-lg elevation-5`}
>
<MaterialIcons name="add" size={24} color="white" />
</TouchableOpacity>
</MyTouchableOpacity>
</View>
{/* Add Product Dialog */}
<BottomDialog
open={showAddDialog}
onClose={() => setShowAddDialog(false)}
onClose={() => {
setShowAddDialog(false);
setSelectedProductIds([]);
}}
>
<View style={tw`pb-8 pt-2 px-4`}>
<View style={tw`items-center mb-6`}>
<View style={tw`w-12 h-1.5 bg-gray-200 rounded-full mb-4`} />
<MyText style={tw`text-lg font-bold text-gray-900`}>
Add Popular Item
Add Popular Items
</MyText>
<MyText style={tw`text-sm text-gray-500`}>
Select a product to add to popular items
Select products to add to popular items
</MyText>
</View>
@ -398,41 +411,43 @@ export default function CustomizePopularItems() {
) : (
<>
<ProductsSelector
value={selectedProductId || 0}
onChange={(val) => setSelectedProductId(val as number)}
multiple={false}
label="Select Product"
placeholder="Choose a product..."
value={selectedProductIds}
onChange={(val) => setSelectedProductIds(val as number[])}
multiple={true}
label="Select Products"
placeholder="Choose products..."
labelFormat={(product) => `${product.name} - ₹${product.price}`}
/>
<View style={tw`flex-row gap-3 mt-6`}>
<TouchableOpacity
<MyTouchableOpacity
onPress={() => setShowAddDialog(false)}
style={tw`flex-1 bg-gray-100 p-3 rounded-lg`}
>
<MyText style={tw`text-gray-700 text-center font-semibold`}>
Cancel
</MyText>
</TouchableOpacity>
</MyTouchableOpacity>
<TouchableOpacity
<MyTouchableOpacity
onPress={handleAddProduct}
disabled={!selectedProductId}
disabled={selectedProductIds.length === 0}
style={tw`flex-1 ${
selectedProductId ? 'bg-blue-600' : 'bg-gray-300'
selectedProductIds.length > 0 ? 'bg-blue-600' : 'bg-gray-300'
} p-3 rounded-lg`}
>
<MyText style={tw`text-white text-center font-semibold`}>
Add Product
{selectedProductIds.length > 0
? `Add ${selectedProductIds.length} Product${selectedProductIds.length > 1 ? 's' : ''}`
: 'Add Products'}
</MyText>
</TouchableOpacity>
</MyTouchableOpacity>
</View>
</>
)}
</View>
</BottomDialog>
</View>
</AppContainer>
);
}

View file

@ -44,7 +44,6 @@ export default function EditProduct() {
tagIds: values.tagIds,
};
console.log({payload})
const formData = new FormData();
Object.entries(payload).forEach(([key, value]) => {

View file

@ -63,7 +63,6 @@ const VendorSnippetForm: React.FC<VendorSnippetFormProps> = ({
},
onSubmit: async (values) => {
try {
console.log({values})
const submitData = {
snippetCode: values.snippetCode,

View file

@ -1,7 +1,7 @@
ENV_MODE=PROD
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=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
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
PHONE_PE_CLIENT_VERSION=1

File diff suppressed because one or more lines are too long

View file

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

View file

@ -124,7 +124,6 @@ export const updateProduct = async (req: Request, res: Response) => {
const { id } = req.params;
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = req.body;
console.log({productQuantity})
const deals = dealsRaw ? JSON.parse(dealsRaw) : null;
const imagesToDelete = imagesToDeleteRaw ? JSON.parse(imagesToDeleteRaw) : [];

View file

@ -113,6 +113,7 @@ export async function seed() {
{ key: CONST_KEYS.flashFreeDeliveryThreshold, value: 500 },
{ key: CONST_KEYS.flashDeliveryCharge, value: 69 },
{ key: CONST_KEYS.popularItems, value: [] },
{ key: CONST_KEYS.allItemsOrder, value: [] },
{ 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.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',
platformFeePercent: 'platformFeePercent',
taxRate: 'taxRate',
tester: 'tester',
minOrderAmountForCoupon: 'minOrderAmountForCoupon',
maxCouponDiscount: 'maxCouponDiscount',
flashDeliverySlotId: 'flashDeliverySlotId',
@ -14,6 +15,7 @@ export const CONST_KEYS = {
playStoreUrl: 'playStoreUrl',
appStoreUrl: 'appStoreUrl',
popularItems: 'popularItems',
allItemsOrder: 'allItemsOrder',
isFlashDeliveryEnabled: 'isFlashDeliveryEnabled',
supportMobile: 'supportMobile',
supportEmail: 'supportEmail',
@ -27,6 +29,7 @@ export const CONST_LABELS: Record<ConstKey, string> = {
flashDeliveryCharge: 'Flash Delivery Charge',
platformFeePercent: 'Platform Fee Percent',
taxRate: 'Tax Rate',
tester: 'Tester',
minOrderAmountForCoupon: 'Minimum Order Amount for Coupon',
maxCouponDiscount: 'Maximum Coupon Discount',
flashDeliverySlotId: 'Flash Delivery Slot ID',
@ -35,6 +38,7 @@ export const CONST_LABELS: Record<ConstKey, string> = {
playStoreUrl: 'Play Store URL',
appStoreUrl: 'App Store URL',
popularItems: 'Popular Items',
allItemsOrder: 'All Items Order',
isFlashDeliveryEnabled: 'Enable Flash Delivery',
supportMobile: 'Support Mobile',
supportEmail: 'Support Email',

View file

@ -431,7 +431,6 @@ export const orderRouter = router({
.input(updateOrderItemPackagingSchema)
.mutation(async ({ input }) => {
const { orderItemId, isPackaged, isPackageVerified } = input;
console.log({ orderItemId, isPackaged, isPackageVerified });
// Validate that orderItem exists
const orderItem = await db.query.orderItems.findFirst({

View file

@ -543,9 +543,6 @@ export const slotsRouter = router({
const { id, deliverySequence } = input;
console.log({deliverySequence})
const [updatedSlot] = await db
.update(deliverySlotInfo)
.set({ deliverySequence })

View file

@ -130,7 +130,6 @@ export const vendorSnippetsRouter = router({
.input(updateSnippetSchema)
.mutation(async ({ input }) => {
const { id, updates } = input;
console.log({updates})
// Check if snippet exists
const existingSnippet = await db.query.vendorSnippets.findFirst({

View file

@ -29,8 +29,6 @@ export const bannerRouter = router({
}
})
);
console.log({bannersWithSignedUrls})
return {

View file

@ -713,8 +713,6 @@ export const orderRouter = router({
const userId = ctx.user.userId;
const { id, reason } = input;
console.log({id, reason})
// Check if order exists and belongs to user
const order = await db.query.orders.findFirst({
where: eq(orders.id, Number(id)),

View file

@ -114,7 +114,7 @@ export const userRouter = router({
.mutation(async ({ input, ctx }) => {
const userId = ctx.user.userId;
const { token } = input;
if (!userId) {
throw new ApiError('User not authenticated', 401);
}

View file

@ -137,8 +137,6 @@ function ErrorPage() {
export function UserHomeRoute() {
const { user } = useUserStore()
console.log({user})
return (
<div className="max-w-6xl mx-auto space-y-6">
{(user?.role?.name === 'admin' || user?.role?.name === 'super_admin') && <AdminDashboard />}

View file

@ -2,7 +2,7 @@
"expo": {
"name": "Freshyo",
"slug": "freshyo",
"version": "1.1.0",
"version": "1.2.0",
"orientation": "portrait",
"icon": "./assets/images/freshyo-logo.png",
"scheme": "freshyo",
@ -67,7 +67,8 @@
"backgroundColor": "#fff0f6"
},
"edgeToEdgeEnabled": true,
"package": "in.freshyo.app"
"package": "in.freshyo.app",
"googleServicesFile": "./google-services.json"
},
"web": {
"bundler": "metro",
@ -85,7 +86,8 @@
"backgroundColor": "#ffffff"
}
],
"expo-secure-store"
"expo-secure-store",
"expo-notifications"
],
"experiments": {
"typedRoutes": true

View file

@ -8,6 +8,7 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from '@expo/vector-icons/Ionicons';
import dayjs from "dayjs";
import ComplaintForm from "@/components/ComplaintForm";
import { orderStatusManipulator } from "@/src/lib/string-manipulators";
export default function OrderDetails() {
const { id } = useLocalSearchParams<{ id: string }>();
@ -145,7 +146,7 @@ export default function OrderDetails() {
<View style={tw`flex-row items-center gap-2`}>
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
{statusConfig.label}
{orderStatusManipulator(statusConfig.label)}
</MyText>
</View>
{order.isFlashDelivery && (

View file

@ -9,6 +9,7 @@ import { trpc } from '@/src/trpc-client';
// import RazorpayCheckout from 'react-native-razorpay';
import OrderMenu from '@/components/OrderMenu';
import dayjs from 'dayjs';
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
// Type definitions
interface OrderItem {
@ -136,9 +137,9 @@ const OrderItem: React.FC<OrderItemProps> = ({
{item.orderStatus.toLowerCase() === 'cancelled' && (
<View style={[tw`flex-row items-center px-3 py-1.5 rounded-full border ${mainStatus.bg} ${mainStatus.border}`]}>
<MaterialIcons name={mainStatus.icon as any} size={14} color={mainStatus.color} />
<MyText style={[tw`text-[11px] font-bold ml-1.5 uppercase tracking-wide`, { color: mainStatus.color }]}>
{mainStatus.label}
</MyText>
<MyText style={[tw`text-[11px] font-bold ml-1.5 uppercase tracking-wide`, { color: mainStatus.color }]}>
{orderStatusManipulator(mainStatus.label)}
</MyText>
</View>
)}
{item.isFlashDelivery && (
@ -265,7 +266,7 @@ const OrderItem: React.FC<OrderItemProps> = ({
{item.isFlashDelivery ? "1 Hr Delivery:" : "Shipping Status:"}
</MyText>
<MyText style={[tw`text-xs font-bold`, item.isFlashDelivery ? tw`text-amber-700` : { color: deliveryStatus.color }]} numberOfLines={1}>
{item.deliveryStatus}
{orderStatusManipulator(item.deliveryStatus)}
</MyText>
</View>
{item.isFlashDelivery && (

View file

@ -35,6 +35,8 @@ export default function RootLayout() {
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
console.log('from layout')
React.useEffect(() => {
Appearance.setColorScheme('light')
}, []);

View file

@ -7,6 +7,7 @@ import { MaterialIcons, Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
import { trpc } from '@/src/trpc-client';
import { Image } from 'expo-image';
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
interface OrderItem {
productName: string;
@ -126,7 +127,7 @@ export default function NextOrderGlimpse() {
</MyText>
</View>
<View style={tw`bg-white px-2 py-1 rounded-lg border border-amber-200`}>
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{nextOrder.deliveryStatus}</MyText>
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{orderStatusManipulator(nextOrder.deliveryStatus)}</MyText>
</View>
</View>

View file

@ -88,6 +88,10 @@ const ProductCard: React.FC<ProductCardProps> = ({
const slotId = getQuickestSlot(item.id);
const displayIsOutOfStock = item.isOutOfStock || !slotId;
// if(item.name.startsWith('Mutton Curry Cut')) {
// console.log({slotId, displayIsOutOfStock})
// }
// Return null if nullIfNotAvailable is true and the product is out of stock
if (nullIfNotAvailable && displayIsOutOfStock) {
return null;

View file

@ -397,7 +397,14 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
);
}
const filteredProducts: any[] = storeIdNum ? productsQuery?.data?.filter(p => p.store?.id === storeIdNum) || [] : slotQuery.data.products;
// Create a Set of product IDs from slot data for O(1) lookup
const slotProductIds = new Set(slotQuery.data.products?.map((p: any) => p.id) || []);
const filteredProducts: any[] = storeIdNum
? productsQuery?.data?.filter(p =>
p.store?.id === storeIdNum && slotProductIds.has(p.id)
) || []
: slotQuery.data.products;
return (
<View style={tw`flex-1`}>
@ -481,11 +488,18 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
// Filter products to only include those eligible for flash delivery
let flashProducts: any[] = [];
if (storeIdNum) {
// Filter by store and flash availability
flashProducts = productsQuery?.data?.filter(p => p.store?.id === storeIdNum && p.isFlashAvailable) || [];
// Filter by store, flash availability, and stock status
flashProducts = productsQuery?.data?.filter(p =>
p.store?.id === storeIdNum &&
p.isFlashAvailable &&
!p.isOutOfStock
) || [];
} else {
// Show all flash-available products (no slot filtering)
flashProducts = productsQuery?.data?.filter(p => p.isFlashAvailable) || [];
// Show all flash-available products that are in stock
flashProducts = productsQuery?.data?.filter(p =>
p.isFlashAvailable &&
!p.isOutOfStock
) || [];
}
return (

View file

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "535753078248",
"project_id": "freshyo-cefb2",
"storage_bucket": "freshyo-cefb2.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:535753078248:android:d00803550e29eb3238605d",
"android_client_info": {
"package_name": "in.freshyo.app"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDW4w6vGDrZnkf8vgwdQObBLnT_iucs2H8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View file

@ -3,7 +3,8 @@ import dayjs from 'dayjs';
export function useProductSlotIdentifier() {
// Fetch all slots with products
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
const { data: slotsData, isLoading: isProductsLoading } = trpc.user.slots.getSlotsWithProducts.useQuery();
const productSlotsMap = new Map<number, number[]>();
@ -24,16 +25,18 @@ export function useProductSlotIdentifier() {
}
const getQuickestSlot = (productId: number): number | null => {
if (!slotsData?.slots) return null;
const now = dayjs();
// Find slots that contain this product and have future delivery time
const availableSlots = slotsData.slots.filter(slot =>
slot.products.some(product => product.id === productId) &&
dayjs(slot.deliveryTime).isAfter(now)
);
// if(productId === 98)
// console.log(JSON.stringify(slotsData))
if (availableSlots.length === 0) return null;
// Return earliest slot ID (sorted by delivery time)

View file

@ -19,6 +19,7 @@ function NotifChecker(props: Props) {
const { notifPermission, expoPushToken } = useNotification();
React.useEffect(() => {
console.log({isAuthenticated, expoPushToken, notifPermission});
if (isAuthenticated && expoPushToken && notifPermission === 'granted') {
savePushTokenMutation.mutate(
{ token: expoPushToken },

View file

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

View file

@ -0,0 +1,27 @@
/**
* String manipulation utilities for the user UI
*
* This file contains helper functions for transforming and formatting
* strings throughout the application. These utilities ensure consistent
* display formatting across all components.
*/
/**
* Transforms delivery status for display.
*
* Why: The term "Pending" can be confusing for users as it implies
* uncertainty. "Confirmed" provides clearer communication that the
* order has been received and acknowledged by the system.
*
* @param status - The raw delivery status from API
* @returns User-friendly status label
* @example
* orderStatusManipulator('pending') // returns 'Confirmed'
* orderStatusManipulator('packaged') // returns 'packaged' (unchanged)
*/
export const orderStatusManipulator = (status: string): string => {
if (status.toLowerCase() === 'pending') {
return 'Confirmed';
}
return status;
};

View file

@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
// const BASE_API_URL = API_URL;
// const BASE_API_URL = 'http://10.0.2.2:4000';
// const BASE_API_URL = 'http://192.168.100.101:4000';
// const BASE_API_URL = 'http://192.168.1.3:4000';
let BASE_API_URL = "https://mf.freshyo.in";
const BASE_API_URL = 'http://192.168.1.3:4000';
// let BASE_API_URL = "https://mf.freshyo.in";
// let BASE_API_URL = 'http://192.168.100.104:4000';
// let BASE_API_URL = 'http://192.168.29.176:4000';