@mohasinac/appkit 2.4.7 → 2.4.9

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.
Files changed (26) hide show
  1. package/dist/features/admin/components/AdminProductEditorView.js +4 -1
  2. package/dist/features/auth/hooks/useAuth.js +66 -14
  3. package/dist/features/auth/repository/user.repository.d.ts +1 -0
  4. package/dist/features/auth/repository/user.repository.js +10 -6
  5. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +6 -0
  6. package/dist/features/products/components/GroupSettingsPanel.d.ts +13 -0
  7. package/dist/features/products/components/GroupSettingsPanel.js +192 -0
  8. package/dist/features/products/components/PreOrderDetailView.d.ts +3 -1
  9. package/dist/features/products/components/PreOrderDetailView.js +5 -2
  10. package/dist/features/products/components/ProductDetailPageView.js +6 -0
  11. package/dist/features/products/components/ProductDetailView.d.ts +3 -1
  12. package/dist/features/products/components/ProductDetailView.js +7 -3
  13. package/dist/features/products/components/ProductForm.d.ts +6 -1
  14. package/dist/features/products/components/ProductForm.js +2 -2
  15. package/dist/features/products/components/ShowGroupSection.d.ts +8 -0
  16. package/dist/features/products/components/ShowGroupSection.js +56 -0
  17. package/dist/features/products/components/index.d.ts +3 -0
  18. package/dist/features/products/components/index.js +2 -0
  19. package/dist/features/products/repository/products.repository.d.ts +8 -0
  20. package/dist/features/products/repository/products.repository.js +81 -0
  21. package/dist/features/products/schemas/firestore.d.ts +7 -2
  22. package/dist/features/products/schemas/firestore.js +10 -0
  23. package/dist/features/products/types/index.d.ts +5 -0
  24. package/dist/features/wishlist/schemas/index.d.ts +2 -2
  25. package/dist/tailwind-utilities.css +1 -1
  26. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import Link from "next/link";
5
+ import { ROUTES } from "../../../next";
6
+ import { Row, Text, Modal, SideDrawer, Button } from "../../../ui";
7
+ import { formatCurrency } from "../../../utils/number.formatter";
8
+ function memberHref(m) {
9
+ const slug = m.slug ?? m.id;
10
+ if (m.isPreOrder)
11
+ return String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(slug));
12
+ return String(ROUTES.PUBLIC.PRODUCT_DETAIL(slug));
13
+ }
14
+ function MemberThumb({ member, isCurrent }) {
15
+ const image = member.images?.[0] ?? "";
16
+ const href = memberHref(member);
17
+ const price = formatCurrency(member.price, member.currency ?? "INR");
18
+ return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-1.5 flex-shrink-0 w-16 group", children: [_jsxs("div", { className: `relative w-14 h-14 rounded-full overflow-hidden border-2 transition-all ${isCurrent
19
+ ? "border-[var(--appkit-color-primary,#6366f1)] ring-2 ring-[var(--appkit-color-primary,#6366f1)]/30"
20
+ : "border-zinc-200 dark:border-zinc-700 group-hover:border-[var(--appkit-color-primary,#6366f1)]"}`, children: [image ? (
21
+ // eslint-disable-next-line @next/next/no-img-element
22
+ _jsx("img", { src: image, alt: member.title, className: "w-full h-full object-cover", loading: "lazy" })) : (_jsx("div", { className: "w-full h-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-400 text-xs", children: "\u25EF" })), member.isGroupParent && (_jsx("span", { className: "absolute bottom-0 right-0 bg-[var(--appkit-color-primary,#6366f1)] text-white text-[8px] leading-none px-1 py-0.5 rounded-tl", children: "Set" }))] }), _jsx(Text, { className: "text-[10px] text-center text-zinc-600 dark:text-zinc-400 leading-tight line-clamp-2 w-full", children: member.title }), _jsx(Text, { className: "text-[10px] font-semibold text-zinc-800 dark:text-zinc-200", children: price })] }));
23
+ }
24
+ function GroupTableRow({ member }) {
25
+ const href = memberHref(member);
26
+ const price = formatCurrency(member.price, member.currency ?? "INR");
27
+ const image = member.images?.[0] ?? "";
28
+ return (_jsxs("tr", { className: "border-b border-zinc-100 dark:border-zinc-800 last:border-0", children: [_jsx("td", { className: "py-2 pr-3", children: _jsx("div", { className: "w-10 h-10 rounded-full overflow-hidden border border-zinc-200 dark:border-zinc-700", children: image ? (
29
+ // eslint-disable-next-line @next/next/no-img-element
30
+ _jsx("img", { src: image, alt: member.title, className: "w-full h-full object-cover", loading: "lazy" })) : (_jsx("div", { className: "w-full h-full bg-zinc-100 dark:bg-zinc-800" })) }) }), _jsxs("td", { className: "py-2 pr-3", children: [_jsx(Text, { className: "text-sm text-zinc-800 dark:text-zinc-200 font-medium line-clamp-2", children: member.title }), member.isGroupParent && (_jsx("span", { className: "text-[10px] text-[var(--appkit-color-primary,#6366f1)] font-semibold", children: "Parent" }))] }), _jsx("td", { className: "py-2 pr-3", children: _jsx(Text, { className: "text-sm text-zinc-700 dark:text-zinc-300", children: price }) }), _jsx("td", { className: "py-2 pr-3", children: _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 capitalize", children: member.condition ?? "—" }) }), _jsx("td", { className: "py-2", children: _jsx(Link, { href: href, className: "text-xs text-[var(--appkit-color-primary,#6366f1)] hover:underline", children: "View \u2192" }) })] }));
31
+ }
32
+ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle }) {
33
+ const [open, setOpen] = useState(false);
34
+ const [members, setMembers] = useState([]);
35
+ const [loading, setLoading] = useState(false);
36
+ const [showAll, setShowAll] = useState(false);
37
+ useEffect(() => {
38
+ if (!groupId)
39
+ return;
40
+ setLoading(true);
41
+ fetch(`/api/products/group/${encodeURIComponent(groupId)}`)
42
+ .then((r) => r.json())
43
+ .then((res) => setMembers(res.data?.items ?? []))
44
+ .catch(() => { })
45
+ .finally(() => setLoading(false));
46
+ }, [groupId]);
47
+ if (!groupId || loading || members.length <= 1)
48
+ return null;
49
+ const label = groupTitle ?? "Product group";
50
+ const parentLabel = isParent ? `Parts in this group: ${label}` : `Part of: ${label}`;
51
+ const useDrawer = members.length >= 5;
52
+ const tableContent = (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left min-w-[400px]", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-zinc-200 dark:border-zinc-700", children: [_jsx("th", { className: "pb-2 pr-3 text-xs font-semibold text-zinc-500 dark:text-zinc-400", children: "Image" }), _jsx("th", { className: "pb-2 pr-3 text-xs font-semibold text-zinc-500 dark:text-zinc-400", children: "Name" }), _jsx("th", { className: "pb-2 pr-3 text-xs font-semibold text-zinc-500 dark:text-zinc-400", children: "Price" }), _jsx("th", { className: "pb-2 pr-3 text-xs font-semibold text-zinc-500 dark:text-zinc-400", children: "Condition" }), _jsx("th", { className: "pb-2 text-xs font-semibold text-zinc-500 dark:text-zinc-400" })] }) }), _jsx("tbody", { children: members.map((m) => _jsx(GroupTableRow, { member: m }, m.id)) })] }) }));
53
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50/60 dark:bg-zinc-800/40 overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "w-full flex items-center justify-between px-4 py-3 text-left hover:bg-zinc-100/70 dark:hover:bg-zinc-800/70 transition-colors", "aria-expanded": open, children: [_jsxs(Row, { align: "center", gap: "xs", children: [_jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-500 mr-1", children: open ? "▼" : "▶" }), _jsx(Text, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-200", children: parentLabel }), _jsx("span", { className: "ml-1 rounded-full bg-zinc-200 dark:bg-zinc-700 px-2 py-0.5 text-xs text-zinc-600 dark:text-zinc-400", children: members.length })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); setShowAll(true); }, className: "text-xs text-[var(--appkit-color-primary,#6366f1)] hover:underline ml-3 flex-shrink-0", children: "View whole group \u2192" })] }), open && (_jsx("div", { className: "px-4 pb-4 pt-1 overflow-x-auto", children: _jsx("div", { className: "flex gap-3 min-w-0", children: [...members]
54
+ .sort((a) => (a.isGroupParent ? -1 : 0))
55
+ .map((m) => (_jsx(MemberThumb, { member: m, isCurrent: m.slug === currentSlug || m.id === currentSlug }, m.id))) }) }))] }), useDrawer ? (_jsx(SideDrawer, { isOpen: showAll, onClose: () => setShowAll(false), title: label, children: tableContent })) : (_jsx(Modal, { open: showAll, onClose: () => setShowAll(false), title: label, children: tableContent }))] }));
56
+ }
@@ -46,3 +46,6 @@ export { CustomSectionTabContent } from "./CustomSectionTabContent";
46
46
  export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
47
47
  export { SublistingCategorySelect } from "./SublistingCategorySelect";
48
48
  export { SublistingCarouselSection } from "./SublistingCarouselSection";
49
+ export { ShowGroupSection } from "./ShowGroupSection";
50
+ export { GroupSettingsPanel } from "./GroupSettingsPanel";
51
+ export type { GroupSettingsPanelProps } from "./GroupSettingsPanel";
@@ -25,3 +25,5 @@ export { CustomSectionTabContent } from "./CustomSectionTabContent";
25
25
  export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
26
26
  export { SublistingCategorySelect } from "./SublistingCategorySelect";
27
27
  export { SublistingCarouselSection } from "./SublistingCarouselSection";
28
+ export { ShowGroupSection } from "./ShowGroupSection";
29
+ export { GroupSettingsPanel } from "./GroupSettingsPanel";
@@ -28,7 +28,15 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
28
28
  updateAvailableQuantity(productId: string, quantity: number): Promise<void>;
29
29
  updateBid(productId: string, bidAmount: number, bidCount: number): Promise<void>;
30
30
  findAvailable(): Promise<ProductDocument[]>;
31
+ findByGroupId(groupId: string): Promise<ProductDocument[]>;
31
32
  findActiveAuctions(): Promise<ProductDocument[]>;
33
+ startGroup(productId: string, slug: string): Promise<void>;
34
+ updateGroupTitle(productId: string, groupTitle: string): Promise<void>;
35
+ dissolveGroup(groupId: string): Promise<void>;
36
+ linkChildToGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
37
+ unlinkChildFromGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
38
+ leaveGroup(child: ProductDocument, parent: ProductDocument | null): Promise<void>;
39
+ addChildProduct(parent: ProductDocument, child: Omit<import("../schemas/firestore").ProductCreateInput, "groupId" | "isGroupParent" | "groupParentSlug" | "groupTitle">): Promise<ProductDocument>;
32
40
  deleteByStore(storeId: string): Promise<number>;
33
41
  delete(id: string): Promise<void>;
34
42
  static readonly SIEVE_FIELDS: {
@@ -162,6 +162,14 @@ export class ProductRepository extends BaseRepository {
162
162
  .get();
163
163
  return snapshot.docs.map((doc) => this.mapDoc(doc));
164
164
  }
165
+ async findByGroupId(groupId) {
166
+ const snapshot = await this.db
167
+ .collection(this.collection)
168
+ .where("groupId", "==", groupId)
169
+ .where(PRODUCT_FIELDS.IS_AUCTION, "==", false)
170
+ .get();
171
+ return snapshot.docs.map((doc) => this.mapDoc(doc));
172
+ }
165
173
  async findActiveAuctions() {
166
174
  const now = new Date();
167
175
  const snapshot = await this.db
@@ -171,6 +179,79 @@ export class ProductRepository extends BaseRepository {
171
179
  .get();
172
180
  return snapshot.docs.map((doc) => this.mapDoc(doc));
173
181
  }
182
+ async startGroup(productId, slug) {
183
+ await this.getCollection().doc(productId).update({
184
+ groupId: slug,
185
+ isGroupParent: true,
186
+ groupChildSlugs: [],
187
+ });
188
+ }
189
+ async updateGroupTitle(productId, groupTitle) {
190
+ await this.getCollection().doc(productId).update({ groupTitle });
191
+ }
192
+ async dissolveGroup(groupId) {
193
+ const members = await this.findByGroupId(groupId);
194
+ const batch = this.db.batch();
195
+ const clearFields = {
196
+ groupId: null,
197
+ isGroupParent: null,
198
+ groupParentSlug: null,
199
+ groupChildSlugs: null,
200
+ groupTitle: null,
201
+ };
202
+ for (const m of members) {
203
+ batch.update(this.getCollection().doc(m.id), clearFields);
204
+ }
205
+ await batch.commit();
206
+ }
207
+ async linkChildToGroup(parent, child) {
208
+ const batch = this.db.batch();
209
+ batch.update(this.getCollection().doc(child.id), {
210
+ groupId: parent.groupId,
211
+ groupParentSlug: parent.slug ?? parent.id,
212
+ groupTitle: parent.groupTitle ?? null,
213
+ });
214
+ batch.update(this.getCollection().doc(parent.id), {
215
+ groupChildSlugs: [...(parent.groupChildSlugs ?? []), child.id],
216
+ });
217
+ await batch.commit();
218
+ }
219
+ async unlinkChildFromGroup(parent, child) {
220
+ const batch = this.db.batch();
221
+ batch.update(this.getCollection().doc(child.id), {
222
+ groupId: null,
223
+ groupParentSlug: null,
224
+ groupTitle: null,
225
+ });
226
+ const updated = (parent.groupChildSlugs ?? []).filter((s) => s !== child.id && s !== child.slug);
227
+ batch.update(this.getCollection().doc(parent.id), { groupChildSlugs: updated });
228
+ await batch.commit();
229
+ }
230
+ async leaveGroup(child, parent) {
231
+ const batch = this.db.batch();
232
+ batch.update(this.getCollection().doc(child.id), {
233
+ groupId: null,
234
+ groupParentSlug: null,
235
+ groupTitle: null,
236
+ });
237
+ if (parent) {
238
+ const updated = (parent.groupChildSlugs ?? []).filter((s) => s !== child.id && s !== child.slug);
239
+ batch.update(this.getCollection().doc(parent.id), { groupChildSlugs: updated });
240
+ }
241
+ await batch.commit();
242
+ }
243
+ async addChildProduct(parent, child) {
244
+ const newChild = await this.create({
245
+ ...child,
246
+ groupId: parent.groupId,
247
+ isGroupParent: false,
248
+ groupParentSlug: parent.slug ?? parent.id,
249
+ groupTitle: parent.groupTitle,
250
+ });
251
+ const updated = [...(parent.groupChildSlugs ?? []), newChild.id];
252
+ await this.getCollection().doc(parent.id).update({ groupChildSlugs: updated });
253
+ return newChild;
254
+ }
174
255
  async deleteByStore(storeId) {
175
256
  try {
176
257
  const snapshot = await this.getCollection()
@@ -102,6 +102,11 @@ export interface ProductDocument {
102
102
  customFields?: CustomField[];
103
103
  customSections?: CustomSection[];
104
104
  sublistingCategoryId?: string;
105
+ groupId?: string;
106
+ isGroupParent?: boolean;
107
+ groupParentSlug?: string;
108
+ groupChildSlugs?: string[];
109
+ groupTitle?: string;
105
110
  createdAt: Date;
106
111
  updatedAt: Date;
107
112
  }
@@ -117,8 +122,8 @@ export declare const ProductStatusValues: {
117
122
  export declare const PRODUCT_COLLECTION: "products";
118
123
  export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "featured", "isAuction", "isPreOrder", "isPromoted", "isOnSale", "isSold", "createdAt"];
119
124
  export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
120
- 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", "isAuction", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "createdAt"];
121
- 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", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId"];
125
+ 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", "isAuction", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "isPreOrder", "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"];
126
+ 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", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle"];
122
127
  export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
123
128
  export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
124
129
  export type ProductAdminUpdateInput = Partial<Omit<ProductDocument, "id" | "createdAt">>;
@@ -99,6 +99,11 @@ export const PRODUCT_PUBLIC_FIELDS = [
99
99
  "customFields",
100
100
  "customSections",
101
101
  "sublistingCategoryId",
102
+ "groupId",
103
+ "isGroupParent",
104
+ "groupParentSlug",
105
+ "groupChildSlugs",
106
+ "groupTitle",
102
107
  "createdAt",
103
108
  ];
104
109
  export const PRODUCT_UPDATABLE_FIELDS = [
@@ -141,6 +146,11 @@ export const PRODUCT_UPDATABLE_FIELDS = [
141
146
  "customFields",
142
147
  "customSections",
143
148
  "sublistingCategoryId",
149
+ "groupId",
150
+ "isGroupParent",
151
+ "groupParentSlug",
152
+ "groupChildSlugs",
153
+ "groupTitle",
144
154
  ];
145
155
  export const productQueryHelpers = {
146
156
  byStore: (storeId) => ["storeId", "==", storeId],
@@ -44,6 +44,11 @@ export interface ProductItem {
44
44
  isAuction?: boolean;
45
45
  isPreOrder?: boolean;
46
46
  sublistingCategoryId?: string;
47
+ groupId?: string;
48
+ isGroupParent?: boolean;
49
+ groupParentSlug?: string;
50
+ groupChildSlugs?: string[];
51
+ groupTitle?: string;
47
52
  isOnSale?: boolean;
48
53
  isSold?: boolean;
49
54
  inStock?: boolean;
@@ -27,10 +27,10 @@ export declare const wishlistItemSchema: z.ZodObject<{
27
27
  userId: string;
28
28
  productTitle?: string | undefined;
29
29
  productImage?: string | undefined;
30
+ productSlug?: string | undefined;
30
31
  addedAt?: string | undefined;
31
32
  productPrice?: number | undefined;
32
33
  productCurrency?: string | undefined;
33
- productSlug?: string | undefined;
34
34
  productStatus?: string | undefined;
35
35
  }, {
36
36
  id: string;
@@ -38,9 +38,9 @@ export declare const wishlistItemSchema: z.ZodObject<{
38
38
  userId: string;
39
39
  productTitle?: string | undefined;
40
40
  productImage?: string | undefined;
41
+ productSlug?: string | undefined;
41
42
  addedAt?: string | undefined;
42
43
  productPrice?: number | undefined;
43
44
  productCurrency?: string | undefined;
44
- productSlug?: string | undefined;
45
45
  productStatus?: string | undefined;
46
46
  }>;