← ReploCONTENT HISTORY
WHAT CHANGED
Update to Replo
Snapshot Oct 30, 2026 · 00:05 UTC · version 2.0.4
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Replo and check future changes in Following.
Up to 3 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"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"
}SHA-256 of public snapshot: 340f48497c3fc1421c2e469c832a898d6dae6a0d03b399fb9e503edb16a74441