Fejlesztőknek
Kezdj bele
Appok/Webáruház

Webáruház

Hol jelenik meg egy app a webáruházban. Blokkok, amelyeket a kereskedők a témaszerkesztőben helyeznek el, header- és lábléczónák, webáruházi oldalak, a kosár, és amit egy témának támogatnia kell.

Ezen az oldalon

A webáruházban az appok háromféle helyen jelenítenek meg natív workereket: a kereskedő által az oldalsablonokon elhelyezett blokkokban, a téma header- és lábléczónáiban, valamint teljes webáruházi oldalakon a /apps/<slug> címen.

A worker csak ott jelenik meg, ahová a kereskedő a közzétett témában elhelyezte, és csak addig, amíg az app telepítve van. A blokk eltávolítása vagy áthelyezése, a beállításainak vagy az app release-ének módosítása, vagy az app eltávolítása leveszi az oldalról; a független témamódosítások közzététele viszont nem. Ha egy worker betöltése meghiúsul, a vásárlók hibaüzenet helyett egyszerűen semmit sem látnak a helyén.

Blokkok

A storefront.home, storefront.products, storefront.product, storefront.cart, storefront.checkout, storefront.login, storefront.register, storefront.account, storefront.contact és storefront.page célú workerek az adott oldalsablon blokkjai. A témaszerkesztőben minden gyári téma engedi, hogy a kereskedők hozzáadják őket a szekciókhoz ezeken a sablonokon.

Minden blokk a @poltura/app-sdk/elements csomagból származó defineBlock függvénnyel írja le magát: a nevet, az ikont és a kategóriát a témaszerkesztő választójában, a beállításait és a kereskedőknek megjelenő stílusvezérlőket. Sorold fel a definíciókat tartalmazó fájlokat a manifest elements mezőjében, és a blokk type értékét a worker element mezőjében; a build beolvassa őket a release-be. Lásd: Beállítások és fordítások.

export const recommendationsPanelElement = defineBlock({
  type: 'recommendations-panel',
  label: 'Recommendations',
  settings: {
    heading: field.text({ label: 'Heading', default: 'Recommendations' }),
    max_items: field.number({ label: 'Item limit', min: 1, max: 8, default: 4 }),
  },
});

export const recommendationsElement = defineBlock({
  type: 'recommendations',
  label: 'You may also like',
  description: 'Recommended products next to the current product.',
  icon: 'product-grid',
  category: 'commerce',
  settings: {
    heading: field.text({ label: 'Block heading', default: 'You may also like' }),
    variant_id: field.text({
      label: 'Featured variant',
      default: '0199f3a2-af74-7051-b24d-6f8b1c3d4e5f',
    }),
  },
  style: ['spacing', 'background', 'border'],
  presets: [
    {
      id: 'quiet',
      label: 'Quiet',
      settings: { heading: 'More to explore' },
    },
  ],
});

export const newsletterCaptureElement = defineBlock({
  type: 'newsletter-capture',
  label: 'Newsletter capture',
  icon: 'mail',
  category: 'forms',
  style: ['spacing', 'background'],
});

export const recommendationsPageElement = defineBlock({
  type: 'recommendations-page',
  label: 'Recommendations page',
  settings: {
    heading: field.text({ label: 'Page heading', default: 'Recommendations' }),
  },
});
export function StorefrontProduct(): ReactNode {
  const copy = copyFor(useLocale());
  const settings = useSettings();
  const theme = useTheme();
  const cart = useCart();
  const toast = useToast();
  const host = useStorefrontContext();
  const recommend = useAction('recommend');
  const heading = String(settings.heading ?? copy.alsoLike);
  const storeName = theme?.settings?.store_name;
  const [contextLabel, setContextLabel] = useState(copy.alsoLike);

  useEffect(() => {
    void host.read().then(async (context) => {
      if (context.kind === 'product') setContextLabel(context.product.title);
      const suggestion = await recommend({
        productId: context.kind === 'product' ? context.product.id : null,
      });
      const title = z.object({ title: z.string().min(1) }).safeParse(suggestion);
      if (title.success) setContextLabel(title.data.title);
    });
  }, [host, recommend]);

  return (
    <Card className='offer-panel rounded-theme bg-surface text-foreground p-4'>
      <Stack>
        <Heading className='font-display'>{heading}</Heading>
        <Text>{z.string().min(1).safeParse(storeName).data ?? contextLabel}</Text>
        <Button
          label={copy.addToCart}
          className='bg-primary text-on-primary'
          onPress={async () => {
            const parsed = VariantId.safeParse(settings.variant_id);
            if (!parsed.success) {
              await toast.show({ tone: 'warning', message: copy.cartRejected });
              return;
            }
            const reply = await cart.add({ variantId: parsed.data, quantity: 1 });
            if (reply.status === 'ok') {
              await toast.show({ tone: 'success', message: copy.added });
              return;
            }
            await toast.show({ tone: 'warning', message: copy.cartRejected });
          }}
        />
      </Stack>
    </Card>
  );
}
  • useStorefrontContext().read() megadja a blokknak, hogy hol van: { kind: 'product', product, selectedVariantId? } egy termékoldalon, { kind: 'collection', collection } egy gyűjteményoldalon, { kind: 'cart', cart } a kosárban, { kind: 'page', path } máshol, vagy { kind: 'none' }.
  • useCart() beolvassa és módosítja a vásárló kosarát a read, add, update és remove metódusokkal, elemenként egyszerre 1 és 99 közötti darabszámmal. Egy opcionális idempotenciakulcs (legfeljebb 64 betű, számjegy, _ és -) biztosítja, hogy az újrapróbált módosítás csak egyszer érvényesüljön. A kosárhívások csak a webáruházban működnek.
  • useAction('recommend') ugyanúgy a Poltura által aláírt kéréssel hívja meg a szerveredet, ahogy egy adminfelületi worker teszi. A webáruházi hívások a vásárló adatait is átadják.

A workerek nem tudják közvetlenül olvasni az app tárhelyét. A példa a kiemelt változatot a blokk beállításaiban tárolja, a szervere pedig a teljes listát a tárhelyen tartja.

Header- és lábléczónák

A storefront.layout.header és storefront.layout.footer célú workerek a téma headerében és láblécében jelennek meg. Minden gyári témában megvan mindkét zóna. A headerzóna saját, teljes szélességű sorban helyezkedik el a navigáció alatt, így egy magasabb worker lejjebb tolja az oldalt ahelyett, hogy eltakarná.

export function StorefrontFooter(): ReactNode {
  const copy = copyFor(useLocale());
  const toast = useToast();
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  return (
    <Card className='rounded-theme bg-muted text-foreground p-4'>
      <Stack>
        <Text>{copy.footerHelp}</Text>
        <TextField
          label={copy.emailLabel}
          value={email}
          error={error}
          onChange={(value) => {
            setEmail(value);
            setError('');
          }}
        />
        <Button
          label={copy.subscribe}
          onPress={async () => {
            const parsed = Email.safeParse(email);
            if (!parsed.success) {
              setError(copy.invalidEmail);
              await toast.show({ tone: 'warning', message: copy.invalidEmail });
              return;
            }
            setError('');
            await toast.show({ tone: 'success', message: copy.subscribed });
          }}
        />
      </Stack>
    </Card>
  );
}

A lábléc űrlapja a böngészőben ellenőrzi a címet, és nem küldi el sehová.

Webáruházi oldalak

A storefront.app-page célú worker a bolt oldalává válik a /apps/<slug> címen, amint szerepel a manifest storefrontPages listájában (legfeljebb 8 ilyen oldal adható meg). Kimarad a sitemapből, kivéve, ha a seo.indexable értéke true. A kereskedők megtalálják ezeket az oldalakat a témaszerkesztőben, és hivatkozhatnak rájuk a menükből.

export function AppPage(): ReactNode {
  const copy = copyFor(useLocale());
  const settings = useSettings();
  const navigate = useNavigate();
  const heading = String(settings.heading ?? copy.heading);

  return (
    <Card className='rounded-theme bg-surface text-foreground p-4'>
      <Stack>
        <Heading>{heading}</Heading>
        <Text>{copy.pageIntro}</Text>
        <Button label={copy.shopProducts} onPress={() => navigate.to('/products')} />
      </Stack>
    </Card>
  );
}

A storefrontPages bejegyzés nélkül a worker bekerül a bundle-be, de egyetlen cím sem szolgálja ki.

Amit egy témának támogatnia kell

A Poltura minden gyári témája támogatja az appokat. Egy saját készítésű témában a következők kellenek az appok működéséhez:

  • szekciók, amelyek bármilyen blokkot elfogadnak (ELEMENT_WILDCARD) a fenti sablonokon;
  • a layout.header és layout.footer zónák;
  • a /apps/<slug> route-ok a layoutján belül.

Következő lépések

  • UI: minden komponens és hook, amit egy worker használhat.
  • Formázás: hogyan veszik át a workerek a bolt megjelenését.
Utoljára frissítve