The full purchase flow, in your pocket
Browse, search, add to bag, check out and track, using real Tommy Bahama products and imagery. Each feature below explains what it does for the business. Pick one to jump the phone there, or just use the app and the list follows along. For the technical detail, see the Architecture tab.
Interactive prototype · sample catalog data
Architecture
Prototyped in React. Built on FLEX.
BASE1 FLEX is a Flutter metaframework for commerce apps. The production Tommy Bahama app starts from the FLEX SAP Commerce Cloud starter kit. This browser prototype mirrors that structure one to one, so every screen you click maps to a real FLEX page, bloc and SAP client.
- One codebase, two storesFlutter compiles to native iOS and Android from a single FLEX codebase, so features ship to both stores together.
- Start at 70%, not zeroThe SAP starter kit already implements the OCC purchase flow end to end. Work focuses on Tommy Bahama brand, content and integrations.
- Swap any backendPages talk to client interfaces, not SAP directly. Replatforming commerce or CMS means writing a new adapter, not rewriting the app.
- Secure by defaultDual-token OAuth2 with refresh, keychain storage, biometric unlock and tokenized payments keep the app out of PCI scope.
The stack
- Tommy Bahama appWhat this prototype shows
- Pages: Home, PLP, Search, PDP, Cart, Checkout, Orders, Account
- Blocs per feature (state + events)
- Brand theme: navy, sand, Playfair + DM Sans
- FLEX SAP Starter KitPre-built commerce flows, reskinned
- Product, Cart, Checkout, Order, Account modules
- Routing + deep links
- Guest → user cart merge
- FLEX UIDesign system widgets
- FlexImageGallery, ProductCard, VariantSelector
- Forms, steppers, sheets, toasts
- Accessibility + dynamic type
- FLEX Core featuresCross-cutting services
- Auth (anonymous + OAuth2 user tokens)
- Network (retry, caching, interceptors)
- Secure storage · i18n · Logging · Analytics
- SAP Client adapterBackend-agnostic interface
- SapAuthClient · SapProductClient · SapSearchClient
- SapCartClient · SapCheckoutClient
- SapOrderClient · SapUserClient
Systems of record
- SAP Commerce CloudOCC v2 REST: catalog, cart, checkout, orders, users
- AEM + Scene7Content fragments and responsive product imagery
- BloomreachSearch relevance, merchandising, trending
- BazaarvoiceRatings & reviews
- Chase PaymentechCard tokenization; Apple Pay & Google Pay
- NarvarShipment tracking milestones
- Adobe Target + AnalyticsA/B tests and event tracking
- KlaviyoPush notification campaigns
Technical deep dive
For engineering teams. Expand a section to see how each feature maps to FLEX, the SAP calls behind the purchase flow, and the code pattern the app is built on.
Feature-by-feature FLEX mappingPages, blocs, clients, widgets and API calls for each feature
- Personalized homeHome is composed from AEM content fragments and Scene7 renditions. Each slot can be an Adobe Target activity; the push opt-in card registers the device token with Klaviyo.
- Page
- HomePage
- Bloc
- HomeBloc · ContentBloc
- Client
- CmsClient (AEM adapter)
- Widgets
- FlexHeroBannerCategoryTileGridProductCarouselPushOptInCard
- GET /content/dam/tommy/…/homepage (AEM)
- GET /occ/v2/{site}/cms/pages?pageType=ContentPage
- Search & discoveryBloomreach Discovery serves relevance, synonyms and merchandising rules, with OCC product search as a fallback. Queries are debounced in the bloc; trending terms come from Bloomreach analytics.
- Page
- SearchPage
- Bloc
- SearchBloc (debounced)
- Client
- SapSearchClient · BloomreachClient
- Widgets
- FlexSearchFieldSuggestionListProductGrid
- GET /api/v1/core/?q={term} (Bloomreach)
- GET /occ/v2/{site}/products/search?query={term}
- Product listingCategory pages use the FLEX ProductGrid with sort and facet chips. Facets and sort codes are returned by OCC search so the app matches the site.
- Page
- ProductListPage
- Bloc
- ProductListBloc
- Client
- SapSearchClient
- Widgets
- CategoryChipsSortSheetProductCardWishlistButton
- GET /occ/v2/{site}/products/search?query=:relevance:allCategories:{code}&sort={sort}
- Product detailThe PDP loads the full OCC product including the color × size variant matrix and stock. The gallery streams device-sized Scene7 renditions; ratings render natively from Bazaarvoice.
- Page
- ProductDetailPage
- Bloc
- ProductDetailBloc · CartBloc
- Client
- SapProductClient · BazaarvoiceClient
- Widgets
- FlexImageGalleryVariantSelectorRatingSummaryReviewListAddToCartBar
- GET /occ/v2/{site}/products/{code}?fields=FULL
- GET /data/reviews.json?Filter=ProductId:{code} (Bazaarvoice)
- Bag & promotionsThe bag is an OCC cart: anonymous for guests, merged into the user cart on sign-in. Quantities, vouchers and totals are recalculated by SAP on every change.
- Page
- CartPage
- Bloc
- CartBloc
- Client
- SapCartClient
- Widgets
- CartLineItemQuantityStepperPromoCodeFieldFreeShippingMeterOrderSummary
- POST /occ/v2/{site}/users/anonymous/carts
- PATCH /users/{user}/carts/{cart}/entries/{n}
- POST /users/{user}/carts/{cart}/vouchers?voucherId={code}
- Express checkoutFour-step native checkout: address, delivery mode, payment and review. Cards are tokenized by Chase Paymentech so no card data reaches app servers.
- Page
- CheckoutPage
- Bloc
- CheckoutBloc (stepper)
- Client
- SapCheckoutClient · PaymentClient
- Widgets
- AddressFormDeliveryModeListPaymentMethodPickerApplePayButtonPlaceOrderBar
- PUT /carts/{cart}/addresses/delivery
- PUT /carts/{cart}/deliverymode?deliveryModeId={id}
- POST /carts/{cart}/paymentdetails
- POST /users/{user}/orders?cartId={cart}
- Order trackingOrder history comes from OCC; shipment milestones come from Narvar and trigger Klaviyo pushes. Deep links open straight to the order.
- Page
- OrderHistoryPage · OrderDetailPage
- Bloc
- OrdersBloc
- Client
- SapOrderClient · NarvarClient
- Widgets
- OrderCardTrackingTimelineDeepLinkHandler
- GET /occ/v2/{site}/users/{user}/orders
- GET /users/{user}/orders/{code}
- GET /tracking/{carrier}/{number} (Narvar)
- Account & loyaltyFLEX SapAuthFeature runs dual authentication: an anonymous token for browsing and an OAuth2 user token after sign-in, refreshed automatically and stored in the device keychain.
- Page
- AccountPage · LoginPage
- Bloc
- AuthBloc · ProfileBloc
- Client
- SapAuthClient · SapUserClient
- Widgets
- LoginFormBiometricToggleLoyaltyCardNotificationSettings
- POST /authorizationserver/oauth/token
- GET /occ/v2/{site}/users/current
Purchase flow, call by callEvery prototype step mapped to its bloc and SAP Commerce request
Prices, promotions and totals are always recalculated by SAP, never trusted from the device.
| Step | Bloc | OCC request |
|---|---|---|
| Launch | AuthBloc | POST /authorizationserver/oauth/token (client_credentials) |
| Browse category | ProductListBloc | GET /occ/v2/tommybahama/products/search?query=:relevance:allCategories:mens-shirts |
| View product | ProductDetailBloc | GET /occ/v2/tommybahama/products/ST327332?fields=FULL |
| Add to bag | CartBloc | POST /users/anonymous/carts/{guid}/entries |
| Apply promo | CartBloc | POST /users/anonymous/carts/{guid}/vouchers?voucherId=FAMILY |
| Shipping address | CheckoutBloc | PUT /carts/{guid}/addresses/delivery |
| Delivery mode | CheckoutBloc | PUT /carts/{guid}/deliverymode?deliveryModeId=ground |
| Payment | CheckoutBloc | POST /carts/{guid}/paymentdetails (Paymentech token) |
| Place order | CheckoutBloc | POST /users/{user}/orders?cartId={guid}&termsChecked=true |
| Track | OrdersBloc | GET /users/{user}/orders/{code} + Narvar tracking |
The bloc pattern and codeHow state flows, how this prototype maps to FLEX, and a sample bloc
Widgets dispatch events, blocs call a client, clients call SAP, and new state flows back to the UI. Business logic stays testable and out of the widgets.
- Widget
- Event
- Bloc
- SapClient
- OCC API
- State
- Widget
How this prototype maps to FLEX
- Flutter PageReact screen componentcomponents/tb/screens/*
- Bloc (events → state)useReducer store + actionscomponents/tb/store.tsx
- FLEX UI widgetShared componentcomponents/tb/shared.tsx
- SAP ClientCatalog / pricing adapter (mock data)lib/catalog.ts · lib/pricing.ts
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc(this._cartClient) : super(const CartState.empty()) {
on<AddToCart>(_onAddToCart);
on<ApplyVoucher>(_onApplyVoucher);
}
final SapCartClient _cartClient;
Future<void> _onAddToCart(AddToCart e, Emitter<CartState> emit) async {
emit(state.copyWith(status: CartStatus.updating));
final cart = await _cartClient.addEntry(
cartId: state.cartId,
productCode: e.variantCode,
quantity: e.quantity,
);
emit(CartState.fromOcc(cart)); // totals always come from SAP
}
}