This commit is contained in:
shafi54 2026-08-12 23:11:45 +05:30
parent e5d9de33d7
commit e2c6a292a4
65 changed files with 413 additions and 353 deletions

View file

@ -5,7 +5,10 @@
"Shell(npx tsc --noEmit 2 >& 1)", "Shell(npx tsc --noEmit 2 >& 1)",
"Shell(grep:*)", "Shell(grep:*)",
"Shell(npx tsc --noEmit -p packages/shared/tsconfig.json 2 >& 1)", "Shell(npx tsc --noEmit -p packages/shared/tsconfig.json 2 >& 1)",
"Shell(cp:*)" "Shell(cp:*)",
"Shell(rm:*)",
"Shell(ls:*)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/apps/admin-ui && ls \"app/(drawer)\" 2>/dev/null; echo \"---\"; find app -name \"create.tsx\" -path \"*coupon*\" 2>/dev/null)"
], ],
"deny": [], "deny": [],
"defaultMode": "default" "defaultMode": "default"

View file

@ -19,3 +19,4 @@
- Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9 - Prefers test plans written for a non-technical audience, using plain-language, click-by-click instructions ("tap this", "type that", "check there") rather than technical terms or API names. Confidence: 0.9
- When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9 - When updating or creating a document, prefers the agent first compare it against existing source documents, identify missing items or gaps, and add them in the same established format. Confidence: 0.9
- Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9 - Dislikes nested headers in mobile/drawer navigation; prefers a single, shared header (e.g., the drawer header) and relies on device back buttons or gestures for returning to previous screens. Confidence: 0.9
- After reviewing a presented plan, prefers brief, action-oriented approval (e.g., "nice. go ahead") before implementation proceeds. Confidence: 0.6

View file

@ -1,7 +1,6 @@
import { Drawer } from "expo-router/drawer"; import { Drawer } from "expo-router/drawer";
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer"; import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
import { useRouter, Redirect } from "expo-router"; import { useRouter, Redirect } from "expo-router";
import { useNavigation, DrawerActions } from "@react-navigation/native";
import { import {
TouchableOpacity, TouchableOpacity,
DeviceEventEmitter, DeviceEventEmitter,
@ -13,6 +12,25 @@ import { REFRESH_EVENT } from "common-ui/src/lib/const-strs";
import { useStaffAuth } from "@/components/context/staff-auth-context"; import { useStaffAuth } from "@/components/context/staff-auth-context";
import { tw, MyText, theme } from "common-ui"; import { tw, MyText, theme } from "common-ui";
const SCREEN_TITLES: Record<string, string> = {
index: "Dashboard",
products: "Products",
"prices-overview": "Prices Overview",
"product-groupings": "Product Groupings",
"dashboard-banners": "Dashboard Banners",
slots: "Slots",
complaints: "Complaints",
"manage-orders": "Manage Orders",
coupons: "Coupons",
"vendor-snippets": "Vendor Snippets",
stores: "Stores",
"user-management": "User Management",
"send-notifications": "Send Notifications",
"product-tags": "Product Tags",
"rebalance-orders": "Rebalance Orders",
"customize-app": "Customize App",
};
function CustomDrawerContent() { function CustomDrawerContent() {
const router = useRouter(); const router = useRouter();
const { logout, staff } = useStaffAuth(); const { logout, staff } = useStaffAuth();
@ -32,121 +50,108 @@ function CustomDrawerContent() {
)} )}
<DrawerItem <DrawerItem
label="Dashboard" label="Dashboard"
onPress={() => router.push("/(drawer)/dashboard" as any)} onPress={() => router.dismissTo("/(drawer)/dashboard" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="dashboard" size={size} color={color} /> <MaterialIcons name="dashboard" size={size} color={color} />
)} )}
/> />
{/* <DrawerItem <DrawerItem
label="Add Product" label="Products"
onPress={() => router.push("/(drawer)/add-product" as any)} onPress={() => router.push("/(drawer)/dashboard/products" as any)}
icon={({ color, size }) => (
<MaterialIcons name="add" size={size} color={color} />
)}
/> */}
<DrawerItem
label="Products"
onPress={() => router.push("/(drawer)/products" as any)}
icon={({ color, size }) => (
<MaterialIcons name="inventory" size={size} color={color} />
)}
/>
<DrawerItem
label="Prices Overview"
onPress={() => router.push("/(drawer)/prices-overview" as any)}
icon={({ color, size }) => (
<MaterialIcons name="attach-money" size={size} color={color} />
)}
/>
<DrawerItem
label="Product Groupings"
onPress={() => router.push("/(drawer)/product-groupings" as any)}
icon={({ color, size }) => (
<MaterialIcons name="group-work" size={size} color={color} />
)}
/>
<DrawerItem
label="Dashboard Banners"
onPress={() => router.push("/(drawer)/dashboard-banners" as any)}
icon={({ color, size }) => (
<MaterialIcons name="view-carousel" size={size} color={color} />
)}
/>
<DrawerItem
label="Slots"
onPress={() => router.push("/(drawer)/slots" as any)}
icon={({ color, size }) => (
<MaterialIcons name="schedule" size={size} color={color} />
)}
/>
{/* <DrawerItem
label="Edit Product"
onPress={() => router.push("/(drawer)/edit-product" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="edit" size={size} color={color} /> <MaterialIcons name="inventory" size={size} color={color} />
)} )}
/> */} />
<DrawerItem
label="Prices Overview"
onPress={() => router.push("/(drawer)/dashboard/prices-overview" as any)}
icon={({ color, size }) => (
<MaterialIcons name="attach-money" size={size} color={color} />
)}
/>
<DrawerItem
label="Product Groupings"
onPress={() => router.push("/(drawer)/dashboard/product-groupings" as any)}
icon={({ color, size }) => (
<MaterialIcons name="group-work" size={size} color={color} />
)}
/>
<DrawerItem
label="Dashboard Banners"
onPress={() => router.push("/(drawer)/dashboard/dashboard-banners" as any)}
icon={({ color, size }) => (
<MaterialIcons name="view-carousel" size={size} color={color} />
)}
/>
<DrawerItem
label="Slots"
onPress={() => router.push("/(drawer)/dashboard/slots" as any)}
icon={({ color, size }) => (
<MaterialIcons name="schedule" size={size} color={color} />
)}
/>
<DrawerItem <DrawerItem
label="Complaints" label="Complaints"
onPress={() => router.push("/(drawer)/complaints" as any)} onPress={() => router.push("/(drawer)/dashboard/complaints" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="report-problem" size={size} color={color} /> <MaterialIcons name="report-problem" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Manage Orders" label="Manage Orders"
onPress={() => router.push("/(drawer)/manage-orders" as any)} onPress={() => router.push("/(drawer)/dashboard/manage-orders" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="shopping-bag" size={size} color={color} /> <MaterialIcons name="shopping-bag" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Coupons" label="Coupons"
onPress={() => router.push("coupons" as any)} onPress={() => router.push("/(drawer)/dashboard/coupons" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="local-offer" size={size} color={color} /> <MaterialIcons name="local-offer" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Vendor Snippets" label="Vendor Snippets"
onPress={() => router.push("vendor-snippets" as any)} onPress={() => router.push("/(drawer)/dashboard/vendor-snippets" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="code" size={size} color={color} /> <MaterialIcons name="code" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Stores" label="Stores"
onPress={() => router.push("/(drawer)/stores" as any)} onPress={() => router.push("/(drawer)/dashboard/stores" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="store" size={size} color={color} /> <MaterialIcons name="store" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="User Management" label="User Management"
onPress={() => router.push("/(drawer)/user-management" as any)} onPress={() => router.push("/(drawer)/dashboard/user-management" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="people" size={size} color={color} /> <MaterialIcons name="people" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Send Notifications" label="Send Notifications"
onPress={() => router.push("/(drawer)/send-notifications" as any)} onPress={() => router.push("/(drawer)/dashboard/send-notifications" as any)}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="campaign" size={size} color={color} /> <MaterialIcons name="campaign" size={size} color={color} />
)} )}
/> />
<DrawerItem <DrawerItem
label="Logout" label="Logout"
onPress={() => logout()} onPress={() => logout()}
icon={({ color, size }) => ( icon={({ color, size }) => (
<MaterialIcons name="logout" size={size} color={color} /> <MaterialIcons name="logout" size={size} color={color} />
)} )}
/> />
</DrawerContentScrollView> </DrawerContentScrollView>
); );
} }
export default function Layout() { export default function Layout() {
const router = useRouter();
const { isLoggedIn, isLoading } = useStaffAuth(); const { isLoggedIn, isLoading } = useStaffAuth();
if (isLoading) { if (isLoading) {
@ -163,74 +168,72 @@ export default function Layout() {
return ( return (
<Drawer <Drawer
backBehavior="history"
drawerContent={CustomDrawerContent} drawerContent={CustomDrawerContent}
screenOptions={({ navigation, route }) => ({ screenOptions={({ navigation }) => {
swipeEnabled: false, const state = navigation.getState();
headerShown: true, const focusedRoute = state?.routes?.[state?.index ?? 0];
headerStyle: { const dashboardStack = (focusedRoute as any)?.state;
backgroundColor: theme.colors.gray1, const nestedIndex = dashboardStack?.index ?? 0;
shadowOpacity: 0, const canGoBack = nestedIndex > 0;
shadowRadius: 0, const nestedRoute = dashboardStack?.routes?.[nestedIndex];
shadowOffset: { height: 0, width: 0 }, const title = SCREEN_TITLES[nestedRoute?.name ?? ""] ?? "Dashboard";
elevation: 0,
}, return {
headerTitleAlign: "center", swipeEnabled: false,
headerLeft: () => ( headerShown: true,
<TouchableOpacity headerStyle: {
onPress={() => (navigation as any).openDrawer()} backgroundColor: theme.colors.gray1,
style={{ shadowOpacity: 0,
marginLeft: 15, shadowRadius: 0,
width: 40, shadowOffset: { height: 0, width: 0 },
height: 40, elevation: 0,
borderRadius: 20, },
backgroundColor: "#f2f2f2", headerTitle: title,
justifyContent: "center", headerTitleAlign: "center",
alignItems: "center", headerLeft: () => (
}} <TouchableOpacity
> onPress={() =>
<MaterialIcons name="menu" size={24} color="black" /> canGoBack ? router.back() : (navigation as any).openDrawer()
</TouchableOpacity> }
), style={{
headerRight: () => ( marginLeft: 15,
<TouchableOpacity width: 40,
style={{ height: 40,
marginRight: 15, borderRadius: 20,
width: 40, backgroundColor: "#f2f2f2",
height: 40, justifyContent: "center",
borderRadius: 20, alignItems: "center",
backgroundColor: "#f2f2f2", }}
justifyContent: "center", >
alignItems: "center", <MaterialIcons
}} name={canGoBack ? "arrow-back" : "menu"}
onPress={() => { size={24}
DeviceEventEmitter.emit(REFRESH_EVENT); color="black"
}} />
> </TouchableOpacity>
<MaterialIcons name="refresh" size={24} color="black" /> ),
</TouchableOpacity> headerRight: () => (
), <TouchableOpacity
})} style={{
marginRight: 15,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: "#f2f2f2",
justifyContent: "center",
alignItems: "center",
}}
onPress={() => {
DeviceEventEmitter.emit(REFRESH_EVENT);
}}
>
<MaterialIcons name="refresh" size={24} color="black" />
</TouchableOpacity>
),
};
}}
> >
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} /> <Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
<Drawer.Screen name="products" options={{ title: "Products" }} /> </Drawer>
<Drawer.Screen name="prices-overview" options={{ title: "Prices Overview" }} />
<Drawer.Screen name="product-groupings" options={{ title: "Product Groupings" }} />
<Drawer.Screen
name="manage-orders"
options={{ title: "Manage Orders" }}
/>
<Drawer.Screen name="complaints" options={{ title: "Complaints" }} />
<Drawer.Screen name="coupons" options={{ title: "Coupons" }} />
<Drawer.Screen name="slots" options={{ title: "Slots" }} />
<Drawer.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
<Drawer.Screen name="stores" options={{ title: "Stores" }} />
<Drawer.Screen name="product-tags" options={{ title: "Product Tags" }} />
<Drawer.Screen name="rebalance-orders" options={{ title: "Rebalance Orders" }} />
<Drawer.Screen name="user-management" options={{ title: "User Management" }} />
<Drawer.Screen name="send-notifications" options={{ title: "Send Notifications" }} />
</Drawer>
); );
} }

View file

@ -2,14 +2,41 @@ import { Stack } from "expo-router";
export default function Layout() { export default function Layout() {
return ( return (
<Stack> <Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" options={{ title: "Dashboard" }} />
<Stack.Screen name="complaints" options={{ title: "Complaints" }} />
<Stack.Screen name="coupons" options={{ title: "Coupons" }} />
<Stack.Screen name="customize-app" options={{ title: "Customize App" }} />
<Stack.Screen <Stack.Screen
name="index" name="dashboard-banners"
options={{ options={{ title: "Dashboard Banners" }}
title: "Dashboard",
headerShown: false,
}}
/> />
<Stack.Screen name="manage-orders" options={{ title: "Manage Orders" }} />
<Stack.Screen
name="prices-overview"
options={{ title: "Prices Overview" }}
/>
<Stack.Screen
name="product-groupings"
options={{ title: "Product Groupings" }}
/>
<Stack.Screen name="product-tags" options={{ title: "Product Tags" }} />
<Stack.Screen name="products" options={{ title: "Products" }} />
<Stack.Screen
name="rebalance-orders"
options={{ title: "Rebalance Orders" }}
/>
<Stack.Screen
name="send-notifications"
options={{ title: "Send Notifications" }}
/>
<Stack.Screen name="slots" options={{ title: "Slots" }} />
<Stack.Screen name="stores" options={{ title: "Stores" }} />
<Stack.Screen
name="user-management"
options={{ title: "User Management" }}
/>
<Stack.Screen name="vendor-snippets" options={{ title: "Vendor Snippets" }} />
</Stack> </Stack>
); );
} }

View file

@ -47,11 +47,11 @@ export default function Complaints() {
}, [hasNextPage, isFetchingNextPage, fetchNextPage]); }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
const handleUserPress = useCallback((userId: number) => { const handleUserPress = useCallback((userId: number) => {
router.push(`/(drawer)/user-management/${userId}`); router.push(`/(drawer)/dashboard/user-management/${userId}`);
}, [router]); }, [router]);
const handleOrderPress = useCallback((orderId: number) => { const handleOrderPress = useCallback((orderId: number) => {
router.push(`/(drawer)/manage-orders/order-details/${orderId}`); router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
}, [router]); }, [router]);
const handleMarkResolved = (id: number) => { const handleMarkResolved = (id: number) => {

View file

@ -24,9 +24,9 @@ export default function CreateCoupon() {
} }
) )
const handleCreateCoupon = (values: any) => { const handleCreateCoupon = async (values: any) => {
console.log('Form values:', values); // Debug log console.log('Form values:', values); // Debug log
const { isReservedCoupon, ...rest } = values; const { isReservedCoupon, couponCodes, ...rest } = values;
// Transform targetUsers array to targetUser for backend compatibility // Transform targetUsers array to targetUser for backend compatibility
const payload = { const payload = {
...rest, ...rest,
@ -35,6 +35,24 @@ export default function CreateCoupon() {
delete payload.targetUsers; delete payload.targetUsers;
console.log('Payload:', payload); // Debug log console.log('Payload:', payload); // Debug log
if (isReservedCoupon && Array.isArray(couponCodes) && couponCodes.length > 0) {
// Create one reserved coupon per code, same config
const codes = couponCodes.map((c: string) => c.trim()).filter(Boolean);
try {
for (const code of codes) {
await createReservedCoupon.mutateAsync({ ...payload, couponCode: code });
}
await refetchCoupons()
await refetchReservedCoupons()
Alert.alert('Success', `${codes.length} reserved coupon${codes.length > 1 ? 's' : ''} created successfully`, [
{ text: 'OK', onPress: () => router.back() }
]);
} catch (error: any) {
Alert.alert('Error', error.message || 'Failed to create reserved coupons');
}
return;
}
const mutation = isReservedCoupon ? createReservedCoupon : createCoupon; const mutation = isReservedCoupon ? createReservedCoupon : createCoupon;
const isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending; const isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending;

View file

@ -90,7 +90,7 @@ const CouponItem = ({ item, onDelete }: { item: any; onDelete: (id: number) => v
<View style={tw`flex-row mt-3 gap-2`}> <View style={tw`flex-row mt-3 gap-2`}>
<TouchableOpacity <TouchableOpacity
onPress={() => router.push(`/coupons/edit/${item.id}`)} onPress={() => router.push(`/(drawer)/dashboard/coupons/edit/${item.id}`)}
style={tw`bg-blue-500 p-3 rounded-lg shadow-md flex-1 flex-row items-center justify-center`} style={tw`bg-blue-500 p-3 rounded-lg shadow-md flex-1 flex-row items-center justify-center`}
> >
<MaterialCommunityIcons name="pencil" size={16} color="white" /> <MaterialCommunityIcons name="pencil" size={16} color="white" />
@ -497,7 +497,7 @@ export default function Coupons() {
)} )}
/> />
<MyTouchableOpacity <MyTouchableOpacity
onPress={() => router.push('/coupons/create')} onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
activeOpacity={0.95} activeOpacity={0.95}
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }} style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
> >

View file

@ -217,7 +217,7 @@ export default function ReservedCoupons() {
</View> </View>
<MyText style={tw`text-xl font-semibold text-gray-600 mb-2`}>No Reserved Coupons Yet</MyText> <MyText style={tw`text-xl font-semibold text-gray-600 mb-2`}>No Reserved Coupons Yet</MyText>
<MyText style={tw`text-gray-500 text-center mb-4`}>Create your first reserved coupon to start offering secret discounts</MyText> <MyText style={tw`text-gray-500 text-center mb-4`}>Create your first reserved coupon to start offering secret discounts</MyText>
<MyButton onPress={() => router.push('/coupons/create')} style={tw`bg-blue-500`}> <MyButton onPress={() => router.push('/(drawer)/dashboard/coupons/create')} style={tw`bg-blue-500`}>
<View style={tw`flex-row items-center`}> <View style={tw`flex-row items-center`}>
<MaterialCommunityIcons name="plus" size={16} color="white" /> <MaterialCommunityIcons name="plus" size={16} color="white" />
<MyText style={tw`text-white font-semibold ml-1`}>Create Reserved Coupon</MyText> <MyText style={tw`text-white font-semibold ml-1`}>Create Reserved Coupon</MyText>
@ -230,7 +230,7 @@ export default function ReservedCoupons() {
{/* FAB for Add New Reserved Coupon */} {/* FAB for Add New Reserved Coupon */}
<MyTouchableOpacity <MyTouchableOpacity
onPress={() => router.push('/coupons/create')} onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
activeOpacity={0.95} activeOpacity={0.95}
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }} style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
> >

View file

@ -18,7 +18,7 @@ import {
MyTouchableOpacity, MyTouchableOpacity,
} from "common-ui"; } from "common-ui";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { trpc } from "../../../src/trpc-client"; import { trpc } from "../../../../src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";

View file

@ -2,7 +2,7 @@ import React from 'react';
import { View, ScrollView, Alert } from 'react-native'; import { View, ScrollView, Alert } from 'react-native';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { MyText, MyTextInput, MyTouchableOpacity, tw, AppContainer, Checkbox } from 'common-ui'; import { MyText, MyTextInput, MyTouchableOpacity, tw, AppContainer, Checkbox } from 'common-ui';
import { trpc } from '../../../src/trpc-client'; import { trpc } from '../../../../src/trpc-client';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import MaterialIcons from '@expo/vector-icons/MaterialIcons';
@ -71,7 +71,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constantKey, value, setFi
{CONST_LABELS[constantKey] || constantKey} {CONST_LABELS[constantKey] || constantKey}
</MyText> </MyText>
<MyTouchableOpacity <MyTouchableOpacity
onPress={() => router.push('/(drawer)/customize-app/popular-items')} onPress={() => router.push('/(drawer)/dashboard/customize-app/popular-items')}
style={tw`bg-blue-50 border-2 border-dashed border-blue-200 p-4 rounded-lg flex-row items-center justify-center`} style={tw`bg-blue-50 border-2 border-dashed border-blue-200 p-4 rounded-lg flex-row items-center justify-center`}
> >
<MaterialIcons name="edit" size={20} color="#3b82f6" style={tw`mr-2`} /> <MaterialIcons name="edit" size={20} color="#3b82f6" style={tw`mr-2`} />
@ -93,7 +93,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constantKey, value, setFi
{CONST_LABELS[constantKey] || constantKey} {CONST_LABELS[constantKey] || constantKey}
</MyText> </MyText>
<MyTouchableOpacity <MyTouchableOpacity
onPress={() => router.push('/(drawer)/customize-app/all-items-order')} onPress={() => router.push('/(drawer)/dashboard/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`} 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`} /> <MaterialIcons name="reorder" size={20} color="#16a34a" style={tw`mr-2`} />

View file

@ -19,9 +19,9 @@ import {
BottomDropdown, BottomDropdown,
MyTouchableOpacity, 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";
import { trpc } from "../../../src/trpc-client"; import { trpc } from "../../../../src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native'; import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native';
import { AppContainer, MyText, tw, MyTouchableOpacity } from 'common-ui'; import { AppContainer, MyText, tw, MyTouchableOpacity } from 'common-ui';
import { trpc } from '../../../src/trpc-client'; import { trpc } from '../../../../src/trpc-client';
import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
@ -180,7 +180,7 @@ export default function DashboardBanners() {
}; };
const handleEdit = (banner: Banner) => { const handleEdit = (banner: Banner) => {
router.push(`/dashboard-banners/edit/${banner.id}` as any); router.push(`/(drawer)/dashboard/dashboard-banners/edit/${banner.id}` as any);
}; };
const handleDelete = (id: number) => { const handleDelete = (id: number) => {
@ -207,7 +207,7 @@ export default function DashboardBanners() {
}; };
const handleCreate = () => { const handleCreate = () => {
router.push('/dashboard-banners/create' as any); router.push('/(drawer)/dashboard/dashboard-banners/create' as any);
}; };
if (isLoading) { if (isLoading) {

View file

@ -63,13 +63,13 @@ export default function Dashboard() {
const handleManageOrdersPress = () => { const handleManageOrdersPress = () => {
setNavigationTarget('/manage-orders/orders'); setNavigationTarget('/(drawer)/dashboard/manage-orders/orders');
router.push('/(drawer)/manage-orders'); router.push('/(drawer)/dashboard/manage-orders');
}; };
const handleDeliverySequencesPress = () => { const handleDeliverySequencesPress = () => {
setNavigationTarget('/manage-orders/delivery-sequences'); setNavigationTarget('/(drawer)/dashboard/manage-orders/delivery-sequences');
router.push('/(drawer)/manage-orders'); router.push('/(drawer)/dashboard/manage-orders');
}; };
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
@ -77,7 +77,7 @@ export default function Dashboard() {
title: 'Manage Orders', title: 'Manage Orders',
icon: 'shopping-bag', icon: 'shopping-bag',
description: 'View and manage customer orders', description: 'View and manage customer orders',
route: '/(drawer)/manage-orders', route: '/(drawer)/dashboard/manage-orders',
category: 'orders', category: 'orders',
iconColor: '#10B981', iconColor: '#10B981',
iconBg: '#D1FAE5', iconBg: '#D1FAE5',
@ -87,7 +87,7 @@ export default function Dashboard() {
title: 'Delivery Sequences', title: 'Delivery Sequences',
icon: 'alt-route', icon: 'alt-route',
description: 'Plan and optimize delivery routes', description: 'Plan and optimize delivery routes',
route: '/manage-orders/delivery-sequences', route: '/(drawer)/dashboard/manage-orders/delivery-sequences',
category: 'orders', category: 'orders',
iconColor: '#8B5CF6', iconColor: '#8B5CF6',
iconBg: '#EDE9FE', iconBg: '#EDE9FE',
@ -97,7 +97,7 @@ export default function Dashboard() {
title: 'Delivery Slots', title: 'Delivery Slots',
icon: 'schedule', icon: 'schedule',
description: 'Configure delivery times', description: 'Configure delivery times',
route: '/(drawer)/slots' as any, route: '/(drawer)/dashboard/slots' as any,
category: 'quick', category: 'quick',
iconColor: '#06B6D4', iconColor: '#06B6D4',
iconBg: '#CFFAFE', iconBg: '#CFFAFE',
@ -107,7 +107,7 @@ export default function Dashboard() {
title: 'Add Product', title: 'Add Product',
icon: 'add-circle', icon: 'add-circle',
description: 'Create a new product listing', description: 'Create a new product listing',
route: '/products/add', route: '/(drawer)/dashboard/products/add',
category: 'quick', category: 'quick',
iconColor: theme.colors.brand500, iconColor: theme.colors.brand500,
iconBg: theme.colors.brand50, iconBg: theme.colors.brand50,
@ -116,7 +116,7 @@ export default function Dashboard() {
title: 'Complaints', title: 'Complaints',
icon: 'report-problem', icon: 'report-problem',
description: 'Handle customer complaints', description: 'Handle customer complaints',
route: '/(drawer)/complaints', route: '/(drawer)/dashboard/complaints',
category: 'quick', category: 'quick',
iconColor: '#F59E0B', iconColor: '#F59E0B',
iconBg: '#FEF3C7', iconBg: '#FEF3C7',
@ -126,7 +126,7 @@ export default function Dashboard() {
title: 'Products', title: 'Products',
icon: 'inventory-2', icon: 'inventory-2',
description: 'Manage product catalog', description: 'Manage product catalog',
route: '/(drawer)/products', route: '/(drawer)/dashboard/products',
category: 'products', category: 'products',
iconColor: '#6366F1', iconColor: '#6366F1',
iconBg: '#E0E7FF', iconBg: '#E0E7FF',
@ -135,7 +135,7 @@ export default function Dashboard() {
title: 'Prices Overview', title: 'Prices Overview',
icon: 'attach-money', icon: 'attach-money',
description: 'Update product pricing', description: 'Update product pricing',
route: '/(drawer)/prices-overview', route: '/(drawer)/dashboard/prices-overview',
category: 'products', category: 'products',
iconColor: '#059669', iconColor: '#059669',
iconBg: '#ECFDF5', iconBg: '#ECFDF5',
@ -144,7 +144,7 @@ export default function Dashboard() {
title: 'Product Tags', title: 'Product Tags',
icon: 'label', icon: 'label',
description: 'Organize with tags', description: 'Organize with tags',
route: '/(drawer)/product-tags', route: '/(drawer)/dashboard/product-tags',
category: 'products', category: 'products',
iconColor: '#EC4899', iconColor: '#EC4899',
iconBg: '#FCE7F3', iconBg: '#FCE7F3',
@ -153,7 +153,7 @@ export default function Dashboard() {
title: 'Product Groupings', title: 'Product Groupings',
icon: 'category', icon: 'category',
description: 'Group products together', description: 'Group products together',
route: '/(drawer)/product-groupings', route: '/(drawer)/dashboard/product-groupings',
category: 'products', category: 'products',
iconColor: '#8B5CF6', iconColor: '#8B5CF6',
iconBg: '#F3E8FF', iconBg: '#F3E8FF',
@ -162,7 +162,7 @@ export default function Dashboard() {
title: 'Stores', title: 'Stores',
icon: 'store', icon: 'store',
description: 'Manage store locations', description: 'Manage store locations',
route: '/(drawer)/stores', route: '/(drawer)/dashboard/stores',
category: 'settings', category: 'settings',
iconColor: '#14B8A6', iconColor: '#14B8A6',
iconBg: '#CCFBF1', iconBg: '#CCFBF1',
@ -171,7 +171,7 @@ export default function Dashboard() {
title: 'Coupons', title: 'Coupons',
icon: 'local-offer', icon: 'local-offer',
description: 'Create discount coupons', description: 'Create discount coupons',
route: '/(drawer)/coupons', route: '/(drawer)/dashboard/coupons',
category: 'marketing', category: 'marketing',
iconColor: '#F97316', iconColor: '#F97316',
iconBg: '#FFEDD5', iconBg: '#FFEDD5',
@ -180,7 +180,7 @@ export default function Dashboard() {
title: 'App Constants', title: 'App Constants',
icon: 'settings-applications', icon: 'settings-applications',
description: 'Customize app settings', description: 'Customize app settings',
route: '/(drawer)/customize-app', route: '/(drawer)/dashboard/customize-app',
category: 'settings', category: 'settings',
iconColor: '#7C3AED', iconColor: '#7C3AED',
iconBg: '#F3E8FF', iconBg: '#F3E8FF',
@ -189,7 +189,7 @@ export default function Dashboard() {
title: 'User Management', title: 'User Management',
icon: 'people', icon: 'people',
description: 'View and manage all users', description: 'View and manage all users',
route: '/(drawer)/user-management', route: '/(drawer)/dashboard/user-management',
category: 'users', category: 'users',
iconColor: '#0EA5E9', iconColor: '#0EA5E9',
iconBg: '#E0F2FE', iconBg: '#E0F2FE',
@ -198,7 +198,7 @@ export default function Dashboard() {
title: 'Send Notifications', title: 'Send Notifications',
icon: 'campaign', icon: 'campaign',
description: 'Send push notifications to users', description: 'Send push notifications to users',
route: '/(drawer)/send-notifications', route: '/(drawer)/dashboard/send-notifications',
category: 'users', category: 'users',
iconColor: '#8B5CF6', iconColor: '#8B5CF6',
iconBg: '#F3E8FF', iconBg: '#F3E8FF',

View file

@ -46,7 +46,7 @@ export default function ManageOrders() {
Alert.alert('Flash Deliveries', 'Flash deliveries do not have delivery sequences. Use the Orders menu to manage flash deliveries.'); Alert.alert('Flash Deliveries', 'Flash deliveries do not have delivery sequences. Use the Orders menu to manage flash deliveries.');
return; return;
} }
router.push(`/manage-orders/delivery-sequences?slotId=${selectedSlotId}`); router.push(`/(drawer)/dashboard/manage-orders/delivery-sequences?slotId=${selectedSlotId}`);
}, },
}, },
{ {
@ -55,9 +55,9 @@ export default function ManageOrders() {
color: 'bg-cyan-500', color: 'bg-cyan-500',
onPress: () => { onPress: () => {
if (selectedSlotId === 'flash') { if (selectedSlotId === 'flash') {
router.push('/manage-orders/orders?filter=flash'); router.push('/(drawer)/dashboard/manage-orders/orders?filter=flash');
} else { } else {
router.push('/manage-orders/orders'); router.push('/(drawer)/dashboard/manage-orders/orders');
} }
}, },
}, },

View file

@ -378,7 +378,7 @@ export default function OrderDetails() {
{/* Customer Details */} {/* Customer Details */}
<TouchableOpacity <TouchableOpacity
onPress={() => order.userId && router.push(`/(drawer)/user-management/${order.userId}`)} onPress={() => order.userId && router.push(`/(drawer)/dashboard/user-management/${order.userId}`)}
activeOpacity={0.7} activeOpacity={0.7}
style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`} style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}
> >
@ -784,7 +784,7 @@ export default function OrderDetails() {
</TouchableOpacity> </TouchableOpacity>
)} )}
<TouchableOpacity <TouchableOpacity
onPress={() => router.push("/(drawer)/manage-orders")} onPress={() => router.push("/(drawer)/dashboard/manage-orders")}
style={tw`bg-gray-900 rounded-xl py-4 items-center shadow-lg`} style={tw`bg-gray-900 rounded-xl py-4 items-center shadow-lg`}
> >
<MyText style={tw`text-white font-bold text-base`}> <MyText style={tw`text-white font-bold text-base`}>

View file

@ -284,17 +284,17 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
> >
<MaterialIcons name="shopping-cart" size={16} color="#3B82F6" /> <MaterialIcons name="shopping-cart" size={16} color="#3B82F6" />
<MyText style={tw`text-sm font-medium text-blue-700 ml-2`}> <MyText style={tw`text-sm font-medium text-blue-700 ml-2`}>
{order.items.length} {order.items.length === 1 ? 'item' : 'items'} {`${order.items.length} ${order.items.length === 1 ? 'item' : 'items'}`}
</MyText> </MyText>
{order.isFlashDelivery && ( {order.isFlashDelivery && (
<View style={tw`ml-2 bg-amber-100 px-1.5 py-0.5 rounded-full`}> <View style={tw`ml-2 bg-amber-100 px-1.5 py-0.5 rounded-full`}>
<MyText style={tw`text-xs font-bold text-amber-700`}></MyText> <MaterialIcons name="bolt" size={12} color="#D97706" />
</View> </View>
)} )}
</TouchableOpacity> </TouchableOpacity>
<View style={tw`flex-row items-center`}> <View style={tw`flex-row items-center`}>
<MyText style={tw`text-base font-semibold text-gray-900 mr-2`}>Total:</MyText> <MyText style={tw`text-base font-semibold text-gray-900 mr-2`}>Total:</MyText>
<MyText style={tw`text-lg font-bold ${order.isFlashDelivery ? 'text-amber-700' : 'text-gray-900'}`}>{order.totalAmount}</MyText> <MyText style={tw`text-lg font-bold ${order.isFlashDelivery ? 'text-amber-700' : 'text-gray-900'}`}>{`${order.totalAmount}`}</MyText>
</View> </View>
</View> </View>
</View> </View>
@ -315,8 +315,8 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
)} )}
{order.discountAmount && ( {order.discountAmount && (
<MyText style={tw`text-sm font-bold text-pink-800`}> <MyText style={tw`text-sm font-bold text-pink-800`}>
Discount: {order.discountAmount} {`Discount: ₹${order.discountAmount}`}
</MyText> </MyText>
)} )}
</View> </View>
</View> </View>
@ -353,7 +353,7 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
<View style={tw`pt-3 border-t border-gray-100`}> <View style={tw`pt-3 border-t border-gray-100`}>
<View style={tw`flex-row justify-between items-center`}> <View style={tw`flex-row justify-between items-center`}>
<MyText style={tw`text-sm text-gray-500`}>Delivery Charge</MyText> <MyText style={tw`text-sm text-gray-500`}>Delivery Charge</MyText>
<MyText style={tw`text-sm text-gray-900`}>{order.deliveryCharge}</MyText> <MyText style={tw`text-sm text-gray-900`}>{`${order.deliveryCharge}`}</MyText>
</View> </View>
</View> </View>
)} )}
@ -407,13 +407,13 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
)} )}
</View> </View>
<MyText style={tw`text-sm text-gray-600 mb-6`}> <MyText style={tw`text-sm text-gray-600 mb-6`}>
Total: {order.totalAmount} {`Total: ₹${order.totalAmount}`}
</MyText> </MyText>
{order.items.map((item, idx) => ( {order.items.map((item, idx) => (
<View key={idx} style={tw`py-2 border-b border-gray-50 last:border-0`}> <View key={idx} style={tw`py-2 border-b border-gray-50 last:border-0`}>
<View style={tw`flex-row items-center`}> <View style={tw`flex-row items-center`}>
<View style={tw`bg-gray-100 px-2 py-1 rounded items-center justify-center mr-2`}> <View style={tw`bg-gray-100 px-2 py-1 rounded items-center justify-center mr-2`}>
<MyText style={tw`text-xs font-bold text-gray-600`}>{item.quantity} x {item.productSize}{item.unit}</MyText> <MyText style={tw`text-xs font-bold text-gray-600`}>{`${item.quantity} x ${item.productSize}${item.unit}`}</MyText>
</View> </View>
<MyText style={tw`text-sm text-gray-800 flex-1`} numberOfLines={1} ellipsizeMode="tail"> <MyText style={tw`text-sm text-gray-800 flex-1`} numberOfLines={1} ellipsizeMode="tail">
{item.name.length > 30 ? `${item.name.substring(0, 30)}...` : item.name} {item.name.length > 30 ? `${item.name.substring(0, 30)}...` : item.name}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { AppContainer } from 'common-ui'; import { AppContainer } from 'common-ui';
import ProductGroupForm from '../../../components/ProductGroupForm'; import ProductGroupForm from '../../../../components/ProductGroupForm';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
export default function CreateProductGroup() { export default function CreateProductGroup() {

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { AppContainer, MyText } from 'common-ui'; import { AppContainer, MyText } from 'common-ui';
import ProductGroupForm from '../../../../components/ProductGroupForm'; import ProductGroupForm from '../../../../../components/ProductGroupForm';
import { useRouter, useLocalSearchParams } from 'expo-router'; import { useRouter, useLocalSearchParams } from 'expo-router';
import { trpc } from '@/src/trpc-client'; import { trpc } from '@/src/trpc-client';

View file

@ -10,7 +10,7 @@ import {
MyTouchableOpacity, MyTouchableOpacity,
BottomDialog, BottomDialog,
} from "common-ui"; } from "common-ui";
import { trpc } from "../../../src/trpc-client"; import { trpc } from "../../../../src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -143,11 +143,11 @@ export default function ProductGroupings() {
}); });
const handleCreate = () => { const handleCreate = () => {
router.push("/product-groupings/create"); router.push("/(drawer)/dashboard/product-groupings/create");
}; };
const handleEdit = (group: ProductGroup) => { const handleEdit = (group: ProductGroup) => {
router.push(`/product-groupings/edit/${group.id}`); router.push(`/(drawer)/dashboard/product-groupings/edit/${group.id}`);
}; };
const handleDelete = (id: number) => { const handleDelete = (id: number) => {

View file

@ -18,7 +18,7 @@ import {
MyTouchableOpacity, MyTouchableOpacity,
} from 'common-ui'; } from 'common-ui';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { trpc } from '../../../src/trpc-client'; import { trpc } from '../../../../src/trpc-client';
import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';

View file

@ -153,7 +153,7 @@ export default function ProductDetail() {
const defaultSku = product?.skus?.[0] const defaultSku = product?.skus?.[0]
const handleEdit = () => { const handleEdit = () => {
router.push(`/products/edit?id=${productId}` as any); router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
}; };

View file

@ -52,11 +52,11 @@ export default function Products() {
}, [products, searchTerm, activeFilter]); }, [products, searchTerm, activeFilter]);
const handleEdit = (productId: number) => { const handleEdit = (productId: number) => {
router.push(`/products/edit?id=${productId}` as any); router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
}; };
const handleViewDetails = (productId: number) => { const handleViewDetails = (productId: number) => {
router.push(`/products/detail/${productId}` as any); router.push(`/(drawer)/dashboard/products/detail/${productId}` as any);
}; };
const FilterButton = ({ filter, label, count }: { filter: FilterType; label: string; count: number }) => ( const FilterButton = ({ filter, label, count }: { filter: FilterType; label: string; count: number }) => (
@ -105,7 +105,7 @@ export default function Products() {
{/* Header */} {/* Header */}
<View style={tw`flex-row justify-between items-center mb-6`}> <View style={tw`flex-row justify-between items-center mb-6`}>
<MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText> <MyText style={tw`text-2xl font-bold text-gray-800`}>Products</MyText>
<MyButton onPress={() => router.push('/products/add' as any)}> <MyButton onPress={() => router.push('/(drawer)/dashboard/products/add' as any)}>
Add Product Add Product
</MyButton> </MyButton>
</View> </View>

View file

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { View, TouchableOpacity, Alert, FlatList } from 'react-native'; import { View, TouchableOpacity, Alert, FlatList } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { MyText, tw, MyTouchableOpacity, MyFlatList, BottomDialog } from 'common-ui'; import { MyText, tw, MyTouchableOpacity, MyFlatList, BottomDialog } from 'common-ui';
import { trpc } from '../../../src/trpc-client'; import { trpc } from '../../../../src/trpc-client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';

View file

@ -0,0 +1,9 @@
import { Stack } from 'expo-router'
export default function Layout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" options={{ title: 'Send Notifications' }} />
</Stack>
)
}

View file

@ -55,7 +55,7 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)} onPress={() => router.push(`/(drawer)/dashboard/slots/slot-details?slotId=${slot.id}`)}
activeOpacity={0.7} activeOpacity={0.7}
> >
<View style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}> <View style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}>

View file

@ -18,7 +18,7 @@ export default function AddStore() {
onSuccess: async (data) => { onSuccess: async (data) => {
await refetchStores(); await refetchStores();
Alert.alert('Success', data.message); Alert.alert('Success', data.message);
router.push('/stores' as any); // Navigate back to stores list router.push('/(drawer)/dashboard/stores' as any); // Navigate back to stores list
}, },
onError: (error: any) => { onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to create store'); Alert.alert('Error', error.message || 'Failed to create store');

View file

@ -23,7 +23,7 @@ export default function EditStore() {
onSuccess: (data) => { onSuccess: (data) => {
refetch(); refetch();
Alert.alert('Success', data.message); Alert.alert('Success', data.message);
router.push('/stores' as any); router.push('/(drawer)/dashboard/stores' as any);
}, },
onError: (error: any) => { onError: (error: any) => {
Alert.alert('Error', error.message || 'Failed to update store'); Alert.alert('Error', error.message || 'Failed to update store');

View file

@ -106,7 +106,7 @@ export default function Stores() {
const stores = storesData?.stores || []; const stores = storesData?.stores || [];
const handleEdit = (storeId: number) => { const handleEdit = (storeId: number) => {
router.push({ pathname: '/stores/edit', params: { id: storeId } }); router.push({ pathname: '/(drawer)/dashboard/stores/edit', params: { id: storeId } });
}; };
const handleDelete = (storeId: number) => { const handleDelete = (storeId: number) => {
@ -207,7 +207,7 @@ export default function Stores() {
<Animated.View entering={FadeInUp.delay(500)} style={tw`absolute bottom-8 right-6 shadow-xl`}> <Animated.View entering={FadeInUp.delay(500)} style={tw`absolute bottom-8 right-6 shadow-xl`}>
<TouchableOpacity <TouchableOpacity
activeOpacity={0.8} activeOpacity={0.8}
onPress={() => router.push('/stores/add' as any)} onPress={() => router.push('/(drawer)/dashboard/stores/add' as any)}
> >
<LinearGradient <LinearGradient
colors={['#2563EB', '#1D4ED8']} colors={['#2563EB', '#1D4ED8']}

View file

@ -112,7 +112,7 @@ export default function UserDetails() {
}); });
const handleOrderPress = useCallback((orderId: number) => { const handleOrderPress = useCallback((orderId: number) => {
router.push(`/(drawer)/manage-orders/order-details/${orderId}`); router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
}, [router]); }, [router]);
const handleSuspensionToggle = useCallback((isSuspended: boolean) => { const handleSuspensionToggle = useCallback((isSuspended: boolean) => {

View file

@ -0,0 +1,10 @@
import { Stack } from 'expo-router'
export default function Layout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" options={{ title: 'User Management' }} />
<Stack.Screen name="[id]" options={{ title: 'User Details' }} />
</Stack>
)
}

View file

@ -189,7 +189,7 @@ export default function UserManagement() {
}, [hasNextPage, isFetchingNextPage, fetchNextPage]); }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
const handleUserPress = useCallback((userId: number) => { const handleUserPress = useCallback((userId: number) => {
router.push(`/(drawer)/user-management/${userId}`); router.push(`/(drawer)/dashboard/user-management/${userId}`);
}, [router]); }, [router]);
const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => { const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => {

View file

@ -16,22 +16,22 @@ import {
useMarkDataFetchers, useMarkDataFetchers,
MyTouchableOpacity, MyTouchableOpacity,
} from "common-ui"; } from "common-ui";
import { trpc, trpcClient } from "../../../src/trpc-client"; import { trpc, trpcClient } from "../../../../src/trpc-client";
import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import VendorSnippetForm from "../../../components/VendorSnippetForm"; import VendorSnippetForm from "../../../../components/VendorSnippetForm";
import SnippetOrdersView from "../../../components/SnippetOrdersView"; import SnippetOrdersView from "../../../../components/SnippetOrdersView";
import { SnippetMenu } from "../../../components/SnippetMenu"; import { SnippetMenu } from "../../../../components/SnippetMenu";
import { ProductListDialog } from "../../../components/ProductListDialog"; import { ProductListDialog } from "../../../../components/ProductListDialog";
import { import {
VendorSnippet, VendorSnippet,
VendorSnippetProduct, VendorSnippetProduct,
VendorSnippetForm as VendorSnippetFormType, VendorSnippetForm as VendorSnippetFormType,
} from "../../../types/vendor-snippets"; } from "../../../../types/vendor-snippets";
const SnippetItem = ({ const SnippetItem = ({
snippet, snippet,
@ -212,7 +212,7 @@ const SnippetItem = ({
</View> </View>
<MyTouchableOpacity <MyTouchableOpacity
// onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${snippet.slotId}`)} // onPress={() => router.push(`/(drawer)/dashboard/slots/slot-details?slotId=${snippet.slotId}`)}
onPress={() => {}} onPress={() => {}}
activeOpacity={0.7} activeOpacity={0.7}
style={tw`flex-row items-center`} style={tw`flex-row items-center`}

View file

@ -1,93 +0,0 @@
import React, { useState } from 'react';
import { View, TouchableOpacity } from 'react-native';
import { Image } from 'expo-image';
import { useRouter } from 'expo-router';
import { AppContainer, MyText, tw, MyFlatList, MyTextInput } from 'common-ui';
import { trpc } from '@/src/trpc-client';
import Ionicons from '@expo/vector-icons/Ionicons';
interface UserItemProps {
item: any;
onPress: (userId: string) => void;
}
const UserItem: React.FC<UserItemProps> = ({ item, onPress }) => (
<TouchableOpacity
style={tw`flex-row items-center p-4 bg-white border-b border-gray-100`}
onPress={() => onPress(item.id)}
>
{item.image ? (
<Image
source={{ uri: item.image }}
style={tw`w-12 h-12 rounded-full mr-4`}
/>
) : (
<View style={tw`w-12 h-12 rounded-full bg-gray-200 items-center justify-center mr-4`}>
<Ionicons name="person" size={24} color="#6b7280" />
</View>
)}
<View style={tw`flex-1`}>
<MyText style={tw`font-bold text-gray-800 text-lg`}>{item.name}</MyText>
<MyText style={tw`text-gray-600`}>{item.email}</MyText>
<MyText style={tw`text-gray-500 text-sm`}>{item.mobile}</MyText>
</View>
</TouchableOpacity>
);
export default function Users() {
const router = useRouter();
const [search, setSearch] = useState('');
const {
data,
isLoading,
fetchNextPage,
hasNextPage,
refetch,
} = trpc.admin.staffUser.getUsers.useInfiniteQuery(
{ search },
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
}
);
const users = data?.pages.flatMap(page => page.users) || [];
const handleUserPress = (userId: string) => {
router.push(`/(drawer)/user-management/${userId}`);
};
return (
<AppContainer>
<View style={tw`flex-1`}>
<View style={tw`bg-white px-4 py-2 border-b border-gray-100`}>
<MyTextInput
topLabel="Search Users"
placeholder="Search by name, email, or mobile"
value={search}
onChangeText={setSearch}
style={{ marginBottom: 0 }}
/>
</View>
<MyFlatList
data={users}
renderItem={({ item }) => <UserItem item={item} onPress={handleUserPress} />}
keyExtractor={(item) => item.id.toString()}
onRefresh={refetch}
onEndReached={() => {
if (hasNextPage) {
fetchNextPage();
}
}}
onEndReachedThreshold={0.5}
ListEmptyComponent={
<View style={tw`flex-1 justify-center items-center py-8`}>
<MyText style={tw`text-gray-500`}>No users found</MyText>
</View>
}
/>
</View>
</AppContainer>
);
}

View file

@ -83,9 +83,9 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
Notifications.addNotificationResponseReceivedListener((response) => { Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data; const data = response.notification.request.content.data;
if (data && data.doctorId) { if (data && data.doctorId) {
router.push(`/(drawer)/dashboard`); router.navigate(`/(drawer)/dashboard`);
} else if (data && data.tokenId) { } else if (data && data.tokenId) {
router.push(`/(drawer)/dashboard`); router.navigate(`/(drawer)/dashboard`);
} }
}); });

View file

@ -9,10 +9,16 @@ import { CreateCouponPayload } from 'common-ui/shared-types';
const USERS_PAGE_SIZE = 10; const USERS_PAGE_SIZE = 10;
interface CouponFormValues extends CreateCouponPayload {
isReservedCoupon?: boolean;
couponCodes?: string[];
skuIds?: number[];
}
interface CouponFormProps { interface CouponFormProps {
onSubmit: (values: CreateCouponPayload & { isReservedCoupon?: boolean }) => void; onSubmit: (values: CouponFormValues) => void;
isLoading: boolean; isLoading: boolean;
initialValues?: Partial<CreateCouponPayload & { isReservedCoupon?: boolean }>; initialValues?: Partial<CouponFormValues>;
} }
const couponValidationSchema = Yup.object().shape({ const couponValidationSchema = Yup.object().shape({
@ -22,6 +28,13 @@ const couponValidationSchema = Yup.object().shape({
.min(3, 'Coupon code must be at least 3 characters') .min(3, 'Coupon code must be at least 3 characters')
.max(50, 'Coupon code cannot exceed 50 characters') .max(50, 'Coupon code cannot exceed 50 characters')
.matches(/^[A-Z0-9_-]+$/, 'Coupon code can only contain uppercase letters, numbers, underscores, and hyphens'), .matches(/^[A-Z0-9_-]+$/, 'Coupon code can only contain uppercase letters, numbers, underscores, and hyphens'),
couponCodes: Yup.array().of(
Yup.string()
.required('Code is required')
.min(3, 'Code must be at least 3 characters')
.max(50, 'Code cannot exceed 50 characters')
.matches(/^[A-Z0-9_-]+$/, 'Code can only contain uppercase letters, numbers, underscores, and hyphens')
),
discountPercent: Yup.number() discountPercent: Yup.number()
.min(0, 'Must be positive') .min(0, 'Must be positive')
.max(100, 'Cannot exceed 100%') .max(100, 'Cannot exceed 100%')
@ -49,6 +62,22 @@ const couponValidationSchema = Yup.object().shape({
return this.createError({ message: 'Must provide either percentage or flat discount' }); return this.createError({ message: 'Must provide either percentage or flat discount' });
} }
return true; return true;
}).test('reserved-codes-required', 'At least one coupon code is required', function(value: any) {
if (value.isReservedCoupon) {
const codes = (value.couponCodes || []).filter((c: string) => c && c.trim());
if (codes.length === 0) {
return this.createError({ path: 'couponCodes', message: 'Add at least one coupon code' });
}
}
return true;
}).test('reserved-codes-unique', 'Coupon codes must be unique', function(value: any) {
if (value.isReservedCoupon) {
const codes = (value.couponCodes || []).map((c: string) => c.trim()).filter(Boolean);
if (new Set(codes).size !== codes.length) {
return this.createError({ path: 'couponCodes', message: 'Coupon codes must be unique' });
}
}
return true;
}); });
export default function CouponForm({ onSubmit, isLoading, initialValues }: CouponFormProps) { export default function CouponForm({ onSubmit, isLoading, initialValues }: CouponFormProps) {
@ -94,8 +123,9 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
// User search functionality will be inside Formik // User search functionality will be inside Formik
const defaultValues: CreateCouponPayload & { isReservedCoupon?: boolean } = { const defaultValues: CouponFormValues = {
couponCode: '', couponCode: '',
couponCodes: [],
isUserBased: false, isUserBased: false,
isApplyForAll: false, isApplyForAll: false,
targetUsers: [], targetUsers: [],
@ -113,8 +143,8 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
}; };
return ( return (
<Formik <Formik<CouponFormValues>
initialValues={(initialValues || defaultValues) as CreateCouponPayload} initialValues={(initialValues || defaultValues) as CouponFormValues}
validationSchema={couponValidationSchema} validationSchema={couponValidationSchema}
onSubmit={onSubmit} onSubmit={onSubmit}
> >
@ -143,18 +173,70 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
<MyText style={tw`ml-2 text-sm font-medium text-gray-700`}>Is Reserved Coupon</MyText> <MyText style={tw`ml-2 text-sm font-medium text-gray-700`}>Is Reserved Coupon</MyText>
</View> </View>
{/* Coupon Code */} {/* Coupon Code (single, non-reserved) OR Coupon Codes (array, reserved) */}
<View style={tw`mb-4`}> {!isReserved ? (
<MyTextInput <View style={tw`mb-4`}>
topLabel="Coupon Code" <MyTextInput
placeholder="e.g., SAVE20" topLabel="Coupon Code"
value={values.couponCode || ''} placeholder="e.g., SAVE20"
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())} value={values.couponCode || ''}
keyboardType="default" onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
autoCapitalize="characters" keyboardType="default"
error={!!(touched.couponCode && errors.couponCode)} autoCapitalize="characters"
/> error={!!(touched.couponCode && errors.couponCode)}
</View> />
</View>
) : (
<View style={tw`mb-4`}>
<Text style={tw`text-base font-bold mb-1`}>Coupon Codes</Text>
<Text style={tw`text-xs text-gray-500 mb-2`}>
Each code creates a separate reserved coupon with the same settings.
</Text>
{(values.couponCodes || []).map((code: string, index: number) => {
const codeError = Array.isArray(errors.couponCodes)
? (errors.couponCodes as any)[index]
: errors.couponCodes;
return (
<View key={index} style={tw`flex-row items-center mb-2`}>
<View style={tw`flex-1`}>
<MyTextInput
topLabel={`Code ${index + 1}`}
placeholder="e.g., SECRET10"
value={code}
onChangeText={(text: string) => {
const next = [...(values.couponCodes || [])];
next[index] = text.toUpperCase();
setFieldValue('couponCodes', next);
}}
keyboardType="default"
autoCapitalize="characters"
error={!!codeError}
/>
{codeError && <Text style={tw`text-red-500 text-xs mt-1`}>{codeError}</Text>}
</View>
<TouchableOpacity
onPress={() => {
const next = (values.couponCodes || []).filter((_: string, i: number) => i !== index);
setFieldValue('couponCodes', next);
}}
style={tw`ml-2 p-2`}
>
<Text style={tw`text-red-500 font-bold text-lg`}></Text>
</TouchableOpacity>
</View>
);
})}
{(errors.couponCodes as any)?.message && (
<Text style={tw`text-red-500 text-xs mb-2`}>{(errors.couponCodes as any).message}</Text>
)}
<TouchableOpacity
onPress={() => setFieldValue('couponCodes', [...(values.couponCodes || []), ''])}
style={tw`mt-2 py-2 px-3 bg-blue-50 rounded-lg border border-blue-200 self-start`}
>
<Text style={tw`text-blue-600 font-medium`}>+ Add Code</Text>
</TouchableOpacity>
</View>
)}
{/* Discount Type Selection */} {/* Discount Type Selection */}
<Text style={tw`text-base font-bold mb-2`}> <Text style={tw`text-base font-bold mb-2`}>