feat: StorageService S3/MinIO, POST /users, invoice upload, portal dashboard §08 statuses + notifications
This commit is contained in:
@@ -14,6 +14,9 @@
|
||||
"test:ci": "jest --ci --coverage --forceExit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1058.0",
|
||||
"@aws-sdk/lib-storage": "^3.1058.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1058.0",
|
||||
"@moraworld/database": "workspace:*",
|
||||
"@nestjs/common": "^11.1.0",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
Request,
|
||||
} from "@nestjs/common";
|
||||
@@ -44,3 +45,15 @@ export class NotificationsController {
|
||||
return this.svc.seedDefaultTemplates(req.user.tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
/** GET /notifications — bandeja de entrada del usuario */
|
||||
@Controller("notifications")
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class NotificationsUserController {
|
||||
constructor(private readonly svc: NotificationsService) {}
|
||||
|
||||
@Get()
|
||||
list(@Request() req: any, @Query("limit") limit?: string) {
|
||||
return this.svc.findByUser(req.user.id, limit ? Number(limit) : 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { NotificationsService } from "./notifications.service";
|
||||
import { NotificationsController } from "./notifications.controller";
|
||||
import { NotificationsController, NotificationsUserController } from "./notifications.controller";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
import { IntegrationsModule } from "../integrations/integrations.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, IntegrationsModule],
|
||||
controllers: [NotificationsController],
|
||||
controllers: [NotificationsController, NotificationsUserController],
|
||||
providers: [NotificationsService],
|
||||
exports: [NotificationsService],
|
||||
})
|
||||
|
||||
@@ -3,27 +3,21 @@ import {
|
||||
UseGuards, UseInterceptors, UploadedFiles,
|
||||
} from "@nestjs/common";
|
||||
import { FilesInterceptor } from "@nestjs/platform-express";
|
||||
import { diskStorage } from "multer";
|
||||
import { extname, join } from "path";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { randomUUID } from "crypto";
|
||||
import { memoryStorage } from "multer";
|
||||
import { extname } from "path";
|
||||
import { PackagesService } from "./packages.service";
|
||||
import { StorageService } from "../storage/storage.service";
|
||||
import { CreatePackageDto, UpdateStatusDto, VerifyPackageDto, SenaeDeclarationDto } from "./dto/package.dto";
|
||||
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
@Controller("packages")
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class PackagesController {
|
||||
private readonly apiUrl: string;
|
||||
|
||||
constructor(
|
||||
private svc: PackagesService,
|
||||
private config: ConfigService,
|
||||
) {
|
||||
this.apiUrl = this.config.get("API_URL", "http://localhost:3001");
|
||||
}
|
||||
private storage: StorageService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
findAll(
|
||||
@@ -72,23 +66,13 @@ export class PackagesController {
|
||||
return this.svc.verifyPackage(id, dto, user.id);
|
||||
}
|
||||
|
||||
/** Bodega: upload photos via multipart form (doc §10 step 4) */
|
||||
/** Bodega: upload photos via multipart form — stored via StorageService (doc §10 step 4) */
|
||||
@Post(":id/photos")
|
||||
@Roles("OPERADOR_BODEGA", "ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
@UseInterceptors(
|
||||
FilesInterceptor("photos", 10, {
|
||||
storage: diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const pkgId = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
|
||||
const dir = join(process.cwd(), "uploads", "packages", pkgId);
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
cb(null, dir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, `${randomUUID()}${extname(file.originalname)}`);
|
||||
},
|
||||
}),
|
||||
fileFilter: (req, file, cb) => {
|
||||
storage: memoryStorage(),
|
||||
fileFilter: (_req, file, cb) => {
|
||||
const allowed = /jpg|jpeg|png|gif|webp/;
|
||||
cb(null, allowed.test(extname(file.originalname).toLowerCase()));
|
||||
},
|
||||
@@ -100,9 +84,15 @@ export class PackagesController {
|
||||
@UploadedFiles() files: Express.Multer.File[],
|
||||
@CurrentUser() user: any,
|
||||
): Promise<any> {
|
||||
const baseUrl = this.apiUrl;
|
||||
const photoUrls = (files || []).map(
|
||||
f => `${baseUrl}/uploads/packages/${id}/${f.filename}`,
|
||||
const photoUrls = await Promise.all(
|
||||
(files || []).map(f =>
|
||||
this.storage.saveFile(
|
||||
`packages/${id}`,
|
||||
f.originalname,
|
||||
f.buffer,
|
||||
f.mimetype,
|
||||
),
|
||||
),
|
||||
);
|
||||
return this.svc.addPhotos(id, photoUrls, user.id);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import { PackagesController } from "./packages.controller";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { NotificationsModule } from "../notifications/notifications.module";
|
||||
import { StorageModule } from "../storage/storage.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, ConfigModule, NotificationsModule],
|
||||
imports: [PrismaModule, ConfigModule, NotificationsModule, StorageModule],
|
||||
providers: [PackagesService],
|
||||
controllers: [PackagesController],
|
||||
exports: [PackagesService],
|
||||
|
||||
@@ -15,6 +15,10 @@ export class CreatePreAlertDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
vendorTracking?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
estimatedArrival?: string;
|
||||
}
|
||||
|
||||
export class UpdatePreAlertStatusDto {
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards } from "@nestjs/common";
|
||||
import {
|
||||
Controller, Get, Post, Patch, Delete, Body, Param, UseGuards,
|
||||
UseInterceptors, UploadedFile,
|
||||
} from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { memoryStorage } from "multer";
|
||||
import { PreAlertsService } from "./pre-alerts.service";
|
||||
import { CreatePreAlertDto, UpdatePreAlertStatusDto } from "./dto/pre-alert.dto";
|
||||
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
import { StorageService } from "../storage/storage.service";
|
||||
|
||||
@Controller("pre-alerts")
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class PreAlertsController {
|
||||
constructor(private svc: PreAlertsService) {}
|
||||
constructor(
|
||||
private svc: PreAlertsService,
|
||||
private storage: StorageService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
findAll(@CurrentUser() user: any) {
|
||||
@@ -20,6 +29,34 @@ export class PreAlertsController {
|
||||
return this.svc.create(dto, user);
|
||||
}
|
||||
|
||||
/** Upload invoice PDF/image for a pre-alert (doc §07 / §09) */
|
||||
@Post(":id/invoice")
|
||||
@Roles("CLIENTE")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("invoice", {
|
||||
storage: memoryStorage(),
|
||||
fileFilter: (_req, file, cb) => {
|
||||
const allowed = /pdf|jpg|jpeg|png|webp/;
|
||||
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
||||
cb(null, allowed.test(ext));
|
||||
},
|
||||
limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
|
||||
}),
|
||||
)
|
||||
async uploadInvoice(
|
||||
@Param("id") id: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() user: any,
|
||||
): Promise<any> {
|
||||
const url = await this.storage.saveFile(
|
||||
"invoices",
|
||||
file.originalname,
|
||||
file.buffer,
|
||||
file.mimetype,
|
||||
);
|
||||
return this.svc.attachInvoice(id, url, user);
|
||||
}
|
||||
|
||||
@Patch(":id/status")
|
||||
@Roles("OPERADOR_BODEGA", "ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
updateStatus(@Param("id") id: string, @Body() dto: UpdatePreAlertStatusDto): Promise<any> {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { PreAlertsController } from "./pre-alerts.controller";
|
||||
import { PreAlertsService } from "./pre-alerts.service";
|
||||
import { StorageModule } from "../storage/storage.module";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
|
||||
@Module({ controllers: [PreAlertsController], providers: [PreAlertsService] })
|
||||
@Module({
|
||||
imports: [PrismaModule, StorageModule],
|
||||
controllers: [PreAlertsController],
|
||||
providers: [PreAlertsService],
|
||||
})
|
||||
export class PreAlertsModule {}
|
||||
|
||||
@@ -19,17 +19,29 @@ export class PreAlertsService {
|
||||
async create(dto: CreatePreAlertDto, user: any): Promise<any> {
|
||||
return this.prisma.client.preAlert.create({
|
||||
data: {
|
||||
tenantId: user.tenantId,
|
||||
userId: user.id,
|
||||
store: dto.store,
|
||||
description: dto.description,
|
||||
declaredValue: dto.declaredValue ?? 0,
|
||||
vendorTracking: dto.vendorTracking,
|
||||
status: "PENDIENTE",
|
||||
tenantId: user.tenantId,
|
||||
userId: user.id,
|
||||
store: dto.store,
|
||||
description: dto.description,
|
||||
declaredValue: dto.declaredValue ?? 0,
|
||||
vendorTracking: dto.vendorTracking,
|
||||
estimatedArrival: dto.estimatedArrival ? new Date(dto.estimatedArrival) : undefined,
|
||||
status: "PENDIENTE",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Attach invoice URL to a pre-alert (doc §07/§09) */
|
||||
async attachInvoice(id: string, invoiceKey: string, user: any): Promise<any> {
|
||||
const alert = await this.prisma.client.preAlert.findUnique({ where: { id } });
|
||||
if (!alert) throw new NotFoundException("Pre-alerta no encontrada.");
|
||||
if (user.role === "CLIENTE" && alert.userId !== user.id) throw new ForbiddenException();
|
||||
return this.prisma.client.preAlert.update({
|
||||
where: { id },
|
||||
data: { invoiceKey },
|
||||
});
|
||||
}
|
||||
|
||||
async updateStatus(id: string, dto: UpdatePreAlertStatusDto): Promise<any> {
|
||||
const alert = await this.prisma.client.preAlert.findUnique({ where: { id } });
|
||||
if (!alert) throw new NotFoundException("Pre-alerta no encontrada.");
|
||||
|
||||
@@ -3,38 +3,121 @@ import { ConfigService } from "@nestjs/config";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { randomUUID } from "crypto";
|
||||
import { S3Client, DeleteObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
/**
|
||||
* StorageService — dual mode:
|
||||
* • If S3_ENDPOINT is set → MinIO / S3-compatible storage
|
||||
* • Otherwise → local disk under ./uploads/ (development)
|
||||
*/
|
||||
@Injectable()
|
||||
export class StorageService {
|
||||
private readonly logger = new Logger(StorageService.name);
|
||||
private readonly uploadDir: string;
|
||||
private readonly baseUrl: string;
|
||||
private readonly s3?: S3Client;
|
||||
private readonly bucket?: string;
|
||||
private readonly s3Public?: string;
|
||||
|
||||
constructor(private config: ConfigService) {
|
||||
this.uploadDir = path.join(process.cwd(), "uploads");
|
||||
this.baseUrl = this.config.get("API_URL", "http://localhost:3001");
|
||||
// ensure uploads dir exists
|
||||
if (!fs.existsSync(this.uploadDir)) {
|
||||
fs.mkdirSync(this.uploadDir, { recursive: true });
|
||||
this.baseUrl = this.config.get("API_URL", "http://localhost:3001");
|
||||
|
||||
const endpoint = this.config.get<string>("S3_ENDPOINT");
|
||||
const accessKey = this.config.get<string>("S3_ACCESS_KEY");
|
||||
const secretKey = this.config.get<string>("S3_SECRET_KEY");
|
||||
const region = this.config.get<string>("S3_REGION", "us-east-1");
|
||||
const forcePath = this.config.get<string>("S3_FORCE_PATH_STYLE", "false") === "true";
|
||||
this.bucket = this.config.get<string>("S3_BUCKET", "moraworld");
|
||||
this.s3Public = this.config.get<string>("S3_PUBLIC_URL") ?? endpoint;
|
||||
|
||||
if (endpoint && accessKey && secretKey) {
|
||||
this.s3 = new S3Client({
|
||||
endpoint,
|
||||
region,
|
||||
forcePathStyle: forcePath,
|
||||
credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
|
||||
});
|
||||
this.logger.log(`[STORAGE] S3/MinIO mode — endpoint: ${endpoint}, bucket: ${this.bucket}`);
|
||||
} else {
|
||||
// Local disk fallback
|
||||
if (!fs.existsSync(this.uploadDir)) {
|
||||
fs.mkdirSync(this.uploadDir, { recursive: true });
|
||||
}
|
||||
this.logger.log("[STORAGE] Local disk mode (no S3_ENDPOINT configured)");
|
||||
}
|
||||
}
|
||||
|
||||
async saveFile(subdir: string, originalName: string, buffer: Buffer): Promise<string> {
|
||||
const ext = path.extname(originalName);
|
||||
get isS3Mode(): boolean { return !!this.s3; }
|
||||
|
||||
/**
|
||||
* Store a file. Returns its public URL.
|
||||
* @param subdir e.g. "packages/pkg-123" or "invoices"
|
||||
* @param originalName e.g. "photo.jpg"
|
||||
* @param buffer file contents
|
||||
* @param mimeType e.g. "image/jpeg"
|
||||
*/
|
||||
async saveFile(
|
||||
subdir: string,
|
||||
originalName: string,
|
||||
buffer: Buffer,
|
||||
mimeType = "application/octet-stream",
|
||||
): Promise<string> {
|
||||
const ext = path.extname(originalName);
|
||||
const filename = `${randomUUID()}${ext}`;
|
||||
const key = `${subdir}/${filename}`;
|
||||
|
||||
if (this.s3) {
|
||||
await this.s3.send(new PutObjectCommand({
|
||||
Bucket: this.bucket!,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: mimeType,
|
||||
// ACL not used — presigned URLs or public endpoint handle access
|
||||
}));
|
||||
// Return public path (either bucket/key or via configured public URL)
|
||||
return `${this.s3Public}/${this.bucket}/${key}`;
|
||||
}
|
||||
|
||||
// Local disk
|
||||
const dir = path.join(this.uploadDir, subdir);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
const filepath = path.join(dir, filename);
|
||||
fs.writeFileSync(filepath, buffer);
|
||||
// Return URL relative to API base
|
||||
return `${this.baseUrl}/uploads/${subdir}/${filename}`;
|
||||
fs.writeFileSync(path.join(dir, filename), buffer);
|
||||
return `${this.baseUrl}/uploads/${key}`;
|
||||
}
|
||||
|
||||
async deleteFile(url: string): Promise<void> {
|
||||
/**
|
||||
* Generate a presigned GET URL for an S3 key (valid 1 hour).
|
||||
* Falls through to a direct URL if not in S3 mode.
|
||||
*/
|
||||
async presign(keyOrUrl: string, expiresIn = 3600): Promise<string> {
|
||||
if (!this.s3) return keyOrUrl;
|
||||
// If a full URL was passed, extract the key
|
||||
const key = keyOrUrl.includes(`/${this.bucket}/`)
|
||||
? keyOrUrl.split(`/${this.bucket}/`)[1]
|
||||
: keyOrUrl;
|
||||
return getSignedUrl(
|
||||
this.s3,
|
||||
new GetObjectCommand({ Bucket: this.bucket!, Key: key }),
|
||||
{ expiresIn },
|
||||
);
|
||||
}
|
||||
|
||||
/** Delete a file by its URL or S3 key */
|
||||
async deleteFile(urlOrKey: string): Promise<void> {
|
||||
try {
|
||||
const relative = url.replace(/^https?:\/\/[^/]+\/uploads\//, "");
|
||||
const filepath = path.join(this.uploadDir, relative);
|
||||
if (fs.existsSync(filepath)) fs.unlinkSync(filepath);
|
||||
if (this.s3) {
|
||||
const key = urlOrKey.includes(`/${this.bucket}/`)
|
||||
? urlOrKey.split(`/${this.bucket}/`)[1]
|
||||
: urlOrKey;
|
||||
await this.s3.send(new DeleteObjectCommand({ Bucket: this.bucket!, Key: key }));
|
||||
} else {
|
||||
const relative = urlOrKey.replace(/^https?:\/\/[^/]+\/uploads\//, "");
|
||||
const filepath = path.join(this.uploadDir, relative);
|
||||
if (fs.existsSync(filepath)) fs.unlinkSync(filepath);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
this.logger.warn(`deleteFile failed: ${(e as Error).message}`);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Controller, Get, Patch, Body, Param, Query, UseGuards } from "@nestjs/common";
|
||||
import { Controller, Get, Post, Patch, Body, Param, Query, UseGuards } from "@nestjs/common";
|
||||
import { UsersService } from "./users.service";
|
||||
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
import { IsEnum, IsBoolean } from "class-validator";
|
||||
import { IsEnum, IsBoolean, IsEmail, IsString, MinLength, IsOptional } from "class-validator";
|
||||
|
||||
class UpdateRoleDto {
|
||||
@IsEnum(["CLIENTE","OPERADOR_BODEGA","AGENTE_ADUANERO","ADMIN_EMPRESA","SUPER_ADMIN","SOPORTE"])
|
||||
@@ -11,6 +11,15 @@ class UpdateRoleDto {
|
||||
class SetActiveDto {
|
||||
@IsBoolean() isActive!: boolean;
|
||||
}
|
||||
class CreateUserDto {
|
||||
@IsEmail() email!: string;
|
||||
@IsString() @MinLength(8) password!: string;
|
||||
@IsString() firstName!: string;
|
||||
@IsString() lastName!: string;
|
||||
@IsOptional() @IsString() phone?: string;
|
||||
@IsEnum(["CLIENTE","OPERADOR_BODEGA","AGENTE_ADUANERO","ADMIN_EMPRESA","SUPER_ADMIN","SOPORTE"])
|
||||
role!: string;
|
||||
}
|
||||
|
||||
@Controller("users")
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@@ -29,6 +38,13 @@ export class UsersController {
|
||||
return this.svc.findOne(id);
|
||||
}
|
||||
|
||||
/** Admin creates a user directly (doc §12) */
|
||||
@Post()
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
create(@Body() dto: CreateUserDto, @CurrentUser() user: any): Promise<any> {
|
||||
return this.svc.create(dto, user.tenantId);
|
||||
}
|
||||
|
||||
@Patch(":id/role")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
updateRole(@Param("id") id: string, @Body() dto: UpdateRoleDto): Promise<any> {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { UsersController } from "./users.controller";
|
||||
import { UsersService } from "./users.service";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
|
||||
@Module({ controllers: [UsersController], providers: [UsersService] })
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Injectable, NotFoundException, ConflictException } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { generateSuiteCode } from "../common/utils/suite-code.util";
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
@@ -39,6 +41,52 @@ export class UsersService {
|
||||
return user;
|
||||
}
|
||||
|
||||
/** Admin creates a user directly (no self-registration) */
|
||||
async create(dto: {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone?: string;
|
||||
role: string;
|
||||
}, tenantId: string): Promise<any> {
|
||||
const exists = await this.prisma.client.user.findFirst({
|
||||
where: { email: dto.email, tenantId },
|
||||
});
|
||||
if (exists) throw new ConflictException("Ya existe un usuario con ese email.");
|
||||
|
||||
const hash = await bcrypt.hash(dto.password, 12);
|
||||
|
||||
// Generate suite code for CLIENTE role
|
||||
let suiteCode: string | undefined;
|
||||
if (dto.role === "CLIENTE") {
|
||||
const count = await this.prisma.client.user.count({ where: { tenantId } });
|
||||
suiteCode = generateSuiteCode(count + 1);
|
||||
}
|
||||
|
||||
const user = await this.prisma.client.user.create({
|
||||
data: {
|
||||
tenantId,
|
||||
email: dto.email,
|
||||
passwordHash: hash,
|
||||
firstName: dto.firstName,
|
||||
lastName: dto.lastName,
|
||||
phone: dto.phone,
|
||||
role: dto.role as any,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (suiteCode) {
|
||||
await this.prisma.client.suite.create({
|
||||
data: { tenantId, userId: user.id, code: suiteCode },
|
||||
});
|
||||
}
|
||||
|
||||
const { passwordHash: _, ...safe } = user as any;
|
||||
return safe;
|
||||
}
|
||||
|
||||
async updateRole(id: string, role: string): Promise<any> {
|
||||
return this.prisma.client.user.update({ where: { id }, data: { role: role as any } });
|
||||
}
|
||||
|
||||
@@ -4,13 +4,24 @@ import { api } from "@/lib/api";
|
||||
|
||||
const ROLES = ["CLIENTE","OPERADOR_BODEGA","AGENTE_ADUANERO","ADMIN_EMPRESA","SUPER_ADMIN","SOPORTE"];
|
||||
|
||||
const BLANK_FORM = { email: "", password: "", firstName: "", lastName: "", phone: "", role: "CLIENTE" };
|
||||
|
||||
export default function UsuariosPage() {
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
const [updating, setUpdating] = useState<string | null>(null);
|
||||
|
||||
const load = (s?: string) => { setLoading(true); api.users.list(s).then(setUsers).catch(()=>{}).finally(()=>setLoading(false)); };
|
||||
// Create modal
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [form, setForm] = useState({ ...BLANK_FORM });
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [createError, setCreateError] = useState("");
|
||||
|
||||
const load = (s?: string) => {
|
||||
setLoading(true);
|
||||
api.users.list(s).then(setUsers).catch(() => {}).finally(() => setLoading(false));
|
||||
};
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const handleRoleChange = async (id: string, role: string) => {
|
||||
@@ -25,23 +36,62 @@ export default function UsuariosPage() {
|
||||
finally { setUpdating(null); }
|
||||
};
|
||||
|
||||
const handleCreate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setCreating(true); setCreateError("");
|
||||
try {
|
||||
await api.users.create({
|
||||
email: form.email,
|
||||
password: form.password,
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
phone: form.phone || undefined,
|
||||
role: form.role,
|
||||
});
|
||||
setShowModal(false);
|
||||
setForm({ ...BLANK_FORM });
|
||||
load();
|
||||
} catch (err: any) {
|
||||
setCreateError(err.message ?? "Error al crear usuario");
|
||||
} finally { setCreating(false); }
|
||||
};
|
||||
|
||||
const setF = (k: string) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) =>
|
||||
setForm(f => ({ ...f, [k]: e.target.value }));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6 flex justify-between items-center flex-wrap gap-4">
|
||||
<div><h1 className="dash-page-title">Usuarios</h1><p className="dash-page-subtitle">Gestión de cuentas y roles.</p></div>
|
||||
<div>
|
||||
<h1 className="dash-page-title">Usuarios</h1>
|
||||
<p className="dash-page-subtitle">Gestión de cuentas y roles.</p>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: ".75rem" }}>
|
||||
<input className="input" style={{ maxWidth: 260 }} placeholder="Buscar por nombre o email…"
|
||||
<input className="input" style={{ maxWidth: 240 }} placeholder="Buscar por nombre o email…"
|
||||
value={search} onChange={e => setSearch(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && load(search)} />
|
||||
<button className="btn btn-primary" onClick={() => load(search)}>Buscar</button>
|
||||
<button className="btn btn-outline" onClick={() => load(search)}>Buscar</button>
|
||||
<button className="btn btn-primary" onClick={() => { setCreateError(""); setForm({ ...BLANK_FORM }); setShowModal(true); }}>
|
||||
+ Nuevo usuario
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="table-wrap" style={{ borderRadius: "var(--radius-lg)", border: "none" }}>
|
||||
{loading ? <div style={{ padding: "3rem", textAlign: "center" }}><div className="spinner mx-auto" /></div> : (
|
||||
{loading ? (
|
||||
<div style={{ padding: "3rem", textAlign: "center" }}><div className="spinner mx-auto" /></div>
|
||||
) : users.length === 0 ? (
|
||||
<div style={{ padding: "3rem", textAlign: "center", color: "var(--gray-500)" }}>
|
||||
No se encontraron usuarios.
|
||||
</div>
|
||||
) : (
|
||||
<table>
|
||||
<thead><tr><th>Nombre</th><th>Email</th><th>Casillero</th><th>Rol</th><th>Estado</th><th>Acciones</th></tr></thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th><th>Email</th><th>Casillero</th><th>Rol</th><th>Estado</th><th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map(u => (
|
||||
<tr key={u.id}>
|
||||
@@ -55,7 +105,11 @@ export default function UsuariosPage() {
|
||||
{ROLES.map(r => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</td>
|
||||
<td><span className={`badge ${u.isActive ? "badge-green" : "badge-red"}`}>{u.isActive ? "Activo" : "Inactivo"}</span></td>
|
||||
<td>
|
||||
<span className={`badge ${u.isActive ? "badge-green" : "badge-red"}`}>
|
||||
{u.isActive ? "Activo" : "Inactivo"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className={`btn btn-sm ${u.isActive ? "btn-danger" : "btn-success"}`}
|
||||
@@ -72,6 +126,60 @@ export default function UsuariosPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create user modal */}
|
||||
{showModal && (
|
||||
<div style={{
|
||||
position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000,
|
||||
display: "flex", alignItems: "center", justifyContent: "center", padding: "1rem"
|
||||
}} onClick={e => { if (e.target === e.currentTarget) setShowModal(false); }}>
|
||||
<div className="card" style={{ width: "100%", maxWidth: 480, maxHeight: "90vh", overflowY: "auto" }}>
|
||||
<div className="card-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span className="font-semibold">Crear nuevo usuario</span>
|
||||
<button className="btn btn-ghost btn-sm" onClick={() => setShowModal(false)}>✕</button>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{createError && <div className="alert alert-error mb-4">{createError}</div>}
|
||||
<form onSubmit={handleCreate} style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: ".75rem" }}>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Nombre *</label>
|
||||
<input className="form-input" required value={form.firstName} onChange={setF("firstName")} placeholder="Juan" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Apellido *</label>
|
||||
<input className="form-input" required value={form.lastName} onChange={setF("lastName")} placeholder="Pérez" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Email *</label>
|
||||
<input className="form-input" required type="email" value={form.email} onChange={setF("email")} placeholder="juan@ejemplo.com" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Contraseña inicial *</label>
|
||||
<input className="form-input" required type="password" minLength={8} value={form.password} onChange={setF("password")} placeholder="Mínimo 8 caracteres" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Teléfono</label>
|
||||
<input className="form-input" type="tel" value={form.phone} onChange={setF("phone")} placeholder="+593 99 000 0000" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Rol *</label>
|
||||
<select className="form-input" value={form.role} onChange={setF("role")}>
|
||||
{ROLES.map(r => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: ".75rem", justifyContent: "flex-end", marginTop: ".5rem" }}>
|
||||
<button type="button" className="btn btn-outline" onClick={() => setShowModal(false)}>Cancelar</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={creating}>
|
||||
{creating ? "Creando…" : "Crear usuario"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,10 +37,12 @@ const STATUS_BADGE: Record<string, string> = {
|
||||
const PAYABLE_STATUSES = ["VERIFICADO", "DECLARACION_ADUANERA"];
|
||||
|
||||
export default function MisPaquetesPage() {
|
||||
const [packages, setPackages] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [selected, setSelected] = useState<any | null>(null);
|
||||
const [packages, setPackages] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [detail, setDetail] = useState<Record<string, any>>({});
|
||||
const [loadingId, setLoadingId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
api.packages.list()
|
||||
@@ -49,6 +51,20 @@ export default function MisPaquetesPage() {
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
// Expand: fetch full package (includes complete statusHistory) on first open
|
||||
const handleExpand = async (p: any) => {
|
||||
if (selected === p.id) { setSelected(null); return; }
|
||||
setSelected(p.id);
|
||||
if (detail[p.id]) return; // already loaded
|
||||
setLoadingId(p.id);
|
||||
try {
|
||||
const full = await api.packages.get(p.id);
|
||||
setDetail(d => ({ ...d, [p.id]: full }));
|
||||
} catch {
|
||||
setDetail(d => ({ ...d, [p.id]: p })); // fallback to list data
|
||||
} finally { setLoadingId(null); }
|
||||
};
|
||||
|
||||
const filtered = packages.filter(p =>
|
||||
!filter ||
|
||||
p.trackingId?.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
@@ -87,7 +103,7 @@ export default function MisPaquetesPage() {
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
||||
{filtered.map(p => (
|
||||
<div key={p.id} className="card" style={{ cursor: "pointer" }} onClick={() => setSelected(p === selected ? null : p)}>
|
||||
<div key={p.id} className="card" style={{ cursor: "pointer" }} onClick={() => handleExpand(p)}>
|
||||
<div className="card-body" style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "1rem" }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700, fontSize: "1rem", color: "var(--primary)" }}>{p.trackingId}</div>
|
||||
@@ -122,12 +138,14 @@ export default function MisPaquetesPage() {
|
||||
</div>
|
||||
|
||||
{/* Detalle expandido */}
|
||||
{selected?.id === p.id && (
|
||||
{selected === p.id && (
|
||||
<div className="card-footer" style={{ borderTop: "1px solid var(--gray-100)", paddingTop: "1rem" }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: ".75rem", fontSize: ".9rem" }}>Historial de estados</div>
|
||||
{p.statusHistory?.length ? (
|
||||
{loadingId === p.id ? (
|
||||
<div style={{ display: "flex", justifyContent: "center", padding: "1rem" }}><div className="spinner" /></div>
|
||||
) : (detail[p.id]?.statusHistory ?? p.statusHistory)?.length ? (
|
||||
<div className="timeline">
|
||||
{p.statusHistory.map((h: any, i: number) => (
|
||||
{(detail[p.id]?.statusHistory ?? p.statusHistory).map((h: any, i: number) => (
|
||||
<div key={h.id} className="timeline-item">
|
||||
<div className={`timeline-dot ${i === 0 ? "current" : "active"}`} />
|
||||
<div>
|
||||
|
||||
@@ -2,65 +2,96 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { api, getUser } from "@/lib/api";
|
||||
import { Timestamp } from "@/app/_components/timestamp";
|
||||
|
||||
// §08 — 11 estados oficiales del ciclo de vida
|
||||
const STATUS_LABEL: Record<string, string> = {
|
||||
RECIBIDO_EN_NJ: "Recibido en NJ",
|
||||
EN_PROCESO: "En proceso",
|
||||
EN_CAMINO_A_ECUADOR: "En camino a Ecuador",
|
||||
EN_ADUANA: "En aduana",
|
||||
EN_BODEGA_EC: "En bodega EC",
|
||||
LISTO_PARA_RETIRO: "Listo para retiro",
|
||||
REGISTRADO: "Registrado",
|
||||
EN_TRANSITO_BODEGA: "En tránsito a NJ",
|
||||
RECIBIDO_BODEGA: "Recibido en NJ",
|
||||
EN_VERIFICACION: "En verificación",
|
||||
VERIFICADO: "Verificado",
|
||||
DECLARACION_ADUANERA: "Declaración aduanera",
|
||||
EN_TRANSITO_ECUADOR: "En tránsito a Ecuador",
|
||||
EN_ADUANA_ECUADOR: "En aduana Ecuador",
|
||||
LISTO_ENTREGA: "Listo para entrega",
|
||||
ENTREGADO: "Entregado",
|
||||
RETENIDO_ADUANA: "Retenido en aduana",
|
||||
DEVUELTO: "Devuelto",
|
||||
PERDIDO: "Perdido",
|
||||
CANCELADO: "Cancelado",
|
||||
INCIDENCIA: "Incidencia",
|
||||
};
|
||||
|
||||
const STATUS_BADGE: Record<string, string> = {
|
||||
RECIBIDO_EN_NJ: "badge-blue", EN_PROCESO: "badge-yellow",
|
||||
EN_CAMINO_A_ECUADOR: "badge-orange", EN_ADUANA: "badge-yellow",
|
||||
EN_BODEGA_EC: "badge-blue", LISTO_PARA_RETIRO: "badge-green",
|
||||
ENTREGADO: "badge-green", RETENIDO_ADUANA: "badge-red",
|
||||
DEVUELTO: "badge-red", PERDIDO: "badge-red", CANCELADO: "badge-gray",
|
||||
REGISTRADO: "badge-gray",
|
||||
EN_TRANSITO_BODEGA: "badge-yellow",
|
||||
RECIBIDO_BODEGA: "badge-blue",
|
||||
EN_VERIFICACION: "badge-yellow",
|
||||
VERIFICADO: "badge-green",
|
||||
DECLARACION_ADUANERA: "badge-blue",
|
||||
EN_TRANSITO_ECUADOR: "badge-orange",
|
||||
EN_ADUANA_ECUADOR: "badge-red",
|
||||
LISTO_ENTREGA: "badge-green",
|
||||
ENTREGADO: "badge-green",
|
||||
INCIDENCIA: "badge-red",
|
||||
};
|
||||
|
||||
const STATUS_COLOR: Record<string, string> = {
|
||||
REGISTRADO: "#6B7280", EN_TRANSITO_BODEGA: "#F59E0B", RECIBIDO_BODEGA: "#3B82F6",
|
||||
EN_VERIFICACION: "#8B5CF6", VERIFICADO: "#10B981", DECLARACION_ADUANERA: "#0057FF",
|
||||
EN_TRANSITO_ECUADOR: "#F97316", EN_ADUANA_ECUADOR: "#EF4444",
|
||||
LISTO_ENTREGA: "#84CC16", ENTREGADO: "#10B981", INCIDENCIA: "#EF4444",
|
||||
};
|
||||
|
||||
// §08 ordered pipeline for progress bar
|
||||
const STATUS_ORDER = [
|
||||
"REGISTRADO","EN_TRANSITO_BODEGA","RECIBIDO_BODEGA","EN_VERIFICACION",
|
||||
"VERIFICADO","DECLARACION_ADUANERA","EN_TRANSITO_ECUADOR","EN_ADUANA_ECUADOR",
|
||||
"LISTO_ENTREGA","ENTREGADO",
|
||||
];
|
||||
|
||||
export default function PortalDashboard() {
|
||||
const user = getUser();
|
||||
const [packages, setPackages] = useState<any[]>([]);
|
||||
const [packages, setPackages] = useState<any[]>([]);
|
||||
const [notifications, setNotifications] = useState<any[]>([]);
|
||||
const [suite, setSuite] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [suite, setSuite] = useState<any>(null);
|
||||
const [preAlerts, setPreAlerts] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.packages.list(),
|
||||
api.auth.me(),
|
||||
]).then(([pkgs, me]) => {
|
||||
setPackages(pkgs.slice(0, 5));
|
||||
setNotifications([]);
|
||||
api.preAlerts.list().catch(() => []),
|
||||
api.notifications.list(10).catch(() => []),
|
||||
]).then(([pkgs, me, alerts, notifs]) => {
|
||||
setPackages(pkgs);
|
||||
setSuite(me.suite);
|
||||
setPreAlerts(alerts);
|
||||
setNotifications(notifs);
|
||||
}).catch(() => {}).finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading) return <div className="flex justify-center py-16"><div className="spinner" /></div>;
|
||||
|
||||
const active = packages.filter(p => !["ENTREGADO","CANCELADO","DEVUELTO"].includes(p.status)).length;
|
||||
const active = packages.filter(p => !["ENTREGADO","INCIDENCIA"].includes(p.status)).length;
|
||||
const delivered = packages.filter(p => p.status === "ENTREGADO").length;
|
||||
const pending = preAlerts.filter(a => a.status === "PENDIENTE").length;
|
||||
|
||||
// Last 3 active packages
|
||||
const recent = packages.slice(0, 5);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6">
|
||||
<h1 className="dash-page-title">Bienvenido, {user?.firstName} 👋</h1>
|
||||
<p className="dash-page-subtitle">Gestiona tus envíos y tu casillero en NJ.</p>
|
||||
<p className="dash-page-subtitle">Gestiona tus envíos desde New Jersey hasta Ecuador.</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
{/* KPIs */}
|
||||
<div className="grid-4" style={{ marginBottom: "2rem" }}>
|
||||
{[
|
||||
{ label: "Paquetes activos", value: active, color: "var(--primary)" },
|
||||
{ label: "Entregados", value: delivered, color: "var(--green)" },
|
||||
{ label: "Pre-alertas", value: "—", color: "var(--yellow)" },
|
||||
{ label: "Mi casillero", value: suite?.code ?? "—", color: "var(--accent)" },
|
||||
{ label: "Paquetes activos", value: active, color: "var(--primary)" },
|
||||
{ label: "Entregados", value: delivered, color: "var(--green)" },
|
||||
{ label: "Pre-alertas", value: pending, color: "var(--yellow)" },
|
||||
{ label: "Mi casillero", value: suite?.code ?? "—", color: "var(--accent)" },
|
||||
].map(s => (
|
||||
<div key={s.label} className="stat-card">
|
||||
<div className="stat-value" style={{ color: s.color }}>{s.value}</div>
|
||||
@@ -69,29 +100,63 @@ export default function PortalDashboard() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid-2" style={{ gap: "1.5rem" }}>
|
||||
{/* Últimos paquetes */}
|
||||
{/* Suite address callout */}
|
||||
{suite && (
|
||||
<div style={{ background: "var(--blue-50)", border: "1px solid var(--blue-200)", borderRadius: 10, padding: "1rem 1.25rem", marginBottom: "1.5rem", display: "flex", gap: "1rem", alignItems: "center", flexWrap: "wrap" }}>
|
||||
<div style={{ fontSize: "1.5rem" }}>📦</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: ".9rem", color: "var(--primary)", marginBottom: ".25rem" }}>Tu dirección de envío en NJ</div>
|
||||
<div style={{ fontFamily: "monospace", fontSize: ".85rem", color: "var(--gray-700)" }}>
|
||||
150 N Day St, <strong>{suite.code}</strong>, City of Orange, NJ 07050, EE.UU.
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/portal/mi-casillero" className="btn btn-outline btn-sm">Ver casillero →</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem" }}>
|
||||
{/* Paquetes recientes con mini-barra de progreso */}
|
||||
<div className="card">
|
||||
<div className="card-header flex justify-between items-center">
|
||||
<div className="card-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span className="font-semibold">Paquetes recientes</span>
|
||||
<Link href="/portal/mis-paquetes" className="btn btn-ghost btn-sm text-primary">Ver todos →</Link>
|
||||
</div>
|
||||
<div className="card-body" style={{ padding: 0 }}>
|
||||
{packages.length === 0 ? (
|
||||
<p style={{ padding: "1.5rem", color: "var(--gray-500)", textAlign: "center" }}>Aún no tienes paquetes.</p>
|
||||
{recent.length === 0 ? (
|
||||
<div style={{ padding: "2rem", textAlign: "center" }}>
|
||||
<div style={{ fontSize: "2rem", marginBottom: ".5rem" }}>📭</div>
|
||||
<p style={{ color: "var(--gray-500)", fontSize: ".9rem" }}>Aún no tienes paquetes.</p>
|
||||
<Link href="/portal/pre-alerta" className="btn btn-primary btn-sm" style={{ marginTop: ".75rem" }}>
|
||||
Registrar primera compra
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="table-wrap" style={{ border: "none", borderRadius: 0 }}>
|
||||
<table>
|
||||
<tbody>
|
||||
{packages.map(p => (
|
||||
<tr key={p.id}>
|
||||
<td><Link href={`/portal/mis-paquetes?id=${p.id}`} style={{ color: "var(--primary)", fontWeight: 600 }}>{p.trackingId}</Link></td>
|
||||
<td className="text-sm text-muted truncate" style={{ maxWidth: 120 }}>{p.description ?? "—"}</td>
|
||||
<td><span className={`badge ${STATUS_BADGE[p.status] ?? "badge-gray"}`}>{STATUS_LABEL[p.status] ?? p.status}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
|
||||
{recent.map((p, i) => {
|
||||
const step = STATUS_ORDER.indexOf(p.status);
|
||||
const pct = step >= 0 ? Math.round(((step + 1) / STATUS_ORDER.length) * 100) : 0;
|
||||
return (
|
||||
<div key={p.id} style={{ padding: "1rem 1.25rem", borderBottom: i < recent.length - 1 ? "1px solid var(--gray-100)" : "none" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: ".4rem" }}>
|
||||
<div>
|
||||
<Link href={`/portal/mis-paquetes?id=${p.id}`} style={{ color: "var(--primary)", fontWeight: 700, fontSize: ".9rem" }}>
|
||||
{p.trackingId}
|
||||
</Link>
|
||||
<div style={{ fontSize: ".78rem", color: "var(--gray-500)", marginTop: ".1rem", maxWidth: 200, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{p.description ?? "Sin descripción"}
|
||||
</div>
|
||||
</div>
|
||||
<span className={`badge ${STATUS_BADGE[p.status] ?? "badge-gray"}`} style={{ fontSize: ".7rem" }}>
|
||||
{STATUS_LABEL[p.status] ?? p.status}
|
||||
</span>
|
||||
</div>
|
||||
{/* Progress bar */}
|
||||
<div style={{ height: 4, background: "var(--gray-100)", borderRadius: 2, overflow: "hidden" }}>
|
||||
<div style={{ height: "100%", width: `${pct}%`, background: STATUS_COLOR[p.status] ?? "var(--primary)", borderRadius: 2, transition: "width .3s" }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -99,19 +164,30 @@ export default function PortalDashboard() {
|
||||
|
||||
{/* Notificaciones */}
|
||||
<div className="card">
|
||||
<div className="card-header flex justify-between items-center">
|
||||
<span className="font-semibold">Notificaciones</span>
|
||||
<span className="badge badge-red">{notifications.length}</span>
|
||||
<div className="card-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span className="font-semibold">Notificaciones recientes</span>
|
||||
{notifications.length > 0 && <span className="badge badge-red">{notifications.length}</span>}
|
||||
</div>
|
||||
<div className="card-body" style={{ padding: 0 }}>
|
||||
{notifications.length === 0 ? (
|
||||
<p style={{ padding: "1.5rem", color: "var(--gray-500)", textAlign: "center" }}>Sin notificaciones nuevas.</p>
|
||||
<div style={{ padding: "2rem", textAlign: "center", color: "var(--gray-500)", fontSize: ".9rem" }}>
|
||||
Sin notificaciones nuevas.
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{notifications.map(n => (
|
||||
<div key={n.id} style={{ padding: "1rem 1.5rem", borderBottom: "1px solid var(--gray-100)" }}>
|
||||
<div style={{ fontWeight: 600, fontSize: ".9rem" }}>{n.title}</div>
|
||||
<div style={{ fontSize: ".8rem", color: "var(--gray-500)", marginTop: ".2rem" }}>{n.body}</div>
|
||||
{notifications.map((n, i) => (
|
||||
<div key={n.id} style={{ padding: ".875rem 1.25rem", borderBottom: i < notifications.length - 1 ? "1px solid var(--gray-100)" : "none" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: ".5rem" }}>
|
||||
<div style={{ fontSize: ".82rem", color: "var(--gray-700)", lineHeight: 1.5, flex: 1 }}>
|
||||
{n.subject || n.body?.slice(0, 100)}
|
||||
</div>
|
||||
<span style={{ fontSize: ".7rem", color: "var(--gray-400)", flexShrink: 0 }}>
|
||||
<Timestamp value={n.createdAt} dateOnly />
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: ".75rem", color: "var(--gray-400)", marginTop: ".2rem" }}>
|
||||
{n.channel === "EMAIL" ? "✉️" : n.channel === "WHATSAPP" ? "💬" : "🔔"} {n.channel}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -119,6 +195,22 @@ export default function PortalDashboard() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Accesos rápidos */}
|
||||
<div style={{ marginTop: "1.5rem", display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: "1rem" }}>
|
||||
{[
|
||||
{ href: "/portal/pre-alerta", icon: "📄", label: "Pre-alerta", desc: "Avisa qué paquete esperas" },
|
||||
{ href: "/portal/calculadora", icon: "💰", label: "Calculadora", desc: "Estima el costo de envío" },
|
||||
{ href: "/portal/consolidacion", icon: "📦", label: "Consolidar", desc: "Agrupar paquetes" },
|
||||
{ href: "/portal/perfil", icon: "👤", label: "Mi perfil", desc: "Datos y seguridad" },
|
||||
].map(a => (
|
||||
<Link key={a.href} href={a.href} className="card" style={{ padding: "1.25rem", textDecoration: "none", display: "block", transition: "box-shadow .2s" }}>
|
||||
<div style={{ fontSize: "1.75rem", marginBottom: ".5rem" }}>{a.icon}</div>
|
||||
<div style={{ fontWeight: 700, fontSize: ".9rem", marginBottom: ".2rem" }}>{a.label}</div>
|
||||
<div style={{ fontSize: ".78rem", color: "var(--gray-500)" }}>{a.desc}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,16 +63,31 @@ export default function PreAlertaPage() {
|
||||
e.preventDefault();
|
||||
setSubmitting(true); setError(""); setSuccess("");
|
||||
try {
|
||||
await api.preAlerts.create({
|
||||
const created = await api.preAlerts.create({
|
||||
store: form.store,
|
||||
vendorTracking: form.vendorTracking || undefined,
|
||||
description: form.description,
|
||||
declaredValue: parseFloat(form.declaredValue),
|
||||
estimatedArrival: form.estimatedArrival || undefined,
|
||||
});
|
||||
setSuccess("✅ Pre-alerta registrada exitosamente.");
|
||||
// Upload invoice file if the user selected one
|
||||
if (invoice && created?.id) {
|
||||
try {
|
||||
await api.preAlerts.uploadInvoice(created.id, invoice);
|
||||
} catch {
|
||||
// Non-fatal: alert was created, just notify about the upload failure
|
||||
setSuccess("✅ Pre-alerta registrada. No se pudo subir la factura, inténtalo de nuevo.");
|
||||
setForm({ store: "", vendorTracking: "", description: "", declaredValue: "", estimatedArrival: "" });
|
||||
setUrlInput(""); setInvoice(null);
|
||||
if (invoiceRef.current) invoiceRef.current.value = "";
|
||||
load();
|
||||
return;
|
||||
}
|
||||
}
|
||||
setSuccess("✅ Pre-alerta registrada exitosamente." + (invoice ? " Factura adjuntada." : ""));
|
||||
setForm({ store: "", vendorTracking: "", description: "", declaredValue: "", estimatedArrival: "" });
|
||||
setUrlInput(""); setInvoice(null);
|
||||
if (invoiceRef.current) invoiceRef.current.value = "";
|
||||
load();
|
||||
} catch (err: any) { setError(err.message ?? "Error al registrar"); }
|
||||
finally { setSubmitting(false); }
|
||||
@@ -168,10 +183,10 @@ export default function PreAlertaPage() {
|
||||
onChange={e => setInvoice(e.target.files?.[0] ?? null)}
|
||||
style={{ fontSize: ".85rem" }} />
|
||||
{invoice && (
|
||||
<p style={{ fontSize: ".78rem", color: "var(--green)", marginTop: ".25rem" }}>📎 {invoice.name}</p>
|
||||
)}
|
||||
<p style={{ fontSize: ".78rem", color: "var(--green)", marginTop: ".25rem" }}>📎 {invoice.name}</p>
|
||||
)}
|
||||
<p style={{ fontSize: ".75rem", color: "var(--gray-400)", marginTop: ".25rem" }}>
|
||||
Próximamente: la factura se enviará automáticamente a la bodega.
|
||||
Máx. 10 MB — PDF, JPG, PNG o WEBP.
|
||||
</p>
|
||||
</div>
|
||||
<button type="submit" className="btn btn-primary" disabled={submitting}>
|
||||
|
||||
@@ -116,10 +116,22 @@ export const api = {
|
||||
create: (body: any) => request<any>("/pre-alerts", { method: "POST", body: JSON.stringify(body) }),
|
||||
delete: (id: string) => request<any>(`/pre-alerts/${id}`, { method: "DELETE" }),
|
||||
updateStatus: (id: string, body: any) => request<any>(`/pre-alerts/${id}/status`, { method: "PATCH", body: JSON.stringify(body) }),
|
||||
uploadInvoice: (id: string, file: File) => {
|
||||
const token = getToken();
|
||||
const fd = new FormData();
|
||||
fd.append("invoice", file);
|
||||
return fetch(`${API_BASE}/pre-alerts/${id}/invoice`, {
|
||||
method: "POST",
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: fd,
|
||||
}).then(r => { if (!r.ok) throw new Error("Upload failed"); return r.json(); });
|
||||
},
|
||||
},
|
||||
users: {
|
||||
list: (search?: string) => request<any[]>("/users" + (search ? `?search=${search}` : "")),
|
||||
get: (id: string) => request<any>(`/users/${id}`),
|
||||
create: (body: { email: string; password: string; firstName: string; lastName: string; phone?: string; role: string }) =>
|
||||
request<any>("/users", { method: "POST", body: JSON.stringify(body) }),
|
||||
updateRole: (id: string, role: string) => request<any>(`/users/${id}/role`, { method: "PATCH", body: JSON.stringify({ role }) }),
|
||||
setActive: (id: string, isActive: boolean) => request<any>(`/users/${id}/active`, { method: "PATCH", body: JSON.stringify({ isActive }) }),
|
||||
},
|
||||
@@ -165,6 +177,9 @@ export const api = {
|
||||
update: (id: string, body: { body: string; subject?: string; isActive?: boolean }) =>
|
||||
request<any>(`/notification-templates/${id}`, { method: "PUT", body: JSON.stringify(body) }),
|
||||
},
|
||||
notifications: {
|
||||
list: (limit = 20) => request<any[]>(`/notifications?limit=${limit}`),
|
||||
},
|
||||
payments: {
|
||||
list: (status?: string) => request<any[]>(`/payments${status ? `?status=${status}` : ""}`),
|
||||
packageDetail: (packageId: string) => request<any>(`/payments/package/${packageId}`),
|
||||
|
||||
Generated
+565
@@ -17,6 +17,15 @@ importers:
|
||||
|
||||
apps/api:
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.1058.0
|
||||
version: 3.1058.0
|
||||
'@aws-sdk/lib-storage':
|
||||
specifier: ^3.1058.0
|
||||
version: 3.1058.0(@aws-sdk/client-s3@3.1058.0)
|
||||
'@aws-sdk/s3-request-presigner':
|
||||
specifier: ^3.1058.0
|
||||
version: 3.1058.0
|
||||
'@moraworld/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/database
|
||||
@@ -179,6 +188,135 @@ packages:
|
||||
resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==}
|
||||
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/crc32c@5.2.0':
|
||||
resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
|
||||
|
||||
'@aws-crypto/sha1-browser@5.2.0':
|
||||
resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
|
||||
|
||||
'@aws-sdk/client-s3@3.1058.0':
|
||||
resolution: {integrity: sha512-AfED3hhaBZ121NuiBImgnlF98kQRMk6hGPMGfj/Oo1hSaoMFRzM+N4nlICCasUSM2R8QaIRZRYGpZ3fy0ilGZQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/core@3.974.15':
|
||||
resolution: {integrity: sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/crc64-nvme@3.972.9':
|
||||
resolution: {integrity: sha512-P+QGozmXn2mZZI7sDgk+aUm+RTI61MPSFB+Ir2vjEjEbEsE4e7hYtzrDvAUxZy9ko81h53e11+F/GYlvwDkaOQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.972.41':
|
||||
resolution: {integrity: sha512-n1EbJ98yvPWWdHZZv8bRBMqqDQJrtgtxyJ4xLy2Uqrh25BCOZQ7nnS1CsFXvuH8r0b0KVHDZEGEH5FxmEMP8jg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.972.43':
|
||||
resolution: {integrity: sha512-TT76RN1NkI9WoyZqCNxOw6/WBMF7pYOTJcXbMokNFU+euSG40Kaf/t/FhDACVZWP+43wEM6ZynIPIkzS1wR1iA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.972.46':
|
||||
resolution: {integrity: sha512-hvcgcwOiS0nb2XFb5Op1Pz/vYaWz5K8kKullziGpdNRuG0NwzRXseuPt2CoBqknHGaSPVesu1aOn2OcctEYdCA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.972.45':
|
||||
resolution: {integrity: sha512-MZQv4SNjByk1iOKmrqmzcUF/uCB05wjvEHyXKxmGQTUANTIVayX6HPUF0bzkWLvtnkH7sAn9kUCfkXbSpj9sDA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.972.48':
|
||||
resolution: {integrity: sha512-QIbtJP0olSLZ2ImEu636pP+7JJbPfaL3xSJIFXhu472CWuondCc4bGOa8OeyhOFet8z4H1D/ZFKXc39FboWwYA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.972.41':
|
||||
resolution: {integrity: sha512-7I/n1zkysouLOWvkEhjNEP4vMnD2v4kzzr3/3QBdrripEpn7ap1/I5DF3Hou1SUqkKWo1f3oPGMyFAA1FAMvsQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.972.45':
|
||||
resolution: {integrity: sha512-oHgbz/eFD8IKiksqDsz9ZMU4A59BpQq4QwJedBnGD80ZqYcHPPHZBwjBnxLVkB7iRVVHWpDclR8yWdD2PkQIUA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.972.45':
|
||||
resolution: {integrity: sha512-CDhzKdb2onv5bpnjn/acgdNmJOQthPDLsPizU7rZflsEcgMMp8Mlri+U5hdxf8ldvZJpvM3vLU6D56vfJm5AMQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/lib-storage@3.1058.0':
|
||||
resolution: {integrity: sha512-/uGg/qXRqDRABWYoahwzx1aCUPzjKDSfAoWSCRtiHU8mR3piDFNj7u5eYluyancllSB0oJAh3F0+hFmWdr7CxQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/client-s3': ^3.1058.0
|
||||
|
||||
'@aws-sdk/middleware-bucket-endpoint@3.972.17':
|
||||
resolution: {integrity: sha512-lbDmWuHenc+kiwCNrxz4MyN6nkxCWyTXPIWuspJN0ibziu+8CXci7vI1bK9MAkwy8cwJOEXNu0gBM5S0uTGRIg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-expect-continue@3.972.14':
|
||||
resolution: {integrity: sha512-3TNFEVGO4sWZj9TEXOCZLzGEctXHnaO4fk2EQ8KVaboTbwHmEPEQrm17Xb9koImUIXEw0sgi2xtHjg7LuTS3rA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.974.23':
|
||||
resolution: {integrity: sha512-4nPKARo2lfKvQGUt2fPA5NlS/mEohckdxpuC9ecbjVfj7B7NFFYHeTg+Bf5BEQwdn3yRfUIzFiEkPp8Yuaw3wA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-location-constraint@3.972.11':
|
||||
resolution: {integrity: sha512-hkfspNUP4criAH6ton6BGKgnm5dZx+7bUOy1YqlTfejDeUPAM23D81q/IX+hdlS3KUsfwGz5ADTqZWKBEUpf4A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.972.44':
|
||||
resolution: {integrity: sha512-8HQsRg1NpX8vR4vNl1E8pyLnqZroq9VSL2vZQVSgBqp6wv6365LzYD08/c9FFh/9FTg7YRc7aTtEmXF0ir/pqg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-ssec@3.972.11':
|
||||
resolution: {integrity: sha512-7PQvGNhtveKlvVqNahqWx5yrwxP7ecwAoB1dYBf8eKwfo2tzzCbNnW+q2nO3N066ktQaB4iBQbDRWtizm+amoQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/nested-clients@3.997.13':
|
||||
resolution: {integrity: sha512-2pA6eyb5nSo/ZD2cayhOTEMoGQYgspq0RI05GDLkzQ3ajZ6isS6waV6E92Am/hz4LIlLUTrbwPLurJ/fuiHvkg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/s3-request-presigner@3.1058.0':
|
||||
resolution: {integrity: sha512-IRgNfn8U3zfsZ0JkpmwjS59R/XyHMHxpuwW6HVuJhik+FsbClhNkujEO0w1WqJvXrF4FX+7qIAwUrvlwNvaZ7Q==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.30':
|
||||
resolution: {integrity: sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/token-providers@3.1056.0':
|
||||
resolution: {integrity: sha512-81duvlltQlsfn5K+o8zILcystBRdbT1G2JJYVCML5NZHBz4CL/zf+sAemCtBh/uh6RQUMyInGeZLQ7/8igZhbA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/types@3.973.9':
|
||||
resolution: {integrity: sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/xml-builder@3.972.26':
|
||||
resolution: {integrity: sha512-cDbrqvDS73whl6YAPSPq0U6whzG6UWI9PuWh0wrUuGoZexhWEqhdunbukV7iBoaWnFV1AODutM5hOD6rtn439g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4':
|
||||
resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1046,6 +1184,9 @@ packages:
|
||||
resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
'@nodable/entities@2.1.1':
|
||||
resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==}
|
||||
|
||||
'@nuxt/opencollective@0.4.1':
|
||||
resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'}
|
||||
@@ -1111,6 +1252,42 @@ packages:
|
||||
'@sinonjs/fake-timers@10.3.0':
|
||||
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
|
||||
|
||||
'@smithy/core@3.24.6':
|
||||
resolution: {integrity: sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/credential-provider-imds@4.3.7':
|
||||
resolution: {integrity: sha512-xj8gq/bjFABAh6qWPSDCYcY3kzQIm4b561C+YnHH4zGq8rOgzQ3Shk+JGlpUxSd41UGiO6FkLdUCtNX1FAeHgg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/fetch-http-handler@5.4.6':
|
||||
resolution: {integrity: sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/node-http-handler@4.7.6':
|
||||
resolution: {integrity: sha512-3fya8i7GrJilQouk4cZJKdy5k8MWQBpjfXrRNaXDedH8r779tr0jcxyH3+yoTmsluc2+vF4S343yFbnvu8ExDQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/signature-v4@5.4.6':
|
||||
resolution: {integrity: sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/types@4.14.3':
|
||||
resolution: {integrity: sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
@@ -1459,6 +1636,9 @@ packages:
|
||||
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bowser@2.14.1:
|
||||
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||
|
||||
brace-expansion@1.1.14:
|
||||
resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==}
|
||||
|
||||
@@ -1488,6 +1668,9 @@ packages:
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@5.6.0:
|
||||
resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
@@ -1877,6 +2060,13 @@ packages:
|
||||
fast-uri@3.1.2:
|
||||
resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
|
||||
|
||||
fast-xml-builder@1.2.0:
|
||||
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
|
||||
|
||||
fast-xml-parser@5.7.3:
|
||||
resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==}
|
||||
hasBin: true
|
||||
|
||||
fb-watchman@2.0.2:
|
||||
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
|
||||
|
||||
@@ -2578,6 +2768,10 @@ packages:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-expression-matcher@1.5.0:
|
||||
resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2857,6 +3051,9 @@ packages:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
stream-browserify@3.0.0:
|
||||
resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
|
||||
|
||||
streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -2901,6 +3098,9 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
strnum@2.3.0:
|
||||
resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==}
|
||||
|
||||
strtok3@10.3.5:
|
||||
resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3187,6 +3387,10 @@ packages:
|
||||
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
|
||||
xml-naming@0.1.0:
|
||||
resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3245,6 +3449,287 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/crc32c@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha1-browser@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/supports-web-crypto': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@aws-sdk/util-locate-window': 3.965.5
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-crypto/supports-web-crypto': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@aws-sdk/util-locate-window': 3.965.5
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/client-s3@3.1058.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha1-browser': 5.2.0
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/credential-provider-node': 3.972.48
|
||||
'@aws-sdk/middleware-bucket-endpoint': 3.972.17
|
||||
'@aws-sdk/middleware-expect-continue': 3.972.14
|
||||
'@aws-sdk/middleware-flexible-checksums': 3.974.23
|
||||
'@aws-sdk/middleware-location-constraint': 3.972.11
|
||||
'@aws-sdk/middleware-sdk-s3': 3.972.44
|
||||
'@aws-sdk/middleware-ssec': 3.972.11
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.30
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/fetch-http-handler': 5.4.6
|
||||
'@smithy/node-http-handler': 4.7.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/core@3.974.15':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@aws-sdk/xml-builder': 3.972.26
|
||||
'@aws/lambda-invoke-store': 0.2.4
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/signature-v4': 5.4.6
|
||||
'@smithy/types': 4.14.3
|
||||
bowser: 2.14.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/crc64-nvme@3.972.9':
|
||||
dependencies:
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.972.41':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.972.43':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/fetch-http-handler': 5.4.6
|
||||
'@smithy/node-http-handler': 4.7.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.972.46':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/credential-provider-env': 3.972.41
|
||||
'@aws-sdk/credential-provider-http': 3.972.43
|
||||
'@aws-sdk/credential-provider-login': 3.972.45
|
||||
'@aws-sdk/credential-provider-process': 3.972.41
|
||||
'@aws-sdk/credential-provider-sso': 3.972.45
|
||||
'@aws-sdk/credential-provider-web-identity': 3.972.45
|
||||
'@aws-sdk/nested-clients': 3.997.13
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/credential-provider-imds': 4.3.7
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.972.45':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/nested-clients': 3.997.13
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.972.48':
|
||||
dependencies:
|
||||
'@aws-sdk/credential-provider-env': 3.972.41
|
||||
'@aws-sdk/credential-provider-http': 3.972.43
|
||||
'@aws-sdk/credential-provider-ini': 3.972.46
|
||||
'@aws-sdk/credential-provider-process': 3.972.41
|
||||
'@aws-sdk/credential-provider-sso': 3.972.45
|
||||
'@aws-sdk/credential-provider-web-identity': 3.972.45
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/credential-provider-imds': 4.3.7
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.972.41':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.972.45':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/nested-clients': 3.997.13
|
||||
'@aws-sdk/token-providers': 3.1056.0
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.972.45':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/nested-clients': 3.997.13
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/lib-storage@3.1058.0(@aws-sdk/client-s3@3.1058.0)':
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3': 3.1058.0
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
buffer: 5.6.0
|
||||
events: 3.3.0
|
||||
stream-browserify: 3.0.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-bucket-endpoint@3.972.17':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-expect-continue@3.972.14':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.974.23':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@aws-crypto/crc32c': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/crc64-nvme': 3.972.9
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-location-constraint@3.972.11':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.972.44':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.30
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-ssec@3.972.11':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/nested-clients@3.997.13':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.30
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/fetch-http-handler': 5.4.6
|
||||
'@smithy/node-http-handler': 4.7.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/s3-request-presigner@3.1058.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.30
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.30':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/signature-v4': 5.4.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/token-providers@3.1056.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.15
|
||||
'@aws-sdk/nested-clients': 3.997.13
|
||||
'@aws-sdk/types': 3.973.9
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/types@3.973.9':
|
||||
dependencies:
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/xml-builder@3.972.26':
|
||||
dependencies:
|
||||
'@smithy/types': 4.14.3
|
||||
fast-xml-parser: 5.7.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4': {}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
@@ -4114,6 +4599,8 @@ snapshots:
|
||||
|
||||
'@noble/hashes@2.2.0': {}
|
||||
|
||||
'@nodable/entities@2.1.1': {}
|
||||
|
||||
'@nuxt/opencollective@0.4.1':
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
@@ -4192,6 +4679,54 @@ snapshots:
|
||||
dependencies:
|
||||
'@sinonjs/commons': 3.0.1
|
||||
|
||||
'@smithy/core@3.24.6':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/credential-provider-imds@4.3.7':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/fetch-http-handler@5.4.6':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/node-http-handler@4.7.6':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/signature-v4@5.4.6':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.6
|
||||
'@smithy/types': 4.14.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/types@4.14.3':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
dependencies:
|
||||
'@smithy/is-array-buffer': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
dependencies:
|
||||
'@smithy/util-buffer-from': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
@@ -4617,6 +5152,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bowser@2.14.1: {}
|
||||
|
||||
brace-expansion@1.1.14:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -4650,6 +5187,11 @@ snapshots:
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.6.0:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
@@ -5036,6 +5578,18 @@ snapshots:
|
||||
|
||||
fast-uri@3.1.2: {}
|
||||
|
||||
fast-xml-builder@1.2.0:
|
||||
dependencies:
|
||||
path-expression-matcher: 1.5.0
|
||||
xml-naming: 0.1.0
|
||||
|
||||
fast-xml-parser@5.7.3:
|
||||
dependencies:
|
||||
'@nodable/entities': 2.1.1
|
||||
fast-xml-builder: 1.2.0
|
||||
path-expression-matcher: 1.5.0
|
||||
strnum: 2.3.0
|
||||
|
||||
fb-watchman@2.0.2:
|
||||
dependencies:
|
||||
bser: 2.1.1
|
||||
@@ -5908,6 +6462,8 @@ snapshots:
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-expression-matcher@1.5.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
@@ -6213,6 +6769,11 @@ snapshots:
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
stream-browserify@3.0.0:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
||||
string-length@4.0.2:
|
||||
@@ -6246,6 +6807,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.19
|
||||
|
||||
strnum@2.3.0: {}
|
||||
|
||||
strtok3@10.3.5:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
@@ -6499,6 +7062,8 @@ snapshots:
|
||||
imurmurhash: 0.1.4
|
||||
signal-exit: 3.0.7
|
||||
|
||||
xml-naming@0.1.0: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
Reference in New Issue
Block a user