Refactor API Response Handling and Update Service Implementations
- Removed the TransformInterceptor to streamline response handling, ensuring that all responses are returned directly without a success envelope. - Updated various controllers and services to utilize new action response schemas, enhancing clarity and consistency in API responses. - Refactored error handling in the CsrfController and CheckoutController to improve logging and error management. - Cleaned up unused imports and optimized code structure for better maintainability and clarity across the application.
This commit is contained in:
parent
fb682c4c44
commit
2a1b4d93ed
@ -4,7 +4,6 @@ import { RouterModule } from "@nestjs/core";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { ZodSerializerInterceptor, ZodValidationPipe } from "nestjs-zod";
|
||||
import { TransformInterceptor } from "@bff/core/http/transform.interceptor.js";
|
||||
|
||||
// Configuration
|
||||
import { appConfig } from "@bff/core/config/app.config.js";
|
||||
@ -106,10 +105,6 @@ import { HealthModule } from "@bff/modules/health/health.module.js";
|
||||
provide: APP_PIPE,
|
||||
useClass: ZodValidationPipe,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: TransformInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: ZodSerializerInterceptor,
|
||||
|
||||
@ -125,7 +125,55 @@ export async function bootstrap(): Promise<INestApplication> {
|
||||
|
||||
const port = Number(configService.get("BFF_PORT", 4000));
|
||||
|
||||
await app.listen(port, "0.0.0.0");
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "bootstrap.ts:before-listen",
|
||||
message: "About to call app.listen",
|
||||
data: { port },
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "D",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
|
||||
try {
|
||||
await app.listen(port, "0.0.0.0");
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "bootstrap.ts:after-listen",
|
||||
message: "app.listen completed",
|
||||
data: { port },
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "D",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
} catch (listenError) {
|
||||
const err = listenError as Error;
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "bootstrap.ts:listen-error",
|
||||
message: "app.listen failed",
|
||||
data: { error: err?.message, stack: err?.stack, name: err?.name },
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "D",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
throw listenError;
|
||||
}
|
||||
|
||||
// Enhanced startup information
|
||||
logger.log(`🚀 BFF API running on: http://localhost:${port}/api`);
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
import {
|
||||
Injectable,
|
||||
type NestInterceptor,
|
||||
type ExecutionContext,
|
||||
type CallHandler,
|
||||
} from "@nestjs/common";
|
||||
import { SetMetadata } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { Observable, map } from "rxjs";
|
||||
import type { ApiSuccessResponse } from "@customer-portal/domain/common";
|
||||
|
||||
export const SKIP_SUCCESS_ENVELOPE_KEY = "bff:skip-success-envelope";
|
||||
|
||||
/**
|
||||
* Opt-out decorator for endpoints that must not be wrapped in `{ success: true, data }`,
|
||||
* e.g. SSE streams or file downloads.
|
||||
*/
|
||||
export const SkipSuccessEnvelope = () => SetMetadata(SKIP_SUCCESS_ENVELOPE_KEY, true);
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object";
|
||||
}
|
||||
|
||||
function isLikelyStream(value: unknown): boolean {
|
||||
// Avoid wrapping Node streams (file downloads / SSE internals).
|
||||
return isRecord(value) && typeof (value as { pipe?: unknown }).pipe === "function";
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor<T> implements NestInterceptor<T, ApiSuccessResponse<T>> {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<ApiSuccessResponse<T>> {
|
||||
if (context.getType() !== "http") {
|
||||
// Only wrap HTTP responses.
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
const skip =
|
||||
this.reflector.getAllAndOverride<boolean>(SKIP_SUCCESS_ENVELOPE_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]) ?? false;
|
||||
|
||||
if (skip) {
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
const req = context.switchToHttp().getRequest<{ originalUrl?: string; url?: string }>();
|
||||
const url = req?.originalUrl ?? req?.url ?? "";
|
||||
|
||||
// Only enforce success envelopes on the public API surface under `/api`.
|
||||
// Keep non-API endpoints (e.g. `/health`) untouched for operational tooling.
|
||||
if (!url.startsWith("/api")) {
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
return next.handle().pipe(
|
||||
map(data => {
|
||||
// Keep already-wrapped responses as-is (ack/message/data variants).
|
||||
if (isRecord(data) && "success" in data) {
|
||||
return data as unknown as ApiSuccessResponse<T>;
|
||||
}
|
||||
|
||||
// Avoid wrapping streams/buffers that are handled specially by Nest/Express.
|
||||
if (isLikelyStream(data)) {
|
||||
return data as unknown as ApiSuccessResponse<T>;
|
||||
}
|
||||
|
||||
const normalized = (data === undefined ? null : data) as T;
|
||||
return { success: true as const, data: normalized };
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -22,35 +22,83 @@ export class CsrfController {
|
||||
@Public()
|
||||
@Get("token")
|
||||
getCsrfToken(@Req() req: AuthenticatedRequest, @Res() res: Response) {
|
||||
const sessionId = this.extractSessionId(req) || undefined;
|
||||
const userId = req.user?.id;
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "csrf.controller.ts:getCsrfToken-entry",
|
||||
message: "CSRF token endpoint called",
|
||||
data: {},
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "A",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
try {
|
||||
const sessionId = this.extractSessionId(req) || undefined;
|
||||
const userId = req.user?.id;
|
||||
|
||||
// Generate new CSRF token
|
||||
const tokenData = this.csrfService.generateToken(undefined, sessionId, userId);
|
||||
const isProduction = this.configService.get("NODE_ENV") === "production";
|
||||
const cookieName = this.csrfService.getCookieName();
|
||||
// Generate new CSRF token
|
||||
const tokenData = this.csrfService.generateToken(undefined, sessionId, userId);
|
||||
const isProduction = this.configService.get("NODE_ENV") === "production";
|
||||
const cookieName = this.csrfService.getCookieName();
|
||||
|
||||
// Set CSRF secret in secure cookie
|
||||
res.cookie(cookieName, tokenData.secret, {
|
||||
httpOnly: true,
|
||||
secure: isProduction,
|
||||
sameSite: "strict",
|
||||
maxAge: this.csrfService.getTokenTtl(),
|
||||
path: "/api",
|
||||
});
|
||||
// Set CSRF secret in secure cookie
|
||||
res.cookie(cookieName, tokenData.secret, {
|
||||
httpOnly: true,
|
||||
secure: isProduction,
|
||||
sameSite: "strict",
|
||||
maxAge: this.csrfService.getTokenTtl(),
|
||||
path: "/api",
|
||||
});
|
||||
|
||||
this.logger.debug("CSRF token requested", {
|
||||
userId,
|
||||
sessionId,
|
||||
userAgent: req.get("user-agent"),
|
||||
ip: req.ip,
|
||||
});
|
||||
this.logger.debug("CSRF token requested", {
|
||||
userId,
|
||||
sessionId,
|
||||
userAgent: req.get("user-agent"),
|
||||
ip: req.ip,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
token: tokenData.token,
|
||||
expiresAt: tokenData.expiresAt.toISOString(),
|
||||
});
|
||||
// #region agent log
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "csrf.controller.ts:getCsrfToken-success",
|
||||
message: "CSRF token generated successfully",
|
||||
data: { hasToken: !!tokenData.token },
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "A",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
token: tokenData.token,
|
||||
expiresAt: tokenData.expiresAt.toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
// #region agent log
|
||||
const err = error as Error;
|
||||
fetch("http://127.0.0.1:7242/ingest/a683e422-cfe7-4556-a583-809fbfbeeb4a", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: "csrf.controller.ts:getCsrfToken-error",
|
||||
message: "CSRF token generation failed",
|
||||
data: { error: err?.message, stack: err?.stack },
|
||||
timestamp: Date.now(),
|
||||
sessionId: "debug-session",
|
||||
hypothesisId: "A",
|
||||
}),
|
||||
}).catch(() => {});
|
||||
// #endregion
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Public()
|
||||
|
||||
@ -53,15 +53,22 @@ export class RealtimePubSubService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
async onModuleDestroy(): Promise<void> {
|
||||
if (!this.subscriber) return;
|
||||
// Capture the connection reference up-front and null out the field early.
|
||||
// This makes shutdown idempotent and avoids races if the hook is invoked more than once.
|
||||
const subscriber = this.subscriber;
|
||||
if (!subscriber) return;
|
||||
this.subscriber = null;
|
||||
this.handlers.clear();
|
||||
try {
|
||||
await this.subscriber.unsubscribe(this.CHANNEL);
|
||||
await this.subscriber.quit();
|
||||
await subscriber.unsubscribe(this.CHANNEL);
|
||||
await subscriber.quit();
|
||||
} catch {
|
||||
this.subscriber.disconnect();
|
||||
} finally {
|
||||
this.subscriber = null;
|
||||
this.handlers.clear();
|
||||
// Best-effort immediate close if graceful shutdown fails.
|
||||
try {
|
||||
subscriber.disconnect();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,17 @@ for (const signal of signals) {
|
||||
});
|
||||
}
|
||||
|
||||
process.on("uncaughtException", error => {
|
||||
logger.error(`Uncaught Exception: ${error.message}`, error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", reason => {
|
||||
const message = reason instanceof Error ? reason.message : String(reason);
|
||||
const stack = reason instanceof Error ? reason.stack : undefined;
|
||||
logger.error(`Unhandled Rejection: ${message}`, stack);
|
||||
});
|
||||
|
||||
void bootstrap()
|
||||
.then(startedApp => {
|
||||
app = startedApp;
|
||||
|
||||
@ -15,10 +15,7 @@ import {
|
||||
type NotificationListResponse,
|
||||
} from "@customer-portal/domain/notifications";
|
||||
import { notificationQuerySchema } from "@customer-portal/domain/notifications";
|
||||
import {
|
||||
apiSuccessAckResponseSchema,
|
||||
type ApiSuccessAckResponse,
|
||||
} from "@customer-portal/domain/common";
|
||||
import { actionAckResponseSchema, type ActionAckResponse } from "@customer-portal/domain/common";
|
||||
import { createZodDto, ZodResponse } from "nestjs-zod";
|
||||
|
||||
class NotificationQueryDto extends createZodDto(notificationQuerySchema) {}
|
||||
@ -27,7 +24,7 @@ class NotificationListResponseDto extends createZodDto(notificationListResponseS
|
||||
class NotificationUnreadCountResponseDto extends createZodDto(
|
||||
notificationUnreadCountResponseSchema
|
||||
) {}
|
||||
class ApiSuccessAckResponseDto extends createZodDto(apiSuccessAckResponseSchema) {}
|
||||
class ActionAckResponseDto extends createZodDto(actionAckResponseSchema) {}
|
||||
|
||||
@Controller("notifications")
|
||||
@UseGuards(RateLimitGuard)
|
||||
@ -69,13 +66,13 @@ export class NotificationsController {
|
||||
*/
|
||||
@Post(":id/read")
|
||||
@RateLimit({ limit: 60, ttl: 60 })
|
||||
@ZodResponse({ description: "Mark as read", type: ApiSuccessAckResponseDto })
|
||||
@ZodResponse({ description: "Mark as read", type: ActionAckResponseDto })
|
||||
async markAsRead(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param() params: NotificationIdParamDto
|
||||
): Promise<ApiSuccessAckResponse> {
|
||||
): Promise<ActionAckResponse> {
|
||||
await this.notificationService.markAsRead(params.id, req.user.id);
|
||||
return { success: true };
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,10 +80,10 @@ export class NotificationsController {
|
||||
*/
|
||||
@Post("read-all")
|
||||
@RateLimit({ limit: 10, ttl: 60 })
|
||||
@ZodResponse({ description: "Mark all as read", type: ApiSuccessAckResponseDto })
|
||||
async markAllAsRead(@Req() req: RequestWithUser): Promise<ApiSuccessAckResponse> {
|
||||
@ZodResponse({ description: "Mark all as read", type: ActionAckResponseDto })
|
||||
async markAllAsRead(@Req() req: RequestWithUser): Promise<ActionAckResponse> {
|
||||
await this.notificationService.markAllAsRead(req.user.id);
|
||||
return { success: true };
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,12 +91,12 @@ export class NotificationsController {
|
||||
*/
|
||||
@Post(":id/dismiss")
|
||||
@RateLimit({ limit: 60, ttl: 60 })
|
||||
@ZodResponse({ description: "Dismiss notification", type: ApiSuccessAckResponseDto })
|
||||
@ZodResponse({ description: "Dismiss notification", type: ActionAckResponseDto })
|
||||
async dismiss(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param() params: NotificationIdParamDto
|
||||
): Promise<ApiSuccessAckResponse> {
|
||||
): Promise<ActionAckResponse> {
|
||||
await this.notificationService.dismiss(params.id, req.user.id);
|
||||
return { success: true };
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,24 +79,33 @@ export class CheckoutController {
|
||||
orderType: body.orderType,
|
||||
});
|
||||
|
||||
const cart = await this.checkoutService.buildCart(
|
||||
body.orderType,
|
||||
body.selections,
|
||||
body.configuration,
|
||||
req.user?.id
|
||||
);
|
||||
try {
|
||||
const cart = await this.checkoutService.buildCart(
|
||||
body.orderType,
|
||||
body.selections,
|
||||
body.configuration,
|
||||
req.user?.id
|
||||
);
|
||||
|
||||
const session = await this.checkoutSessions.createSession(body, cart);
|
||||
const session = await this.checkoutSessions.createSession(body, cart);
|
||||
|
||||
return {
|
||||
sessionId: session.sessionId,
|
||||
expiresAt: session.expiresAt,
|
||||
orderType: body.orderType,
|
||||
cart: {
|
||||
items: cart.items,
|
||||
totals: cart.totals,
|
||||
},
|
||||
};
|
||||
return {
|
||||
sessionId: session.sessionId,
|
||||
expiresAt: session.expiresAt,
|
||||
orderType: body.orderType,
|
||||
cart: {
|
||||
items: cart.items,
|
||||
totals: cart.totals,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to create checkout session", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
userId: req.user?.id,
|
||||
orderType: body.orderType,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Get("session/:sessionId")
|
||||
|
||||
@ -31,7 +31,6 @@ import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards
|
||||
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard.js";
|
||||
import { CheckoutService } from "./services/checkout.service.js";
|
||||
import { CheckoutSessionService } from "./services/checkout-session.service.js";
|
||||
import { SkipSuccessEnvelope } from "@bff/core/http/transform.interceptor.js";
|
||||
|
||||
class CreateOrderRequestDto extends createZodDto(createOrderRequestSchema) {}
|
||||
class CheckoutSessionCreateOrderDto extends createZodDto(checkoutSessionCreateOrderRequestSchema) {}
|
||||
@ -156,7 +155,6 @@ export class OrdersController {
|
||||
|
||||
@Sse(":sfOrderId/events")
|
||||
@UseGuards(SalesforceReadThrottleGuard)
|
||||
@SkipSuccessEnvelope()
|
||||
async streamOrderUpdates(
|
||||
@Request() req: RequestWithUser,
|
||||
@Param() params: SfOrderIdParamDto
|
||||
|
||||
@ -35,6 +35,29 @@ export class CheckoutService {
|
||||
private readonly vpnCatalogService: VpnServicesService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Produce a safe-to-log summary of user selections.
|
||||
* Avoid logging PII/sensitive identifiers (EID, MNP reservation numbers, names, etc.).
|
||||
*/
|
||||
private summarizeSelectionsForLog(selections: OrderSelections): Record<string, unknown> {
|
||||
const addons = this.collectAddonRefs(selections);
|
||||
const normalizeBool = (value?: string) =>
|
||||
value === "true" ? true : value === "false" ? false : undefined;
|
||||
|
||||
return {
|
||||
planSku: selections.planSku,
|
||||
installationSku: selections.installationSku,
|
||||
addonSku: selections.addonSku,
|
||||
addonsCount: addons.length,
|
||||
activationType: selections.activationType,
|
||||
simType: selections.simType,
|
||||
isMnp: normalizeBool(selections.isMnp),
|
||||
hasEid: typeof selections.eid === "string" && selections.eid.trim().length > 0,
|
||||
hasMnpNumber:
|
||||
typeof selections.mnpNumber === "string" && selections.mnpNumber.trim().length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build checkout cart from order type and selections
|
||||
*/
|
||||
@ -44,7 +67,11 @@ export class CheckoutService {
|
||||
configuration?: OrderConfigurations,
|
||||
userId?: string
|
||||
): Promise<CheckoutCart> {
|
||||
this.logger.log("Building checkout cart", { orderType, selections });
|
||||
this.logger.log("Building checkout cart", {
|
||||
userId,
|
||||
orderType,
|
||||
selections: this.summarizeSelectionsForLog(selections),
|
||||
});
|
||||
|
||||
try {
|
||||
const items: CheckoutItem[] = [];
|
||||
@ -93,8 +120,9 @@ export class CheckoutService {
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to build checkout cart", {
|
||||
error: getErrorMessage(error),
|
||||
userId,
|
||||
orderType,
|
||||
selections,
|
||||
selections: this.summarizeSelectionsForLog(selections),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import { RateLimit, RateLimitGuard } from "@bff/core/rate-limiting/index.js";
|
||||
import { RealtimeConnectionLimiterService } from "./realtime-connection-limiter.service.js";
|
||||
import { SkipSuccessEnvelope } from "@bff/core/http/transform.interceptor.js";
|
||||
|
||||
@Controller("events")
|
||||
export class RealtimeController {
|
||||
@ -40,7 +39,6 @@ export class RealtimeController {
|
||||
@RateLimit({ limit: 30, ttl: 60 }) // protect against reconnect storms / refresh spam
|
||||
@Header("Cache-Control", "private, no-store")
|
||||
@Header("X-Accel-Buffering", "no") // nginx: disable response buffering for SSE
|
||||
@SkipSuccessEnvelope()
|
||||
async stream(@Request() req: RequestWithUser): Promise<Observable<MessageEvent>> {
|
||||
if (!this.limiter.tryAcquire(req.user.id)) {
|
||||
throw new HttpException(
|
||||
|
||||
@ -57,7 +57,6 @@ export class InternetController {
|
||||
): Promise<SubscriptionActionResponse> {
|
||||
await this.internetCancellationService.submitCancellation(req.user.id, params.id, body);
|
||||
return {
|
||||
success: true,
|
||||
message: `Internet cancellation scheduled for end of ${body.cancellationMonth}`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ export class SimController {
|
||||
@Body() body: SimTopupRequestDto
|
||||
): Promise<SimActionResponse> {
|
||||
await this.simManagementService.topUpSim(req.user.id, params.id, body);
|
||||
return { success: true, message: "SIM top-up completed successfully" };
|
||||
return { message: "SIM top-up completed successfully" };
|
||||
}
|
||||
|
||||
@Post(":id/sim/change-plan")
|
||||
@ -170,7 +170,6 @@ export class SimController {
|
||||
): Promise<SimPlanChangeResult> {
|
||||
const result = await this.simManagementService.changeSimPlan(req.user.id, params.id, body);
|
||||
return {
|
||||
success: true,
|
||||
message: "SIM plan change completed successfully",
|
||||
...result,
|
||||
};
|
||||
@ -184,7 +183,7 @@ export class SimController {
|
||||
@Body() body: SimCancelRequestDto
|
||||
): Promise<SimActionResponse> {
|
||||
await this.simManagementService.cancelSim(req.user.id, params.id, body);
|
||||
return { success: true, message: "SIM cancellation completed successfully" };
|
||||
return { message: "SIM cancellation completed successfully" };
|
||||
}
|
||||
|
||||
@Post(":id/sim/reissue-esim")
|
||||
@ -196,7 +195,7 @@ export class SimController {
|
||||
): Promise<SimActionResponse> {
|
||||
const parsedBody = simReissueEsimRequestSchema.parse(body as unknown);
|
||||
await this.simManagementService.reissueEsimProfile(req.user.id, params.id, parsedBody.newEid);
|
||||
return { success: true, message: "eSIM profile reissue completed successfully" };
|
||||
return { message: "eSIM profile reissue completed successfully" };
|
||||
}
|
||||
|
||||
@Post(":id/sim/features")
|
||||
@ -207,7 +206,7 @@ export class SimController {
|
||||
@Body() body: SimFeaturesRequestDto
|
||||
): Promise<SimActionResponse> {
|
||||
await this.simManagementService.updateSimFeatures(req.user.id, params.id, body);
|
||||
return { success: true, message: "SIM features updated successfully" };
|
||||
return { message: "SIM features updated successfully" };
|
||||
}
|
||||
|
||||
// ==================== Enhanced SIM Management Endpoints ====================
|
||||
@ -232,7 +231,6 @@ export class SimController {
|
||||
): Promise<SimPlanChangeResult> {
|
||||
const result = await this.simPlanService.changeSimPlanFull(req.user.id, params.id, body);
|
||||
return {
|
||||
success: true,
|
||||
message: `SIM plan change scheduled for ${result.scheduledAt}`,
|
||||
...result,
|
||||
};
|
||||
@ -264,7 +262,6 @@ export class SimController {
|
||||
): Promise<SimActionResponse> {
|
||||
await this.simCancellationService.cancelSimFull(req.user.id, params.id, body);
|
||||
return {
|
||||
success: true,
|
||||
message: `SIM cancellation scheduled for end of ${body.cancellationMonth}`,
|
||||
};
|
||||
}
|
||||
@ -279,10 +276,9 @@ export class SimController {
|
||||
await this.esimManagementService.reissueSim(req.user.id, params.id, body);
|
||||
|
||||
if (body.simType === "esim") {
|
||||
return { success: true, message: "eSIM profile reissue request submitted" };
|
||||
return { message: "eSIM profile reissue request submitted" };
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
message: "Physical SIM reissue request submitted. You will be contacted shortly.",
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,8 +27,8 @@ import {
|
||||
} from "@customer-portal/domain/support";
|
||||
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
|
||||
import { hashEmailForLogs } from "./support.logging.js";
|
||||
import type { ApiSuccessMessageResponse } from "@customer-portal/domain/common";
|
||||
import { apiSuccessMessageResponseSchema } from "@customer-portal/domain/common";
|
||||
import type { ActionMessageResponse } from "@customer-portal/domain/common";
|
||||
import { actionMessageResponseSchema } from "@customer-portal/domain/common";
|
||||
|
||||
class SupportCaseFilterDto extends createZodDto(supportCaseFilterSchema) {}
|
||||
class CreateCaseRequestDto extends createZodDto(createCaseRequestSchema) {}
|
||||
@ -36,7 +36,7 @@ class PublicContactRequestDto extends createZodDto(publicContactRequestSchema) {
|
||||
class SupportCaseListDto extends createZodDto(supportCaseListSchema) {}
|
||||
class SupportCaseDto extends createZodDto(supportCaseSchema) {}
|
||||
class CreateCaseResponseDto extends createZodDto(createCaseResponseSchema) {}
|
||||
class ApiSuccessMessageResponseDto extends createZodDto(apiSuccessMessageResponseSchema) {}
|
||||
class ActionMessageResponseDto extends createZodDto(actionMessageResponseSchema) {}
|
||||
|
||||
@Controller("support")
|
||||
export class SupportController {
|
||||
@ -84,16 +84,15 @@ export class SupportController {
|
||||
@RateLimit({ limit: 5, ttl: 300 }) // 5 requests per 5 minutes
|
||||
@ZodResponse({
|
||||
description: "Public contact form submission",
|
||||
type: ApiSuccessMessageResponseDto,
|
||||
type: ActionMessageResponseDto,
|
||||
})
|
||||
async publicContact(@Body() body: PublicContactRequestDto): Promise<ApiSuccessMessageResponse> {
|
||||
async publicContact(@Body() body: PublicContactRequestDto): Promise<ActionMessageResponse> {
|
||||
this.logger.log("Public contact form submission", { emailHash: hashEmailForLogs(body.email) });
|
||||
|
||||
try {
|
||||
await this.supportService.createPublicContactRequest(body);
|
||||
|
||||
return {
|
||||
success: true as const,
|
||||
message: "Your message has been received. We will get back to you within 24 hours.",
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
2
apps/portal/next-env.d.ts
vendored
2
apps/portal/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@ -20,26 +20,26 @@
|
||||
"dependencies": {
|
||||
"@customer-portal/domain": "workspace:*",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-query": "^5.90.14",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.0.10",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"world-countries": "^5.1.0",
|
||||
"zod": "catalog:",
|
||||
"zod": "^4.2.1",
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^16.0.9",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@tanstack/react-query-devtools": "^5.91.1",
|
||||
"@next/bundle-analyzer": "^16.1.1",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import type {
|
||||
OrderSelections,
|
||||
OrderTypeValue,
|
||||
} from "@customer-portal/domain/orders";
|
||||
import type { ApiSuccessResponse } from "@customer-portal/domain/common";
|
||||
|
||||
type CheckoutCartSummary = { items: CheckoutCart["items"]; totals: CheckoutCart["totals"] };
|
||||
|
||||
@ -25,7 +24,7 @@ export const checkoutService = {
|
||||
selections: OrderSelections,
|
||||
configuration?: OrderConfigurations
|
||||
): Promise<CheckoutCart> {
|
||||
const response = await apiClient.POST<ApiSuccessResponse<CheckoutCart>>("/api/checkout/cart", {
|
||||
const response = await apiClient.POST<CheckoutCart>("/api/checkout/cart", {
|
||||
body: {
|
||||
orderType,
|
||||
selections,
|
||||
@ -33,11 +32,7 @@ export const checkoutService = {
|
||||
},
|
||||
});
|
||||
|
||||
const wrappedResponse = getDataOrThrow(response, "Failed to build checkout cart");
|
||||
if (!wrappedResponse.success) {
|
||||
throw new Error("Failed to build checkout cart");
|
||||
}
|
||||
return wrappedResponse.data;
|
||||
return getDataOrThrow(response, "Failed to build checkout cart");
|
||||
},
|
||||
|
||||
async createSession(
|
||||
@ -45,33 +40,20 @@ export const checkoutService = {
|
||||
selections: OrderSelections,
|
||||
configuration?: OrderConfigurations
|
||||
): Promise<CheckoutSessionResponse> {
|
||||
const response = await apiClient.POST<ApiSuccessResponse<CheckoutSessionResponse>>(
|
||||
"/api/checkout/session",
|
||||
{
|
||||
body: { orderType, selections, configuration },
|
||||
}
|
||||
);
|
||||
|
||||
const wrappedResponse = getDataOrThrow(response, "Failed to create checkout session");
|
||||
if (!wrappedResponse.success) {
|
||||
throw new Error("Failed to create checkout session");
|
||||
}
|
||||
return wrappedResponse.data;
|
||||
const response = await apiClient.POST<CheckoutSessionResponse>("/api/checkout/session", {
|
||||
body: { orderType, selections, configuration },
|
||||
});
|
||||
return getDataOrThrow(response, "Failed to create checkout session");
|
||||
},
|
||||
|
||||
async getSession(sessionId: string): Promise<CheckoutSessionResponse> {
|
||||
const response = await apiClient.GET<ApiSuccessResponse<CheckoutSessionResponse>>(
|
||||
const response = await apiClient.GET<CheckoutSessionResponse>(
|
||||
"/api/checkout/session/{sessionId}",
|
||||
{
|
||||
params: { path: { sessionId } },
|
||||
}
|
||||
);
|
||||
|
||||
const wrappedResponse = getDataOrThrow(response, "Failed to load checkout session");
|
||||
if (!wrappedResponse.success) {
|
||||
throw new Error("Failed to load checkout session");
|
||||
}
|
||||
return wrappedResponse.data;
|
||||
return getDataOrThrow(response, "Failed to load checkout session");
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { apiClient } from "@/lib/api";
|
||||
import { apiClient, getDataOrThrow } from "@/lib/api";
|
||||
import { log } from "@/lib/logger";
|
||||
import {
|
||||
orderDetailsSchema,
|
||||
@ -7,7 +7,12 @@ import {
|
||||
type OrderDetails,
|
||||
type OrderSummary,
|
||||
} from "@customer-portal/domain/orders";
|
||||
import { assertSuccess, type DomainApiResponse } from "@/lib/api/response-helpers";
|
||||
|
||||
interface CreateOrderResponse {
|
||||
sfOrderId: string;
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
async function createOrder(payload: CreateOrderRequest): Promise<{ sfOrderId: string }> {
|
||||
const body: CreateOrderRequest = {
|
||||
@ -23,12 +28,9 @@ async function createOrder(payload: CreateOrderRequest): Promise<{ sfOrderId: st
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await apiClient.POST("/api/orders", { body });
|
||||
|
||||
const parsed = assertSuccess<{ sfOrderId: string; status: string; message: string }>(
|
||||
response.data as DomainApiResponse<{ sfOrderId: string; status: string; message: string }>
|
||||
);
|
||||
return { sfOrderId: parsed.data.sfOrderId };
|
||||
const response = await apiClient.POST<CreateOrderResponse>("/api/orders", { body });
|
||||
const data = getDataOrThrow(response, "Failed to create order");
|
||||
return { sfOrderId: data.sfOrderId };
|
||||
} catch (error) {
|
||||
log.error("Order creation failed", error instanceof Error ? error : undefined, {
|
||||
orderType: body.orderType,
|
||||
@ -41,14 +43,11 @@ async function createOrder(payload: CreateOrderRequest): Promise<{ sfOrderId: st
|
||||
async function createOrderFromCheckoutSession(
|
||||
checkoutSessionId: string
|
||||
): Promise<{ sfOrderId: string }> {
|
||||
const response = await apiClient.POST("/api/orders/from-checkout-session", {
|
||||
const response = await apiClient.POST<CreateOrderResponse>("/api/orders/from-checkout-session", {
|
||||
body: { checkoutSessionId },
|
||||
});
|
||||
|
||||
const parsed = assertSuccess<{ sfOrderId: string; status: string; message: string }>(
|
||||
response.data as DomainApiResponse<{ sfOrderId: string; status: string; message: string }>
|
||||
);
|
||||
return { sfOrderId: parsed.data.sfOrderId };
|
||||
const data = getDataOrThrow(response, "Failed to create order");
|
||||
return { sfOrderId: data.sfOrderId };
|
||||
}
|
||||
|
||||
async function getMyOrders(): Promise<OrderSummary[]> {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { apiClient, getDataOrDefault, getDataOrThrow } from "@/lib/api";
|
||||
import { apiClient, getDataOrThrow, getDataOrDefault } from "@/lib/api";
|
||||
import {
|
||||
EMPTY_SIM_CATALOG,
|
||||
EMPTY_VPN_CATALOG,
|
||||
@ -29,11 +29,7 @@ export const servicesService = {
|
||||
const response = await apiClient.GET<InternetCatalogCollection>(
|
||||
"/api/public/services/internet/plans"
|
||||
);
|
||||
const data = getDataOrThrow<InternetCatalogCollection>(
|
||||
response,
|
||||
"Failed to load internet services"
|
||||
);
|
||||
return data; // BFF already validated
|
||||
return getDataOrThrow(response, "Failed to load internet services");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -45,14 +41,12 @@ export const servicesService = {
|
||||
|
||||
async getPublicSimCatalog(): Promise<SimCatalogCollection> {
|
||||
const response = await apiClient.GET<SimCatalogCollection>("/api/public/services/sim/plans");
|
||||
const data = getDataOrDefault<SimCatalogCollection>(response, EMPTY_SIM_CATALOG);
|
||||
return data; // BFF already validated
|
||||
return getDataOrDefault(response, EMPTY_SIM_CATALOG);
|
||||
},
|
||||
|
||||
async getPublicVpnCatalog(): Promise<VpnCatalogCollection> {
|
||||
const response = await apiClient.GET<VpnCatalogCollection>("/api/public/services/vpn/plans");
|
||||
const data = getDataOrDefault<VpnCatalogCollection>(response, EMPTY_VPN_CATALOG);
|
||||
return data; // BFF already validated
|
||||
return getDataOrDefault(response, EMPTY_VPN_CATALOG);
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
@ -63,30 +57,24 @@ export const servicesService = {
|
||||
const response = await apiClient.GET<InternetCatalogCollection>(
|
||||
"/api/account/services/internet/plans"
|
||||
);
|
||||
const data = getDataOrThrow<InternetCatalogCollection>(
|
||||
response,
|
||||
"Failed to load internet services"
|
||||
);
|
||||
return data; // BFF already validated
|
||||
return getDataOrThrow(response, "Failed to load internet services");
|
||||
},
|
||||
|
||||
async getAccountSimCatalog(): Promise<SimCatalogCollection> {
|
||||
const response = await apiClient.GET<SimCatalogCollection>("/api/account/services/sim/plans");
|
||||
const data = getDataOrDefault<SimCatalogCollection>(response, EMPTY_SIM_CATALOG);
|
||||
return data; // BFF already validated
|
||||
return getDataOrDefault(response, EMPTY_SIM_CATALOG);
|
||||
},
|
||||
|
||||
async getAccountVpnCatalog(): Promise<VpnCatalogCollection> {
|
||||
const response = await apiClient.GET<VpnCatalogCollection>("/api/account/services/vpn/plans");
|
||||
const data = getDataOrDefault<VpnCatalogCollection>(response, EMPTY_VPN_CATALOG);
|
||||
return data; // BFF already validated
|
||||
return getDataOrDefault(response, EMPTY_VPN_CATALOG);
|
||||
},
|
||||
|
||||
async getInternetInstallations(): Promise<InternetInstallationCatalogItem[]> {
|
||||
const response = await apiClient.GET<InternetInstallationCatalogItem[]>(
|
||||
"/api/services/internet/installations"
|
||||
);
|
||||
const data = getDataOrDefault<InternetInstallationCatalogItem[]>(response, []);
|
||||
const data = getDataOrDefault(response, []);
|
||||
return internetInstallationCatalogItemSchema.array().parse(data);
|
||||
},
|
||||
|
||||
@ -94,7 +82,7 @@ export const servicesService = {
|
||||
const response = await apiClient.GET<InternetAddonCatalogItem[]>(
|
||||
"/api/services/internet/addons"
|
||||
);
|
||||
const data = getDataOrDefault<InternetAddonCatalogItem[]>(response, []);
|
||||
const data = getDataOrDefault(response, []);
|
||||
return internetAddonCatalogItemSchema.array().parse(data);
|
||||
},
|
||||
|
||||
@ -109,13 +97,13 @@ export const servicesService = {
|
||||
const response = await apiClient.GET<SimActivationFeeCatalogItem[]>(
|
||||
"/api/services/sim/activation-fees"
|
||||
);
|
||||
const data = getDataOrDefault<SimActivationFeeCatalogItem[]>(response, []);
|
||||
const data = getDataOrDefault(response, []);
|
||||
return simActivationFeeCatalogItemSchema.array().parse(data);
|
||||
},
|
||||
|
||||
async getSimAddons(): Promise<SimCatalogProduct[]> {
|
||||
const response = await apiClient.GET<SimCatalogProduct[]>("/api/services/sim/addons");
|
||||
const data = getDataOrDefault<SimCatalogProduct[]>(response, []);
|
||||
const data = getDataOrDefault(response, []);
|
||||
return simCatalogProductSchema.array().parse(data);
|
||||
},
|
||||
|
||||
@ -128,7 +116,7 @@ export const servicesService = {
|
||||
|
||||
async getVpnActivationFees(): Promise<VpnCatalogProduct[]> {
|
||||
const response = await apiClient.GET<VpnCatalogProduct[]>("/api/services/vpn/activation-fees");
|
||||
const data = getDataOrDefault<VpnCatalogProduct[]>(response, []);
|
||||
const data = getDataOrDefault(response, []);
|
||||
return vpnCatalogProductSchema.array().parse(data);
|
||||
},
|
||||
|
||||
|
||||
@ -3,14 +3,14 @@ import type {
|
||||
InternetCancelRequest,
|
||||
InternetCancellationPreview,
|
||||
} from "@customer-portal/domain/subscriptions";
|
||||
import type { ApiSuccessResponse } from "@customer-portal/domain/common";
|
||||
import { internetCancellationPreviewSchema } from "@customer-portal/domain/subscriptions";
|
||||
|
||||
export const internetActionsService = {
|
||||
/**
|
||||
* Get cancellation preview (available months, service details)
|
||||
*/
|
||||
async getCancellationPreview(subscriptionId: string): Promise<InternetCancellationPreview> {
|
||||
const response = await apiClient.GET<ApiSuccessResponse<InternetCancellationPreview>>(
|
||||
const response = await apiClient.GET<InternetCancellationPreview>(
|
||||
"/api/subscriptions/{subscriptionId}/internet/cancellation-preview",
|
||||
{
|
||||
params: { path: { subscriptionId } },
|
||||
@ -18,11 +18,7 @@ export const internetActionsService = {
|
||||
);
|
||||
|
||||
const payload = getDataOrThrow(response, "Failed to load cancellation information");
|
||||
if (!payload.data) {
|
||||
throw new Error("Failed to load cancellation information");
|
||||
}
|
||||
|
||||
return payload.data;
|
||||
return internetCancellationPreviewSchema.parse(payload);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { ApiSuccessResponse } from "@customer-portal/domain/common";
|
||||
import { apiClient, getDataOrDefault } from "@/lib/api";
|
||||
import {
|
||||
simInfoSchema,
|
||||
simAvailablePlanArraySchema,
|
||||
simCancellationPreviewSchema,
|
||||
simDomesticCallHistoryResponseSchema,
|
||||
simInternationalCallHistoryResponseSchema,
|
||||
simSmsHistoryResponseSchema,
|
||||
simHistoryAvailableMonthsSchema,
|
||||
type SimAvailablePlan,
|
||||
type SimInfo,
|
||||
type SimCancelFullRequest,
|
||||
@ -81,23 +86,23 @@ export const simActionsService = {
|
||||
},
|
||||
|
||||
async getAvailablePlans(subscriptionId: string): Promise<SimAvailablePlan[]> {
|
||||
const response = await apiClient.GET<ApiSuccessResponse<SimAvailablePlan[]>>(
|
||||
const response = await apiClient.GET<SimAvailablePlan[]>(
|
||||
"/api/subscriptions/{subscriptionId}/sim/available-plans",
|
||||
{
|
||||
params: { path: { subscriptionId } },
|
||||
}
|
||||
{ params: { path: { subscriptionId } } }
|
||||
);
|
||||
return response.data?.data ?? [];
|
||||
const data = getDataOrDefault(response, []);
|
||||
return simAvailablePlanArraySchema.parse(data);
|
||||
},
|
||||
|
||||
async getCancellationPreview(subscriptionId: string): Promise<SimCancellationPreview | null> {
|
||||
const response = await apiClient.GET<ApiSuccessResponse<SimCancellationPreview>>(
|
||||
const response = await apiClient.GET<SimCancellationPreview>(
|
||||
"/api/subscriptions/{subscriptionId}/sim/cancellation-preview",
|
||||
{
|
||||
params: { path: { subscriptionId } },
|
||||
}
|
||||
);
|
||||
return response.data?.data ?? null;
|
||||
const data = getDataOrDefault(response, null);
|
||||
return data ? simCancellationPreviewSchema.parse(data) : null;
|
||||
},
|
||||
|
||||
async reissueSim(subscriptionId: string, request: SimReissueFullRequest): Promise<void> {
|
||||
@ -120,13 +125,14 @@ export const simActionsService = {
|
||||
params.page = String(page);
|
||||
params.limit = String(limit);
|
||||
|
||||
const response = await apiClient.GET<ApiSuccessResponse<SimDomesticCallHistoryResponse>>(
|
||||
const response = await apiClient.GET<SimDomesticCallHistoryResponse>(
|
||||
"/api/subscriptions/{subscriptionId}/sim/call-history/domestic",
|
||||
{
|
||||
params: { path: { subscriptionId }, query: params },
|
||||
}
|
||||
);
|
||||
return response.data?.data ?? null;
|
||||
const data = getDataOrDefault(response, null);
|
||||
return data ? simDomesticCallHistoryResponseSchema.parse(data) : null;
|
||||
},
|
||||
|
||||
async getInternationalCallHistory(
|
||||
@ -140,13 +146,14 @@ export const simActionsService = {
|
||||
params.page = String(page);
|
||||
params.limit = String(limit);
|
||||
|
||||
const response = await apiClient.GET<ApiSuccessResponse<SimInternationalCallHistoryResponse>>(
|
||||
const response = await apiClient.GET<SimInternationalCallHistoryResponse>(
|
||||
"/api/subscriptions/{subscriptionId}/sim/call-history/international",
|
||||
{
|
||||
params: { path: { subscriptionId }, query: params },
|
||||
}
|
||||
);
|
||||
return response.data?.data ?? null;
|
||||
const data = getDataOrDefault(response, null);
|
||||
return data ? simInternationalCallHistoryResponseSchema.parse(data) : null;
|
||||
},
|
||||
|
||||
async getSmsHistory(
|
||||
@ -160,20 +167,21 @@ export const simActionsService = {
|
||||
params.page = String(page);
|
||||
params.limit = String(limit);
|
||||
|
||||
const response = await apiClient.GET<ApiSuccessResponse<SimSmsHistoryResponse>>(
|
||||
const response = await apiClient.GET<SimSmsHistoryResponse>(
|
||||
"/api/subscriptions/{subscriptionId}/sim/sms-history",
|
||||
{
|
||||
params: { path: { subscriptionId }, query: params },
|
||||
}
|
||||
);
|
||||
return response.data?.data ?? null;
|
||||
const data = getDataOrDefault(response, null);
|
||||
return data ? simSmsHistoryResponseSchema.parse(data) : null;
|
||||
},
|
||||
|
||||
async getAvailableHistoryMonths(): Promise<string[]> {
|
||||
const response = await apiClient.GET<ApiSuccessResponse<string[]>>(
|
||||
"/api/subscriptions/sim/call-history/available-months",
|
||||
{}
|
||||
const response = await apiClient.GET<string[]>(
|
||||
"/api/subscriptions/sim/call-history/available-months"
|
||||
);
|
||||
return response.data?.data ?? [];
|
||||
const data = getDataOrDefault(response, []);
|
||||
return simHistoryAvailableMonthsSchema.parse(data);
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import {
|
||||
apiErrorResponseSchema,
|
||||
type ApiErrorResponse,
|
||||
type ApiSuccessResponse,
|
||||
} from "@customer-portal/domain/common";
|
||||
import type { ZodTypeAny, infer as ZodInfer } from "zod";
|
||||
import { apiErrorResponseSchema, type ApiErrorResponse } from "@customer-portal/domain/common";
|
||||
|
||||
/**
|
||||
* API Response Helper Types and Functions
|
||||
*
|
||||
* Generic utilities for working with API responses
|
||||
* Generic utilities for working with API responses.
|
||||
*
|
||||
* Note: Success responses from the BFF return data directly (no `{ success: true, data }` envelope).
|
||||
* Error responses are returned as `{ success: false, error: { code, message, details? } }` and
|
||||
* are surfaced via the API client error handler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic API response wrapper
|
||||
* Generic API response wrapper from the client.
|
||||
* After client unwrapping, `data` contains the actual payload (not the BFF envelope).
|
||||
*/
|
||||
export type ApiResponse<T> = {
|
||||
data?: T;
|
||||
@ -20,8 +20,8 @@ export type ApiResponse<T> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract data from API response or return null
|
||||
* Useful for optional data handling
|
||||
* Extract data from API response or return null.
|
||||
* Useful for optional data handling.
|
||||
*/
|
||||
export function getNullableData<T>(response: ApiResponse<T>): T | null {
|
||||
if (response.error || response.data === undefined) {
|
||||
@ -31,7 +31,7 @@ export function getNullableData<T>(response: ApiResponse<T>): T | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract data from API response or throw error
|
||||
* Extract data from API response or throw error.
|
||||
*/
|
||||
export function getDataOrThrow<T>(response: ApiResponse<T>, errorMessage?: string): T {
|
||||
if (response.error || response.data === undefined) {
|
||||
@ -41,54 +41,30 @@ export function getDataOrThrow<T>(response: ApiResponse<T>, errorMessage?: strin
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract data from API response or return default value
|
||||
* Extract data from API response or return default value.
|
||||
*/
|
||||
export function getDataOrDefault<T>(response: ApiResponse<T>, defaultValue: T): T {
|
||||
return response.data ?? defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response has an error
|
||||
* Check if response has an error.
|
||||
*/
|
||||
export function hasError<T>(response: ApiResponse<T>): boolean {
|
||||
return !!response.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if response has data
|
||||
* Check if response has data.
|
||||
*/
|
||||
export function hasData<T>(response: ApiResponse<T>): boolean {
|
||||
return response.data !== undefined && !response.error;
|
||||
}
|
||||
|
||||
export type DomainApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
||||
|
||||
export function assertSuccess<T>(response: DomainApiResponse<T>): ApiSuccessResponse<T> {
|
||||
if (response.success === true) {
|
||||
return response;
|
||||
}
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
|
||||
export function parseDomainResponse<T>(
|
||||
response: DomainApiResponse<T>,
|
||||
parser?: (payload: T) => T
|
||||
): T {
|
||||
const success = assertSuccess(response);
|
||||
return parser ? parser(success.data) : success.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an error payload into a structured API error response.
|
||||
*/
|
||||
export function parseDomainError(payload: unknown): ApiErrorResponse | null {
|
||||
const parsed = apiErrorResponseSchema.safeParse(payload);
|
||||
return parsed.success ? parsed.data : null;
|
||||
}
|
||||
|
||||
export function buildSuccessResponse<T extends ZodTypeAny>(
|
||||
data: ZodInfer<T>,
|
||||
schema: T
|
||||
): ApiSuccessResponse<ZodInfer<T>> {
|
||||
return {
|
||||
success: true,
|
||||
data: schema.parse(data),
|
||||
};
|
||||
}
|
||||
|
||||
@ -176,6 +176,12 @@ async function defaultHandleError(response: Response) {
|
||||
throw new ApiError(message, response, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse response body from the BFF.
|
||||
*
|
||||
* The BFF returns data directly without any wrapper envelope.
|
||||
* Errors are handled via HTTP status codes (4xx/5xx) and caught by `handleError`.
|
||||
*/
|
||||
const parseResponseBody = async (response: Response): Promise<unknown> => {
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
|
||||
@ -9,7 +9,6 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
* @see https://nextjs.org/docs/app/guides/content-security-policy
|
||||
* @see https://nextjs.org/docs/messages/middleware-to-proxy
|
||||
*/
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Generate a random nonce for this request
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
||||
|
||||
@ -92,32 +92,30 @@ export const soqlFieldNameSchema = z
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Schema for successful API responses
|
||||
* Usage: apiSuccessResponseSchema(yourDataSchema)
|
||||
* Schema for action acknowledgement responses (no payload).
|
||||
* Used for endpoints that confirm an operation succeeded without returning data.
|
||||
*
|
||||
* @example
|
||||
* // Controller returns:
|
||||
* return {}; // Empty object as acknowledgement
|
||||
*/
|
||||
export const apiSuccessResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
||||
z.object({
|
||||
success: z.literal(true),
|
||||
data: dataSchema,
|
||||
});
|
||||
export const actionAckResponseSchema = z.object({});
|
||||
|
||||
/**
|
||||
* Schema for successful API acknowledgements (no payload)
|
||||
* Schema for action responses with a human-readable message.
|
||||
* Used for endpoints that return a confirmation message.
|
||||
*
|
||||
* @example
|
||||
* // Controller returns:
|
||||
* return { message: "SIM top-up completed successfully" };
|
||||
*/
|
||||
export const apiSuccessAckResponseSchema = z.object({
|
||||
success: z.literal(true),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for successful API responses with a human-readable message (no data payload)
|
||||
*/
|
||||
export const apiSuccessMessageResponseSchema = z.object({
|
||||
success: z.literal(true),
|
||||
export const actionMessageResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for error API responses
|
||||
* Schema for error API responses.
|
||||
* Used by the exception filter to return structured errors.
|
||||
*/
|
||||
export const apiErrorResponseSchema = z.object({
|
||||
success: z.literal(false),
|
||||
@ -128,12 +126,25 @@ export const apiErrorResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Legacy API Response Schemas (Deprecated)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Discriminated union schema for API responses
|
||||
* Usage: apiResponseSchema(yourDataSchema)
|
||||
* @deprecated Use direct data schemas instead. The success envelope has been removed.
|
||||
* Kept for backwards compatibility during migration.
|
||||
*/
|
||||
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
||||
z.discriminatedUnion("success", [apiSuccessResponseSchema(dataSchema), apiErrorResponseSchema]);
|
||||
export const apiSuccessResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) => dataSchema;
|
||||
|
||||
/**
|
||||
* @deprecated Use actionAckResponseSchema instead.
|
||||
*/
|
||||
export const apiSuccessAckResponseSchema = actionAckResponseSchema;
|
||||
|
||||
/**
|
||||
* @deprecated Use actionMessageResponseSchema instead.
|
||||
*/
|
||||
export const apiSuccessMessageResponseSchema = actionMessageResponseSchema;
|
||||
|
||||
// ============================================================================
|
||||
// Pagination Schemas
|
||||
|
||||
@ -40,31 +40,27 @@ export type SalesforceCaseId = string & { readonly __brand: "SalesforceCaseId" }
|
||||
// ============================================================================
|
||||
|
||||
// ============================================================================
|
||||
// API Response Wrappers
|
||||
// API Response Types
|
||||
// ============================================================================
|
||||
|
||||
export interface ApiSuccessResponse<T> {
|
||||
success: true;
|
||||
data: T;
|
||||
}
|
||||
/**
|
||||
* Action acknowledgement response (empty object).
|
||||
* Used for endpoints that confirm an operation succeeded without returning data.
|
||||
*/
|
||||
export type ActionAckResponse = Record<string, never>;
|
||||
|
||||
/**
|
||||
* Success acknowledgement response (no payload)
|
||||
* Used for endpoints that simply confirm the operation succeeded.
|
||||
* Action message response.
|
||||
* Used for endpoints that return a confirmation message.
|
||||
*/
|
||||
export interface ApiSuccessAckResponse {
|
||||
success: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Success message response (no data payload)
|
||||
* Used for endpoints that return a human-readable message (e.g., actions).
|
||||
*/
|
||||
export interface ApiSuccessMessageResponse {
|
||||
success: true;
|
||||
export interface ActionMessageResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response from the BFF.
|
||||
* All errors include a code, message, and optional details.
|
||||
*/
|
||||
export interface ApiErrorResponse {
|
||||
success: false;
|
||||
error: {
|
||||
@ -74,6 +70,36 @@ export interface ApiErrorResponse {
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Legacy API Response Types (Deprecated)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @deprecated The success envelope has been removed. Use data types directly.
|
||||
*/
|
||||
export interface ApiSuccessResponse<T> {
|
||||
success: true;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ActionAckResponse instead.
|
||||
*/
|
||||
export interface ApiSuccessAckResponse {
|
||||
success: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ActionMessageResponse instead.
|
||||
*/
|
||||
export interface ApiSuccessMessageResponse {
|
||||
success: true;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use data types directly for success, ApiErrorResponse for errors.
|
||||
*/
|
||||
export type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { apiSuccessResponseSchema } from "../common/index.js";
|
||||
|
||||
// ============================================================================
|
||||
// Enum Value Arrays (for Zod schemas)
|
||||
@ -316,7 +315,7 @@ export const checkoutBuildCartRequestSchema = z.object({
|
||||
configuration: orderConfigurationsSchema.optional(),
|
||||
});
|
||||
|
||||
export const checkoutBuildCartResponseSchema = apiSuccessResponseSchema(checkoutCartSchema);
|
||||
export const checkoutBuildCartResponseSchema = checkoutCartSchema;
|
||||
|
||||
// ============================================================================
|
||||
// BFF endpoint request/param schemas (DTO inputs)
|
||||
@ -352,12 +351,10 @@ export const checkoutSessionDataSchema = z.object({
|
||||
cart: checkoutCartSummarySchema,
|
||||
});
|
||||
|
||||
export const checkoutSessionResponseSchema = apiSuccessResponseSchema(checkoutSessionDataSchema);
|
||||
export const checkoutSessionResponseSchema = checkoutSessionDataSchema;
|
||||
|
||||
export const checkoutValidateCartDataSchema = z.object({ valid: z.boolean() });
|
||||
export const checkoutValidateCartResponseSchema = apiSuccessResponseSchema(
|
||||
checkoutValidateCartDataSchema
|
||||
);
|
||||
export const checkoutValidateCartResponseSchema = checkoutValidateCartDataSchema;
|
||||
|
||||
/**
|
||||
* Schema for order creation response
|
||||
|
||||
@ -124,10 +124,10 @@
|
||||
"typecheck": "pnpm run type-check"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
"zod": "catalog:"
|
||||
"zod": "^4.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { apiSuccessMessageResponseSchema } from "../common/schema.js";
|
||||
|
||||
// Subscription Status Schema
|
||||
export const subscriptionStatusSchema = z.enum([
|
||||
@ -104,14 +103,16 @@ export const subscriptionStatsSchema = z.object({
|
||||
/**
|
||||
* Schema for SIM action responses (top-up, cancellation, feature updates)
|
||||
*/
|
||||
export const simActionResponseSchema = apiSuccessMessageResponseSchema.extend({
|
||||
export const simActionResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
data: z.unknown().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for SIM plan change result with IP addresses
|
||||
*/
|
||||
export const simPlanChangeResultSchema = apiSuccessMessageResponseSchema.extend({
|
||||
export const simPlanChangeResultSchema = z.object({
|
||||
message: z.string(),
|
||||
ipv4: z.string().optional(),
|
||||
ipv6: z.string().optional(),
|
||||
scheduledAt: z
|
||||
|
||||
431
pnpm-lock.yaml
generated
431
pnpm-lock.yaml
generated
@ -64,7 +64,7 @@ importers:
|
||||
dependencies:
|
||||
"@customer-portal/domain":
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/domain
|
||||
version: file:packages/domain(zod@4.1.13)
|
||||
"@nestjs/bullmq":
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.65.1)
|
||||
@ -91,7 +91,7 @@ importers:
|
||||
version: 7.1.0
|
||||
"@prisma/client":
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3)
|
||||
version: 7.1.0(prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3)
|
||||
"@sendgrid/mail":
|
||||
specifier: ^8.1.6
|
||||
version: 8.1.6
|
||||
@ -130,7 +130,7 @@ importers:
|
||||
version: 8.16.3
|
||||
prisma:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)
|
||||
version: 7.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
|
||||
rate-limiter-flexible:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
@ -191,10 +191,10 @@ importers:
|
||||
version: link:../../packages/domain
|
||||
"@heroicons/react":
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(react@19.2.1)
|
||||
version: 2.2.0(react@19.2.3)
|
||||
"@tanstack/react-query":
|
||||
specifier: ^5.90.12
|
||||
version: 5.90.12(react@19.2.1)
|
||||
specifier: ^5.90.14
|
||||
version: 5.90.14(react@19.2.3)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@ -206,16 +206,16 @@ importers:
|
||||
version: 4.1.0
|
||||
lucide-react:
|
||||
specifier: ^0.562.0
|
||||
version: 0.562.0(react@19.2.1)
|
||||
version: 0.562.0(react@19.2.3)
|
||||
next:
|
||||
specifier: 16.0.10
|
||||
version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react:
|
||||
specifier: 19.2.1
|
||||
version: 19.2.1
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3
|
||||
react-dom:
|
||||
specifier: 19.2.1
|
||||
version: 19.2.1(react@19.2.1)
|
||||
specifier: 19.2.3
|
||||
version: 19.2.3(react@19.2.3)
|
||||
tailwind-merge:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
@ -223,21 +223,21 @@ importers:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
zod:
|
||||
specifier: "catalog:"
|
||||
version: 4.1.13
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
zustand:
|
||||
specifier: ^5.0.9
|
||||
version: 5.0.9(@types/react@19.2.7)(react@19.2.1)
|
||||
version: 5.0.9(@types/react@19.2.7)(react@19.2.3)
|
||||
devDependencies:
|
||||
"@next/bundle-analyzer":
|
||||
specifier: ^16.0.9
|
||||
version: 16.0.10
|
||||
specifier: ^16.1.1
|
||||
version: 16.1.1
|
||||
"@tailwindcss/postcss":
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
"@tanstack/react-query-devtools":
|
||||
specifier: ^5.91.1
|
||||
version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)
|
||||
specifier: ^5.91.2
|
||||
version: 5.91.2(@tanstack/react-query@5.90.14(react@19.2.3))(react@19.2.3)
|
||||
"@types/react":
|
||||
specifier: ^19.2.7
|
||||
version: 19.2.7
|
||||
@ -245,11 +245,11 @@ importers:
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.7)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@4.1.17)
|
||||
version: 1.0.7(tailwindcss@4.1.18)
|
||||
typescript:
|
||||
specifier: "catalog:"
|
||||
version: 5.9.3
|
||||
@ -260,8 +260,8 @@ importers:
|
||||
specifier: "catalog:"
|
||||
version: 5.9.3
|
||||
zod:
|
||||
specifier: "catalog:"
|
||||
version: 4.1.13
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
|
||||
packages:
|
||||
"@alloc/quick-lru@5.2.0":
|
||||
@ -508,6 +508,11 @@ packages:
|
||||
}
|
||||
engines: { node: ">=0.1.90" }
|
||||
|
||||
"@customer-portal/domain@file:packages/domain":
|
||||
resolution: { directory: packages/domain, type: directory }
|
||||
peerDependencies:
|
||||
zod: 4.1.13
|
||||
|
||||
"@discoveryjs/json-ext@0.5.7":
|
||||
resolution:
|
||||
{
|
||||
@ -1765,16 +1770,16 @@ packages:
|
||||
"@nestjs/platform-express":
|
||||
optional: true
|
||||
|
||||
"@next/bundle-analyzer@16.0.10":
|
||||
"@next/bundle-analyzer@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-AHA6ZomhQuRsJtkoRvsq+hIuwA6F26mQzQT8ICcc2dL3BvHRcWOA+EiFr+BgWFY++EE957xVDqMIJjLApyxnwA==,
|
||||
integrity: sha512-aNJy301GGH8k36rDgrYdnyYEdjRQg6csMi1njzqHo+3qyZvOOWMHSv+p7SztNTzP5RU2KRwX0pPwYBtDcE+vVA==,
|
||||
}
|
||||
|
||||
"@next/env@16.0.10":
|
||||
"@next/env@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==,
|
||||
integrity: sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==,
|
||||
}
|
||||
|
||||
"@next/eslint-plugin-next@16.0.9":
|
||||
@ -1783,73 +1788,73 @@ packages:
|
||||
integrity: sha512-ea6F0Towc70S+5y0HfkmMeNvWXHH+5yQUhovmed5qHu9WxJRW0oE26+OU6z4u0hR5WHYec7KwwHZCyWlnwdpOg==,
|
||||
}
|
||||
|
||||
"@next/swc-darwin-arm64@16.0.10":
|
||||
"@next/swc-darwin-arm64@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==,
|
||||
integrity: sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
"@next/swc-darwin-x64@16.0.10":
|
||||
"@next/swc-darwin-x64@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==,
|
||||
integrity: sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
"@next/swc-linux-arm64-gnu@16.0.10":
|
||||
"@next/swc-linux-arm64-gnu@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==,
|
||||
integrity: sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
"@next/swc-linux-arm64-musl@16.0.10":
|
||||
"@next/swc-linux-arm64-musl@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==,
|
||||
integrity: sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
"@next/swc-linux-x64-gnu@16.0.10":
|
||||
"@next/swc-linux-x64-gnu@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==,
|
||||
integrity: sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
"@next/swc-linux-x64-musl@16.0.10":
|
||||
"@next/swc-linux-x64-musl@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==,
|
||||
integrity: sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
"@next/swc-win32-arm64-msvc@16.0.10":
|
||||
"@next/swc-win32-arm64-msvc@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==,
|
||||
integrity: sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
"@next/swc-win32-x64-msvc@16.0.10":
|
||||
"@next/swc-win32-x64-msvc@16.1.1":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==,
|
||||
integrity: sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
@ -2254,97 +2259,97 @@ packages:
|
||||
}
|
||||
engines: { node: ">=14.16" }
|
||||
|
||||
"@tailwindcss/node@4.1.17":
|
||||
"@tailwindcss/node@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==,
|
||||
integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==,
|
||||
}
|
||||
|
||||
"@tailwindcss/oxide-android-arm64@4.1.17":
|
||||
"@tailwindcss/oxide-android-arm64@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==,
|
||||
integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64@4.1.17":
|
||||
"@tailwindcss/oxide-darwin-arm64@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==,
|
||||
integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64@4.1.17":
|
||||
"@tailwindcss/oxide-darwin-x64@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==,
|
||||
integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64@4.1.17":
|
||||
"@tailwindcss/oxide-freebsd-x64@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==,
|
||||
integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==,
|
||||
integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==,
|
||||
integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==,
|
||||
integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.1.17":
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==,
|
||||
integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.1.17":
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==,
|
||||
integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.1.17":
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==,
|
||||
integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==,
|
||||
}
|
||||
engines: { node: ">=14.0.0" }
|
||||
cpu: [wasm32]
|
||||
@ -2356,62 +2361,62 @@ packages:
|
||||
- "@emnapi/wasi-threads"
|
||||
- tslib
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.1.17":
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==,
|
||||
integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.17":
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==,
|
||||
integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
"@tailwindcss/oxide@4.1.17":
|
||||
"@tailwindcss/oxide@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==,
|
||||
integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==,
|
||||
}
|
||||
engines: { node: ">= 10" }
|
||||
|
||||
"@tailwindcss/postcss@4.1.17":
|
||||
"@tailwindcss/postcss@4.1.18":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==,
|
||||
integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==,
|
||||
}
|
||||
|
||||
"@tanstack/query-core@5.90.12":
|
||||
"@tanstack/query-core@5.90.14":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==,
|
||||
integrity: sha512-/6di2yNI+YxpVrH9Ig74Q+puKnkCE+D0LGyagJEGndJHJc6ahkcc/UqirHKy8zCYE/N9KLggxcQvzYCsUBWgdw==,
|
||||
}
|
||||
|
||||
"@tanstack/query-devtools@5.91.1":
|
||||
"@tanstack/query-devtools@5.92.0":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==,
|
||||
integrity: sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==,
|
||||
}
|
||||
|
||||
"@tanstack/react-query-devtools@5.91.1":
|
||||
"@tanstack/react-query-devtools@5.91.2":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==,
|
||||
integrity: sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==,
|
||||
}
|
||||
peerDependencies:
|
||||
"@tanstack/react-query": ^5.90.10
|
||||
"@tanstack/react-query": ^5.90.14
|
||||
react: ^18 || ^19
|
||||
|
||||
"@tanstack/react-query@5.90.12":
|
||||
"@tanstack/react-query@5.90.14":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==,
|
||||
integrity: sha512-JAMuULej09hrZ14W9+mxoRZ44rR2BuZfCd6oKTQVNfynQxCN3muH3jh3W46gqZNw5ZqY0ZVaS43Imb3dMr6tgw==,
|
||||
}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
@ -3127,6 +3132,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">=6.0.0" }
|
||||
|
||||
baseline-browser-mapping@2.9.11:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==,
|
||||
}
|
||||
hasBin: true
|
||||
|
||||
baseline-browser-mapping@2.9.5:
|
||||
resolution:
|
||||
{
|
||||
@ -3298,6 +3310,12 @@ packages:
|
||||
integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==,
|
||||
}
|
||||
|
||||
caniuse-lite@1.0.30001761:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==,
|
||||
}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution:
|
||||
{
|
||||
@ -3919,6 +3937,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">=10.13.0" }
|
||||
|
||||
enhanced-resolve@5.18.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==,
|
||||
}
|
||||
engines: { node: ">=10.13.0" }
|
||||
|
||||
environment@1.1.0:
|
||||
resolution:
|
||||
{
|
||||
@ -5013,10 +5038,10 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 0.8.0" }
|
||||
|
||||
libphonenumber-js@1.12.31:
|
||||
libphonenumber-js@1.12.33:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Z3IhgVgrqO1S5xPYM3K5XwbkDasU67/Vys4heW+lfSBALcUZjeIIzI8zCLifY+OCzSq+fpDdywMDa7z+4srJPQ==,
|
||||
integrity: sha512-r9kw4OA6oDO4dPXkOrXTkArQAafIKAU71hChInV4FxZ69dxCfbwQGDPzqR5/vea94wU705/3AZroEbSoeVWrQw==,
|
||||
}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
@ -5568,10 +5593,10 @@ packages:
|
||||
"@nestjs/swagger":
|
||||
optional: true
|
||||
|
||||
next@16.0.10:
|
||||
next@16.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==,
|
||||
integrity: sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==,
|
||||
}
|
||||
engines: { node: ">=20.9.0" }
|
||||
hasBin: true
|
||||
@ -6228,18 +6253,18 @@ packages:
|
||||
integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==,
|
||||
}
|
||||
|
||||
react-dom@19.2.1:
|
||||
react-dom@19.2.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==,
|
||||
integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==,
|
||||
}
|
||||
peerDependencies:
|
||||
react: ^19.2.1
|
||||
react: ^19.2.3
|
||||
|
||||
react@19.2.1:
|
||||
react@19.2.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==,
|
||||
integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==,
|
||||
}
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
@ -6899,10 +6924,10 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: ">=3.0.0 || insiders"
|
||||
|
||||
tailwindcss@4.1.17:
|
||||
tailwindcss@4.1.18:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==,
|
||||
integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==,
|
||||
}
|
||||
|
||||
tapable@2.3.0:
|
||||
@ -7245,10 +7270,10 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
validator@13.15.23:
|
||||
validator@13.15.26:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==,
|
||||
integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==,
|
||||
}
|
||||
engines: { node: ">= 0.10" }
|
||||
|
||||
@ -7494,10 +7519,10 @@ packages:
|
||||
integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==,
|
||||
}
|
||||
|
||||
zod@4.2.0:
|
||||
zod@4.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==,
|
||||
integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==,
|
||||
}
|
||||
|
||||
zustand@5.0.9:
|
||||
@ -7704,6 +7729,10 @@ snapshots:
|
||||
"@colors/colors@1.5.0":
|
||||
optional: true
|
||||
|
||||
"@customer-portal/domain@file:packages/domain(zod@4.1.13)":
|
||||
dependencies:
|
||||
zod: 4.1.13
|
||||
|
||||
"@discoveryjs/json-ext@0.5.7": {}
|
||||
|
||||
"@electric-sql/pglite-socket@0.0.6(@electric-sql/pglite@0.3.2)":
|
||||
@ -7859,9 +7888,9 @@ snapshots:
|
||||
protobufjs: 7.5.4
|
||||
yargs: 17.7.2
|
||||
|
||||
"@heroicons/react@2.2.0(react@19.2.1)":
|
||||
"@heroicons/react@2.2.0(react@19.2.3)":
|
||||
dependencies:
|
||||
react: 19.2.1
|
||||
react: 19.2.3
|
||||
|
||||
"@hono/node-server@1.19.6(hono@4.10.6)":
|
||||
dependencies:
|
||||
@ -8388,41 +8417,41 @@ snapshots:
|
||||
optionalDependencies:
|
||||
"@nestjs/platform-express": 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
||||
|
||||
"@next/bundle-analyzer@16.0.10":
|
||||
"@next/bundle-analyzer@16.1.1":
|
||||
dependencies:
|
||||
webpack-bundle-analyzer: 4.10.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
"@next/env@16.0.10": {}
|
||||
"@next/env@16.1.1": {}
|
||||
|
||||
"@next/eslint-plugin-next@16.0.9":
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
"@next/swc-darwin-arm64@16.0.10":
|
||||
"@next/swc-darwin-arm64@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-darwin-x64@16.0.10":
|
||||
"@next/swc-darwin-x64@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-linux-arm64-gnu@16.0.10":
|
||||
"@next/swc-linux-arm64-gnu@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-linux-arm64-musl@16.0.10":
|
||||
"@next/swc-linux-arm64-musl@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-linux-x64-gnu@16.0.10":
|
||||
"@next/swc-linux-x64-gnu@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-linux-x64-musl@16.0.10":
|
||||
"@next/swc-linux-x64-musl@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-win32-arm64-msvc@16.0.10":
|
||||
"@next/swc-win32-arm64-msvc@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@next/swc-win32-x64-msvc@16.0.10":
|
||||
"@next/swc-win32-x64-msvc@16.1.1":
|
||||
optional: true
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
@ -8457,11 +8486,11 @@ snapshots:
|
||||
|
||||
"@prisma/client-runtime-utils@7.1.0": {}
|
||||
|
||||
"@prisma/client@7.1.0(prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3))(typescript@5.9.3)":
|
||||
"@prisma/client@7.1.0(prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3)":
|
||||
dependencies:
|
||||
"@prisma/client-runtime-utils": 7.1.0
|
||||
optionalDependencies:
|
||||
prisma: 7.1.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)
|
||||
prisma: 7.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
"@prisma/config@7.1.0":
|
||||
@ -8528,11 +8557,11 @@ snapshots:
|
||||
|
||||
"@prisma/query-plan-executor@6.18.0": {}
|
||||
|
||||
"@prisma/studio-core@0.8.2(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)":
|
||||
"@prisma/studio-core@0.8.2(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)":
|
||||
dependencies:
|
||||
"@types/react": 19.2.7
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
"@protobufjs/aspromise@1.1.2": {}
|
||||
|
||||
@ -8668,89 +8697,89 @@ snapshots:
|
||||
defer-to-connect: 2.0.1
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/node@4.1.17":
|
||||
"@tailwindcss/node@4.1.18":
|
||||
dependencies:
|
||||
"@jridgewell/remapping": 2.3.5
|
||||
enhanced-resolve: 5.18.3
|
||||
enhanced-resolve: 5.18.4
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.1.17
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
"@tailwindcss/oxide-android-arm64@4.1.17":
|
||||
"@tailwindcss/oxide-android-arm64@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64@4.1.17":
|
||||
"@tailwindcss/oxide-darwin-arm64@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64@4.1.17":
|
||||
"@tailwindcss/oxide-darwin-x64@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64@4.1.17":
|
||||
"@tailwindcss/oxide-freebsd-x64@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.1.17":
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.1.17":
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.1.17":
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.1.17":
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.1.17":
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.17":
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.18":
|
||||
optional: true
|
||||
|
||||
"@tailwindcss/oxide@4.1.17":
|
||||
"@tailwindcss/oxide@4.1.18":
|
||||
optionalDependencies:
|
||||
"@tailwindcss/oxide-android-arm64": 4.1.17
|
||||
"@tailwindcss/oxide-darwin-arm64": 4.1.17
|
||||
"@tailwindcss/oxide-darwin-x64": 4.1.17
|
||||
"@tailwindcss/oxide-freebsd-x64": 4.1.17
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.17
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": 4.1.17
|
||||
"@tailwindcss/oxide-linux-arm64-musl": 4.1.17
|
||||
"@tailwindcss/oxide-linux-x64-gnu": 4.1.17
|
||||
"@tailwindcss/oxide-linux-x64-musl": 4.1.17
|
||||
"@tailwindcss/oxide-wasm32-wasi": 4.1.17
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": 4.1.17
|
||||
"@tailwindcss/oxide-win32-x64-msvc": 4.1.17
|
||||
"@tailwindcss/oxide-android-arm64": 4.1.18
|
||||
"@tailwindcss/oxide-darwin-arm64": 4.1.18
|
||||
"@tailwindcss/oxide-darwin-x64": 4.1.18
|
||||
"@tailwindcss/oxide-freebsd-x64": 4.1.18
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.18
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": 4.1.18
|
||||
"@tailwindcss/oxide-linux-arm64-musl": 4.1.18
|
||||
"@tailwindcss/oxide-linux-x64-gnu": 4.1.18
|
||||
"@tailwindcss/oxide-linux-x64-musl": 4.1.18
|
||||
"@tailwindcss/oxide-wasm32-wasi": 4.1.18
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": 4.1.18
|
||||
"@tailwindcss/oxide-win32-x64-msvc": 4.1.18
|
||||
|
||||
"@tailwindcss/postcss@4.1.17":
|
||||
"@tailwindcss/postcss@4.1.18":
|
||||
dependencies:
|
||||
"@alloc/quick-lru": 5.2.0
|
||||
"@tailwindcss/node": 4.1.17
|
||||
"@tailwindcss/oxide": 4.1.17
|
||||
"@tailwindcss/node": 4.1.18
|
||||
"@tailwindcss/oxide": 4.1.18
|
||||
postcss: 8.5.6
|
||||
tailwindcss: 4.1.17
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
"@tanstack/query-core@5.90.12": {}
|
||||
"@tanstack/query-core@5.90.14": {}
|
||||
|
||||
"@tanstack/query-devtools@5.91.1": {}
|
||||
"@tanstack/query-devtools@5.92.0": {}
|
||||
|
||||
"@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)":
|
||||
"@tanstack/react-query-devtools@5.91.2(@tanstack/react-query@5.90.14(react@19.2.3))(react@19.2.3)":
|
||||
dependencies:
|
||||
"@tanstack/query-devtools": 5.91.1
|
||||
"@tanstack/react-query": 5.90.12(react@19.2.1)
|
||||
react: 19.2.1
|
||||
"@tanstack/query-devtools": 5.92.0
|
||||
"@tanstack/react-query": 5.90.14(react@19.2.3)
|
||||
react: 19.2.3
|
||||
|
||||
"@tanstack/react-query@5.90.12(react@19.2.1)":
|
||||
"@tanstack/react-query@5.90.14(react@19.2.3)":
|
||||
dependencies:
|
||||
"@tanstack/query-core": 5.90.12
|
||||
react: 19.2.1
|
||||
"@tanstack/query-core": 5.90.14
|
||||
react: 19.2.3
|
||||
|
||||
"@tokenizer/inflate@0.2.7":
|
||||
dependencies:
|
||||
@ -9277,6 +9306,8 @@ snapshots:
|
||||
|
||||
base64url@3.0.1: {}
|
||||
|
||||
baseline-browser-mapping@2.9.11: {}
|
||||
|
||||
baseline-browser-mapping@2.9.5: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
@ -9413,6 +9444,8 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001760: {}
|
||||
|
||||
caniuse-lite@1.0.30001761: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@ -9457,8 +9490,8 @@ snapshots:
|
||||
class-validator@0.14.2:
|
||||
dependencies:
|
||||
"@types/validator": 13.15.10
|
||||
libphonenumber-js: 1.12.31
|
||||
validator: 13.15.23
|
||||
libphonenumber-js: 1.12.33
|
||||
validator: 13.15.26
|
||||
optional: true
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
@ -9718,6 +9751,11 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
enhanced-resolve@5.18.4:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
environment@1.1.0: {}
|
||||
|
||||
error-ex@1.3.4:
|
||||
@ -9784,8 +9822,8 @@ snapshots:
|
||||
"@babel/parser": 7.28.5
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
hermes-parser: 0.25.1
|
||||
zod: 4.2.0
|
||||
zod-validation-error: 4.0.2(zod@4.2.0)
|
||||
zod: 4.2.1
|
||||
zod-validation-error: 4.0.2(zod@4.2.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -10446,7 +10484,7 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
libphonenumber-js@1.12.31:
|
||||
libphonenumber-js@1.12.33:
|
||||
optional: true
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
@ -10565,9 +10603,9 @@ snapshots:
|
||||
|
||||
lru.min@1.1.3: {}
|
||||
|
||||
lucide-react@0.562.0(react@19.2.1):
|
||||
lucide-react@0.562.0(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.1
|
||||
react: 19.2.3
|
||||
|
||||
luxon@3.7.2: {}
|
||||
|
||||
@ -10728,24 +10766,25 @@ snapshots:
|
||||
optionalDependencies:
|
||||
"@nestjs/swagger": 11.2.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||
|
||||
next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
|
||||
next@16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
"@next/env": 16.0.10
|
||||
"@next/env": 16.1.1
|
||||
"@swc/helpers": 0.5.15
|
||||
caniuse-lite: 1.0.30001760
|
||||
baseline-browser-mapping: 2.9.11
|
||||
caniuse-lite: 1.0.30001761
|
||||
postcss: 8.4.31
|
||||
react: 19.2.1
|
||||
react-dom: 19.2.1(react@19.2.1)
|
||||
styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.1)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3)
|
||||
optionalDependencies:
|
||||
"@next/swc-darwin-arm64": 16.0.10
|
||||
"@next/swc-darwin-x64": 16.0.10
|
||||
"@next/swc-linux-arm64-gnu": 16.0.10
|
||||
"@next/swc-linux-arm64-musl": 16.0.10
|
||||
"@next/swc-linux-x64-gnu": 16.0.10
|
||||
"@next/swc-linux-x64-musl": 16.0.10
|
||||
"@next/swc-win32-arm64-msvc": 16.0.10
|
||||
"@next/swc-win32-x64-msvc": 16.0.10
|
||||
"@next/swc-darwin-arm64": 16.1.1
|
||||
"@next/swc-darwin-x64": 16.1.1
|
||||
"@next/swc-linux-arm64-gnu": 16.1.1
|
||||
"@next/swc-linux-arm64-musl": 16.1.1
|
||||
"@next/swc-linux-x64-gnu": 16.1.1
|
||||
"@next/swc-linux-x64-musl": 16.1.1
|
||||
"@next/swc-win32-arm64-msvc": 16.1.1
|
||||
"@next/swc-win32-x64-msvc": 16.1.1
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- "@babel/core"
|
||||
@ -11034,12 +11073,12 @@ snapshots:
|
||||
|
||||
prettier@3.7.4: {}
|
||||
|
||||
prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3):
|
||||
prisma@7.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3):
|
||||
dependencies:
|
||||
"@prisma/config": 7.1.0
|
||||
"@prisma/dev": 0.15.0(typescript@5.9.3)
|
||||
"@prisma/engines": 7.1.0
|
||||
"@prisma/studio-core": 0.8.2(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||
"@prisma/studio-core": 0.8.2(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
mysql2: 3.15.3
|
||||
postgres: 3.4.7
|
||||
optionalDependencies:
|
||||
@ -11122,12 +11161,12 @@ snapshots:
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
|
||||
react-dom@19.2.1(react@19.2.1):
|
||||
react-dom@19.2.3(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.1
|
||||
react: 19.2.3
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.1: {}
|
||||
react@19.2.3: {}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
@ -11498,10 +11537,10 @@ snapshots:
|
||||
dependencies:
|
||||
"@tokenizer/token": 0.3.0
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.1):
|
||||
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.2.1
|
||||
react: 19.2.3
|
||||
optionalDependencies:
|
||||
"@babel/core": 7.28.5
|
||||
|
||||
@ -11530,11 +11569,11 @@ snapshots:
|
||||
|
||||
tailwind-merge@3.4.0: {}
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@4.1.17):
|
||||
tailwindcss-animate@1.0.7(tailwindcss@4.1.18):
|
||||
dependencies:
|
||||
tailwindcss: 4.1.17
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
tailwindcss@4.1.17: {}
|
||||
tailwindcss@4.1.18: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
@ -11730,7 +11769,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
validator@13.15.23:
|
||||
validator@13.15.26:
|
||||
optional: true
|
||||
|
||||
vary@1.1.2: {}
|
||||
@ -11885,15 +11924,15 @@ snapshots:
|
||||
dependencies:
|
||||
grammex: 3.1.12
|
||||
|
||||
zod-validation-error@4.0.2(zod@4.2.0):
|
||||
zod-validation-error@4.0.2(zod@4.2.1):
|
||||
dependencies:
|
||||
zod: 4.2.0
|
||||
zod: 4.2.1
|
||||
|
||||
zod@4.1.13: {}
|
||||
|
||||
zod@4.2.0: {}
|
||||
zod@4.2.1: {}
|
||||
|
||||
zustand@5.0.9(@types/react@19.2.7)(react@19.2.1):
|
||||
zustand@5.0.9(@types/react@19.2.7)(react@19.2.3):
|
||||
optionalDependencies:
|
||||
"@types/react": 19.2.7
|
||||
react: 19.2.1
|
||||
react: 19.2.3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user