@aglyn/plugins-marketplace 1.0.0-beta.143

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 (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,419 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
19
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
20
+ import { ceilingedWindow, collectionCeiling, useFirestore, useFirestoreCollection, useUser } from "@aglyn/tenant-feature-instance";
21
+ import { Alert, Avatar, Button, Chip, Divider, Rating, Stack, TextField, Typography } from "@mui/material";
22
+ import { collection } from "firebase/firestore";
23
+ import { useCallback, useEffect, useMemo, useState } from "react";
24
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
25
+ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
26
+ import { pluginDocsHelp } from "@aglyn/aglyn";
27
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
28
+ import ReportTarget from "./report-target.component.js";
29
+ /**
30
+ * How many review documents this card reads.
31
+ *
32
+ * A CEILING, not a page size. Three things below are computed over the whole
33
+ * set — the hidden-review filter, the newest-first sort, and finding the
34
+ * reader's OWN review to seed the form — so a server page would be a page of
35
+ * candidates and a reader whose review sat past the first ten would be shown
36
+ * an empty form over a review they had already written.
37
+ */ const REVIEW_CEILING = 200;
38
+ /**
39
+ * Ratings and comments on a listing (AGL-655).
40
+ *
41
+ * Reads the subcollection directly — it is public-read, so there is no
42
+ * value in proxying it through an API. Writes go through
43
+ * `/api/marketplace/reviews`, which owns every rule that matters: only
44
+ * accounts that installed the listing may rate it, the publishing org
45
+ * cannot review itself, and the aggregates it maintains are frozen from
46
+ * client writes.
47
+ *
48
+ * The star input is offered to everyone and refused server-side rather than
49
+ * hidden, because "you must install this to rate it" is worth saying out
50
+ * loud — a control that silently does nothing teaches nothing.
51
+ */ export function ListingReviews({ listingId, listing }) {
52
+ var _ref, _ref1;
53
+ const firestore = useFirestore();
54
+ const { data: user } = useUser();
55
+ const { enqueueSnackbar } = useSnackbar();
56
+ const uid = user == null ? void 0 : user.uid;
57
+ // Rating (not commenting) requires a verified email (AGL-865). We can't see
58
+ // the install pins from here, so the "installed" half is still enforced
59
+ // server-side; the email half we can reflect in the control's state.
60
+ const emailVerified = Boolean(user == null ? void 0 : user.emailVerified);
61
+ const { data: reviewDocs } = useFirestoreCollection(/*
62
+ * ORDERED AND CEILINGED, with a probe (AGL-2501).
63
+ *
64
+ * `limit(100)` alone is answered in DOCUMENT-ID order, and a review's
65
+ * document id is its author's uid — so the hundred were a pseudo-random
66
+ * hundred of the reviewers, and on a popular listing the reviews past
67
+ * them were not merely unrendered but unreachable, because nothing drew
68
+ * them and no control asked for more.
69
+ *
70
+ * Ordering on the document NAME changes which rows come back only in the
71
+ * sense that the walk is now total: every reviewer is reachable by
72
+ * paging. It is not chronological and nothing here claims it is — the
73
+ * sort below puts the newest first, which it may do because it holds the
74
+ * whole ceiling rather than a slice of it.
75
+ *
76
+ * `orderBy('updatedAtMs')` would have been the tempting fix and is the
77
+ * dangerous one: it matches only documents that HAVE the field, so a
78
+ * review written before that field existed would vanish from a listing's
79
+ * page rather than sort oddly on it.
80
+ */ ()=>collectionCeiling(collection(firestore, 'marketplaceListings', listingId, 'reviews'), REVIEW_CEILING), [
81
+ firestore,
82
+ listingId
83
+ ], {
84
+ idField: '$id'
85
+ });
86
+ const { rows: readReviews, truncated: reviewsTruncated } = ceilingedWindow(reviewDocs, REVIEW_CEILING);
87
+ const reviews = useMemo(()=>[
88
+ ...readReviews
89
+ ].filter((entry)=>!entry.hidden).sort((a, b)=>{
90
+ var _b_updatedAtMs, _a_updatedAtMs;
91
+ return ((_b_updatedAtMs = b.updatedAtMs) != null ? _b_updatedAtMs : 0) - ((_a_updatedAtMs = a.updatedAtMs) != null ? _a_updatedAtMs : 0);
92
+ }), // eslint-disable-next-line react-hooks/exhaustive-deps
93
+ [
94
+ reviewDocs
95
+ ]);
96
+ const mine = useMemo(()=>reviews.find((entry)=>entry.$id === uid), [
97
+ reviews,
98
+ uid
99
+ ]);
100
+ /*
101
+ * The page is a SLICE of what the card already holds, for the three reasons
102
+ * the ceiling exists: `hidden` is filtered after reading, the order on
103
+ * screen is not the order the query walks, and `mine` seeds the form above
104
+ * from anywhere in the set.
105
+ */ const [page, setPage] = useState(0);
106
+ const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
107
+ const visibleReviews = useMemo(()=>reviews.slice(page * pageSize, page * pageSize + pageSize), [
108
+ reviews,
109
+ page,
110
+ pageSize
111
+ ]);
112
+ /*
113
+ * A new listing starts at page one. Page four of a listing with two pages
114
+ * does not exist, and an out-of-range page renders as an empty list with
115
+ * nothing saying why — which reads as the reviews having gone.
116
+ */ useEffect(()=>{
117
+ setPage(0);
118
+ }, [
119
+ listingId
120
+ ]);
121
+ const [rating, setRating] = useState(null);
122
+ const [comment, setComment] = useState('');
123
+ const [busy, setBusy] = useState(false);
124
+ useEffect(()=>{
125
+ var _ref, _ref1;
126
+ setRating((_ref = mine == null ? void 0 : mine.rating) != null ? _ref : null);
127
+ setComment((_ref1 = mine == null ? void 0 : mine.comment) != null ? _ref1 : '');
128
+ }, [
129
+ mine
130
+ ]);
131
+ const submit = useCallback(async ()=>{
132
+ if (busy || !rating && !comment.trim()) return;
133
+ setBusy(true);
134
+ try {
135
+ const response = await authorizedFetch(user, '/api/marketplace/reviews', {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json'
139
+ },
140
+ body: JSON.stringify({
141
+ listingId,
142
+ rating,
143
+ comment: comment.trim()
144
+ })
145
+ });
146
+ const payload = await response.json().catch(()=>({}));
147
+ if (!response.ok) {
148
+ var _ref;
149
+ // 403 here is usually "you have not installed this" — actionable,
150
+ // so it reads as guidance rather than an error.
151
+ return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not post that', {
152
+ variant: response.status === 403 ? 'warning' : 'error',
153
+ allowDuplicate: true
154
+ });
155
+ }
156
+ enqueueSnackbar(mine ? 'Updated your review' : 'Thanks for the review', {
157
+ variant: 'success',
158
+ persist: false
159
+ });
160
+ } catch (error) {
161
+ console.error(error);
162
+ enqueueSnackbar('Could not post that', {
163
+ variant: 'error',
164
+ allowDuplicate: true
165
+ });
166
+ } finally{
167
+ setBusy(false);
168
+ }
169
+ }, [
170
+ busy,
171
+ rating,
172
+ comment,
173
+ user,
174
+ listingId,
175
+ mine,
176
+ enqueueSnackbar
177
+ ]);
178
+ const remove = useCallback(async ()=>{
179
+ if (busy || !mine) return;
180
+ setBusy(true);
181
+ try {
182
+ await authorizedFetch(user, '/api/marketplace/reviews', {
183
+ method: 'DELETE',
184
+ headers: {
185
+ 'Content-Type': 'application/json'
186
+ },
187
+ body: JSON.stringify({
188
+ listingId
189
+ })
190
+ });
191
+ setRating(null);
192
+ setComment('');
193
+ enqueueSnackbar('Removed your review', {
194
+ variant: 'success',
195
+ persist: false
196
+ });
197
+ } finally{
198
+ setBusy(false);
199
+ }
200
+ }, [
201
+ busy,
202
+ mine,
203
+ user,
204
+ listingId,
205
+ enqueueSnackbar
206
+ ]);
207
+ const average = Number((_ref = listing == null ? void 0 : listing.ratingAverage) != null ? _ref : 0);
208
+ const ratingCount = Number((_ref1 = listing == null ? void 0 : listing.ratingCount) != null ? _ref1 : 0);
209
+ return /*#__PURE__*/ _jsx(CardDisplay, {
210
+ header: 'Ratings & comments',
211
+ help: pluginDocsHelp('installYourFirstPlugin', {
212
+ anchor: '#step-3-reviews'
213
+ }),
214
+ contentGutterX: true,
215
+ contentGutterY: true,
216
+ children: /*#__PURE__*/ _jsxs(Stack, {
217
+ spacing: 2,
218
+ children: [
219
+ /*#__PURE__*/ _jsx(Stack, {
220
+ direction: "row",
221
+ spacing: 1,
222
+ sx: {
223
+ alignItems: 'center'
224
+ },
225
+ children: ratingCount ? /*#__PURE__*/ _jsxs(_Fragment, {
226
+ children: [
227
+ /*#__PURE__*/ _jsx(Rating, {
228
+ value: average,
229
+ precision: 0.1,
230
+ size: "small",
231
+ readOnly: true
232
+ }),
233
+ /*#__PURE__*/ _jsx(Typography, {
234
+ variant: "body2",
235
+ color: "text.secondary",
236
+ children: `${average} · ${ratingCount} rating${ratingCount === 1 ? '' : 's'}`
237
+ })
238
+ ]
239
+ }) : /*#__PURE__*/ _jsx(Typography, {
240
+ variant: "body2",
241
+ color: "text.secondary",
242
+ children: 'No ratings yet.'
243
+ })
244
+ }),
245
+ uid ? /*#__PURE__*/ _jsxs(Stack, {
246
+ spacing: 1,
247
+ children: [
248
+ /*#__PURE__*/ _jsx(Typography, {
249
+ variant: "subtitle2",
250
+ children: mine ? 'Your review' : 'Leave a review'
251
+ }),
252
+ /*#__PURE__*/ _jsxs(Stack, {
253
+ direction: "row",
254
+ spacing: 1,
255
+ sx: {
256
+ alignItems: 'center'
257
+ },
258
+ children: [
259
+ /*#__PURE__*/ _jsx(Typography, {
260
+ variant: "caption",
261
+ sx: {
262
+ minWidth: 56
263
+ },
264
+ children: 'Rating'
265
+ }),
266
+ /*#__PURE__*/ _jsx(Rating, {
267
+ value: rating,
268
+ size: "small",
269
+ onChange: (_event, value)=>setRating(value),
270
+ disabled: busy || !emailVerified
271
+ }),
272
+ /*#__PURE__*/ _jsx(Typography, {
273
+ variant: "caption",
274
+ color: "text.secondary",
275
+ children: emailVerified ? 'Requires having installed this' : 'Verify your email to rate'
276
+ })
277
+ ]
278
+ }),
279
+ /*#__PURE__*/ _jsx(TextField, {
280
+ size: "small",
281
+ label: "Comment",
282
+ placeholder: "Share how it worked out, or ask a question — anyone can comment",
283
+ value: comment,
284
+ onChange: (event)=>setComment(event.target.value),
285
+ disabled: busy,
286
+ multiline: true,
287
+ minRows: 2,
288
+ fullWidth: true
289
+ }),
290
+ /*#__PURE__*/ _jsxs(Stack, {
291
+ direction: "row",
292
+ spacing: 1,
293
+ children: [
294
+ /*#__PURE__*/ _jsx(Button, {
295
+ size: "small",
296
+ variant: "contained",
297
+ color: "primary",
298
+ disabled: busy || !rating && !comment.trim(),
299
+ onClick: ()=>void submit(),
300
+ children: busy ? 'Posting…' : mine ? 'Update' : 'Post'
301
+ }),
302
+ mine ? /*#__PURE__*/ _jsx(Button, {
303
+ size: "small",
304
+ disabled: busy,
305
+ onClick: ()=>void remove(),
306
+ children: 'Remove'
307
+ }) : null
308
+ ]
309
+ })
310
+ ]
311
+ }) : /*#__PURE__*/ _jsx(Typography, {
312
+ variant: "body2",
313
+ color: "text.secondary",
314
+ children: 'Sign in to leave a rating or comment.'
315
+ }),
316
+ reviews.length ? /*#__PURE__*/ _jsxs(_Fragment, {
317
+ children: [
318
+ /*#__PURE__*/ _jsx(Divider, {}),
319
+ /*#__PURE__*/ _jsx(Stack, {
320
+ spacing: 2,
321
+ children: visibleReviews.map((review)=>{
322
+ var _review_displayName;
323
+ const name = String((_review_displayName = review.displayName) != null ? _review_displayName : 'Someone');
324
+ return /*#__PURE__*/ _jsxs(Stack, {
325
+ direction: "row",
326
+ spacing: 1.5,
327
+ sx: {
328
+ alignItems: 'flex-start'
329
+ },
330
+ children: [
331
+ /*#__PURE__*/ _jsx(Avatar, {
332
+ sx: {
333
+ width: 32,
334
+ height: 32,
335
+ fontSize: 14
336
+ },
337
+ children: name.trim().charAt(0).toUpperCase() || '?'
338
+ }),
339
+ /*#__PURE__*/ _jsxs(Stack, {
340
+ spacing: 0.25,
341
+ sx: {
342
+ flex: 1,
343
+ minWidth: 0
344
+ },
345
+ children: [
346
+ /*#__PURE__*/ _jsxs(Stack, {
347
+ direction: "row",
348
+ spacing: 1,
349
+ sx: {
350
+ alignItems: 'center',
351
+ flexWrap: 'wrap'
352
+ },
353
+ children: [
354
+ /*#__PURE__*/ _jsx(Typography, {
355
+ variant: "body2",
356
+ sx: {
357
+ fontWeight: 600
358
+ },
359
+ children: name
360
+ }),
361
+ review.verifiedInstaller ? /*#__PURE__*/ _jsx(Chip, {
362
+ size: "small",
363
+ label: "Installed",
364
+ color: "primary"
365
+ }) : null,
366
+ review.updatedAtMs ? /*#__PURE__*/ _jsx(Typography, {
367
+ variant: "caption",
368
+ color: "text.secondary",
369
+ children: new Date(review.updatedAtMs).toLocaleDateString()
370
+ }) : null
371
+ ]
372
+ }),
373
+ review.rating ? /*#__PURE__*/ _jsx(Rating, {
374
+ value: review.rating,
375
+ size: "small",
376
+ readOnly: true
377
+ }) : null,
378
+ review.comment ? /*#__PURE__*/ _jsx(Typography, {
379
+ variant: "body2",
380
+ color: "text.secondary",
381
+ children: review.comment
382
+ }) : null,
383
+ uid && review.$id !== uid ? /*#__PURE__*/ _jsx(ReportTarget, {
384
+ listingId: listingId,
385
+ reviewUid: review.$id,
386
+ label: `this review by ${name}`
387
+ }) : null
388
+ ]
389
+ })
390
+ ]
391
+ }, review.$id);
392
+ })
393
+ }),
394
+ /*#__PURE__*/ _jsx(ListPagination, {
395
+ page: page,
396
+ pageSize: pageSize,
397
+ rowCount: visibleReviews.length,
398
+ // The reviews this card HOLDS, after the hidden ones are
399
+ // dropped — a slice of rows already read, so the total is
400
+ // known exactly for the window. The alert below is what says
401
+ // when the window itself is short of the listing.
402
+ count: reviews.length,
403
+ onPageChange: setPage,
404
+ onPageSizeChange: setPageSize
405
+ }),
406
+ reviewsTruncated ? /*#__PURE__*/ _jsx(Alert, {
407
+ severity: "info",
408
+ children: `Showing ${REVIEW_CEILING} reviews, ordered by author. This ` + 'listing has more, and the rating summary above counts all ' + 'of them.'
409
+ }) : null
410
+ ]
411
+ }) : null
412
+ ]
413
+ })
414
+ });
415
+ }
416
+ ListingReviews.displayName = 'ListingReviews';
417
+ export default ListingReviews;
418
+
419
+ //# sourceMappingURL=listing-reviews.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-reviews.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Avatar,\n Button,\n Chip,\n Divider,\n Rating,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport ReportTarget from './report-target.component'\n\n/**\n * How many review documents this card reads.\n *\n * A CEILING, not a page size. Three things below are computed over the whole\n * set — the hidden-review filter, the newest-first sort, and finding the\n * reader's OWN review to seed the form — so a server page would be a page of\n * candidates and a reader whose review sat past the first ten would be shown\n * an empty form over a review they had already written.\n */\nconst REVIEW_CEILING = 200\n\n/**\n * Ratings and comments on a listing (AGL-655).\n *\n * Reads the subcollection directly — it is public-read, so there is no\n * value in proxying it through an API. Writes go through\n * `/api/marketplace/reviews`, which owns every rule that matters: only\n * accounts that installed the listing may rate it, the publishing org\n * cannot review itself, and the aggregates it maintains are frozen from\n * client writes.\n *\n * The star input is offered to everyone and refused server-side rather than\n * hidden, because \"you must install this to rate it\" is worth saying out\n * loud — a control that silently does nothing teaches nothing.\n */\nexport function ListingReviews({\n listingId,\n listing,\n}: {\n listingId: string\n listing: Record<string, any> | undefined\n}) {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const uid = (user as any)?.uid as string | undefined\n // Rating (not commenting) requires a verified email (AGL-865). We can't see\n // the install pins from here, so the \"installed\" half is still enforced\n // server-side; the email half we can reflect in the control's state.\n const emailVerified = Boolean((user as any)?.emailVerified)\n\n const { data: reviewDocs } = useFirestoreCollection<any>(\n /*\n * ORDERED AND CEILINGED, with a probe (AGL-2501).\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, and a review's\n * document id is its author's uid — so the hundred were a pseudo-random\n * hundred of the reviewers, and on a popular listing the reviews past\n * them were not merely unrendered but unreachable, because nothing drew\n * them and no control asked for more.\n *\n * Ordering on the document NAME changes which rows come back only in the\n * sense that the walk is now total: every reviewer is reachable by\n * paging. It is not chronological and nothing here claims it is — the\n * sort below puts the newest first, which it may do because it holds the\n * whole ceiling rather than a slice of it.\n *\n * `orderBy('updatedAtMs')` would have been the tempting fix and is the\n * dangerous one: it matches only documents that HAVE the field, so a\n * review written before that field existed would vanish from a listing's\n * page rather than sort oddly on it.\n */\n () =>\n collectionCeiling(\n collection(firestore, 'marketplaceListings', listingId, 'reviews'),\n REVIEW_CEILING,\n ),\n [firestore, listingId],\n { idField: '$id' },\n )\n const { rows: readReviews, truncated: reviewsTruncated } = ceilingedWindow<any>(\n reviewDocs,\n REVIEW_CEILING,\n )\n\n const reviews = useMemo(\n () =>\n [...readReviews]\n .filter((entry: any) => !entry.hidden)\n .sort((a: any, b: any) => (b.updatedAtMs ?? 0) - (a.updatedAtMs ?? 0)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [reviewDocs],\n )\n const mine = useMemo(\n () => reviews.find((entry: any) => entry.$id === uid),\n [reviews, uid],\n )\n\n /*\n * The page is a SLICE of what the card already holds, for the three reasons\n * the ceiling exists: `hidden` is filtered after reading, the order on\n * screen is not the order the query walks, and `mine` seeds the form above\n * from anywhere in the set.\n */\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleReviews = useMemo(\n () => reviews.slice(page * pageSize, page * pageSize + pageSize),\n [reviews, page, pageSize],\n )\n /*\n * A new listing starts at page one. Page four of a listing with two pages\n * does not exist, and an out-of-range page renders as an empty list with\n * nothing saying why — which reads as the reviews having gone.\n */\n useEffect(() => {\n setPage(0)\n }, [listingId])\n\n const [rating, setRating] = useState<number | null>(null)\n const [comment, setComment] = useState('')\n const [busy, setBusy] = useState(false)\n\n useEffect(() => {\n setRating(mine?.rating ?? null)\n setComment(mine?.comment ?? '')\n }, [mine])\n\n const submit = useCallback(async () => {\n if (busy || (!rating && !comment.trim())) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/marketplace/reviews', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId, rating, comment: comment.trim() }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // 403 here is usually \"you have not installed this\" — actionable,\n // so it reads as guidance rather than an error.\n return void enqueueSnackbar(payload?.error ?? 'Could not post that', {\n variant: response.status === 403 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(mine ? 'Updated your review' : 'Thanks for the review', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('Could not post that', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [busy, rating, comment, user, listingId, mine, enqueueSnackbar])\n\n const remove = useCallback(async () => {\n if (busy || !mine) return\n setBusy(true)\n try {\n await authorizedFetch(user, '/api/marketplace/reviews', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId }),\n })\n setRating(null)\n setComment('')\n enqueueSnackbar('Removed your review', {\n variant: 'success',\n persist: false,\n })\n } finally {\n setBusy(false)\n }\n }, [busy, mine, user, listingId, enqueueSnackbar])\n\n const average = Number(listing?.ratingAverage ?? 0)\n const ratingCount = Number(listing?.ratingCount ?? 0)\n\n return (\n <CardDisplay\n header={'Ratings & comments'}\n help={pluginDocsHelp('installYourFirstPlugin', {\n anchor: '#step-3-reviews',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {ratingCount ? (\n <>\n <Rating value={average} precision={0.1} size=\"small\" readOnly />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${average} · ${ratingCount} rating${\n ratingCount === 1 ? '' : 's'\n }`}\n </Typography>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No ratings yet.'}\n </Typography>\n )}\n </Stack>\n\n {uid ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">\n {mine ? 'Your review' : 'Leave a review'}\n </Typography>\n {/* Rating and comment are distinct affordances (AGL-865): a rating\n is gated on a verified email + an install and moves the score;\n a comment is open to anyone signed in. */}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"caption\" sx={{ minWidth: 56 }}>\n {'Rating'}\n </Typography>\n <Rating\n value={rating}\n size=\"small\"\n onChange={(_event, value) => setRating(value)}\n disabled={busy || !emailVerified}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {emailVerified\n ? 'Requires having installed this'\n : 'Verify your email to rate'}\n </Typography>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Comment\"\n placeholder=\"Share how it worked out, or ask a question — anyone can comment\"\n value={comment}\n onChange={(event) => setComment(event.target.value)}\n disabled={busy}\n multiline\n minRows={2}\n fullWidth\n />\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || (!rating && !comment.trim())}\n onClick={() => void submit()}\n >\n {busy ? 'Posting…' : mine ? 'Update' : 'Post'}\n </Button>\n {mine ? (\n <Button size=\"small\" disabled={busy} onClick={() => void remove()}>\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Sign in to leave a rating or comment.'}\n </Typography>\n )}\n\n {reviews.length ? (\n <>\n <Divider />\n <Stack spacing={2}>\n {visibleReviews.map((review: any) => {\n const name = String(review.displayName ?? 'Someone')\n return (\n <Stack\n key={review.$id}\n direction=\"row\"\n spacing={1.5}\n sx={{ alignItems: 'flex-start' }}\n >\n <Avatar\n sx={{ width: 32, height: 32, fontSize: 14 }}\n >\n {name.trim().charAt(0).toUpperCase() || '?'}\n </Avatar>\n <Stack spacing={0.25} sx={{ flex: 1, minWidth: 0 }}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {name}\n </Typography>\n {/* Server-set, so the badge means something: it is the\n difference between \"used it\" and \"walked past it\". */}\n {review.verifiedInstaller ? (\n <Chip\n size=\"small\"\n label=\"Installed\"\n color=\"primary\"\n />\n ) : null}\n {review.updatedAtMs ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {new Date(review.updatedAtMs).toLocaleDateString()}\n </Typography>\n ) : null}\n </Stack>\n {review.rating ? (\n <Rating value={review.rating} size=\"small\" readOnly />\n ) : null}\n {review.comment ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {review.comment}\n </Typography>\n ) : null}\n {/* Not on your OWN review: the queue is for things a\n stranger wrote, and \"report yourself\" is noise a\n staff member then has to read. Editing yours is\n already the control above. */}\n {uid && review.$id !== uid ? (\n <ReportTarget\n listingId={listingId}\n reviewUid={review.$id}\n label={`this review by ${name}`}\n />\n ) : null}\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleReviews.length}\n // The reviews this card HOLDS, after the hidden ones are\n // dropped — a slice of rows already read, so the total is\n // known exactly for the window. The alert below is what says\n // when the window itself is short of the listing.\n count={reviews.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n {reviewsTruncated ? (\n <Alert severity=\"info\">\n {`Showing ${REVIEW_CEILING} reviews, ordered by author. This ` +\n 'listing has more, and the rating summary above counts all ' +\n 'of them.'}\n </Alert>\n ) : null}\n </>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\n\nListingReviews.displayName = 'ListingReviews'\n\nexport default ListingReviews\n"],"names":["CardDisplay","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","useUser","Alert","Avatar","Button","Chip","Divider","Rating","Stack","TextField","Typography","collection","useCallback","useEffect","useMemo","useState","ListPagination","TABLE_PAGE_SIZE_DEFAULT","pluginDocsHelp","authorizedFetch","ReportTarget","REVIEW_CEILING","ListingReviews","listingId","listing","firestore","data","user","enqueueSnackbar","uid","emailVerified","Boolean","reviewDocs","idField","rows","readReviews","truncated","reviewsTruncated","reviews","filter","entry","hidden","sort","a","b","updatedAtMs","mine","find","$id","page","setPage","pageSize","setPageSize","visibleReviews","slice","rating","setRating","comment","setComment","busy","setBusy","submit","trim","response","method","headers","body","JSON","stringify","payload","json","catch","ok","error","variant","status","allowDuplicate","persist","console","remove","average","Number","ratingAverage","ratingCount","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","value","precision","size","readOnly","color","minWidth","onChange","_event","disabled","label","placeholder","event","target","multiline","minRows","fullWidth","onClick","length","map","review","name","String","displayName","width","height","fontSize","charAt","toUpperCase","flex","flexWrap","fontWeight","verifiedInstaller","Date","toLocaleDateString","reviewUid","rowCount","count","onPageChange","onPageSizeChange","severity"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type OrgPermissions } from '@aglyn/aglyn';
2
+ export interface MarketplaceBrowseProps {
3
+ hostId: string;
4
+ /** Signed-in user's org permissions, supplied by the shell (AGL-395). */
5
+ permissions?: Partial<OrgPermissions>;
6
+ /**
7
+ * Rendered inside the org-scope `/marketplace` route (AGL-772) rather
8
+ * than a site's marketplace tab. Only affects link targets — the grid
9
+ * still installs through the acting `hostId` until targeting lands
10
+ * (AGL-773) — so detail links resolve to the org route, not a per-site
11
+ * one that is being retired.
12
+ */
13
+ orgScoped?: boolean;
14
+ /**
15
+ * The acting org's slug from the URL (AGL-867). When given at org scope,
16
+ * detail links build from it directly instead of the async
17
+ * `hostIndex`→`orgs` resolution, which can return empty and leave the detail
18
+ * page — the only place installs happen now — unreachable from browse.
19
+ */
20
+ orgSlug?: string;
21
+ /**
22
+ * Restrict the grid to one publisher's listings (AGL-869), for the org-scope
23
+ * publisher page. Omitted on the main browse, which shows everyone.
24
+ */
25
+ publisherId?: string;
26
+ }
27
+ /**
28
+ * Marketplace components browse (AGL-44). A read-only catalogue: each card links
29
+ * to the listing's detail page, which is the only place an install happens
30
+ * (AGL-867) — installing from a grid card was too easy and skipped the
31
+ * site-targeting choice. The card still shows install STATE (installed,
32
+ * org-wide) so the shelf reads honestly, but carries no install action.
33
+ */
34
+ export declare function MarketplaceBrowse(props: MarketplaceBrowseProps): import("react").JSX.Element;
35
+ export declare namespace MarketplaceBrowse {
36
+ var displayName: string;
37
+ }
38
+ export default MarketplaceBrowse;