Compare commits

..

11 commits
master ... main

Author SHA1 Message Date
shafi54
84d0add3f4 Merge branch 'admin_notifs' 2026-06-27 08:32:51 +05:30
shafi54
52a8423421 Merge branch 'main' of https://git.technocracy.ovh/shafi/freshyo 2026-05-21 08:46:19 +05:30
shafi54
16c3b56a98 migration_notice 2026-04-10 21:45:09 +05:30
shafi54
809c87e712 enh 2026-03-25 16:04:19 +05:30
shafi54
6ff1fd63e5 enh 2026-03-23 10:57:28 +05:30
shafi54
ea848992c9 enh 2026-03-23 04:26:04 +05:30
shafi54
5ed889a34f Merge branch 'main' of https://git.technocracy.ovh/shafi/freshyo 2026-03-23 03:23:15 +05:30
shafi54
3ddc939a48 enh 2026-03-23 03:22:20 +05:30
shafi54
24252b717b enh 2026-03-22 21:43:44 +05:30
shafi54
78305e1670 enh 2026-03-22 21:28:32 +05:30
shafi54
1a3fe7826f enh 2026-03-15 22:38:17 +05:30
200 changed files with 6251 additions and 683 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
.pnpm-debug.log* .pnpm-debug.log*
*.apk
**/.wrangler/* **/.wrangler/*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)

View file

@ -87,7 +87,12 @@ const formatCancellationMessage = (orderData: any, cancellationData: Cancellatio
📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'} 📞 <b>Phone:</b> ${orderData.address?.phone || 'N/A'}
📦 <b>Items:</b> 📦 <b>Items:</b>
${orderData.orderItems?.map((item: any) => `${item.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'} ${orderData.orderItems?.map((item: any) => {
const productQuantity = item.product?.productQuantity ?? 1
const unitNotation = item.product?.unit?.shortNotation || ''
const quantityWithUnit = unitNotation ? `${productQuantity}${unitNotation}` : `${productQuantity}`
return `${item.product?.name || 'Unknown'} ${quantityWithUnit} x${item.quantity}`
}).join('\n') || ' N/A'}
💰 <b>Total:</b> ${orderData.totalAmount} 💰 <b>Total:</b> ${orderData.totalAmount}
💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'} 💳 <b>Refund:</b> ${orderData.refundStatus === 'na' ? 'N/A (COD)' : orderData.refundStatus || 'Pending'}

58
apps/migrator/README.md Normal file
View file

@ -0,0 +1,58 @@
# Migrator
Data-only migration tool between Postgres and SQLite using Drizzle.
## What it does
- Copies data from source to target
- Does NOT create or alter tables
- Overwrites target data if any rows exist (via `DELETE FROM` in dependency-safe order)
## Requirements
- Tables must already exist on the target database
- Schema must match the backend Drizzle schemas
## Usage
Run from repo root:
```bash
bun --cwd apps/migrator run migrate --from postgres --to sqlite --source "<PG_URL>" --target "<SQLITE_PATH>"
```
```bash
bun --cwd apps/migrator run migrate --from sqlite --to postgres --source "<SQLITE_PATH>" --target "<PG_URL>"
```
### Flags
- `--from`: `postgres` or `sqlite`
- `--to`: `postgres` or `sqlite`
- `--source`: Postgres connection string or SQLite file path
- `--target`: Postgres connection string or SQLite file path
- `--batch`: optional batch size (default: `500`)
## Examples
```bash
bun --cwd apps/migrator run migrate \
--from postgres \
--to sqlite \
--source "postgres://user:pass@host:5432/db" \
--target "./sqlite.db"
```
```bash
bun --cwd apps/migrator run migrate \
--from sqlite \
--to postgres \
--source "./sqlite.db" \
--target "postgres://user:pass@host:5432/db" \
--batch 1000
```
## Notes
- IDs are copied as-is to preserve relationships.
- For Postgres targets, sequences may need manual reset after import.

View file

@ -0,0 +1,13 @@
{
"name": "migrator",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"migrate": "bun src/index.ts"
},
"dependencies": {
"drizzle-orm": "^0.45.1",
"pg": "^8.11.3"
}
}

View file

@ -0,0 +1,13 @@
import { runPostgresToSqlite } from './postgres_to_sqlite'
import { runSqliteToPostgres } from './sqlite_to_postgres'
import { parseArgs } from './lib/args'
const args = parseArgs(Bun.argv.slice(2))
if (args.from === 'postgres' && args.to === 'sqlite') {
await runPostgresToSqlite(args)
} else if (args.from === 'sqlite' && args.to === 'postgres') {
await runSqliteToPostgres(args)
} else {
throw new Error('Unsupported migration direction. Use --from postgres --to sqlite or --from sqlite --to postgres.')
}

View file

@ -0,0 +1,50 @@
export type MigrationArgs = {
from: 'postgres' | 'sqlite'
to: 'postgres' | 'sqlite'
source: string
target: string
batchSize: number
}
const getFlagValue = (args: string[], flag: string): string | undefined => {
const index = args.indexOf(flag)
if (index === -1) return undefined
return args[index + 1]
}
export const parseArgs = (args: string[]): MigrationArgs => {
const from = getFlagValue(args, '--from')
const to = getFlagValue(args, '--to')
const source = getFlagValue(args, '--source')
const target = getFlagValue(args, '--target')
const batch = getFlagValue(args, '--batch')
if (!from || (from !== 'postgres' && from !== 'sqlite')) {
throw new Error('Missing or invalid --from (postgres|sqlite)')
}
if (!to || (to !== 'postgres' && to !== 'sqlite')) {
throw new Error('Missing or invalid --to (postgres|sqlite)')
}
if (!source) {
throw new Error('Missing --source')
}
if (!target) {
throw new Error('Missing --target')
}
const batchSize = batch ? Number(batch) : 500
if (Number.isNaN(batchSize) || batchSize <= 0) {
throw new Error('Invalid --batch, must be a positive number')
}
return {
from,
to,
source,
target,
batchSize,
}
}

View file

@ -0,0 +1,35 @@
export const parseJsonValue = <T>(value: unknown, fallback: T): T => {
if (value === null || value === undefined) return fallback
if (typeof value === 'string') {
try {
return JSON.parse(value) as T
} catch {
return fallback
}
}
return value as T
}
export const toJsonString = (value: unknown, fallback: string): string => {
if (value === null || value === undefined) return fallback
if (typeof value === 'string') return value
return JSON.stringify(value)
}
export const toEpochSeconds = (value: Date | number): number => {
if (typeof value === 'number') return value
return Math.floor(value.getTime() / 1000)
}
export const toDate = (value: number | Date | null | undefined): Date | null => {
if (value === null || value === undefined) return null
if (value instanceof Date) return value
return new Date(value * 1000)
}
export const toBoolean = (value: unknown): boolean => {
if (typeof value === 'boolean') return value
if (typeof value === 'number') return value !== 0
if (typeof value === 'string') return value === 'true' || value === '1'
return false
}

View file

@ -0,0 +1,92 @@
import type { TableName } from './table-order'
export const jsonColumns: Partial<Record<TableName, string[]>> = {
productInfo: ['images'],
productAvailabilitySchedules: ['productIds', 'groupIds'],
homeBanners: ['productIds'],
productReviews: ['imageUrls', 'adminResponseImages'],
deliverySlotInfo: ['deliverySequence', 'groupIds'],
vendorSnippets: ['productIds'],
paymentInfoTable: ['payload'],
payments: ['payload'],
keyValStore: ['value'],
complaints: ['images'],
coupons: ['productIds'],
reservedCoupons: ['productIds'],
userNotifications: ['applicableUsers'],
productTagInfo: ['relatedStores'],
}
export const jsonArrayColumns: Partial<Record<TableName, string[]>> = {
productInfo: ['images'],
productAvailabilitySchedules: ['productIds', 'groupIds'],
homeBanners: ['productIds'],
productReviews: ['imageUrls', 'adminResponseImages'],
deliverySlotInfo: ['groupIds'],
vendorSnippets: ['productIds'],
complaints: ['images'],
coupons: ['productIds'],
reservedCoupons: ['productIds'],
userNotifications: ['applicableUsers'],
productTagInfo: ['relatedStores'],
}
export const jsonObjectColumns: Partial<Record<TableName, string[]>> = {
deliverySlotInfo: ['deliverySequence'],
paymentInfoTable: ['payload'],
payments: ['payload'],
keyValStore: ['value'],
}
export const timestampColumns: Partial<Record<TableName, string[]>> = {
users: ['createdAt'],
userDetails: ['createdAt', 'updatedAt', 'dateOfBirth'],
userCreds: ['createdAt'],
addresses: ['createdAt'],
addressZones: ['addedAt'],
addressAreas: ['createdAt'],
staffUsers: ['createdAt'],
storeInfo: ['createdAt'],
productInfo: ['createdAt'],
productAvailabilitySchedules: ['createdAt', 'lastUpdated'],
productGroupInfo: ['createdAt'],
productGroupMembership: ['addedAt'],
homeBanners: ['createdAt', 'lastUpdated'],
productReviews: ['reviewTime'],
uploadUrlStatus: ['createdAt'],
productTagInfo: ['createdAt'],
productTags: ['assignedAt'],
deliverySlotInfo: ['deliveryTime', 'freezeTime'],
vendorSnippets: ['validTill', 'createdAt'],
specialDeals: ['validTill'],
orders: ['createdAt'],
orderStatus: ['orderTime', 'cancellationReviewedAt'],
refunds: ['refundProcessedAt', 'createdAt'],
notifications: ['createdAt'],
cartItems: ['addedAt'],
complaints: ['createdAt'],
coupons: ['validTill', 'createdAt'],
couponUsage: ['usedAt'],
userIncidents: ['dateAdded'],
reservedCoupons: ['validTill', 'redeemedAt', 'createdAt'],
notifCreds: ['addedAt', 'lastVerified'],
unloggedUserTokens: ['addedAt', 'lastVerified'],
userNotifications: ['createdAt'],
}
export const booleanColumns: Partial<Record<TableName, string[]>> = {
userDetails: ['isSuspended'],
addresses: ['isDefault'],
productInfo: ['isOutOfStock', 'isSuspended', 'isFlashAvailable', 'scheduledAvailability'],
homeBanners: ['isActive'],
productTagInfo: ['isDashboardTag'],
deliverySlotInfo: ['isActive', 'isFlash', 'isCapacityFull'],
vendorSnippets: ['isPermanent'],
orders: ['isCod', 'isOnlinePayment', 'isFlashDelivery'],
orderItems: ['is_packaged', 'is_package_verified'],
orderStatus: ['isPackaged', 'isDelivered', 'isCancelled', 'isCancelledByAdmin', 'cancellationReviewed'],
notifications: ['isRead'],
complaints: ['isResolved'],
coupons: ['isUserBased', 'isApplyForAll', 'isInvalidated', 'exclusiveApply'],
reservedCoupons: ['exclusiveApply', 'isRedeemed'],
}

View file

@ -0,0 +1,18 @@
import { drizzle as drizzlePostgres } from 'drizzle-orm/node-postgres'
import { drizzle as drizzleSqlite } from 'drizzle-orm/bun-sqlite'
import { Pool } from 'pg'
import { Database } from 'bun:sqlite'
import * as postgresSchema from '../../../backend/src/db/schema-postgres'
import * as sqliteSchema from '../../../backend/src/db/schema-sqlite'
export const createPostgresDb = (connectionString: string) => {
const pool = new Pool({ connectionString })
const db = drizzlePostgres(pool, { schema: postgresSchema })
return { db, pool }
}
export const createSqliteDb = (filePath: string) => {
const sqlite = new Database(filePath)
const db = drizzleSqlite(sqlite, { schema: sqliteSchema })
return { db, sqlite }
}

View file

@ -0,0 +1,142 @@
import { sql, asc } from 'drizzle-orm'
import type { TableName } from './table-order'
import { tableOrder } from './table-order'
import { postgresTables, sqliteTables, type TableMap } from './schema-maps'
import { booleanColumns, jsonArrayColumns, jsonColumns, jsonObjectColumns, timestampColumns } from './column-maps'
import { parseJsonValue, toBoolean, toDate, toEpochSeconds, toJsonString } from './casts'
export type Dialect = 'postgres' | 'sqlite'
type MigrationContext = {
from: Dialect
to: Dialect
sourceDb: any
targetDb: any
batchSize: number
}
const getTables = (dialect: Dialect): TableMap => (dialect === 'postgres' ? postgresTables : sqliteTables)
const getOrderById = (table: any) => {
const idColumn = table?.id
if (!idColumn) return undefined
return asc(idColumn)
}
const getRowCount = async (db: any, table: any): Promise<number> => {
const result = await db.select({ count: sql`count(*)` }).from(table)
const count = result[0]?.count
return Number(count || 0)
}
const shouldOverwriteTarget = async (db: any, tables: TableMap): Promise<boolean> => {
for (const name of tableOrder) {
const count = await getRowCount(db, tables[name])
if (count > 0) return true
}
return false
}
const clearTarget = async (db: any, tables: TableMap) => {
const reversed = [...tableOrder].reverse()
for (const name of reversed) {
await db.delete(tables[name])
}
}
const normalizeJson = (value: unknown, tableName: TableName, column: string, toDialect: Dialect) => {
const isArray = jsonArrayColumns[tableName]?.includes(column)
const isObject = jsonObjectColumns[tableName]?.includes(column)
const fallback = isArray ? [] : isObject ? {} : null
if (toDialect === 'sqlite') {
if (value === null || value === undefined) {
return isArray ? '[]' : isObject ? '{}' : null
}
return toJsonString(value, isArray ? '[]' : isObject ? '{}' : 'null')
}
return parseJsonValue(value, fallback)
}
const normalizeTimestamps = (value: unknown, toDialect: Dialect) => {
if (value === null || value === undefined) return value
if (toDialect === 'sqlite') {
return toEpochSeconds(value as Date | number)
}
return toDate(value as number | Date)
}
const normalizeBoolean = (value: unknown, toDialect: Dialect) => {
if (toDialect === 'postgres') {
return toBoolean(value)
}
return value
}
const transformRow = (row: Record<string, unknown>, tableName: TableName, toDialect: Dialect) => {
const jsonCols = jsonColumns[tableName] || []
const timeCols = timestampColumns[tableName] || []
const boolCols = booleanColumns[tableName] || []
const output: Record<string, unknown> = { ...row }
jsonCols.forEach((column) => {
output[column] = normalizeJson(output[column], tableName, column, toDialect)
})
timeCols.forEach((column) => {
output[column] = normalizeTimestamps(output[column], toDialect)
})
boolCols.forEach((column) => {
output[column] = normalizeBoolean(output[column], toDialect)
})
return output
}
const copyTable = async (context: MigrationContext, tableName: TableName) => {
const fromTables = getTables(context.from)
const toTables = getTables(context.to)
const fromTable = fromTables[tableName]
const toTable = toTables[tableName]
const total = await getRowCount(context.sourceDb, fromTable)
if (total === 0) return
let offset = 0
while (offset < total) {
let query = context.sourceDb.select().from(fromTable)
const orderBy = getOrderById(fromTable)
if (orderBy) {
query = query.orderBy(orderBy)
}
const batch = await query.limit(context.batchSize).offset(offset)
const normalized = batch.map((row: Record<string, unknown>) =>
transformRow(row, tableName, context.to)
)
if (normalized.length > 0) {
await context.targetDb.insert(toTable).values(normalized)
}
offset += context.batchSize
}
}
export const runMigration = async (context: MigrationContext) => {
const targetTables = getTables(context.to)
const overwrite = await shouldOverwriteTarget(context.targetDb, targetTables)
if (overwrite) {
await clearTarget(context.targetDb, targetTables)
}
for (const tableName of tableOrder) {
await copyTable(context, tableName)
}
}

View file

@ -0,0 +1,21 @@
import type { TableName } from './table-order'
import * as postgresSchema from '../../../backend/src/db/schema-postgres'
import * as sqliteSchema from '../../../backend/src/db/schema-sqlite'
import { tableOrder } from './table-order'
export type TableMap = Record<TableName, any>
const buildMap = (schema: Record<string, unknown>): TableMap => {
const map = {} as TableMap
tableOrder.forEach((name) => {
const table = schema[name]
if (!table) {
throw new Error(`Missing table export for ${name}`)
}
map[name] = table
})
return map
}
export const postgresTables = buildMap(postgresSchema as Record<string, unknown>)
export const sqliteTables = buildMap(sqliteSchema as Record<string, unknown>)

View file

@ -0,0 +1,48 @@
export const tableOrder = [
'users',
'staffRoles',
'staffPermissions',
'staffRolePermissions',
'staffUsers',
'storeInfo',
'units',
'productInfo',
'productGroupInfo',
'productGroupMembership',
'productTagInfo',
'productTags',
'addressZones',
'addressAreas',
'addresses',
'deliverySlotInfo',
'productSlots',
'productAvailabilitySchedules',
'homeBanners',
'vendorSnippets',
'specialDeals',
'coupons',
'couponApplicableUsers',
'couponApplicableProducts',
'reservedCoupons',
'paymentInfoTable',
'orders',
'payments',
'orderItems',
'orderStatus',
'refunds',
'complaints',
'couponUsage',
'userDetails',
'userCreds',
'notifications',
'cartItems',
'keyValStore',
'notifCreds',
'unloggedUserTokens',
'userNotifications',
'productReviews',
'uploadUrlStatus',
'userIncidents',
] as const
export type TableName = typeof tableOrder[number]

View file

@ -0,0 +1,21 @@
import type { MigrationArgs } from '../lib/args'
import { createPostgresDb, createSqliteDb } from '../lib/db'
import { runMigration } from '../lib/migrate'
export const runPostgresToSqlite = async (args: MigrationArgs) => {
const { db: sourceDb, pool } = createPostgresDb(args.source)
const { db: targetDb, sqlite } = createSqliteDb(args.target)
try {
await runMigration({
from: 'postgres',
to: 'sqlite',
sourceDb,
targetDb,
batchSize: args.batchSize,
})
} finally {
await pool.end()
sqlite.close()
}
}

View file

@ -0,0 +1,21 @@
import type { MigrationArgs } from '../lib/args'
import { createPostgresDb, createSqliteDb } from '../lib/db'
import { runMigration } from '../lib/migrate'
export const runSqliteToPostgres = async (args: MigrationArgs) => {
const { db: sourceDb, sqlite } = createSqliteDb(args.source)
const { db: targetDb, pool } = createPostgresDb(args.target)
try {
await runMigration({
from: 'sqlite',
to: 'postgres',
sourceDb,
targetDb,
batchSize: args.batchSize,
})
} finally {
sqlite.close()
await pool.end()
}
}

View file

@ -292,7 +292,7 @@ const CompactProductCard = ({
onChange={handleQuantityChange} onChange={handleQuantityChange}
step={item.incrementStep} step={item.incrementStep}
showUnits={true} showUnits={true}
// unit={item.unitNotation} unit={item.unitNotation}
/> />
) : ( ) : (
<MyTouchableOpacity <MyTouchableOpacity

View file

@ -29,13 +29,6 @@ import { LinearGradient } from "expo-linear-gradient";
const { height: screenHeight } = Dimensions.get("window"); const { height: screenHeight } = Dimensions.get("window");
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
if (unit?.toLowerCase() === 'kg' && quantity < 1) {
return { value: `${Math.round(quantity * 1000)} g`, display: `${Math.round(quantity * 1000)}g` };
}
return { value: `${quantity} ${unit}(s)`, display: `${quantity}${unit}` };
};
interface FloatingCartBarProps { interface FloatingCartBarProps {
isFlashDelivery?: boolean; isFlashDelivery?: boolean;
isExpanded?: boolean; isExpanded?: boolean;
@ -266,15 +259,10 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
<React.Fragment key={item.id}> <React.Fragment key={item.id}>
<View style={tw`py-4`}> <View style={tw`py-4`}>
<View style={tw`flex-row items-center`}> <View style={tw`flex-row items-center`}>
<View style={tw`items-center`}> <Image
<Image source={{ uri: productsById[item.productId]?.images?.[0] }}
source={{ uri: productsById[item.productId]?.images?.[0] }} style={tw`w-8 h-8 rounded-lg bg-slate-50 border border-slate-100`}
style={tw`w-8 h-8 rounded-lg bg-slate-50 border border-slate-100`} />
/>
<MyText style={tw`text-gray-500 text-[9px] font-medium mt-1`}>
<MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(productsById[item.productId]?.productQuantity || 1, productsById[item.productId]?.unitNotation || '').display}</MyText>
</MyText>
</View>
<View style={tw`flex-1 ml-4`}> <View style={tw`flex-1 ml-4`}>
<View style={tw`flex-row items-center justify-between mb-1`}> <View style={tw`flex-row items-center justify-between mb-1`}>
@ -295,7 +283,7 @@ const FloatingCartBar: React.FC<FloatingCartBarProps> = ({
}} }}
step={productsById[item.productId]?.incrementStep || 1} step={productsById[item.productId]?.incrementStep || 1}
showUnits={true} showUnits={true}
// unit={productsById[item.productId]?.unitNotation} unit={productsById[item.productId]?.unitNotation}
/> />
</View> </View>
<View style={tw`flex-row items-center justify-between`}> <View style={tw`flex-row items-center justify-between`}>

View file

@ -1,173 +0,0 @@
import { useState, useRef } from 'react'
import { X, Upload, ImageIcon, Loader2 } from 'lucide-react'
import { trpc } from '../lib/trpc-client'
import { useUploadToObjectStorage } from '../hooks/useUploadToObjectStorage'
interface ComplaintFormProps {
open: boolean
onClose: () => void
orderId: number
}
interface ComplaintImage {
imgUrl: string
mimeType: string
file: File
}
export default function ComplaintForm({ open, onClose, orderId }: ComplaintFormProps) {
const [complaintBody, setComplaintBody] = useState('')
const [complaintImages, setComplaintImages] = useState<ComplaintImage[]>([])
const fileInputRef = useRef<HTMLInputElement>(null)
const raiseComplaintMutation = trpc.user.complaint.raise.useMutation()
const { refetch: refetchComplaints } = trpc.user.complaint.getAll.useQuery(undefined, {
enabled: false,
})
const { upload, isUploading } = useUploadToObjectStorage()
const handleAddImages = (files: FileList | null) => {
if (!files || files.length === 0) return
const newImages: ComplaintImage[] = []
Array.from(files).forEach((file) => {
if (!file.type.startsWith('image/')) return
newImages.push({
imgUrl: URL.createObjectURL(file),
mimeType: file.type || 'image/jpeg',
file,
})
})
setComplaintImages((prev) => [...prev, ...newImages])
}
const handleRemoveImage = (image: ComplaintImage) => {
setComplaintImages((prev) => {
URL.revokeObjectURL(image.imgUrl)
return prev.filter((item) => item.imgUrl !== image.imgUrl)
})
}
const handleSubmit = async () => {
if (!complaintBody.trim()) {
alert('Please enter complaint details')
return
}
try {
let imageUrls: string[] = []
if (complaintImages.length > 0) {
const uploadImages = complaintImages.map((image) => ({
blob: image.file,
mimeType: image.mimeType || 'image/jpeg',
}))
const { keys } = await upload({
images: uploadImages,
contextString: 'complaint',
})
imageUrls = keys
}
await raiseComplaintMutation.mutateAsync({
orderId: orderId.toString(),
complaintBody: complaintBody.trim(),
imageUrls,
})
await refetchComplaints()
alert('Complaint raised successfully')
setComplaintBody('')
setComplaintImages([])
onClose()
} catch (error: any) {
alert(error.message || 'Failed to raise complaint')
}
}
if (!open) return null
return (
<div className="p-6">
<div className="mb-4 flex flex-row items-center justify-between">
<p className="text-xl font-bold text-gray-900">Raise Complaint</p>
<button onClick={onClose}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<p className="mb-2 font-medium text-gray-700">Describe the issue</p>
<textarea
className="mb-4 min-h-32 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
value={complaintBody}
onChange={(e) => setComplaintBody(e.target.value)}
placeholder="Tell us what went wrong..."
rows={4}
/>
{/* Image uploader */}
<div className="mb-4">
<p className="mb-2 font-medium text-gray-700">Add images (optional)</p>
<div className="flex flex-row flex-wrap gap-3">
{complaintImages.map((image) => (
<div key={image.imgUrl} className="relative h-20 w-20 overflow-hidden rounded-xl border border-gray-200">
<img src={image.imgUrl} alt="complaint" className="h-full w-full object-cover" />
<button
onClick={() => handleRemoveImage(image)}
className="absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-black/60"
>
<X className="h-4 w-4 text-white" />
</button>
</div>
))}
<button
onClick={() => fileInputRef.current?.click()}
className="flex h-20 w-20 flex-col items-center justify-center rounded-xl border-2 border-dashed border-gray-300 hover:border-brand-500 hover:bg-gray-50"
>
<Upload className="h-5 w-5 text-gray-400" />
<span className="mt-1 text-[10px] font-medium text-gray-500">Add</span>
</button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
multiple
className="hidden"
onChange={(e) => {
handleAddImages(e.target.files)
e.target.value = ''
}}
/>
</div>
{complaintImages.length === 0 && (
<div className="mt-2 flex flex-row items-center">
<ImageIcon className="h-3.5 w-3.5 text-gray-400" />
<p className="ml-1 text-xs text-gray-400">No images selected</p>
</div>
)}
</div>
<button
className={`mt-2 flex w-full items-center justify-center gap-2 rounded-xl py-4 font-bold text-white shadow-sm transition-colors ${
raiseComplaintMutation.isPending || isUploading ? 'bg-yellow-400 opacity-70' : 'bg-yellow-500 hover:bg-yellow-600'
}`}
onClick={handleSubmit}
disabled={raiseComplaintMutation.isPending || isUploading}
>
{raiseComplaintMutation.isPending || isUploading ? (
<>
<Loader2 className="h-5 w-5 animate-spin" />
{isUploading ? 'Uploading...' : 'Submitting...'}
</>
) : (
'Submit Complaint'
)}
</button>
</div>
)
}

View file

@ -1,119 +0,0 @@
import { useState } from 'react'
import { trpc } from '../lib/trpc-client'
type ContextString = 'review' | 'product_info' | 'notification' | 'store' | 'complaint' | 'profile'
interface UploadInput {
blob: Blob
mimeType: string
}
interface UploadBatchInput {
images: UploadInput[]
contextString: ContextString
}
interface UploadResult {
keys: string[]
presignedUrls: string[]
}
export function useUploadToObjectStorage() {
const [isUploading, setIsUploading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const [progress, setProgress] = useState<{ completed: number; total: number } | null>(null)
const generateUploadUrls = trpc.common.generateUploadUrls.useMutation()
const upload = async (input: UploadBatchInput): Promise<UploadResult> => {
setIsUploading(true)
setError(null)
setProgress({ completed: 0, total: input.images.length })
try {
const { images, contextString } = input
if (images.length === 0) {
return { keys: [], presignedUrls: [] }
}
const mimeTypes = images.map((img) => img.mimeType)
const { uploadUrls } = await generateUploadUrls.mutateAsync({
contextString: contextString as any,
mimeTypes,
})
if (uploadUrls.length !== images.length) {
throw new Error(`Expected ${images.length} URLs, got ${uploadUrls.length}`)
}
const uploadPromises = images.map(async (image, index) => {
const presignedUrl = uploadUrls[index]
const { blob, mimeType } = image
const response = await fetch(presignedUrl, {
method: 'PUT',
body: blob,
headers: { 'Content-Type': mimeType },
})
if (!response.ok) {
throw new Error(`Upload ${index + 1} failed with status ${response.status}`)
}
setProgress((prev) => (prev ? { ...prev, completed: prev.completed + 1 } : null))
return {
key: extractKeyFromPresignedUrl(presignedUrl),
presignedUrl,
}
})
const results = await Promise.all(uploadPromises)
return {
keys: results.map((result) => result.key),
presignedUrls: results.map((result) => result.presignedUrl),
}
} catch (err) {
const uploadError = err instanceof Error ? err : new Error('Upload failed')
setError(uploadError)
throw uploadError
} finally {
setIsUploading(false)
setProgress(null)
}
}
const uploadSingle = async (
blob: Blob,
mimeType: string,
contextString: ContextString
): Promise<{ key: string; presignedUrl: string }> => {
const result = await upload({
images: [{ blob, mimeType }],
contextString,
})
return {
key: result.keys[0],
presignedUrl: result.presignedUrls[0],
}
}
return {
upload,
uploadSingle,
isUploading,
error,
progress,
isPending: generateUploadUrls.isPending,
}
}
function extractKeyFromPresignedUrl(url: string): string {
const parsedUrl = new URL(url)
let rawKey = parsedUrl.pathname.replace(/^\/+/, '')
rawKey = rawKey.split('/').slice(1).join('/')
return decodeURIComponent(rawKey)
}

View file

@ -1,6 +0,0 @@
export const orderStatusManipulator = (status: string): string => {
if (status.toLowerCase() === 'pending') {
return 'Confirmed'
}
return status
}

View file

@ -12,7 +12,6 @@ import dayjs from 'dayjs'
import { ChevronLeft, Star, Zap, Truck, Store, Package, Plus, Clock, AlertCircle } from 'lucide-react' import { ChevronLeft, Star, Zap, Truck, Store, Package, Plus, Clock, AlertCircle } from 'lucide-react'
import { Dialog } from '../components/Dialog' import { Dialog } from '../components/Dialog'
import { FloatingCartBar } from '../components/FloatingCartBar' import { FloatingCartBar } from '../components/FloatingCartBar'
import AddToCartDialog from '../components/AddToCartDialog'
import { AppLayout } from '../components/AppLayout' import { AppLayout } from '../components/AppLayout'
export const Route = createFileRoute('/home/product/$id')({ export const Route = createFileRoute('/home/product/$id')({
@ -433,8 +432,6 @@ function ProductDetailPage() {
{/* Floating Cart Bar */} {/* Floating Cart Bar */}
<FloatingCartBar /> <FloatingCartBar />
<AddToCartDialog />
</div> </div>
</AppLayout> </AppLayout>
) )

View file

@ -6,7 +6,6 @@ import { p, SearchBar, div } from 'web-components'
import { ProductCard } from '../components/ProductCard' import { ProductCard } from '../components/ProductCard'
import { SearchX, Loader2, ChevronLeft } from 'lucide-react' import { SearchX, Loader2, ChevronLeft } from 'lucide-react'
import { AppLayout } from '../components/AppLayout' import { AppLayout } from '../components/AppLayout'
import AddToCartDialog from '../components/AddToCartDialog'
import { useCartStore } from '../lib/stores/cart-store' import { useCartStore } from '../lib/stores/cart-store'
export const Route = createFileRoute('/home/search')({ export const Route = createFileRoute('/home/search')({
@ -176,8 +175,6 @@ function SearchPage() {
)} )}
</div> </div>
</div> </div>
<AddToCartDialog />
</AppLayout> </AppLayout>
) )
} }

View file

@ -1,10 +1,9 @@
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router' import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { trpc } from '../lib/trpc-client' import { trpc } from '../lib/trpc-client'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { ChevronLeft, CreditCard, CheckCircle, Zap, Edit2, Tag, XCircle, X, AlertCircle, Loader2 } from 'lucide-react' import { p, AppContainer, div } from 'web-components'
import { ChevronLeft, CreditCard, Package, CheckCircle, XCircle, Clock, Zap, Calendar, Edit2, Tag, AlertCircle, X } from 'lucide-react'
import { Dialog } from '../components/Dialog' import { Dialog } from '../components/Dialog'
import ComplaintForm from '../components/ComplaintForm'
import { orderStatusManipulator } from '../lib/string-manipulators'
import dayjs from 'dayjs' import dayjs from 'dayjs'
export const Route = createFileRoute('/me/orders/$id')({ export const Route = createFileRoute('/me/orders/$id')({
@ -14,15 +13,11 @@ export const Route = createFileRoute('/me/orders/$id')({
function OrderDetailPage() { function OrderDetailPage() {
const { id } = useParams({ from: '/me/orders/$id' }) const { id } = useParams({ from: '/me/orders/$id' })
const navigate = useNavigate() const navigate = useNavigate()
const orderId = Number(id)
const { const { data: orderData, isLoading, error, refetch } = trpc.user.order.getOrderById.useQuery(
data: orderData, { orderId: orderId+'' },
isLoading, { enabled: !!orderId }
error,
refetch,
} = trpc.user.order.getOrderById.useQuery(
{ orderId: id },
{ enabled: !!id }
) )
const [isEditingNotes, setIsEditingNotes] = useState(false) const [isEditingNotes, setIsEditingNotes] = useState(false)
@ -31,16 +26,15 @@ function OrderDetailPage() {
const [cancelDialogOpen, setCancelDialogOpen] = useState(false) const [cancelDialogOpen, setCancelDialogOpen] = useState(false)
const [cancelReason, setCancelReason] = useState('') const [cancelReason, setCancelReason] = useState('')
const [complaintDialogOpen, setComplaintDialogOpen] = useState(false) const [complaintDialogOpen, setComplaintDialogOpen] = useState(false)
const [complaintBody, setComplaintBody] = useState('')
// const order = orderData?.data
const updateNotesMutation = trpc.user.order.updateUserNotes.useMutation({ const updateNotesMutation = trpc.user.order.updateUserNotes.useMutation({
onSuccess: () => { onSuccess: () => {
setNotesText(notesInput) setNotesText(notesInput)
setIsEditingNotes(false) setIsEditingNotes(false)
refetch() refetch()
alert('Notes updated successfully')
},
onError: (error: any) => {
alert(error.message || 'Failed to update notes')
}, },
}) })
@ -49,21 +43,16 @@ function OrderDetailPage() {
setCancelDialogOpen(false) setCancelDialogOpen(false)
setCancelReason('') setCancelReason('')
refetch() refetch()
alert('Order cancelled successfully')
},
onError: (error: any) => {
alert(error.message || 'Failed to cancel order')
}, },
}) })
const handleCancelOrder = () => { const raiseComplaintMutation = trpc.user.complaint.raise.useMutation({
if (!order) return onSuccess: () => {
if (!cancelReason.trim()) { setComplaintDialogOpen(false)
alert('Please enter a reason for cancellation') setComplaintBody('')
return refetch()
} },
cancelOrderMutation.mutate({ id: order.id, reason: cancelReason }) })
}
useEffect(() => { useEffect(() => {
if (orderData?.userNotes) { if (orderData?.userNotes) {
@ -72,376 +61,419 @@ function OrderDetailPage() {
} }
}, [orderData]) }, [orderData])
const handleCancelOrder = () => {
if (!cancelReason.trim()) {
alert('Please enter a reason for cancellation')
return
}
cancelOrderMutation.mutate({ id: orderId, reason: cancelReason })
}
const handleRaiseComplaint = () => {
if (!complaintBody.trim()) {
alert('Please describe your complaint')
return
}
raiseComplaintMutation.mutate({ orderId: orderId+'', complaintBody: complaintBody.trim() })
}
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex min-h-screen flex-1 items-center justify-center bg-white"> <AppContainer>
<p className="font-medium text-slate-400">Loading details...</p> <div className="flex min-h-full flex-1 items-center justify-center bg-white">
</div> <p className="font-medium text-slate-400">Loading details...</p>
</div>
</AppContainer>
) )
} }
if (error || !orderData) { if (error || !orderData) {
return ( return (
<div className="flex min-h-screen flex-1 flex-col items-center justify-center bg-white p-8"> <AppContainer>
<AlertCircle className="mb-4 h-12 w-12 text-red-500" /> <div className="flex min-h-full flex-1 flex-col items-center justify-center bg-white p-8">
<p className="mt-4 text-lg font-bold text-slate-900">Failed to load</p> <AlertCircle className="mb-4 h-12 w-12 text-red-500" />
<button <p className="mt-4 text-lg font-bold text-slate-900">Failed to load</p>
onClick={() => navigate({ to: '/me/orders' })} <button
className="mt-6 rounded-xl bg-slate-900 px-6 py-2 font-bold text-white" onClick={() => navigate({ to: '/me/orders' })}
> className="mt-6 rounded-xl bg-slate-900 px-6 py-2 font-bold text-white"
Go Back >
</button> Go Back
</div> </button>
</div>
</AppContainer>
) )
} }
const order = orderData
const orderAny = order as any
const getStatusConfig = (status: string) => { const getStatusConfig = (status: string) => {
const s = status.toLowerCase() const s = status.toLowerCase()
switch (s) { switch (s) {
case 'delivered': case 'delivered':
case 'success': case 'success':
return { label: 'Delivered', color: '#10B981' } return { label: 'Delivered', color: '#10B981', bgColor: 'bg-green-50', textColor: 'text-green-700' }
case 'cancelled': case 'cancelled':
case 'failed': case 'failed':
return { label: 'Cancelled', color: '#EF4444' } return { label: 'Cancelled', color: '#EF4444', bgColor: 'bg-red-50', textColor: 'text-red-700' }
case 'pending': case 'pending':
case 'processing': case 'processing':
return { label: 'Pending', color: '#F59E0B' } return { label: 'Pending', color: '#F59E0B', bgColor: 'bg-yellow-50', textColor: 'text-yellow-700' }
default: default:
return { label: status, color: '#1570EF' } return { label: status, color: '#3B82F6', bgColor: 'bg-blue-50', textColor: 'text-blue-700' }
} }
} }
const subtotal = order.items.reduce((sum: number, item: any) => sum + item.amount, 0) const orderAny = orderData as any
const discountAmount = order.discountAmount || 0 const subtotal = orderData.items?.reduce((sum: number, item: any) => sum + (item.amount || item.price * item.quantity), 0) || 0
const deliveryCharge = orderAny.deliveryCharge || 0 const discountAmount = orderData.discountAmount || 0
const totalAmount = order.orderAmount const deliveryCharge = orderData.deliveryCharge || 0
const statusConfig = getStatusConfig(order.deliveryStatus || 'pending') const totalAmount = orderData.orderAmount || subtotal - discountAmount + deliveryCharge
const statusConfig = getStatusConfig(orderData.deliveryStatus || 'pending')
return ( return (
<div className="flex min-h-screen flex-1 flex-col bg-slate-50"> <AppContainer>
{/* Header */} <div className="flex min-h-full flex-1 flex-col bg-slate-50">
<div className="flex flex-row items-center justify-between border-b border-slate-100 bg-white px-6 pb-4 pt-4"> {/* Header */}
<div className="flex flex-row items-center"> <div className="flex flex-row items-center justify-between border-b border-slate-100 bg-white px-6 pb-4 pt-4">
<button <div className="flex flex-row items-center">
onClick={() => navigate({ to: '/me/orders' })} <button
className="-ml-2 mr-3 p-2" onClick={() => navigate({ to: '/me/orders' })}
> className="-ml-2 mr-3 p-2"
<ChevronLeft className="h-6 w-6 text-slate-800" />
</button>
<div>
<p className="text-lg font-bold text-slate-900">Order #{order.orderId || order.id}</p>
<p className="text-xs text-slate-400">
{dayjs(order.orderDate || order.createdAt).format('DD MMM, h:mm A')}
</p>
{order.isFlashDelivery && (
<p className="mt-1 text-xs font-bold text-amber-600"> 1 Hr Delivery</p>
)}
</div>
</div>
<div className="flex flex-row items-center gap-2">
<div
className="rounded-full px-3 py-1"
style={{ backgroundColor: statusConfig.color + '10' }}
>
<p
className="text-[10px] font-bold uppercase"
style={{ color: statusConfig.color }}
> >
{orderStatusManipulator(statusConfig.label)} <ChevronLeft className="h-6 w-6 text-slate-800" />
</p> </button>
</div> <div>
{order.isFlashDelivery && ( <p className="text-lg font-bold text-slate-900">Order #{orderData.orderId || orderData.id}</p>
<div className="rounded-full border border-amber-200 bg-amber-100 px-2 py-1"> <p className="text-xs text-slate-400">
<p className="text-[10px] font-black uppercase text-amber-700"></p> {dayjs(orderData.orderDate || orderData.createdAt).format('DD MMM, h:mm A')}
</div> </p>
)} {orderData.isFlashDelivery && (
</div> <p className="mt-1 text-xs font-bold text-amber-600"> 1 Hr Delivery</p>
</div>
<div className="flex-1 overflow-y-auto p-4 pb-12">
{/* 1 Hr Delivery Banner */}
{order.isFlashDelivery && (
<div className="mb-4 rounded-2xl border border-amber-200 bg-linear-to-r from-amber-50 to-yellow-50 p-4">
<div className="flex flex-row items-center">
<Zap className="h-6 w-6 text-amber-600" />
<div className="ml-3 flex-1">
<p className="text-sm font-bold text-amber-900">1 Hr Delivery Order</p>
<p className="mt-1 text-xs text-amber-700">
Your order will be delivered within 1 hr of placement
</p>
</div>
</div>
</div>
)}
{/* Main Info Card */}
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<div className="mb-4 flex flex-row items-center justify-between">
<div className="flex flex-row items-center">
<div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-slate-50">
<CreditCard className="h-5 w-5 text-slate-500" />
</div>
<div>
<p className="text-[10px] font-bold uppercase text-slate-400">Payment Method</p>
<p className="font-bold text-slate-900">
{order.paymentMode?.toUpperCase() === 'COD' ? 'Cash on Delivery' : order.paymentMode}
</p>
</div>
</div>
<div className="items-end text-right">
<p className="text-[10px] font-bold uppercase text-slate-400">Status</p>
<p className="font-bold capitalize text-slate-900">{order.paymentStatus}</p>
</div>
</div>
{(order.deliveryDate || order.isFlashDelivery) &&
['delivered', 'success'].includes((order.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
{order.isFlashDelivery ? (
<Zap className="h-4 w-4 text-amber-600" />
) : (
<CheckCircle className="h-4 w-4 text-green-600" />
)}
<p className={`ml-2 text-xs font-medium ${order.isFlashDelivery ? 'text-amber-700' : 'text-slate-600'}`}>
{order.isFlashDelivery
? `Flash Delivered on ${dayjs(order.createdAt || order.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}`
: `Delivered on ${dayjs(order.deliveryDate).format('DD MMM YYYY, h:mm A')}`}
</p>
</div>
)}
{order.isFlashDelivery &&
!['delivered', 'success'].includes((order.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
<Zap className="h-4 w-4 text-amber-600" />
<p className="ml-2 text-xs font-medium text-amber-700">
1 Hr Delivery: {dayjs(order.createdAt || order.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}
</p>
</div>
)}
</div>
{/* Special Instructions */}
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<div className="mb-3 flex flex-row items-center justify-between">
<p className="text-[10px] font-bold uppercase text-slate-400">Special Instructions</p>
{isEditingNotes ? (
<div className="flex flex-row gap-2">
<button
onClick={() => {
setIsEditingNotes(false)
setNotesInput(notesText)
}}
className="rounded-lg bg-slate-100 px-3 py-1"
>
<p className="text-xs font-bold text-slate-600">Cancel</p>
</button>
<button
onClick={() => {
updateNotesMutation.mutate({ id: order.id, userNotes: notesInput })
}}
disabled={updateNotesMutation.isPending}
className="rounded-lg bg-brand-500 px-3 py-1"
>
<p className="text-xs font-bold text-white">
{updateNotesMutation.isPending ? 'Saving...' : 'Save'}
</p>
</button>
</div>
) : (
<button
onClick={() => {
setNotesInput(notesText || '')
setIsEditingNotes(true)
}}
className="flex flex-row items-center rounded-lg bg-slate-50 px-2 py-1"
>
<Edit2 className="mr-1 h-3 w-3 text-slate-500" />
<p className="text-xs font-medium text-slate-500">{notesText ? 'Edit' : 'Add'}</p>
</button>
)}
</div>
{isEditingNotes ? (
<textarea
className="min-h-20 rounded-xl border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700"
value={notesInput}
onChange={(e) => setNotesInput(e.target.value)}
placeholder="Add special delivery instructions..."
rows={4}
/>
) : (
<p className="text-sm leading-5 text-slate-700">
{notesText || 'No instructions added'}
</p>
)}
</div>
{/* Cancellation Detail */}
{order.cancelReason && (
<div className="mb-4 rounded-2xl border border-rose-100 bg-rose-50 p-5">
<p className="mb-2 text-[10px] font-bold uppercase text-rose-700">Cancellation Reason</p>
<p className="text-sm font-medium text-rose-900">{order.cancelReason}</p>
{order.refundAmount && (
<div className="mt-3 flex flex-row items-center justify-between border-t border-rose-200/50 pt-3">
<p className="text-xs text-rose-700">Refund Amount</p>
<p className="text-base font-bold text-rose-900">{order.refundAmount}</p>
</div>
)}
</div>
)}
{/* Items Section */}
<div className="mb-2 px-1">
<p className="mb-3 text-base font-bold text-slate-900">Order Items</p>
</div>
{order.items.map((item: any, index: number) => (
<div
key={index}
className="mb-3 flex flex-row items-center rounded-2xl border border-slate-100 bg-white p-3 shadow-sm"
>
<div className="mr-4 flex h-14 w-14 items-center justify-center overflow-hidden rounded-xl border border-slate-100 bg-slate-50 p-1">
{item.image ? (
<img
src={item.image}
alt={item.productName}
className="h-full w-full rounded-lg object-cover"
/>
) : (
<div className="text-[10px] font-bold text-slate-400">No img</div>
)} )}
</div> </div>
<div className="flex-1">
<p className="line-clamp-1 text-sm font-bold text-slate-900">{item.productName}</p>
<p className="mt-1 text-xs text-slate-400">{item.quantity} × {item.price}</p>
</div>
<p className="ml-2 text-base font-bold text-slate-900">{item.amount}</p>
</div> </div>
))} <div className="flex flex-row items-center gap-2">
<div
className={`rounded-full px-3 py-1 ${statusConfig.bgColor}`}
style={{ backgroundColor: statusConfig.color + '10' }}
>
<p className="text-[10px] font-bold uppercase" style={{ color: statusConfig.color }}>
{statusConfig.label}
</p>
</div>
{orderData.isFlashDelivery && (
<div className="rounded-full border border-amber-200 bg-amber-100 px-2 py-1">
<p className="text-[10px] font-black uppercase text-amber-700"></p>
</div>
)}
</div>
</div>
{/* Coupon */} <div className="flex-1 overflow-y-auto p-4 pb-12">
{order.couponCode && ( {/* 1 Hr Delivery Banner */}
<div className="mb-4 mt-2 flex flex-row items-center justify-between rounded-2xl border border-emerald-100 bg-emerald-50 p-4"> {orderData.isFlashDelivery && (
<div className="flex flex-row items-center"> <div className="mb-4 rounded-2xl border border-amber-200 bg-linear-to-r from-amber-50 to-yellow-50 p-4">
<Tag className="h-5 w-5 text-emerald-600" /> <div className="flex flex-row items-center">
<div className="ml-3"> <Zap className="h-6 w-6 text-amber-600" />
<p className="text-sm font-bold text-emerald-900">{order.couponCode}</p> <div className="ml-3 flex-1">
<p className="text-[10px] text-emerald-600">Coupon Applied</p> <p className="text-sm font-bold text-amber-900">1 Hr Delivery Order</p>
<p className="mt-1 text-xs text-amber-700">
Your order will be delivered within 1 hr of placement
</p>
</div>
</div> </div>
</div> </div>
<p className="font-bold text-emerald-700">-{order.discountAmount}</p> )}
</div>
)}
{/* Summary Section */} {/* Main Info Card */}
<div className="mb-6 rounded-2xl border border-slate-100 bg-white p-5"> <div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<div className="mb-3 flex flex-row justify-between"> <div className="mb-4 flex flex-row items-center justify-between">
<p className="text-sm text-slate-500">Subtotal</p> <div className="flex flex-row items-center">
<p className="font-medium text-slate-900">{subtotal}</p> <div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-slate-50">
<CreditCard className="h-5 w-5 text-slate-500" />
</div>
<div>
<p className="text-[10px] font-bold uppercase text-slate-400">Payment Method</p>
<p className="font-bold text-slate-900">
{orderData.paymentMode?.toUpperCase() === 'COD' ? 'Cash on Delivery' : orderData.paymentMode}
</p>
</div>
</div>
<div className="items-end text-right">
<p className="text-[10px] font-bold uppercase text-slate-400">Status</p>
<p className="font-bold capitalize text-slate-900">{orderData.paymentStatus}</p>
</div>
</div>
{/* Delivery Date Info */}
{(orderData.deliveryDate || orderData.isFlashDelivery) &&
['delivered', 'success'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
{orderData.isFlashDelivery ? (
<Zap className="h-4 w-4 text-amber-600" />
) : (
<CheckCircle className="h-4 w-4 text-green-600" />
)}
<p className={`ml-2 text-xs font-medium ${orderData.isFlashDelivery ? 'text-amber-700' : 'text-slate-600'}`}>
{orderData.isFlashDelivery
? `Flash Delivered on ${dayjs(orderData.createdAt || orderData.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}`
: `Delivered on ${dayjs(orderData.deliveryDate).format('DD MMM YYYY, h:mm A')}`}
</p>
</div>
)}
{/* Pending 1 Hr Delivery Info */}
{orderData.isFlashDelivery &&
!['delivered', 'success'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
<Zap className="h-4 w-4 text-amber-600" />
<p className="ml-2 text-xs font-medium text-amber-700">
1 Hr Delivery: {dayjs(orderData.createdAt || orderData.orderDate).add(30, 'minutes').format('DD MMM YYYY, h:mm A')}
</p>
</div>
)}
</div> </div>
{discountAmount > 0 && (
<div className="mb-3 flex flex-row justify-between"> {/* Special Instructions */}
<p className="text-sm text-emerald-600">Discount</p> <div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
<p className="font-medium text-emerald-600">-{discountAmount}</p> <div className="mb-3 flex flex-row items-center justify-between">
<p className="text-[10px] font-bold uppercase text-slate-400">Special Instructions</p>
{isEditingNotes ? (
<div className="flex flex-row gap-2">
<button
onClick={() => {
setIsEditingNotes(false)
setNotesInput(notesText)
}}
className="rounded-lg bg-slate-100 px-3 py-1"
>
<p className="text-xs font-bold text-slate-600">Cancel</p>
</button>
<button
onClick={() => {
updateNotesMutation.mutate({ id: orderId, userNotes: notesInput })
}}
disabled={updateNotesMutation.isPending}
className="rounded-lg bg-blue-500 px-3 py-1"
>
<p className="text-xs font-bold text-white">
{updateNotesMutation.isPending ? 'Saving...' : 'Save'}
</p>
</button>
</div>
) : (
<button
onClick={() => {
setNotesInput(notesText || '')
setIsEditingNotes(true)
}}
className="flex flex-row items-center rounded-lg bg-slate-50 px-2 py-1"
>
<Edit2 className="mr-1 h-3 w-3 text-slate-500" />
<p className="text-xs font-medium text-slate-500">{notesText ? 'Edit' : 'Add'}</p>
</button>
)}
</div>
{isEditingNotes ? (
<textarea
className="min-h-20 rounded-xl border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700"
value={notesInput}
onChange={(e) => setNotesInput(e.target.value)}
placeholder="Add special delivery instructions..."
rows={4}
/>
) : (
<p className="text-sm leading-5 text-slate-700">
{notesText || 'No instructions added'}
</p>
)}
</div>
{/* Cancellation Detail */}
{orderData.cancelReason && (
<div className="mb-4 rounded-2xl border border-rose-100 bg-rose-50 p-5">
<p className="mb-2 text-[10px] font-bold uppercase text-rose-700">Cancellation Reason</p>
<p className="text-sm font-medium text-rose-900">{orderData.cancelReason}</p>
{orderData.refundAmount && (
<div className="mt-3 flex flex-row items-center justify-between border-t border-rose-200/50 pt-3">
<p className="text-xs text-rose-700">Refund Amount</p>
<p className="text-base font-bold text-rose-900">{orderData.refundAmount}</p>
</div>
)}
</div> </div>
)} )}
<div className="mb-3 flex flex-row justify-between">
<p className="text-sm text-slate-500">Delivery</p>
<p className="font-medium text-slate-900">
{deliveryCharge > 0 ? `${deliveryCharge}` : 'Free'}
</p>
</div>
<div className="flex flex-row items-center justify-between border-t border-slate-50 pt-4">
<p className="text-base font-bold text-slate-900">Total Amount</p>
<p className="text-xl font-bold text-slate-900">{totalAmount}</p>
</div>
</div>
{/* Footer Actions */} {/* Items Section */}
<div className="flex flex-row gap-3"> <div className="mb-2 px-1">
<button <p className="mb-3 text-base font-bold text-slate-900">Order Items</p>
onClick={() => navigate({ to: '/me' })} </div>
className="flex-1 rounded-xl bg-slate-100 py-3.5 text-center font-bold text-slate-600"
>
Back
</button>
<button
onClick={() => setComplaintDialogOpen(true)}
className="flex-1 rounded-xl bg-blue-600 py-3.5 text-center font-bold text-white"
>
Raise Complaint
</button>
</div>
{/* Cancel Order Button */} {orderData.items?.map((item: any, index: number) => (
{order.deliveryStatus !== 'success' && order.deliveryStatus !== 'cancelled' && ( <div
<div className="mt-4"> key={index}
<button className="mb-3 flex flex-row items-center rounded-2xl border border-slate-100 bg-white p-3 shadow-sm"
onClick={() => setCancelDialogOpen(true)}
className="flex w-full flex-row items-center justify-center rounded-xl border border-red-100 bg-red-50 py-3.5"
> >
<XCircle className="mr-2 h-5 w-5 text-red-600" /> <div className="mr-4 flex h-14 w-14 items-center justify-center rounded-xl border border-slate-100 bg-slate-50 p-1">
<p className="font-bold text-red-600">Cancel Order</p> {item.image ? (
</button> <img
</div> src={item.image}
)} alt=""
</div> className="h-full w-full rounded-lg object-cover"
/>
) : (
<Package className="h-6 w-6 text-slate-400" />
)}
</div>
<div className="flex-1">
<p className="text-sm font-bold text-slate-900">{item.productName || item.product?.name || `Product #${item.productId}`}</p>
<p className="mt-1 text-xs text-slate-400">
{item.quantity} × {item.price}
</p>
</div>
<p className="ml-2 text-base font-bold text-slate-900">{item.amount || item.price * item.quantity}</p>
</div>
))}
{/* Cancel Order Dialog */} {/* Coupon */}
<Dialog open={cancelDialogOpen} onClose={() => setCancelDialogOpen(false)}> {orderData.couponCode && (
<div className="p-6"> <div className="mb-4 mt-2 flex flex-row items-center justify-between rounded-2xl border border-emerald-100 bg-emerald-50 p-4">
<div className="mb-4 flex flex-row items-center justify-between"> <div className="flex flex-row items-center">
<p className="text-xl font-bold text-gray-900">Cancel Order</p> <Tag className="h-5 w-5 text-emerald-600" />
<button onClick={() => setCancelDialogOpen(false)}> <div className="ml-3">
<X className="h-6 w-6 text-gray-400" /> <p className="text-sm font-bold text-emerald-900">{orderData.couponCode}</p>
</button> <p className="text-[10px] text-emerald-600">Coupon Applied</p>
</div> </div>
</div>
<p className="font-bold text-emerald-700">-{orderData.discountAmount}</p>
</div>
)}
<div className="mb-4 rounded-xl border border-red-100 bg-red-50 p-4"> {/* Summary Section */}
<p className="text-sm text-red-800"> <div className="mb-6 rounded-2xl border border-slate-100 bg-white p-5">
Are you sure you want to cancel this order? This action cannot be undone. <div className="mb-3 flex flex-row justify-between">
</p> <p className="text-sm text-slate-500">Subtotal</p>
</div> <p className="font-medium text-slate-900">{subtotal}</p>
</div>
<p className="mb-2 font-medium text-gray-700">Reason for cancellation</p> {discountAmount > 0 && (
<textarea <div className="mb-3 flex flex-row justify-between">
className="mb-6 min-h-24 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800" <p className="text-sm text-emerald-600">Discount</p>
value={cancelReason} <p className="font-medium text-emerald-600">-{discountAmount}</p>
onChange={(e) => setCancelReason(e.target.value)} </div>
placeholder="Please tell us why you are cancelling..."
rows={3}
/>
<button
className={`flex w-full items-center justify-center rounded-xl py-4 text-center font-bold text-white shadow-sm ${
cancelOrderMutation.isPending ? 'bg-red-400 opacity-70' : 'bg-red-600'
}`}
onClick={handleCancelOrder}
disabled={cancelOrderMutation.isPending}
>
{cancelOrderMutation.isPending ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
'Confirm Cancellation'
)} )}
</button> <div className="mb-3 flex flex-row justify-between">
</div> <p className="text-sm text-slate-500">Delivery</p>
</Dialog> <p className="font-medium text-slate-900">
{deliveryCharge > 0 ? `${deliveryCharge}` : 'Free'}
</p>
</div>
<div className="flex flex-row items-center justify-between border-t border-slate-50 pt-4">
<p className="text-base font-bold text-slate-900">Total Amount</p>
<p className="text-xl font-bold text-slate-900">{totalAmount}</p>
</div>
</div>
{/* Raise Complaint Dialog */} {/* Footer Actions */}
<Dialog open={complaintDialogOpen} onClose={() => setComplaintDialogOpen(false)}> <div className="flex flex-row gap-3">
<ComplaintForm <button
open={complaintDialogOpen} onClick={() => navigate({ to: '/me' })}
onClose={() => { className="flex-1 rounded-xl bg-slate-100 py-3.5 text-center font-bold text-slate-600"
setComplaintDialogOpen(false) >
refetch() Back
}} </button>
orderId={order.id} <button
/> onClick={() => setComplaintDialogOpen(true)}
</Dialog> className="flex-1 rounded-xl bg-blue-600 py-3.5 text-center font-bold text-white"
</div> >
Raise Complaint
</button>
</div>
{/* Cancel Order Button */}
{!['success', 'delivered', 'cancelled'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
<div className="mt-4">
<button
onClick={() => setCancelDialogOpen(true)}
className="flex w-full flex-row items-center justify-center rounded-xl border border-red-100 bg-red-50 py-3.5"
>
<XCircle className="mr-2 h-5 w-5 text-red-600" />
<p className="font-bold text-red-600">Cancel Order</p>
</button>
</div>
)}
</div>
{/* Cancel Order Dialog */}
<Dialog open={cancelDialogOpen} onClose={() => setCancelDialogOpen(false)}>
<div className="p-6">
<div className="mb-4 flex flex-row items-center justify-between">
<p className="text-xl font-bold text-gray-900">Cancel Order</p>
<button onClick={() => setCancelDialogOpen(false)}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<div className="mb-4 rounded-xl border border-red-100 bg-red-50 p-4">
<p className="text-sm text-red-800">
Are you sure you want to cancel this order? This action cannot be undone.
</p>
</div>
<p className="mb-2 font-medium text-gray-700">Reason for cancellation</p>
<textarea
className="mb-6 min-h-24 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
placeholder="Please tell us why you are cancelling..."
rows={3}
/>
<button
className={`w-full rounded-xl py-4 text-center font-bold text-white shadow-sm ${
cancelOrderMutation.isPending ? 'bg-red-400 opacity-70' : 'bg-red-600'
}`}
onClick={handleCancelOrder}
disabled={cancelOrderMutation.isPending}
>
{cancelOrderMutation.isPending ? 'Cancelling...' : 'Confirm Cancellation'}
</button>
</div>
</Dialog>
{/* Raise Complaint Dialog */}
<Dialog open={complaintDialogOpen} onClose={() => setComplaintDialogOpen(false)}>
<div className="p-6">
<div className="mb-4 flex flex-row items-center justify-between">
<p className="text-xl font-bold text-gray-900">Raise Complaint</p>
<button onClick={() => setComplaintDialogOpen(false)}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<div className="mb-4 rounded-xl border border-amber-100 bg-amber-50 p-4">
<p className="text-sm text-amber-800">
Please describe your issue with this order. Our support team will get back to you within 24 hours.
</p>
</div>
<p className="mb-2 font-medium text-gray-700">Complaint Details</p>
<textarea
className="mb-6 min-h-32 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
value={complaintBody}
onChange={(e) => setComplaintBody(e.target.value)}
placeholder="Describe your issue in detail..."
rows={4}
/>
<button
className={`w-full rounded-xl py-4 text-center font-bold text-white shadow-sm ${
raiseComplaintMutation.isPending ? 'bg-blue-400 opacity-70' : 'bg-blue-600'
}`}
onClick={handleRaiseComplaint}
disabled={raiseComplaintMutation.isPending}
>
{raiseComplaintMutation.isPending ? 'Submitting...' : 'Submit Complaint'}
</button>
</div>
</Dialog>
</div>
</AppContainer>
) )
} }

View file

@ -3,7 +3,6 @@ import { useStoreWithProducts, useAllProducts } from '../hooks/prominent-api-hoo
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { p } from 'web-components' import { p } from 'web-components'
import { AppLayout } from '../components/AppLayout' import { AppLayout } from '../components/AppLayout'
import AddToCartDialog from '../components/AddToCartDialog'
import { ProductCard } from '../components/ProductCard' import { ProductCard } from '../components/ProductCard'
import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores' import { usePopulateCentralStores } from '../hooks/usePopulateCentralStores'
import { ArrowLeft, Store, X, Grid3X3 } from 'lucide-react' import { ArrowLeft, Store, X, Grid3X3 } from 'lucide-react'
@ -178,8 +177,6 @@ function StoreDetailPage() {
)} )}
</div> </div>
</div> </div>
<AddToCartDialog />
</AppLayout> </AppLayout>
) )
} }

View file

@ -542,8 +542,7 @@ export async function getAllOrders(input: GetAllOrdersInput): Promise<AdminGetAl
isPackageVerified: item.is_package_verified, isPackageVerified: item.is_package_verified,
})) }))
.sort((first: any, second: any) => first.id - second.id) .sort((first: any, second: any) => first.id - second.id)
order.address = order.address || {}
return { return {
id: order.id, id: order.id,
orderId: order.id.toString(), orderId: order.id.toString(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

View file

@ -0,0 +1 @@
{"name":"Symbuyote Admin","slug":"freshyoadmin","version":"1.0.0","orientation":"portrait","icon":"./assets/images/symbuyoteadmin.png","scheme":"freshyoadmin","userInterfaceStyle":"automatic","newArchEnabled":true,"ios":{"supportsTablet":true,"bundleIdentifier":"in.freshyo.adminui","infoPlist":{"LSApplicationQueriesSchemes":["ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay"],"ITSAppUsesNonExemptEncryption":false}},"android":{"adaptiveIcon":{"foregroundImage":"./assets/images/symbuyoteadmin.png","backgroundColor":"#fff0f6"},"edgeToEdgeEnabled":true,"package":"in.freshyo.adminui"},"web":{"bundler":"metro","output":"static","favicon":"./assets/images/favicon.png"},"plugins":["expo-router",["expo-splash-screen",{"image":"./assets/images/symbuyoteadmin.png","imageWidth":200,"resizeMode":"contain","backgroundColor":"#ffffff"}],"expo-secure-store"],"experiments":{"typedRoutes":true},"extra":{"router":{},"eas":{"projectId":"55e2f200-eb9d-4880-a193-70f59320e054"}},"runtimeVersion":{"policy":"appVersion"},"updates":{"url":"https://u.expo.dev/55e2f200-eb9d-4880-a193-70f59320e054"},"sdkVersion":"53.0.0","platforms":["ios","android","web"],"androidStatusBar":{"backgroundColor":"#ffffff"}}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>0A2A.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
<string>85F4.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>hermes</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>dev.hermesengine.iphonesimulator</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string></string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.12.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.12.0</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>MinimumOSVersion</key>
<string>15.1</string>
</dict>
</plist>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Info.plist</key>
<data>
4Hno0Ddszl7pNxmsMdj4eZ8APpg=
</data>
</dict>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

Binary file not shown.

View file

@ -0,0 +1 @@
APPL????

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>0A2A.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
<string>85F4.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,535 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>AppIcon60x60@2x.png</key>
<data>
97LNT9kpa48T+CswHSue8trK1eE=
</data>
<key>AppIcon76x76@2x~ipad.png</key>
<data>
VzU6ZM8C95tff2IFjtvjPfEV8Vs=
</data>
<key>Assets.car</key>
<data>
LrIAJ0UWrloOF0ANfYZBMOddVeQ=
</data>
<key>EXConstants.bundle/Info.plist</key>
<data>
Hlxsc20/U0owoVLTeqzSm5ybNIs=
</data>
<key>EXConstants.bundle/app.config</key>
<data>
IFa3PxmiSkaJ0td8DhhIk/PexXY=
</data>
<key>EXUpdates.bundle/Info.plist</key>
<data>
wjdfAxEpgfQFinoWBfIM8p9jaw8=
</data>
<key>Expo.plist</key>
<data>
yXM53emO8rHxLh7yvjjxI7jUS4U=
</data>
<key>ExpoApplication_privacy.bundle/Info.plist</key>
<data>
8BRDaa8J7FLCzhVYdsGF90Fhe6A=
</data>
<key>ExpoApplication_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
mUc2YHiDtobIhFXi+Mvm12TXeb8=
</data>
<key>ExpoConstants_privacy.bundle/Info.plist</key>
<data>
gHWCze8PybGkM8T+sLc+3tpj/QE=
</data>
<key>ExpoConstants_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
dHAsEQehwJCS8hMOpBoz7emiNj8=
</data>
<key>ExpoDevice_privacy.bundle/Info.plist</key>
<data>
6hGpMQ+NbBTY+ghWXzsUw8XJGyM=
</data>
<key>ExpoDevice_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
hWEgzzi+YPgmddeTqWfAi6jGQ0E=
</data>
<key>ExpoFileSystem_privacy.bundle/Info.plist</key>
<data>
WIOt6Nu0S3BZ/+6OsBQFrMyXaNE=
</data>
<key>ExpoFileSystem_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
UieOpg4b1PxYR6jA3/cs9mU9rdo=
</data>
<key>ExpoNotifications_privacy.bundle/Info.plist</key>
<data>
BwASpOTXQeKbJUrAWQFpwRpHkM8=
</data>
<key>ExpoNotifications_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
dHAsEQehwJCS8hMOpBoz7emiNj8=
</data>
<key>ExpoSystemUI_privacy.bundle/Info.plist</key>
<data>
ZY9+IxqDzlo+4baYZWU5AcIgICQ=
</data>
<key>ExpoSystemUI_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
dHAsEQehwJCS8hMOpBoz7emiNj8=
</data>
<key>Frameworks/hermes.framework/Info.plist</key>
<data>
4Hno0Ddszl7pNxmsMdj4eZ8APpg=
</data>
<key>Frameworks/hermes.framework/_CodeSignature/CodeResources</key>
<data>
UltW0Jw9IEI+gE7DBGy5/VsUBrw=
</data>
<key>Frameworks/hermes.framework/hermes</key>
<data>
zNwvRCO4iW9WNlr9JGQIrylk2Ko=
</data>
<key>Info.plist</key>
<data>
4ZwmTOgnIm01EgWvwwqDN/hpTVI=
</data>
<key>PkgInfo</key>
<data>
n57qDP4tZfLD1rCS43W0B4LQjzE=
</data>
<key>PrivacyInfo.xcprivacy</key>
<data>
QWVPQQrLs8XwFZWrDE5vARWvUdA=
</data>
<key>RCT-Folly_privacy.bundle/Info.plist</key>
<data>
QV6mi/fThThHpU2soqgmADF/NUI=
</data>
<key>ReachabilitySwift.bundle/Info.plist</key>
<data>
R1f4iy65ziHGDflHUDJ3rYb7QJw=
</data>
<key>ReachabilitySwift.bundle/PrivacyInfo.xcprivacy</key>
<data>
0RESd+++ZxZWQhIEMSOOvP7phYs=
</data>
<key>React-Core_privacy.bundle/Info.plist</key>
<data>
bwZ/mVvwWYRpCtLUK8MTyiLp/JU=
</data>
<key>React-Core_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
ZahcOiTSEcJJdvNh0xqgAKPqbMs=
</data>
<key>React-cxxreact_privacy.bundle/Info.plist</key>
<data>
DHapNtVVUNHS9BKJUN7HbkRj/0E=
</data>
<key>React-cxxreact_privacy.bundle/PrivacyInfo.xcprivacy</key>
<data>
dxJQFdQ77efnBkB0VBZmuIamJ4g=
</data>
<key>SDWebImage.bundle/Info.plist</key>
<data>
MiKmS7AM8ulTkag/cANN1izmsx4=
</data>
<key>SDWebImage.bundle/PrivacyInfo.xcprivacy</key>
<data>
PFHYbs0V3eUFDWQyYQcwEetuqEk=
</data>
<key>SplashScreen.storyboardc/EXPO-VIEWCONTROLLER-1-view-EXPO-ContainerView.nib</key>
<data>
+LtcMiEvVQs2QdoxGRtI33YPCYs=
</data>
<key>SplashScreen.storyboardc/Info.plist</key>
<data>
E68oTK3pecVBbvVw/Td2doSlDiA=
</data>
<key>SplashScreen.storyboardc/SplashScreenViewController.nib</key>
<data>
XO9GpHETPa/KEeOkIqhZoQ6OIvU=
</data>
<key>SymbuyoteAdmin.debug.dylib</key>
<data>
ZwTTt/x0U07XvLmnb3+tTdSeZn0=
</data>
<key>__preview.dylib</key>
<data>
NKNb0Q7QaNmfiMI7dnipVcSb6Bo=
</data>
<key>boost_privacy.bundle/Info.plist</key>
<data>
V94X3Cp8LSj+pZ/hfbsOD4huj5Q=
</data>
<key>glog_privacy.bundle/Info.plist</key>
<data>
MxuR75ZIsXAD5pxH3nEwX9uafJ0=
</data>
</dict>
<key>files2</key>
<dict>
<key>AppIcon60x60@2x.png</key>
<dict>
<key>hash2</key>
<data>
1EdHEGg/ZaMS6Zip6Ie7YlVSaTP8FBbCsE+pAI+y0Yk=
</data>
</dict>
<key>AppIcon76x76@2x~ipad.png</key>
<dict>
<key>hash2</key>
<data>
0FSd2xHbBOAtgMQ4Et7BIh1mcFoPKKSNPELVi5SQJAc=
</data>
</dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
VeXk52gO5+lQhdUDpJkX5HFitYSp/HTm9kpkxMmfwws=
</data>
</dict>
<key>EXConstants.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
jZG3+Tzakbtg344R3nPmevDrI3G9hqlhuDM+DGKsmHY=
</data>
</dict>
<key>EXConstants.bundle/app.config</key>
<dict>
<key>hash2</key>
<data>
KZQx0xB/v36dzA1NH6PtrZPUnlG5NMIASOIYzeC36j4=
</data>
</dict>
<key>EXUpdates.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
TSLIu7uoFgEkmWl6rOkXOLHgeB1Z/nLtqptH/f2Kzds=
</data>
</dict>
<key>Expo.plist</key>
<dict>
<key>hash2</key>
<data>
WwvRV3RJHdWPGFQnnyrsouAha0/2EaB+goHuQsVMZ2Q=
</data>
</dict>
<key>ExpoApplication_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
OF6pzmZB+LuE1u+wzImbqZDIJmhoflDtg2sTmrOtGiY=
</data>
</dict>
<key>ExpoApplication_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
tIdj/9KcutgVElrlbhBzJz5BiuCGED/H3/fvvsFnWqo=
</data>
</dict>
<key>ExpoConstants_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
MGRLpoZ+01WpddRUuE8TLN2RgOiqhzIDZEWy3MA1kNQ=
</data>
</dict>
<key>ExpoConstants_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
Ky2O23HVHFsfGs5M2yipS68i/d6bvy4r/BfRh/97X0c=
</data>
</dict>
<key>ExpoDevice_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
VW+4r911lj+g2ALGOJp8aFap1Y0t3bccy5wunDzTLDs=
</data>
</dict>
<key>ExpoDevice_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
2JioNW3Ie3zSsXkh1opGNtQkBns6dcg7eTX9eXcZycs=
</data>
</dict>
<key>ExpoFileSystem_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
ByWljZT3TE7/EQIJQP/napK+gMwwBFaLiROcsjSHmJk=
</data>
</dict>
<key>ExpoFileSystem_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
M7DgdPJz9YimS6VjKQnGqM8fFtuOTNhMaXi9Ii39Zb0=
</data>
</dict>
<key>ExpoNotifications_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
tKJ+YduFArUfPD3zQIGLIPtXagl8rbk4RDDBfsLvJC8=
</data>
</dict>
<key>ExpoNotifications_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
Ky2O23HVHFsfGs5M2yipS68i/d6bvy4r/BfRh/97X0c=
</data>
</dict>
<key>ExpoSystemUI_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
tGD6qrPgcjmHblKSEqq1CRuX18qzPmRBwHGfZltFSCw=
</data>
</dict>
<key>ExpoSystemUI_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
Ky2O23HVHFsfGs5M2yipS68i/d6bvy4r/BfRh/97X0c=
</data>
</dict>
<key>Frameworks/hermes.framework/Info.plist</key>
<dict>
<key>hash2</key>
<data>
JXB+dif18YekowEWiL1F0bJhRmDJCNhC7Yuki1yxMK0=
</data>
</dict>
<key>Frameworks/hermes.framework/_CodeSignature/CodeResources</key>
<dict>
<key>hash2</key>
<data>
1oRx8Mn/IhJGRZOGyHTCY2w0MZ+C71dPBNeLWZ06EJk=
</data>
</dict>
<key>Frameworks/hermes.framework/hermes</key>
<dict>
<key>hash2</key>
<data>
0po9tSLjyYKKx+ArJ1vK4kNtKcwcCF1fCYfIP3UR8M8=
</data>
</dict>
<key>PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
0iT0B29OMhTbiXYnweoynu+q6Im8gta4P/eeuquI8zU=
</data>
</dict>
<key>RCT-Folly_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
0dcC3Z35ltB1Rk2HWpjCzA4wPFt+2WaTjgv/z5AxE1E=
</data>
</dict>
<key>ReachabilitySwift.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
a0Ywukh2Qq/wQxGNTeIC7/8oN2YZMvE9YYIecPYUN1M=
</data>
</dict>
<key>ReachabilitySwift.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
pwgfFQbJDo5nMQDlwWHnZbi3piREbiC9S9bvrKgICLg=
</data>
</dict>
<key>React-Core_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
omnNUjXWFudh+cE0TJqsI2YDpTvWTixl77Voxv40Jf4=
</data>
</dict>
<key>React-Core_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
fAiWnkWWIabs7AQ8zbkBJlHxhYEZRfdVF8Yj2uLhFic=
</data>
</dict>
<key>React-cxxreact_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
tKBcBEwmtUE9GGaNVVGdsi6/KnlaX4bi1D54dKd5mm4=
</data>
</dict>
<key>React-cxxreact_privacy.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
BYfRVcVqb08GR+vGtqC9AmYVzWEO6PIJqXhrealq0zU=
</data>
</dict>
<key>SDWebImage.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
ssr4wetNnB+bGHW2na0M24sSv1inTqC0ReqiCMf6fWU=
</data>
</dict>
<key>SDWebImage.bundle/PrivacyInfo.xcprivacy</key>
<dict>
<key>hash2</key>
<data>
A7LHCDOjMaKx79Ef8WjtAqjq39Xn0fvzDuzHUJpK6kc=
</data>
</dict>
<key>SplashScreen.storyboardc/EXPO-VIEWCONTROLLER-1-view-EXPO-ContainerView.nib</key>
<dict>
<key>hash2</key>
<data>
X+tAJBYf6l0fu6OOgc8+aot/0jOHh5N+R9T2CA7fpzw=
</data>
</dict>
<key>SplashScreen.storyboardc/Info.plist</key>
<dict>
<key>hash2</key>
<data>
dNvO7WzwpeXGmDR5MyjJeD7Ksd5ILUlU4lfKITK3Q68=
</data>
</dict>
<key>SplashScreen.storyboardc/SplashScreenViewController.nib</key>
<dict>
<key>hash2</key>
<data>
nBo0wSHSJHlAjPDqrLNJFUjO0WZVeZuuO19/I4AxS6g=
</data>
</dict>
<key>SymbuyoteAdmin.debug.dylib</key>
<dict>
<key>hash2</key>
<data>
bJ9FzLhQRgwj5TfBA8qj2AMq6UuOeEBnqpn4Mdj7YRc=
</data>
</dict>
<key>__preview.dylib</key>
<dict>
<key>hash2</key>
<data>
JxYb3r7Pg5bMt+qPjY4ibIde7zNM5U7OL6HGGuldoTM=
</data>
</dict>
<key>boost_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
ewhZPFvqBmGCXr9cyPcgBgi1XfwdibD9dwEvGqRXAFc=
</data>
</dict>
<key>glog_privacy.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
ppv2/Di+oXBAtgOAUjnelHqDc6Xxh7Ki3j5KlqckbEY=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

Binary file not shown.

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

View file

@ -0,0 +1 @@
{"name":"Freshyo","slug":"freshyo","version":"1.2.0","orientation":"portrait","icon":"./assets/images/freshyo-logo.png","scheme":"freshyo","userInterfaceStyle":"automatic","newArchEnabled":true,"ios":{"buildNumber":"1.1.0","supportsTablet":true,"bundleIdentifier":"com.freshyotrial.app","infoPlist":{"LSApplicationQueriesSchemes":["ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay","ppemerchantsdkv1","ppemerchantsdkv2","ppemerchantsdkv3","paytmmp","gpay"],"ITSAppUsesNonExemptEncryption":false,"NSPhotoLibraryUsageDescription":"This app uses photo library to allow users to upload pictures as a part or product's review and feedback.","NSLocationWhenInUseUsageDescription":"This app uses your location to decide if your place is serviceable or not."}},"android":{"softwareKeyboardLayoutMode":"resize","adaptiveIcon":{"foregroundImage":"./assets/images/freshyo-logo.png","backgroundColor":"#fff0f6"},"edgeToEdgeEnabled":true,"package":"in.freshyo.app","googleServicesFile":"./google-services.json"},"web":{"bundler":"metro","output":"static","favicon":"./assets/images/favicon.png"},"plugins":["expo-router",["expo-splash-screen",{"image":"./assets/images/freshyo-logo.png","imageWidth":200,"resizeMode":"contain","backgroundColor":"#ffffff"}],"expo-secure-store","expo-notifications"],"experiments":{"typedRoutes":true},"extra":{"router":{},"eas":{"projectId":"7f3e7611-f7a8-45f9-8a99-c2b1f6454d48"}},"runtimeVersion":{"policy":"appVersion"},"updates":{"url":"https://u.expo.dev/7f3e7611-f7a8-45f9-8a99-c2b1f6454d48"},"owner":"mohammedshafiuddin54","sdkVersion":"53.0.0","platforms":["ios","android","web"],"androidStatusBar":{"backgroundColor":"#ffffff"}}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>0A2A.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
<string>85F4.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Info.plist</key>
<data>
m2+OcUVS+NCF/RaQ0NMeexAJIbU=
</data>
</dict>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Info.plist</key>
<data>
IgSq/mUvfi0nncHC2TAKVG0zPwA=
</data>
</dict>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

Some files were not shown because too many files have changed in this diff Show more