Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .cursor/rules/function-style.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: Function style convention — arrow functions for non-components, function declarations for React components; use when writing or reviewing TS/TSX
globs: "**/*.{ts,tsx}"
alwaysApply: false
---

# Function Style Convention

Unify function style across the codebase:

## Use arrow functions

For **all non-component code** (helpers, API functions, handlers, mappers, hooks, async functions, etc.):

- **Format**: `const name = (params): ReturnType => { ... }` or `export const name = (params): ReturnType => { ... }`
- **Async**: `export const name = async (params): Promise<ReturnType> => { ... }`
- **Single expression**: `const name = (x: T): R => expression;` when the body is a single return.

Examples: `mapHttpError`, `getApiUrl`, `request`, `buildProductsQuery`, `getProducts`, `toUserResponse`, `toListItem`, `healthHandler`, `useNotification`, `useLocalStorage`, `getErrorMessage`, `formatDateTime`, `notifyError`.

## Use `function` declarations

For **React components** (anything that returns JSX or is used as `<Component />`):

- **Format**: `export function ComponentName(props): React.ReactElement` or `function ComponentName(props) { ... }`
- Keep subcomponents (e.g. `ProductImageUploadField`, `LayoutContent`, `GroupLinks`) and providers (e.g. `NotificationProvider`, `DataBoundary`) as `function` as well.

Examples: `ProductsPage`, `ProductDetailPage`, `DataBoundary`, `ProductEditModal`, `NotificationProvider`, `DefaultLayout`, `AuthGuard`, `SidebarProvider`.

## Summary

| Kind | Style |
|------|--------|
| React component / returns JSX | `function Name()` |
| Everything else (utils, API, handlers, hooks, mappers) | `const name = () =>` |

Preserve existing explicit return types and type annotations when converting.
35 changes: 18 additions & 17 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,26 +205,27 @@ const deleteUserRoute = createRoute({
});

// Single OpenAPIHono with one continuous chain so RPC schema is preserved for hc<AppType>.
// Handlers are typed as Promise<Response> / Response; OpenAPIHono expects TypedResponse, so cast is required.
const api = new OpenAPIHono()
.openapi(healthRoute, healthHandler as unknown as RouteHandler<typeof healthRoute>)
.openapi(healthLiveRoute, healthLiveHandler as unknown as RouteHandler<typeof healthLiveRoute>)
.openapi(listPriceHistoryRoute, listPriceHistoryHandler as RouteHandler<typeof listPriceHistoryRoute>)
.openapi(getPriceHistoryByIdRoute, getPriceHistoryByIdHandler as RouteHandler<typeof getPriceHistoryByIdRoute>)
.openapi(createPriceHistoryRoute, createPriceHistoryHandler as RouteHandler<typeof createPriceHistoryRoute>)
.openapi(listInventoryAdjustmentsRoute, listInventoryAdjustmentsHandler as RouteHandler<typeof listInventoryAdjustmentsRoute>)
.openapi(getInventoryAdjustmentByIdRoute, getInventoryAdjustmentByIdHandler as RouteHandler<typeof getInventoryAdjustmentByIdRoute>)
.openapi(createInventoryAdjustmentRoute, createInventoryAdjustmentHandler as RouteHandler<typeof createInventoryAdjustmentRoute>)
.openapi(listProductsRoute, listProductsHandler as RouteHandler<typeof listProductsRoute>)
.openapi(getProductByIdRoute, getProductByIdHandler as RouteHandler<typeof getProductByIdRoute>)
.openapi(createProductRoute, createProductHandler as RouteHandler<typeof createProductRoute>)
.openapi(updateProductRoute, updateProductHandler as RouteHandler<typeof updateProductRoute>)
.openapi(deleteProductRoute, deleteProductHandler as RouteHandler<typeof deleteProductRoute>)
.openapi(uploadRoute, uploadHandler as RouteHandler<typeof uploadRoute>)
.openapi(listUsersRoute, listUsersHandler as RouteHandler<typeof listUsersRoute>)
.openapi(getUserByIdRoute, getUserByIdHandler as RouteHandler<typeof getUserByIdRoute>)
.openapi(createUserRoute, createUserHandler as RouteHandler<typeof createUserRoute>)
.openapi(updateUserRoute, updateUserHandler as RouteHandler<typeof updateUserRoute>)
.openapi(deleteUserRoute, deleteUserHandler as RouteHandler<typeof deleteUserRoute>)
.openapi(listPriceHistoryRoute, listPriceHistoryHandler as unknown as RouteHandler<typeof listPriceHistoryRoute>)
.openapi(getPriceHistoryByIdRoute, getPriceHistoryByIdHandler as unknown as RouteHandler<typeof getPriceHistoryByIdRoute>)
.openapi(createPriceHistoryRoute, createPriceHistoryHandler as unknown as RouteHandler<typeof createPriceHistoryRoute>)
.openapi(listInventoryAdjustmentsRoute, listInventoryAdjustmentsHandler as unknown as RouteHandler<typeof listInventoryAdjustmentsRoute>)
.openapi(getInventoryAdjustmentByIdRoute, getInventoryAdjustmentByIdHandler as unknown as RouteHandler<typeof getInventoryAdjustmentByIdRoute>)
.openapi(createInventoryAdjustmentRoute, createInventoryAdjustmentHandler as unknown as RouteHandler<typeof createInventoryAdjustmentRoute>)
.openapi(listProductsRoute, listProductsHandler as unknown as RouteHandler<typeof listProductsRoute>)
.openapi(getProductByIdRoute, getProductByIdHandler as unknown as RouteHandler<typeof getProductByIdRoute>)
.openapi(createProductRoute, createProductHandler as unknown as RouteHandler<typeof createProductRoute>)
.openapi(updateProductRoute, updateProductHandler as unknown as RouteHandler<typeof updateProductRoute>)
.openapi(deleteProductRoute, deleteProductHandler as unknown as RouteHandler<typeof deleteProductRoute>)
.openapi(uploadRoute, uploadHandler as unknown as RouteHandler<typeof uploadRoute>)
.openapi(listUsersRoute, listUsersHandler as unknown as RouteHandler<typeof listUsersRoute>)
.openapi(getUserByIdRoute, getUserByIdHandler as unknown as RouteHandler<typeof getUserByIdRoute>)
.openapi(createUserRoute, createUserHandler as unknown as RouteHandler<typeof createUserRoute>)
.openapi(updateUserRoute, updateUserHandler as unknown as RouteHandler<typeof updateUserRoute>)
.openapi(deleteUserRoute, deleteUserHandler as unknown as RouteHandler<typeof deleteUserRoute>)
Comment on lines +208 to +228
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For hono v4.6.0 and @hono/zod-openapiv0.19.10, what is the recommended way to type handlers soapp.openapi(route, handler)works withoutas unknown as RouteHandler? Does annotating handlers as PromiseeraseTypedResponseinference fromc.json()/c.body()?

💡 Result:

1) Typing handlers so app.openapi(route, handler) accepts them (no cast)

For @hono/zod-openapi, the intended handler type is RouteHandler<R>, which is defined to return MaybePromise<RouteConfigToTypedResponse<R>> (and only allows plain Response in some cases). So the “recommended” way is to type-check your handler against RouteHandler<typeof route>. (github.com)

Best pattern (keeps inference, avoids widening):

import { createRoute, type RouteHandler } from '@hono/zod-openapi'

const route = createRoute({ /* ... */ })

export const handler =
  ((c) => c.json({ ok: true }, 200)) satisfies RouteHandler<typeof route>

// later
app.openapi(route, handler)

You can also annotate directly:

export const handler: RouteHandler<typeof route> = (c) => c.json({ ok: true }, 200)

But satisfies is usually nicer because it checks compatibility without forcing the function’s inferred return type to widen.

2) Does Promise<Response> annotation erase TypedResponse inference?

Yes—at the handler boundary.

In Hono’s types, TypedResponse<T, U, F> is the marker that carries response-body/status/format type info. (jsr.io) If you annotate your handler as Promise<Response> (or Response), TypeScript will accept returning c.json() (because it’s still a Response), but it will widen the function’s return type to Response, so app.openapi(...) can no longer “see” the TypedResponse information for that handler’s output typing.

So: don’t annotate OpenAPI handlers as Promise<Response>; use RouteHandler<typeof route> (or satisfies it) to preserve the TypedResponse-based inference. (github.com)

Citations:


🏁 Script executed:

# Find all handlers referenced in app.ts
rg -A 2 "healthHandler|healthLiveHandler|listPriceHistoryHandler|getPriceHistoryByIdHandler|createPriceHistoryHandler" apps/api/src --type ts | head -50

Repository: sam821203/SalesOps

Length of output: 3043


🏁 Script executed:

# Look for handler file definitions and their return types
fd "handler" apps/api/src --type ts | head -20

Repository: sam821203/SalesOps

Length of output: 231


🏁 Script executed:

# Check where handlers are imported from in app.ts
head -50 apps/api/src/app.ts | rg "import.*Handler"

Repository: sam821203/SalesOps

Length of output: 303


🏁 Script executed:

# Count all handlers annotated with Promise<Response> or Response
rg "export const \w+Handler.*:\s*(async\s+)?\(.*\)\s*:\s*(Promise<)?Response" apps/api/src --type-list | wc -l

Repository: sam821203/SalesOps

Length of output: 470


🏁 Script executed:

# Get a comprehensive list of all handlers and their annotations
rg "export const \w+Handler" apps/api/src -A 1 | grep -E "export const|Promise|Response" | head -40

Repository: sam821203/SalesOps

Length of output: 3390


Refactor all handlers to use RouteHandler<typeof route> instead of Promise<Response> annotations.

Every handler across the codebase is explicitly annotated as Promise<Response> or Response, which erases the TypedResponse inference that @hono/zod-openapi needs. This is why the casts as unknown as RouteHandler<...> are required in app.ts—they work around a type mismatch created by the controller annotations. The casts disable compile-time verification: mismatched input schemas or undocumented response shapes will still register cleanly.

Fix by removing Promise<Response> annotations from all handlers and using RouteHandler<typeof route> instead (or satisfies RouteHandler<typeof route>). This preserves TypedResponse inference from c.json() / c.body() and allows the route chain to stay type-checked end to end.

Affected files:

  • apps/api/src/modules/health/health.controller.ts (2 handlers)
  • apps/api/src/modules/ecommerce/sku-price-history.controller.ts (3 handlers)
  • apps/api/src/modules/ecommerce/inventory-adjustment.controller.ts (3 handlers)
  • apps/api/src/modules/ecommerce/product.controller.ts (5 handlers)
  • apps/api/src/modules/user/user.controller.ts (6 handlers)
  • apps/api/src/modules/upload/upload.controller.ts (1 handler)
  • apps/api/src/common/filters/error-handler.ts (1 handler)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app.ts` around lines 208 - 228, All route handlers are annotated
as Promise<Response> or Response which strips TypedResponse inference and forces
unsafe casts in app.ts; update each handler in the listed controller files
(health.controller.ts, sku-price-history.controller.ts,
inventory-adjustment.controller.ts, product.controller.ts, user.controller.ts,
upload.controller.ts, and error-handler.ts) to use RouteHandler<typeof <route>>
(or append "satisfies RouteHandler<typeof <route>>") instead of
Promise<Response>/Response so the OpenAPIHono .openapi(...) calls no longer
require "as unknown as RouteHandler<...>" casts—adjust the function signatures
(e.g., healthHandler, listPriceHistoryHandler, createProductHandler,
getUserByIdHandler, uploadHandler, errorHandler, etc.) to the RouteHandler form
to restore typed c.body()/c.json() response inference and remove the unsafe
casts in app.ts.

.doc('/openapi.json', (c) => {
const baseUrl = c.req.url.replace(/\/openapi\.json.*$/, '');
return {
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/common/filters/error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type ErrorLike = Error & { code?: string; statusCode?: number };
/**
* Central error handler. Masks internal details in production.
*/
export function errorHandler(err: ErrorLike, c: Context): Response {
export const errorHandler = (err: ErrorLike, c: Context): Response => {
// Log server-side (never expose stack to client in production)
console.error('[Error]', err.message, env.NODE_ENV === 'development' ? err.stack : '');

Expand All @@ -29,4 +29,4 @@ export function errorHandler(err: ErrorLike, c: Context): Response {
: err.message;

return c.json({ error: 'Internal Server Error', message }, 500);
}
};
4 changes: 2 additions & 2 deletions apps/api/src/common/guards/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { HTTPException } from 'hono/http-exception';
* Placeholder auth guard. Replace with JWT verification when implementing auth.
* Usage: app.use('/protected/*', authGuard);
*/
export async function authGuard(c: Context, next: Next): Promise<void> {
export const authGuard = async (c: Context, next: Next): Promise<void> => {
const authHeader = c.req.header('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new HTTPException(401, { message: 'Missing or invalid Authorization header' });
}
// TODO: verify JWT, set c.set('user', payload)
await next();
}
};
4 changes: 2 additions & 2 deletions apps/api/src/common/interceptors/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type { Context, Next } from 'hono';
/**
* Request logging interceptor. Log method, path, status, and duration.
*/
export async function requestLogger(c: Context, next: Next): Promise<void> {
export const requestLogger = async (c: Context, next: Next): Promise<void> => {
const start = Date.now();
await next();
const ms = Date.now() - start;
const status = c.res.status;
const method = c.req.method;
const path = c.req.path;
console.info(`[${method}] ${path} ${status} ${ms}ms`);
}
};
4 changes: 2 additions & 2 deletions apps/api/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ const envSchema = z.object({

export type Env = z.infer<typeof envSchema>;

function loadEnv(): Env {
const loadEnv = (): Env => {
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('Invalid environment variables:', parsed.error.flatten().fieldErrors);
throw new Error('Invalid environment variables');
}
return parsed.data;
}
};

export const env = loadEnv();
24 changes: 24 additions & 0 deletions apps/api/src/lib/cloudinary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Cloudinary delivery URL transforms for outbound image URLs.
* All Cloudinary image URLs must include format optimization (f_auto),
* quality adjustment (q_auto), and explicit dimensions with crop mode.
*/

const CLOUDINARY_UPLOAD_PREFIX = 'res.cloudinary.com/';
const UPLOAD_PATH = '/image/upload/';
const TRANSFORMS = 'f_auto,q_auto,w_800,h_600,c_fill';

/**
* Applies standard delivery transforms to a Cloudinary image URL.
* Inserts f_auto, q_auto, and explicit dimensions (w_800, h_600, c_fill).
* Non-Cloudinary URLs are returned unchanged.
*/
export const toOptimizedImageUrl = (url: string | null | undefined): string | undefined => {
if (url == null || url === '') return undefined;
if (!url.includes(CLOUDINARY_UPLOAD_PREFIX) || !url.includes(UPLOAD_PATH)) return url;
const insert = `${UPLOAD_PATH}${TRANSFORMS}/`;
const afterUpload = url.indexOf(UPLOAD_PATH) + UPLOAD_PATH.length;
const alreadyHasTransforms = url.slice(afterUpload, afterUpload + 20).includes('f_auto');
if (alreadyHasTransforms) return url;
return url.replace(UPLOAD_PATH, insert);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ type ListQuery = z.infer<typeof listInventoryAdjustmentsQuerySchema>;
type ParamId = z.infer<typeof inventoryAdjustmentIdParamSchema>;
type CreateBody = z.infer<typeof createInventoryAdjustmentSchema>;

export async function listInventoryAdjustmentsHandler(c: Context<object, string, { out: { query: ListQuery } }>) {
export const listInventoryAdjustmentsHandler = async (c: Context<object, string, { out: { query: ListQuery } }>): Promise<Response> => {
const { page, pageSize, q } = c.req.valid('query');
const list = await inventoryAdjustmentService.getList(page, pageSize, { q });
return c.json(list);
}
};

export async function getInventoryAdjustmentByIdHandler(c: Context<object, string, { out: { param: ParamId } }>) {
export const getInventoryAdjustmentByIdHandler = async (c: Context<object, string, { out: { param: ParamId } }>): Promise<Response> => {
const { id } = c.req.valid('param');
const item = await inventoryAdjustmentService.getById(id);
if (!item) {
Expand All @@ -37,9 +37,9 @@ export async function getInventoryAdjustmentByIdHandler(c: Context<object, strin
);
}
return c.json(item);
}
};

export async function createInventoryAdjustmentHandler(c: Context<object, string, { out: { json: CreateBody } }>) {
export const createInventoryAdjustmentHandler = async (c: Context<object, string, { out: { json: CreateBody } }>): Promise<Response> => {
try {
const body = c.req.valid('json');
const created = await inventoryAdjustmentService.create(body);
Expand All @@ -53,4 +53,4 @@ export async function createInventoryAdjustmentHandler(c: Context<object, string
}
throw e;
}
}
};
23 changes: 18 additions & 5 deletions apps/api/src/modules/ecommerce/inventory-adjustment.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { CreateInventoryAdjustmentInput } from './dto/ecommerce.dto.js';
import type { InventoryAdjustmentGetPayload } from '../../../generated/prisma/models/InventoryAdjustment.js';
import { prisma } from '../../lib/prisma.js';

function buildWhere(q?: string) {
/** Prisma payload for InventoryAdjustment with sku and product included (matches our queries). */
type InventoryAdjustmentWithSkuAndProduct = InventoryAdjustmentGetPayload<{
include: { sku: { include: { product: true } } };
}>;

type OrCondition =
| { sku: { product: { name: { contains: string } } } }
| { skuId: number };
type WhereClause = { OR: OrCondition[] };

const buildWhere = (q?: string): WhereClause | undefined => {
const qTrim = q?.trim();
if (!qTrim) return undefined;
const or: Array<
Expand All @@ -10,7 +21,7 @@ function buildWhere(q?: string) {
const id = /^\d+$/.test(qTrim) ? parseInt(qTrim, 10) : NaN;
if (!Number.isNaN(id)) or.push({ skuId: id });
return { OR: or };
}
};

/** Sentinel returned by create() when Decrease would make stock negative. */
export const INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK' as const;
Expand All @@ -23,7 +34,7 @@ export const inventoryAdjustmentRepository = {
page: number,
pageSize: number,
options: { q?: string }
) {
): Promise<{ items: InventoryAdjustmentWithSkuAndProduct[]; total: number }> {
const where = buildWhere(options.q);
const skip = (page - 1) * pageSize;
return prisma.$transaction(async (tx) => {
Expand All @@ -41,14 +52,16 @@ export const inventoryAdjustmentRepository = {
});
},

async findById(id: number) {
async findById(id: number): Promise<InventoryAdjustmentWithSkuAndProduct | null> {
return prisma.inventoryAdjustment.findUnique({
where: { id },
include: { sku: { include: { product: true } } },
});
},

async create(data: CreateInventoryAdjustmentInput) {
async create(
data: CreateInventoryAdjustmentInput
): Promise<InventoryAdjustmentWithSkuAndProduct | null | typeof INSUFFICIENT_STOCK> {
return prisma.$transaction(async (tx) => {
const sku = await tx.sku.findUnique({
where: { id: data.skuId },
Expand Down
12 changes: 6 additions & 6 deletions apps/api/src/modules/ecommerce/inventory-adjustment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ type PrismaRow = Awaited<
>;
type PrismaRowNonNull = NonNullable<PrismaRow>;

function toListItem(row: PrismaRowNonNull): InventoryAdjustmentListItem {
const isStringRecord = (v: unknown): v is Record<string, string> =>
typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((x) => typeof x === 'string');

const toListItem = (row: PrismaRowNonNull): InventoryAdjustmentListItem => {
const attrs = row.sku.attributes;
const skuAttributes =
typeof attrs === 'object' && attrs !== null && !Array.isArray(attrs)
? (attrs as Record<string, string>)
: {};
const skuAttributes = isStringRecord(attrs) ? attrs : {};
return {
id: row.id,
skuId: row.skuId,
Expand All @@ -46,7 +46,7 @@ function toListItem(row: PrismaRowNonNull): InventoryAdjustmentListItem {
productId: row.sku.productId,
skuAttributes,
};
}
};

/**
* Business logic only. No HTTP/framework types.
Expand Down
4 changes: 1 addition & 3 deletions apps/api/src/modules/ecommerce/mappers/enum.mapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ import {
refundStatusMap,
} from './enum.mapper.js';

function sorted(values: readonly string[]): string[] {
return [...values].sort();
}
const sorted = (values: readonly string[]): string[] => [...values].sort();

describe('ecommerce enum mappings', () => {
it('stays consistent with API ecommerce product statuses', () => {
Expand Down
47 changes: 18 additions & 29 deletions apps/api/src/modules/ecommerce/mappers/enum.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,42 +80,31 @@ export const refundStatusMap = {
Failed: 'Failed',
} as const satisfies Record<PrismaRefundStatus, RefundStatus>;

export function toSharedProductStatus(status: PrismaProductStatus): ProductStatus {
return productStatusMap[status];
}
export const toSharedProductStatus = (status: PrismaProductStatus): ProductStatus =>
productStatusMap[status];

export function toSharedDiscountType(type: PrismaDiscountType): DiscountType {
return discountTypeMap[type];
}
export const toSharedDiscountType = (type: PrismaDiscountType): DiscountType =>
discountTypeMap[type];

export function toSharedOrderStatus(status: PrismaOrderStatus): OrderStatus {
return orderStatusMap[status];
}
export const toSharedOrderStatus = (status: PrismaOrderStatus): OrderStatus =>
orderStatusMap[status];

export function toSharedPaymentStatus(status: PrismaPaymentStatus): PaymentStatus {
return paymentStatusMap[status];
}
export const toSharedPaymentStatus = (status: PrismaPaymentStatus): PaymentStatus =>
paymentStatusMap[status];

export function toSharedInventoryAdjustmentType(
export const toSharedInventoryAdjustmentType = (
type: PrismaInventoryAdjustmentType,
): InventoryAdjustmentType {
return inventoryAdjustmentTypeMap[type];
}
): InventoryAdjustmentType => inventoryAdjustmentTypeMap[type];

export function toSharedPaymentMethod(method: PrismaPaymentMethod): PaymentMethod {
return paymentMethodMap[method];
}
export const toSharedPaymentMethod = (method: PrismaPaymentMethod): PaymentMethod =>
paymentMethodMap[method];

export function toSharedPaymentTransactionStatus(
export const toSharedPaymentTransactionStatus = (
status: PrismaPaymentTransactionStatus,
): PaymentTransactionStatus {
return paymentTransactionStatusMap[status];
}
): PaymentTransactionStatus => paymentTransactionStatusMap[status];

export function toSharedPromotionStatus(status: PrismaPromotionStatus): PromotionStatusValue {
return promotionStatusMap[status];
}
export const toSharedPromotionStatus = (status: PrismaPromotionStatus): PromotionStatusValue =>
promotionStatusMap[status];

export function toSharedRefundStatus(status: PrismaRefundStatus): RefundStatus {
return refundStatusMap[status];
}
export const toSharedRefundStatus = (status: PrismaRefundStatus): RefundStatus =>
refundStatusMap[status];
Loading
Loading