Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5338505bfc | ||
|
|
306297ed09 | ||
|
|
8c9df8151a | ||
|
|
1e9796104d |
200 changed files with 671 additions and 6239 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,7 +7,6 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,7 @@ 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) => {
|
${orderData.orderItems?.map((item: any) => ` • ${item.product?.name || 'Unknown'} x${item.quantity}`).join('\n') || ' N/A'}
|
||||||
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'}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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.')
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
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'],
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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>)
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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]
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@ 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;
|
||||||
|
|
@ -259,10 +266,15 @@ 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`}>
|
||||||
<Image
|
<View style={tw`items-center`}>
|
||||||
source={{ uri: productsById[item.productId]?.images?.[0] }}
|
<Image
|
||||||
style={tw`w-8 h-8 rounded-lg bg-slate-50 border border-slate-100`}
|
source={{ uri: productsById[item.productId]?.images?.[0] }}
|
||||||
/>
|
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`}>
|
||||||
|
|
@ -283,7 +295,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`}>
|
||||||
|
|
|
||||||
173
apps/web-ui/src/components/ComplaintForm.tsx
Normal file
173
apps/web-ui/src/components/ComplaintForm.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
119
apps/web-ui/src/hooks/useUploadToObjectStorage.ts
Normal file
119
apps/web-ui/src/hooks/useUploadToObjectStorage.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
6
apps/web-ui/src/lib/string-manipulators.ts
Normal file
6
apps/web-ui/src/lib/string-manipulators.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const orderStatusManipulator = (status: string): string => {
|
||||||
|
if (status.toLowerCase() === 'pending') {
|
||||||
|
return 'Confirmed'
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ 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')({
|
||||||
|
|
@ -432,6 +433,8 @@ function ProductDetailPage() {
|
||||||
|
|
||||||
{/* Floating Cart Bar */}
|
{/* Floating Cart Bar */}
|
||||||
<FloatingCartBar />
|
<FloatingCartBar />
|
||||||
|
|
||||||
|
<AddToCartDialog />
|
||||||
</div>
|
</div>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ 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')({
|
||||||
|
|
@ -175,6 +176,8 @@ function SearchPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AddToCartDialog />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
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 { p, AppContainer, div } from 'web-components'
|
import { ChevronLeft, CreditCard, CheckCircle, Zap, Edit2, Tag, XCircle, X, AlertCircle, Loader2 } from 'lucide-react'
|
||||||
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')({
|
||||||
|
|
@ -13,11 +14,15 @@ 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 { data: orderData, isLoading, error, refetch } = trpc.user.order.getOrderById.useQuery(
|
const {
|
||||||
{ orderId: orderId+'' },
|
data: orderData,
|
||||||
{ enabled: !!orderId }
|
isLoading,
|
||||||
|
error,
|
||||||
|
refetch,
|
||||||
|
} = trpc.user.order.getOrderById.useQuery(
|
||||||
|
{ orderId: id },
|
||||||
|
{ enabled: !!id }
|
||||||
)
|
)
|
||||||
|
|
||||||
const [isEditingNotes, setIsEditingNotes] = useState(false)
|
const [isEditingNotes, setIsEditingNotes] = useState(false)
|
||||||
|
|
@ -26,15 +31,16 @@ 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')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -43,16 +49,21 @@ 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 raiseComplaintMutation = trpc.user.complaint.raise.useMutation({
|
const handleCancelOrder = () => {
|
||||||
onSuccess: () => {
|
if (!order) return
|
||||||
setComplaintDialogOpen(false)
|
if (!cancelReason.trim()) {
|
||||||
setComplaintBody('')
|
alert('Please enter a reason for cancellation')
|
||||||
refetch()
|
return
|
||||||
},
|
}
|
||||||
})
|
cancelOrderMutation.mutate({ id: order.id, reason: cancelReason })
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (orderData?.userNotes) {
|
if (orderData?.userNotes) {
|
||||||
|
|
@ -61,419 +72,376 @@ 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 (
|
||||||
<AppContainer>
|
<div className="flex min-h-screen flex-1 items-center justify-center bg-white">
|
||||||
<div className="flex min-h-full flex-1 items-center justify-center bg-white">
|
<p className="font-medium text-slate-400">Loading details...</p>
|
||||||
<p className="font-medium text-slate-400">Loading details...</p>
|
</div>
|
||||||
</div>
|
|
||||||
</AppContainer>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !orderData) {
|
if (error || !orderData) {
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<div className="flex min-h-screen flex-1 flex-col items-center justify-center bg-white p-8">
|
||||||
<div className="flex min-h-full flex-1 flex-col items-center justify-center bg-white p-8">
|
<AlertCircle className="mb-4 h-12 w-12 text-red-500" />
|
||||||
<AlertCircle className="mb-4 h-12 w-12 text-red-500" />
|
<p className="mt-4 text-lg font-bold text-slate-900">Failed to load</p>
|
||||||
<p className="mt-4 text-lg font-bold text-slate-900">Failed to load</p>
|
<button
|
||||||
<button
|
onClick={() => navigate({ to: '/me/orders' })}
|
||||||
onClick={() => navigate({ to: '/me/orders' })}
|
className="mt-6 rounded-xl bg-slate-900 px-6 py-2 font-bold text-white"
|
||||||
className="mt-6 rounded-xl bg-slate-900 px-6 py-2 font-bold text-white"
|
>
|
||||||
>
|
Go Back
|
||||||
Go Back
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</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', bgColor: 'bg-green-50', textColor: 'text-green-700' }
|
return { label: 'Delivered', color: '#10B981' }
|
||||||
case 'cancelled':
|
case 'cancelled':
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return { label: 'Cancelled', color: '#EF4444', bgColor: 'bg-red-50', textColor: 'text-red-700' }
|
return { label: 'Cancelled', color: '#EF4444' }
|
||||||
case 'pending':
|
case 'pending':
|
||||||
case 'processing':
|
case 'processing':
|
||||||
return { label: 'Pending', color: '#F59E0B', bgColor: 'bg-yellow-50', textColor: 'text-yellow-700' }
|
return { label: 'Pending', color: '#F59E0B' }
|
||||||
default:
|
default:
|
||||||
return { label: status, color: '#3B82F6', bgColor: 'bg-blue-50', textColor: 'text-blue-700' }
|
return { label: status, color: '#1570EF' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderAny = orderData as any
|
const subtotal = order.items.reduce((sum: number, item: any) => sum + item.amount, 0)
|
||||||
const subtotal = orderData.items?.reduce((sum: number, item: any) => sum + (item.amount || item.price * item.quantity), 0) || 0
|
const discountAmount = order.discountAmount || 0
|
||||||
const discountAmount = orderData.discountAmount || 0
|
const deliveryCharge = orderAny.deliveryCharge || 0
|
||||||
const deliveryCharge = orderData.deliveryCharge || 0
|
const totalAmount = order.orderAmount
|
||||||
const totalAmount = orderData.orderAmount || subtotal - discountAmount + deliveryCharge
|
const statusConfig = getStatusConfig(order.deliveryStatus || 'pending')
|
||||||
const statusConfig = getStatusConfig(orderData.deliveryStatus || 'pending')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<div className="flex min-h-screen flex-1 flex-col bg-slate-50">
|
||||||
<div className="flex min-h-full flex-1 flex-col bg-slate-50">
|
{/* Header */}
|
||||||
{/* Header */}
|
<div className="flex flex-row items-center justify-between border-b border-slate-100 bg-white px-6 pb-4 pt-4">
|
||||||
<div className="flex flex-row items-center justify-between border-b border-slate-100 bg-white px-6 pb-4 pt-4">
|
<div className="flex flex-row items-center">
|
||||||
<div className="flex flex-row items-center">
|
<button
|
||||||
<button
|
onClick={() => navigate({ to: '/me/orders' })}
|
||||||
onClick={() => navigate({ to: '/me/orders' })}
|
className="-ml-2 mr-3 p-2"
|
||||||
className="-ml-2 mr-3 p-2"
|
>
|
||||||
>
|
<ChevronLeft className="h-6 w-6 text-slate-800" />
|
||||||
<ChevronLeft className="h-6 w-6 text-slate-800" />
|
</button>
|
||||||
</button>
|
<div>
|
||||||
<div>
|
<p className="text-lg font-bold text-slate-900">Order #{order.orderId || order.id}</p>
|
||||||
<p className="text-lg font-bold text-slate-900">Order #{orderData.orderId || orderData.id}</p>
|
<p className="text-xs text-slate-400">
|
||||||
<p className="text-xs text-slate-400">
|
{dayjs(order.orderDate || order.createdAt).format('DD MMM, h:mm A')}
|
||||||
{dayjs(orderData.orderDate || orderData.createdAt).format('DD MMM, h:mm A')}
|
</p>
|
||||||
</p>
|
{order.isFlashDelivery && (
|
||||||
{orderData.isFlashDelivery && (
|
<p className="mt-1 text-xs font-bold text-amber-600">⚡ 1 Hr Delivery</p>
|
||||||
<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 ${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>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
<div className="flex-1 overflow-y-auto p-4 pb-12">
|
<div
|
||||||
{/* 1 Hr Delivery Banner */}
|
className="rounded-full px-3 py-1"
|
||||||
{orderData.isFlashDelivery && (
|
style={{ backgroundColor: statusConfig.color + '10' }}
|
||||||
<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">
|
<p
|
||||||
<Zap className="h-6 w-6 text-amber-600" />
|
className="text-[10px] font-bold uppercase"
|
||||||
<div className="ml-3 flex-1">
|
style={{ color: statusConfig.color }}
|
||||||
<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">
|
|
||||||
{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>
|
|
||||||
|
|
||||||
{/* 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: 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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Items Section */}
|
|
||||||
<div className="mb-2 px-1">
|
|
||||||
<p className="mb-3 text-base font-bold text-slate-900">Order Items</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{orderData.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 rounded-xl border border-slate-100 bg-slate-50 p-1">
|
{orderStatusManipulator(statusConfig.label)}
|
||||||
{item.image ? (
|
</p>
|
||||||
<img
|
</div>
|
||||||
src={item.image}
|
{order.isFlashDelivery && (
|
||||||
alt=""
|
<div className="rounded-full border border-amber-200 bg-amber-100 px-2 py-1">
|
||||||
className="h-full w-full rounded-lg object-cover"
|
<p className="text-[10px] font-black uppercase text-amber-700">⚡</p>
|
||||||
/>
|
</div>
|
||||||
) : (
|
)}
|
||||||
<Package className="h-6 w-6 text-slate-400" />
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
<div className="flex-1 overflow-y-auto p-4 pb-12">
|
||||||
<p className="text-sm font-bold text-slate-900">{item.productName || item.product?.name || `Product #${item.productId}`}</p>
|
{/* 1 Hr Delivery Banner */}
|
||||||
<p className="mt-1 text-xs text-slate-400">
|
{order.isFlashDelivery && (
|
||||||
{item.quantity} × ₹{item.price}
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-2 text-base font-bold text-slate-900">₹{item.amount || item.price * item.quantity}</p>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Coupon */}
|
{/* Main Info Card */}
|
||||||
{orderData.couponCode && (
|
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
|
||||||
<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">
|
<div className="flex flex-row items-center">
|
||||||
<Tag className="h-5 w-5 text-emerald-600" />
|
<div className="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-slate-50">
|
||||||
<div className="ml-3">
|
<CreditCard className="h-5 w-5 text-slate-500" />
|
||||||
<p className="text-sm font-bold text-emerald-900">{orderData.couponCode}</p>
|
</div>
|
||||||
<p className="text-[10px] text-emerald-600">Coupon Applied</p>
|
<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>
|
||||||
<p className="font-bold text-emerald-700">-₹{orderData.discountAmount}</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>
|
||||||
|
|
||||||
{/* Summary Section */}
|
{(order.deliveryDate || order.isFlashDelivery) &&
|
||||||
<div className="mb-6 rounded-2xl border border-slate-100 bg-white p-5">
|
['delivered', 'success'].includes((order.deliveryStatus || '').toLowerCase()) && (
|
||||||
<div className="mb-3 flex flex-row justify-between">
|
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
|
||||||
<p className="text-sm text-slate-500">Subtotal</p>
|
{order.isFlashDelivery ? (
|
||||||
<p className="font-medium text-slate-900">₹{subtotal}</p>
|
<Zap className="h-4 w-4 text-amber-600" />
|
||||||
</div>
|
) : (
|
||||||
{discountAmount > 0 && (
|
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||||
<div className="mb-3 flex flex-row justify-between">
|
)}
|
||||||
<p className="text-sm text-emerald-600">Discount</p>
|
<p className={`ml-2 text-xs font-medium ${order.isFlashDelivery ? 'text-amber-700' : 'text-slate-600'}`}>
|
||||||
<p className="font-medium text-emerald-600">-₹{discountAmount}</p>
|
{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>
|
</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 */}
|
{order.isFlashDelivery &&
|
||||||
<div className="flex flex-row gap-3">
|
!['delivered', 'success'].includes((order.deliveryStatus || '').toLowerCase()) && (
|
||||||
<button
|
<div className="flex flex-row items-center border-t border-slate-50 pt-4">
|
||||||
onClick={() => navigate({ to: '/me' })}
|
<Zap className="h-4 w-4 text-amber-600" />
|
||||||
className="flex-1 rounded-xl bg-slate-100 py-3.5 text-center font-bold text-slate-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')}
|
||||||
Back
|
</p>
|
||||||
</button>
|
</div>
|
||||||
<button
|
)}
|
||||||
onClick={() => setComplaintDialogOpen(true)}
|
</div>
|
||||||
className="flex-1 rounded-xl bg-blue-600 py-3.5 text-center font-bold text-white"
|
|
||||||
>
|
|
||||||
Raise Complaint
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Cancel Order Button */}
|
{/* Special Instructions */}
|
||||||
{!['success', 'delivered', 'cancelled'].includes((orderData.deliveryStatus || '').toLowerCase()) && (
|
<div className="mb-4 rounded-2xl border border-slate-100 bg-white p-5">
|
||||||
<div className="mt-4">
|
<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
|
<button
|
||||||
onClick={() => setCancelDialogOpen(true)}
|
onClick={() => {
|
||||||
className="flex w-full flex-row items-center justify-center rounded-xl border border-red-100 bg-red-50 py-3.5"
|
setNotesInput(notesText || '')
|
||||||
|
setIsEditingNotes(true)
|
||||||
|
}}
|
||||||
|
className="flex flex-row items-center rounded-lg bg-slate-50 px-2 py-1"
|
||||||
>
|
>
|
||||||
<XCircle className="mr-2 h-5 w-5 text-red-600" />
|
<Edit2 className="mr-1 h-3 w-3 text-slate-500" />
|
||||||
<p className="font-bold text-red-600">Cancel Order</p>
|
<p className="text-xs font-medium text-slate-500">{notesText ? 'Edit' : 'Add'}</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Cancel Order Dialog */}
|
{/* Cancellation Detail */}
|
||||||
<Dialog open={cancelDialogOpen} onClose={() => setCancelDialogOpen(false)}>
|
{order.cancelReason && (
|
||||||
<div className="p-6">
|
<div className="mb-4 rounded-2xl border border-rose-100 bg-rose-50 p-5">
|
||||||
<div className="mb-4 flex flex-row items-center justify-between">
|
<p className="mb-2 text-[10px] font-bold uppercase text-rose-700">Cancellation Reason</p>
|
||||||
<p className="text-xl font-bold text-gray-900">Cancel Order</p>
|
<p className="text-sm font-medium text-rose-900">{order.cancelReason}</p>
|
||||||
<button onClick={() => setCancelDialogOpen(false)}>
|
{order.refundAmount && (
|
||||||
<X className="h-6 w-6 text-gray-400" />
|
<div className="mt-3 flex flex-row items-center justify-between border-t border-rose-200/50 pt-3">
|
||||||
</button>
|
<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">
|
||||||
<div className="mb-4 rounded-xl border border-red-100 bg-red-50 p-4">
|
<p className="line-clamp-1 text-sm font-bold text-slate-900">{item.productName}</p>
|
||||||
<p className="text-sm text-red-800">
|
<p className="mt-1 text-xs text-slate-400">{item.quantity} × ₹{item.price}</p>
|
||||||
Are you sure you want to cancel this order? This action cannot be undone.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="ml-2 text-base font-bold text-slate-900">₹{item.amount}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
<p className="mb-2 font-medium text-gray-700">Reason for cancellation</p>
|
{/* Coupon */}
|
||||||
<textarea
|
{order.couponCode && (
|
||||||
className="mb-6 min-h-24 w-full rounded-xl border border-gray-200 bg-gray-50 p-4 text-base text-gray-800"
|
<div className="mb-4 mt-2 flex flex-row items-center justify-between rounded-2xl border border-emerald-100 bg-emerald-50 p-4">
|
||||||
value={cancelReason}
|
<div className="flex flex-row items-center">
|
||||||
onChange={(e) => setCancelReason(e.target.value)}
|
<Tag className="h-5 w-5 text-emerald-600" />
|
||||||
placeholder="Please tell us why you are cancelling..."
|
<div className="ml-3">
|
||||||
rows={3}
|
<p className="text-sm font-bold text-emerald-900">{order.couponCode}</p>
|
||||||
/>
|
<p className="text-[10px] text-emerald-600">Coupon Applied</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="font-bold text-emerald-700">-₹{order.discountAmount}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Summary Section */}
|
||||||
|
<div className="mb-6 rounded-2xl border border-slate-100 bg-white p-5">
|
||||||
|
<div className="mb-3 flex flex-row justify-between">
|
||||||
|
<p className="text-sm text-slate-500">Subtotal</p>
|
||||||
|
<p className="font-medium text-slate-900">₹{subtotal}</p>
|
||||||
|
</div>
|
||||||
|
{discountAmount > 0 && (
|
||||||
|
<div className="mb-3 flex flex-row justify-between">
|
||||||
|
<p className="text-sm text-emerald-600">Discount</p>
|
||||||
|
<p className="font-medium text-emerald-600">-₹{discountAmount}</p>
|
||||||
|
</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 */}
|
||||||
|
<div className="flex flex-row gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate({ to: '/me' })}
|
||||||
|
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 */}
|
||||||
|
{order.deliveryStatus !== 'success' && order.deliveryStatus !== 'cancelled' && (
|
||||||
|
<div className="mt-4">
|
||||||
<button
|
<button
|
||||||
className={`w-full rounded-xl py-4 text-center font-bold text-white shadow-sm ${
|
onClick={() => setCancelDialogOpen(true)}
|
||||||
cancelOrderMutation.isPending ? 'bg-red-400 opacity-70' : 'bg-red-600'
|
className="flex w-full flex-row items-center justify-center rounded-xl border border-red-100 bg-red-50 py-3.5"
|
||||||
}`}
|
|
||||||
onClick={handleCancelOrder}
|
|
||||||
disabled={cancelOrderMutation.isPending}
|
|
||||||
>
|
>
|
||||||
{cancelOrderMutation.isPending ? 'Cancelling...' : 'Confirm Cancellation'}
|
<XCircle className="mr-2 h-5 w-5 text-red-600" />
|
||||||
|
<p className="font-bold text-red-600">Cancel Order</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</AppContainer>
|
|
||||||
|
{/* 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={`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>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Raise Complaint Dialog */}
|
||||||
|
<Dialog open={complaintDialogOpen} onClose={() => setComplaintDialogOpen(false)}>
|
||||||
|
<ComplaintForm
|
||||||
|
open={complaintDialogOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setComplaintDialogOpen(false)
|
||||||
|
refetch()
|
||||||
|
}}
|
||||||
|
orderId={order.id}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ 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'
|
||||||
|
|
@ -177,6 +178,8 @@ function StoreDetailPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AddToCartDialog />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -542,7 +542,8 @@ 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.
|
Before Width: | Height: | Size: 9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
{"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.
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,34 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
<?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.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
APPL????
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,8 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,29 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,21 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,23 +0,0 @@
|
||||||
<?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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,535 +0,0 @@
|
||||||
<?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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,16 +0,0 @@
|
||||||
<?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.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
{"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.
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,34 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
|
|
@ -1,14 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,101 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,101 +0,0 @@
|
||||||
<?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>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue