Compare commits
No commits in common. "d599c2e0044826ec4056213a95357ecb54811cba" and "d234c8a00fd49912699b99dc7eb131d9afc0ced0" have entirely different histories.
d599c2e004
...
d234c8a00f
34 changed files with 82 additions and 599 deletions
File diff suppressed because one or more lines are too long
6
apps/admin-ui/.expo/types/router.d.ts
vendored
6
apps/admin-ui/.expo/types/router.d.ts
vendored
File diff suppressed because one or more lines are too long
|
|
@ -17,13 +17,6 @@ export default function Layout() {
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
|
||||||
name="all-items-order"
|
|
||||||
options={{
|
|
||||||
title: "All Items Order",
|
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -31,7 +31,6 @@ 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',
|
||||||
|
|
@ -49,7 +48,6 @@ 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`}>
|
||||||
|
|
@ -69,28 +67,6 @@ 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 (
|
||||||
|
|
@ -158,7 +134,6 @@ 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) => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
ScrollView,
|
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import DraggableFlatList, {
|
import DraggableFlatList, {
|
||||||
RenderItemParams,
|
RenderItemParams,
|
||||||
|
|
@ -17,7 +16,6 @@ import {
|
||||||
tw,
|
tw,
|
||||||
BottomDialog,
|
BottomDialog,
|
||||||
BottomDropdown,
|
BottomDropdown,
|
||||||
MyTouchableOpacity,
|
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import ProductsSelector from "../../../components/ProductsSelector";
|
import ProductsSelector from "../../../components/ProductsSelector";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
|
@ -29,8 +27,8 @@ interface PopularProduct {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
shortDescription: string | null;
|
shortDescription: string | null;
|
||||||
price: number;
|
price: string;
|
||||||
marketPrice: number | null;
|
marketPrice: string | null;
|
||||||
unit: string;
|
unit: string;
|
||||||
incrementStep: number;
|
incrementStep: number;
|
||||||
productQuantity: number;
|
productQuantity: number;
|
||||||
|
|
@ -121,7 +119,7 @@ export default function CustomizePopularItems() {
|
||||||
const [popularProducts, setPopularProducts] = useState<PopularProduct[]>([]);
|
const [popularProducts, setPopularProducts] = useState<PopularProduct[]>([]);
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||||
const [selectedProductIds, setSelectedProductIds] = useState<number[]>([]);
|
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
|
||||||
|
|
||||||
// Get current popular items from constants
|
// Get current popular items from constants
|
||||||
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
|
const { data: constants, isLoading: isLoadingConstants, error: constantsError } = trpc.admin.const.getConstants.useQuery();
|
||||||
|
|
@ -184,21 +182,15 @@ export default function CustomizePopularItems() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddProduct = () => {
|
const handleAddProduct = () => {
|
||||||
if (selectedProductIds.length > 0) {
|
if (selectedProductId) {
|
||||||
const newProducts = selectedProductIds
|
const product = allProducts?.products.find(p => p.id === selectedProductId);
|
||||||
.map(id => allProducts?.products.find(p => p.id === id))
|
if (product && !popularProducts.find(p => p.id === product.id)) {
|
||||||
.filter((product): product is NonNullable<typeof product> =>
|
setPopularProducts(prev => [...prev, product as PopularProduct]);
|
||||||
product !== undefined && !popularProducts.find(p => p.id === product.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newProducts.length > 0) {
|
|
||||||
setPopularProducts(prev => [...prev, ...newProducts as PopularProduct[]]);
|
|
||||||
setHasChanges(true);
|
setHasChanges(true);
|
||||||
}
|
setSelectedProductId(null);
|
||||||
|
|
||||||
setSelectedProductIds([]);
|
|
||||||
setShowAddDialog(false);
|
setShowAddDialog(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
|
@ -301,19 +293,20 @@ export default function CustomizePopularItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[tw`flex-1 bg-gray-50 relative`]}>
|
<AppContainer>
|
||||||
|
<View style={tw`flex-1 bg-gray-50`}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
|
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center justify-between`}>
|
||||||
<MyTouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.back()}
|
onPress={() => router.back()}
|
||||||
style={tw`p-2 -ml-4`}
|
style={tw`p-2 -ml-4`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="chevron-left" size={24} color="#374151" />
|
<MaterialIcons name="chevron-left" size={24} color="#374151" />
|
||||||
</MyTouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<MyText style={tw`text-xl font-bold text-gray-900`}>Popular Items</MyText>
|
<MyText style={tw`text-xl font-bold text-gray-900`}>Popular Items</MyText>
|
||||||
|
|
||||||
<MyTouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
disabled={!hasChanges || updateConstants.isPending}
|
disabled={!hasChanges || updateConstants.isPending}
|
||||||
style={tw`px-4 py-2 rounded-lg ${
|
style={tw`px-4 py-2 rounded-lg ${
|
||||||
|
|
@ -329,7 +322,7 @@ export default function CustomizePopularItems() {
|
||||||
} font-semibold`}>
|
} font-semibold`}>
|
||||||
{updateConstants.isPending ? 'Saving...' : 'Save'}
|
{updateConstants.isPending ? 'Saving...' : 'Save'}
|
||||||
</MyText>
|
</MyText>
|
||||||
</MyTouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
|
|
@ -363,41 +356,35 @@ export default function CustomizePopularItems() {
|
||||||
)}
|
)}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
showsVerticalScrollIndicator={true}
|
showsVerticalScrollIndicator={false}
|
||||||
scrollEnabled={true}
|
contentContainerStyle={tw`pb-8`}
|
||||||
contentContainerStyle={{ paddingBottom: 80 }}
|
|
||||||
containerStyle={tw`flex-1`}
|
|
||||||
keyboardShouldPersistTaps="handled"
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FAB for Add Product - Fixed position */}
|
{/* FAB for Add Product */}
|
||||||
<View style={tw`absolute bottom-12 right-6 z-50`}>
|
<View style={tw`absolute bottom-4 right-4`}>
|
||||||
<MyTouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => setShowAddDialog(true)}
|
onPress={() => setShowAddDialog(true)}
|
||||||
style={tw`bg-blue-600 p-4 rounded-full shadow-lg elevation-5`}
|
style={tw`bg-blue-600 p-4 rounded-full shadow-lg`}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="add" size={24} color="white" />
|
<MaterialIcons name="add" size={24} color="white" />
|
||||||
</MyTouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Add Product Dialog */}
|
{/* Add Product Dialog */}
|
||||||
<BottomDialog
|
<BottomDialog
|
||||||
open={showAddDialog}
|
open={showAddDialog}
|
||||||
onClose={() => {
|
onClose={() => setShowAddDialog(false)}
|
||||||
setShowAddDialog(false);
|
|
||||||
setSelectedProductIds([]);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<View style={tw`pb-8 pt-2 px-4`}>
|
<View style={tw`pb-8 pt-2 px-4`}>
|
||||||
<View style={tw`items-center mb-6`}>
|
<View style={tw`items-center mb-6`}>
|
||||||
<View style={tw`w-12 h-1.5 bg-gray-200 rounded-full mb-4`} />
|
<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`}>
|
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
||||||
Add Popular Items
|
Add Popular Item
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={tw`text-sm text-gray-500`}>
|
<MyText style={tw`text-sm text-gray-500`}>
|
||||||
Select products to add to popular items
|
Select a product to add to popular items
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -411,43 +398,41 @@ export default function CustomizePopularItems() {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ProductsSelector
|
<ProductsSelector
|
||||||
value={selectedProductIds}
|
value={selectedProductId || 0}
|
||||||
onChange={(val) => setSelectedProductIds(val as number[])}
|
onChange={(val) => setSelectedProductId(val as number)}
|
||||||
multiple={true}
|
multiple={false}
|
||||||
label="Select Products"
|
label="Select Product"
|
||||||
placeholder="Choose products..."
|
placeholder="Choose a product..."
|
||||||
labelFormat={(product) => `${product.name} - ₹${product.price}`}
|
labelFormat={(product) => `${product.name} - ₹${product.price}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={tw`flex-row gap-3 mt-6`}>
|
<View style={tw`flex-row gap-3 mt-6`}>
|
||||||
<MyTouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => setShowAddDialog(false)}
|
onPress={() => setShowAddDialog(false)}
|
||||||
style={tw`flex-1 bg-gray-100 p-3 rounded-lg`}
|
style={tw`flex-1 bg-gray-100 p-3 rounded-lg`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-gray-700 text-center font-semibold`}>
|
<MyText style={tw`text-gray-700 text-center font-semibold`}>
|
||||||
Cancel
|
Cancel
|
||||||
</MyText>
|
</MyText>
|
||||||
</MyTouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<MyTouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={handleAddProduct}
|
onPress={handleAddProduct}
|
||||||
disabled={selectedProductIds.length === 0}
|
disabled={!selectedProductId}
|
||||||
style={tw`flex-1 ${
|
style={tw`flex-1 ${
|
||||||
selectedProductIds.length > 0 ? 'bg-blue-600' : 'bg-gray-300'
|
selectedProductId ? 'bg-blue-600' : 'bg-gray-300'
|
||||||
} p-3 rounded-lg`}
|
} p-3 rounded-lg`}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-white text-center font-semibold`}>
|
<MyText style={tw`text-white text-center font-semibold`}>
|
||||||
{selectedProductIds.length > 0
|
Add Product
|
||||||
? `Add ${selectedProductIds.length} Product${selectedProductIds.length > 1 ? 's' : ''}`
|
|
||||||
: 'Add Products'}
|
|
||||||
</MyText>
|
</MyText>
|
||||||
</MyTouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</BottomDialog>
|
</BottomDialog>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
</AppContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ export default function EditProduct() {
|
||||||
tagIds: values.tagIds,
|
tagIds: values.tagIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log({payload})
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.entries(payload).forEach(([key, value]) => {
|
Object.entries(payload).forEach(([key, value]) => {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ const VendorSnippetForm: React.FC<VendorSnippetFormProps> = ({
|
||||||
},
|
},
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
try {
|
try {
|
||||||
|
console.log({values})
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
snippetCode: values.snippetCode,
|
snippetCode: values.snippetCode,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ export const updateProduct = async (req: Request, res: Response) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = req.body;
|
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 deals = dealsRaw ? JSON.parse(dealsRaw) : null;
|
||||||
const imagesToDelete = imagesToDeleteRaw ? JSON.parse(imagesToDeleteRaw) : [];
|
const imagesToDelete = imagesToDeleteRaw ? JSON.parse(imagesToDeleteRaw) : [];
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,6 @@ 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' },
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ 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',
|
||||||
|
|
@ -15,7 +14,6 @@ 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',
|
||||||
|
|
@ -29,7 +27,6 @@ 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',
|
||||||
|
|
@ -38,7 +35,6 @@ 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',
|
||||||
|
|
|
||||||
|
|
@ -431,6 +431,7 @@ export const orderRouter = router({
|
||||||
.input(updateOrderItemPackagingSchema)
|
.input(updateOrderItemPackagingSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { orderItemId, isPackaged, isPackageVerified } = input;
|
const { orderItemId, isPackaged, isPackageVerified } = input;
|
||||||
|
console.log({ orderItemId, isPackaged, isPackageVerified });
|
||||||
|
|
||||||
// Validate that orderItem exists
|
// Validate that orderItem exists
|
||||||
const orderItem = await db.query.orderItems.findFirst({
|
const orderItem = await db.query.orderItems.findFirst({
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,9 @@ export const slotsRouter = router({
|
||||||
|
|
||||||
const { id, deliverySequence } = input;
|
const { id, deliverySequence } = input;
|
||||||
|
|
||||||
|
|
||||||
|
console.log({deliverySequence})
|
||||||
|
|
||||||
const [updatedSlot] = await db
|
const [updatedSlot] = await db
|
||||||
.update(deliverySlotInfo)
|
.update(deliverySlotInfo)
|
||||||
.set({ deliverySequence })
|
.set({ deliverySequence })
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ export const vendorSnippetsRouter = router({
|
||||||
.input(updateSnippetSchema)
|
.input(updateSnippetSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { id, updates } = input;
|
const { id, updates } = input;
|
||||||
|
console.log({updates})
|
||||||
|
|
||||||
// Check if snippet exists
|
// Check if snippet exists
|
||||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ export const bannerRouter = router({
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log({bannersWithSignedUrls})
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
banners: bannersWithSignedUrls,
|
banners: bannersWithSignedUrls,
|
||||||
|
|
|
||||||
|
|
@ -713,6 +713,8 @@ export const orderRouter = router({
|
||||||
const userId = ctx.user.userId;
|
const userId = ctx.user.userId;
|
||||||
const { id, reason } = input;
|
const { id, reason } = input;
|
||||||
|
|
||||||
|
console.log({id, reason})
|
||||||
|
|
||||||
// Check if order exists and belongs to user
|
// Check if order exists and belongs to user
|
||||||
const order = await db.query.orders.findFirst({
|
const order = await db.query.orders.findFirst({
|
||||||
where: eq(orders.id, Number(id)),
|
where: eq(orders.id, Number(id)),
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ function ErrorPage() {
|
||||||
export function UserHomeRoute() {
|
export function UserHomeRoute() {
|
||||||
const { user } = useUserStore()
|
const { user } = useUserStore()
|
||||||
|
|
||||||
|
console.log({user})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto space-y-6">
|
<div className="max-w-6xl mx-auto space-y-6">
|
||||||
{(user?.role?.name === 'admin' || user?.role?.name === 'super_admin') && <AdminDashboard />}
|
{(user?.role?.name === 'admin' || user?.role?.name === 'super_admin') && <AdminDashboard />}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "Freshyo",
|
"name": "Freshyo",
|
||||||
"slug": "freshyo",
|
"slug": "freshyo",
|
||||||
"version": "1.2.0",
|
"version": "1.1.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/freshyo-logo.png",
|
"icon": "./assets/images/freshyo-logo.png",
|
||||||
"scheme": "freshyo",
|
"scheme": "freshyo",
|
||||||
|
|
@ -67,8 +67,7 @@
|
||||||
"backgroundColor": "#fff0f6"
|
"backgroundColor": "#fff0f6"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": true,
|
"edgeToEdgeEnabled": true,
|
||||||
"package": "in.freshyo.app",
|
"package": "in.freshyo.app"
|
||||||
"googleServicesFile": "./google-services.json"
|
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro",
|
"bundler": "metro",
|
||||||
|
|
@ -86,8 +85,7 @@
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"expo-secure-store",
|
"expo-secure-store"
|
||||||
"expo-notifications"
|
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import ComplaintForm from "@/components/ComplaintForm";
|
import ComplaintForm from "@/components/ComplaintForm";
|
||||||
import { orderStatusManipulator } from "@/src/lib/string-manipulators";
|
|
||||||
|
|
||||||
export default function OrderDetails() {
|
export default function OrderDetails() {
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
|
|
@ -146,7 +145,7 @@ export default function OrderDetails() {
|
||||||
<View style={tw`flex-row items-center gap-2`}>
|
<View style={tw`flex-row items-center gap-2`}>
|
||||||
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
|
<View style={[tw`px-3 py-1 rounded-full`, { backgroundColor: statusConfig.color + '10' }]}>
|
||||||
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
|
<MyText style={[tw`text-[10px] font-bold uppercase`, { color: statusConfig.color }]}>
|
||||||
{orderStatusManipulator(statusConfig.label)}
|
{statusConfig.label}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{order.isFlashDelivery && (
|
{order.isFlashDelivery && (
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { trpc } from '@/src/trpc-client';
|
||||||
// import RazorpayCheckout from 'react-native-razorpay';
|
// import RazorpayCheckout from 'react-native-razorpay';
|
||||||
import OrderMenu from '@/components/OrderMenu';
|
import OrderMenu from '@/components/OrderMenu';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
|
||||||
|
|
||||||
// Type definitions
|
// Type definitions
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
|
|
@ -138,7 +137,7 @@ const OrderItem: React.FC<OrderItemProps> = ({
|
||||||
<View style={[tw`flex-row items-center px-3 py-1.5 rounded-full border ${mainStatus.bg} ${mainStatus.border}`]}>
|
<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} />
|
<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 }]}>
|
<MyText style={[tw`text-[11px] font-bold ml-1.5 uppercase tracking-wide`, { color: mainStatus.color }]}>
|
||||||
{orderStatusManipulator(mainStatus.label)}
|
{mainStatus.label}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
@ -266,7 +265,7 @@ const OrderItem: React.FC<OrderItemProps> = ({
|
||||||
{item.isFlashDelivery ? "1 Hr Delivery:" : "Shipping Status:"}
|
{item.isFlashDelivery ? "1 Hr Delivery:" : "Shipping Status:"}
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={[tw`text-xs font-bold`, item.isFlashDelivery ? tw`text-amber-700` : { color: deliveryStatus.color }]} numberOfLines={1}>
|
<MyText style={[tw`text-xs font-bold`, item.isFlashDelivery ? tw`text-amber-700` : { color: deliveryStatus.color }]} numberOfLines={1}>
|
||||||
{orderStatusManipulator(item.deliveryStatus)}
|
{item.deliveryStatus}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{item.isFlashDelivery && (
|
{item.isFlashDelivery && (
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ export default function RootLayout() {
|
||||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('from layout')
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
Appearance.setColorScheme('light')
|
Appearance.setColorScheme('light')
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { MaterialIcons, Ionicons } from '@expo/vector-icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { orderStatusManipulator } from '@/src/lib/string-manipulators';
|
|
||||||
|
|
||||||
interface OrderItem {
|
interface OrderItem {
|
||||||
productName: string;
|
productName: string;
|
||||||
|
|
@ -127,7 +126,7 @@ export default function NextOrderGlimpse() {
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
<View style={tw`bg-white px-2 py-1 rounded-lg border border-amber-200`}>
|
<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`}>{orderStatusManipulator(nextOrder.deliveryStatus)}</MyText>
|
<MyText style={tw`text-[10px] font-bold text-amber-700 uppercase`}>{nextOrder.deliveryStatus}</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,6 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
const slotId = getQuickestSlot(item.id);
|
const slotId = getQuickestSlot(item.id);
|
||||||
const displayIsOutOfStock = item.isOutOfStock || !slotId;
|
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
|
// Return null if nullIfNotAvailable is true and the product is out of stock
|
||||||
if (nullIfNotAvailable && displayIsOutOfStock) {
|
if (nullIfNotAvailable && displayIsOutOfStock) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -397,14 +397,7 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Set of product IDs from slot data for O(1) lookup
|
const filteredProducts: any[] = storeIdNum ? productsQuery?.data?.filter(p => p.store?.id === storeIdNum) || [] : slotQuery.data.products;
|
||||||
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 (
|
return (
|
||||||
<View style={tw`flex-1`}>
|
<View style={tw`flex-1`}>
|
||||||
|
|
@ -488,18 +481,11 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
|
||||||
// Filter products to only include those eligible for flash delivery
|
// Filter products to only include those eligible for flash delivery
|
||||||
let flashProducts: any[] = [];
|
let flashProducts: any[] = [];
|
||||||
if (storeIdNum) {
|
if (storeIdNum) {
|
||||||
// Filter by store, flash availability, and stock status
|
// Filter by store and flash availability
|
||||||
flashProducts = productsQuery?.data?.filter(p =>
|
flashProducts = productsQuery?.data?.filter(p => p.store?.id === storeIdNum && p.isFlashAvailable) || [];
|
||||||
p.store?.id === storeIdNum &&
|
|
||||||
p.isFlashAvailable &&
|
|
||||||
!p.isOutOfStock
|
|
||||||
) || [];
|
|
||||||
} else {
|
} else {
|
||||||
// Show all flash-available products that are in stock
|
// Show all flash-available products (no slot filtering)
|
||||||
flashProducts = productsQuery?.data?.filter(p =>
|
flashProducts = productsQuery?.data?.filter(p => p.isFlashAvailable) || [];
|
||||||
p.isFlashAvailable &&
|
|
||||||
!p.isOutOfStock
|
|
||||||
) || [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
@ -3,8 +3,7 @@ import dayjs from 'dayjs';
|
||||||
|
|
||||||
export function useProductSlotIdentifier() {
|
export function useProductSlotIdentifier() {
|
||||||
// Fetch all slots with products
|
// Fetch all slots with products
|
||||||
const { data: slotsData, isLoading: isProductsLoading } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
||||||
|
|
||||||
|
|
||||||
const productSlotsMap = new Map<number, number[]>();
|
const productSlotsMap = new Map<number, number[]>();
|
||||||
|
|
||||||
|
|
@ -25,7 +24,6 @@ export function useProductSlotIdentifier() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getQuickestSlot = (productId: number): number | null => {
|
const getQuickestSlot = (productId: number): number | null => {
|
||||||
|
|
||||||
if (!slotsData?.slots) return null;
|
if (!slotsData?.slots) return null;
|
||||||
|
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
|
|
@ -35,8 +33,7 @@ export function useProductSlotIdentifier() {
|
||||||
slot.products.some(product => product.id === productId) &&
|
slot.products.some(product => product.id === productId) &&
|
||||||
dayjs(slot.deliveryTime).isAfter(now)
|
dayjs(slot.deliveryTime).isAfter(now)
|
||||||
);
|
);
|
||||||
// if(productId === 98)
|
|
||||||
// console.log(JSON.stringify(slotsData))
|
|
||||||
if (availableSlots.length === 0) return null;
|
if (availableSlots.length === 0) return null;
|
||||||
|
|
||||||
// Return earliest slot ID (sorted by delivery time)
|
// Return earliest slot ID (sorted by delivery time)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ function NotifChecker(props: Props) {
|
||||||
const { notifPermission, expoPushToken } = useNotification();
|
const { notifPermission, expoPushToken } = useNotification();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
console.log({isAuthenticated, expoPushToken, notifPermission});
|
|
||||||
if (isAuthenticated && expoPushToken && notifPermission === 'granted') {
|
if (isAuthenticated && expoPushToken && notifPermission === 'granted') {
|
||||||
savePushTokenMutation.mutate(
|
savePushTokenMutation.mutate(
|
||||||
{ token: expoPushToken },
|
{ token: expoPushToken },
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,11 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerForPushNotificationsAsync()
|
registerForPushNotificationsAsync()
|
||||||
.then((token) => {
|
.then((token) => {
|
||||||
|
|
||||||
|
|
||||||
setExpoPushToken(token);
|
setExpoPushToken(token);
|
||||||
setNotifPermission("granted");
|
setNotifPermission("granted");
|
||||||
})
|
})
|
||||||
.catch((errorRaw) => {
|
.catch((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
|
||||||
|
|
||||||
if (err === NOTIF_PERMISSION_DENIED) {
|
if (err === NOTIF_PERMISSION_DENIED) {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
};
|
|
||||||
|
|
@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// 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.100.101:4000';
|
||||||
const BASE_API_URL = 'http://192.168.1.3:4000';
|
// const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
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.100.104:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue