@mohasinac/appkit 2.7.53 → 2.7.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/jobs/core/auctionSettlement.js +6 -3
- package/dist/_internal/server/jobs/core/bundleStockSync.js +9 -7
- package/dist/_internal/server/jobs/core/onProductStockChange.d.ts +12 -3
- package/dist/_internal/server/jobs/core/onProductStockChange.js +12 -13
- package/dist/_internal/server/jobs/core/onProductWrite.js +1 -3
- package/dist/_internal/server/jobs/handlers/onProductStockChange.js +11 -2
- package/dist/_internal/server/jobs/runtime/adapters/firebase.js +5 -14
- package/dist/_internal/shared/actions/action-registry.js +827 -4
- package/dist/_internal/shared/features/events/schema.d.ts +4 -4
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +4 -3
- package/dist/constants/field-names.js +8 -8
- package/dist/errors/error-codes.d.ts +3 -0
- package/dist/errors/error-codes.js +3 -0
- package/dist/errors/messages.d.ts +3 -0
- package/dist/errors/messages.js +3 -0
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -3
- package/dist/features/admin/components/AdminBidsView.js +5 -3
- package/dist/features/admin/components/AdminBlogView.js +3 -2
- package/dist/features/admin/components/AdminBrandsView.js +2 -1
- package/dist/features/admin/components/AdminBundleEditorView.js +4 -5
- package/dist/features/admin/components/AdminBundlesView.js +3 -3
- package/dist/features/admin/components/AdminCarouselView.d.ts +2 -1
- package/dist/features/admin/components/AdminCarouselView.js +14 -5
- package/dist/features/admin/components/AdminCategoriesView.js +2 -1
- package/dist/features/admin/components/AdminContactView.d.ts +4 -1
- package/dist/features/admin/components/AdminContactView.js +27 -7
- package/dist/features/admin/components/AdminCouponsView.d.ts +3 -1
- package/dist/features/admin/components/AdminCouponsView.js +17 -3
- package/dist/features/admin/components/AdminFaqsView.d.ts +3 -1
- package/dist/features/admin/components/AdminFaqsView.js +18 -4
- package/dist/features/admin/components/AdminFeaturesView.js +2 -1
- package/dist/features/admin/components/AdminMediaView.js +3 -2
- package/dist/features/admin/components/AdminNavigationView.js +3 -2
- package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -1
- package/dist/features/admin/components/AdminNewsletterView.js +13 -4
- package/dist/features/admin/components/AdminNotificationsView.js +6 -4
- package/dist/features/admin/components/AdminOrdersView.js +4 -3
- package/dist/features/admin/components/AdminPayoutsView.js +2 -2
- package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -3
- package/dist/features/admin/components/AdminReviewsView.js +4 -3
- package/dist/features/admin/components/AdminScammersView.js +2 -1
- package/dist/features/admin/components/AdminSessionsView.js +5 -3
- package/dist/features/admin/components/AdminStoresView.js +2 -2
- package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +5 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +12 -3
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -1
- package/dist/features/admin/components/AdminTeamView.d.ts +2 -1
- package/dist/features/admin/components/AdminTeamView.js +13 -4
- package/dist/features/admin/components/AdminUsersView.js +2 -2
- package/dist/features/admin/types/product.types.d.ts +1 -1
- package/dist/features/admin/types/product.types.js +2 -3
- package/dist/features/auctions/actions/bid-actions.d.ts +9 -0
- package/dist/features/auctions/actions/bid-actions.js +61 -0
- package/dist/features/auctions/components/AuctionDetailPageView.d.ts +2 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +5 -4
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +3 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +32 -2
- package/dist/features/auctions/types/index.d.ts +1 -1
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -11
- package/dist/features/products/api/[id]/route.js +4 -8
- package/dist/features/products/api/route.js +1 -3
- package/dist/features/products/components/AuctionsIndexListing.js +3 -11
- package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -10
- package/dist/features/products/components/ProductFilters.d.ts +33 -9
- package/dist/features/products/components/ProductFilters.js +6 -29
- package/dist/features/products/components/ProductForm.js +2 -3
- package/dist/features/products/constants/action-defs.d.ts +86 -7
- package/dist/features/products/constants/action-defs.js +99 -7
- package/dist/features/products/constants/sieve.d.ts +162 -0
- package/dist/features/products/constants/sieve.js +95 -0
- package/dist/features/products/repository/products.repository.d.ts +5 -0
- package/dist/features/products/repository/products.repository.js +23 -3
- package/dist/features/products/schemas/firestore.d.ts +4 -4
- package/dist/features/products/schemas/firestore.js +2 -3
- package/dist/features/products/schemas/index.d.ts +6 -6
- package/dist/features/products/schemas/index.js +2 -6
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/seller/actions/offer-actions.js +2 -2
- package/dist/features/seller/components/QuickProductForm.d.ts +13 -0
- package/dist/features/seller/components/QuickProductForm.js +50 -0
- package/dist/features/seller/components/SellerAuctionsView.d.ts +4 -1
- package/dist/features/seller/components/SellerAuctionsView.js +37 -3
- package/dist/features/seller/components/SellerBidsView.js +2 -1
- package/dist/features/seller/components/SellerBundlesView.d.ts +3 -1
- package/dist/features/seller/components/SellerBundlesView.js +38 -5
- package/dist/features/seller/components/SellerClassifiedView.d.ts +3 -1
- package/dist/features/seller/components/SellerClassifiedView.js +37 -4
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +3 -1
- package/dist/features/seller/components/SellerDigitalCodesView.js +37 -4
- package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -1
- package/dist/features/seller/components/SellerGroupedListingsView.js +3 -2
- package/dist/features/seller/components/SellerLiveView.d.ts +3 -1
- package/dist/features/seller/components/SellerLiveView.js +37 -4
- package/dist/features/seller/components/SellerOffersPanel.js +2 -1
- package/dist/features/seller/components/SellerOffersView.d.ts +4 -1
- package/dist/features/seller/components/SellerOffersView.js +8 -3
- package/dist/features/seller/components/SellerPayoutsView.d.ts +3 -1
- package/dist/features/seller/components/SellerPayoutsView.js +7 -3
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -1
- package/dist/features/seller/components/SellerPreOrdersView.js +30 -7
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +30 -7
- package/dist/features/seller/components/SellerProductShell.js +12 -0
- package/dist/features/seller/components/SellerReviewsView.js +2 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +3 -2
- package/dist/features/seller/components/SellerStoreCategoriesView.js +6 -5
- package/dist/features/seller/components/SellerTemplatesView.js +4 -3
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +30 -16
- package/dist/features/wishlist/types/index.d.ts +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -4
- package/dist/seed/addresses-seed-data.d.ts +2 -18
- package/dist/seed/addresses-seed-data.js +137 -203
- package/dist/seed/bids-seed-data.d.ts +2 -8
- package/dist/seed/bids-seed-data.js +172 -648
- package/dist/seed/blog-posts-seed-data.d.ts +0 -5
- package/dist/seed/blog-posts-seed-data.js +341 -596
- package/dist/seed/carousel-slides-seed-data.d.ts +0 -5
- package/dist/seed/carousel-slides-seed-data.js +61 -192
- package/dist/seed/carousels-seed-data.js +18 -1
- package/dist/seed/cart-seed-data.d.ts +1 -18
- package/dist/seed/cart-seed-data.js +116 -293
- package/dist/seed/categories-seed-data.d.ts +0 -6
- package/dist/seed/categories-seed-data.js +1409 -1537
- package/dist/seed/claimed-coupons-seed-data.d.ts +0 -11
- package/dist/seed/claimed-coupons-seed-data.js +32 -29
- package/dist/seed/conversations-seed-data.d.ts +0 -6
- package/dist/seed/conversations-seed-data.js +132 -280
- package/dist/seed/coupon-usage-seed-data.d.ts +0 -16
- package/dist/seed/coupon-usage-seed-data.js +30 -44
- package/dist/seed/coupons-seed-data.d.ts +0 -6
- package/dist/seed/coupons-seed-data.js +141 -489
- package/dist/seed/events-seed-data.d.ts +0 -6
- package/dist/seed/events-seed-data.js +201 -379
- package/dist/seed/faq-seed-data.d.ts +0 -14
- package/dist/seed/faq-seed-data.js +609 -1184
- package/dist/seed/grouped-listings-seed-data.d.ts +119 -8
- package/dist/seed/grouped-listings-seed-data.js +148 -233
- package/dist/seed/history-seed-data.d.ts +0 -7
- package/dist/seed/history-seed-data.js +50 -54
- package/dist/seed/homepage-sections-seed-data.d.ts +0 -5
- package/dist/seed/homepage-sections-seed-data.js +83 -175
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/seed/manifest.js +2 -2
- package/dist/seed/notifications-seed-data.d.ts +0 -6
- package/dist/seed/notifications-seed-data.js +309 -196
- package/dist/seed/offers-seed-data.d.ts +0 -8
- package/dist/seed/offers-seed-data.js +118 -197
- package/dist/seed/orders-seed-data.d.ts +2 -8
- package/dist/seed/orders-seed-data.js +248 -1510
- package/dist/seed/payouts-seed-data.d.ts +0 -6
- package/dist/seed/payouts-seed-data.js +102 -441
- package/dist/seed/products-auctions-seed-data.d.ts +1 -14
- package/dist/seed/products-auctions-seed-data.js +612 -1084
- package/dist/seed/products-preorders-seed-data.d.ts +119 -14
- package/dist/seed/products-preorders-seed-data.js +154 -455
- package/dist/seed/products-standard-seed-data.d.ts +1 -15
- package/dist/seed/products-standard-seed-data.js +1355 -4128
- package/dist/seed/reviews-seed-data.d.ts +2 -8
- package/dist/seed/reviews-seed-data.js +99 -1182
- package/dist/seed/scammers-seed-data.d.ts +0 -15
- package/dist/seed/scammers-seed-data.js +36 -37
- package/dist/seed/sessions-seed-data.d.ts +0 -13
- package/dist/seed/sessions-seed-data.js +58 -335
- package/dist/seed/site-settings-seed-data.d.ts +0 -4
- package/dist/seed/site-settings-seed-data.js +61 -52
- package/dist/seed/store-addresses-seed-data.js +27 -151
- package/dist/seed/store-extensions-seed-data.d.ts +0 -7
- package/dist/seed/store-extensions-seed-data.js +147 -261
- package/dist/seed/stores-seed-data.d.ts +0 -5
- package/dist/seed/stores-seed-data.js +222 -184
- package/dist/seed/sub-listings-seed-data.d.ts +119 -0
- package/dist/seed/sub-listings-seed-data.js +447 -0
- package/dist/seed/support-tickets-seed-data.d.ts +0 -7
- package/dist/seed/support-tickets-seed-data.js +82 -75
- package/dist/seed/users-seed-data.d.ts +0 -5
- package/dist/seed/users-seed-data.js +279 -634
- package/dist/seed/wishlists-seed-data.d.ts +0 -9
- package/dist/seed/wishlists-seed-data.js +36 -52
- package/dist/seo/json-ld.js +6 -4
- package/dist/styles.css +8052 -3
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/search-tokens.d.ts +2 -0
- package/dist/utils/search-tokens.js +21 -0
- package/package.json +2 -2
- package/scripts/seed-cli.mjs +2 -2
|
@@ -2,7 +2,7 @@ import { increment, serverTimestamp } from "../../../contracts/field-ops";
|
|
|
2
2
|
import { DatabaseError } from "../../../errors";
|
|
3
3
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
4
4
|
import { cacheManager } from "../../../core";
|
|
5
|
-
import { generateUniqueId, slugify } from "../../../utils";
|
|
5
|
+
import { generateUniqueId, slugify, buildSearchTokens, tokenizeQuery } from "../../../utils";
|
|
6
6
|
import { PRODUCT_COLLECTION, ProductStatusValues, } from "../schemas";
|
|
7
7
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
8
8
|
/**
|
|
@@ -35,6 +35,9 @@ function buildListingKindClause(kind, inverted) {
|
|
|
35
35
|
const op = inverted ? "!=" : "==";
|
|
36
36
|
return `${PRODUCT_FIELDS.LISTING_TYPE}${op}${canonical}`;
|
|
37
37
|
}
|
|
38
|
+
function buildProductSearchTokens(p) {
|
|
39
|
+
return buildSearchTokens(p.title, p.description, p.brand, p.brandSlug, p.categoryNames, p.tags, p.features, p.condition, p.card?.setName, p.card?.cardNumber, p.grading?.service, p.specifications?.map((s) => `${s.name} ${s.value}`));
|
|
40
|
+
}
|
|
38
41
|
export class ProductRepository extends BaseRepository {
|
|
39
42
|
constructor() {
|
|
40
43
|
super(PRODUCT_COLLECTION);
|
|
@@ -80,7 +83,12 @@ export class ProductRepository extends BaseRepository {
|
|
|
80
83
|
}
|
|
81
84
|
async update(id, data) {
|
|
82
85
|
this.cacheInvalidateForId(id);
|
|
83
|
-
const
|
|
86
|
+
const current = await super.findById(id);
|
|
87
|
+
const merged = { ...current, ...data };
|
|
88
|
+
const updated = await super.update(id, {
|
|
89
|
+
...data,
|
|
90
|
+
searchTokens: buildProductSearchTokens(merged),
|
|
91
|
+
});
|
|
84
92
|
this.cacheSet(updated);
|
|
85
93
|
return updated;
|
|
86
94
|
}
|
|
@@ -100,6 +108,7 @@ export class ProductRepository extends BaseRepository {
|
|
|
100
108
|
...input,
|
|
101
109
|
slug: id,
|
|
102
110
|
availableQuantity: input.stockQuantity,
|
|
111
|
+
searchTokens: buildProductSearchTokens(input),
|
|
103
112
|
createdAt: new Date(),
|
|
104
113
|
updatedAt: new Date(),
|
|
105
114
|
};
|
|
@@ -320,7 +329,17 @@ export class ProductRepository extends BaseRepository {
|
|
|
320
329
|
if (opts?.storeId) {
|
|
321
330
|
baseQuery = baseQuery.where(PRODUCT_FIELDS.STORE_ID, "==", opts.storeId);
|
|
322
331
|
}
|
|
323
|
-
|
|
332
|
+
const searchTokens = tokenizeQuery(opts?.search ?? "");
|
|
333
|
+
if (searchTokens.length > 0 && opts?.categoriesIn && opts.categoriesIn.length > 0) {
|
|
334
|
+
// Firestore cannot combine two array operators in one query
|
|
335
|
+
}
|
|
336
|
+
else if (searchTokens.length === 1) {
|
|
337
|
+
baseQuery = baseQuery.where("searchTokens", "array-contains", searchTokens[0]);
|
|
338
|
+
}
|
|
339
|
+
else if (searchTokens.length > 1) {
|
|
340
|
+
baseQuery = baseQuery.where("searchTokens", "array-contains-any", searchTokens);
|
|
341
|
+
}
|
|
342
|
+
if (searchTokens.length === 0 && opts?.categoriesIn && opts.categoriesIn.length > 0) {
|
|
324
343
|
baseQuery = baseQuery.where(PRODUCT_FIELDS.CATEGORY_SLUGS, "array-contains-any", opts.categoriesIn);
|
|
325
344
|
}
|
|
326
345
|
return this.sieveQuery(model, ProductRepository.SIEVE_FIELDS, {
|
|
@@ -464,6 +483,7 @@ ProductRepository.SIEVE_FIELDS = {
|
|
|
464
483
|
insurance: { canFilter: true, canSort: false },
|
|
465
484
|
currency: { canFilter: true, canSort: false },
|
|
466
485
|
freeShipping: { canFilter: true, canSort: false },
|
|
486
|
+
searchTokens: { canFilter: true, canSort: false },
|
|
467
487
|
};
|
|
468
488
|
/**
|
|
469
489
|
* Virtual filter aliases — callers can pass `f=listingType==auction` for
|
|
@@ -169,6 +169,7 @@ export interface ProductDocument {
|
|
|
169
169
|
preOrderCurrentCount?: number;
|
|
170
170
|
preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship";
|
|
171
171
|
preOrderCancellable?: boolean;
|
|
172
|
+
preOrderClosed?: boolean;
|
|
172
173
|
isPromoted?: boolean;
|
|
173
174
|
isOnSale?: boolean;
|
|
174
175
|
isSold?: boolean;
|
|
@@ -246,6 +247,7 @@ export interface ProductDocument {
|
|
|
246
247
|
shelf: string;
|
|
247
248
|
bin: string;
|
|
248
249
|
};
|
|
250
|
+
searchTokens?: string[];
|
|
249
251
|
createdAt: Date;
|
|
250
252
|
updatedAt: Date;
|
|
251
253
|
}
|
|
@@ -271,13 +273,11 @@ export interface PrizeDrawItem {
|
|
|
271
273
|
export declare const ProductStatusValues: {
|
|
272
274
|
readonly DRAFT: "draft";
|
|
273
275
|
readonly PUBLISHED: "published";
|
|
276
|
+
readonly IN_REVIEW: "in_review";
|
|
274
277
|
readonly ARCHIVED: "archived";
|
|
275
|
-
readonly SOLD: "sold";
|
|
276
|
-
readonly OUT_OF_STOCK: "out_of_stock";
|
|
277
|
-
readonly DISCONTINUED: "discontinued";
|
|
278
278
|
};
|
|
279
279
|
export declare const PRODUCT_COLLECTION: "products";
|
|
280
|
-
export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "createdAt"];
|
|
280
|
+
export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "searchTokens", "createdAt"];
|
|
281
281
|
export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
|
|
282
282
|
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["id", "title", "description", "category", "subcategory", "brand", "price", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "buyItNowPriceInPaise", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "createdAt"];
|
|
283
283
|
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "subcategory", "brand", "price", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "buyItNowPriceInPaise", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle"];
|
|
@@ -8,10 +8,8 @@ export const MAX_CUSTOM_SECTIONS = 3;
|
|
|
8
8
|
export const ProductStatusValues = {
|
|
9
9
|
DRAFT: "draft",
|
|
10
10
|
PUBLISHED: "published",
|
|
11
|
+
IN_REVIEW: "in_review",
|
|
11
12
|
ARCHIVED: "archived",
|
|
12
|
-
SOLD: "sold",
|
|
13
|
-
OUT_OF_STOCK: "out_of_stock",
|
|
14
|
-
DISCONTINUED: "discontinued",
|
|
15
13
|
};
|
|
16
14
|
export const PRODUCT_COLLECTION = "products";
|
|
17
15
|
export const PRODUCT_INDEXED_FIELDS = [
|
|
@@ -23,6 +21,7 @@ export const PRODUCT_INDEXED_FIELDS = [
|
|
|
23
21
|
"isPromoted",
|
|
24
22
|
"isOnSale",
|
|
25
23
|
"isSold",
|
|
24
|
+
"searchTokens",
|
|
26
25
|
"createdAt",
|
|
27
26
|
];
|
|
28
27
|
export const DEFAULT_PRODUCT_DATA = {
|
|
@@ -91,7 +91,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
91
91
|
category: z.ZodOptional<z.ZodString>;
|
|
92
92
|
categorySlug: z.ZodOptional<z.ZodString>;
|
|
93
93
|
sellerAvatar: z.ZodOptional<z.ZodString>;
|
|
94
|
-
status: z.ZodEnum<["draft", "published", "
|
|
94
|
+
status: z.ZodEnum<["draft", "published", "in_review", "archived"]>;
|
|
95
95
|
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>;
|
|
96
96
|
listingType: z.ZodOptional<z.ZodEnum<["standard", "auction", "pre-order", "prize-draw", "bundle"]>>;
|
|
97
97
|
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
@@ -284,7 +284,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
284
284
|
}[] | undefined;
|
|
285
285
|
}>, "many">>;
|
|
286
286
|
}, "strip", z.ZodTypeAny, {
|
|
287
|
-
status: "draft" | "published" | "archived" | "
|
|
287
|
+
status: "draft" | "published" | "archived" | "in_review";
|
|
288
288
|
id: string;
|
|
289
289
|
title: string;
|
|
290
290
|
price: number;
|
|
@@ -413,7 +413,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
413
413
|
} | undefined;
|
|
414
414
|
publishedAt?: string | undefined;
|
|
415
415
|
}, {
|
|
416
|
-
status: "draft" | "published" | "archived" | "
|
|
416
|
+
status: "draft" | "published" | "archived" | "in_review";
|
|
417
417
|
id: string;
|
|
418
418
|
title: string;
|
|
419
419
|
price: number;
|
|
@@ -546,7 +546,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
546
546
|
export declare const productListParamsSchema: z.ZodObject<{
|
|
547
547
|
q: z.ZodOptional<z.ZodString>;
|
|
548
548
|
category: z.ZodOptional<z.ZodString>;
|
|
549
|
-
status: z.ZodOptional<z.ZodEnum<["draft", "published", "
|
|
549
|
+
status: z.ZodOptional<z.ZodEnum<["draft", "published", "in_review", "archived"]>>;
|
|
550
550
|
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>;
|
|
551
551
|
minPrice: z.ZodOptional<z.ZodNumber>;
|
|
552
552
|
maxPrice: z.ZodOptional<z.ZodNumber>;
|
|
@@ -558,7 +558,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
558
558
|
perPage: z.ZodOptional<z.ZodNumber>;
|
|
559
559
|
featured: z.ZodOptional<z.ZodBoolean>;
|
|
560
560
|
}, "strip", z.ZodTypeAny, {
|
|
561
|
-
status?: "draft" | "published" | "archived" | "
|
|
561
|
+
status?: "draft" | "published" | "archived" | "in_review" | undefined;
|
|
562
562
|
sort?: string | undefined;
|
|
563
563
|
storeId?: string | undefined;
|
|
564
564
|
category?: string | undefined;
|
|
@@ -572,7 +572,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
572
572
|
maxPrice?: number | undefined;
|
|
573
573
|
inStock?: boolean | undefined;
|
|
574
574
|
}, {
|
|
575
|
-
status?: "draft" | "published" | "archived" | "
|
|
575
|
+
status?: "draft" | "published" | "archived" | "in_review" | undefined;
|
|
576
576
|
sort?: string | undefined;
|
|
577
577
|
storeId?: string | undefined;
|
|
578
578
|
category?: string | undefined;
|
|
@@ -51,10 +51,8 @@ export const productItemSchema = z.object({
|
|
|
51
51
|
status: z.enum([
|
|
52
52
|
"draft",
|
|
53
53
|
"published",
|
|
54
|
+
"in_review",
|
|
54
55
|
"archived",
|
|
55
|
-
"sold",
|
|
56
|
-
"out_of_stock",
|
|
57
|
-
"discontinued",
|
|
58
56
|
]),
|
|
59
57
|
condition: z
|
|
60
58
|
.enum([
|
|
@@ -193,10 +191,8 @@ export const productListParamsSchema = z.object({
|
|
|
193
191
|
.enum([
|
|
194
192
|
"draft",
|
|
195
193
|
"published",
|
|
194
|
+
"in_review",
|
|
196
195
|
"archived",
|
|
197
|
-
"sold",
|
|
198
|
-
"out_of_stock",
|
|
199
|
-
"discontinued",
|
|
200
196
|
])
|
|
201
197
|
.optional(),
|
|
202
198
|
condition: z
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MediaField } from "../../media/types/index";
|
|
2
2
|
import type { CustomField, CustomSection } from "../schemas/firestore";
|
|
3
3
|
export type { CustomField, CustomSection } from "../schemas/firestore";
|
|
4
|
-
export type ProductStatus = "draft" | "published" | "
|
|
4
|
+
export type ProductStatus = "draft" | "published" | "in_review" | "archived";
|
|
5
5
|
export type ProductCondition = "new" | "like_new" | "good" | "fair" | "poor" | "used" | "refurbished" | "broken";
|
|
6
6
|
/**
|
|
7
7
|
* Canonical listing-kind discriminator (SB1-G Phase 4).
|
|
@@ -267,8 +267,8 @@ export async function checkoutOffer(userId, offerId) {
|
|
|
267
267
|
if (!product)
|
|
268
268
|
throw new NotFoundError(ERROR_MESSAGES.PRODUCT.NOT_FOUND);
|
|
269
269
|
if ((product.availableQuantity ?? 0) <= 0 ||
|
|
270
|
-
product.
|
|
271
|
-
product.status === ProductStatusValues.
|
|
270
|
+
product.isSold === true ||
|
|
271
|
+
product.status === ProductStatusValues.ARCHIVED)
|
|
272
272
|
throw new ValidationError(ERROR_MESSAGES.OFFER.PRODUCT_UNAVAILABLE);
|
|
273
273
|
serverLogger.info("checkoutOffer — adding to cart", {
|
|
274
274
|
offerId,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SellerProductDraft, SellerProductShellProps } from "./SellerProductShell";
|
|
2
|
+
export interface QuickProductFormProps {
|
|
3
|
+
values: SellerProductDraft;
|
|
4
|
+
onChange: (partial: Partial<SellerProductDraft>) => void;
|
|
5
|
+
onPublish: () => void;
|
|
6
|
+
onSave: () => void;
|
|
7
|
+
onSwitchToFull: () => void;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
storeSlug?: string;
|
|
10
|
+
renderCategorySelector?: SellerProductShellProps["renderCategorySelector"];
|
|
11
|
+
onUploadImage?: (file: File) => Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
export declare function QuickProductForm({ values, onChange, onPublish, onSave, onSwitchToFull, isLoading, renderCategorySelector, onUploadImage, }: QuickProductFormProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Alert, Button, Div, FormField, Stack, Text } from "../../../ui";
|
|
5
|
+
function toRupees(paise) {
|
|
6
|
+
return paise != null && paise > 0 ? String(Math.round(paise / 100)) : "";
|
|
7
|
+
}
|
|
8
|
+
function toPaise(rupeeStr) {
|
|
9
|
+
return Math.round((parseFloat(rupeeStr) || 0) * 100);
|
|
10
|
+
}
|
|
11
|
+
function validate(values) {
|
|
12
|
+
const errors = {};
|
|
13
|
+
if (!values.title?.trim() || (values.title.trim().length < 3)) {
|
|
14
|
+
errors.title = "Title must be at least 3 characters";
|
|
15
|
+
}
|
|
16
|
+
if (!values.price || values.price <= 0) {
|
|
17
|
+
errors.price = "Price is required";
|
|
18
|
+
}
|
|
19
|
+
if (!values.mainImage) {
|
|
20
|
+
errors.mainImage = "Product image is required";
|
|
21
|
+
}
|
|
22
|
+
if (!values.category?.trim()) {
|
|
23
|
+
errors.category = "Category is required";
|
|
24
|
+
}
|
|
25
|
+
return errors;
|
|
26
|
+
}
|
|
27
|
+
export function QuickProductForm({ values, onChange, onPublish, onSave, onSwitchToFull, isLoading, renderCategorySelector, onUploadImage, }) {
|
|
28
|
+
const [errors, setErrors] = useState({});
|
|
29
|
+
const [touched, setTouched] = useState(false);
|
|
30
|
+
const handlePublish = () => {
|
|
31
|
+
setTouched(true);
|
|
32
|
+
const errs = validate(values);
|
|
33
|
+
setErrors(errs);
|
|
34
|
+
if (Object.keys(errs).length === 0) {
|
|
35
|
+
onPublish();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const handleSave = () => {
|
|
39
|
+
if (!values.title?.trim()) {
|
|
40
|
+
setErrors({ title: "Title is required to save a draft" });
|
|
41
|
+
setTouched(true);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
onSave();
|
|
45
|
+
};
|
|
46
|
+
return (_jsxs(Stack, { gap: "lg", className: "p-5", children: [_jsx(Div, { children: _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Quick add \u2014 fill the essentials and publish. You can add more details later." }) }), touched && Object.keys(errors).length > 0 && (_jsx(Alert, { variant: "error", children: "Please fix the highlighted fields before publishing." })), _jsx(FormField, { name: "title", label: "Product Name", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9", required: true, error: touched ? errors.title : undefined }), renderCategorySelector ? (_jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Category" }), renderCategorySelector({
|
|
47
|
+
value: values.category ?? "",
|
|
48
|
+
onChange: (v) => onChange({ category: v }),
|
|
49
|
+
}), touched && errors.category && (_jsx(Text, { className: "text-xs text-[var(--appkit-color-error)] mt-1", children: errors.category }))] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards", error: touched ? errors.category : undefined })), _jsx(FormField, { name: "price", label: "Price (\u20B9)", type: "number", value: toRupees(values.price), onChange: (v) => onChange({ price: toPaise(v) }), placeholder: "e.g. 499", required: true, error: touched ? errors.price : undefined }), _jsx(FormField, { name: "mainImage", label: "Product Image", type: "image", value: values.mainImage ?? "", onChange: (v) => onChange({ mainImage: v }), onUpload: onUploadImage, required: true, error: touched ? errors.mainImage : undefined }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Brief description (optional)", rows: 3 }), _jsx(FormField, { name: "stockQuantity", label: "Stock Quantity", type: "number", value: String(values.stockQuantity ?? 1), onChange: (v) => onChange({ stockQuantity: Math.max(0, parseInt(v, 10) || 0) }), placeholder: "1", hint: "How many units are available" }), _jsxs(Div, { className: "flex flex-col gap-3 pt-2", children: [_jsxs(Div, { className: "flex gap-3", children: [_jsx(Button, { variant: "primary", onClick: handlePublish, disabled: isLoading, className: "flex-1", children: isLoading ? "Publishing..." : "Publish" }), _jsx(Button, { variant: "secondary", onClick: handleSave, disabled: isLoading, children: "Save Draft" })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: onSwitchToFull, className: "self-center text-xs", children: "Show all fields (advanced)" })] })] }));
|
|
50
|
+
}
|
|
@@ -2,5 +2,8 @@ import React from "react";
|
|
|
2
2
|
import type { ListingViewShellProps } from "../../../ui";
|
|
3
3
|
export interface SellerAuctionsViewProps extends ListingViewShellProps {
|
|
4
4
|
renderHeader?: (onAdd: () => void) => React.ReactNode;
|
|
5
|
+
onEditClick?: (id: string) => void;
|
|
6
|
+
onDelete?: (id: string) => Promise<void>;
|
|
7
|
+
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
5
8
|
}
|
|
6
|
-
export declare function SellerAuctionsView({ renderHeader, children, ...props }: SellerAuctionsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function SellerAuctionsView({ renderHeader, children, onEditClick, onDelete, onBulkDelete, ...props }: SellerAuctionsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,9 +5,11 @@ import { X } from "lucide-react";
|
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
7
|
import { AdminViewCards } from "../../admin/components/AdminViewCards";
|
|
8
|
-
import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
|
|
8
|
+
import { BulkActionBar, ConfirmDeleteModal, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu } from "../../../ui";
|
|
9
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
11
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
12
|
+
import { ROUTES } from "../../../constants";
|
|
11
13
|
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
12
14
|
import { DataTable } from "../../admin/components/DataTable";
|
|
13
15
|
const PAGE_SIZE = 25;
|
|
@@ -20,9 +22,11 @@ const SORT_OPTIONS = [
|
|
|
20
22
|
{ value: "createdAt", label: "Oldest" },
|
|
21
23
|
];
|
|
22
24
|
const STATUS_OPTIONS = SELLER_AUCTION_STATUS_TABS;
|
|
23
|
-
export function SellerAuctionsView({ renderHeader, children, ...props }) {
|
|
25
|
+
export function SellerAuctionsView({ renderHeader, children, onEditClick, onDelete, onBulkDelete, ...props }) {
|
|
24
26
|
const hasChildren = React.Children.count(children) > 0;
|
|
25
27
|
const [view, setView] = useState("table");
|
|
28
|
+
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
29
|
+
const [deletingId, setDeletingId] = useState(null);
|
|
26
30
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
27
31
|
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
28
32
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
@@ -78,8 +82,38 @@ export function SellerAuctionsView({ renderHeader, children, ...props }) {
|
|
|
78
82
|
const currentPage = table.getNumber("page", 1);
|
|
79
83
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
80
84
|
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
85
|
+
const bulkActions = onBulkDelete
|
|
86
|
+
? [{
|
|
87
|
+
id: "bulk-delete",
|
|
88
|
+
label: ACTIONS.STORE["delete-listing"].label,
|
|
89
|
+
variant: "danger",
|
|
90
|
+
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
91
|
+
}]
|
|
92
|
+
: [];
|
|
93
|
+
const handleDelete = useCallback(async (id) => {
|
|
94
|
+
setDeletingId(id);
|
|
95
|
+
try {
|
|
96
|
+
if (onDelete)
|
|
97
|
+
await onDelete(id);
|
|
98
|
+
else
|
|
99
|
+
await fetch(`/api/store/products/${id}`, { method: "DELETE", credentials: "include" });
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
setDeletingId(null);
|
|
103
|
+
setDeleteTargetId(null);
|
|
104
|
+
}
|
|
105
|
+
}, [onDelete]);
|
|
106
|
+
const handleEdit = useCallback((id) => {
|
|
107
|
+
if (onEditClick)
|
|
108
|
+
onEditClick(id);
|
|
109
|
+
else
|
|
110
|
+
window.location.href = String(ROUTES.STORE.PRODUCTS_EDIT(id));
|
|
111
|
+
}, [onEditClick]);
|
|
81
112
|
if (hasChildren) {
|
|
82
113
|
return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
|
|
83
114
|
}
|
|
84
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No auctions found"
|
|
115
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No auctions found", selectedIds: selection.selectedIdSet, onToggleSelect: (id) => selection.toggle(id), onToggleSelectAll: () => selection.toggleAll(), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
116
|
+
{ label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
|
|
117
|
+
{ label: ACTIONS.STORE["delete-listing"].label, destructive: true, onClick: () => setDeleteTargetId(row.id), disabled: deletingId === row.id },
|
|
118
|
+
] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No auctions found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Auction", message: "Are you sure you want to delete this auction? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
|
|
85
119
|
}
|
|
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
7
|
import { Badge, BulkActionBar, Div, FilterChipGroup, ListingToolbar, Pagination, Text } from "../../../ui";
|
|
8
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
10
|
import { SELLER_BID_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
10
11
|
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
12
|
import { DataTable } from "../../admin/components/DataTable";
|
|
@@ -147,7 +148,7 @@ export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
|
147
148
|
selection.clearSelection();
|
|
148
149
|
}, [selection]);
|
|
149
150
|
const bulkActions = [
|
|
150
|
-
{ id: "cancel", label: "
|
|
151
|
+
{ id: "cancel", label: ACTIONS.SELLER["cancel-bid"].label, onClick: () => void bulkCancel(), variant: "danger" },
|
|
151
152
|
];
|
|
152
153
|
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by bidder name", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedIds.length > 0 && (_jsx("div", { className: "sticky z-20 px-3 sm:px-4 py-2 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700", style: { top: "calc(var(--header-height, 0px) + 88px)" }, children: _jsx(BulkActionBar, { selectedCount: selection.selectedIds.length, onClearSelection: selection.clearSelection, actions: bulkActions }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), table.get("grouped") === "0" ? (_jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No bids found for your auctions", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: () => selection.toggleAll() })) : (_jsxs("div", { className: "space-y-3", children: [groupedRows.length === 0 && !isLoading && (_jsx(Text, { className: "text-sm text-zinc-500", children: "No bids found for your auctions." })), groupedRows.map((group) => {
|
|
153
154
|
const collapsed = collapsedGroups.has(group.id);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface SellerBundlesViewProps {
|
|
2
2
|
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDelete?: (id: string) => Promise<void>;
|
|
3
5
|
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
6
|
}
|
|
5
|
-
export declare function SellerBundlesView({ onCreateClick, onBulkDelete, }: SellerBundlesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function SellerBundlesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerBundlesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,8 +4,9 @@ import { useState, useCallback } from "react";
|
|
|
4
4
|
import { Plus } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
7
|
+
import { BulkActionBar, Button, ConfirmDeleteModal, DataTable, Div, ListingToolbar, Pagination, RowActionMenu, Text, } from "../../../ui";
|
|
8
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
10
|
import { ROUTES } from "../../..";
|
|
10
11
|
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
12
|
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
@@ -48,10 +49,12 @@ const COLUMNS = [
|
|
|
48
49
|
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
49
50
|
},
|
|
50
51
|
];
|
|
51
|
-
export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
|
|
52
|
+
export function SellerBundlesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }) {
|
|
52
53
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
53
54
|
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
54
|
-
const [view] = useState("table");
|
|
55
|
+
const [view, setView] = useState("table");
|
|
56
|
+
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
57
|
+
const [deletingId, setDeletingId] = useState(null);
|
|
55
58
|
const commitSearch = useCallback(() => {
|
|
56
59
|
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
57
60
|
}, [searchInput, table]);
|
|
@@ -87,11 +90,30 @@ export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
|
|
|
87
90
|
const bulkActions = onBulkDelete
|
|
88
91
|
? [{
|
|
89
92
|
id: "bulk-delete",
|
|
90
|
-
label: "
|
|
93
|
+
label: ACTIONS.STORE["delete-listing"].label,
|
|
91
94
|
variant: "danger",
|
|
92
95
|
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
93
96
|
}]
|
|
94
97
|
: [];
|
|
98
|
+
const handleDelete = useCallback(async (id) => {
|
|
99
|
+
setDeletingId(id);
|
|
100
|
+
try {
|
|
101
|
+
if (onDelete)
|
|
102
|
+
await onDelete(id);
|
|
103
|
+
else
|
|
104
|
+
await fetch(`/api/store/products/${id}`, { method: "DELETE", credentials: "include" });
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
setDeletingId(null);
|
|
108
|
+
setDeleteTargetId(null);
|
|
109
|
+
}
|
|
110
|
+
}, [onDelete]);
|
|
111
|
+
const handleEdit = useCallback((id) => {
|
|
112
|
+
if (onEditClick)
|
|
113
|
+
onEditClick(id);
|
|
114
|
+
else
|
|
115
|
+
window.location.href = String(ROUTES.STORE.PRODUCTS_EDIT(id));
|
|
116
|
+
}, [onEditClick]);
|
|
95
117
|
const handleCreate = useCallback(() => {
|
|
96
118
|
if (onCreateClick) {
|
|
97
119
|
onCreateClick();
|
|
@@ -100,5 +122,16 @@ export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
|
|
|
100
122
|
window.location.href = String(ROUTES.STORE.PRODUCTS_NEW);
|
|
101
123
|
}
|
|
102
124
|
}, [onCreateClick]);
|
|
103
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () =>
|
|
125
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Bundle" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No bundles yet \u2014 create a bundle to group multiple products together" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
126
|
+
{
|
|
127
|
+
label: ACTIONS.STORE["edit-listing"].label,
|
|
128
|
+
onClick: () => handleEdit(row.id),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: ACTIONS.STORE["delete-listing"].label,
|
|
132
|
+
destructive: true,
|
|
133
|
+
onClick: () => setDeleteTargetId(row.id),
|
|
134
|
+
disabled: deletingId === row.id,
|
|
135
|
+
},
|
|
136
|
+
] })) }))] }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Bundle", message: "Are you sure you want to delete this bundle? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
|
|
104
137
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface SellerClassifiedViewProps {
|
|
2
2
|
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDelete?: (id: string) => Promise<void>;
|
|
3
5
|
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
6
|
}
|
|
5
|
-
export declare function SellerClassifiedView({ onCreateClick, onBulkDelete, }: SellerClassifiedViewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerClassifiedViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,8 +4,9 @@ import { useState, useCallback } from "react";
|
|
|
4
4
|
import { Plus } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
|
|
7
|
+
import { BulkActionBar, Button, ConfirmDeleteModal, DataTable, Div, ListingToolbar, Pagination, RowActionMenu, Text, } from "../../../ui";
|
|
8
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
10
|
import { ROUTES } from "../../..";
|
|
10
11
|
import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
11
12
|
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
@@ -54,10 +55,12 @@ const COLUMNS = [
|
|
|
54
55
|
render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
|
|
55
56
|
},
|
|
56
57
|
];
|
|
57
|
-
export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
|
|
58
|
+
export function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }) {
|
|
58
59
|
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
59
60
|
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
60
61
|
const [view] = useState("table");
|
|
62
|
+
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
63
|
+
const [deletingId, setDeletingId] = useState(null);
|
|
61
64
|
const commitSearch = useCallback(() => {
|
|
62
65
|
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
63
66
|
}, [searchInput, table]);
|
|
@@ -96,11 +99,30 @@ export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
|
|
|
96
99
|
const bulkActions = onBulkDelete
|
|
97
100
|
? [{
|
|
98
101
|
id: "bulk-delete",
|
|
99
|
-
label: "
|
|
102
|
+
label: ACTIONS.STORE["delete-listing"].label,
|
|
100
103
|
variant: "danger",
|
|
101
104
|
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
102
105
|
}]
|
|
103
106
|
: [];
|
|
107
|
+
const handleDelete = useCallback(async (id) => {
|
|
108
|
+
setDeletingId(id);
|
|
109
|
+
try {
|
|
110
|
+
if (onDelete)
|
|
111
|
+
await onDelete(id);
|
|
112
|
+
else
|
|
113
|
+
await fetch(`/api/store/products/${id}`, { method: "DELETE", credentials: "include" });
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
setDeletingId(null);
|
|
117
|
+
setDeleteTargetId(null);
|
|
118
|
+
}
|
|
119
|
+
}, [onDelete]);
|
|
120
|
+
const handleEdit = useCallback((id) => {
|
|
121
|
+
if (onEditClick)
|
|
122
|
+
onEditClick(id);
|
|
123
|
+
else
|
|
124
|
+
window.location.href = String(ROUTES.STORE.CLASSIFIED_EDIT(id));
|
|
125
|
+
}, [onEditClick]);
|
|
104
126
|
const handleCreate = useCallback(() => {
|
|
105
127
|
if (onCreateClick) {
|
|
106
128
|
onCreateClick();
|
|
@@ -109,5 +131,16 @@ export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
|
|
|
109
131
|
window.location.href = String(ROUTES.STORE.CLASSIFIED_NEW);
|
|
110
132
|
}
|
|
111
133
|
}, [onCreateClick]);
|
|
112
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search classified listings...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Classified" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No classified listings yet \u2014 post your first buy/sell/trade ad" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids)
|
|
134
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search classified listings...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Classified" })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No classified listings yet \u2014 post your first buy/sell/trade ad" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
135
|
+
{
|
|
136
|
+
label: ACTIONS.STORE["edit-listing"].label,
|
|
137
|
+
onClick: () => handleEdit(row.id),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: ACTIONS.STORE["delete-listing"].label,
|
|
141
|
+
destructive: true,
|
|
142
|
+
onClick: () => setDeleteTargetId(row.id),
|
|
143
|
+
disabled: deletingId === row.id,
|
|
144
|
+
},
|
|
145
|
+
] })) }))] }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Classified Listing", message: "Are you sure you want to delete this classified listing? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
|
|
113
146
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface SellerDigitalCodesViewProps {
|
|
2
2
|
onCreateClick?: () => void;
|
|
3
|
+
onEditClick?: (id: string) => void;
|
|
4
|
+
onDelete?: (id: string) => Promise<void>;
|
|
3
5
|
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
4
6
|
}
|
|
5
|
-
export declare function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }: SellerDigitalCodesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function SellerDigitalCodesView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerDigitalCodesViewProps): import("react/jsx-runtime").JSX.Element;
|