brand
Reads the store's brands, or a single brand, for brand pages and brand-filtered product lists.
import { brand } from '@salla.sa/twilight-theme-engine/api/brands';In plain words
Brands are the makers a store sells: each has a name, a logo and a banner. brand.list() returns the store's brands for a brand index page, and brand.find(id) returns one brand.
The products of a brand come from product.list with source: 'brands' and the brand id.
Signature
brand.list(): Promise<BrandsGroup> // declared { [letter: string]: Brand[] }
brand.find(id: string): Promise<Brand>
brand.findOrThrow(id: string): Promise<Brand> // any failure → NotFoundError
brand.queries.list() // key ['brands', 'list']
brand.queries.detail(id) // key ['brands', 'detail', id]
// import type { Brand, BrandsGroup } from '@salla.sa/twilight-theme-engine/routes/brands'
interface Brand {
id: string;
name: string;
label: string;
description: string;
logo: string;
banner?: string | null;
url: string;
status: boolean;
ar_char: string; // first letter, per language
en_char: string;
channels?: string[];
metadata?: { title: string; description: string; url: string };
}Try it live
Loading brands…
import { useQuery } from '@tanstack/react-query';
import { brand } from '@salla.sa/twilight-theme-engine/api/brands';
import type { Brand } from '@salla.sa/twilight-theme-engine/routes/brands';
export function BrandWall() {
const { data } = useQuery(brand.queries.list());
// Declared as { [letter]: Brand[] }; some stores send Brand[]. Accept both.
const brands: Brand[] = Array.isArray(data) ? data : Object.values(data ?? {}).flat();
return (
<div className="brand-wall">
{brands.map((item) => <img key={item.id} src={item.logo} alt={item.name} width={96} height={48} />)}
</div>
);
}
Example
import { useQuery } from '@tanstack/react-query';
import { brand } from '@salla.sa/twilight-theme-engine/api/brands';
export function BrandHeader({ brandId }: { brandId: string }) {
const { data } = useQuery(brand.queries.detail(brandId));
if (!data) return null;
return (
<header className="brand-header">
{data.banner && <img src={data.banner} alt="" />}
<img src={data.logo} alt={data.name} width={96} height={96} />
<h1>{data.name}</h1>
</header>
);
}
How it behaves
Endpoints:
GET brands(returnsdata ?? {}) andGET brands/{id}, both public.The types come with the brands route:
import type { Brand, BrandsGroup } from '@salla.sa/twilight-theme-engine/routes/brands'.@salla.sa/twilight-theme-engine/typesexports a different, smallerBrand(id,name,logo,url).The engine product-listing loader fetches
brand.queries.detail(id)and the brand's products together; a theme lists them withproduct.queries.list({ source: 'brands', sourceValue: [id] }).
Gotchas
The declared type and the answer can differ. On the demo store
GET brandsreturned a flat array of brands with acursor, not an object keyed by letter. Code that trusts the type (Object.entries(data), then.mapon each group) gets array indexes as letters and a single brand where it expects a list. Accept both:Array.isArray(data) ? data : Object.values(data).flat(), then group byar_charoren_charyourself.brand.listtakes no page or cursor: when the API paginates the brands, only the first page is reachable.Brand ids arrived as numbers on the demo store although
Brand.idis typedstring; passString(item.id)tobrand.findandbrand.queries.detail.The subpath is plural and the export singular:
import { brand } from '@salla.sa/twilight-theme-engine/api/brands'. src/api/README.md imports from.../api/brandand passes alocaletobrand.list; neither exists.
Related
Source and docs
- Engine source:
packages/theme-engine/src/api/brands.ts