Rename app to Offerlyst: Bundles & BOGO; add App Store icon and brand assets
Icon rebuilt as a full-bleed 1200x1200 square (Shopify rounds corners itself). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
938387407f
commit
ba7a7c89a2
@ -2,5 +2,5 @@
|
||||
APP_DOMAIN=bundles.example.com
|
||||
SHOPIFY_API_KEY=your_client_id
|
||||
SHOPIFY_API_SECRET=your_client_secret
|
||||
SHOPIFY_APP_HANDLE=bundle-offers
|
||||
SHOPIFY_APP_HANDLE=offerlyst
|
||||
POSTGRES_PASSWORD=change-me-to-a-long-random-string
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Bundle Offers web app (admin, app proxy, webhooks, scheduler). The Shopify extensions are deployed
|
||||
# Offerlyst web app (admin, app proxy, webhooks, scheduler). The Shopify extensions are deployed
|
||||
# separately with `shopify app deploy`.
|
||||
FROM node:22-alpine AS build
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
# Bundle Offers (Shopify app)
|
||||
# Offerlyst: Bundles & BOGO (Shopify app)
|
||||
|
||||
Brand assets: `brand/` (App Store icon `offerlyst-icon-1200.png`).
|
||||
|
||||
Public Shopify app that re-creates the ClicksToCart Odoo module `c2c_bundle_offers`: fixed bundles, mix & match
|
||||
and buy X get Y, applied by **one** automatic app discount whose function evaluates every running offer in
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// orders/create -> analytics (F23, N02). The function's candidate message is the offer name, or "A + B" when
|
||||
// one line carries two offers; we attribute by name. If Shopify reports only the discount title
|
||||
// ("Bundle Offers"), the usage is kept under that title. Verify the exact title on a dev store (README).
|
||||
// (DISCOUNT_TITLE, "Offerlyst bundles"), the usage is kept under that title. Verify the exact title on a dev store (README).
|
||||
import db from "../db.server";
|
||||
|
||||
export interface OrderPayload {
|
||||
|
||||
@ -6,7 +6,7 @@ import { compile, settingsOf, type CompileResult } from "./compile";
|
||||
import { gql, ensureShop, type AdminLike } from "./shop.server";
|
||||
|
||||
export const FUNCTION_HANDLE = "bundle-discount";
|
||||
export const DISCOUNT_TITLE = "Bundle Offers";
|
||||
export const DISCOUNT_TITLE = "Offerlyst bundles";
|
||||
export const STOREFRONT_NAMESPACE = "bundle_offers";
|
||||
|
||||
const CREATE = `#graphql
|
||||
@ -94,7 +94,7 @@ async function ensureDiscount(admin: AdminLike, shop: { id: string; discountNode
|
||||
},
|
||||
});
|
||||
const err = userErrorText(data.discountAutomaticAppCreate.userErrors);
|
||||
if (err) throw new Error(`Could not create the Bundle Offers discount: ${err}`);
|
||||
if (err) throw new Error(`Could not create the Offerlyst discount: ${err}`);
|
||||
const id = data.discountAutomaticAppCreate.automaticAppDiscount.discountId as string;
|
||||
await db.shop.update({ where: { id: shop.id }, data: { discountNodeId: id } });
|
||||
return { id, created: true };
|
||||
|
||||
@ -106,7 +106,7 @@ export function bundlesListLiquid(offers: OfferWithRelations[], shop: ShopCtx, l
|
||||
return `<section class="bo-section">
|
||||
<h1 class="bo-section__title">Bundle offers</h1>
|
||||
${body}
|
||||
${shop.branding ? `<p class="bo-powered">Bundles by <a href="https://apps.shopify.com/bundle-offers" rel="nofollow">Bundle Offers</a></p>` : ""}
|
||||
${shop.branding ? `<p class="bo-powered">Bundles by <a href="https://apps.shopify.com/offerlyst" rel="nofollow">Offerlyst</a></p>` : ""}
|
||||
</section>`;
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ export default function App() {
|
||||
return (
|
||||
<div className={styles.index}>
|
||||
<div className={styles.content}>
|
||||
<h1 className={styles.heading}>Bundle Offers: run all your festival deals at once</h1>
|
||||
<h1 className={styles.heading}>Offerlyst: run all your bundle deals at once</h1>
|
||||
<p className={styles.text}>
|
||||
Fixed bundles, mix & match and buy X get Y, applied automatically at checkout. Each item counts towards one offer, in the order you choose.
|
||||
</p>
|
||||
|
||||
@ -78,7 +78,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
export default function Home() {
|
||||
const d = useLoaderData<typeof loader>();
|
||||
const steps = [
|
||||
{ done: d.setup.embedOn, label: "Turn on the Bundle Offers app embed", help: "Loads the storefront script and styles (cart hints, add bundle to cart).", action: <s-button href={d.embedLink} target="_blank">Open theme editor</s-button> },
|
||||
{ done: d.setup.embedOn, label: "Turn on the Offerlyst app embed", help: "Loads the storefront script and styles (cart hints, add bundle to cart).", action: <s-button href={d.embedLink} target="_blank">Open theme editor</s-button> },
|
||||
{ done: d.menuLinked, label: "Link the bundles page in your menu", help: "Your bundles page is at /apps/bundles. Add it to the main menu (Online Store › Navigation), or place the “Bundles grid” block on any page.", action: <s-button href={`https://${d.shop.domain}/admin/menus`} target="_blank">Open navigation</s-button> },
|
||||
{ done: d.totalOffers > 0, label: "Create your first offer", help: "Start from scratch or from a festival template.", action: <s-button href="/app/offers/new">Create offer</s-button> },
|
||||
{ done: d.running.length > 0, label: "Publish an offer and preview it", help: "Published offers within their dates run automatically in cart and checkout.", action: <s-button href={`https://${d.shop.domain}/apps/bundles`} target="_blank">View bundles page</s-button> },
|
||||
@ -86,7 +86,7 @@ export default function Home() {
|
||||
const doneCount = steps.filter((s) => s.done).length;
|
||||
|
||||
return (
|
||||
<s-page heading="Bundle Offers">
|
||||
<s-page heading="Offerlyst">
|
||||
<s-button slot="primary-action" variant="primary" href="/app/offers/new">Create offer</s-button>
|
||||
|
||||
{d.shop.publishError ? (
|
||||
|
||||
@ -10,7 +10,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const { admin, session } = await authenticate.admin(request);
|
||||
const shop = await syncShop(admin, session.shop); // refresh the plan from activeSubscriptions
|
||||
const store = session.shop.replace(".myshopify.com", "");
|
||||
const handle = process.env.SHOPIFY_APP_HANDLE || "bundle-offers";
|
||||
const handle = process.env.SHOPIFY_APP_HANDLE || "offerlyst";
|
||||
return {
|
||||
current: shop.planCode,
|
||||
pricingUrl: `https://admin.shopify.com/store/${store}/charges/${handle}/pricing_plans`,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Public privacy policy (required for the App Store listing): https://<app domain>/privacy
|
||||
import type { MetaFunction } from "react-router";
|
||||
|
||||
export const meta: MetaFunction = () => [{ title: "Privacy policy · FestiveBundle" }];
|
||||
export const meta: MetaFunction = () => [{ title: "Privacy policy · Offerlyst" }];
|
||||
|
||||
const CONTACT = "info@metatroncubesolutions.com";
|
||||
|
||||
@ -9,7 +9,7 @@ export default function Privacy() {
|
||||
return (
|
||||
<main style={{ maxWidth: 720, margin: "48px auto", padding: "0 20px", font: "16px/1.6 system-ui, sans-serif", color: "#1a1a1a" }}>
|
||||
<h1>Privacy policy</h1>
|
||||
<p>Last updated: October 13, 2026. This policy explains what the FestiveBundle app (by Metatroncube Software Solutions) collects when a merchant installs it on a Shopify store.</p>
|
||||
<p>Last updated: October 14, 2026. This policy explains what the Offerlyst app (by Metatroncube Software Solutions) collects when a merchant installs it on a Shopify store.</p>
|
||||
|
||||
<h2>What we collect</h2>
|
||||
<ul>
|
||||
|
||||
@ -5,7 +5,7 @@ import db from "../db.server";
|
||||
// The access token is already revoked here, so the app can't call the Admin API. Shopify removes the app's
|
||||
// functions with the app, so the automatic discount stops applying, and it deletes app-owned ($app and
|
||||
// app-data) metafields. We mark the shop and keep its data for 48 h, until shop/redact deletes it.
|
||||
// The in-app "Remove all Bundle Offers data" action (Settings) deletes the discount explicitly.
|
||||
// The in-app "Remove all Offerlyst data" action (Settings) deletes the discount explicitly.
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const { shop, session, topic } = await authenticate.webhook(request);
|
||||
console.log(`Received ${topic} webhook for ${shop}`);
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ActionFunctionArgs } from "react-router";
|
||||
import { authenticate } from "../shopify.server";
|
||||
import db from "../db.server";
|
||||
|
||||
// Mandatory GDPR webhooks. Bundle Offers stores no customer personal data: only order ids, order names and
|
||||
// Mandatory GDPR webhooks. Offerlyst stores no customer personal data: only order ids, order names and
|
||||
// amounts for analytics (no names, emails or addresses). customers/data_request and customers/redact have
|
||||
// nothing to return or erase; shop/redact (48 h after uninstall) deletes everything about the shop.
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
|
||||
BIN
brand/offerlyst-brand-board.png
Normal file
BIN
brand/offerlyst-brand-board.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
BIN
brand/offerlyst-icon-1200.png
Normal file
BIN
brand/offerlyst-icon-1200.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 966 KiB |
BIN
brand/offerlyst-icon-256.png
Normal file
BIN
brand/offerlyst-icon-256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
BIN
brand/offerlyst-icon-64.png
Normal file
BIN
brand/offerlyst-icon-64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
brand/offerlyst-logo-horizontal.png
Normal file
BIN
brand/offerlyst-logo-horizontal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 598 KiB |
BIN
brand/offerlyst-symbol-transparent.png
Normal file
BIN
brand/offerlyst-symbol-transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 794 KiB |
@ -28,7 +28,7 @@ services:
|
||||
SHOPIFY_API_KEY: ${SHOPIFY_API_KEY:?set SHOPIFY_API_KEY in .env}
|
||||
SHOPIFY_API_SECRET: ${SHOPIFY_API_SECRET:?set SHOPIFY_API_SECRET in .env}
|
||||
SHOPIFY_APP_URL: https://${APP_DOMAIN:?set APP_DOMAIN in .env}
|
||||
SHOPIFY_APP_HANDLE: ${SHOPIFY_APP_HANDLE:-bundle-offers}
|
||||
SHOPIFY_APP_HANDLE: ${SHOPIFY_APP_HANDLE:-offerlyst}
|
||||
SCOPES: read_products,write_discounts,read_discounts,read_orders,write_files,read_themes,write_app_proxy
|
||||
expose:
|
||||
- "3000"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "Bundle Offers",
|
||||
"name": "Offerlyst bundles",
|
||||
"description": "Applies fixed, mix & match and buy X get Y bundle discounts. Each item counts towards one offer, in priority order."
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Bundle Offers: ONE automatic app discount per shop runs this function, which evaluates every running offer.
|
||||
# Offerlyst: ONE automatic app discount per shop runs this function, which evaluates every running offer.
|
||||
# Rust (not JS): the JS build needed 39M instructions just to read a 200-line cart (limit 11M). See README.
|
||||
api_version = "2026-07"
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/* Bundle Offers storefront styles.
|
||||
/* Offerlyst storefront styles.
|
||||
Inherits the theme: fonts, text colour and background come from the page. Lines are a mix of the text
|
||||
colour, so they work on light and dark themes alike. The merchant can set an accent and a radius (Settings).
|
||||
The one decorative device is the coupon stub on the badge: festival offers as tear-off coupons. */
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/* Bundle Offers storefront script (app embed). Vanilla JS, no dependencies.
|
||||
/* Offerlyst storefront script (app embed). Vanilla JS, no dependencies.
|
||||
* - converts shop-currency deal amounts when the customer shops in another currency
|
||||
* - bundle pages (/apps/bundles/...): quantity steppers, "N of M chosen", add all items in one request
|
||||
* - refreshes cart hints after AJAX cart changes */
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
{% schema %}
|
||||
{
|
||||
"name": "Bundle Offers",
|
||||
"name": "Offerlyst",
|
||||
"target": "body",
|
||||
"javascript": "bundle-offers.js",
|
||||
"stylesheet": "bundle-offers.css",
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<a class="bo-button" href="{{ routes.all_products_collection_url }}">{{ 'bundle_offers.grid.continue' | t }}</a>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- if data.brand -%}<p class="bo-powered">Bundles by <a href="https://apps.shopify.com/bundle-offers" rel="nofollow">Bundle Offers</a></p>{%- endif -%}
|
||||
{%- if data.brand -%}<p class="bo-powered">Bundles by <a href="https://apps.shopify.com/offerlyst" rel="nofollow">Offerlyst</a></p>{%- endif -%}
|
||||
</section>
|
||||
|
||||
{% schema %}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
name = "Bundle Offers storefront"
|
||||
name = "Offerlyst storefront"
|
||||
type = "theme"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Bundle Offers app database (brief §14). Postgres.
|
||||
// Offerlyst app database (brief §14). Postgres.
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# Bundle Offers — Shopify app configuration.
|
||||
# Offerlyst — Shopify app configuration.
|
||||
# Run `npm run config:link` once to connect this folder to your app in the Partner Dashboard (fills client_id).
|
||||
client_id = ""
|
||||
name = "Bundle Offers"
|
||||
name = "Offerlyst: Bundles & BOGO"
|
||||
handle = "offerlyst"
|
||||
application_url = "https://example.com"
|
||||
embedded = true
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user