@mohasinac/appkit 2.7.12 → 2.7.13
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/server/jobs/core/bundleStockSync.js +6 -14
- package/dist/_internal/server/jobs/core/onProductStockChange.js +6 -13
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +3 -6
- package/dist/_internal/shared/features/categories/bundle-copy.js +3 -6
- package/dist/constants/field-names.d.ts +11 -0
- package/dist/constants/field-names.js +11 -0
- package/dist/features/admin/components/AdminBundlesView.js +0 -1
- package/dist/features/categories/components/BundleDetailView.js +0 -1
- package/dist/features/categories/components/CategoryBundlesListing.js +2 -4
- package/dist/features/categories/schemas/firestore.d.ts +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -3
- package/dist/features/pre-orders/repository/preorders.repository.js +8 -4
- package/dist/features/promotions/components/CouponsIndexListing.js +39 -18
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/stores/repository/stores.repository.js +8 -5
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/providers/firebase-client/auth.d.ts +7 -0
- package/dist/providers/firebase-client/auth.js +15 -8
- package/package.json +2 -2
|
@@ -14,31 +14,24 @@ const UNAVAILABLE = new Set([
|
|
|
14
14
|
PRODUCT_FIELDS.STATUS_VALUES.OUT_OF_STOCK,
|
|
15
15
|
PRODUCT_FIELDS.STATUS_VALUES.DISCONTINUED,
|
|
16
16
|
]);
|
|
17
|
-
|
|
18
|
-
async function computeBundleStockStatus(productIds, minActive, ctx) {
|
|
17
|
+
async function computeBundleStockStatus(productIds, ctx) {
|
|
19
18
|
if (productIds.length === 0)
|
|
20
19
|
return "out_of_stock";
|
|
21
|
-
let unavailable = 0;
|
|
22
20
|
for (let i = 0; i < productIds.length; i += 30) {
|
|
23
21
|
const chunk = productIds.slice(i, i + 30);
|
|
24
22
|
const snap = await ctx.db
|
|
25
23
|
.collection(PRODUCT_COLLECTION)
|
|
26
24
|
.where("__name__", "in", chunk)
|
|
27
25
|
.get();
|
|
26
|
+
if (snap.size < chunk.length)
|
|
27
|
+
return "out_of_stock";
|
|
28
28
|
for (const doc of snap.docs) {
|
|
29
29
|
const status = doc.data().status;
|
|
30
30
|
if (!status || UNAVAILABLE.has(status))
|
|
31
|
-
|
|
31
|
+
return "out_of_stock";
|
|
32
32
|
}
|
|
33
|
-
if (snap.size < chunk.length)
|
|
34
|
-
unavailable += chunk.length - snap.size;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
if (unavailable === 0)
|
|
38
|
-
return "in_stock";
|
|
39
|
-
if (active < minActive)
|
|
40
|
-
return "out_of_stock";
|
|
41
|
-
return "partial";
|
|
34
|
+
return "in_stock";
|
|
42
35
|
}
|
|
43
36
|
export async function runBundleStockSync(ctx) {
|
|
44
37
|
ctx.logger.info("Bundle stock sync starting (SB-UNI-V categories)");
|
|
@@ -58,8 +51,7 @@ export async function runBundleStockSync(ctx) {
|
|
|
58
51
|
const productIds = data.bundleProductIds ?? [];
|
|
59
52
|
if (productIds.length === 0)
|
|
60
53
|
continue;
|
|
61
|
-
const
|
|
62
|
-
const nextStatus = await computeBundleStockStatus(productIds, minActive, ctx);
|
|
54
|
+
const nextStatus = await computeBundleStockStatus(productIds, ctx);
|
|
63
55
|
if (nextStatus !== data.bundleStockStatus) {
|
|
64
56
|
await bundleDoc.ref.update({
|
|
65
57
|
[CATEGORY_FIELDS.BUNDLE_STOCK_STATUS]: nextStatus,
|
|
@@ -25,30 +25,24 @@ export function isAvailable(status) {
|
|
|
25
25
|
return false;
|
|
26
26
|
return !UNAVAILABLE_STATUSES.has(status);
|
|
27
27
|
}
|
|
28
|
-
async function recomputeBundleStatus(productIds,
|
|
28
|
+
async function recomputeBundleStatus(productIds, ctx) {
|
|
29
29
|
if (productIds.length === 0)
|
|
30
30
|
return "out_of_stock";
|
|
31
|
-
let unavailable = 0;
|
|
32
31
|
for (let i = 0; i < productIds.length; i += 30) {
|
|
33
32
|
const chunk = productIds.slice(i, i + 30);
|
|
34
33
|
const snap = await ctx.db
|
|
35
34
|
.collection(PRODUCT_COLLECTION)
|
|
36
35
|
.where("__name__", "in", chunk)
|
|
37
36
|
.get();
|
|
37
|
+
if (snap.size < chunk.length)
|
|
38
|
+
return "out_of_stock";
|
|
38
39
|
for (const doc of snap.docs) {
|
|
39
40
|
const status = doc.data().status;
|
|
40
41
|
if (!isAvailable(status))
|
|
41
|
-
|
|
42
|
+
return "out_of_stock";
|
|
42
43
|
}
|
|
43
|
-
if (snap.size < chunk.length)
|
|
44
|
-
unavailable += chunk.length - snap.size;
|
|
45
44
|
}
|
|
46
|
-
|
|
47
|
-
if (unavailable === 0)
|
|
48
|
-
return "in_stock";
|
|
49
|
-
if (active < minActive)
|
|
50
|
-
return "out_of_stock";
|
|
51
|
-
return "partial";
|
|
45
|
+
return "in_stock";
|
|
52
46
|
}
|
|
53
47
|
export async function syncBundlesForProduct(productId, ctx) {
|
|
54
48
|
const snap = await ctx.db
|
|
@@ -60,8 +54,7 @@ export async function syncBundlesForProduct(productId, ctx) {
|
|
|
60
54
|
for (const bundleDoc of snap.docs) {
|
|
61
55
|
const data = bundleDoc.data();
|
|
62
56
|
const ids = data.bundleProductIds ?? [];
|
|
63
|
-
const
|
|
64
|
-
const next = await recomputeBundleStatus(ids, minActive, ctx);
|
|
57
|
+
const next = await recomputeBundleStatus(ids, ctx);
|
|
65
58
|
if (next !== data.bundleStockStatus) {
|
|
66
59
|
await bundleDoc.ref.update({
|
|
67
60
|
[CATEGORY_FIELDS.BUNDLE_STOCK_STATUS]: next,
|
|
@@ -108,16 +108,13 @@ export declare const BUNDLE_COPY: {
|
|
|
108
108
|
};
|
|
109
109
|
readonly stockBadge: {
|
|
110
110
|
readonly in_stock: "In stock";
|
|
111
|
-
readonly
|
|
112
|
-
readonly out_of_stock: "Currently out of stock";
|
|
111
|
+
readonly out_of_stock: "Not active";
|
|
113
112
|
readonly listVariantInStock: "In stock";
|
|
114
|
-
readonly
|
|
115
|
-
readonly listVariantOutOfStock: "Out of stock";
|
|
113
|
+
readonly listVariantOutOfStock: "Not active";
|
|
116
114
|
};
|
|
117
115
|
};
|
|
118
116
|
/** Stock-status variant mapping (kept here so consumers can drive Badge variant prop). */
|
|
119
117
|
export declare const BUNDLE_STOCK_VARIANT: {
|
|
120
118
|
readonly in_stock: "success";
|
|
121
|
-
readonly
|
|
122
|
-
readonly out_of_stock: "danger";
|
|
119
|
+
readonly out_of_stock: "warning";
|
|
123
120
|
};
|
|
@@ -119,16 +119,13 @@ export const BUNDLE_COPY = {
|
|
|
119
119
|
// Stock badges (shared between list + featured + detail)
|
|
120
120
|
stockBadge: {
|
|
121
121
|
in_stock: "In stock",
|
|
122
|
-
|
|
123
|
-
out_of_stock: "Currently out of stock",
|
|
122
|
+
out_of_stock: "Not active",
|
|
124
123
|
listVariantInStock: "In stock",
|
|
125
|
-
|
|
126
|
-
listVariantOutOfStock: "Out of stock",
|
|
124
|
+
listVariantOutOfStock: "Not active",
|
|
127
125
|
},
|
|
128
126
|
};
|
|
129
127
|
/** Stock-status variant mapping (kept here so consumers can drive Badge variant prop). */
|
|
130
128
|
export const BUNDLE_STOCK_VARIANT = {
|
|
131
129
|
in_stock: "success",
|
|
132
|
-
|
|
133
|
-
out_of_stock: "danger",
|
|
130
|
+
out_of_stock: "warning",
|
|
134
131
|
};
|
|
@@ -363,11 +363,14 @@ export declare const STORE_FIELDS: {
|
|
|
363
363
|
readonly OWNER_ID: "ownerId";
|
|
364
364
|
readonly STORE_NAME: "storeName";
|
|
365
365
|
readonly SLUG: "slug";
|
|
366
|
+
readonly STORE_SLUG: "storeSlug";
|
|
367
|
+
readonly STORE_CATEGORY: "storeCategory";
|
|
366
368
|
readonly DESCRIPTION: "storeDescription";
|
|
367
369
|
readonly LOGO_URL: "storeLogoURL";
|
|
368
370
|
readonly BANNER_URL: "storeBannerURL";
|
|
369
371
|
readonly STATUS: "status";
|
|
370
372
|
readonly IS_VERIFIED: "isVerified";
|
|
373
|
+
readonly IS_PUBLIC: "isPublic";
|
|
371
374
|
readonly SHIPPING_CONFIG: "shippingConfig";
|
|
372
375
|
readonly PAYOUT_DETAILS: "payoutDetails";
|
|
373
376
|
readonly STATS: "stats";
|
|
@@ -379,6 +382,13 @@ export declare const STORE_FIELDS: {
|
|
|
379
382
|
readonly SUSPENDED: "suspended";
|
|
380
383
|
readonly REJECTED: "rejected";
|
|
381
384
|
};
|
|
385
|
+
readonly STATS_FIELDS: {
|
|
386
|
+
readonly ITEMS_SOLD: "stats.itemsSold";
|
|
387
|
+
readonly AVERAGE_RATING: "stats.averageRating";
|
|
388
|
+
readonly TOTAL_REVIEWS: "stats.totalReviews";
|
|
389
|
+
readonly TOTAL_ORDERS: "stats.totalOrders";
|
|
390
|
+
readonly TOTAL_PRODUCTS: "stats.totalProducts";
|
|
391
|
+
};
|
|
382
392
|
};
|
|
383
393
|
export declare const CATEGORY_FIELDS: {
|
|
384
394
|
readonly ID: "id";
|
|
@@ -633,6 +643,7 @@ export declare const COUPON_FIELDS: {
|
|
|
633
643
|
readonly TYPE: "type";
|
|
634
644
|
readonly SCOPE: "scope";
|
|
635
645
|
readonly SELLER_ID: "sellerId";
|
|
646
|
+
readonly STORE_ID: "storeId";
|
|
636
647
|
readonly STORE_SLUG: "storeSlug";
|
|
637
648
|
readonly APPLICABLE_TO_AUCTIONS: "applicableToAuctions";
|
|
638
649
|
readonly DISCOUNT: "discount";
|
|
@@ -400,11 +400,14 @@ export const STORE_FIELDS = {
|
|
|
400
400
|
OWNER_ID: "ownerId",
|
|
401
401
|
STORE_NAME: "storeName",
|
|
402
402
|
SLUG: "slug",
|
|
403
|
+
STORE_SLUG: "storeSlug",
|
|
404
|
+
STORE_CATEGORY: "storeCategory",
|
|
403
405
|
DESCRIPTION: "storeDescription",
|
|
404
406
|
LOGO_URL: "storeLogoURL",
|
|
405
407
|
BANNER_URL: "storeBannerURL",
|
|
406
408
|
STATUS: "status",
|
|
407
409
|
IS_VERIFIED: "isVerified",
|
|
410
|
+
IS_PUBLIC: "isPublic",
|
|
408
411
|
SHIPPING_CONFIG: "shippingConfig",
|
|
409
412
|
PAYOUT_DETAILS: "payoutDetails",
|
|
410
413
|
STATS: "stats",
|
|
@@ -416,6 +419,13 @@ export const STORE_FIELDS = {
|
|
|
416
419
|
SUSPENDED: "suspended",
|
|
417
420
|
REJECTED: "rejected",
|
|
418
421
|
},
|
|
422
|
+
STATS_FIELDS: {
|
|
423
|
+
ITEMS_SOLD: "stats.itemsSold",
|
|
424
|
+
AVERAGE_RATING: "stats.averageRating",
|
|
425
|
+
TOTAL_REVIEWS: "stats.totalReviews",
|
|
426
|
+
TOTAL_ORDERS: "stats.totalOrders",
|
|
427
|
+
TOTAL_PRODUCTS: "stats.totalProducts",
|
|
428
|
+
},
|
|
419
429
|
};
|
|
420
430
|
// ============================================================================
|
|
421
431
|
// CATEGORY FIELDS
|
|
@@ -720,6 +730,7 @@ export const COUPON_FIELDS = {
|
|
|
720
730
|
TYPE: "type",
|
|
721
731
|
SCOPE: "scope",
|
|
722
732
|
SELLER_ID: "sellerId",
|
|
733
|
+
STORE_ID: "storeId",
|
|
723
734
|
STORE_SLUG: "storeSlug",
|
|
724
735
|
APPLICABLE_TO_AUCTIONS: "applicableToAuctions",
|
|
725
736
|
DISCOUNT: "discount",
|
|
@@ -17,7 +17,6 @@ import { Badge, Button, Container, Div, Heading, Row, Section, Stack, Text, } fr
|
|
|
17
17
|
import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
18
18
|
const STOCK_LIST_LABEL = {
|
|
19
19
|
in_stock: BUNDLE_COPY.stockBadge.listVariantInStock,
|
|
20
|
-
partial: BUNDLE_COPY.stockBadge.listVariantPartial,
|
|
21
20
|
out_of_stock: BUNDLE_COPY.stockBadge.listVariantOutOfStock,
|
|
22
21
|
};
|
|
23
22
|
function formatPrice(paise) {
|
|
@@ -7,7 +7,6 @@ import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/fe
|
|
|
7
7
|
import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
8
8
|
const STOCK_BADGE_TEXT = {
|
|
9
9
|
in_stock: BUNDLE_COPY.stockBadge.in_stock,
|
|
10
|
-
partial: BUNDLE_COPY.stockBadge.partial,
|
|
11
10
|
out_of_stock: BUNDLE_COPY.stockBadge.out_of_stock,
|
|
12
11
|
};
|
|
13
12
|
const PLACEHOLDER_EMOJI = "📦";
|
|
@@ -19,13 +19,11 @@ const SORT_OPTIONS = [
|
|
|
19
19
|
];
|
|
20
20
|
const STOCK_BADGE_TEXT = {
|
|
21
21
|
in_stock: "",
|
|
22
|
-
|
|
23
|
-
out_of_stock: "Out of stock",
|
|
22
|
+
out_of_stock: "Not active",
|
|
24
23
|
};
|
|
25
24
|
const STOCK_BADGE_VARIANT = {
|
|
26
25
|
in_stock: "success",
|
|
27
|
-
|
|
28
|
-
out_of_stock: "danger",
|
|
26
|
+
out_of_stock: "warning",
|
|
29
27
|
};
|
|
30
28
|
const PLACEHOLDER_EMOJI = "📦";
|
|
31
29
|
const COPY = {
|
|
@@ -105,7 +105,7 @@ export interface CategoryDocument {
|
|
|
105
105
|
/** Rule resolving the bundle's member products — static list or live query. */
|
|
106
106
|
bundleQueryRule?: BundleQueryRule;
|
|
107
107
|
/** Snapshot stock state — recomputed by onProductStockChange. */
|
|
108
|
-
bundleStockStatus?: "in_stock" | "
|
|
108
|
+
bundleStockStatus?: "in_stock" | "out_of_stock";
|
|
109
109
|
/** Timestamp of the last dynamic-rule resolution. */
|
|
110
110
|
bundleQueryResolvedAt?: Date;
|
|
111
111
|
/** Hand-picked products list (mirror of bundleQueryRule for static rules); kept for index-friendly queries. */
|
|
@@ -18,7 +18,5 @@ function FeaturedBundleCard({ bundle }) {
|
|
|
18
18
|
// eslint-disable-next-line @next/next/no-img-element, lir/no-raw-media-elements
|
|
19
19
|
_jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-3xl", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2 text-sm font-semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1 flex-wrap", children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
|
|
20
20
|
? formatCurrency(bundle.bundlePriceInPaise / 100, "INR")
|
|
21
|
-
: BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children:
|
|
22
|
-
? BUNDLE_COPY.stockBadge.listVariantPartial
|
|
23
|
-
: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }));
|
|
21
|
+
: BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }));
|
|
24
22
|
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
+
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
2
|
+
const { LISTING_TYPE, STATUS, FEATURED, CREATED_AT } = PRODUCT_FIELDS;
|
|
3
|
+
const { PRE_ORDER } = PRODUCT_FIELDS.LISTING_TYPE_VALUES;
|
|
4
|
+
const { PUBLISHED } = PRODUCT_FIELDS.STATUS_VALUES;
|
|
1
5
|
export class PreordersRepository {
|
|
2
6
|
constructor(repo) {
|
|
3
7
|
this.repo = repo;
|
|
4
8
|
}
|
|
5
9
|
async findPreorders() {
|
|
6
10
|
const result = await this.repo.findAll({
|
|
7
|
-
filters:
|
|
8
|
-
sort:
|
|
11
|
+
filters: `${LISTING_TYPE}==${PRE_ORDER},${STATUS}==${PUBLISHED}`,
|
|
12
|
+
sort: CREATED_AT,
|
|
9
13
|
order: "desc",
|
|
10
14
|
});
|
|
11
15
|
return result.data;
|
|
12
16
|
}
|
|
13
17
|
async findFeaturedPreorders() {
|
|
14
18
|
const result = await this.repo.findAll({
|
|
15
|
-
filters:
|
|
16
|
-
sort:
|
|
19
|
+
filters: `${LISTING_TYPE}==${PRE_ORDER},${STATUS}==${PUBLISHED},${FEATURED}==true`,
|
|
20
|
+
sort: CREATED_AT,
|
|
17
21
|
order: "desc",
|
|
18
22
|
});
|
|
19
23
|
return result.data;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from "react";
|
|
3
|
+
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { Search, SlidersHorizontal, X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { Pagination, SortDropdown, Div, Text, Heading } from "../../../ui";
|
|
@@ -13,34 +13,55 @@ const DEFAULT_SORT = sortBy(COUPON_FIELDS.CREATED_AT);
|
|
|
13
13
|
const COUPON_SORT_OPTIONS = [
|
|
14
14
|
{ value: sortBy(COUPON_FIELDS.NAME, "ASC"), label: "Name A–Z" },
|
|
15
15
|
{ value: sortBy(COUPON_FIELDS.NAME), label: "Name Z–A" },
|
|
16
|
-
{ value:
|
|
16
|
+
{ value: sortBy(COUPON_FIELDS.VALIDITY_FIELDS.END_DATE, "ASC"), label: "Expiring Soon" },
|
|
17
17
|
{ value: sortBy(COUPON_FIELDS.CREATED_AT), label: "Newest First" },
|
|
18
18
|
{ value: sortBy(COUPON_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
19
19
|
];
|
|
20
20
|
const COUPON_TYPES = [
|
|
21
|
-
{ value:
|
|
22
|
-
{ value:
|
|
23
|
-
{ value:
|
|
24
|
-
{ value:
|
|
21
|
+
{ value: COUPON_FIELDS.TYPE_VALUES.PERCENTAGE, label: "% Off" },
|
|
22
|
+
{ value: COUPON_FIELDS.TYPE_VALUES.FIXED, label: "Fixed Amount" },
|
|
23
|
+
{ value: COUPON_FIELDS.TYPE_VALUES.FREE_SHIPPING, label: "Free Shipping" },
|
|
24
|
+
{ value: COUPON_FIELDS.TYPE_VALUES.BUY_X_GET_Y, label: "Buy X Get Y" },
|
|
25
25
|
];
|
|
26
|
+
const FILTER_KEYS = [TABLE_KEYS.TYPE, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
|
|
26
27
|
export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
27
28
|
const table = useUrlTable({ defaults: { pageSize: "12", sort: DEFAULT_SORT } });
|
|
28
29
|
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
29
30
|
const [filterOpen, setFilterOpen] = useState(false);
|
|
30
|
-
//
|
|
31
|
+
// Pending filter state — buffered until "Apply Filters" clicked
|
|
32
|
+
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
33
|
+
const pendingTable = useMemo(() => ({
|
|
34
|
+
get: (key) => pendingFilters[key] ?? "",
|
|
35
|
+
set: (key, value) => setPendingFilters((p) => ({ ...p, [key]: value })),
|
|
36
|
+
}), [pendingFilters]);
|
|
37
|
+
const openFilters = useCallback(() => {
|
|
38
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
39
|
+
setFilterOpen(true);
|
|
40
|
+
}, [table]);
|
|
41
|
+
const applyFilters = useCallback(() => {
|
|
42
|
+
const updates = { [TABLE_KEYS.PAGE]: "1" };
|
|
43
|
+
for (const k of FILTER_KEYS)
|
|
44
|
+
updates[k] = pendingFilters[k] ?? "";
|
|
45
|
+
table.setMany(updates);
|
|
46
|
+
setFilterOpen(false);
|
|
47
|
+
}, [pendingFilters, table]);
|
|
48
|
+
const clearPending = useCallback(() => {
|
|
49
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
50
|
+
}, []);
|
|
51
|
+
// Build Sieve filter string from committed URL table values
|
|
31
52
|
const buildFilters = () => {
|
|
32
|
-
const parts = [
|
|
33
|
-
const typeFilter = table.get(
|
|
53
|
+
const parts = [`${COUPON_FIELDS.VALIDITY_FIELDS.IS_ACTIVE}==true`];
|
|
54
|
+
const typeFilter = table.get(TABLE_KEYS.TYPE);
|
|
34
55
|
if (typeFilter)
|
|
35
|
-
parts.push(
|
|
56
|
+
parts.push(`${COUPON_FIELDS.TYPE}==${typeFilter}`);
|
|
36
57
|
const dateFrom = table.get(TABLE_KEYS.DATE_FROM);
|
|
37
58
|
if (dateFrom)
|
|
38
|
-
parts.push(
|
|
59
|
+
parts.push(`${COUPON_FIELDS.VALIDITY_FIELDS.START_DATE}>=${dateFrom}`);
|
|
39
60
|
const dateTo = table.get(TABLE_KEYS.DATE_TO);
|
|
40
61
|
if (dateTo)
|
|
41
|
-
parts.push(
|
|
62
|
+
parts.push(`${COUPON_FIELDS.VALIDITY_FIELDS.END_DATE}<=${dateTo}`);
|
|
42
63
|
if (storeId)
|
|
43
|
-
parts.push(
|
|
64
|
+
parts.push(`${COUPON_FIELDS.STORE_ID}==${storeId}`);
|
|
44
65
|
return parts.join(",");
|
|
45
66
|
};
|
|
46
67
|
const { promotions: coupons, total, totalPages, isLoading } = usePromotions({
|
|
@@ -52,7 +73,7 @@ export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
|
52
73
|
// Use initial data on first load if available and no search/filter active
|
|
53
74
|
const displayCoupons = !isLoading && coupons.length > 0
|
|
54
75
|
? coupons
|
|
55
|
-
: !isLoading && initialCoupons && !table.get(TABLE_KEYS.QUERY) && !table.get(
|
|
76
|
+
: !isLoading && initialCoupons && !table.get(TABLE_KEYS.QUERY) && !table.get(TABLE_KEYS.TYPE)
|
|
56
77
|
? initialCoupons
|
|
57
78
|
: coupons;
|
|
58
79
|
const commitSearch = useCallback(() => {
|
|
@@ -62,19 +83,19 @@ export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
|
62
83
|
if (e.key === "Enter")
|
|
63
84
|
commitSearch();
|
|
64
85
|
};
|
|
65
|
-
const activeType = table.get(
|
|
86
|
+
const activeType = table.get(TABLE_KEYS.TYPE);
|
|
66
87
|
const hasActiveFilters = !!activeType || !!table.get(TABLE_KEYS.DATE_FROM) || !!table.get(TABLE_KEYS.DATE_TO);
|
|
67
88
|
const clearFilters = () => {
|
|
68
89
|
table.setMany({ type: "", [TABLE_KEYS.DATE_FROM]: "", [TABLE_KEYS.DATE_TO]: "" });
|
|
69
90
|
};
|
|
70
|
-
return (_jsxs("div", { className: "min-h-[40vh]", children: [_jsxs("div", { className: "sticky top-[var(--header-height,0px)] z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: [_jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick:
|
|
91
|
+
return (_jsxs("div", { className: "min-h-[40vh]", children: [_jsxs("div", { className: "sticky top-[var(--header-height,0px)] z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: [_jsxs("div", { className: "flex items-center gap-2.5 max-w-full", children: [_jsxs("button", { type: "button", onClick: openFilters, className: `flex shrink-0 items-center gap-2 rounded-lg border px-3.5 py-2 text-sm font-medium transition-colors ${hasActiveFilters
|
|
71
92
|
? "border-primary bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
|
|
72
|
-
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsxs("span", { className: "hidden sm:inline", children: ["Filters", hasActiveFilters ? " •" : ""] })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleKeyDown, placeholder: "Search by name or description\u2026", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), searchInput && (_jsx("button", { type: "button", onClick: () => { setSearchInput(""); table.set(TABLE_KEYS.QUERY, ""); }, className: "p-2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300", "aria-label": "Clear search", children: _jsx(X, { className: "h-3.5 w-3.5" }) })), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, onChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, options: COUPON_SORT_OPTIONS })] })] }), hasActiveFilters && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 mt-2", children: [activeType && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: [COUPON_TYPES.find((t) => t.value === activeType)?.label ?? activeType, _jsx("button", { type: "button", onClick: () => { table.set(
|
|
93
|
+
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsxs("span", { className: "hidden sm:inline", children: ["Filters", hasActiveFilters ? " •" : ""] })] }), _jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleKeyDown, placeholder: "Search by name or description\u2026", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), searchInput && (_jsx("button", { type: "button", onClick: () => { setSearchInput(""); table.set(TABLE_KEYS.QUERY, ""); }, className: "p-2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300", "aria-label": "Clear search", children: _jsx(X, { className: "h-3.5 w-3.5" }) })), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, onChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, options: COUPON_SORT_OPTIONS })] })] }), hasActiveFilters && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 mt-2", children: [activeType && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: [COUPON_TYPES.find((t) => t.value === activeType)?.label ?? activeType, _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.TYPE, ""); }, "aria-label": "Remove type filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), table.get(TABLE_KEYS.DATE_FROM) && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: ["From: ", table.get(TABLE_KEYS.DATE_FROM), _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.DATE_FROM, ""); }, "aria-label": "Remove from-date filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), table.get(TABLE_KEYS.DATE_TO) && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: ["To: ", table.get(TABLE_KEYS.DATE_TO), _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.DATE_TO, ""); }, "aria-label": "Remove to-date filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), _jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 underline", children: "Clear all" })] }))] }), _jsxs("div", { className: "py-6 px-4", children: [isLoading ? (_jsx("div", { className: "grid gap-3 md:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx("div", { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : displayCoupons.length === 0 ? (_jsx("div", { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons match your search." }) })) : (_jsx("div", { className: "grid gap-3 md:grid-cols-2 lg:grid-cols-3", children: displayCoupons.map((coupon) => (_jsx(CouponCard, { coupon: coupon, labels: {
|
|
73
94
|
copy: "Copy",
|
|
74
95
|
copied: "Copied!",
|
|
75
96
|
expires: "Expires",
|
|
76
97
|
minOrder: "Min. order",
|
|
77
98
|
off: "OFF",
|
|
78
99
|
freeShipping: "Free Shipping",
|
|
79
|
-
} }, coupon.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), !isLoading && total > 0 && (_jsx(Div, { className: "mt-4 text-center", children: _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [total, " coupon", total !== 1 ? "s" : "", " available"] }) }))] }), 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", 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" }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-6", children: [_jsxs("div", { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Discount Type" }), _jsxs("div", { className: "space-y-2", children: [COUPON_TYPES.map((t) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "coupon-type", value: t.value, checked:
|
|
100
|
+
} }, coupon.id))) })), totalPages > 1 && (_jsx("div", { className: "mt-8 flex justify-center", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), !isLoading && total > 0 && (_jsx(Div, { className: "mt-4 text-center", children: _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [total, " coupon", total !== 1 ? "s" : "", " available"] }) }))] }), 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: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", 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" }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-6", children: [_jsxs("div", { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Discount Type" }), _jsxs("div", { className: "space-y-2", children: [COUPON_TYPES.map((t) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "coupon-type", value: t.value, checked: pendingTable.get(TABLE_KEYS.TYPE) === t.value, onChange: () => { pendingTable.set(TABLE_KEYS.TYPE, t.value); }, className: "accent-primary" }), t.label] }, t.value))), pendingTable.get(TABLE_KEYS.TYPE) && (_jsx("button", { type: "button", onClick: () => { pendingTable.set(TABLE_KEYS.TYPE, ""); }, className: "text-xs text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 underline", children: "Clear type" }))] })] }), _jsxs("div", { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Valid Date Range" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "From date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_FROM) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_FROM, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "To date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_TO) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_TO, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] })] })] })] }), _jsxs("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5 flex gap-2", children: [_jsx("button", { type: "button", onClick: clearPending, className: "flex-1 rounded-lg border border-zinc-300 dark:border-slate-600 py-2.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: "Clear all" }), _jsx("button", { type: "button", onClick: applyFilters, className: "flex-1 rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors", children: "Apply Filters" })] })] })] }))] }));
|
|
80
101
|
}
|
|
@@ -102,6 +102,7 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
102
102
|
} | undefined;
|
|
103
103
|
returnPolicy?: string | undefined;
|
|
104
104
|
website?: string | undefined;
|
|
105
|
+
storeCategory?: string | undefined;
|
|
105
106
|
storeDescription?: string | undefined;
|
|
106
107
|
storeLogoURL?: string | undefined;
|
|
107
108
|
storeBannerURL?: string | undefined;
|
|
@@ -112,7 +113,6 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
112
113
|
twitter?: string | undefined;
|
|
113
114
|
linkedin?: string | undefined;
|
|
114
115
|
} | undefined;
|
|
115
|
-
storeCategory?: string | undefined;
|
|
116
116
|
shippingPolicy?: string | undefined;
|
|
117
117
|
isVacationMode?: boolean | undefined;
|
|
118
118
|
vacationMessage?: string | undefined;
|
|
@@ -134,6 +134,7 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
134
134
|
} | undefined;
|
|
135
135
|
returnPolicy?: string | undefined;
|
|
136
136
|
website?: string | undefined;
|
|
137
|
+
storeCategory?: string | undefined;
|
|
137
138
|
storeDescription?: string | undefined;
|
|
138
139
|
storeLogoURL?: string | undefined;
|
|
139
140
|
storeBannerURL?: string | undefined;
|
|
@@ -144,7 +145,6 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
144
145
|
twitter?: string | undefined;
|
|
145
146
|
linkedin?: string | undefined;
|
|
146
147
|
} | undefined;
|
|
147
|
-
storeCategory?: string | undefined;
|
|
148
148
|
shippingPolicy?: string | undefined;
|
|
149
149
|
isVacationMode?: boolean | undefined;
|
|
150
150
|
vacationMessage?: string | undefined;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { STORE_FIELDS } from "../../../constants/field-names";
|
|
2
|
+
const { STATUS, IS_PUBLIC, STORE_SLUG, STORE_CATEGORY, STATS_FIELDS } = STORE_FIELDS;
|
|
3
|
+
const { ACTIVE } = STORE_FIELDS.STATUS_VALUES;
|
|
1
4
|
export class StoresRepository {
|
|
2
5
|
constructor(repo) {
|
|
3
6
|
this.repo = repo;
|
|
@@ -10,23 +13,23 @@ export class StoresRepository {
|
|
|
10
13
|
}
|
|
11
14
|
async findBySlug(storeSlug) {
|
|
12
15
|
const result = await this.repo.findAll({
|
|
13
|
-
filters:
|
|
16
|
+
filters: `${STORE_SLUG}==${storeSlug},${STATUS}==${ACTIVE}`,
|
|
14
17
|
perPage: 1,
|
|
15
18
|
});
|
|
16
19
|
return result.data[0] ?? null;
|
|
17
20
|
}
|
|
18
21
|
async findActive(page = 1, perPage = 20) {
|
|
19
22
|
return this.repo.findAll({
|
|
20
|
-
filters:
|
|
21
|
-
sort:
|
|
23
|
+
filters: `${STATUS}==${ACTIVE},${IS_PUBLIC}==true`,
|
|
24
|
+
sort: `-${STATS_FIELDS.ITEMS_SOLD}`,
|
|
22
25
|
page,
|
|
23
26
|
perPage,
|
|
24
27
|
});
|
|
25
28
|
}
|
|
26
29
|
async findByCategory(category, page = 1, perPage = 20) {
|
|
27
30
|
return this.repo.findAll({
|
|
28
|
-
filters:
|
|
29
|
-
sort:
|
|
31
|
+
filters: `${STORE_CATEGORY}==${category},${STATUS}==${ACTIVE}`,
|
|
32
|
+
sort: `-${STATS_FIELDS.AVERAGE_RATING}`,
|
|
30
33
|
page,
|
|
31
34
|
perPage,
|
|
32
35
|
});
|
|
@@ -40,10 +40,10 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
40
40
|
ownerId: string;
|
|
41
41
|
createdAt?: string | undefined;
|
|
42
42
|
itemsSold?: number | undefined;
|
|
43
|
+
storeCategory?: string | undefined;
|
|
43
44
|
storeDescription?: string | undefined;
|
|
44
45
|
storeLogoURL?: string | undefined;
|
|
45
46
|
storeBannerURL?: string | undefined;
|
|
46
|
-
storeCategory?: string | undefined;
|
|
47
47
|
totalProducts?: number | undefined;
|
|
48
48
|
totalReviews?: number | undefined;
|
|
49
49
|
averageRating?: number | undefined;
|
|
@@ -56,10 +56,10 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
56
56
|
ownerId: string;
|
|
57
57
|
createdAt?: string | undefined;
|
|
58
58
|
itemsSold?: number | undefined;
|
|
59
|
+
storeCategory?: string | undefined;
|
|
59
60
|
storeDescription?: string | undefined;
|
|
60
61
|
storeLogoURL?: string | undefined;
|
|
61
62
|
storeBannerURL?: string | undefined;
|
|
62
|
-
storeCategory?: string | undefined;
|
|
63
63
|
totalProducts?: number | undefined;
|
|
64
64
|
totalReviews?: number | undefined;
|
|
65
65
|
averageRating?: number | undefined;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { type Auth } from "firebase/auth";
|
|
1
2
|
import type { IClientAuthProvider } from "../../contracts/client-auth";
|
|
2
3
|
/**
|
|
3
4
|
* Firebase Auth client SDK implementation of IClientAuthProvider.
|
|
5
|
+
*
|
|
6
|
+
* Accepts the already-initialized Auth instance explicitly to avoid relying on
|
|
7
|
+
* the global Firebase app registry — which breaks when Turbopack resolves
|
|
8
|
+
* firebase/auth to a different module copy than the one that called initializeApp().
|
|
4
9
|
*/
|
|
5
10
|
export declare class FirebaseClientAuthProvider implements IClientAuthProvider {
|
|
11
|
+
private readonly _auth;
|
|
12
|
+
constructor(_auth: Auth);
|
|
6
13
|
signInWithEmailAndPassword(email: string, password: string): Promise<void>;
|
|
7
14
|
applyActionCode(code: string): Promise<void>;
|
|
8
15
|
sendPasswordResetEmail(email: string): Promise<void>;
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
import { applyActionCode as fbApplyActionCode, confirmPasswordReset as fbConfirmPasswordReset, EmailAuthProvider,
|
|
1
|
+
import { applyActionCode as fbApplyActionCode, confirmPasswordReset as fbConfirmPasswordReset, EmailAuthProvider, reauthenticateWithCredential, sendPasswordResetEmail as fbSendPasswordResetEmail, signInWithEmailAndPassword as fbSignInWithEmailAndPassword, updatePassword, verifyBeforeUpdateEmail, } from "firebase/auth";
|
|
2
2
|
/**
|
|
3
3
|
* Firebase Auth client SDK implementation of IClientAuthProvider.
|
|
4
|
+
*
|
|
5
|
+
* Accepts the already-initialized Auth instance explicitly to avoid relying on
|
|
6
|
+
* the global Firebase app registry — which breaks when Turbopack resolves
|
|
7
|
+
* firebase/auth to a different module copy than the one that called initializeApp().
|
|
4
8
|
*/
|
|
5
9
|
export class FirebaseClientAuthProvider {
|
|
10
|
+
constructor(_auth) {
|
|
11
|
+
this._auth = _auth;
|
|
12
|
+
}
|
|
6
13
|
async signInWithEmailAndPassword(email, password) {
|
|
7
|
-
await fbSignInWithEmailAndPassword(
|
|
14
|
+
await fbSignInWithEmailAndPassword(this._auth, email, password);
|
|
8
15
|
}
|
|
9
16
|
async applyActionCode(code) {
|
|
10
|
-
await fbApplyActionCode(
|
|
17
|
+
await fbApplyActionCode(this._auth, code);
|
|
11
18
|
}
|
|
12
19
|
async sendPasswordResetEmail(email) {
|
|
13
|
-
await fbSendPasswordResetEmail(
|
|
20
|
+
await fbSendPasswordResetEmail(this._auth, email);
|
|
14
21
|
}
|
|
15
22
|
async confirmPasswordReset(code, newPassword) {
|
|
16
|
-
await fbConfirmPasswordReset(
|
|
23
|
+
await fbConfirmPasswordReset(this._auth, code, newPassword);
|
|
17
24
|
}
|
|
18
25
|
async reauthenticateAndChangePassword(currentPassword, newPassword) {
|
|
19
|
-
const user =
|
|
26
|
+
const user = this._auth.currentUser;
|
|
20
27
|
if (!user?.email)
|
|
21
28
|
throw new Error("No authenticated user.");
|
|
22
29
|
const credential = EmailAuthProvider.credential(user.email, currentPassword);
|
|
@@ -24,7 +31,7 @@ export class FirebaseClientAuthProvider {
|
|
|
24
31
|
await updatePassword(user, newPassword);
|
|
25
32
|
}
|
|
26
33
|
async reauthenticateAndSendEmailUpdateVerification(currentPassword, newEmail) {
|
|
27
|
-
const user =
|
|
34
|
+
const user = this._auth.currentUser;
|
|
28
35
|
if (!user?.email)
|
|
29
36
|
throw new Error("No authenticated user.");
|
|
30
37
|
const credential = EmailAuthProvider.credential(user.email, currentPassword);
|
|
@@ -32,6 +39,6 @@ export class FirebaseClientAuthProvider {
|
|
|
32
39
|
await verifyBeforeUpdateEmail(user, newEmail);
|
|
33
40
|
}
|
|
34
41
|
async reloadCurrentUser() {
|
|
35
|
-
await
|
|
42
|
+
await this._auth.currentUser?.reload();
|
|
36
43
|
}
|
|
37
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohasinac/appkit",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"watch:css": "tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --watch",
|
|
148
148
|
"audit": "node scripts/audit-violations.mjs",
|
|
149
149
|
"check:types": "tsc --noEmit",
|
|
150
|
-
"check:audits": "node scripts/audit-violations.mjs && node scripts/verify-entries.mjs && node scripts/verify-css-build.mjs && node scripts/audit-use-client.mjs && node scripts/audit-double-navigation.mjs",
|
|
150
|
+
"check:audits": "node scripts/audit-violations.mjs && node scripts/verify-entries.mjs && node scripts/verify-css-build.mjs && node scripts/audit-use-client.mjs && node scripts/audit-double-navigation.mjs && node scripts/audit-repository-fields.mjs",
|
|
151
151
|
"check": "npm run check:types && npm run check:audits"
|
|
152
152
|
},
|
|
153
153
|
"dependencies": {
|