create-shopify-firebase-app 2.0.5 → 2.1.0
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/README.md +122 -74
- package/lib/index.js +540 -244
- package/lib/preflight.js +358 -0
- package/lib/provision.js +187 -5
- package/package.json +1 -1
- package/templates/js/functions/package.json +6 -6
- package/templates/js/functions/src/admin-api.js +1 -1
- package/templates/js/functions/src/auth.js +45 -13
- package/templates/js/functions/src/firebase.js +6 -4
- package/templates/js/functions/src/index.js +14 -0
- package/templates/shared/extensions/theme-block/blocks/app-block.liquid +1 -1
- package/templates/shared/extensions/theme-block/shopify.extension.toml +1 -1
- package/templates/shared/firebase.json +1 -1
- package/templates/shared/firestore.rules +10 -2
- package/templates/shopify.app.toml +1 -1
- package/templates/ts/functions/package.json +10 -10
- package/templates/ts/functions/src/admin-api.ts +5 -2
- package/templates/ts/functions/src/auth.ts +69 -15
- package/templates/ts/functions/src/firebase.ts +8 -4
- package/templates/ts/functions/tsconfig.json +2 -1
- package/templates/web/css/app.css +78 -1138
- package/templates/web/index.html +79 -68
- package/templates/web/js/app.js +60 -33
- package/templates/web/js/pages/home.js +33 -22
- package/templates/web/js/pages/polaris-demo.js +25 -83
- package/templates/web/js/pages/products.js +100 -114
- package/templates/web/js/pages/settings.js +68 -166
- package/templates/web/polaris.html +1221 -950
- package/templates/web/products.html +35 -40
- package/templates/web/settings.html +69 -17
package/templates/web/index.html
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>{{APP_NAME}}</title>
|
|
7
7
|
<link rel="stylesheet" href="/css/app.css">
|
|
8
|
+
<meta name="shopify-api-key" content="{{API_KEY}}">
|
|
8
9
|
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
|
|
10
|
+
<script src="https://cdn.shopify.com/shopifycloud/polaris.js"></script>
|
|
9
11
|
</head>
|
|
10
12
|
<body>
|
|
11
13
|
<ui-nav-menu>
|
|
@@ -15,83 +17,92 @@
|
|
|
15
17
|
<a href="/polaris">Components</a>
|
|
16
18
|
</ui-nav-menu>
|
|
17
19
|
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
<div class="page">
|
|
20
|
+
<s-page heading="Dashboard">
|
|
21
21
|
<!-- Welcome banner -->
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
<s-section>
|
|
23
|
+
<s-banner tone="info">
|
|
24
|
+
<s-text>Welcome to <s-text fontWeight="semibold">{{APP_NAME}}</s-text>! Your app is running on Firebase Hosting and embedded in the Shopify admin. Edit the files in <code>web/</code> to build your app.</s-text>
|
|
25
|
+
</s-banner>
|
|
26
|
+
</s-section>
|
|
26
27
|
|
|
27
28
|
<!-- Store info -->
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<div class="loading-state">
|
|
35
|
-
<div class="spinner"></div>
|
|
36
|
-
<p>Loading store info...</p>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
29
|
+
<s-section heading="Store Information">
|
|
30
|
+
<s-box id="shop-loading" padding="large-400">
|
|
31
|
+
<s-stack alignItems="center"><s-spinner></s-spinner></s-stack>
|
|
32
|
+
</s-box>
|
|
33
|
+
<div id="shop-info-content" style="display:none;"></div>
|
|
34
|
+
</s-section>
|
|
40
35
|
|
|
41
36
|
<!-- Stats row -->
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
<s-section>
|
|
38
|
+
<s-grid gridTemplateColumns="@container (inline-size <= 400px) 1fr, 1fr 1fr 1fr" gap="base">
|
|
39
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
40
|
+
<s-stack gap="small-200">
|
|
41
|
+
<s-text color="subdued">Products</s-text>
|
|
42
|
+
<s-text id="stat-products" variant="headingLg">--</s-text>
|
|
43
|
+
</s-stack>
|
|
44
|
+
</s-box>
|
|
45
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
46
|
+
<s-stack gap="small-200">
|
|
47
|
+
<s-text color="subdued">Store Plan</s-text>
|
|
48
|
+
<s-text id="stat-plan" variant="headingLg">--</s-text>
|
|
49
|
+
</s-stack>
|
|
50
|
+
</s-box>
|
|
51
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
52
|
+
<s-stack gap="small-200">
|
|
53
|
+
<s-text color="subdued">App Status</s-text>
|
|
54
|
+
<s-badge tone="success">Active</s-badge>
|
|
55
|
+
</s-stack>
|
|
56
|
+
</s-box>
|
|
57
|
+
</s-grid>
|
|
58
|
+
</s-section>
|
|
56
59
|
|
|
57
60
|
<!-- Quick links -->
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</
|
|
67
|
-
</
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
<s-section heading="Quick Links">
|
|
62
|
+
<s-grid gridTemplateColumns="@container (inline-size <= 480px) 1fr, 1fr 1fr 1fr" gap="base">
|
|
63
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
64
|
+
<s-clickable href="/products">
|
|
65
|
+
<s-stack gap="small-200">
|
|
66
|
+
<s-text variant="headingSm">Products</s-text>
|
|
67
|
+
<s-text color="subdued">Search and browse products</s-text>
|
|
68
|
+
</s-stack>
|
|
69
|
+
</s-clickable>
|
|
70
|
+
</s-box>
|
|
71
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
72
|
+
<s-clickable href="/settings">
|
|
73
|
+
<s-stack gap="small-200">
|
|
74
|
+
<s-text variant="headingSm">Settings</s-text>
|
|
75
|
+
<s-text color="subdued">Configure your app</s-text>
|
|
76
|
+
</s-stack>
|
|
77
|
+
</s-clickable>
|
|
78
|
+
</s-box>
|
|
79
|
+
<s-box border="base" borderRadius="base" padding="base">
|
|
80
|
+
<s-clickable href="/polaris">
|
|
81
|
+
<s-stack gap="small-200">
|
|
82
|
+
<s-text variant="headingSm">Components</s-text>
|
|
83
|
+
<s-text color="subdued">Polaris UI reference</s-text>
|
|
84
|
+
</s-stack>
|
|
85
|
+
</s-clickable>
|
|
86
|
+
</s-box>
|
|
87
|
+
</s-grid>
|
|
88
|
+
</s-section>
|
|
84
89
|
|
|
85
90
|
<!-- Quick actions -->
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
<button
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
<s-section heading="Quick Actions">
|
|
92
|
+
<s-text color="subdued">Test your API connection and App Bridge features.</s-text>
|
|
93
|
+
<s-stack direction="inline" gap="base" style="margin-top: 12px;">
|
|
94
|
+
<s-button variant="primary" onclick="testApiConnection()">Test API Connection</s-button>
|
|
95
|
+
<s-button onclick="showToast('Everything is working!')">Test Toast</s-button>
|
|
96
|
+
</s-stack>
|
|
97
|
+
</s-section>
|
|
98
|
+
|
|
99
|
+
<!-- Footer -->
|
|
100
|
+
<s-section>
|
|
101
|
+
<s-stack alignItems="center">
|
|
102
|
+
<s-text color="subdued">Built with Shopify App Bridge + Firebase Hosting. Edit <code>web/</code> to customize.</s-text>
|
|
103
|
+
</s-stack>
|
|
104
|
+
</s-section>
|
|
105
|
+
</s-page>
|
|
95
106
|
|
|
96
107
|
<script src="/js/app.js"></script>
|
|
97
108
|
<script src="/js/pages/home.js"></script>
|
package/templates/web/js/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* App Bridge initialization and shared utilities
|
|
3
|
-
* Loaded on every page -- provides apiFetch, showToast,
|
|
3
|
+
* Loaded on every page -- provides apiFetch, showToast, and helpers.
|
|
4
4
|
*
|
|
5
5
|
* In embedded admin, window.shopify is injected by the App Bridge CDN script.
|
|
6
6
|
* Outside embedded admin (direct URL visit), graceful fallbacks are used.
|
|
@@ -9,15 +9,37 @@
|
|
|
9
9
|
(function () {
|
|
10
10
|
"use strict";
|
|
11
11
|
|
|
12
|
-
// ──
|
|
12
|
+
// ── Shop context ────────────────────────────────────────────────
|
|
13
|
+
// App Bridge is the source of truth: in the embedded admin the CDN
|
|
14
|
+
// script injects window.shopify, and window.shopify.config carries
|
|
15
|
+
// { apiKey, shop, host, locale } on EVERY page load.
|
|
16
|
+
//
|
|
17
|
+
// The ?shop=/?host= query params are only present on the first load --
|
|
18
|
+
// the <ui-nav-menu> links are plain hrefs with no query string -- so
|
|
19
|
+
// they are used purely as a fallback for viewing a page outside the
|
|
20
|
+
// embedded admin (e.g. hitting the Hosting URL directly).
|
|
13
21
|
var params = new URLSearchParams(window.location.search);
|
|
14
|
-
var shop = params.get("shop") || "";
|
|
15
|
-
var host = params.get("host") || "";
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
function appBridgeConfig() {
|
|
24
|
+
return (window.shopify && window.shopify.config) || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Shared state accessible across pages.
|
|
28
|
+
// shop/host are getters, not fixed values, so they resolve at read time
|
|
29
|
+
// -- correct on every page and independent of script load order.
|
|
18
30
|
window.__app = {
|
|
19
|
-
shop
|
|
20
|
-
|
|
31
|
+
get shop() {
|
|
32
|
+
var cfg = appBridgeConfig();
|
|
33
|
+
if (cfg && cfg.shop) return cfg.shop;
|
|
34
|
+
// Query params are user-controlled -- validate before trusting.
|
|
35
|
+
var rawShop = params.get("shop") || "";
|
|
36
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com$/.test(rawShop) ? rawShop : "";
|
|
37
|
+
},
|
|
38
|
+
get host() {
|
|
39
|
+
var cfg = appBridgeConfig();
|
|
40
|
+
if (cfg && cfg.host) return cfg.host;
|
|
41
|
+
return params.get("host") || "";
|
|
42
|
+
},
|
|
21
43
|
ready: false,
|
|
22
44
|
};
|
|
23
45
|
|
|
@@ -92,50 +114,46 @@
|
|
|
92
114
|
}, 4000);
|
|
93
115
|
};
|
|
94
116
|
|
|
95
|
-
// ── Navigation
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
var search = qs.toString();
|
|
102
|
-
window.location.href = path + (search ? "?" + search : "");
|
|
103
|
-
};
|
|
117
|
+
// ── Navigation ──────────────────────────────────────────────────
|
|
118
|
+
// No helper needed: App Bridge intercepts in-app navigation, so plain
|
|
119
|
+
// <a href="/products"> links (see <ui-nav-menu>) and <s-clickable href>
|
|
120
|
+
// are the supported way to move between pages. Do NOT hand-append
|
|
121
|
+
// shop/host to URLs -- that is the legacy pattern, and window.shopify
|
|
122
|
+
// provides the shop context on every page (see window.__app above).
|
|
104
123
|
|
|
105
|
-
// ── Loading state helpers
|
|
124
|
+
// ── Loading state helpers (Polaris Web Components) ──────────────
|
|
106
125
|
window.showLoading = function showLoading(containerId) {
|
|
107
126
|
var el = document.getElementById(containerId);
|
|
108
127
|
if (!el) return;
|
|
109
128
|
el.innerHTML =
|
|
110
|
-
'<
|
|
111
|
-
'<
|
|
112
|
-
"<
|
|
113
|
-
"</
|
|
129
|
+
'<s-box padding="large-400">' +
|
|
130
|
+
'<s-stack alignItems="center" gap="base">' +
|
|
131
|
+
"<s-spinner></s-spinner>" +
|
|
132
|
+
'<s-text color="subdued">Loading\u2026</s-text>' +
|
|
133
|
+
"</s-stack></s-box>";
|
|
114
134
|
};
|
|
115
135
|
|
|
116
136
|
window.hideLoading = function hideLoading(containerId) {
|
|
117
137
|
var el = document.getElementById(containerId);
|
|
118
138
|
if (!el) return;
|
|
119
|
-
|
|
120
|
-
if (spinner) spinner.remove();
|
|
139
|
+
el.innerHTML = "";
|
|
121
140
|
};
|
|
122
141
|
|
|
123
|
-
// ── Error rendering helper
|
|
142
|
+
// ── Error rendering helper (Polaris Web Components) ─────────────
|
|
124
143
|
window.showError = function showError(containerId, message) {
|
|
125
144
|
var el = document.getElementById(containerId);
|
|
126
145
|
if (!el) return;
|
|
127
146
|
el.innerHTML =
|
|
128
|
-
'<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
"</div></div>";
|
|
147
|
+
'<s-banner tone="critical">' +
|
|
148
|
+
"<s-text>" +
|
|
149
|
+
"<s-text fontWeight=\"semibold\">Something went wrong</s-text> " +
|
|
150
|
+
escapeHtml(message) +
|
|
151
|
+
"</s-text></s-banner>";
|
|
134
152
|
};
|
|
135
153
|
|
|
136
154
|
// ── Format helpers ──────────────────────────────────────────────
|
|
137
155
|
window.formatCurrency = function formatCurrency(amount, currency) {
|
|
138
|
-
return new Intl.NumberFormat(
|
|
156
|
+
return new Intl.NumberFormat(undefined, {
|
|
139
157
|
style: "currency",
|
|
140
158
|
currency: currency || "USD",
|
|
141
159
|
}).format(amount);
|
|
@@ -143,7 +161,7 @@
|
|
|
143
161
|
|
|
144
162
|
window.formatDate = function formatDate(dateStr) {
|
|
145
163
|
if (!dateStr) return "--";
|
|
146
|
-
return new Date(dateStr).toLocaleDateString(
|
|
164
|
+
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
147
165
|
year: "numeric",
|
|
148
166
|
month: "short",
|
|
149
167
|
day: "numeric",
|
|
@@ -171,7 +189,16 @@
|
|
|
171
189
|
}
|
|
172
190
|
window.escapeHtml = escapeHtml;
|
|
173
191
|
|
|
192
|
+
function escapeAttr(str) {
|
|
193
|
+
return String(str)
|
|
194
|
+
.replace(/&/g, "&")
|
|
195
|
+
.replace(/"/g, """)
|
|
196
|
+
.replace(/'/g, "'")
|
|
197
|
+
.replace(/</g, "<")
|
|
198
|
+
.replace(/>/g, ">");
|
|
199
|
+
}
|
|
200
|
+
window.escapeAttr = escapeAttr;
|
|
201
|
+
|
|
174
202
|
// ── Mark ready ──────────────────────────────────────────────────
|
|
175
203
|
window.__app.ready = true;
|
|
176
|
-
console.log("[app.js] Initialized", { shop: shop, host: host });
|
|
177
204
|
})();
|
|
@@ -1,43 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard (home) page logic
|
|
3
|
-
* Fetches shop info and renders store details + stats.
|
|
3
|
+
* Fetches shop info and renders store details + stats using Polaris Web Components.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
(function () {
|
|
7
7
|
"use strict";
|
|
8
8
|
|
|
9
9
|
// ── Load shop data on page ready ────────────────────────────────
|
|
10
|
-
document.addEventListener("DOMContentLoaded",
|
|
10
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
11
|
+
if (typeof apiFetch !== "function") {
|
|
12
|
+
var loading = document.getElementById("shop-loading");
|
|
13
|
+
if (loading) {
|
|
14
|
+
loading.innerHTML =
|
|
15
|
+
'<s-banner tone="critical"><s-text><s-text fontWeight="semibold">App failed to initialize</s-text> Core scripts did not load. Please refresh the page.</s-text></s-banner>';
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
loadDashboard();
|
|
20
|
+
});
|
|
11
21
|
|
|
12
22
|
async function loadDashboard() {
|
|
13
|
-
var
|
|
14
|
-
var
|
|
23
|
+
var loading = document.getElementById("shop-loading");
|
|
24
|
+
var content = document.getElementById("shop-info-content");
|
|
15
25
|
|
|
16
26
|
try {
|
|
17
27
|
var data = await apiFetch("/api/shop");
|
|
18
28
|
var shop = data.shop || data;
|
|
19
29
|
|
|
20
30
|
// Render store info
|
|
21
|
-
renderShopInfo(
|
|
31
|
+
renderShopInfo(content, shop);
|
|
22
32
|
|
|
23
|
-
//
|
|
24
|
-
if (
|
|
33
|
+
// Hide loading, show content
|
|
34
|
+
if (loading) loading.style.display = "none";
|
|
35
|
+
content.style.display = "";
|
|
25
36
|
|
|
26
37
|
// Update stats
|
|
27
38
|
updateStats(shop);
|
|
28
39
|
} catch (err) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
if (loading) {
|
|
41
|
+
loading.innerHTML =
|
|
42
|
+
'<s-banner tone="critical">' +
|
|
43
|
+
"<s-text>" +
|
|
44
|
+
"<s-text fontWeight=\"semibold\">Could not load store information</s-text> " +
|
|
45
|
+
escapeHtml(err.message) +
|
|
46
|
+
"</s-text></s-banner>";
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
// ── Render shop info list ───────────────────────────────────────
|
|
40
|
-
// The /api/shop endpoint returns GraphQL field names (camelCase).
|
|
41
52
|
function renderShopInfo(container, shop) {
|
|
42
53
|
var fields = [
|
|
43
54
|
{ label: "Store name", value: shop.name || "--" },
|
|
@@ -49,15 +60,16 @@
|
|
|
49
60
|
{ label: "Shopify domain", value: shop.myshopifyDomain || "--" },
|
|
50
61
|
];
|
|
51
62
|
|
|
52
|
-
var html = '<
|
|
63
|
+
var html = '<s-box border="base" borderRadius="base" padding="base">';
|
|
64
|
+
html += '<s-stack gap="small-200">';
|
|
53
65
|
for (var i = 0; i < fields.length; i++) {
|
|
54
66
|
html +=
|
|
55
|
-
|
|
56
|
-
'<
|
|
57
|
-
|
|
58
|
-
"</
|
|
67
|
+
'<s-grid gridTemplateColumns="1fr 1fr" gap="small-200">' +
|
|
68
|
+
'<s-text color="subdued">' + escapeHtml(fields[i].label) + "</s-text>" +
|
|
69
|
+
"<s-text>" + escapeHtml(String(fields[i].value)) + "</s-text>" +
|
|
70
|
+
"</s-grid>";
|
|
59
71
|
}
|
|
60
|
-
html += "</
|
|
72
|
+
html += "</s-stack></s-box>";
|
|
61
73
|
|
|
62
74
|
container.innerHTML = html;
|
|
63
75
|
}
|
|
@@ -68,7 +80,6 @@
|
|
|
68
80
|
var planEl = document.getElementById("stat-plan");
|
|
69
81
|
|
|
70
82
|
if (productsEl) {
|
|
71
|
-
// GraphQL returns productCount: { count } via alias
|
|
72
83
|
var count = shop.productCount && shop.productCount.count;
|
|
73
84
|
productsEl.textContent = count !== undefined && count !== null ? String(count) : "--";
|
|
74
85
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Polaris
|
|
3
|
-
* Copy-to-clipboard,
|
|
2
|
+
* Polaris Web Components reference page logic
|
|
3
|
+
* Copy-to-clipboard, resource picker demo, and interactive examples.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
(function () {
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
// ── Copy to clipboard ──────────────────────────────────────────
|
|
10
10
|
window.copyCode = function (button) {
|
|
11
|
-
// Find the <pre><code> sibling
|
|
12
11
|
var container = button.closest(".demo-code") || button.closest(".code-block");
|
|
13
12
|
if (!container) return;
|
|
14
13
|
|
|
@@ -17,7 +16,6 @@
|
|
|
17
16
|
|
|
18
17
|
var text = codeEl.textContent;
|
|
19
18
|
|
|
20
|
-
// Use clipboard API when available
|
|
21
19
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
22
20
|
navigator.clipboard.writeText(text).then(function () {
|
|
23
21
|
showCopiedFeedback(button);
|
|
@@ -55,64 +53,6 @@
|
|
|
55
53
|
}, 2000);
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
// ── Banner reset ───────────────────────────────────────────────
|
|
59
|
-
window.resetBanners = function () {
|
|
60
|
-
var preview = document.getElementById("banners-preview");
|
|
61
|
-
if (!preview) return;
|
|
62
|
-
|
|
63
|
-
// Remove existing banners
|
|
64
|
-
var existing = preview.querySelectorAll(".banner");
|
|
65
|
-
for (var i = 0; i < existing.length; i++) {
|
|
66
|
-
existing[i].remove();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Re-insert banners before the reset button
|
|
70
|
-
var resetBtn = preview.querySelector("button");
|
|
71
|
-
|
|
72
|
-
var banners = [
|
|
73
|
-
{
|
|
74
|
-
cls: "banner-info",
|
|
75
|
-
icon: "ℹ",
|
|
76
|
-
title: "Information",
|
|
77
|
-
text: "This is an informational banner. Use it for tips or neutral messages.",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
cls: "banner-success",
|
|
81
|
-
icon: "✓",
|
|
82
|
-
title: "Success",
|
|
83
|
-
text: "The operation completed successfully. Your changes have been saved.",
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
cls: "banner-warning",
|
|
87
|
-
icon: "⚠",
|
|
88
|
-
title: "Warning",
|
|
89
|
-
text: "This action may have unintended consequences. Please review before proceeding.",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
cls: "banner-critical",
|
|
93
|
-
icon: "✖",
|
|
94
|
-
title: "Error",
|
|
95
|
-
text: "Something went wrong. Please try again or contact support.",
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
for (var j = 0; j < banners.length; j++) {
|
|
100
|
-
var b = banners[j];
|
|
101
|
-
var div = document.createElement("div");
|
|
102
|
-
div.className = "banner " + b.cls;
|
|
103
|
-
div.innerHTML =
|
|
104
|
-
'<div class="banner-icon">' + b.icon + "</div>" +
|
|
105
|
-
'<div class="banner-content">' +
|
|
106
|
-
"<p><strong>" + b.title + "</strong></p>" +
|
|
107
|
-
"<p>" + b.text + "</p>" +
|
|
108
|
-
"</div>" +
|
|
109
|
-
'<button class="banner-dismiss" onclick="this.closest(\'.banner\').remove()">×</button>';
|
|
110
|
-
preview.insertBefore(div, resetBtn);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
showToast("Banners restored");
|
|
114
|
-
};
|
|
115
|
-
|
|
116
56
|
// ── Confirmation delete demo ───────────────────────────────────
|
|
117
57
|
window.demoConfirmDelete = function () {
|
|
118
58
|
var modal = document.getElementById("demo-modal-confirm");
|
|
@@ -127,13 +67,12 @@
|
|
|
127
67
|
var resultDiv = document.getElementById("picker-demo-result");
|
|
128
68
|
if (resultDiv) {
|
|
129
69
|
resultDiv.innerHTML =
|
|
130
|
-
'<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"</div></div>";
|
|
70
|
+
'<s-banner tone="warning">' +
|
|
71
|
+
"<s-text>" +
|
|
72
|
+
"<s-text fontWeight=\"semibold\">Not available</s-text> " +
|
|
73
|
+
"The Resource Picker requires the app to be loaded inside the Shopify admin iframe. " +
|
|
74
|
+
"Install the app on a Shopify store to test this feature." +
|
|
75
|
+
"</s-text></s-banner>";
|
|
137
76
|
}
|
|
138
77
|
return;
|
|
139
78
|
}
|
|
@@ -143,20 +82,20 @@
|
|
|
143
82
|
var resultDiv = document.getElementById("picker-demo-result");
|
|
144
83
|
|
|
145
84
|
if (!selected || selected.length === 0) {
|
|
146
|
-
if (resultDiv) resultDiv.innerHTML = '<
|
|
85
|
+
if (resultDiv) resultDiv.innerHTML = '<s-text color="subdued">Selection cancelled.</s-text>';
|
|
147
86
|
return;
|
|
148
87
|
}
|
|
149
88
|
|
|
150
|
-
var html = '<
|
|
151
|
-
html += '<
|
|
152
|
-
html += '<
|
|
89
|
+
var html = '<s-box border="base" borderRadius="base" padding="base">';
|
|
90
|
+
html += '<s-stack gap="base">';
|
|
91
|
+
html += '<s-text variant="headingSm">Selected ' + selected.length + " product(s)</s-text>";
|
|
153
92
|
for (var i = 0; i < selected.length; i++) {
|
|
154
|
-
html +=
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
93
|
+
html += '<s-stack direction="inline" gap="base" alignItems="center">';
|
|
94
|
+
html += '<s-text fontWeight="semibold">' + escapeHtml(selected[i].title || "Untitled") + "</s-text>";
|
|
95
|
+
html += '<s-badge tone="success">Selected</s-badge>';
|
|
96
|
+
html += "</s-stack>";
|
|
158
97
|
}
|
|
159
|
-
html += "</
|
|
98
|
+
html += "</s-stack></s-box>";
|
|
160
99
|
if (resultDiv) resultDiv.innerHTML = html;
|
|
161
100
|
} catch (err) {
|
|
162
101
|
showToast("Resource picker error: " + err.message, true);
|
|
@@ -176,13 +115,16 @@
|
|
|
176
115
|
|
|
177
116
|
// ── Smooth scroll for anchor links ─────────────────────────────
|
|
178
117
|
document.addEventListener("DOMContentLoaded", function () {
|
|
179
|
-
var anchors = document.querySelectorAll('a[href^="#section-"]');
|
|
118
|
+
var anchors = document.querySelectorAll('a[href^="#section-"], s-button[href^="#section-"]');
|
|
180
119
|
for (var i = 0; i < anchors.length; i++) {
|
|
181
120
|
anchors[i].addEventListener("click", function (e) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
target.
|
|
121
|
+
var href = this.getAttribute("href");
|
|
122
|
+
if (href && href.charAt(0) === "#") {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
var target = document.querySelector(href);
|
|
125
|
+
if (target) {
|
|
126
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
127
|
+
}
|
|
186
128
|
}
|
|
187
129
|
});
|
|
188
130
|
}
|