{"id":71498,"plugin_id":"plugin_asdk_app_6a70d49bbd808191856702565c3be463","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-30T00:05:09.236Z","digest":"340f48497c3fc1421c2e469c832a898d6dae6a0d03b399fb9e503edb16a74441","against":null,"payload":{"description":"REQUIRED before building loyalty landing pages with Smile.io data. Contains mandatory data loading patterns — code generated without reading this skill will use incorrect architecture.","included_files":[],"name":"smile-io-integration","skill_md_contents":"---\nname: smile-io-integration\ntitle: Smile.io Loyalty\nsummary: Build loyalty and rewards pages backed by Smile.io.\ndescription: \"REQUIRED before building loyalty landing pages with Smile.io data. Contains mandatory data loading patterns — code generated without reading this skill will use incorrect architecture.\"\n---\n\n# Smile.io Integration\n\n## Data Loader Keys\n\nAlways import constants from `@replohq/sdk/loaders/loader-keys`:\n\n- `DATA_LOADER_KEYS.SMILE_IO_VIP_TIERS` — VIP tier definitions\n- `DATA_LOADER_KEYS.SMILE_IO_EARNING_RULES` — earning rules (ways to earn points)\n- `DATA_LOADER_KEYS.SMILE_IO_REWARDS` — rewards catalog (redeemable items)\n\nThese loaders have **no input args** — they load the full program configuration.\n\n## Data Loading Pattern\n\n**Always use the prefetch pattern by default.** The pattern has two layers:\n\n1. **Server component (page):** Uses `PrefetchedLoaders` to fetch data during SSR and seed the React Query cache via `HydrationBoundary`.\n2. **Client component:** Uses a loader component (e.g., `SmileVipTiersLoader`) with `useSuspenseQuery` under the hood.\n\n### Example: VIP Tier Benefits Section\n\n**Client component** (`app/components/VipTierBenefits.tsx`):\n\n```tsx\n\"use client\";\n\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { SmileVipTiersLoader } from \"@replohq/sdk/loaders/smile-vip-tiers-loader\";\n\nexport function VipTierBenefits() {\n  return (\n    <SmileVipTiersLoader\n      loaderKey={DATA_LOADER_KEYS.SMILE_IO_VIP_TIERS}\n      fallback={<div>Loading tiers…</div>}\n    >\n      {({ vipTiers }) => (\n        <div className=\"grid grid-cols-3 gap-6\">\n          {vipTiers.map((tier) => (\n            <div\n              key={tier.id}\n              className=\"rounded-xl border border-border p-6 text-center\"\n            >\n              <h3 className=\"text-xl font-bold\">{tier.name}</h3>\n              <p className=\"mt-2 text-sm text-muted-foreground\">\n                {tier.minimumPointsRequired === 0\n                  ? \"Starting tier\"\n                  : `${tier.minimumPointsRequired.toLocaleString()} points to unlock`}\n              </p>\n            </div>\n          ))}\n        </div>\n      )}\n    </SmileVipTiersLoader>\n  );\n}\n```\n\n**Server component** (`app/loyalty/page.tsx`):\n\n```tsx\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { PrefetchedLoaders } from \"@replohq/sdk/loaders/prefetch-loaders\";\nimport { VipTierBenefits } from \"../components/VipTierBenefits\";\n\nexport default function LoyaltyPage() {\n  return (\n    <PrefetchedLoaders\n      queries={[\n        { loaderKey: DATA_LOADER_KEYS.SMILE_IO_VIP_TIERS, args: {} },\n      ]}\n    >\n      <VipTierBenefits />\n    </PrefetchedLoaders>\n  );\n}\n```\n\n### Example: How to Earn Points Section\n\n**Client component** (`app/components/HowToEarnPoints.tsx`):\n\n```tsx\n\"use client\";\n\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { SmileEarningRulesLoader } from \"@replohq/sdk/loaders/smile-earning-rules-loader\";\n\nexport function HowToEarnPoints() {\n  return (\n    <SmileEarningRulesLoader\n      loaderKey={DATA_LOADER_KEYS.SMILE_IO_EARNING_RULES}\n      fallback={<div>Loading earning rules…</div>}\n    >\n      {({ earningRules }) => (\n        <div className=\"space-y-4\">\n          <h2 className=\"text-2xl font-bold\">How to Earn Points</h2>\n          <ul className=\"space-y-3\">\n            {earningRules.map((rule) => (\n              <li key={rule.id} className=\"flex items-center justify-between rounded-lg bg-muted text-muted-foreground p-4\">\n                <span className=\"font-medium\">{rule.name}</span>\n                <span className=\"text-sm font-semibold text-foreground\">\n                  {rule.pointsAwarded != null\n                    ? `+${rule.pointsAwarded} ${rule.pointsAwardedType === \"per_dollar\" ? \"pts/$\" : \"pts\"}`\n                    : \"Variable\"}\n                </span>\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n    </SmileEarningRulesLoader>\n  );\n}\n```\n\n### Example: Rewards Catalog Section\n\n**Client component** (`app/components/RewardsCatalog.tsx`):\n\n```tsx\n\"use client\";\n\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { SmileRewardsLoader } from \"@replohq/sdk/loaders/smile-rewards-loader\";\n\nexport function RewardsCatalog() {\n  return (\n    <SmileRewardsLoader\n      loaderKey={DATA_LOADER_KEYS.SMILE_IO_REWARDS}\n      fallback={<div>Loading rewards…</div>}\n    >\n      {({ rewards }) => (\n        <div className=\"space-y-4\">\n          <h2 className=\"text-2xl font-bold\">Rewards</h2>\n          <div className=\"grid grid-cols-2 gap-4 md:grid-cols-3\">\n            {rewards.map((reward) => (\n              <div key={reward.id} className=\"rounded-xl border p-4 text-center\">\n                <h3 className=\"font-semibold\">{reward.name}</h3>\n                <p className=\"mt-1 text-lg font-bold text-foreground\">\n                  {reward.pointsCost.toLocaleString()} pts\n                </p>\n                {reward.discountValue && (\n                  <p className=\"mt-1 text-sm text-muted-foreground\">\n                    ${reward.discountValue} off\n                  </p>\n                )}\n              </div>\n            ))}\n          </div>\n        </div>\n      )}\n    </SmileRewardsLoader>\n  );\n}\n```\n\n### Full Loyalty Landing Page with All Loaders\n\nCombine multiple loaders on a single page by listing them all in `PrefetchedLoaders`:\n\n```tsx\nimport { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\nimport { PrefetchedLoaders } from \"@replohq/sdk/loaders/prefetch-loaders\";\nimport { VipTierBenefits } from \"../components/VipTierBenefits\";\nimport { HowToEarnPoints } from \"../components/HowToEarnPoints\";\nimport { RewardsCatalog } from \"../components/RewardsCatalog\";\n\nexport default function LoyaltyPage() {\n  return (\n    <PrefetchedLoaders\n      queries={[\n        { loaderKey: DATA_LOADER_KEYS.SMILE_IO_VIP_TIERS, args: {} },\n        { loaderKey: DATA_LOADER_KEYS.SMILE_IO_EARNING_RULES, args: {} },\n        { loaderKey: DATA_LOADER_KEYS.SMILE_IO_REWARDS, args: {} },\n      ]}\n    >\n      <section className=\"space-y-16 py-12\">\n        <VipTierBenefits />\n        <HowToEarnPoints />\n        <RewardsCatalog />\n      </section>\n    </PrefetchedLoaders>\n  );\n}\n```\n\n## Data Shapes\n\n### SmileVipTier\n\n| Field                  | Type             | Description                    |\n| ---------------------- | ---------------- | ------------------------------ |\n| id                     | number           | Tier ID                        |\n| name                   | string           | Tier name (e.g. \"Gold\")        |\n| minimumPointsRequired  | number           | Points threshold to reach tier |\n| color                  | string \\| null   | Hex color code                 |\n\n### SmileEarningRule\n\n| Field              | Type   | Description                              |\n| ------------------ | ------ | ---------------------------------------- |\n| id                 | number | Rule ID                                  |\n| name               | string | Display name (e.g. \"Make a purchase\")    |\n| pointsAwarded      | number \\| null | Points awarded per action (null for variable rules) |\n| pointsAwardedType  | string | \"fixed\" or \"per_dollar\"                  |\n| activityToken      | string | Internal activity identifier             |\n\n### SmileReward\n\n| Field         | Type           | Description                          |\n| ------------- | -------------- | ------------------------------------ |\n| id            | number         | Reward ID                            |\n| name          | string         | Display name (e.g. \"$5 Off Coupon\")  |\n| pointsCost    | number         | Points required to redeem            |\n| rewardType    | string         | Reward type identifier               |\n| discountValue | string \\| null | Discount amount (if applicable)      |\n\n## Empty Data Handling\n\nWhen a loader returns an empty array, the loyalty program likely doesn't have that feature configured:\n\n- `{ vipTiers: [] }` — VIP tiers not set up\n- `{ earningRules: [] }` — No earning rules enabled\n- `{ rewards: [] }` — No rewards configured\n\nRender a helpful message or hide the section when empty.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}