@mohasinac/appkit 3.2.6 → 3.2.8

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 (53) hide show
  1. package/dist/_internal/shared/actions/action-registry.js +62 -0
  2. package/dist/constants/api-endpoints.d.ts +12 -0
  3. package/dist/constants/api-endpoints.js +4 -0
  4. package/dist/features/about/components/PublicProfileView.js +8 -5
  5. package/dist/features/addresses/repository/addresses.repository.d.ts +14 -1
  6. package/dist/features/addresses/repository/addresses.repository.js +122 -1
  7. package/dist/features/addresses/schemas/firestore.d.ts +22 -3
  8. package/dist/features/addresses/schemas/firestore.js +25 -0
  9. package/dist/features/addresses/schemas/index.d.ts +2 -2
  10. package/dist/features/admin/components/AdminAddressClustersView.d.ts +5 -0
  11. package/dist/features/admin/components/AdminAddressClustersView.js +38 -0
  12. package/dist/features/admin/components/AdminAddressesView.d.ts +5 -0
  13. package/dist/features/admin/components/AdminAddressesView.js +137 -0
  14. package/dist/features/admin/components/AdminPaymentClustersView.d.ts +5 -0
  15. package/dist/features/admin/components/AdminPaymentClustersView.js +37 -0
  16. package/dist/features/admin/components/AdminPaymentMethodsView.d.ts +5 -0
  17. package/dist/features/admin/components/AdminPaymentMethodsView.js +130 -0
  18. package/dist/features/admin/components/index.d.ts +8 -0
  19. package/dist/features/admin/components/index.js +4 -0
  20. package/dist/features/auth/actions/profile-actions.d.ts +3 -1
  21. package/dist/features/auth/actions/profile-actions.js +29 -2
  22. package/dist/features/cart/schemas/index.d.ts +6 -6
  23. package/dist/features/orders/schemas/index.d.ts +4 -4
  24. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +48 -0
  25. package/dist/features/payments/repository/saved-payment-methods.repository.js +236 -0
  26. package/dist/features/payments/schemas/index.d.ts +1 -0
  27. package/dist/features/payments/schemas/index.js +1 -0
  28. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +63 -0
  29. package/dist/features/payments/schemas/saved-methods-firestore.js +60 -0
  30. package/dist/features/payments/server.d.ts +2 -0
  31. package/dist/features/payments/server.js +2 -0
  32. package/dist/features/promotions/schemas/index.d.ts +6 -6
  33. package/dist/features/reviews/repository/reviews.repository.d.ts +12 -0
  34. package/dist/features/reviews/repository/reviews.repository.js +24 -0
  35. package/dist/features/reviews/schemas/firestore.d.ts +5 -1
  36. package/dist/features/reviews/schemas/firestore.js +2 -0
  37. package/dist/features/reviews/schemas/index.d.ts +6 -6
  38. package/dist/features/seller/components/SellerAddressesView.js +7 -3
  39. package/dist/features/support/schemas/index.d.ts +8 -8
  40. package/dist/features/wishlist/schemas/index.d.ts +2 -2
  41. package/dist/index.d.ts +9 -0
  42. package/dist/index.js +10 -0
  43. package/dist/next/routing/route-map.d.ts +8 -0
  44. package/dist/next/routing/route-map.js +4 -0
  45. package/dist/repositories/index.d.ts +1 -0
  46. package/dist/repositories/index.js +1 -0
  47. package/dist/schemas/registry.d.ts +66 -66
  48. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  49. package/dist/security/index.d.ts +1 -1
  50. package/dist/security/index.js +1 -1
  51. package/dist/security/pii-schemas.d.ts +2 -0
  52. package/dist/security/pii-schemas.js +2 -0
  53. package/package.json +1 -2
@@ -0,0 +1,236 @@
1
+ /**
2
+ * SavedPaymentMethodsRepository
3
+ *
4
+ * Persists user payment identifiers (UPI VPAs, card tokens, etc.) for
5
+ * checkout pre-fill and cross-account fraud detection.
6
+ *
7
+ * PII: `identifier` is encrypted at rest (AES-256-GCM) — never returned
8
+ * to clients. `identifierHash` is unencrypted SHA-256 for cross-account
9
+ * dedup queries. `displayLabel` is pre-masked and safe to display.
10
+ */
11
+ import { createHash } from "crypto";
12
+ import { normalizeError } from "../../../errors/normalize";
13
+ import { DatabaseError } from "../../../errors";
14
+ import { serverLogger } from "../../../monitoring";
15
+ import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
16
+ import { PAYMENT_METHOD_PII_FIELDS, decryptPiiFields, encryptPiiFields, } from "../../../security";
17
+ import { SAVED_PAYMENT_METHOD_FIELDS, SAVED_PAYMENT_METHODS_COLLECTION, } from "../schemas/saved-methods-firestore";
18
+ function normaliseIdentifier(s) {
19
+ return s.toLowerCase().replace(/\s+/g, "").trim();
20
+ }
21
+ function computeIdentifierHash(type, identifier) {
22
+ return createHash("sha256")
23
+ .update(`${type}|${normaliseIdentifier(identifier)}`)
24
+ .digest("hex");
25
+ }
26
+ export class SavedPaymentMethodsRepository extends BaseRepository {
27
+ constructor() {
28
+ super(SAVED_PAYMENT_METHODS_COLLECTION);
29
+ }
30
+ decrypt(doc) {
31
+ return decryptPiiFields(doc, [...PAYMENT_METHOD_PII_FIELDS]);
32
+ }
33
+ encrypt(data) {
34
+ return encryptPiiFields(data, [...PAYMENT_METHOD_PII_FIELDS]);
35
+ }
36
+ mapDoc(snap) {
37
+ const raw = super.mapDoc(snap);
38
+ return this.decrypt(raw);
39
+ }
40
+ async createWithId(id, data) {
41
+ return super.createWithId(id, this.encrypt(data));
42
+ }
43
+ async update(id, data) {
44
+ return super.update(id, this.encrypt(data));
45
+ }
46
+ /** List all saved methods for a user. Never returns raw `identifier` — only `displayLabel`. */
47
+ async listByUser(userId) {
48
+ try {
49
+ const snap = await this.getCollection()
50
+ .where(SAVED_PAYMENT_METHOD_FIELDS.USER_ID, "==", userId)
51
+ .orderBy(SAVED_PAYMENT_METHOD_FIELDS.LAST_USED_AT, "desc")
52
+ .get();
53
+ return snap.docs.map((d) => {
54
+ const doc = this.mapDoc(d);
55
+ doc.identifier = ""; // never expose decrypted PII to caller
56
+ return doc;
57
+ });
58
+ }
59
+ catch (error) {
60
+ void normalizeError(error);
61
+ throw new DatabaseError(`Failed to list payment methods for user:${userId}`, error);
62
+ }
63
+ }
64
+ /**
65
+ * Idempotent upsert by (userId + identifierHash).
66
+ * Computes hash + encrypts identifier before write.
67
+ * Updates lastUsedAt on re-add.
68
+ */
69
+ async upsertForUser(userId, input) {
70
+ try {
71
+ const hash = computeIdentifierHash(input.type, input.identifier);
72
+ // Check for existing
73
+ const existing = await this.getCollection()
74
+ .where(SAVED_PAYMENT_METHOD_FIELDS.USER_ID, "==", userId)
75
+ .where(SAVED_PAYMENT_METHOD_FIELDS.IDENTIFIER_HASH, "==", hash)
76
+ .limit(1)
77
+ .get();
78
+ const now = new Date();
79
+ if (!existing.empty) {
80
+ const docId = existing.docs[0].id;
81
+ return this.update(docId, { lastUsedAt: now, displayLabel: input.displayLabel });
82
+ }
83
+ const docRef = this.getCollection().doc();
84
+ const data = {
85
+ userId,
86
+ type: input.type,
87
+ identifier: input.identifier,
88
+ displayLabel: input.displayLabel,
89
+ identifierHash: hash,
90
+ isDefault: input.isDefault ?? false,
91
+ lastUsedAt: now,
92
+ createdAt: now,
93
+ updatedAt: now,
94
+ };
95
+ await docRef.set(prepareForFirestore(this.encrypt(data)));
96
+ serverLogger.info("Saved payment method created", { userId, type: input.type, docId: docRef.id });
97
+ const refetched = await this.findById(docRef.id);
98
+ if (!refetched)
99
+ throw new DatabaseError("Payment method not readable after create");
100
+ refetched.identifier = "";
101
+ return refetched;
102
+ }
103
+ catch (error) {
104
+ void normalizeError(error);
105
+ throw new DatabaseError(`Failed to upsert payment method for user:${userId}`, error);
106
+ }
107
+ }
108
+ /** Cross-account lookup by identifierHash. Returns display-safe docs (identifier stripped). */
109
+ async listByIdentifierHash(hash) {
110
+ try {
111
+ const snap = await this.getCollection()
112
+ .where(SAVED_PAYMENT_METHOD_FIELDS.IDENTIFIER_HASH, "==", hash)
113
+ .get();
114
+ return snap.docs.map((d) => {
115
+ const doc = this.mapDoc(d);
116
+ doc.identifier = "";
117
+ return doc;
118
+ });
119
+ }
120
+ catch (error) {
121
+ void normalizeError(error);
122
+ throw new DatabaseError(`Failed to lookup payment methods by hash`, error);
123
+ }
124
+ }
125
+ /** Batch-ban all methods for a user. Used by hard-ban cascade. */
126
+ async banAllForUser(userId, banData) {
127
+ try {
128
+ const snap = await this.getCollection()
129
+ .where(SAVED_PAYMENT_METHOD_FIELDS.USER_ID, "==", userId)
130
+ .get();
131
+ if (snap.empty)
132
+ return 0;
133
+ const now = new Date();
134
+ const batch = this.db.batch();
135
+ snap.docs.forEach((doc) => {
136
+ batch.update(doc.ref, {
137
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_STATUS]: "banned",
138
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_REASON]: banData.banReason,
139
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_BY]: banData.bannedBy,
140
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_AT]: now,
141
+ [SAVED_PAYMENT_METHOD_FIELDS.AUTO_BANNED]: true,
142
+ [SAVED_PAYMENT_METHOD_FIELDS.UPDATED_AT]: now,
143
+ });
144
+ });
145
+ await batch.commit();
146
+ return snap.size;
147
+ }
148
+ catch (error) {
149
+ void normalizeError(error);
150
+ throw new DatabaseError(`Failed to ban payment methods for user:${userId}`, error);
151
+ }
152
+ }
153
+ /** Reverse auto-ban cascade on user unban. Leaves manually-banned methods untouched. */
154
+ async unbanAutoForUser(userId) {
155
+ try {
156
+ const snap = await this.getCollection()
157
+ .where(SAVED_PAYMENT_METHOD_FIELDS.USER_ID, "==", userId)
158
+ .where(SAVED_PAYMENT_METHOD_FIELDS.AUTO_BANNED, "==", true)
159
+ .get();
160
+ if (snap.empty)
161
+ return 0;
162
+ const now = new Date();
163
+ const batch = this.db.batch();
164
+ snap.docs.forEach((doc) => {
165
+ batch.update(doc.ref, {
166
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_STATUS]: null,
167
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_REASON]: null,
168
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_BY]: null,
169
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_AT]: null,
170
+ [SAVED_PAYMENT_METHOD_FIELDS.AUTO_BANNED]: null,
171
+ [SAVED_PAYMENT_METHOD_FIELDS.UPDATED_AT]: now,
172
+ });
173
+ });
174
+ await batch.commit();
175
+ return snap.size;
176
+ }
177
+ catch (error) {
178
+ void normalizeError(error);
179
+ throw new DatabaseError(`Failed to unban payment methods for user:${userId}`, error);
180
+ }
181
+ }
182
+ /** List by banStatus for admin view. Returns display-safe docs. */
183
+ async listByBanStatus(banStatus, limit = 50, offset = 0) {
184
+ try {
185
+ const snap = await this.getCollection()
186
+ .where(SAVED_PAYMENT_METHOD_FIELDS.BAN_STATUS, "==", banStatus)
187
+ .orderBy(SAVED_PAYMENT_METHOD_FIELDS.BANNED_AT, "desc")
188
+ .limit(limit)
189
+ .offset(offset)
190
+ .get();
191
+ return snap.docs.map((d) => {
192
+ const doc = this.mapDoc(d);
193
+ doc.identifier = "";
194
+ return doc;
195
+ });
196
+ }
197
+ catch (error) {
198
+ void normalizeError(error);
199
+ throw new DatabaseError(`Failed to list payment methods by banStatus:${banStatus}`, error);
200
+ }
201
+ }
202
+ async banById(id, banData) {
203
+ await this.update(id, {
204
+ banStatus: "banned",
205
+ banReason: banData.banReason,
206
+ bannedBy: banData.bannedBy,
207
+ bannedAt: new Date(),
208
+ autoBanned: false,
209
+ });
210
+ }
211
+ async clearBanById(id) {
212
+ const now = new Date();
213
+ await this.db.collection(SAVED_PAYMENT_METHODS_COLLECTION).doc(id).update({
214
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_STATUS]: null,
215
+ [SAVED_PAYMENT_METHOD_FIELDS.BAN_REASON]: null,
216
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_BY]: null,
217
+ [SAVED_PAYMENT_METHOD_FIELDS.BANNED_AT]: null,
218
+ [SAVED_PAYMENT_METHOD_FIELDS.AUTO_BANNED]: null,
219
+ [SAVED_PAYMENT_METHOD_FIELDS.UNBAN_REQUEST_NOTE]: null,
220
+ [SAVED_PAYMENT_METHOD_FIELDS.UNBAN_REQUESTED_AT]: null,
221
+ [SAVED_PAYMENT_METHOD_FIELDS.UPDATED_AT]: now,
222
+ });
223
+ }
224
+ async deleteForUser(userId, id) {
225
+ const existing = await this.findById(id);
226
+ if (!existing || existing.userId !== userId) {
227
+ throw new DatabaseError(`Payment method ${id} not found for user:${userId}`);
228
+ }
229
+ await this.delete(id);
230
+ }
231
+ /** Public wrapper — compute identifier hash from outside the class (e.g. in API routes). */
232
+ computeIdentifierHash(type, identifier) {
233
+ return computeIdentifierHash(type, identifier);
234
+ }
235
+ }
236
+ export const savedPaymentMethodsRepository = new SavedPaymentMethodsRepository();
@@ -1,4 +1,5 @@
1
1
  export * from "./firestore";
2
+ export * from "./saved-methods-firestore";
2
3
  import { z } from "zod";
3
4
  export declare const payoutStatusEnumSchema: z.ZodEnum<["pending", "processing", "completed", "failed"]>;
4
5
  export declare const payoutPaymentMethodSchema: z.ZodEnum<["bank_transfer", "upi"]>;
@@ -1,4 +1,5 @@
1
1
  export * from "./firestore";
2
+ export * from "./saved-methods-firestore";
2
3
  import { z } from "zod";
3
4
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
4
5
  import { auditTimestampsShape, firestoreDateSchema, paiseSchema } from "../../../schemas/firestore-helpers";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * SavedPaymentMethod Firestore Document Types & Constants
3
+ *
4
+ * Stores user-saved payment identifiers (UPI VPAs, card tokens, bank accounts)
5
+ * for checkout pre-fill and cross-account fraud detection.
6
+ *
7
+ * PII: `identifier` (full UPI VPA / card PAN) is encrypted at rest via
8
+ * encryptPiiFields. `identifierHash` is a deterministic SHA-256 of the
9
+ * normalised identifier and stored unencrypted for cross-account queries.
10
+ * `displayLabel` is a pre-masked string and never contains full PII.
11
+ */
12
+ import type { BaseDocument } from "../../../_internal/shared/types/base-document";
13
+ export type SavedPaymentMethodType = "upi" | "card" | "bank_account" | "wallet";
14
+ export type SavedPaymentMethodBanStatus = "banned" | "suspicious" | "unban_requested";
15
+ export interface SavedPaymentMethodDocument extends BaseDocument {
16
+ userId: string;
17
+ type: SavedPaymentMethodType;
18
+ /** Full UPI VPA / card PAN / account number — ENCRYPTED at rest. */
19
+ identifier: string;
20
+ /** Pre-masked display string, never contains full PII. e.g. "9876...@paytm", "XXXX 4321 (HDFC)" */
21
+ displayLabel: string;
22
+ /** SHA-256(type|normalised(identifier)) — unencrypted; enables cross-account dedup without decrypting. */
23
+ identifierHash: string;
24
+ isDefault?: boolean;
25
+ lastUsedAt?: Date;
26
+ banStatus?: SavedPaymentMethodBanStatus;
27
+ banReason?: string;
28
+ bannedBy?: string;
29
+ bannedAt?: Date;
30
+ autoBanned?: boolean;
31
+ unbanRequestNote?: string;
32
+ unbanRequestedAt?: Date;
33
+ }
34
+ export declare const SAVED_PAYMENT_METHODS_COLLECTION: "savedPaymentMethods";
35
+ export declare const SAVED_PAYMENT_METHOD_INDEXED_FIELDS: readonly ["userId", "identifierHash", "banStatus", "type", "createdAt"];
36
+ export declare const SAVED_PAYMENT_METHOD_PUBLIC_FIELDS: readonly ["id", "userId", "type", "displayLabel", "identifierHash", "isDefault", "lastUsedAt", "banStatus", "banReason", "bannedAt", "unbanRequestNote", "unbanRequestedAt", "createdAt", "updatedAt"];
37
+ export declare const SAVED_PAYMENT_METHOD_UPDATABLE_FIELDS: readonly ["displayLabel", "isDefault", "lastUsedAt", "unbanRequestNote", "unbanRequestedAt"];
38
+ export declare const SAVED_PAYMENT_METHOD_FIELDS: {
39
+ readonly USER_ID: "userId";
40
+ readonly TYPE: "type";
41
+ readonly IDENTIFIER: "identifier";
42
+ readonly DISPLAY_LABEL: "displayLabel";
43
+ readonly IDENTIFIER_HASH: "identifierHash";
44
+ readonly IS_DEFAULT: "isDefault";
45
+ readonly LAST_USED_AT: "lastUsedAt";
46
+ readonly BAN_STATUS: "banStatus";
47
+ readonly BAN_REASON: "banReason";
48
+ readonly BANNED_BY: "bannedBy";
49
+ readonly BANNED_AT: "bannedAt";
50
+ readonly AUTO_BANNED: "autoBanned";
51
+ readonly UNBAN_REQUEST_NOTE: "unbanRequestNote";
52
+ readonly UNBAN_REQUESTED_AT: "unbanRequestedAt";
53
+ readonly CREATED_AT: "createdAt";
54
+ readonly UPDATED_AT: "updatedAt";
55
+ };
56
+ export type SavedPaymentMethodCreateInput = {
57
+ type: SavedPaymentMethodType;
58
+ /** Raw identifier — will be encrypted + hashed by repository. */
59
+ identifier: string;
60
+ displayLabel: string;
61
+ isDefault?: boolean;
62
+ };
63
+ export type SavedPaymentMethodUpdateInput = Partial<Pick<SavedPaymentMethodDocument, (typeof SAVED_PAYMENT_METHOD_UPDATABLE_FIELDS)[number]>>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SavedPaymentMethod Firestore Document Types & Constants
3
+ *
4
+ * Stores user-saved payment identifiers (UPI VPAs, card tokens, bank accounts)
5
+ * for checkout pre-fill and cross-account fraud detection.
6
+ *
7
+ * PII: `identifier` (full UPI VPA / card PAN) is encrypted at rest via
8
+ * encryptPiiFields. `identifierHash` is a deterministic SHA-256 of the
9
+ * normalised identifier and stored unencrypted for cross-account queries.
10
+ * `displayLabel` is a pre-masked string and never contains full PII.
11
+ */
12
+ export const SAVED_PAYMENT_METHODS_COLLECTION = "savedPaymentMethods";
13
+ export const SAVED_PAYMENT_METHOD_INDEXED_FIELDS = [
14
+ "userId",
15
+ "identifierHash",
16
+ "banStatus",
17
+ "type",
18
+ "createdAt",
19
+ ];
20
+ export const SAVED_PAYMENT_METHOD_PUBLIC_FIELDS = [
21
+ "id",
22
+ "userId",
23
+ "type",
24
+ "displayLabel",
25
+ "identifierHash",
26
+ "isDefault",
27
+ "lastUsedAt",
28
+ "banStatus",
29
+ "banReason",
30
+ "bannedAt",
31
+ "unbanRequestNote",
32
+ "unbanRequestedAt",
33
+ "createdAt",
34
+ "updatedAt",
35
+ ];
36
+ export const SAVED_PAYMENT_METHOD_UPDATABLE_FIELDS = [
37
+ "displayLabel",
38
+ "isDefault",
39
+ "lastUsedAt",
40
+ "unbanRequestNote",
41
+ "unbanRequestedAt",
42
+ ];
43
+ export const SAVED_PAYMENT_METHOD_FIELDS = {
44
+ USER_ID: "userId",
45
+ TYPE: "type",
46
+ IDENTIFIER: "identifier",
47
+ DISPLAY_LABEL: "displayLabel",
48
+ IDENTIFIER_HASH: "identifierHash",
49
+ IS_DEFAULT: "isDefault",
50
+ LAST_USED_AT: "lastUsedAt",
51
+ BAN_STATUS: "banStatus",
52
+ BAN_REASON: "banReason",
53
+ BANNED_BY: "bannedBy",
54
+ BANNED_AT: "bannedAt",
55
+ AUTO_BANNED: "autoBanned",
56
+ UNBAN_REQUEST_NOTE: "unbanRequestNote",
57
+ UNBAN_REQUESTED_AT: "unbanRequestedAt",
58
+ CREATED_AT: "createdAt",
59
+ UPDATED_AT: "updatedAt",
60
+ };
@@ -5,3 +5,5 @@
5
5
  */
6
6
  export { PaymentsRepository } from "./repository/payments.repository";
7
7
  export { payoutRepository } from "./repository/payout.repository";
8
+ export { savedPaymentMethodsRepository, SavedPaymentMethodsRepository, } from "./repository/saved-payment-methods.repository";
9
+ export * from "./schemas/saved-methods-firestore";
@@ -5,3 +5,5 @@
5
5
  */
6
6
  export { PaymentsRepository } from "./repository/payments.repository";
7
7
  export { payoutRepository } from "./repository/payout.repository";
8
+ export { savedPaymentMethodsRepository, SavedPaymentMethodsRepository, } from "./repository/saved-payment-methods.repository";
9
+ export * from "./schemas/saved-methods-firestore";
@@ -405,22 +405,22 @@ export declare const couponUsageFirestoreSchema: z.ZodObject<{
405
405
  orders: z.ZodArray<z.ZodString, "many">;
406
406
  }, "strip", z.ZodTypeAny, {
407
407
  id: string;
408
- orders: string[];
409
408
  userId: string;
410
- couponCode: string;
411
- usageCount: number;
412
409
  lastUsedAt: string | Date | z.objectOutputType<{
413
410
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
414
411
  }, z.ZodTypeAny, "passthrough">;
415
- }, {
416
- id: string;
417
412
  orders: string[];
418
- userId: string;
419
413
  couponCode: string;
420
414
  usageCount: number;
415
+ }, {
416
+ id: string;
417
+ userId: string;
421
418
  lastUsedAt: string | Date | z.objectInputType<{
422
419
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
423
420
  }, z.ZodTypeAny, "passthrough">;
421
+ orders: string[];
422
+ couponCode: string;
423
+ usageCount: number;
424
424
  }>;
425
425
  export declare const claimedCouponStatusSchema: z.ZodEnum<["active", "expired", "used"]>;
426
426
  export declare const claimedCouponSourceSchema: z.ZodEnum<["manual", "promo", "spin", "raffle", "prize-draw"]>;
@@ -35,6 +35,10 @@ declare class ReviewRepository extends BaseRepository<ReviewDocument> {
35
35
  * Cloud Functions compatibility: approved review count + average by store.
36
36
  */
37
37
  getApprovedRatingAggregateByStore(storeId: string): Promise<ReviewRatingAggregate>;
38
+ /** Find reviews where this user is the reviewee (seller→buyer reviews received by a buyer). */
39
+ findByReviewee(revieweeId: string): Promise<ReviewDocument[]>;
40
+ /** Find reviews written by a user filtered by their role as reviewer. */
41
+ findByUserAsRole(userId: string, role: "buyer" | "seller"): Promise<ReviewDocument[]>;
38
42
  static readonly SIEVE_FIELDS: {
39
43
  id: {
40
44
  canFilter: boolean;
@@ -68,6 +72,14 @@ declare class ReviewRepository extends BaseRepository<ReviewDocument> {
68
72
  canFilter: boolean;
69
73
  canSort: boolean;
70
74
  };
75
+ revieweeId: {
76
+ canFilter: boolean;
77
+ canSort: boolean;
78
+ };
79
+ reviewerRole: {
80
+ canFilter: boolean;
81
+ canSort: boolean;
82
+ };
71
83
  status: {
72
84
  canFilter: boolean;
73
85
  canSort: boolean;
@@ -8,6 +8,8 @@ const REVIEW_FIELDS = {
8
8
  STATUS: "status",
9
9
  FEATURED: "featured",
10
10
  CREATED_AT: "createdAt",
11
+ REVIEWEE_ID: "revieweeId",
12
+ REVIEWER_ROLE: "reviewerRole",
11
13
  };
12
14
  class ReviewRepository extends BaseRepository {
13
15
  constructor() {
@@ -197,6 +199,26 @@ class ReviewRepository extends BaseRepository {
197
199
  const avgRating = Math.round((sum / count) * 10) / 10;
198
200
  return { count, avgRating };
199
201
  }
202
+ /** Find reviews where this user is the reviewee (seller→buyer reviews received by a buyer). */
203
+ async findByReviewee(revieweeId) {
204
+ const snapshot = await this.db
205
+ .collection(this.collection)
206
+ .where(REVIEW_FIELDS.REVIEWEE_ID, "==", revieweeId)
207
+ .where(REVIEW_FIELDS.STATUS, "==", "approved")
208
+ .orderBy(REVIEW_FIELDS.CREATED_AT, "desc")
209
+ .get();
210
+ return snapshot.docs.map((doc) => this.mapDoc(doc));
211
+ }
212
+ /** Find reviews written by a user filtered by their role as reviewer. */
213
+ async findByUserAsRole(userId, role) {
214
+ const snapshot = await this.db
215
+ .collection(this.collection)
216
+ .where(REVIEW_FIELDS.USER_ID, "==", userId)
217
+ .where(REVIEW_FIELDS.REVIEWER_ROLE, "==", role)
218
+ .orderBy(REVIEW_FIELDS.CREATED_AT, "desc")
219
+ .get();
220
+ return snapshot.docs.map((doc) => this.mapDoc(doc));
221
+ }
200
222
  async listForProduct(productId, model) {
201
223
  const baseQuery = this.getCollection().where(REVIEW_FIELDS.PRODUCT_ID, "==", productId);
202
224
  const result = await this.sieveQuery(model, ReviewRepository.SIEVE_FIELDS, {
@@ -232,6 +254,8 @@ ReviewRepository.SIEVE_FIELDS = {
232
254
  userName: { canFilter: false, canSort: false },
233
255
  storeId: { canFilter: true, canSort: false },
234
256
  storeName: { canFilter: true, canSort: false },
257
+ revieweeId: { canFilter: true, canSort: false },
258
+ reviewerRole: { canFilter: true, canSort: false },
235
259
  status: { canFilter: true, canSort: true },
236
260
  rating: { canFilter: true, canSort: true },
237
261
  verified: { canFilter: true, canSort: false },
@@ -46,9 +46,13 @@ export interface ReviewDocument extends BaseDocument {
46
46
  sellerRepliedAt?: Date;
47
47
  approvedAt?: Date;
48
48
  rejectedAt?: Date;
49
+ /** Set only on seller→buyer reviews. The userId of the buyer being reviewed. */
50
+ revieweeId?: string;
51
+ /** Absent on legacy docs = "buyer". Distinguishes seller→buyer reviews from buyer→product reviews. */
52
+ reviewerRole?: "buyer" | "seller";
49
53
  }
50
54
  export declare const REVIEW_COLLECTION: "reviews";
51
- export declare const REVIEW_INDEXED_FIELDS: readonly ["productId", "storeId", "userId", "status", "rating", "verified", "featured", "createdAt"];
55
+ export declare const REVIEW_INDEXED_FIELDS: readonly ["productId", "storeId", "userId", "revieweeId", "reviewerRole", "status", "rating", "verified", "featured", "createdAt"];
52
56
  export declare const DEFAULT_REVIEW_DATA: Partial<ReviewDocument>;
53
57
  export declare const REVIEW_PUBLIC_FIELDS: readonly ["id", "productId", "userName", "userAvatar", "rating", "title", "comment", "helpfulCount", "verified", "images", "createdAt"];
54
58
  export declare const REVIEW_UPDATABLE_FIELDS: readonly ["rating", "title", "comment", "images"];
@@ -13,6 +13,8 @@ export const REVIEW_INDEXED_FIELDS = [
13
13
  "productId",
14
14
  "storeId",
15
15
  "userId",
16
+ "revieweeId",
17
+ "reviewerRole",
16
18
  "status",
17
19
  "rating",
18
20
  "verified",
@@ -108,10 +108,10 @@ export declare const reviewFirestoreSchema: z.ZodObject<{
108
108
  updatedAt: string | Date | z.objectOutputType<{
109
109
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
110
110
  }, z.ZodTypeAny, "passthrough">;
111
+ userId: string;
111
112
  productId: string;
112
113
  productTitle: string;
113
114
  rating: number;
114
- userId: string;
115
115
  userName: string;
116
116
  comment: string;
117
117
  helpfulCount: number;
@@ -155,10 +155,10 @@ export declare const reviewFirestoreSchema: z.ZodObject<{
155
155
  updatedAt: string | Date | z.objectInputType<{
156
156
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
157
157
  }, z.ZodTypeAny, "passthrough">;
158
+ userId: string;
158
159
  productId: string;
159
160
  productTitle: string;
160
161
  rating: number;
161
- userId: string;
162
162
  userName: string;
163
163
  comment: string;
164
164
  helpfulCount: number;
@@ -288,9 +288,9 @@ export declare const reviewSchema: z.ZodObject<{
288
288
  }, "strip", z.ZodTypeAny, {
289
289
  status: "pending" | "approved" | "rejected";
290
290
  id: string;
291
+ userId: string;
291
292
  productId: string;
292
293
  rating: 3 | 1 | 2 | 4 | 5;
293
- userId: string;
294
294
  userName: string;
295
295
  video?: {
296
296
  url: string;
@@ -318,9 +318,9 @@ export declare const reviewSchema: z.ZodObject<{
318
318
  }, {
319
319
  status: "pending" | "approved" | "rejected";
320
320
  id: string;
321
+ userId: string;
321
322
  productId: string;
322
323
  rating: 3 | 1 | 2 | 4 | 5;
323
- userId: string;
324
324
  userName: string;
325
325
  video?: {
326
326
  url: string;
@@ -364,9 +364,9 @@ export declare const reviewListParamsSchema: z.ZodObject<{
364
364
  featured?: boolean | undefined;
365
365
  page?: number | undefined;
366
366
  perPage?: number | undefined;
367
+ userId?: string | undefined;
367
368
  productId?: string | undefined;
368
369
  rating?: number | undefined;
369
- userId?: string | undefined;
370
370
  }, {
371
371
  status?: "pending" | "approved" | "rejected" | undefined;
372
372
  sort?: string | undefined;
@@ -374,7 +374,7 @@ export declare const reviewListParamsSchema: z.ZodObject<{
374
374
  featured?: boolean | undefined;
375
375
  page?: number | undefined;
376
376
  perPage?: number | undefined;
377
+ userId?: string | undefined;
377
378
  productId?: string | undefined;
378
379
  rating?: number | undefined;
379
- userId?: string | undefined;
380
380
  }>;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState } from "react";
4
4
  import { MapPin, Pencil, Plus, Trash2, Star } from "lucide-react";
5
- import { Button, ConfirmDeleteModal, Div, Grid, Heading, Label, Row, SideDrawer, Span, Stack, Table, Thead, Tbody, Tr, Th, Td, Text } from "../../../ui";
5
+ import { Badge, Button, ConfirmDeleteModal, Div, Grid, Heading, Label, Row, SideDrawer, Span, Stack, Table, Thead, Tbody, Tr, Th, Td, Text } from "../../../ui";
6
6
  import { FieldInput } from "../../../ui/forms/FieldInput";
7
7
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
8
8
  import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
@@ -50,7 +50,8 @@ function fromDoc(doc) {
50
50
  };
51
51
  }
52
52
  function AddressCard({ address, onEdit, onDelete, }) {
53
- return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "sm", children: [_jsxs(Row, { align: "start", justify: "between", gap: "xs", children: [_jsxs(Row, { gap: "xs", className: "min-w-0", children: [_jsx(MapPin, { className: "h-4 w-4 shrink-0 text-[var(--appkit-color-primary)]" }), _jsx(Span, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: address.label }), address.isDefault && (_jsxs(Span, { size: "xs", weight: "medium", className: CLS_DEFAULT_PILL, children: [_jsx(Star, { className: "h-3 w-3" }), "Default"] }))] }), _jsxs(Row, { gap: "px", className: "shrink-0", children: [_jsx("button", { type: "button", onClick: onEdit, title: "Edit address", className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: onDelete, title: "Delete address", className: CLS_DELETE_BTN, children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }), _jsxs(Text, { size: "sm", color: "muted", children: [address.fullName, " \u00B7 ", address.phone] }), _jsxs(Text, { className: "leading-relaxed", color: "muted", size: "xs", children: [address.addressLine1, address.addressLine2 ? `, ${address.addressLine2}` : "", address.landmark ? ` (near ${address.landmark})` : "", _jsx("br", {}), address.city, ", ", address.state, " ", address.postalCode, ", ", address.country] })] }));
53
+ const isBanned = address.banStatus === "banned";
54
+ return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "sm", children: [_jsxs(Row, { align: "start", justify: "between", gap: "xs", children: [_jsxs(Row, { gap: "xs", wrap: true, className: "min-w-0", children: [_jsx(MapPin, { className: "h-4 w-4 shrink-0 text-[var(--appkit-color-primary)]" }), _jsx(Span, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: address.label }), address.isDefault && (_jsxs(Span, { size: "xs", weight: "medium", className: CLS_DEFAULT_PILL, children: [_jsx(Star, { className: "h-3 w-3" }), "Default"] })), isBanned && _jsx(Badge, { variant: "danger", size: "sm", children: "Banned" })] }), _jsx(Row, { gap: "px", className: "shrink-0", children: isBanned ? (_jsx(Span, { size: "xs", color: "muted", title: "Contact support to resolve address ban", children: "Contact support" })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: onEdit, title: "Edit address", className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors", children: _jsx(Pencil, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: onDelete, title: "Delete address", className: CLS_DELETE_BTN, children: _jsx(Trash2, { className: "h-4 w-4" }) })] })) })] }), _jsxs(Text, { size: "sm", color: "muted", children: [address.fullName, " \u00B7 ", address.phone] }), _jsxs(Text, { className: "leading-relaxed", color: "muted", size: "xs", children: [address.addressLine1, address.addressLine2 ? `, ${address.addressLine2}` : "", address.landmark ? ` (near ${address.landmark})` : "", _jsx("br", {}), address.city, ", ", address.state, " ", address.postalCode, ", ", address.country] })] }));
54
55
  }
55
56
  // ---------------------------------------------------------------------------
56
57
  // Main Component
@@ -143,5 +144,8 @@ export function SellerAddressesView({ apiBase = SELLER_ENDPOINTS.STORE_ADDRESSES
143
144
  const handleDelete = (addr) => setDeleteTargetAddr(addr);
144
145
  const set = (key, value) => setDraft((p) => ({ ...p, [key]: value }));
145
146
  const setField = (key) => (value) => set(key, value);
146
- return (_jsxs(Div, { className: "min-h-screen", children: [_jsxs(Row, { border: "default", justify: "between", className: "sticky top-[var(--header-height,0px)] z-10 backdrop-blur-sm border-b", surface: "default", padding: "inline", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: "Pickup Addresses" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: "Manage your store's pickup and return locations" })] }), _jsxs(Button, { gap: "sm", size: "sm", onClick: openAdd, children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx(Span, { children: "Add Address" })] })] }), _jsxs(Div, { paddingX: "x-md-lg", className: "max-w-2xl", padding: "y-lg", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), isLoading ? (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })) : addresses.length === 0 ? (_jsxs(Stack, { border: "default", className: "border-2 border-dashed", padding: "y-4xl", align: "center", gap: "3", rounded: "xl", children: [_jsx(MapPin, { className: "h-8 w-8 text-zinc-300 dark:text-slate-600" }), _jsx(Text, { size: "sm", color: "muted", children: "No pickup addresses yet" }), _jsx(Button, { size: "sm", variant: "outline", onClick: openAdd, children: "Add your first address" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Row, { justify: "end", className: "mb-2", children: [_jsx(Button, { size: "sm", variant: listView === "table" ? "primary" : "ghost", onClick: () => setListView("table"), children: "Table" }), _jsx(Button, { size: "sm", variant: listView === "cards" ? "primary" : "ghost", onClick: () => setListView("cards"), children: "Cards" })] }), listView === "cards" ? (_jsx(Grid, { gap: "sm", children: addresses.map((addr) => (_jsx(Div, { className: deletingId === addr.id ? "opacity-50 pointer-events-none" : "", children: _jsx(AddressCard, { address: addr, onEdit: () => openEdit(addr), onDelete: () => handleDelete(addr) }) }, addr.id))) })) : (_jsx(Div, { className: `${__O.xAuto}`, rounded: "lg", border: "default", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { surface: "muted", children: _jsxs(Tr, { children: [_jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Label" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Name" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "City" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Phone" }), _jsx(Th, { className: "text-right", padding: "sm", weight: "semibold", children: "Actions" })] }) }), _jsx(Tbody, { children: addresses.map((addr) => (_jsxs(Tr, { className: `border-t border-[var(--appkit-color-border)] ${deletingId === addr.id ? "opacity-50" : ""}`, children: [_jsx(Td, { padding: "sm", children: addr.label }), _jsx(Td, { padding: "sm", children: addr.fullName }), _jsxs(Td, { padding: "sm", children: [addr.city, ", ", addr.state] }), _jsx(Td, { className: "tabular-nums", padding: "sm", children: addr.phone }), _jsx(Td, { className: "text-right", padding: "sm", children: _jsxs(Row, { justify: "end", gap: "xs", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => openEdit(addr), children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleDelete(addr), children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label })] }) })] }, addr.id))) })] }) }))] }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer: _jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, className: "flex-1", children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, className: "flex-1", children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs(Stack, { gap: "md", padding: "y-2xs", children: [saveError && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError })), _jsx(FieldInput, { name: "label", label: "Label *", hint: "e.g. Warehouse, Shop, Home", type: "text", value: draft.label, onChange: setField("label"), placeholder: "Warehouse", maxLength: 60 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "fullName", label: "Full Name *", type: "text", value: draft.fullName, onChange: setField("fullName"), placeholder: "Ravi Kumar", maxLength: 100 }), _jsx(FieldInput, { name: "phone", label: "Phone *", type: "tel", value: draft.phone, onChange: setField("phone"), placeholder: "+91 98765 43210", maxLength: 20 })] }), _jsx(FieldInput, { name: "addressLine1", label: "Address Line 1 *", type: "text", value: draft.addressLine1, onChange: setField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200 }), _jsx(FieldInput, { name: "addressLine2", label: "Address Line 2", type: "text", value: draft.addressLine2, onChange: setField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200 }), _jsx(FieldInput, { name: "landmark", label: "Landmark", type: "text", value: draft.landmark, onChange: setField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "city", label: "City *", type: "text", value: draft.city, onChange: setField("city"), placeholder: "Mumbai", maxLength: 100 }), _jsx(FieldInput, { name: "state", label: "State *", type: "text", value: draft.state, onChange: setField("state"), placeholder: "Maharashtra", maxLength: 100 })] }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "postalCode", label: "Postal Code *", type: "text", value: draft.postalCode, onChange: setField("postalCode"), placeholder: "400001", maxLength: 10 }), _jsx(FieldInput, { name: "country", label: "Country *", type: "text", value: draft.country, onChange: setField("country"), placeholder: "India", maxLength: 60 })] }), _jsx(FieldCheckbox, { name: "isDefault", label: "Set as default pickup address", checked: draft.isDefault, onChange: (c) => set("isDefault", c) })] }) }), deleteTargetAddr && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Address", message: `Delete address "${deleteTargetAddr.label}"? This cannot be undone.`, onConfirm: () => { deleteById(deleteTargetAddr.id); setDeleteTargetAddr(null); }, onClose: () => setDeleteTargetAddr(null), isDeleting: deletingId === deleteTargetAddr.id }))] }));
147
+ return (_jsxs(Div, { className: "min-h-screen", children: [_jsxs(Row, { border: "default", justify: "between", className: "sticky top-[var(--header-height,0px)] z-10 backdrop-blur-sm border-b", surface: "default", padding: "inline", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: "Pickup Addresses" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: "Manage your store's pickup and return locations" })] }), _jsxs(Button, { gap: "sm", size: "sm", onClick: openAdd, children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx(Span, { children: "Add Address" })] })] }), _jsxs(Div, { paddingX: "x-md-lg", className: "max-w-2xl", padding: "y-lg", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), isLoading ? (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })) : addresses.length === 0 ? (_jsxs(Stack, { border: "default", className: "border-2 border-dashed", padding: "y-4xl", align: "center", gap: "3", rounded: "xl", children: [_jsx(MapPin, { className: "h-8 w-8 text-zinc-300 dark:text-slate-600" }), _jsx(Text, { size: "sm", color: "muted", children: "No pickup addresses yet" }), _jsx(Button, { size: "sm", variant: "outline", onClick: openAdd, children: "Add your first address" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Row, { justify: "end", className: "mb-2", children: [_jsx(Button, { size: "sm", variant: listView === "table" ? "primary" : "ghost", onClick: () => setListView("table"), children: "Table" }), _jsx(Button, { size: "sm", variant: listView === "cards" ? "primary" : "ghost", onClick: () => setListView("cards"), children: "Cards" })] }), listView === "cards" ? (_jsx(Grid, { gap: "sm", children: addresses.map((addr) => (_jsx(Div, { className: deletingId === addr.id ? "opacity-50 pointer-events-none" : "", children: _jsx(AddressCard, { address: addr, onEdit: () => openEdit(addr), onDelete: () => handleDelete(addr) }) }, addr.id))) })) : (_jsx(Div, { className: `${__O.xAuto}`, rounded: "lg", border: "default", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { surface: "muted", children: _jsxs(Tr, { children: [_jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Label" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Name" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "City" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Phone" }), _jsx(Th, { className: "text-right", padding: "sm", weight: "semibold", children: "Actions" })] }) }), _jsx(Tbody, { children: addresses.map((addr) => {
148
+ const isBanned = addr.banStatus === "banned";
149
+ return (_jsxs(Tr, { className: `border-t border-[var(--appkit-color-border)] ${deletingId === addr.id ? "opacity-50" : ""}`, children: [_jsx(Td, { padding: "sm", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { children: addr.label }), isBanned && _jsx(Badge, { variant: "danger", size: "sm", children: "Banned" })] }) }), _jsx(Td, { padding: "sm", children: addr.fullName }), _jsxs(Td, { padding: "sm", children: [addr.city, ", ", addr.state] }), _jsx(Td, { className: "tabular-nums", padding: "sm", children: addr.phone }), _jsx(Td, { className: "text-right", padding: "sm", children: isBanned ? (_jsx(Span, { size: "xs", color: "muted", title: "Contact support to resolve address ban", children: "Contact support" })) : (_jsxs(Row, { justify: "end", gap: "xs", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => openEdit(addr), children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleDelete(addr), children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label })] })) })] }, addr.id));
150
+ }) })] }) }))] }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer: _jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, className: "flex-1", children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, className: "flex-1", children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs(Stack, { gap: "md", padding: "y-2xs", children: [saveError && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError })), _jsx(FieldInput, { name: "label", label: "Label *", hint: "e.g. Warehouse, Shop, Home", type: "text", value: draft.label, onChange: setField("label"), placeholder: "Warehouse", maxLength: 60 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "fullName", label: "Full Name *", type: "text", value: draft.fullName, onChange: setField("fullName"), placeholder: "Ravi Kumar", maxLength: 100 }), _jsx(FieldInput, { name: "phone", label: "Phone *", type: "tel", value: draft.phone, onChange: setField("phone"), placeholder: "+91 98765 43210", maxLength: 20 })] }), _jsx(FieldInput, { name: "addressLine1", label: "Address Line 1 *", type: "text", value: draft.addressLine1, onChange: setField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200 }), _jsx(FieldInput, { name: "addressLine2", label: "Address Line 2", type: "text", value: draft.addressLine2, onChange: setField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200 }), _jsx(FieldInput, { name: "landmark", label: "Landmark", type: "text", value: draft.landmark, onChange: setField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "city", label: "City *", type: "text", value: draft.city, onChange: setField("city"), placeholder: "Mumbai", maxLength: 100 }), _jsx(FieldInput, { name: "state", label: "State *", type: "text", value: draft.state, onChange: setField("state"), placeholder: "Maharashtra", maxLength: 100 })] }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "postalCode", label: "Postal Code *", type: "text", value: draft.postalCode, onChange: setField("postalCode"), placeholder: "400001", maxLength: 10 }), _jsx(FieldInput, { name: "country", label: "Country *", type: "text", value: draft.country, onChange: setField("country"), placeholder: "India", maxLength: 60 })] }), _jsx(FieldCheckbox, { name: "isDefault", label: "Set as default pickup address", checked: draft.isDefault, onChange: (c) => set("isDefault", c) })] }) }), deleteTargetAddr && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Address", message: `Delete address "${deleteTargetAddr.label}"? This cannot be undone.`, onConfirm: () => { deleteById(deleteTargetAddr.id); setDeleteTargetAddr(null); }, onClose: () => setDeleteTargetAddr(null), isDeleting: deletingId === deleteTargetAddr.id }))] }));
147
151
  }