{"id":71488,"plugin_id":"plugin_asdk_app_6a70d49bbd808191856702565c3be463","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-30T00:05:08.884Z","digest":"6eea52a37b31f531883a252c2f6066d466b983b18b884b707303ab89a4595913","against":null,"payload":{"description":"REQUIRED before generating pages with Okendo review data. Contains mandatory data loading patterns — code generated without reading this skill will use incorrect architecture.","included_files":[],"name":"okendo","skill_md_contents":"---\nname: okendo\ntitle: Okendo Reviews\nsummary: Render Okendo reviews and star ratings on a page.\ndescription: \"REQUIRED before generating pages with Okendo review data. Contains mandatory data loading patterns — code generated without reading this skill will use incorrect architecture.\"\n---\n\n# Okendo Integration\n\n## Data Loading Pattern\n\n**Always use the prefetch pattern by default.** The only exception is when data must be loaded dynamically in response to a client-side input or interaction.\n\nThe pattern has two layers:\n\n1. **Server component (page):** Uses `PrefetchedLoaders` to fetch data during SSR and seed the React Query cache via `HydrationBoundary`. This ensures the first paint includes the data with no loading state.\n2. **Client component:** Uses a loader component (e.g., `OkendoReviewsLoader`) with `useSuspenseQuery` under the hood. On initial render it reads the prefetched cache — instant. On subsequent client-side navigations or prop changes, it fetches fresh data automatically.\n\n### Discovering the Shopify Product ID\n\nOkendo loaders require a **numeric Shopify product ID** (e.g. `\"12345\"`), not a Shopify GID. To get this:\n\n1. Get the product's numeric ID from the Shopify admin (the number at the end of the product's admin URL), or ask a Replo session to look it up. Given a GID like `gid://shopify/Product/12345`, take the numeric portion.\n2. Extract the numeric portion: `const numericId = gid.split(\"/\").pop()`.\n3. Pass the numeric ID to Okendo loader components as `shopifyProductId`.\n\n### Example: Reviews Section with Prefetch\n\n**Client component** (`app/components/ProductReviews.tsx`):\n\n```tsx\n\"use client\";\n\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { OkendoReviewsLoader } from \"@replohq/sdk/loaders/okendo-reviews-loader\";\n\nexport function ProductReviews({ shopifyProductId }: { shopifyProductId: string }) {\n  return (\n    <OkendoReviewsLoader\n      loaderKey={DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEWS}\n      shopifyProductId={shopifyProductId}\n      fallback={<div>Loading reviews…</div>}\n    >\n      {({ reviews }) => (\n        <div>\n          <h2>{reviews.length} Reviews</h2>\n          {reviews.map((review) => (\n            <div key={review.id} className=\"border-b py-4\">\n              <div className=\"flex items-center gap-2\">\n                <span>{\"★\".repeat(review.rating ?? 0)}</span>\n                <span className=\"font-semibold\">{review.title}</span>\n              </div>\n              {review.reviewerName && (\n                <p className=\"text-sm text-muted-foreground\">by {review.reviewerName}</p>\n              )}\n              <p className=\"mt-2\">{review.body}</p>\n            </div>\n          ))}\n        </div>\n      )}\n    </OkendoReviewsLoader>\n  );\n}\n```\n\n**Server component** (`app/products/[handle]/page.tsx`):\n\n```tsx\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { ProductReviews } from \"@/app/components/ProductReviews\";\nimport { PrefetchedLoaders } from \"@replohq/sdk/loaders/prefetch-loaders\";\n\nconst SHOPIFY_PRODUCT_ID = \"12345\";\n\nexport default function ProductPage() {\n  return (\n    <PrefetchedLoaders\n      queries={[\n        {\n          loaderKey: DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEWS,\n          args: { shopifyProductId: SHOPIFY_PRODUCT_ID },\n        },\n      ]}\n    >\n      <ProductReviews shopifyProductId={SHOPIFY_PRODUCT_ID} />\n    </PrefetchedLoaders>\n  );\n}\n```\n\n### Example: Star Rating Widget with Aggregate Data\n\n**Client component** (`app/components/StarRating.tsx`):\n\n```tsx\n\"use client\";\n\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { OkendoReviewAggregateLoader } from \"@replohq/sdk/loaders/okendo-review-aggregate-loader\";\n\nexport function StarRating({ shopifyProductId }: { shopifyProductId: string }) {\n  return (\n    <OkendoReviewAggregateLoader\n      loaderKey={DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEW_AGGREGATE}\n      shopifyProductId={shopifyProductId}\n      fallback={<div>Loading rating…</div>}\n    >\n      {(aggregate) => (\n        <div className=\"flex items-center gap-2\">\n          <span className=\"text-foreground\">\n            {\"★\".repeat(Math.round(aggregate.reviewAverageValue))}\n            {\"☆\".repeat(5 - Math.round(aggregate.reviewAverageValue))}\n          </span>\n          <span className=\"text-sm text-muted-foreground\">\n            {aggregate.reviewAverageValue.toFixed(1)} ({aggregate.reviewCount} reviews)\n          </span>\n        </div>\n      )}\n    </OkendoReviewAggregateLoader>\n  );\n}\n```\n\n**Server component** — prefetch alongside reviews or product data:\n\n```tsx\n<PrefetchedLoaders\n  queries={[\n    {\n      loaderKey: DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEW_AGGREGATE,\n      args: { shopifyProductId: SHOPIFY_PRODUCT_ID },\n    },\n    {\n      loaderKey: DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEWS,\n      args: { shopifyProductId: SHOPIFY_PRODUCT_ID },\n    },\n  ]}\n>\n  <StarRating shopifyProductId={SHOPIFY_PRODUCT_ID} />\n  <ProductReviews shopifyProductId={SHOPIFY_PRODUCT_ID} />\n</PrefetchedLoaders>\n```\n\n## Available Loaders\n\n| Loader Key | Component | Import Path | Input |\n|---|---|---|---|\n| `DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEWS` | `OkendoReviewsLoader` | `@replohq/sdk/loaders/okendo-reviews-loader` | `{ shopifyProductId: string }` |\n| `DATA_LOADER_KEYS.OKENDO_PRODUCT_REVIEW_AGGREGATE` | `OkendoReviewAggregateLoader` | `@replohq/sdk/loaders/okendo-review-aggregate-loader` | `{ shopifyProductId: string }` |\n\n## Key Rules\n\n- **Always prefer the prefetch pattern** (server component with `PrefetchedLoaders` wrapping a client loader component).\n- Use `DATA_LOADER_KEYS` constants from `@replohq/sdk/loaders/loader-keys` — never raw strings.\n- The `shopifyProductId` must be a **numeric** product ID, not a GID. Extract it from the GID if needed.\n- Both Okendo loaders use the public Storefront API — no API key is needed at render time (only the `okendoUserId` from the project's connection, resolved server-side).\n- Loader components use React Query hooks → they **must** live in `\"use client\"` files.\n- `PrefetchedLoaders` is a **server component** → import it directly from `@replohq/sdk/loaders/prefetch-loaders`.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}