here’s a single, copy-paste Replit prompt that implements icons with “Free with Attribution” + Pro unlock (no attribution), complete with endpoints, UI badges, “Copy attribution” button, and a CREDIT.txt in free downloads.

🔧 PROMPT FOR REPLIT — Icons with Attribution + Pro Unlock

Goal: Add an Icons library with the same pipeline quality as Stock Photos/Mockups, but with no quotas and the following licensing rules:

Free users: can download icons for free but must give attribution. When they download, return a ZIP containing the icon + CREDIT.txt.

IBrandBiz Pro ($19/mo) or paid icon packs: no attribution required; serve the raw SVG/PNG directly.

Keep watermark/storage code untouched. Use our existing entitlements service (services/entitlements.js). Icons do not burn photo credits or quotas.

1) Backend
A. New route file: routes/iconLibraryRoutes.js

Create an Icons pipeline with these routes:

GET /api/icons/list → returns { icons: Array<{id,name,formats:["svg","png"], previewUrl, tags, category, createdAt}> }

GET /api/icons/:id/entitlement → { licensed, requiresAttribution, canDownload }

GET /api/icons/:id/download?format=svg|png

If licensed (Pro or owns pack) → return raw file.

Else (free) → return a ZIP with the selected file + CREDIT.txt (see content below).

(optional admin) POST /api/icons/upload to add icons (SVG+PNG). If you already have a seeding/importer, wire to that.

Implementation notes

Storage keys:

SVG: icons/svg/{id}/{filename}.svg

PNG: icons/png/{id}/{filename}.png

Preview (PNG): icons/preview/{id}/{filename}.png

Use ObjectStorageService like photos/mockups.

Entitlement logic:

licensed = hasLicense(userId, iconId) || userHasPro(userId) (see helper below).

requiresAttribution = !licensed

canDownload = true (icons are always downloadable; licensing controls whether we bundle CREDIT.txt)

Add a tiny helper userHasPro(userId) that checks for any active subscription price lookup key in:

ibrandbiz_pro_monthly (our Pro plan), OR

any icon pack license previously granted via webhook (future packs).
For now, stub userHasPro to read a boolean from your session/user record or to always return false if you don’t have auth yet.

B. CREDIT.txt content

Ship this exact text when requiresAttribution is true:

Thank you for using IBrandBiz Icons!

License: Free with Attribution
You MAY use these icons commercially, modify them, and publish in unlimited projects.
You MUST include a visible credit where the icons appear (site footer, About/Credits page, caption, or end credits):

"Icons by IBrandBiz Icons — Free with Attribution (https://ibrandbiz.com/icons)"

Full license: https://ibrandbiz.com/license/icons/free
Upgrading to IBrandBiz Pro removes the attribution requirement: https://ibrandbiz.com/pricing

C. Helper to generate attribution string

Add services/iconsAttribution.js:

// services/iconsAttribution.js
function buildAttribution({ iconName, author = "IBrandBiz Icons" }) {
  const text = `Icon "${iconName}" by ${author} — Free with Attribution (https://ibrandbiz.com/icons)`;
  const html = `Icon “${iconName}” by <a href="https://ibrandbiz.com/icons" target="_blank" rel="noopener">IBrandBiz Icons</a> — Free with Attribution.`;
  return { text, html };
}

module.exports = { buildAttribution };

D. Wire in server.js
const iconLibraryRoutes = require("./routes/iconLibraryRoutes");

// keep webhook BEFORE json
app.use("/api/stripe", stripeWebhook);
app.use(express.json());

app.use("/api/icons", iconLibraryRoutes);

// Optional alias for FE if needed later:
app.get("/api/stock/icons", async (req, res) => {
  req.url = "/list";
  iconLibraryRoutes.handle(req, res);
});

2) Frontend
A. Add a reusable “Attribution” UI

src/components/AttributionNotice.tsx

import { useState } from "react";

export default function AttributionNotice({ html, text }: { html: string; text: string }) {
  const [copied, setCopied] = useState(false);
  const copy = async () => {
    await navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  return (
    <div className="mt-2 text-xs text-muted-foreground">
      <div className="flex items-center gap-2">
        <span className="px-2 py-0.5 rounded bg-amber-100 text-amber-800 border border-amber-200">
          Attribution required
        </span>
        <button onClick={copy} className="underline hover:no-underline">
          {copied ? "Copied!" : "Copy attribution"}
        </button>
      </div>
      <div className="mt-1" dangerouslySetInnerHTML={{ __html: html }} />
    </div>
  );
}


Helper: src/utils/iconAttribution.ts

export function buildAttribution(iconName: string) {
  const text = `Icon "${iconName}" by IBrandBiz Icons — Free with Attribution (https://ibrandbiz.com/icons)`;
  const html = `Icon “${iconName}” by <a href="https://ibrandbiz.com/icons" target="_blank" rel="noopener">IBrandBiz Icons</a> — Free with Attribution.`;
  return { text, html };
}

B. Entitlement hook for icons

src/hooks/useIconEntitlement.ts

import { useEffect, useState } from "react";

type Ent = { licensed: boolean; requiresAttribution: boolean; canDownload: boolean };

export function useIconEntitlement(iconId?: string) {
  const [s, setS] = useState<Ent & { loading: boolean; error?: string }>({
    licensed: false,
    requiresAttribution: true,
    canDownload: true,
    loading: true,
  });

  useEffect(() => {
    let cancel = false;
    if (!iconId) return;
    setS((x) => ({ ...x, loading: true }));
    fetch(`/api/icons/${iconId}/entitlement`)
      .then((r) => r.json())
      .then((d) => !cancel && setS({ ...d, loading: false }))
      .catch((e) => !cancel && setS((x) => ({ ...x, loading: false, error: String(e) })));
    return () => { cancel = true; };
  }, [iconId]);

  return s;
}

C. Icons page/component adjustments

Where you render each icon card:

Show title IBrandBiz | #XXXXXXXXXX if you use codes (optional).

Button logic:

If licensed → Download SVG / Download PNG

Else → Download Free (Attribution) that hits /api/icons/:id/download?format=svg and; show <AttributionNotice/> with the copy button.

Add “No attribution required” badge if licensed.

Example button block:

const ent = useIconEntitlement(icon.id);

<div className="mt-2 flex items-center gap-2">
  {ent.loading ? (
    <button className="px-3 py-1 bg-gray-200 rounded text-sm">Checking…</button>
  ) : ent.licensed ? (
    <>
      <a href={`/api/icons/${icon.id}/download?format=svg`} className="px-3 py-1 bg-black text-white rounded text-sm">Download SVG</a>
      <a href={`/api/icons/${icon.id}/download?format=png`} className="px-3 py-1 border rounded text-sm">PNG</a>
      <span className="text-xs text-emerald-600 ml-2">No attribution required</span>
    </>
  ) : (
    <>
      <a href={`/api/icons/${icon.id}/download?format=svg`} className="px-3 py-1 bg-black text-white rounded text-sm">
        Download Free (Attribution)
      </a>
    </>
  )}
</div>

{!ent.loading && !ent.licensed && (
  <AttributionNotice {...buildAttribution(icon.name)} />
)}

3) License pages (static)

Add two simple pages or markdown routes:

Free with Attribution: /license/icons/free

Allowed: commercial use, modify, unlimited projects.

Required: visible attribution linking to IBrandBiz Icons.

Forbidden: resell/redistribute the icons as-is or in competing libraries, imply endorsement.

Pro & Paid Icon License: /license/icons/pro

No attribution required.

Same allowed/forbidden as above.

(You can render these as simple markdown from /public/legal/icons-free.md and /public/legal/icons-pro.md.)

4) Stripe (later, for icon packs)

If/when we sell packs:

Product: IBrandBiz Icon Pack – <Theme> one-off $4.99, lookup key icon_pack_<theme>_499.

Webhook on successful payment → grantLicenses(userId, iconIdsInPack) (or grant a pack ID and treat all icons in that pack as licensed).

5) Acceptance Criteria

Free user:

Click Download Free (Attribution) → receives ZIP with icon + CREDIT.txt.

UI shows Attribution required + Copy attribution works.

Pro user (or stubbed userHasPro returns true):

Sees No attribution required.

Download links return raw SVG/PNG (no ZIP).

All existing photo/mockup features remain untouched.