@andrezhan27/intelis-restaurant-ui
v0.2.2
Published
Shared native UI for Intelis restaurant websites and dashboards.
Readme
@andrezhan27/intelis-restaurant-ui
Shared native React/Next.js UI for Intelis restaurant websites and dashboards.
Restaurant website
Install this package and its peers (react, next, and @supabase/supabase-js).
Supabase is now required for the live banner, not just for the dashboard.
Configure NEXT_PUBLIC_SUPABASE_URL and
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY (or the legacy anon key), then add:
import { PromotionBanner } from "@andrezhan27/intelis-restaurant-ui";
<body>
<PromotionBanner restaurantId={restaurantId} />
<Header />
{children}
</body>The server renders an initial snapshot and the browser subscribes to live
changes, including when there are initially no promotions. Only a publishable or
legacy anon key may be passed to the client. Secret/service-role keys are
rejected.
Popup flow
- Active means the restaurant is active, the promotion is enabled, its start has arrived (or is unset), and its exclusive end has not arrived (or is unset).
- The centered popup opens automatically once per restaurant and browser-tab
session. Closing with the × button, backdrop, or Escape leaves a compact
See Promotions/Ver PromoçõesCTA at the top so it can be reopened. - Language is selected from the document's
langvalue, then the browser language. Portuguese locales use Portuguese UI labels; all others use English. - Desktop shows every active promotion as cards in authoritative database order. Mobile shows one card at a time with previous/next arrows and a position count.
- On mobile, the popup stays at 65% of the viewport height without scrolling. Equal-width columns show the complete image on the left and offer text on the right, including on narrow phones. Titles use up to three lines; descriptions fit the available space with an ellipsis, reserving room for the CTA and spacing.
- Promotion CTAs use normal same-tab links. Popup focus is trapped while open, background scrolling is locked, and focus is restored after closing.
- A single promotion uses a wide image-and-copy treatment inspired by a classic editorial offer modal.
Live changes and scheduling
get_restaurant_promotion_state is a narrow public RPC returning ordered active
promotion cards, server time, and the next scheduled change. It uses cache: "no-store".
Database triggers broadcast invalidations on
restaurant-promotions:<restaurantId> for promotion insert/update/delete and
restaurant activation/deactivation. Payloads contain no promotion content
(Supabase adds a delivery ID). Visitors refetch the authoritative public RPC;
public messages cannot inject banner text or links.
Changes appear as soon as the push notification and refresh arrive, subject to normal network latency. Scheduled starts trigger a refresh at the returned boundary. Known endings are removed locally at their deadline even if offline. Reconnect, visibility return, and online events refresh the data. A 30-second safety check catches missed notifications; failed requests retry with backoff from one to 30 seconds. During an outage, retain the last known unexpired data. Instant remote updates cannot be guaranteed while a visitor is offline.
The RPC's deliberate SECURITY DEFINER boundary preserves existing restrictions:
anonymous users cannot read the admin table, and draft/future/expired content is
not returned. RLS for restaurant administrators remains unchanged. Supabase's
public executable function advisor
flags this intentional public API (as it does the legacy single-banner API).
Trigger functions are private and are not executable by anonymous/authenticated
clients. Public topics disclose change timing, never unpublished row contents.
Styling
Override these brand variables to keep a site's own banner colors:
:root {
--promotion-banner-bg: #171717;
--promotion-banner-text: #ffffff;
--promotion-banner-link: #f1d48a;
--promotion-banner-border: transparent;
}Dashboard
The dashboard entry uses the existing authenticated Supabase client. RLS limits
reads and writes to owner/manager memberships for the selected restaurant.
Administrators can edit the popup title, description, safe root-relative or
HTTPS image reference, required image alt text, CTA, schedule, variant, and
priority. title is the single source of truth for promotion headings.
import { PromotionManager } from "@andrezhan27/intelis-restaurant-ui/dashboard";
<PromotionManager supabase={supabase} restaurantId={restaurantId} />Saving updates connected websites through the database trigger; onSaved remains
available for deployment-specific follow-up work.
Rollout and verification
Apply the existing promotion migrations followed by
supabase/migrations/20260901110855_add_promotion_popup_content.sql and
supabase/migrations/20260901152955_remove_legacy_promotion_message.sql before
upgrading the UI. The first migration backfills titles from existing messages;
the second removes the duplicate table column after all package code switches
to title.
The original get_active_restaurant_promotion RPC and fetchActivePromotion
export remain available for older consumers; the RPC derives its legacy
message output from title. Websites only gain the popup and live updates
after installing a release containing these changes and redeploying.
This repository does not deploy those consumer websites automatically.
npm test: build plus data-contract, URL, ordering, urgency, expiry, and key-safety tests.npm run check: TypeScript verification.tests/promotion-database.sql: database-owner integration test; fixtures and broadcasts roll back. Needs an existing active restaurant.npm run preview:banners: local popup fixture athttp://127.0.0.1:3111. Visit/mobilefor a portrait poster fixture with single and multiple offers. Uses the actual component and Supabase client against a local mock RPC and Broadcast server. Controls exercise live edits, removals, urgent notices, schedules, and outages without touching restaurant data.tests/realtime-probe.mjs: optional real-project public API/Broadcast probe; see its usage comments. Never provide a service-role key.
