enh
This commit is contained in:
parent
e5d9de33d7
commit
e2c6a292a4
65 changed files with 413 additions and 353 deletions
|
|
@ -5,7 +5,10 @@
|
|||
"Shell(npx tsc --noEmit 2 >& 1)",
|
||||
"Shell(grep:*)",
|
||||
"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": [],
|
||||
"defaultMode": "default"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
- 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
|
||||
- After reviewing a presented plan, prefers brief, action-oriented approval (e.g., "nice. go ahead") before implementation proceeds. Confidence: 0.6
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Drawer } from "expo-router/drawer";
|
||||
import { DrawerContentScrollView, DrawerItem } from "@react-navigation/drawer";
|
||||
import { useRouter, Redirect } from "expo-router";
|
||||
import { useNavigation, DrawerActions } from "@react-navigation/native";
|
||||
import {
|
||||
TouchableOpacity,
|
||||
DeviceEventEmitter,
|
||||
|
|
@ -13,6 +12,25 @@ import { REFRESH_EVENT } from "common-ui/src/lib/const-strs";
|
|||
import { useStaffAuth } from "@/components/context/staff-auth-context";
|
||||
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() {
|
||||
const router = useRouter();
|
||||
const { logout, staff } = useStaffAuth();
|
||||
|
|
@ -32,121 +50,108 @@ function CustomDrawerContent() {
|
|||
)}
|
||||
<DrawerItem
|
||||
label="Dashboard"
|
||||
onPress={() => router.push("/(drawer)/dashboard" as any)}
|
||||
onPress={() => router.dismissTo("/(drawer)/dashboard" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="dashboard" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
{/* <DrawerItem
|
||||
label="Add Product"
|
||||
onPress={() => router.push("/(drawer)/add-product" 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)}
|
||||
<DrawerItem
|
||||
label="Products"
|
||||
onPress={() => router.push("/(drawer)/dashboard/products" as any)}
|
||||
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
|
||||
label="Complaints"
|
||||
onPress={() => router.push("/(drawer)/complaints" as any)}
|
||||
onPress={() => router.push("/(drawer)/dashboard/complaints" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="report-problem" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Manage Orders"
|
||||
onPress={() => router.push("/(drawer)/manage-orders" as any)}
|
||||
onPress={() => router.push("/(drawer)/dashboard/manage-orders" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="shopping-bag" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Coupons"
|
||||
onPress={() => router.push("coupons" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="local-offer" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Vendor Snippets"
|
||||
onPress={() => router.push("vendor-snippets" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="code" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Stores"
|
||||
onPress={() => router.push("/(drawer)/stores" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="store" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="User Management"
|
||||
onPress={() => router.push("/(drawer)/user-management" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="people" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Send Notifications"
|
||||
onPress={() => router.push("/(drawer)/send-notifications" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="campaign" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Logout"
|
||||
onPress={() => logout()}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="logout" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Coupons"
|
||||
onPress={() => router.push("/(drawer)/dashboard/coupons" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="local-offer" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Vendor Snippets"
|
||||
onPress={() => router.push("/(drawer)/dashboard/vendor-snippets" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="code" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Stores"
|
||||
onPress={() => router.push("/(drawer)/dashboard/stores" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="store" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="User Management"
|
||||
onPress={() => router.push("/(drawer)/dashboard/user-management" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="people" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Send Notifications"
|
||||
onPress={() => router.push("/(drawer)/dashboard/send-notifications" as any)}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="campaign" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
<DrawerItem
|
||||
label="Logout"
|
||||
onPress={() => logout()}
|
||||
icon={({ color, size }) => (
|
||||
<MaterialIcons name="logout" size={size} color={color} />
|
||||
)}
|
||||
/>
|
||||
</DrawerContentScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Layout() {
|
||||
const router = useRouter();
|
||||
const { isLoggedIn, isLoading } = useStaffAuth();
|
||||
|
||||
if (isLoading) {
|
||||
|
|
@ -163,74 +168,72 @@ export default function Layout() {
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
backBehavior="history"
|
||||
drawerContent={CustomDrawerContent}
|
||||
screenOptions={({ navigation, route }) => ({
|
||||
swipeEnabled: false,
|
||||
headerShown: true,
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.gray1,
|
||||
shadowOpacity: 0,
|
||||
shadowRadius: 0,
|
||||
shadowOffset: { height: 0, width: 0 },
|
||||
elevation: 0,
|
||||
},
|
||||
headerTitleAlign: "center",
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() => (navigation as any).openDrawer()}
|
||||
style={{
|
||||
marginLeft: 15,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: "#f2f2f2",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="menu" 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>
|
||||
),
|
||||
})}
|
||||
screenOptions={({ navigation }) => {
|
||||
const state = navigation.getState();
|
||||
const focusedRoute = state?.routes?.[state?.index ?? 0];
|
||||
const dashboardStack = (focusedRoute as any)?.state;
|
||||
const nestedIndex = dashboardStack?.index ?? 0;
|
||||
const canGoBack = nestedIndex > 0;
|
||||
const nestedRoute = dashboardStack?.routes?.[nestedIndex];
|
||||
const title = SCREEN_TITLES[nestedRoute?.name ?? ""] ?? "Dashboard";
|
||||
|
||||
return {
|
||||
swipeEnabled: false,
|
||||
headerShown: true,
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.gray1,
|
||||
shadowOpacity: 0,
|
||||
shadowRadius: 0,
|
||||
shadowOffset: { height: 0, width: 0 },
|
||||
elevation: 0,
|
||||
},
|
||||
headerTitle: title,
|
||||
headerTitleAlign: "center",
|
||||
headerLeft: () => (
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
canGoBack ? router.back() : (navigation as any).openDrawer()
|
||||
}
|
||||
style={{
|
||||
marginLeft: 15,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: "#f2f2f2",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<MaterialIcons
|
||||
name={canGoBack ? "arrow-back" : "menu"}
|
||||
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="products" options={{ title: "Products" }} />
|
||||
<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>
|
||||
<Drawer.Screen name="dashboard" options={{ title: "Dashboard" }} />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,41 @@ import { Stack } from "expo-router";
|
|||
|
||||
export default function Layout() {
|
||||
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
|
||||
name="index"
|
||||
options={{
|
||||
title: "Dashboard",
|
||||
headerShown: false,
|
||||
}}
|
||||
name="dashboard-banners"
|
||||
options={{ title: "Dashboard Banners" }}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ export default function Complaints() {
|
|||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||
|
||||
const handleUserPress = useCallback((userId: number) => {
|
||||
router.push(`/(drawer)/user-management/${userId}`);
|
||||
router.push(`/(drawer)/dashboard/user-management/${userId}`);
|
||||
}, [router]);
|
||||
|
||||
const handleOrderPress = useCallback((orderId: number) => {
|
||||
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
|
||||
router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
|
||||
}, [router]);
|
||||
|
||||
const handleMarkResolved = (id: number) => {
|
||||
|
|
@ -24,9 +24,9 @@ export default function CreateCoupon() {
|
|||
}
|
||||
)
|
||||
|
||||
const handleCreateCoupon = (values: any) => {
|
||||
const handleCreateCoupon = async (values: any) => {
|
||||
console.log('Form values:', values); // Debug log
|
||||
const { isReservedCoupon, ...rest } = values;
|
||||
const { isReservedCoupon, couponCodes, ...rest } = values;
|
||||
// Transform targetUsers array to targetUser for backend compatibility
|
||||
const payload = {
|
||||
...rest,
|
||||
|
|
@ -35,6 +35,24 @@ export default function CreateCoupon() {
|
|||
delete payload.targetUsers;
|
||||
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 isLoading = isReservedCoupon ? createReservedCoupon.isPending : createCoupon.isPending;
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ const CouponItem = ({ item, onDelete }: { item: any; onDelete: (id: number) => v
|
|||
|
||||
<View style={tw`flex-row mt-3 gap-2`}>
|
||||
<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`}
|
||||
>
|
||||
<MaterialCommunityIcons name="pencil" size={16} color="white" />
|
||||
|
|
@ -497,7 +497,7 @@ export default function Coupons() {
|
|||
)}
|
||||
/>
|
||||
<MyTouchableOpacity
|
||||
onPress={() => router.push('/coupons/create')}
|
||||
onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
|
||||
activeOpacity={0.95}
|
||||
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
||||
>
|
||||
|
|
@ -217,7 +217,7 @@ export default function ReservedCoupons() {
|
|||
</View>
|
||||
<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>
|
||||
<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`}>
|
||||
<MaterialCommunityIcons name="plus" size={16} color="white" />
|
||||
<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 */}
|
||||
<MyTouchableOpacity
|
||||
onPress={() => router.push('/coupons/create')}
|
||||
onPress={() => router.push('/(drawer)/dashboard/coupons/create')}
|
||||
activeOpacity={0.95}
|
||||
style={{ position: 'absolute', bottom: 32, right: 24, zIndex: 100 }}
|
||||
>
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
MyTouchableOpacity,
|
||||
} from "common-ui";
|
||||
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 { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { View, ScrollView, Alert } from 'react-native';
|
||||
import { Formik } from 'formik';
|
||||
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 MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ const ConstantInput: React.FC<ConstantInputProps> = ({ constantKey, value, setFi
|
|||
{CONST_LABELS[constantKey] || constantKey}
|
||||
</MyText>
|
||||
<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`}
|
||||
>
|
||||
<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}
|
||||
</MyText>
|
||||
<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`}
|
||||
>
|
||||
<MaterialIcons name="reorder" size={20} color="#16a34a" style={tw`mr-2`} />
|
||||
|
|
@ -19,9 +19,9 @@ import {
|
|||
BottomDropdown,
|
||||
MyTouchableOpacity,
|
||||
} from "common-ui";
|
||||
import ProductsSelector from "../../../components/ProductsSelector";
|
||||
import ProductsSelector from "../../../../components/ProductsSelector";
|
||||
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 { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, ScrollView, TouchableOpacity, Alert, Image, RefreshControl } from 'react-native';
|
||||
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 { useRouter } from 'expo-router';
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ export default function DashboardBanners() {
|
|||
};
|
||||
|
||||
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) => {
|
||||
|
|
@ -207,7 +207,7 @@ export default function DashboardBanners() {
|
|||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
router.push('/dashboard-banners/create' as any);
|
||||
router.push('/(drawer)/dashboard/dashboard-banners/create' as any);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
|
|
@ -63,13 +63,13 @@ export default function Dashboard() {
|
|||
|
||||
|
||||
const handleManageOrdersPress = () => {
|
||||
setNavigationTarget('/manage-orders/orders');
|
||||
router.push('/(drawer)/manage-orders');
|
||||
setNavigationTarget('/(drawer)/dashboard/manage-orders/orders');
|
||||
router.push('/(drawer)/dashboard/manage-orders');
|
||||
};
|
||||
|
||||
const handleDeliverySequencesPress = () => {
|
||||
setNavigationTarget('/manage-orders/delivery-sequences');
|
||||
router.push('/(drawer)/manage-orders');
|
||||
setNavigationTarget('/(drawer)/dashboard/manage-orders/delivery-sequences');
|
||||
router.push('/(drawer)/dashboard/manage-orders');
|
||||
};
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
|
|
@ -77,7 +77,7 @@ export default function Dashboard() {
|
|||
title: 'Manage Orders',
|
||||
icon: 'shopping-bag',
|
||||
description: 'View and manage customer orders',
|
||||
route: '/(drawer)/manage-orders',
|
||||
route: '/(drawer)/dashboard/manage-orders',
|
||||
category: 'orders',
|
||||
iconColor: '#10B981',
|
||||
iconBg: '#D1FAE5',
|
||||
|
|
@ -87,7 +87,7 @@ export default function Dashboard() {
|
|||
title: 'Delivery Sequences',
|
||||
icon: 'alt-route',
|
||||
description: 'Plan and optimize delivery routes',
|
||||
route: '/manage-orders/delivery-sequences',
|
||||
route: '/(drawer)/dashboard/manage-orders/delivery-sequences',
|
||||
category: 'orders',
|
||||
iconColor: '#8B5CF6',
|
||||
iconBg: '#EDE9FE',
|
||||
|
|
@ -97,7 +97,7 @@ export default function Dashboard() {
|
|||
title: 'Delivery Slots',
|
||||
icon: 'schedule',
|
||||
description: 'Configure delivery times',
|
||||
route: '/(drawer)/slots' as any,
|
||||
route: '/(drawer)/dashboard/slots' as any,
|
||||
category: 'quick',
|
||||
iconColor: '#06B6D4',
|
||||
iconBg: '#CFFAFE',
|
||||
|
|
@ -107,7 +107,7 @@ export default function Dashboard() {
|
|||
title: 'Add Product',
|
||||
icon: 'add-circle',
|
||||
description: 'Create a new product listing',
|
||||
route: '/products/add',
|
||||
route: '/(drawer)/dashboard/products/add',
|
||||
category: 'quick',
|
||||
iconColor: theme.colors.brand500,
|
||||
iconBg: theme.colors.brand50,
|
||||
|
|
@ -116,7 +116,7 @@ export default function Dashboard() {
|
|||
title: 'Complaints',
|
||||
icon: 'report-problem',
|
||||
description: 'Handle customer complaints',
|
||||
route: '/(drawer)/complaints',
|
||||
route: '/(drawer)/dashboard/complaints',
|
||||
category: 'quick',
|
||||
iconColor: '#F59E0B',
|
||||
iconBg: '#FEF3C7',
|
||||
|
|
@ -126,7 +126,7 @@ export default function Dashboard() {
|
|||
title: 'Products',
|
||||
icon: 'inventory-2',
|
||||
description: 'Manage product catalog',
|
||||
route: '/(drawer)/products',
|
||||
route: '/(drawer)/dashboard/products',
|
||||
category: 'products',
|
||||
iconColor: '#6366F1',
|
||||
iconBg: '#E0E7FF',
|
||||
|
|
@ -135,7 +135,7 @@ export default function Dashboard() {
|
|||
title: 'Prices Overview',
|
||||
icon: 'attach-money',
|
||||
description: 'Update product pricing',
|
||||
route: '/(drawer)/prices-overview',
|
||||
route: '/(drawer)/dashboard/prices-overview',
|
||||
category: 'products',
|
||||
iconColor: '#059669',
|
||||
iconBg: '#ECFDF5',
|
||||
|
|
@ -144,7 +144,7 @@ export default function Dashboard() {
|
|||
title: 'Product Tags',
|
||||
icon: 'label',
|
||||
description: 'Organize with tags',
|
||||
route: '/(drawer)/product-tags',
|
||||
route: '/(drawer)/dashboard/product-tags',
|
||||
category: 'products',
|
||||
iconColor: '#EC4899',
|
||||
iconBg: '#FCE7F3',
|
||||
|
|
@ -153,7 +153,7 @@ export default function Dashboard() {
|
|||
title: 'Product Groupings',
|
||||
icon: 'category',
|
||||
description: 'Group products together',
|
||||
route: '/(drawer)/product-groupings',
|
||||
route: '/(drawer)/dashboard/product-groupings',
|
||||
category: 'products',
|
||||
iconColor: '#8B5CF6',
|
||||
iconBg: '#F3E8FF',
|
||||
|
|
@ -162,7 +162,7 @@ export default function Dashboard() {
|
|||
title: 'Stores',
|
||||
icon: 'store',
|
||||
description: 'Manage store locations',
|
||||
route: '/(drawer)/stores',
|
||||
route: '/(drawer)/dashboard/stores',
|
||||
category: 'settings',
|
||||
iconColor: '#14B8A6',
|
||||
iconBg: '#CCFBF1',
|
||||
|
|
@ -171,7 +171,7 @@ export default function Dashboard() {
|
|||
title: 'Coupons',
|
||||
icon: 'local-offer',
|
||||
description: 'Create discount coupons',
|
||||
route: '/(drawer)/coupons',
|
||||
route: '/(drawer)/dashboard/coupons',
|
||||
category: 'marketing',
|
||||
iconColor: '#F97316',
|
||||
iconBg: '#FFEDD5',
|
||||
|
|
@ -180,7 +180,7 @@ export default function Dashboard() {
|
|||
title: 'App Constants',
|
||||
icon: 'settings-applications',
|
||||
description: 'Customize app settings',
|
||||
route: '/(drawer)/customize-app',
|
||||
route: '/(drawer)/dashboard/customize-app',
|
||||
category: 'settings',
|
||||
iconColor: '#7C3AED',
|
||||
iconBg: '#F3E8FF',
|
||||
|
|
@ -189,7 +189,7 @@ export default function Dashboard() {
|
|||
title: 'User Management',
|
||||
icon: 'people',
|
||||
description: 'View and manage all users',
|
||||
route: '/(drawer)/user-management',
|
||||
route: '/(drawer)/dashboard/user-management',
|
||||
category: 'users',
|
||||
iconColor: '#0EA5E9',
|
||||
iconBg: '#E0F2FE',
|
||||
|
|
@ -198,7 +198,7 @@ export default function Dashboard() {
|
|||
title: 'Send Notifications',
|
||||
icon: 'campaign',
|
||||
description: 'Send push notifications to users',
|
||||
route: '/(drawer)/send-notifications',
|
||||
route: '/(drawer)/dashboard/send-notifications',
|
||||
category: 'users',
|
||||
iconColor: '#8B5CF6',
|
||||
iconBg: '#F3E8FF',
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
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',
|
||||
onPress: () => {
|
||||
if (selectedSlotId === 'flash') {
|
||||
router.push('/manage-orders/orders?filter=flash');
|
||||
router.push('/(drawer)/dashboard/manage-orders/orders?filter=flash');
|
||||
} else {
|
||||
router.push('/manage-orders/orders');
|
||||
router.push('/(drawer)/dashboard/manage-orders/orders');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -378,7 +378,7 @@ export default function OrderDetails() {
|
|||
|
||||
{/* Customer Details */}
|
||||
<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}
|
||||
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
|
||||
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`}
|
||||
>
|
||||
<MyText style={tw`text-white font-bold text-base`}>
|
||||
|
|
@ -284,17 +284,17 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
>
|
||||
<MaterialIcons name="shopping-cart" size={16} color="#3B82F6" />
|
||||
<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>
|
||||
{order.isFlashDelivery && (
|
||||
<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>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<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-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>
|
||||
|
|
@ -315,8 +315,8 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
)}
|
||||
{order.discountAmount && (
|
||||
<MyText style={tw`text-sm font-bold text-pink-800`}>
|
||||
Discount: ₹{order.discountAmount}
|
||||
</MyText>
|
||||
{`Discount: ₹${order.discountAmount}`}
|
||||
</MyText>
|
||||
)}
|
||||
</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`flex-row justify-between items-center`}>
|
||||
<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>
|
||||
)}
|
||||
|
|
@ -407,13 +407,13 @@ const OrderItem = ({ order, refetch }: { order: OrderType; refetch: () => void }
|
|||
)}
|
||||
</View>
|
||||
<MyText style={tw`text-sm text-gray-600 mb-6`}>
|
||||
Total: ₹{order.totalAmount}
|
||||
{`Total: ₹${order.totalAmount}`}
|
||||
</MyText>
|
||||
{order.items.map((item, idx) => (
|
||||
<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`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>
|
||||
<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}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { AppContainer } from 'common-ui';
|
||||
import ProductGroupForm from '../../../components/ProductGroupForm';
|
||||
import ProductGroupForm from '../../../../components/ProductGroupForm';
|
||||
import { useRouter } from 'expo-router';
|
||||
|
||||
export default function CreateProductGroup() {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { AppContainer, MyText } from 'common-ui';
|
||||
import ProductGroupForm from '../../../../components/ProductGroupForm';
|
||||
import ProductGroupForm from '../../../../../components/ProductGroupForm';
|
||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||
import { trpc } from '@/src/trpc-client';
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
MyTouchableOpacity,
|
||||
BottomDialog,
|
||||
} from "common-ui";
|
||||
import { trpc } from "../../../src/trpc-client";
|
||||
import { trpc } from "../../../../src/trpc-client";
|
||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import dayjs from "dayjs";
|
||||
|
|
@ -143,11 +143,11 @@ export default function ProductGroupings() {
|
|||
});
|
||||
|
||||
const handleCreate = () => {
|
||||
router.push("/product-groupings/create");
|
||||
router.push("/(drawer)/dashboard/product-groupings/create");
|
||||
};
|
||||
|
||||
const handleEdit = (group: ProductGroup) => {
|
||||
router.push(`/product-groupings/edit/${group.id}`);
|
||||
router.push(`/(drawer)/dashboard/product-groupings/edit/${group.id}`);
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
MyTouchableOpacity,
|
||||
} from 'common-ui';
|
||||
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 { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ export default function ProductDetail() {
|
|||
const defaultSku = product?.skus?.[0]
|
||||
|
||||
const handleEdit = () => {
|
||||
router.push(`/products/edit?id=${productId}` as any);
|
||||
router.push(`/(drawer)/dashboard/products/edit?id=${productId}` as any);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -52,11 +52,11 @@ export default function Products() {
|
|||
}, [products, searchTerm, activeFilter]);
|
||||
|
||||
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) => {
|
||||
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 }) => (
|
||||
|
|
@ -105,7 +105,7 @@ export default function Products() {
|
|||
{/* Header */}
|
||||
<View style={tw`flex-row justify-between items-center mb-6`}>
|
||||
<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
|
||||
</MyButton>
|
||||
</View>
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||
import { View, TouchableOpacity, Alert, FlatList } from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
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 { LinearGradient } from 'expo-linear-gradient';
|
||||
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
|||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
<View style={tw`bg-white p-5 mb-4 rounded-3xl shadow-sm border border-gray-100`}>
|
||||
|
|
@ -18,7 +18,7 @@ export default function AddStore() {
|
|||
onSuccess: async (data) => {
|
||||
await refetchStores();
|
||||
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) => {
|
||||
Alert.alert('Error', error.message || 'Failed to create store');
|
||||
|
|
@ -23,7 +23,7 @@ export default function EditStore() {
|
|||
onSuccess: (data) => {
|
||||
refetch();
|
||||
Alert.alert('Success', data.message);
|
||||
router.push('/stores' as any);
|
||||
router.push('/(drawer)/dashboard/stores' as any);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
Alert.alert('Error', error.message || 'Failed to update store');
|
||||
|
|
@ -106,7 +106,7 @@ export default function Stores() {
|
|||
const stores = storesData?.stores || [];
|
||||
|
||||
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) => {
|
||||
|
|
@ -207,7 +207,7 @@ export default function Stores() {
|
|||
<Animated.View entering={FadeInUp.delay(500)} style={tw`absolute bottom-8 right-6 shadow-xl`}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={() => router.push('/stores/add' as any)}
|
||||
onPress={() => router.push('/(drawer)/dashboard/stores/add' as any)}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#2563EB', '#1D4ED8']}
|
||||
|
|
@ -112,7 +112,7 @@ export default function UserDetails() {
|
|||
});
|
||||
|
||||
const handleOrderPress = useCallback((orderId: number) => {
|
||||
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
|
||||
router.push(`/(drawer)/dashboard/manage-orders/order-details/${orderId}`);
|
||||
}, [router]);
|
||||
|
||||
const handleSuspensionToggle = useCallback((isSuspended: boolean) => {
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ export default function UserManagement() {
|
|||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||
|
||||
const handleUserPress = useCallback((userId: number) => {
|
||||
router.push(`/(drawer)/user-management/${userId}`);
|
||||
router.push(`/(drawer)/dashboard/user-management/${userId}`);
|
||||
}, [router]);
|
||||
|
||||
const renderUserItem = useCallback(({ item, index }: { item: User; index: number }) => {
|
||||
|
|
@ -16,22 +16,22 @@ import {
|
|||
useMarkDataFetchers,
|
||||
MyTouchableOpacity,
|
||||
} 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 { Ionicons } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { useRouter } from "expo-router";
|
||||
|
||||
import VendorSnippetForm from "../../../components/VendorSnippetForm";
|
||||
import SnippetOrdersView from "../../../components/SnippetOrdersView";
|
||||
import { SnippetMenu } from "../../../components/SnippetMenu";
|
||||
import { ProductListDialog } from "../../../components/ProductListDialog";
|
||||
import VendorSnippetForm from "../../../../components/VendorSnippetForm";
|
||||
import SnippetOrdersView from "../../../../components/SnippetOrdersView";
|
||||
import { SnippetMenu } from "../../../../components/SnippetMenu";
|
||||
import { ProductListDialog } from "../../../../components/ProductListDialog";
|
||||
import {
|
||||
VendorSnippet,
|
||||
VendorSnippetProduct,
|
||||
VendorSnippetForm as VendorSnippetFormType,
|
||||
} from "../../../types/vendor-snippets";
|
||||
} from "../../../../types/vendor-snippets";
|
||||
|
||||
const SnippetItem = ({
|
||||
snippet,
|
||||
|
|
@ -212,7 +212,7 @@ const SnippetItem = ({
|
|||
</View>
|
||||
|
||||
<MyTouchableOpacity
|
||||
// onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${snippet.slotId}`)}
|
||||
// onPress={() => router.push(`/(drawer)/dashboard/slots/slot-details?slotId=${snippet.slotId}`)}
|
||||
onPress={() => {}}
|
||||
activeOpacity={0.7}
|
||||
style={tw`flex-row items-center`}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -83,9 +83,9 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
|
|||
Notifications.addNotificationResponseReceivedListener((response) => {
|
||||
const data = response.notification.request.content.data;
|
||||
if (data && data.doctorId) {
|
||||
router.push(`/(drawer)/dashboard`);
|
||||
router.navigate(`/(drawer)/dashboard`);
|
||||
} else if (data && data.tokenId) {
|
||||
router.push(`/(drawer)/dashboard`);
|
||||
router.navigate(`/(drawer)/dashboard`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,16 @@ import { CreateCouponPayload } from 'common-ui/shared-types';
|
|||
|
||||
const USERS_PAGE_SIZE = 10;
|
||||
|
||||
interface CouponFormValues extends CreateCouponPayload {
|
||||
isReservedCoupon?: boolean;
|
||||
couponCodes?: string[];
|
||||
skuIds?: number[];
|
||||
}
|
||||
|
||||
interface CouponFormProps {
|
||||
onSubmit: (values: CreateCouponPayload & { isReservedCoupon?: boolean }) => void;
|
||||
onSubmit: (values: CouponFormValues) => void;
|
||||
isLoading: boolean;
|
||||
initialValues?: Partial<CreateCouponPayload & { isReservedCoupon?: boolean }>;
|
||||
initialValues?: Partial<CouponFormValues>;
|
||||
}
|
||||
|
||||
const couponValidationSchema = Yup.object().shape({
|
||||
|
|
@ -22,6 +28,13 @@ const couponValidationSchema = Yup.object().shape({
|
|||
.min(3, 'Coupon code must be at least 3 characters')
|
||||
.max(50, 'Coupon code cannot exceed 50 characters')
|
||||
.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()
|
||||
.min(0, 'Must be positive')
|
||||
.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 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) {
|
||||
|
|
@ -94,8 +123,9 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
|
||||
// User search functionality will be inside Formik
|
||||
|
||||
const defaultValues: CreateCouponPayload & { isReservedCoupon?: boolean } = {
|
||||
const defaultValues: CouponFormValues = {
|
||||
couponCode: '',
|
||||
couponCodes: [],
|
||||
isUserBased: false,
|
||||
isApplyForAll: false,
|
||||
targetUsers: [],
|
||||
|
|
@ -113,8 +143,8 @@ export default function CouponForm({ onSubmit, isLoading, initialValues }: Coupo
|
|||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={(initialValues || defaultValues) as CreateCouponPayload}
|
||||
<Formik<CouponFormValues>
|
||||
initialValues={(initialValues || defaultValues) as CouponFormValues}
|
||||
validationSchema={couponValidationSchema}
|
||||
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>
|
||||
</View>
|
||||
|
||||
{/* Coupon Code */}
|
||||
<View style={tw`mb-4`}>
|
||||
<MyTextInput
|
||||
topLabel="Coupon Code"
|
||||
placeholder="e.g., SAVE20"
|
||||
value={values.couponCode || ''}
|
||||
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
|
||||
keyboardType="default"
|
||||
autoCapitalize="characters"
|
||||
error={!!(touched.couponCode && errors.couponCode)}
|
||||
/>
|
||||
</View>
|
||||
{/* Coupon Code (single, non-reserved) OR Coupon Codes (array, reserved) */}
|
||||
{!isReserved ? (
|
||||
<View style={tw`mb-4`}>
|
||||
<MyTextInput
|
||||
topLabel="Coupon Code"
|
||||
placeholder="e.g., SAVE20"
|
||||
value={values.couponCode || ''}
|
||||
onChangeText={(text: string) => setFieldValue('couponCode', text.toUpperCase())}
|
||||
keyboardType="default"
|
||||
autoCapitalize="characters"
|
||||
error={!!(touched.couponCode && errors.couponCode)}
|
||||
/>
|
||||
</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 */}
|
||||
<Text style={tw`text-base font-bold mb-2`}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue