@mohasinac/appkit 4.41.0 → 4.41.1
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/features/shell/SectionForm.js +44 -5
- package/package.json +2 -2
- package/dist/_internal/server/features/classified/actions.d.ts +0 -12
- package/dist/_internal/server/features/classified/actions.js +0 -33
- package/dist/_internal/server/features/digital-code/actions.d.ts +0 -12
- package/dist/_internal/server/features/digital-code/actions.js +0 -33
- package/dist/_internal/server/features/homepage/data.d.ts +0 -18
- package/dist/_internal/server/features/homepage/data.js +0 -70
- package/dist/_internal/server/features/live/actions.d.ts +0 -6
- package/dist/_internal/server/features/live/actions.js +0 -27
- package/dist/_internal/server/features/messages/actions.d.ts +0 -6
- package/dist/_internal/server/features/messages/actions.js +0 -6
- package/dist/_internal/server/features/messages/data.d.ts +0 -10
- package/dist/_internal/server/features/messages/data.js +0 -24
- package/dist/_internal/server/features/messages/index.d.ts +0 -2
- package/dist/_internal/server/features/messages/index.js +0 -2
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +0 -12
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +0 -112
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +0 -2
- package/dist/_internal/shared/features/homepage/section-gate.d.ts +0 -14
- package/dist/_internal/shared/features/homepage/section-gate.js +0 -51
- package/dist/_internal/shared/features/lottery/config-form.d.ts +0 -58
- package/dist/_internal/shared/features/lottery/config-form.js +0 -71
- package/dist/client/api/useFeatureFlags.d.ts +0 -22
- package/dist/client/api/useFeatureFlags.js +0 -27
- package/dist/features/account/address-validation.d.ts +0 -20
- package/dist/features/account/address-validation.js +0 -35
- package/dist/features/account/components/ChatList.d.ts +0 -14
- package/dist/features/account/components/ChatList.js +0 -9
- package/dist/features/account/components/ChatWindow.d.ts +0 -17
- package/dist/features/account/components/ChatWindow.js +0 -6
- package/dist/features/account/components/MessagesView.d.ts +0 -16
- package/dist/features/account/components/MessagesView.js +0 -5
- package/dist/features/account/components/UserOffersView.d.ts +0 -10
- package/dist/features/account/components/UserOffersView.js +0 -5
- package/dist/features/account/constants/addresses.d.ts +0 -13
- package/dist/features/account/constants/addresses.js +0 -43
- package/dist/features/account/hooks/useAddressForm.d.ts +0 -24
- package/dist/features/account/hooks/useAddressForm.js +0 -58
- package/dist/features/admin/actions/chat-actions.d.ts +0 -29
- package/dist/features/admin/actions/chat-actions.js +0 -101
- package/dist/features/admin/components/AdminFeatureFlagsView.d.ts +0 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +0 -109
- package/dist/features/admin/components/AdminSiteView.d.ts +0 -10
- package/dist/features/admin/components/AdminSiteView.js +0 -52
- package/dist/features/admin/hooks/useChat.d.ts +0 -24
- package/dist/features/admin/hooks/useChat.js +0 -100
- package/dist/features/admin/permission-map.d.ts +0 -3
- package/dist/features/admin/permission-map.js +0 -26
- package/dist/features/admin/repository/chat.repository.d.ts +0 -50
- package/dist/features/admin/repository/chat.repository.js +0 -223
- package/dist/features/auth/actions/password-change-otp-actions.d.ts +0 -18
- package/dist/features/auth/actions/password-change-otp-actions.js +0 -88
- package/dist/features/auth/actions/realtime-token-actions.d.ts +0 -11
- package/dist/features/auth/actions/realtime-token-actions.js +0 -38
- package/dist/features/auth/password-change-otp.d.ts +0 -34
- package/dist/features/auth/password-change-otp.js +0 -56
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +0 -12
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +0 -49
- package/dist/features/events/api/route.d.ts +0 -12
- package/dist/features/events/api/route.js +0 -90
- package/dist/features/faq/api/route.d.ts +0 -11
- package/dist/features/faq/api/route.js +0 -52
- package/dist/features/faq/hooks/useFAQs.d.ts +0 -22
- package/dist/features/faq/hooks/useFAQs.js +0 -41
- package/dist/features/media/upload/video-poster.test.d.ts +0 -1
- package/dist/features/media/upload/video-poster.test.js +0 -42
- package/dist/features/messages/actions/index.d.ts +0 -2
- package/dist/features/messages/actions/index.js +0 -2
- package/dist/features/messages/actions/messages-actions.d.ts +0 -15
- package/dist/features/messages/actions/messages-actions.js +0 -33
- package/dist/features/messages/actions/ping-rtdb.d.ts +0 -2
- package/dist/features/messages/actions/ping-rtdb.js +0 -26
- package/dist/features/messages/hooks/useConversation.d.ts +0 -22
- package/dist/features/messages/hooks/useConversation.js +0 -155
- package/dist/features/messages/hooks/useConversations.d.ts +0 -15
- package/dist/features/messages/hooks/useConversations.js +0 -104
- package/dist/features/messages/index.d.ts +0 -6
- package/dist/features/messages/index.js +0 -4
- package/dist/features/messages/realtime.d.ts +0 -32
- package/dist/features/messages/realtime.js +0 -39
- package/dist/features/messages/repository/conversations.repository.d.ts +0 -71
- package/dist/features/messages/repository/conversations.repository.js +0 -284
- package/dist/features/messages/schemas/firestore.d.ts +0 -34
- package/dist/features/messages/schemas/firestore.js +0 -16
- package/dist/features/messages/schemas/index.d.ts +0 -177
- package/dist/features/messages/schemas/index.js +0 -35
- package/dist/features/messages/server.d.ts +0 -6
- package/dist/features/messages/server.js +0 -6
- package/dist/features/products/components/MakeOfferForm.d.ts +0 -20
- package/dist/features/products/components/MakeOfferForm.js +0 -9
- package/dist/features/reviews/api/route.d.ts +0 -21
- package/dist/features/reviews/api/route.js +0 -174
- package/dist/features/seller/api/offers/route.d.ts +0 -17
- package/dist/features/seller/api/offers/route.js +0 -63
- package/dist/features/seller/components/SellerAuctionsView.d.ts +0 -9
- package/dist/features/seller/components/SellerAuctionsView.js +0 -113
- package/dist/features/seller/components/SellerOffersPanel.d.ts +0 -21
- package/dist/features/seller/components/SellerOffersPanel.js +0 -160
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +0 -6
- package/dist/features/seller/components/SellerPreOrdersView.js +0 -148
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +0 -6
- package/dist/features/seller/components/SellerPrizeDrawsView.js +0 -152
- package/dist/features/seller/permission-map.d.ts +0 -5
- package/dist/features/seller/permission-map.js +0 -17
- package/dist/features/shell/StepForm.d.ts +0 -80
- package/dist/features/shell/StepForm.js +0 -136
- package/dist/features/stores/api/route.d.ts +0 -12
- package/dist/features/stores/api/route.js +0 -110
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +0 -33
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +0 -138
- package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/bids-tester-seed-data.js +0 -37
- package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/blog-tester-seed-data.js +0 -40
- package/dist/features/tester/seed-data/carts-tester-seed-data.d.ts +0 -3
- package/dist/features/tester/seed-data/carts-tester-seed-data.js +0 -70
- package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +0 -218
- package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +0 -76
- package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/events-tester-seed-data.js +0 -193
- package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +0 -191
- package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +0 -329
- package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/products-tester-seed-data.js +0 -815
- package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +0 -79
- package/dist/features/wishlist/components/WishlistToggleButton.d.ts +0 -12
- package/dist/features/wishlist/components/WishlistToggleButton.js +0 -36
- package/dist/monitoring/analytics.d.ts +0 -80
- package/dist/monitoring/analytics.js +0 -176
- package/dist/next/middleware/pii-redactor.d.ts +0 -32
- package/dist/next/middleware/pii-redactor.js +0 -84
- package/dist/providers/db-firebase/realtime.d.ts +0 -42
- package/dist/providers/db-firebase/realtime.js +0 -138
- package/dist/security/rbac/Can.d.ts +0 -31
- package/dist/security/rbac/Can.js +0 -22
- package/dist/security/rbac/default-roles.d.ts +0 -2
- package/dist/security/rbac/default-roles.js +0 -83
- package/dist/security/rbac/hook.d.ts +0 -20
- package/dist/security/rbac/hook.js +0 -30
- package/dist/security/rbac/index.d.ts +0 -8
- package/dist/security/rbac/index.js +0 -6
- package/dist/security/rbac/middleware.d.ts +0 -51
- package/dist/security/rbac/middleware.js +0 -51
- package/dist/security/rbac/resolver.d.ts +0 -5
- package/dist/security/rbac/resolver.js +0 -38
- package/dist/security/rbac/server.d.ts +0 -24
- package/dist/security/rbac/server.js +0 -42
- package/dist/security/rbac/types.d.ts +0 -22
- package/dist/security/rbac/types.js +0 -2
- package/dist/seed/conversations-seed-data.d.ts +0 -2
- package/dist/seed/conversations-seed-data.js +0 -292
- package/dist/utils/filter.helper.d.ts +0 -18
- package/dist/utils/filter.helper.js +0 -21
- package/dist/utils/pagination.helper.d.ts +0 -23
- package/dist/utils/pagination.helper.js +0 -24
- package/dist/utils/search-tokens.d.ts +0 -2
- package/dist/utils/search-tokens.js +0 -21
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* feat-reviews — Next.js App Router API handler (GET/POST /api/reviews)
|
|
3
|
-
*
|
|
4
|
-
* 2-line stub for GET:
|
|
5
|
-
* ```ts
|
|
6
|
-
* // app/api/reviews/route.ts
|
|
7
|
-
* export { GET, POST } from "@mohasinac/feat-reviews";
|
|
8
|
-
* ```
|
|
9
|
-
*
|
|
10
|
-
* Query modes for GET:
|
|
11
|
-
* ?featured=true → returns flat Review[] (for testimonial sections)
|
|
12
|
-
* ?latest=true → paginated approved reviews, no productId required
|
|
13
|
-
* ?productId=<id> → paginated + aggregate stats (averageRating, ratingDistribution)
|
|
14
|
-
*
|
|
15
|
-
* POST creates a new review; requires authentication.
|
|
16
|
-
*/
|
|
17
|
-
import { NextResponse } from "next/server.js";
|
|
18
|
-
import { z } from "zod";
|
|
19
|
-
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
20
|
-
import { getProviders } from "../../../contracts";
|
|
21
|
-
import { createRouteHandler } from "../../../next";
|
|
22
|
-
import { ReviewStatusValues } from "../schemas";
|
|
23
|
-
import { normalizeError } from "../../../errors/normalize";
|
|
24
|
-
function param(url, key) {
|
|
25
|
-
return url.searchParams.get(key);
|
|
26
|
-
}
|
|
27
|
-
function numParam(url, key, fallback) {
|
|
28
|
-
const v = url.searchParams.get(key);
|
|
29
|
-
const n = v !== null ? Number(v) : NaN;
|
|
30
|
-
return Number.isFinite(n) ? n : fallback;
|
|
31
|
-
}
|
|
32
|
-
function computeAggregates(reviews) {
|
|
33
|
-
const dist = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
34
|
-
let total = 0;
|
|
35
|
-
let count = 0;
|
|
36
|
-
for (const r of reviews) {
|
|
37
|
-
if (r.status === ReviewStatusValues.APPROVED) {
|
|
38
|
-
dist[r.rating] = (dist[r.rating] ?? 0) + 1;
|
|
39
|
-
total += r.rating;
|
|
40
|
-
count++;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
averageRating: count > 0 ? total / count : 0,
|
|
45
|
-
ratingDistribution: dist,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
export async function GET(request) {
|
|
49
|
-
try {
|
|
50
|
-
const url = new URL(request.url);
|
|
51
|
-
const featured = param(url, "featured") === "true";
|
|
52
|
-
const latest = param(url, "latest") === "true";
|
|
53
|
-
const page = numParam(url, "page", 1);
|
|
54
|
-
const pageSize = numParam(url, "pageSize", 10);
|
|
55
|
-
const sort = param(url, "sorts") ?? param(url, "sort") ?? "-createdAt";
|
|
56
|
-
const { db } = getProviders();
|
|
57
|
-
if (!db) {
|
|
58
|
-
return NextResponse.json({ success: false, error: "Database provider not registered" }, { status: 503 });
|
|
59
|
-
}
|
|
60
|
-
const repo = db.getRepository("reviews");
|
|
61
|
-
// -- ?featured=true --------------------------------------------------------
|
|
62
|
-
// Returns flat Review[] for homepage testimonial sections.
|
|
63
|
-
if (featured) {
|
|
64
|
-
const result = await repo.findAll({
|
|
65
|
-
filters: "featured==true,status==approved",
|
|
66
|
-
sort,
|
|
67
|
-
perPage: Math.min(pageSize, 50),
|
|
68
|
-
});
|
|
69
|
-
const response = NextResponse.json({ success: true, data: result.data });
|
|
70
|
-
response.headers.set("Cache-Control", "public, max-age=300, s-maxage=600, stale-while-revalidate=120");
|
|
71
|
-
return response;
|
|
72
|
-
}
|
|
73
|
-
// -- ?latest=true ----------------------------------------------------------
|
|
74
|
-
// Paginated latest approved reviews, no productId required.
|
|
75
|
-
if (latest) {
|
|
76
|
-
const latestPageSize = numParam(url, "pageSize", 24);
|
|
77
|
-
const result = await repo.findAll({
|
|
78
|
-
filters: "status==approved",
|
|
79
|
-
sort,
|
|
80
|
-
page,
|
|
81
|
-
perPage: latestPageSize,
|
|
82
|
-
});
|
|
83
|
-
const totalPages = Math.max(1, Math.ceil(result.total / latestPageSize));
|
|
84
|
-
const body = {
|
|
85
|
-
items: result.data,
|
|
86
|
-
total: result.total,
|
|
87
|
-
page: result.page,
|
|
88
|
-
pageSize: latestPageSize,
|
|
89
|
-
totalPages,
|
|
90
|
-
hasMore: result.page < totalPages,
|
|
91
|
-
};
|
|
92
|
-
const response = NextResponse.json({ success: true, data: body });
|
|
93
|
-
response.headers.set("Cache-Control", "public, max-age=120, s-maxage=300, stale-while-revalidate=60");
|
|
94
|
-
return response;
|
|
95
|
-
}
|
|
96
|
-
// -- ?productId=<id> -------------------------------------------------------
|
|
97
|
-
// Paginated reviews for a product with aggregate rating stats.
|
|
98
|
-
const productId = param(url, "productId");
|
|
99
|
-
if (!productId) {
|
|
100
|
-
return NextResponse.json({ success: false, error: "productId query parameter is required" }, { status: 400 });
|
|
101
|
-
}
|
|
102
|
-
// Fetch all approved reviews for aggregate computation (no pagination)
|
|
103
|
-
const allResult = await repo.findAll({
|
|
104
|
-
filters: `productId==${productId},status==approved`,
|
|
105
|
-
perPage: 1000,
|
|
106
|
-
});
|
|
107
|
-
const { averageRating, ratingDistribution } = computeAggregates(allResult.data);
|
|
108
|
-
// Fetch the current page for display
|
|
109
|
-
const pagedResult = await repo.findAll({
|
|
110
|
-
filters: `productId==${productId},status==approved`,
|
|
111
|
-
sort,
|
|
112
|
-
page,
|
|
113
|
-
perPage: pageSize,
|
|
114
|
-
});
|
|
115
|
-
const totalPages = Math.max(1, Math.ceil(pagedResult.total / pageSize));
|
|
116
|
-
const body = {
|
|
117
|
-
items: pagedResult.data,
|
|
118
|
-
total: pagedResult.total,
|
|
119
|
-
page: pagedResult.page,
|
|
120
|
-
pageSize,
|
|
121
|
-
totalPages,
|
|
122
|
-
hasMore: pagedResult.page < totalPages,
|
|
123
|
-
averageRating,
|
|
124
|
-
ratingDistribution,
|
|
125
|
-
};
|
|
126
|
-
const response = NextResponse.json({ success: true, data: body });
|
|
127
|
-
response.headers.set("Cache-Control", "public, max-age=120, s-maxage=300, stale-while-revalidate=60");
|
|
128
|
-
return response;
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
void normalizeError(error);
|
|
132
|
-
console.error("[feat-reviews] GET /api/reviews failed", error);
|
|
133
|
-
return NextResponse.json({ success: false, error: "Failed to fetch reviews" }, { status: 500 });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// POST /api/reviews — create a new review (authenticated)
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
const reviewCreateSchema = z
|
|
140
|
-
.object({
|
|
141
|
-
productId: z.string().min(1),
|
|
142
|
-
rating: z.union([
|
|
143
|
-
z.literal(1),
|
|
144
|
-
z.literal(2),
|
|
145
|
-
z.literal(3),
|
|
146
|
-
z.literal(4),
|
|
147
|
-
z.literal(5),
|
|
148
|
-
]),
|
|
149
|
-
title: z.string().min(1).max(100).optional(),
|
|
150
|
-
comment: z.string().min(1).max(2000).optional(),
|
|
151
|
-
images: z.array(mediaUrlSchema).max(10).optional(),
|
|
152
|
-
video: mediaUrlSchema.optional(),
|
|
153
|
-
})
|
|
154
|
-
.passthrough();
|
|
155
|
-
export const POST = createRouteHandler({
|
|
156
|
-
auth: true,
|
|
157
|
-
schema: reviewCreateSchema,
|
|
158
|
-
handler: async ({ user, body }) => {
|
|
159
|
-
const { db } = getProviders();
|
|
160
|
-
if (!db) {
|
|
161
|
-
return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
|
|
162
|
-
}
|
|
163
|
-
const repo = db.getRepository("reviews");
|
|
164
|
-
const now = new Date().toISOString();
|
|
165
|
-
const created = await repo.create({
|
|
166
|
-
...body,
|
|
167
|
-
userId: user.uid,
|
|
168
|
-
status: "pending",
|
|
169
|
-
createdAt: now,
|
|
170
|
-
updatedAt: now,
|
|
171
|
-
});
|
|
172
|
-
return NextResponse.json({ success: true, data: created }, { status: 201 });
|
|
173
|
-
},
|
|
174
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* feat-seller — GET /api/seller/offers
|
|
3
|
-
*
|
|
4
|
-
* Returns all incoming offers for the authenticated seller.
|
|
5
|
-
* Queries the `offers` collection filtered by `sellerId == uid`.
|
|
6
|
-
*
|
|
7
|
-
* Response: `{ success: true, data: { offers: OfferDocument[], total: number } }`
|
|
8
|
-
*
|
|
9
|
-
* Consumer stub:
|
|
10
|
-
* ```ts
|
|
11
|
-
* // src/app/api/seller/offers/route.ts
|
|
12
|
-
* export { sellerOffersGET as GET } from "@mohasinac/feat-seller";
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export declare const GET: (request: Request, context?: {
|
|
16
|
-
params: Promise<Record<string, string>>;
|
|
17
|
-
} | undefined) => Promise<Response>;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* feat-seller — GET /api/seller/offers
|
|
3
|
-
*
|
|
4
|
-
* Returns all incoming offers for the authenticated seller.
|
|
5
|
-
* Queries the `offers` collection filtered by `sellerId == uid`.
|
|
6
|
-
*
|
|
7
|
-
* Response: `{ success: true, data: { offers: OfferDocument[], total: number } }`
|
|
8
|
-
*
|
|
9
|
-
* Consumer stub:
|
|
10
|
-
* ```ts
|
|
11
|
-
* // src/app/api/seller/offers/route.ts
|
|
12
|
-
* export { sellerOffersGET as GET } from "@mohasinac/feat-seller";
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
import { NextResponse } from "next/server.js";
|
|
16
|
-
import { getProviders } from "../../../../contracts";
|
|
17
|
-
import { createRouteHandler } from "../../../../next";
|
|
18
|
-
function numParam(url, key, fallback) {
|
|
19
|
-
const v = url.searchParams.get(key);
|
|
20
|
-
const n = v !== null ? Number(v) : NaN;
|
|
21
|
-
return Number.isFinite(n) ? n : fallback;
|
|
22
|
-
}
|
|
23
|
-
export const GET = createRouteHandler({
|
|
24
|
-
roles: ["seller", "admin", "moderator"],
|
|
25
|
-
handler: async ({ request, user }) => {
|
|
26
|
-
const url = new URL(request.url);
|
|
27
|
-
const page = Math.max(1, numParam(url, "page", 1));
|
|
28
|
-
const pageSize = Math.min(100, Math.max(1, numParam(url, "pageSize", 50)));
|
|
29
|
-
const sort = url.searchParams.get("sorts") ??
|
|
30
|
-
url.searchParams.get("sort") ??
|
|
31
|
-
"-createdAt";
|
|
32
|
-
// Optional status filter passed by client
|
|
33
|
-
const parts = [`sellerId==${user.uid}`];
|
|
34
|
-
const status = url.searchParams.get("status");
|
|
35
|
-
if (status && status !== "all")
|
|
36
|
-
parts.push(`status==${status}`);
|
|
37
|
-
const extraFilters = url.searchParams.get("filters");
|
|
38
|
-
if (extraFilters)
|
|
39
|
-
parts.push(extraFilters);
|
|
40
|
-
const { db } = getProviders();
|
|
41
|
-
if (!db)
|
|
42
|
-
return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
|
|
43
|
-
const repo = db.getRepository("offers");
|
|
44
|
-
const result = await repo.findAll({
|
|
45
|
-
filters: parts.join(","),
|
|
46
|
-
sort,
|
|
47
|
-
page,
|
|
48
|
-
perPage: pageSize,
|
|
49
|
-
});
|
|
50
|
-
const totalPages = Math.max(1, Math.ceil(result.total / pageSize));
|
|
51
|
-
return NextResponse.json({
|
|
52
|
-
success: true,
|
|
53
|
-
data: {
|
|
54
|
-
items: result.data,
|
|
55
|
-
total: result.total,
|
|
56
|
-
page: result.page,
|
|
57
|
-
pageSize,
|
|
58
|
-
totalPages,
|
|
59
|
-
hasMore: result.page < totalPages,
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { ListingLayoutProps } from "../../../ui";
|
|
3
|
-
export interface SellerAuctionsViewProps extends ListingLayoutProps {
|
|
4
|
-
renderHeader?: (onAdd: () => void) => React.ReactNode;
|
|
5
|
-
onEditClick?: (id: string) => void;
|
|
6
|
-
onDelete?: (id: string) => Promise<void>;
|
|
7
|
-
onBulkDelete?: (ids: string[]) => Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
export declare function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDelete, ...props }: SellerAuctionsViewProps): React.JSX.Element;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
|
|
4
|
-
import { sortBy } from "@mohasinac/appkit/client";
|
|
5
|
-
import React, { useState, useCallback } from "react";
|
|
6
|
-
import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
|
|
7
|
-
import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu } from "../../../ui";
|
|
8
|
-
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
-
import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
10
|
-
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
11
|
-
import { ROUTES } from "../../../constants";
|
|
12
|
-
import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
|
|
13
|
-
import { DataListingView } from "../../admin/components/DataListingView";
|
|
14
|
-
import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
|
|
15
|
-
import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
|
|
16
|
-
const AUCTION_TYPES = ["auction"];
|
|
17
|
-
export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDelete, ...props }) {
|
|
18
|
-
// Independent of DataListingView's own internal useUrlTable() — both read
|
|
19
|
-
// and write the same URL, useUrlTable has no local state, so they stay in
|
|
20
|
-
// sync (same pattern SellerProductsView already uses for its "Show sold"
|
|
21
|
-
// toggle).
|
|
22
|
-
const scope = useAvailabilityScope(AUCTION_TYPES);
|
|
23
|
-
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
24
|
-
const { deletingId, handleDelete: performDelete } = useEntityDelete({
|
|
25
|
-
endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
|
|
26
|
-
deleteFn: onDelete,
|
|
27
|
-
successMessage: "Auction deleted.",
|
|
28
|
-
fetchOptions: { credentials: "include" },
|
|
29
|
-
});
|
|
30
|
-
const handleDelete = useCallback(async (id) => {
|
|
31
|
-
await performDelete(id);
|
|
32
|
-
setDeleteTargetId(null);
|
|
33
|
-
}, [performDelete]);
|
|
34
|
-
const handleEdit = useCallback((id) => {
|
|
35
|
-
if (onEditClick)
|
|
36
|
-
onEditClick(id);
|
|
37
|
-
else
|
|
38
|
-
window.location.href = String(ROUTES.STORE.PRODUCTS_EDIT(id));
|
|
39
|
-
}, [onEditClick]);
|
|
40
|
-
if (React.Children.count(children) > 0) {
|
|
41
|
-
return (_jsx(ListingLayout, { portal: "seller", ...props, children: children }));
|
|
42
|
-
}
|
|
43
|
-
const config = {
|
|
44
|
-
portal: "seller",
|
|
45
|
-
title: "Auctions",
|
|
46
|
-
searchPlaceholder: "Search auctions by product name",
|
|
47
|
-
emptyLabel: "No auctions found",
|
|
48
|
-
filterKeys: ["status"],
|
|
49
|
-
defaultSort: sortBy("auctionEndDate", "ASC"),
|
|
50
|
-
queryKey: ["seller", "auctions", "listing"],
|
|
51
|
-
endpoint: SELLER_ENDPOINTS.AUCTIONS,
|
|
52
|
-
sortOptions: [
|
|
53
|
-
{ value: "auctionEndDate", label: "Ending soon" },
|
|
54
|
-
{ value: sortBy("auctionEndDate", "DESC"), label: "Ending latest" },
|
|
55
|
-
{ value: sortBy("createdAt", "DESC"), label: "Newest" },
|
|
56
|
-
{ value: sortBy("createdAt", "ASC"), label: "Oldest" },
|
|
57
|
-
],
|
|
58
|
-
mapRows: (response) => toRecordArray(response.auctions).map((item, index) => ({
|
|
59
|
-
id: toStringValue(item.id, `auction-${index}`),
|
|
60
|
-
primary: toStringValue(item.productTitle ?? item.title, "Untitled auction"),
|
|
61
|
-
secondary: [
|
|
62
|
-
`Reserve: ${toCurrency(item.reservePrice)}`,
|
|
63
|
-
`Bids: ${item.bidCount ?? 0}`,
|
|
64
|
-
].join(" · "),
|
|
65
|
-
status: toStringValue(item.status, "Unknown"),
|
|
66
|
-
updatedAt: toRelativeDate(item.endsAt ?? item.updatedAt ?? item.createdAt),
|
|
67
|
-
image: toStringValue(item.mainImage, "") || undefined,
|
|
68
|
-
})),
|
|
69
|
-
getTotal: (response, mappedRows) => typeof response.meta?.total === "number"
|
|
70
|
-
? response.meta.total
|
|
71
|
-
: mappedRows.length,
|
|
72
|
-
buildFilters: (state) => {
|
|
73
|
-
// Previously missing entirely — the seller Auctions tab silently
|
|
74
|
-
// listed the entire seller catalog, not just auctions.
|
|
75
|
-
//
|
|
76
|
-
// The hide-ended clause that used to live here is gone: it hard-coded
|
|
77
|
-
// `auctionEndDate >= now`, which is an INEQUALITY pushed straight into
|
|
78
|
-
// Firestore alongside whichever sort the toolbar happened to be on —
|
|
79
|
-
// the FAILED_PRECONDITION shape of Root Cause #59. The scope param now
|
|
80
|
-
// carries it, and `listPublicProducts` decides whether it can be pushed
|
|
81
|
-
// down or has to be applied over a bounded fetch.
|
|
82
|
-
const parts = ["listingType==auction"];
|
|
83
|
-
if (state.status && state.status !== "All")
|
|
84
|
-
parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
|
|
85
|
-
return parts.join(",");
|
|
86
|
-
},
|
|
87
|
-
buildExtraParams: () => scope.extraParams,
|
|
88
|
-
renderAboveContent: scope.renderAboveContent,
|
|
89
|
-
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
90
|
-
buildBulkActions: onBulkDelete
|
|
91
|
-
? (selection) => SELLER_BULK_ACTIONS.auctions.map((id) => ({
|
|
92
|
-
id,
|
|
93
|
-
label: ROW_ACTION_META[id].label,
|
|
94
|
-
destructive: ROW_ACTION_META[id].destructive,
|
|
95
|
-
onClick: async () => {
|
|
96
|
-
await onBulkDelete(selection.selectedIds);
|
|
97
|
-
selection.clearSelection();
|
|
98
|
-
},
|
|
99
|
-
}))
|
|
100
|
-
: undefined,
|
|
101
|
-
renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
102
|
-
{ label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
|
|
103
|
-
{
|
|
104
|
-
label: ACTIONS.STORE["delete-listing"].label,
|
|
105
|
-
destructive: true,
|
|
106
|
-
onClick: () => setDeleteTargetId(row.id),
|
|
107
|
-
disabled: deletingId === row.id,
|
|
108
|
-
},
|
|
109
|
-
] })),
|
|
110
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: SELLER_AUCTION_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
|
|
111
|
-
};
|
|
112
|
-
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), 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 }))] }));
|
|
113
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
export type SellerOfferAction = {
|
|
3
|
-
action: "accept";
|
|
4
|
-
offerId: string;
|
|
5
|
-
sellerNote?: string;
|
|
6
|
-
} | {
|
|
7
|
-
action: "decline";
|
|
8
|
-
offerId: string;
|
|
9
|
-
sellerNote?: string;
|
|
10
|
-
} | {
|
|
11
|
-
action: "counter";
|
|
12
|
-
offerId: string;
|
|
13
|
-
counterAmount: number;
|
|
14
|
-
sellerNote?: string;
|
|
15
|
-
};
|
|
16
|
-
export interface SellerOffersPanelProps {
|
|
17
|
-
fetchEndpoint?: string;
|
|
18
|
-
onRespond: (input: SellerOfferAction) => Promise<void>;
|
|
19
|
-
className?: string;
|
|
20
|
-
}
|
|
21
|
-
export declare function SellerOffersPanel({ fetchEndpoint, onRespond, className, }: SellerOffersPanelProps): React.JSX.Element;
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useState, useTransition } from "react";
|
|
4
|
-
import { Alert, Badge, Button, Div, Heading, Input, LoginRequiredModal, Row, Spinner, Stack, Text } from "../../../ui";
|
|
5
|
-
import { isAuthError } from "../../../utils/auth-error";
|
|
6
|
-
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
7
|
-
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
-
import { normalizeError } from "../../../errors/normalize";
|
|
9
|
-
function formatRupees(amount) {
|
|
10
|
-
if (amount === undefined || amount === null)
|
|
11
|
-
return "—";
|
|
12
|
-
return `₹${amount.toLocaleString("en-IN")}`;
|
|
13
|
-
}
|
|
14
|
-
function relativeTime(date) {
|
|
15
|
-
if (!date)
|
|
16
|
-
return "";
|
|
17
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
18
|
-
const diff = Date.now() - d.getTime();
|
|
19
|
-
const mins = Math.floor(diff / 60000);
|
|
20
|
-
if (mins < 60)
|
|
21
|
-
return `${mins}m ago`;
|
|
22
|
-
const hrs = Math.floor(diff / 3600000);
|
|
23
|
-
if (hrs < 24)
|
|
24
|
-
return `${hrs}h ago`;
|
|
25
|
-
return `${Math.floor(diff / 86400000)}d ago`;
|
|
26
|
-
}
|
|
27
|
-
function expiresIn(expiresAt) {
|
|
28
|
-
if (!expiresAt)
|
|
29
|
-
return "";
|
|
30
|
-
const exp = expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
|
|
31
|
-
const diff = exp.getTime() - Date.now();
|
|
32
|
-
if (diff <= 0)
|
|
33
|
-
return "Expired";
|
|
34
|
-
const hrs = Math.floor(diff / 3600000);
|
|
35
|
-
if (hrs < 1)
|
|
36
|
-
return "< 1h left";
|
|
37
|
-
if (hrs < 24)
|
|
38
|
-
return `${hrs}h left`;
|
|
39
|
-
return `${Math.floor(diff / 86400000)}d left`;
|
|
40
|
-
}
|
|
41
|
-
function statusVariant(status) {
|
|
42
|
-
switch (status) {
|
|
43
|
-
case "pending": return "warning";
|
|
44
|
-
case "accepted": return "success";
|
|
45
|
-
case "declined": return "danger";
|
|
46
|
-
case "countered": return "info";
|
|
47
|
-
case "expired": return "default";
|
|
48
|
-
case "withdrawn": return "default";
|
|
49
|
-
case "paid": return "success";
|
|
50
|
-
default: return "default";
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const PLACEHOLDER_SELLER_NOTE = "Optional note to buyer";
|
|
54
|
-
function OfferCard({ offer, onRespond, onUpdate, onNeedsLogin }) {
|
|
55
|
-
const [uiState, setUiState] = useState("idle");
|
|
56
|
-
const [counterInput, setCounterInput] = useState("");
|
|
57
|
-
const [sellerNote, setSellerNote] = useState("");
|
|
58
|
-
const [error, setError] = useState("");
|
|
59
|
-
const [isPending, startTransition] = useTransition();
|
|
60
|
-
const toAccepting = useCallback(() => setUiState("accepting"), []);
|
|
61
|
-
const toCountering = useCallback(() => setUiState("countering"), []);
|
|
62
|
-
const toDeclining = useCallback(() => setUiState("declining"), []);
|
|
63
|
-
const toIdle = useCallback(() => setUiState("idle"), []);
|
|
64
|
-
const isPendingOffer = offer.status === "pending";
|
|
65
|
-
function submit(action) {
|
|
66
|
-
setError("");
|
|
67
|
-
startTransition(async () => {
|
|
68
|
-
try {
|
|
69
|
-
await onRespond(action);
|
|
70
|
-
onUpdate(offer.id, {
|
|
71
|
-
status: action.action === "accept"
|
|
72
|
-
? "accepted"
|
|
73
|
-
: action.action === "decline"
|
|
74
|
-
? "declined"
|
|
75
|
-
: "countered",
|
|
76
|
-
});
|
|
77
|
-
setUiState("idle");
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
void normalizeError(err);
|
|
81
|
-
if (isAuthError(err)) {
|
|
82
|
-
onNeedsLogin();
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
setError(err instanceof Error ? err.message : "Something went wrong.");
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
function handleAccept() {
|
|
91
|
-
submit({ action: "accept", offerId: offer.id, sellerNote: sellerNote || undefined });
|
|
92
|
-
}
|
|
93
|
-
function handleDecline() {
|
|
94
|
-
submit({ action: "decline", offerId: offer.id, sellerNote: sellerNote || undefined });
|
|
95
|
-
}
|
|
96
|
-
function handleCounter() {
|
|
97
|
-
const amount = parseInt(counterInput, 10);
|
|
98
|
-
if (!amount || amount <= 0) {
|
|
99
|
-
setError("Enter a valid counter amount.");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (amount >= offer.listedPrice) {
|
|
103
|
-
setError("Counter must be below the listed price.");
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (amount === offer.offerAmount) {
|
|
107
|
-
setError("Counter must differ from the buyer's offer.");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
submit({ action: "counter", offerId: offer.id, counterAmount: amount, sellerNote: sellerNote || undefined });
|
|
111
|
-
}
|
|
112
|
-
return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsxs(Row, { align: "start", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: offer.productTitle ?? "Unknown Product" }), _jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: [relativeTime(offer.createdAt), " \u00B7 ", isPendingOffer ? expiresIn(offer.expiresAt) : ""] })] }), _jsx(Badge, { variant: statusVariant(offer.status), size: "xs", className: "shrink-0 capitalize", children: offer.status })] }), _jsxs(Row, { wrap: true, gap: "md", children: [_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "faint", size: "xs", transform: "uppercase", children: "Listed" }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", children: formatRupees(offer.listedPrice) })] }), _jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "faint", size: "xs", transform: "uppercase", children: "Offered" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatRupees(offer.offerAmount) })] }), offer.counterAmount != null && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "faint", size: "xs", transform: "uppercase", children: "Your Counter" }), _jsx(Text, { className: "text-info", size: "sm", weight: "medium", children: formatRupees(offer.counterAmount) })] }))] }), offer.buyerNote && (_jsxs(Text, { className: "italic", color: "muted", size: "xs", children: ["\"", offer.buyerNote, "\""] })), isPendingOffer && uiState === "idle" && (_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Button, { size: "sm", variant: "primary", onClick: toAccepting, disabled: isPending, children: "Accept" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: toCountering, disabled: isPending, border: "strong", children: "Counter" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: toDeclining, disabled: isPending, className: "text-error border border-error/20", children: "Decline" })] })), uiState === "accepting" && (_jsxs(Stack, { gap: "sm", children: [_jsx(Input, { placeholder: PLACEHOLDER_SELLER_NOTE, value: sellerNote, onChange: (e) => setSellerNote(e.target.value), disabled: isPending }), _jsxs(Row, { gap: "sm", children: [_jsx(Button, { size: "sm", variant: "primary", onClick: handleAccept, disabled: isPending, children: isPending ? "Accepting…" : "Confirm Accept" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: toIdle, disabled: isPending, children: ACTIONS.STORE["cancel-form"].label })] })] })), uiState === "declining" && (_jsxs(Stack, { gap: "sm", children: [_jsx(Input, { placeholder: PLACEHOLDER_SELLER_NOTE, value: sellerNote, onChange: (e) => setSellerNote(e.target.value), disabled: isPending }), _jsxs(Row, { gap: "sm", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: handleDecline, disabled: isPending, className: "text-error border border-error/20", children: isPending ? "Declining…" : "Confirm Decline" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: toIdle, disabled: isPending, children: ACTIONS.STORE["cancel-form"].label })] })] })), uiState === "countering" && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Suggest a price between the buyer's offer (", formatRupees(offer.offerAmount), ") and your listed price (", formatRupees(offer.listedPrice), ")."] }), _jsx(Input, { type: "number", placeholder: "Your counter amount (\u20B9)", value: counterInput, onChange: (e) => setCounterInput(e.target.value), disabled: isPending }), _jsx(Input, { placeholder: PLACEHOLDER_SELLER_NOTE, value: sellerNote, onChange: (e) => setSellerNote(e.target.value), disabled: isPending }), _jsxs(Row, { gap: "sm", children: [_jsx(Button, { size: "sm", variant: "secondary", onClick: handleCounter, disabled: isPending, children: isPending ? "Sending…" : "Send Counter" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: toIdle, disabled: isPending, children: ACTIONS.STORE["cancel-form"].label })] })] })), error && _jsx(Alert, { variant: "error", children: _jsx(Text, { size: "xs", children: error }) })] }));
|
|
113
|
-
}
|
|
114
|
-
export function SellerOffersPanel({ fetchEndpoint = SELLER_ENDPOINTS.OFFERS, onRespond, className = "", }) {
|
|
115
|
-
const [offers, setOffers] = useState([]);
|
|
116
|
-
const [loading, setLoading] = useState(true);
|
|
117
|
-
const [fetchError, setFetchError] = useState("");
|
|
118
|
-
const [showLoginModal, setShowLoginModal] = useState(false);
|
|
119
|
-
const [statusFilter, setStatusFilter] = useState("all");
|
|
120
|
-
const loadOffers = useCallback(async () => {
|
|
121
|
-
setLoading(true);
|
|
122
|
-
setFetchError("");
|
|
123
|
-
try {
|
|
124
|
-
const url = statusFilter === "all"
|
|
125
|
-
? fetchEndpoint
|
|
126
|
-
: `${fetchEndpoint}?status=${statusFilter}`;
|
|
127
|
-
const res = await fetch(url);
|
|
128
|
-
if (!res.ok) {
|
|
129
|
-
if (res.status === 401 || res.status === 403) {
|
|
130
|
-
setShowLoginModal(true);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
throw new Error(`Error ${res.status}`);
|
|
134
|
-
}
|
|
135
|
-
const json = (await res.json());
|
|
136
|
-
setOffers(json.items ?? json.offers ?? []);
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
void normalizeError(err);
|
|
140
|
-
setFetchError(err instanceof Error ? err.message : "Failed to load offers.");
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
setLoading(false);
|
|
144
|
-
}
|
|
145
|
-
}, [fetchEndpoint, statusFilter]);
|
|
146
|
-
useEffect(() => { void loadOffers(); }, [loadOffers]);
|
|
147
|
-
function handleUpdate(id, patch) {
|
|
148
|
-
setOffers((prev) => prev.map((o) => (o.id === id ? { ...o, ...patch } : o)));
|
|
149
|
-
}
|
|
150
|
-
const STATUS_FILTERS = [
|
|
151
|
-
{ value: "all", label: "All" },
|
|
152
|
-
{ value: "pending", label: "Pending" },
|
|
153
|
-
{ value: "countered", label: "Countered" },
|
|
154
|
-
{ value: "accepted", label: "Accepted" },
|
|
155
|
-
{ value: "declined", label: "Declined" },
|
|
156
|
-
{ value: "expired", label: "Expired" },
|
|
157
|
-
];
|
|
158
|
-
const pending = offers.filter((o) => o.status === "pending").length;
|
|
159
|
-
return (_jsxs(Stack, { className: `${className}`, gap: "md", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to manage offers. Please log in or create an account to continue." }), _jsxs(Row, { align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Div, { children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", color: "primary", children: "Offers Received" }), pending > 0 && (_jsxs(Text, { className: "text-warning mt-0.5", size: "xs", children: [pending, " pending offer", pending > 1 ? "s" : "", " awaiting your response"] }))] }), _jsx(Button, { size: "sm", variant: "ghost", onClick: loadOffers, disabled: loading, border: "strong", textSize: "xs", children: loading ? "Refreshing…" : ACTIONS.STORE["refresh-offers"].label })] }), _jsx(Row, { wrap: true, gap: "xs", children: STATUS_FILTERS.map((f) => (_jsx(Button, { size: "sm", variant: statusFilter === f.value ? "primary" : "ghost", onClick: () => setStatusFilter(f.value), className: "text-[length:var(--appkit-text-xs)] px-[var(--appkit-space-2)] py-[var(--appkit-space-1)]", children: f.label }, f.value))) }), fetchError && _jsx(Alert, { variant: "error", children: _jsx(Text, { size: "sm", children: fetchError }) }), loading && (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Spinner, { size: "lg" }) })), !loading && offers.length === 0 && (_jsx(Div, { className: "text-center", padding: "y-3xl", children: _jsx(Text, { size: "sm", color: "faint", children: "No offers found" }) })), !loading && offers.length > 0 && (_jsx(Stack, { gap: "3", children: offers.map((offer) => (_jsx(OfferCard, { offer: offer, onRespond: onRespond, onUpdate: handleUpdate, onNeedsLogin: () => setShowLoginModal(true) }, offer.id))) }))] }));
|
|
160
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { ListingLayoutProps } from "../../../ui";
|
|
3
|
-
export interface SellerPreOrdersViewProps extends ListingLayoutProps {
|
|
4
|
-
onDelete?: (id: string) => Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
export declare function SellerPreOrdersView({ children, onDelete, ...props }: SellerPreOrdersViewProps): React.JSX.Element;
|