@aglyn/plugins-commerce 1.0.0-beta.206 → 1.0.0-beta.209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-commerce",
3
- "version": "1.0.0-beta.206",
3
+ "version": "1.0.0-beta.209",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.206",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.206",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.206",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.206",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.206",
33
- "@aglyn/shared-util-email": "1.0.0-beta.206",
34
- "@aglyn/shared-util-http": "1.0.0-beta.206",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.206",
36
- "@aglyn/shared-util-tools": "1.0.0-beta.206",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.206",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.206",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.206",
28
+ "@aglyn/aglyn": "1.0.0-beta.209",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.209",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.209",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.209",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.209",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.209",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.209",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.209",
36
+ "@aglyn/shared-util-tools": "1.0.0-beta.209",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.209",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.209",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.209",
40
40
  "@stripe/react-stripe-js": "^3.10.0",
41
41
  "@stripe/stripe-js": "^5.10.0",
42
42
  "@swc/helpers": "0.5.23",
@@ -21,9 +21,10 @@ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
21
21
  import { Button, FormControlLabel, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
22
22
  import { doc, setDoc } from "firebase/firestore";
23
23
  import { useCallback, useState } from "react";
24
- import { useFirestore } from "@aglyn/tenant-feature-instance";
24
+ import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
25
25
  import { useFirestoreDoc, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
26
26
  import { hostPublicOrigin, pluginDocsHelp } from "@aglyn/aglyn";
27
+ import { announceSiteWideChange } from "@aglyn/aglyn/app-utils/announce-site-wide-change";
27
28
  /**
28
29
  * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP
29
30
  * template screen (drives /products/{slug}), currency display, checkout
@@ -33,6 +34,7 @@ import { hostPublicOrigin, pluginDocsHelp } from "@aglyn/aglyn";
33
34
  var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _current_pdpScreenId, _current_collectionScreenId, _current_currency, _current_termsUrl, _current_receiptFooter;
34
35
  const { hostId } = props;
35
36
  const firestore = useFirestore();
37
+ const { data: user } = useUser();
36
38
  const { enqueueSnackbar } = useSnackbar();
37
39
  const { data: store, status: storeStatus, /**
38
40
  * The settings doc this form is seeded from is unconfirmed by the server
@@ -123,6 +125,13 @@ import { hostPublicOrigin, pluginDocsHelp } from "@aglyn/aglyn";
123
125
  });
124
126
  }
125
127
  setDraft(null);
128
+ // The product and collection templates, the currency and the checkout
129
+ // rules render on the live store with no publish step: drop its cached
130
+ // pages so they show now rather than within the hour.
131
+ void announceSiteWideChange({
132
+ user,
133
+ hostId
134
+ });
126
135
  enqueueSnackbar('Store settings saved', {
127
136
  variant: 'success',
128
137
  persist: false
@@ -131,6 +140,7 @@ import { hostPublicOrigin, pluginDocsHelp } from "@aglyn/aglyn";
131
140
  current,
132
141
  firestore,
133
142
  hostId,
143
+ user,
134
144
  enqueueSnackbar,
135
145
  storeStatus,
136
146
  storeFromCache
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, setDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n )\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [current, firestore, hostId, enqueueSnackbar, storeStatus, storeFromCache])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /products/{slug} — design it in the ' +\n 'besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","setDoc","useCallback","useState","useFirestore","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","StoreSettingsCard","props","current","hostId","firestore","enqueueSnackbar","data","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, setDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\nimport { announceSiteWideChange } from '@aglyn/aglyn/app-utils/announce-site-wide-change'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n )\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n // The product and collection templates, the currency and the checkout\n // rules render on the live store with no publish step: drop its cached\n // pages so they show now rather than within the hour.\n void announceSiteWideChange({ user, hostId })\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [\n current,\n firestore,\n hostId,\n user,\n enqueueSnackbar,\n storeStatus,\n storeFromCache,\n ])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /products/{slug} — design it in the ' +\n 'besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","setDoc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","announceSiteWideChange","StoreSettingsCard","props","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts';
18
+ export declare const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition;
19
+ export declare const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition;
20
+ export declare const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition;
21
+ export declare const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition;
22
+ export declare const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { BUNDLE_ID } from "./bundle-common.js";
17
+ /**
18
+ * The storefront's operator alerts (AGL-3377): the money faults only this
19
+ * plugin can see, declared in the registry's shape and registered from
20
+ * `declarations.server`, so core lists none of them and an install without
21
+ * commerce shows none of them on Staff → Operator alerts.
22
+ *
23
+ * Each is raised through `raiseOperatorAlert` with its definition, which
24
+ * also registers it in a process whose declarations have not loaded yet.
25
+ */ const DAY = 24 * 60;
26
+ export const COMMERCE_CHARGEBACK_UNROUTABLE = {
27
+ type: 'commerce.chargebackUnroutable',
28
+ pluginId: BUNDLE_ID,
29
+ label: 'Storefront chargeback could not be routed',
30
+ description: 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',
31
+ tier: 'must',
32
+ category: 'payments',
33
+ title: 'A {{amount}} chargeback could not be routed to an order',
34
+ body: '{{detail}}',
35
+ delivery: 'immediate',
36
+ dedupeWindowMinutes: DAY,
37
+ defaultEnabled: true
38
+ };
39
+ export const COMMERCE_TAX_NOT_REVERSED = {
40
+ type: 'commerce.taxNotReversed',
41
+ pluginId: BUNDLE_ID,
42
+ label: 'Subscription sales tax not reversed',
43
+ description: 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',
44
+ tier: 'must',
45
+ category: 'payments',
46
+ title: 'Sales tax not reversed on invoice {{invoiceId}}',
47
+ body: '{{amount}} of sales tax on invoice {{invoiceId}} (site {{hostId}}) was not pulled back from the merchant: {{reason}}.',
48
+ delivery: 'immediate',
49
+ dedupeWindowMinutes: 7 * DAY,
50
+ defaultEnabled: true
51
+ };
52
+ export const COMMERCE_SELLER_SHARE_NOT_REVERSED = {
53
+ type: 'commerce.sellerShareNotReversed',
54
+ pluginId: BUNDLE_ID,
55
+ label: 'Seller share not reversed on a lost dispute',
56
+ description: 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',
57
+ tier: 'must',
58
+ category: 'payments',
59
+ title: 'Seller share not reversed on dispute {{disputeId}}',
60
+ body: 'The lost dispute {{disputeId}} on order {{orderId}} (site {{hostId}}) cost {{amount}}, and the merchant’s share was not reversed: {{reason}}.',
61
+ delivery: 'immediate',
62
+ dedupeWindowMinutes: 30 * DAY,
63
+ defaultEnabled: true
64
+ };
65
+ export const COMMERCE_FEE_REPRICE_REFUSED = {
66
+ type: 'commerce.feeRepriceRefused',
67
+ pluginId: BUNDLE_ID,
68
+ label: 'Platform fee correction refused',
69
+ description: 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',
70
+ tier: 'must',
71
+ category: 'payments',
72
+ title: 'Platform fee not corrected on {{subject}}',
73
+ body: 'The platform fee on {{subject}} (site {{hostId}}) could not be corrected: {{reason}}.',
74
+ delivery: 'immediate',
75
+ dedupeWindowMinutes: 7 * DAY,
76
+ defaultEnabled: true
77
+ };
78
+ export const COMMERCE_OPERATOR_ALERTS = [
79
+ COMMERCE_CHARGEBACK_UNROUTABLE,
80
+ COMMERCE_TAX_NOT_REVERSED,
81
+ COMMERCE_SELLER_SHARE_NOT_REVERSED,
82
+ COMMERCE_FEE_REPRICE_REFUSED
83
+ ];
84
+
85
+ //# sourceMappingURL=operator-alerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/operator-alerts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { OperatorAlertDefinition } from '@aglyn/aglyn/app-utils/operator-alerts'\nimport { BUNDLE_ID } from './bundle-common'\n\n/**\n * The storefront's operator alerts (AGL-3377): the money faults only this\n * plugin can see, declared in the registry's shape and registered from\n * `declarations.server`, so core lists none of them and an install without\n * commerce shows none of them on Staff → Operator alerts.\n *\n * Each is raised through `raiseOperatorAlert` with its definition, which\n * also registers it in a process whose declarations have not loaded yet.\n */\nconst DAY = 24 * 60\n\nexport const COMMERCE_CHARGEBACK_UNROUTABLE: OperatorAlertDefinition = {\n type: 'commerce.chargebackUnroutable',\n pluginId: BUNDLE_ID,\n label: 'Storefront chargeback could not be routed',\n description:\n 'A storefront chargeback matched no order, or more than one, so no order was flagged, no seller share reversed and no merchant told. A missing index means every storefront chargeback is being ignored.',\n tier: 'must',\n category: 'payments',\n title: 'A {{amount}} chargeback could not be routed to an order',\n body: '{{detail}}',\n delivery: 'immediate',\n dedupeWindowMinutes: DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_TAX_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.taxNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Subscription sales tax not reversed',\n description:\n 'Sales tax collected on a storefront subscription stayed with the merchant instead of coming back to the platform, which files and remits it. The platform may owe tax it does not hold.',\n tier: 'must',\n category: 'payments',\n title: 'Sales tax not reversed on invoice {{invoiceId}}',\n body:\n '{{amount}} of sales tax on invoice {{invoiceId}} (site {{hostId}}) was not pulled back from the merchant: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_SELLER_SHARE_NOT_REVERSED: OperatorAlertDefinition = {\n type: 'commerce.sellerShareNotReversed',\n pluginId: BUNDLE_ID,\n label: 'Seller share not reversed on a lost dispute',\n description:\n 'A storefront dispute was lost and the merchant’s share could not be pulled back, so the platform is out the principal. Recover it by hand in Stripe.',\n tier: 'must',\n category: 'payments',\n title: 'Seller share not reversed on dispute {{disputeId}}',\n body:\n 'The lost dispute {{disputeId}} on order {{orderId}} (site {{hostId}}) cost {{amount}}, and the merchant’s share was not reversed: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 30 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_FEE_REPRICE_REFUSED: OperatorAlertDefinition = {\n type: 'commerce.feeRepriceRefused',\n pluginId: BUNDLE_ID,\n label: 'Platform fee correction refused',\n description:\n 'Stripe refused to change a storefront subscription’s platform fee, or to refund the part of a fee charged on the wrong base. The merchant is being charged a fee the plan does not set.',\n tier: 'must',\n category: 'payments',\n title: 'Platform fee not corrected on {{subject}}',\n body: 'The platform fee on {{subject}} (site {{hostId}}) could not be corrected: {{reason}}.',\n delivery: 'immediate',\n dedupeWindowMinutes: 7 * DAY,\n defaultEnabled: true,\n}\n\nexport const COMMERCE_OPERATOR_ALERTS: readonly OperatorAlertDefinition[] = [\n COMMERCE_CHARGEBACK_UNROUTABLE,\n COMMERCE_TAX_NOT_REVERSED,\n COMMERCE_SELLER_SHARE_NOT_REVERSED,\n COMMERCE_FEE_REPRICE_REFUSED,\n]\n"],"names":["BUNDLE_ID","DAY","COMMERCE_CHARGEBACK_UNROUTABLE","type","pluginId","label","description","tier","category","title","body","delivery","dedupeWindowMinutes","defaultEnabled","COMMERCE_TAX_NOT_REVERSED","COMMERCE_SELLER_SHARE_NOT_REVERSED","COMMERCE_FEE_REPRICE_REFUSED","COMMERCE_OPERATOR_ALERTS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,SAAS,QAAQ,qBAAiB;AAE3C;;;;;;;;CAQC,GACD,MAAMC,MAAM,KAAK;AAEjB,OAAO,MAAMC,iCAA0D;IACrEC,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqBX;IACrBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMC,4BAAqD;IAChEX,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAME,qCAA8D;IACzEZ,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MACE;IACFC,UAAU;IACVC,qBAAqB,KAAKX;IAC1BY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMG,+BAAwD;IACnEb,MAAM;IACNC,UAAUJ;IACVK,OAAO;IACPC,aACE;IACFC,MAAM;IACNC,UAAU;IACVC,OAAO;IACPC,MAAM;IACNC,UAAU;IACVC,qBAAqB,IAAIX;IACzBY,gBAAgB;AAClB,EAAC;AAED,OAAO,MAAMI,2BAA+D;IAC1Ef;IACAY;IACAC;IACAC;CACD,CAAA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The commerce plugin's server declarations: the light registrations core
19
+ * reads at boot, before any surface loads.
20
+ *
21
+ * Its operator alerts (AGL-3377), so Staff → Operator alerts lists them and
22
+ * staff can switch them before the first one is ever raised.
23
+ */
24
+ export declare function registerCommerceServerDeclarations(): void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The seam from its own module, not the plugin-manager barrel: boot needs the
17
+ // registry and nothing else, and the barrel reaches the client contexts.
18
+ import { registerOperatorAlerts } from "@aglyn/aglyn/plugin-manager/operator-alerts";
19
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
20
+ import { COMMERCE_OPERATOR_ALERTS } from "./constants/operator-alerts.js";
21
+ /**
22
+ * The commerce plugin's server declarations: the light registrations core
23
+ * reads at boot, before any surface loads.
24
+ *
25
+ * Its operator alerts (AGL-3377), so Staff → Operator alerts lists them and
26
+ * staff can switch them before the first one is ever raised.
27
+ */ export function registerCommerceServerDeclarations() {
28
+ registerOperatorAlerts(COMMERCE_OPERATOR_ALERTS, {
29
+ pluginId: BUNDLE_ID
30
+ });
31
+ }
32
+
33
+ //# sourceMappingURL=declarations.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The seam from its own module, not the plugin-manager barrel: boot needs the\n// registry and nothing else, and the barrel reaches the client contexts.\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_OPERATOR_ALERTS } from './constants/operator-alerts'\n\n/**\n * The commerce plugin's server declarations: the light registrations core\n * reads at boot, before any surface loads.\n *\n * Its operator alerts (AGL-3377), so Staff → Operator alerts lists them and\n * staff can switch them before the first one is ever raised.\n */\nexport function registerCommerceServerDeclarations(): void {\n registerOperatorAlerts(COMMERCE_OPERATOR_ALERTS, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerOperatorAlerts","BUNDLE_ID","COMMERCE_OPERATOR_ALERTS","registerCommerceServerDeclarations","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,yEAAyE;AACzE,SAASA,sBAAsB,QAAQ,8CAA6C;AACpF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,wBAAwB,QAAQ,iCAA6B;AAEtE;;;;;;CAMC,GACD,OAAO,SAASC;IACdH,uBAAuBE,0BAA0B;QAAEE,UAAUH;IAAU;AACzE"}