This commit is contained in:
shafi54 2026-02-09 00:40:57 +05:30
parent 2a106b5467
commit 31395e5cc7
6 changed files with 70 additions and 132 deletions

View file

@ -348,12 +348,17 @@ export default function OrderDetails() {
)}
{/* Customer Details */}
<View
<TouchableOpacity
onPress={() => order.userId && router.push(`/(drawer)/user-management/${order.userId}`)}
activeOpacity={0.7}
style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}
>
<MyText style={tw`text-base font-bold text-gray-900 mb-4`}>
Customer Details
</MyText>
<View style={tw`flex-row items-center justify-between mb-4`}>
<MyText style={tw`text-base font-bold text-gray-900`}>
Customer Details
</MyText>
<MaterialIcons name="chevron-right" size={20} color="#6B7280" />
</View>
<View style={tw`flex-row items-center mb-4`}>
<View
@ -363,7 +368,7 @@ export default function OrderDetails() {
</View>
<View>
<MyText style={tw`text-sm font-bold text-gray-900`}>
{order.customerName}
{order.customerName || 'Unknown User'}
</MyText>
<MyText style={tw`text-xs text-gray-500`}>Customer</MyText>
</View>
@ -404,7 +409,7 @@ export default function OrderDetails() {
</View>
</View>
</View>
</View>
</TouchableOpacity>
{/* Order Items */}
<View

View file

@ -187,23 +187,6 @@ export default function SendNotifications() {
</View>
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`p-4`}>
{/* User Selection */}
<View style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-base font-bold text-gray-900 mb-3`}>Select Users</MyText>
<BottomDropdown
label="Select Users"
value={selectedUserIds}
options={dropdownOptions}
onValueChange={(value) => setSelectedUserIds(value as number[])}
multiple={true}
placeholder="Select users"
onSearch={(query) => setSearchQuery(query)}
/>
<MyText style={tw`text-gray-500 text-sm mt-2`}>
{getDisplayText()}
</MyText>
</View>
{/* Title Input */}
<View style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-base font-bold text-gray-900 mb-3`}>Title</MyText>
@ -228,8 +211,8 @@ export default function SendNotifications() {
/>
</View>
{/* Image Upload */}
<View style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm`}>
{/* Image Upload - Hidden for now */}
{/* <View style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-base font-bold text-gray-900 mb-3`}>Image (Optional)</MyText>
<ImageUploader
images={displayImage ? [displayImage] : []}
@ -237,6 +220,23 @@ export default function SendNotifications() {
onAddImage={handleImagePick}
onRemoveImage={handleRemoveImage}
/>
</View> */}
{/* User Selection */}
<View style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm`}>
<MyText style={tw`text-base font-bold text-gray-900 mb-3`}>Select Users (Optional)</MyText>
<BottomDropdown
label="Select Users"
value={selectedUserIds}
options={dropdownOptions}
onValueChange={(value) => setSelectedUserIds(value as number[])}
multiple={true}
placeholder="Select users"
onSearch={(query) => setSearchQuery(query)}
/>
<MyText style={tw`text-gray-500 text-sm mt-2`}>
{getDisplayText()}
</MyText>
</View>
{/* Submit Button */}

View file

@ -37,7 +37,6 @@
"drizzle-orm": "^0.44.5",
"expo-server-sdk": "^4.0.0",
"express": "^5.1.0",
"firebase-admin": "^13.6.1",
"fuse.js": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",

View file

@ -1,14 +0,0 @@
import admin from 'firebase-admin';
import path from 'path';
// // Initialize Firebase Admin SDK
// const serviceAccountPath = path.join('.', 'creds', 'fcm-v1-account.json');
// if (!admin.apps.length) {
// admin.initializeApp({
// credential: admin.credential.cert(serviceAccountPath),
// });
// }
// export const messaging = admin.messaging();
// export default admin;

View file

@ -304,6 +304,7 @@ export const orderRouter = router({
return {
id: orderData.id,
readableId: orderData.id,
userId: orderData.user.id,
customerName: `${orderData.user.name}`,
customerEmail: orderData.user.email,
customerMobile: orderData.user.mobile,

View file

@ -5,14 +5,8 @@ import { users, complaints, orders, orderItems, notifCreds, userNotifications, u
import { eq, sql, desc, asc, count, max } from 'drizzle-orm';
import { ApiError } from '../../lib/api-error';
import { Expo } from 'expo-server-sdk';
// import { messaging } from '../../lib/firebase';
import { generateSignedUrlFromS3Url } from '../../lib/s3-client';
// Toggle between notification providers: 'expo' | 'fcm'
// const NOTIFICATION_PROVIDER: 'expo' | 'fcm' = 'fcm';
// let NOTIFICATION_PROVIDER: 'expo' | 'fcm' = 'expo';
let NOTIFICATION_PROVIDER: string = 'expo';
async function createUserByMobile(mobile: string): Promise<typeof users.$inferSelect> {
// Clean mobile number (remove non-digits)
const cleanMobile = mobile.replace(/\D/g, '');
@ -401,95 +395,48 @@ export const userRouter = {
// Generate signed URL for image if provided
const signedImageUrl = imageUrl ? await generateSignedUrlFromS3Url(imageUrl) : null;
// Send using Expo
const expo = new Expo();
// Helper function to chunk array
const chunkArray = (array: any[], size: number) => {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
const chunks = chunkArray(tokens, 50);
let sentCount = 0;
let failedCount = 0;
if (NOTIFICATION_PROVIDER === 'fcm') {
// Build FCM messages
const messages = tokens.map(({ token }) => ({
token: token,
notification: {
title: title,
for (const chunk of chunks) {
const messages = chunk
.filter(({ token }) => Expo.isExpoPushToken(token))
.map(({ token }) => ({
to: token,
sound: 'default',
title,
body: text,
},
data: {
imageUrl: imageUrl || '',
},
android: {
notification: {
imageUrl: signedImageUrl || undefined,
},
},
apns: {
payload: {
aps: {
'mutable-content': 1,
},
},
fcm_options: {
image: signedImageUrl || undefined,
},
},
}));
data: { imageUrl },
...(signedImageUrl ? {
attachments: [
{
url: signedImageUrl,
contentType: 'image/jpeg',
}
]
} : {}),
}));
// Send notifications using Firebase Admin SDK
try {
// const response = await messaging.sendEach(messages);
// sentCount = response.successCount;
// failedCount = response.failureCount;
// // Log failed tokens
// response.responses.forEach((resp, idx) => {
// if (!resp.success) {
// console.error(`Failed to send to token ${tokens[idx].token}:`, resp.error);
// }
// });
} catch (error) {
console.error('Error sending push notifications:', error);
throw new ApiError('Failed to send push notifications', 500);
}
} else {
// Send using Expo (legacy)
const expo = new Expo();
// Helper function to chunk array
const chunkArray = (array: any[], size: number) => {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
const chunks = chunkArray(tokens, 50);
for (const chunk of chunks) {
const messages = chunk
.filter(({ token }) => Expo.isExpoPushToken(token))
.map(({ token }) => ({
to: token,
sound: 'default',
title,
body: text,
data: { imageUrl },
...(signedImageUrl ? {
attachments: [
{
url: signedImageUrl,
contentType: 'image/jpeg',
}
]
} : {}),
}));
if (messages.length > 0) {
try {
await expo.sendPushNotificationsAsync(messages);
sentCount += messages.length;
} catch (error) {
console.error('Error sending push notifications:', error);
failedCount += chunk.length;
}
if (messages.length > 0) {
try {
await expo.sendPushNotificationsAsync(messages);
sentCount += messages.length;
} catch (error) {
console.error('Error sending push notifications:', error);
failedCount += chunk.length;
}
}
}