@reblu/site-blocks 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.cjs CHANGED
@@ -43,10 +43,16 @@ __export(client_exports, {
43
43
  DEFAULT_COURSE_SYLLABUS_LABELS: () => DEFAULT_COURSE_SYLLABUS_LABELS,
44
44
  DEFAULT_DATA_REQUEST_FORM_LABELS: () => DEFAULT_DATA_REQUEST_FORM_LABELS,
45
45
  DEFAULT_PRODUCT_CAROUSEL_LABELS: () => DEFAULT_PRODUCT_CAROUSEL_LABELS,
46
+ DEFAULT_REVIEWS_CAROUSEL_LABELS: () => DEFAULT_REVIEWS_CAROUSEL_LABELS,
47
+ DEFAULT_TEAM_DIRECTORY_LABELS: () => DEFAULT_TEAM_DIRECTORY_LABELS,
46
48
  DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS: () => DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS,
47
49
  DataRequestForm: () => DataRequestForm,
50
+ FeaturedReviews: () => FeaturedReviews,
48
51
  ProductCarousel: () => ProductCarousel,
52
+ ReviewsBlockRenderer: () => ReviewsBlockRenderer,
53
+ ReviewsCarousel: () => ReviewsCarousel,
49
54
  ShareButtons: () => ShareButtons,
55
+ TeamDirectory: () => TeamDirectory,
50
56
  WITHDRAWAL_REQUEST_FIELD_LIMITS: () => WITHDRAWAL_REQUEST_FIELD_LIMITS,
51
57
  WithdrawalRequestForm: () => WithdrawalRequestForm,
52
58
  useCollection: () => useCollection
@@ -2227,6 +2233,453 @@ function CourseGuestCheckoutForm({
2227
2233
  }
2228
2234
  );
2229
2235
  }
2236
+
2237
+ // src/components/reviews-carousel.tsx
2238
+ var import_utils16 = require("@reblu/site-ui/utils");
2239
+
2240
+ // src/components/_review-card.tsx
2241
+ var import_utils15 = require("@reblu/site-ui/utils");
2242
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2243
+ function isHttps(value) {
2244
+ if (typeof value !== "string") return false;
2245
+ try {
2246
+ const url = new URL(value);
2247
+ return url.protocol === "https:" && url.hostname !== "";
2248
+ } catch {
2249
+ return false;
2250
+ }
2251
+ }
2252
+ function isUsableReview(value) {
2253
+ if (typeof value !== "object" || value === null) return false;
2254
+ const review = value;
2255
+ return typeof review.id === "string" && typeof review.authorName === "string" && review.authorName.trim() !== "" && typeof review.rating === "number" && Number.isInteger(review.rating) && review.rating >= 1 && review.rating <= 5 && typeof review.sourceName === "string" && isHttps(review.sourceUrl);
2256
+ }
2257
+ function formatDay(day, locale) {
2258
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) return null;
2259
+ const date = /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
2260
+ if (Number.isNaN(date.getTime())) return null;
2261
+ return new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" }).format(date);
2262
+ }
2263
+ var STAR_PATH = "M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.3l-5.8 3.1 1.1-6.5L2.6 9.3l6.5-.9z";
2264
+ function Stars({ rating }) {
2265
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { role: "img", "aria-label": `${rating} de 5 estrellas`, className: "flex gap-0.5", children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2266
+ "svg",
2267
+ {
2268
+ "aria-hidden": "true",
2269
+ viewBox: "0 0 24 24",
2270
+ width: "16",
2271
+ height: "16",
2272
+ "data-filled": n <= rating ? "true" : "false",
2273
+ className: n <= rating ? "fill-current" : "fill-none opacity-40",
2274
+ stroke: "currentColor",
2275
+ strokeWidth: "1.5",
2276
+ strokeLinejoin: "round",
2277
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: STAR_PATH })
2278
+ },
2279
+ n
2280
+ )) });
2281
+ }
2282
+ function usableReviews(reviews) {
2283
+ return Array.isArray(reviews) ? reviews.filter(isUsableReview) : [];
2284
+ }
2285
+ function ReviewCard({
2286
+ review,
2287
+ linkLabel,
2288
+ newWindowLabel,
2289
+ locale,
2290
+ className
2291
+ }) {
2292
+ const date = formatDay(review.reviewDate, locale);
2293
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2294
+ "article",
2295
+ {
2296
+ "data-slot": "review",
2297
+ "data-source": review.source,
2298
+ className: (0, import_utils15.cn)("flex h-full flex-col gap-3 rounded-lg border p-5", className),
2299
+ children: [
2300
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Stars, { rating: review.rating }),
2301
+ review.text && review.text.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("blockquote", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { children: review.text }) }) : null,
2302
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("footer", { className: "mt-auto space-y-1 text-sm", children: [
2303
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "font-semibold", children: review.authorName }),
2304
+ date ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("time", { dateTime: review.reviewDate, className: "block text-muted-foreground", children: date }) : null,
2305
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2306
+ "a",
2307
+ {
2308
+ href: review.sourceUrl,
2309
+ target: "_blank",
2310
+ rel: "noopener nofollow",
2311
+ className: "inline-flex underline underline-offset-4",
2312
+ children: [
2313
+ linkLabel(review.sourceName),
2314
+ " ",
2315
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "sr-only", children: newWindowLabel })
2316
+ ]
2317
+ }
2318
+ )
2319
+ ] })
2320
+ ]
2321
+ }
2322
+ );
2323
+ }
2324
+
2325
+ // src/components/reviews-carousel.tsx
2326
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2327
+ var DEFAULT_REVIEWS_CAROUSEL_LABELS = {
2328
+ region: "Rese\xF1as",
2329
+ roleDescription: "carrusel",
2330
+ previous: "Rese\xF1a anterior",
2331
+ next: "Rese\xF1a siguiente"
2332
+ };
2333
+ function ReviewsCarousel({
2334
+ reviews,
2335
+ heading,
2336
+ headingLevel = 2,
2337
+ notice,
2338
+ linkLabel = (name) => `Ver en ${name}`,
2339
+ newWindowLabel = "(se abre en una pesta\xF1a nueva)",
2340
+ locale = "es-ES",
2341
+ labels,
2342
+ className,
2343
+ headingClassName,
2344
+ itemClassName,
2345
+ cardClassName,
2346
+ noticeClassName
2347
+ }) {
2348
+ const usable = usableReviews(reviews);
2349
+ if (usable.length === 0) return null;
2350
+ const hasNotice = notice !== void 0 && notice !== null && notice !== "";
2351
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-slot": "reviews-carousel", className: (0, import_utils16.cn)("space-y-4", className), children: [
2352
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2353
+ ProductCarousel,
2354
+ {
2355
+ products: usable,
2356
+ heading,
2357
+ headingLevel,
2358
+ headingClassName,
2359
+ labels: { ...DEFAULT_REVIEWS_CAROUSEL_LABELS, ...labels },
2360
+ itemClassName,
2361
+ renderProduct: (review) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2362
+ ReviewCard,
2363
+ {
2364
+ review,
2365
+ linkLabel,
2366
+ newWindowLabel,
2367
+ locale,
2368
+ className: cardClassName
2369
+ }
2370
+ )
2371
+ }
2372
+ ),
2373
+ hasNotice ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2374
+ "p",
2375
+ {
2376
+ "data-slot": "reviews-notice",
2377
+ className: slotClassName("", "text-sm text-muted-foreground", noticeClassName),
2378
+ children: notice
2379
+ }
2380
+ ) : null
2381
+ ] });
2382
+ }
2383
+
2384
+ // src/components/reviews.tsx
2385
+ var import_utils17 = require("@reblu/site-ui/utils");
2386
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2387
+ function Reviews({
2388
+ reviews,
2389
+ heading,
2390
+ headingId = "reviews-heading",
2391
+ headingLevel = 2,
2392
+ notice,
2393
+ noticePosition = "below",
2394
+ linkLabel = (name) => `Ver en ${name}`,
2395
+ newWindowLabel = "(se abre en una pesta\xF1a nueva)",
2396
+ locale = "es-ES",
2397
+ className,
2398
+ headingClassName,
2399
+ gridClassName,
2400
+ cardClassName,
2401
+ noticeClassName
2402
+ }) {
2403
+ const usable = usableReviews(reviews);
2404
+ if (usable.length === 0) return null;
2405
+ const hasNotice = notice !== void 0 && notice !== null && notice !== "";
2406
+ const noticeNode = hasNotice ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2407
+ "p",
2408
+ {
2409
+ "data-slot": "reviews-notice",
2410
+ className: slotClassName("", "text-sm text-muted-foreground", noticeClassName),
2411
+ children: notice
2412
+ }
2413
+ ) : null;
2414
+ const grid = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2415
+ "ul",
2416
+ {
2417
+ "data-slot": "reviews-grid",
2418
+ className: slotClassName("grid gap-6", "sm:grid-cols-2 lg:grid-cols-3", gridClassName),
2419
+ children: usable.map((review) => {
2420
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2421
+ ReviewCard,
2422
+ {
2423
+ review,
2424
+ linkLabel,
2425
+ newWindowLabel,
2426
+ locale,
2427
+ className: cardClassName
2428
+ }
2429
+ ) }, review.id);
2430
+ })
2431
+ }
2432
+ );
2433
+ const body = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2434
+ noticePosition === "above" ? noticeNode : null,
2435
+ grid,
2436
+ noticePosition === "below" ? noticeNode : null
2437
+ ] });
2438
+ const hasHeading = heading !== void 0 && heading !== null && heading !== "";
2439
+ if (!hasHeading) {
2440
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { "data-slot": "reviews", className: (0, import_utils17.cn)("space-y-4", className), children: body });
2441
+ }
2442
+ const Tag = `h${headingLevel}`;
2443
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { "data-slot": "reviews", "aria-labelledby": headingId, className: (0, import_utils17.cn)("space-y-4", className), children: [
2444
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Tag, { id: headingId, className: slotClassName("", "text-xl font-semibold", headingClassName), children: heading }),
2445
+ body
2446
+ ] });
2447
+ }
2448
+
2449
+ // src/components/featured-reviews.tsx
2450
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2451
+ function FeaturedReviews({
2452
+ reviews,
2453
+ layout,
2454
+ heading,
2455
+ headingLevel,
2456
+ notice,
2457
+ linkLabel,
2458
+ locale,
2459
+ carouselLabels,
2460
+ className,
2461
+ cardClassName,
2462
+ gridClassName,
2463
+ itemClassName
2464
+ }) {
2465
+ if (layout === "carousel") {
2466
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2467
+ ReviewsCarousel,
2468
+ {
2469
+ reviews,
2470
+ heading,
2471
+ headingLevel,
2472
+ notice,
2473
+ linkLabel,
2474
+ locale,
2475
+ labels: carouselLabels,
2476
+ className,
2477
+ cardClassName,
2478
+ itemClassName
2479
+ }
2480
+ );
2481
+ }
2482
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2483
+ Reviews,
2484
+ {
2485
+ reviews,
2486
+ heading,
2487
+ headingLevel,
2488
+ notice,
2489
+ linkLabel,
2490
+ locale,
2491
+ className,
2492
+ cardClassName,
2493
+ gridClassName
2494
+ }
2495
+ );
2496
+ }
2497
+
2498
+ // src/components/reviews-block-renderer.tsx
2499
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2500
+ function ReviewsBlockRenderer({ block }) {
2501
+ const { layout, notice } = block.data;
2502
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2503
+ FeaturedReviews,
2504
+ {
2505
+ reviews: block.reviews,
2506
+ layout: layout ?? "grid",
2507
+ notice: notice && notice.trim() !== "" ? notice : void 0,
2508
+ carouselLabels: { region: "Carrusel de rese\xF1as" }
2509
+ }
2510
+ );
2511
+ }
2512
+
2513
+ // src/components/team-directory.tsx
2514
+ var import_react9 = require("react");
2515
+ var import_utils20 = require("@reblu/site-ui/utils");
2516
+
2517
+ // src/lib/page-links.ts
2518
+ function linkKey(link) {
2519
+ return `${link.category?.slug ?? ""}/${link.slug}`;
2520
+ }
2521
+ function linkFilterOptions(items) {
2522
+ const seen = /* @__PURE__ */ new Map();
2523
+ for (const item of items) {
2524
+ for (const link of item.links ?? []) {
2525
+ const key = linkKey(link);
2526
+ if (!seen.has(key)) seen.set(key, { key, label: link.title });
2527
+ }
2528
+ }
2529
+ return [...seen.values()];
2530
+ }
2531
+ function filterByLink(items, key) {
2532
+ if (key === null) return items;
2533
+ return items.filter((item) => (item.links ?? []).some((link) => linkKey(link) === key));
2534
+ }
2535
+
2536
+ // src/components/profile-card.tsx
2537
+ var import_utils19 = require("@reblu/site-ui/utils");
2538
+
2539
+ // src/components/professional-fields.tsx
2540
+ var import_utils18 = require("@reblu/site-ui/utils");
2541
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2542
+ function ProfessionalFields({
2543
+ fields,
2544
+ variant = "profile",
2545
+ className
2546
+ }) {
2547
+ const shown = (fields ?? []).filter((f) => variant !== "card" || f.showOnCard);
2548
+ if (shown.length === 0) return null;
2549
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2550
+ "dl",
2551
+ {
2552
+ className: (0, import_utils18.cn)(
2553
+ "grid gap-x-3 gap-y-1 text-sm",
2554
+ variant === "card" ? "justify-center" : "grid-cols-[auto_1fr]",
2555
+ className
2556
+ ),
2557
+ "data-slot": "professional-fields",
2558
+ "data-variant": variant,
2559
+ children: shown.map((field, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2560
+ "div",
2561
+ {
2562
+ className: variant === "card" ? "flex justify-center gap-1" : "contents",
2563
+ children: [
2564
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("dt", { className: "text-muted-foreground", children: field.label }),
2565
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("dd", { className: "m-0 font-medium", children: field.value })
2566
+ ]
2567
+ },
2568
+ `${field.label}-${index}`
2569
+ ))
2570
+ }
2571
+ );
2572
+ }
2573
+
2574
+ // src/components/profile-card.tsx
2575
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2576
+ function initials(name) {
2577
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
2578
+ }
2579
+ function ProfileCard({
2580
+ name,
2581
+ role,
2582
+ imageUrl,
2583
+ specialties = [],
2584
+ bio,
2585
+ fields,
2586
+ href,
2587
+ className
2588
+ }) {
2589
+ const NameTag = href ? "a" : "span";
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2591
+ "article",
2592
+ {
2593
+ className: (0, import_utils19.cn)(
2594
+ "flex flex-col items-center gap-3 rounded-lg border bg-card p-6 text-center",
2595
+ className
2596
+ ),
2597
+ "data-slot": "profile-card",
2598
+ children: [
2599
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "relative h-24 w-24 overflow-hidden rounded-full bg-muted", children: imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { src: imageUrl, alt: name, loading: "lazy", className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2600
+ "span",
2601
+ {
2602
+ className: "flex h-full w-full items-center justify-center text-2xl font-semibold text-muted-foreground",
2603
+ "aria-hidden": "true",
2604
+ children: initials(name)
2605
+ }
2606
+ ) }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-1", children: [
2608
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "text-lg font-semibold leading-tight", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(NameTag, { ...href ? { href, className: "transition-colors hover:text-primary" } : {}, children: name }) }),
2609
+ role && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-sm text-muted-foreground", children: role })
2610
+ ] }),
2611
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ProfessionalFields, { fields, variant: "card" }),
2612
+ specialties.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "flex flex-wrap justify-center gap-1.5 p-0", children: specialties.map((s) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2613
+ "li",
2614
+ {
2615
+ className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
2616
+ children: s
2617
+ },
2618
+ s
2619
+ )) }),
2620
+ bio && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-sm text-muted-foreground", children: bio })
2621
+ ]
2622
+ }
2623
+ );
2624
+ }
2625
+
2626
+ // src/components/team-directory.tsx
2627
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2628
+ var DEFAULT_TEAM_DIRECTORY_LABELS = {
2629
+ all: "Todos",
2630
+ filter: "Filtrar el equipo"
2631
+ };
2632
+ var CHIP_CLASS = "inline-flex cursor-pointer items-center rounded-full border px-3 py-1 text-sm font-medium transition-colors aria-pressed:bg-primary aria-pressed:text-primary-foreground aria-pressed:border-primary hover:bg-muted";
2633
+ function TeamDirectory({
2634
+ members,
2635
+ memberHref,
2636
+ memberHrefBase,
2637
+ showLinksOnCards = false,
2638
+ labels,
2639
+ className
2640
+ }) {
2641
+ const t = { ...DEFAULT_TEAM_DIRECTORY_LABELS, ...labels };
2642
+ const [selected, setSelected] = (0, import_react9.useState)(null);
2643
+ if (members.length === 0) return null;
2644
+ const options = linkFilterOptions(members);
2645
+ const visible = filterByLink(members, selected);
2646
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: (0, import_utils20.cn)("space-y-6", className), "data-slot": "team-directory", children: [
2647
+ options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { role: "group", "aria-label": t.filter, className: "flex flex-wrap gap-2", children: [
2648
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2649
+ "button",
2650
+ {
2651
+ type: "button",
2652
+ className: CHIP_CLASS,
2653
+ "aria-pressed": selected === null,
2654
+ onClick: () => setSelected(null),
2655
+ children: t.all
2656
+ }
2657
+ ),
2658
+ options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2659
+ "button",
2660
+ {
2661
+ type: "button",
2662
+ className: CHIP_CLASS,
2663
+ "aria-pressed": selected === option.key,
2664
+ onClick: () => setSelected(option.key),
2665
+ children: option.label
2666
+ },
2667
+ option.key
2668
+ ))
2669
+ ] }),
2670
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "grid list-none grid-cols-1 gap-6 p-0 sm:grid-cols-2 lg:grid-cols-3", children: visible.map((member) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2671
+ ProfileCard,
2672
+ {
2673
+ name: member.title,
2674
+ role: member.professional?.role ?? void 0,
2675
+ imageUrl: member.coverImageUrl ?? void 0,
2676
+ specialties: showLinksOnCards ? (member.links ?? []).map((l) => l.title) : [],
2677
+ href: memberHref ? memberHref(member) : memberHrefBase !== void 0 ? `${memberHrefBase}/${member.slug}` : void 0,
2678
+ className: "h-full"
2679
+ }
2680
+ ) }, linkKey(member))) })
2681
+ ] });
2682
+ }
2230
2683
  // Annotate the CommonJS export names for ESM import in node:
2231
2684
  0 && (module.exports = {
2232
2685
  AddToCartButton,
@@ -2241,10 +2694,16 @@ function CourseGuestCheckoutForm({
2241
2694
  DEFAULT_COURSE_SYLLABUS_LABELS,
2242
2695
  DEFAULT_DATA_REQUEST_FORM_LABELS,
2243
2696
  DEFAULT_PRODUCT_CAROUSEL_LABELS,
2697
+ DEFAULT_REVIEWS_CAROUSEL_LABELS,
2698
+ DEFAULT_TEAM_DIRECTORY_LABELS,
2244
2699
  DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS,
2245
2700
  DataRequestForm,
2701
+ FeaturedReviews,
2246
2702
  ProductCarousel,
2703
+ ReviewsBlockRenderer,
2704
+ ReviewsCarousel,
2247
2705
  ShareButtons,
2706
+ TeamDirectory,
2248
2707
  WITHDRAWAL_REQUEST_FIELD_LIMITS,
2249
2708
  WithdrawalRequestForm,
2250
2709
  useCollection