From 27fa15fe1eadcbe5d7613a4a4422f5e77a023e0a Mon Sep 17 00:00:00 2001 From: Jonas Jesus Date: Tue, 6 Oct 2026 20:51:11 -0300 Subject: [PATCH] feat(apps-nuvemshop): CMS Props for the deco-nuvemshop admin form generate-schema builds the app block form from the `Props` exported by the app's mod (re-exported by the site's src/apps/ bridge). Without it the block showed 0 fields, so the store couldn't be configured from the admin. Adds Props (storeId, token as a Secret, currency, defaultSort as an enum of the supported sorts, apiVersion) plus the default export bridges use, following apps-vtex. Verified by running generate-schema on a site with a bridge: 5 props, all 7 loaders listed with their output types. Co-Authored-By: Claude Opus 5.5 (1M context) --- bun.lock | 1 + packages/apps-nuvemshop/package.json | 3 +- .../apps-nuvemshop/src/__tests__/mod.test.ts | 21 +++++++++- packages/apps-nuvemshop/src/mod.ts | 42 +++++++++++++++++++ packages/apps-nuvemshop/src/utils/listing.ts | 12 +++++- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 78e39787..c9e93829 100644 --- a/bun.lock +++ b/bun.lock @@ -134,6 +134,7 @@ "version": "0.0.0", "dependencies": { "@decocms/apps-commerce": "workspace:*", + "@decocms/apps-website": "workspace:*", "@decocms/blocks": "workspace:*", }, "devDependencies": { diff --git a/packages/apps-nuvemshop/package.json b/packages/apps-nuvemshop/package.json index 6edeaa78..c9908867 100644 --- a/packages/apps-nuvemshop/package.json +++ b/packages/apps-nuvemshop/package.json @@ -28,7 +28,8 @@ }, "dependencies": { "@decocms/blocks": "workspace:*", - "@decocms/apps-commerce": "workspace:*" + "@decocms/apps-commerce": "workspace:*", + "@decocms/apps-website": "workspace:*" }, "peerDependencies": { "react": "^19.0.0" }, "devDependencies": { diff --git a/packages/apps-nuvemshop/src/__tests__/mod.test.ts b/packages/apps-nuvemshop/src/__tests__/mod.test.ts index 40ca5c59..849293ab 100644 --- a/packages/apps-nuvemshop/src/__tests__/mod.test.ts +++ b/packages/apps-nuvemshop/src/__tests__/mod.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { getNuvemshopConfig } from "../client"; -import { configure } from "../mod"; +import NuvemshopApp, { configure, type Props } from "../mod"; import { nuvemshopOperationRouter } from "../utils/operationRouter"; const noSecret = async () => null; @@ -34,6 +34,25 @@ describe("configure", () => { }); }); +describe("CMS Props (admin form for the deco-nuvemshop block)", () => { + it("is the shape configure() reads, and is the default export's input", async () => { + const props: Props = { + storeId: "8336778", + currency: "ARS", + defaultSort: "price-ascending", + apiVersion: "v2026-11", + }; + expect(NuvemshopApp(props)).toEqual({ state: props }); + await configure({ ...props }, noSecret); + expect(getNuvemshopConfig()).toMatchObject({ + storeId: "8336778", + currency: "ARS", + defaultSort: "price-ascending", + apiVersion: "v2026-11", + }); + }); +}); + describe("nuvemshopOperationRouter", () => { const base = "https://storefront-api.tiendanube.com/v2026-11/stores/1"; it.each([ diff --git a/packages/apps-nuvemshop/src/mod.ts b/packages/apps-nuvemshop/src/mod.ts index 8007fe3b..ff23bf36 100644 --- a/packages/apps-nuvemshop/src/mod.ts +++ b/packages/apps-nuvemshop/src/mod.ts @@ -9,8 +9,45 @@ * production — see `client.ts` for the rate-limit trade-off. */ import type { AppDefinition, ResolveSecretFn } from "@decocms/apps-commerce/app-types"; +import type { Secret } from "@decocms/apps-website/mod"; import { configureNuvemshop, type NuvemshopConfig } from "./client"; import manifest from "./manifest.gen"; +import type { SortValue } from "./utils/listing"; + +// ------------------------------------------------------------------------- +// CMS Props — the admin form for the `deco-nuvemshop` block. Sites expose it +// through a bridge (`src/apps/nuvemshop.ts` re-exporting this module's default +// and `Props`); generate-schema builds the form from this interface. +// ------------------------------------------------------------------------- + +/** @title Nuvemshop */ +export interface Props { + /** + * @title Store ID + * @description Numeric Nuvemshop/Tiendanube store id (e.g. 8336778). + */ + storeId: string; + /** + * @title Storefront token + * @description Optional. Without it all buyers share 120 req/min per IP; with it the store gets 1,200 req/min. + */ + token?: Secret; + /** + * @title Currency + * @description The API returns no currency. @default BRL + */ + currency?: string; + /** + * @title Default category sort + * @description The API doesn't expose the category's configured sort. @default created-descending + */ + defaultSort?: SortValue; + /** + * @title API version + * @default v2026-11 + */ + apiVersion?: string; +} export interface NuvemshopState { config: NuvemshopConfig; @@ -40,3 +77,8 @@ export async function configure( } export const preview = undefined; + +/** Default export for schema generation and app bridges. */ +export default function Nuvemshop(props: Props) { + return { state: props }; +} diff --git a/packages/apps-nuvemshop/src/utils/listing.ts b/packages/apps-nuvemshop/src/utils/listing.ts index 367f6f5b..b974602a 100644 --- a/packages/apps-nuvemshop/src/utils/listing.ts +++ b/packages/apps-nuvemshop/src/utils/listing.ts @@ -22,7 +22,17 @@ export const LISTING_WINDOW = 200; * The theme's "user" (merchant's manual order) isn't in the API, so it's not * offered; `?sort_by=user` falls back to the default sort. */ -export const SORT_OPTIONS: SortOption[] = [ +export type SortValue = + | "best-selling" + | "price-ascending" + | "price-descending" + | "alpha-ascending" + | "alpha-descending" + | "created-descending" + | "created-ascending" + | "discount-descending"; + +export const SORT_OPTIONS: (SortOption & { value: SortValue })[] = [ { value: "best-selling", label: "Mais vendidos" }, { value: "price-ascending", label: "Menor preço" }, { value: "price-descending", label: "Maior preço" },