create-shopify-firebase-app 2.0.6 → 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 -238
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings page logic
|
|
3
3
|
* Load, edit, and save app settings via the API.
|
|
4
|
-
*
|
|
4
|
+
* Uses Polaris Web Components + data-save-bar for native save/discard UX.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
(function () {
|
|
@@ -9,161 +9,94 @@
|
|
|
9
9
|
|
|
10
10
|
// ── State ───────────────────────────────────────────────────────
|
|
11
11
|
var savedSettings = {};
|
|
12
|
-
var isDirty = false;
|
|
13
|
-
var isSaving = false;
|
|
14
12
|
|
|
15
13
|
// ── Init ────────────────────────────────────────────────────────
|
|
16
14
|
document.addEventListener("DOMContentLoaded", function () {
|
|
15
|
+
if (typeof apiFetch !== "function") {
|
|
16
|
+
var loading = document.getElementById("settings-loading");
|
|
17
|
+
if (loading) {
|
|
18
|
+
loading.innerHTML =
|
|
19
|
+
'<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>';
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
17
23
|
loadSettings();
|
|
18
|
-
|
|
24
|
+
setupForm();
|
|
19
25
|
});
|
|
20
26
|
|
|
21
|
-
// ──
|
|
22
|
-
function
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
37
|
-
}
|
|
27
|
+
// ── Form wiring (data-save-bar handles save/discard bar) ────────
|
|
28
|
+
function setupForm() {
|
|
29
|
+
var form = document.getElementById("settings-form");
|
|
30
|
+
|
|
31
|
+
// Submit = Save
|
|
32
|
+
form.addEventListener("submit", async function (e) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
await saveSettings();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Reset = Discard
|
|
38
|
+
form.addEventListener("reset", function () {
|
|
39
|
+
populateForm(savedSettings);
|
|
40
|
+
showToast("Changes discarded");
|
|
41
|
+
});
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
// ── Load settings ──────────────────────────────────────────────
|
|
41
45
|
async function loadSettings() {
|
|
42
|
-
var
|
|
46
|
+
var loading = document.getElementById("settings-loading");
|
|
47
|
+
var content = document.getElementById("settings-content");
|
|
43
48
|
|
|
44
49
|
try {
|
|
45
50
|
var data = await apiFetch("/api/settings");
|
|
46
51
|
savedSettings = data.settings || data || {};
|
|
47
|
-
|
|
52
|
+
populateForm(savedSettings);
|
|
53
|
+
|
|
54
|
+
// Hide loading, show content
|
|
55
|
+
if (loading) loading.style.display = "none";
|
|
56
|
+
content.style.display = "";
|
|
48
57
|
} catch (err) {
|
|
49
|
-
// If 404 or no settings yet,
|
|
58
|
+
// If 404 or no settings yet, show empty form
|
|
50
59
|
if (err.message && err.message.indexOf("404") !== -1) {
|
|
51
60
|
savedSettings = {};
|
|
52
|
-
|
|
61
|
+
populateForm(savedSettings);
|
|
62
|
+
if (loading) loading.style.display = "none";
|
|
63
|
+
content.style.display = "";
|
|
53
64
|
} else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
if (loading) {
|
|
66
|
+
loading.innerHTML =
|
|
67
|
+
'<s-banner tone="critical">' +
|
|
68
|
+
"<s-text>" +
|
|
69
|
+
"<s-text fontWeight=\"semibold\">Could not load settings</s-text> " +
|
|
70
|
+
escapeHtml(err.message) +
|
|
71
|
+
"</s-text></s-banner>" +
|
|
72
|
+
'<s-box paddingBlockStart="base"><s-button onclick="location.reload()">Retry</s-button></s-box>';
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
// ──
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
html += '<p class="mb-4">Configure basic app settings.</p>';
|
|
74
|
-
|
|
75
|
-
// Greeting message
|
|
76
|
-
html += '<div class="form-group">';
|
|
77
|
-
html += '<label for="setting-greeting">Store greeting message</label>';
|
|
78
|
-
html += '<input type="text" class="form-input" id="setting-greeting" placeholder="Welcome to our store!" value="' + escapeAttr(settings.greeting || "") + '">';
|
|
79
|
-
html += '<p class="form-hint">Displayed to customers when they visit your app.</p>';
|
|
80
|
-
html += "</div>";
|
|
81
|
-
|
|
82
|
-
// Theme select
|
|
83
|
-
html += '<div class="form-group">';
|
|
84
|
-
html += '<label for="setting-theme">Theme</label>';
|
|
85
|
-
html += '<select class="form-select" id="setting-theme">';
|
|
86
|
-
html += '<option value="auto"' + (settings.theme === "auto" ? " selected" : "") + '>Auto (System)</option>';
|
|
87
|
-
html += '<option value="light"' + (settings.theme === "light" ? " selected" : "") + ">Light</option>";
|
|
88
|
-
html += '<option value="dark"' + (settings.theme === "dark" ? " selected" : "") + ">Dark</option>";
|
|
89
|
-
html += "</select>";
|
|
90
|
-
html += '<p class="form-hint">Choose the visual theme for your app interface.</p>';
|
|
91
|
-
html += "</div>";
|
|
92
|
-
|
|
93
|
-
html += "</div>"; // end card
|
|
94
|
-
|
|
95
|
-
// Notifications card
|
|
96
|
-
html += '<div class="card">';
|
|
97
|
-
html += '<h2>Notifications</h2>';
|
|
98
|
-
html += '<p class="mb-4">Manage email and push notification preferences.</p>';
|
|
99
|
-
|
|
100
|
-
// Enable notifications checkbox
|
|
101
|
-
html += '<div class="form-group">';
|
|
102
|
-
html += '<label class="form-checkbox">';
|
|
103
|
-
html += '<input type="checkbox" id="setting-notifications"' + (settings.notifications ? " checked" : "") + ">";
|
|
104
|
-
html += "<span>Enable email notifications</span>";
|
|
105
|
-
html += "</label>";
|
|
106
|
-
html += '<p class="form-hint" style="margin-left: 26px;">Receive email alerts for new orders and important events.</p>';
|
|
107
|
-
html += "</div>";
|
|
108
|
-
|
|
109
|
-
// Order notifications checkbox
|
|
110
|
-
html += '<div class="form-group">';
|
|
111
|
-
html += '<label class="form-checkbox">';
|
|
112
|
-
html += '<input type="checkbox" id="setting-order-alerts"' + (settings.orderAlerts ? " checked" : "") + ">";
|
|
113
|
-
html += "<span>Order alert notifications</span>";
|
|
114
|
-
html += "</label>";
|
|
115
|
-
html += '<p class="form-hint" style="margin-left: 26px;">Get notified when a new order is placed.</p>';
|
|
116
|
-
html += "</div>";
|
|
117
|
-
|
|
118
|
-
html += "</div>"; // end card
|
|
119
|
-
|
|
120
|
-
// Advanced card
|
|
121
|
-
html += '<div class="card">';
|
|
122
|
-
html += '<h2>Advanced</h2>';
|
|
123
|
-
html += '<p class="mb-4">Advanced customization options.</p>';
|
|
124
|
-
|
|
125
|
-
// Custom CSS textarea
|
|
126
|
-
html += '<div class="form-group">';
|
|
127
|
-
html += '<label for="setting-css">Custom CSS</label>';
|
|
128
|
-
html += '<textarea class="form-textarea" id="setting-css" rows="6" placeholder="/* Add custom styles here */">' + escapeHtml(settings.customCss || "") + "</textarea>";
|
|
129
|
-
html += '<p class="form-hint">Add custom CSS to override default styles. Use with caution.</p>';
|
|
130
|
-
html += "</div>";
|
|
131
|
-
|
|
132
|
-
html += "</div>"; // end card
|
|
133
|
-
|
|
134
|
-
// Save button (visible below form too)
|
|
135
|
-
html += '<div class="flex justify-end gap-3 mb-8">';
|
|
136
|
-
html += '<button class="btn" onclick="discardChanges()">Discard</button>';
|
|
137
|
-
html += '<button class="btn btn-primary" id="save-button" onclick="saveSettings()">Save settings</button>';
|
|
138
|
-
html += "</div>";
|
|
139
|
-
|
|
140
|
-
container.innerHTML = html;
|
|
141
|
-
|
|
142
|
-
// Attach change listeners for dirty tracking
|
|
143
|
-
var inputs = container.querySelectorAll("input, select, textarea");
|
|
144
|
-
for (var i = 0; i < inputs.length; i++) {
|
|
145
|
-
inputs[i].addEventListener("input", markDirty);
|
|
146
|
-
inputs[i].addEventListener("change", markDirty);
|
|
147
|
-
}
|
|
78
|
+
// ── Populate form fields ────────────────────────────────────────
|
|
79
|
+
function populateForm(settings) {
|
|
80
|
+
setFieldValue("setting-greeting", settings.greeting || "");
|
|
81
|
+
setFieldValue("setting-theme", settings.theme || "auto");
|
|
82
|
+
setFieldValue("setting-css", settings.customCss || "");
|
|
83
|
+
setSwitch("setting-notifications", !!settings.notifications);
|
|
84
|
+
setSwitch("setting-order-alerts", !!settings.orderAlerts);
|
|
148
85
|
}
|
|
149
86
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
isDirty = true;
|
|
154
|
-
|
|
155
|
-
var saveBar = document.getElementById("save-bar");
|
|
156
|
-
if (saveBar && saveBar.show) {
|
|
157
|
-
saveBar.show();
|
|
158
|
-
}
|
|
87
|
+
function setFieldValue(id, value) {
|
|
88
|
+
var el = document.getElementById(id);
|
|
89
|
+
if (el) el.value = value;
|
|
159
90
|
}
|
|
160
91
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
92
|
+
function setSwitch(id, checked) {
|
|
93
|
+
var el = document.getElementById(id);
|
|
94
|
+
if (!el) return;
|
|
95
|
+
el.checked = checked;
|
|
96
|
+
if (checked) {
|
|
97
|
+
el.setAttribute("checked", "");
|
|
98
|
+
} else {
|
|
99
|
+
el.removeAttribute("checked");
|
|
167
100
|
}
|
|
168
101
|
}
|
|
169
102
|
|
|
@@ -185,20 +118,11 @@
|
|
|
185
118
|
|
|
186
119
|
function getChecked(id) {
|
|
187
120
|
var el = document.getElementById(id);
|
|
188
|
-
return el ? el.checked : false;
|
|
121
|
+
return el ? el.hasAttribute("checked") || el.checked : false;
|
|
189
122
|
}
|
|
190
123
|
|
|
191
124
|
// ── Save settings ──────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
if (isSaving) return;
|
|
194
|
-
isSaving = true;
|
|
195
|
-
|
|
196
|
-
var saveButton = document.getElementById("save-button");
|
|
197
|
-
if (saveButton) {
|
|
198
|
-
saveButton.classList.add("btn-loading");
|
|
199
|
-
saveButton.disabled = true;
|
|
200
|
-
}
|
|
201
|
-
|
|
125
|
+
async function saveSettings() {
|
|
202
126
|
try {
|
|
203
127
|
var values = collectFormValues();
|
|
204
128
|
|
|
@@ -208,34 +132,12 @@
|
|
|
208
132
|
});
|
|
209
133
|
|
|
210
134
|
savedSettings = values;
|
|
211
|
-
clearDirty();
|
|
212
135
|
showToast("Settings saved successfully");
|
|
213
136
|
} catch (err) {
|
|
214
137
|
showToast("Failed to save: " + err.message, true);
|
|
215
|
-
} finally {
|
|
216
|
-
isSaving = false;
|
|
217
|
-
if (saveButton) {
|
|
218
|
-
saveButton.classList.remove("btn-loading");
|
|
219
|
-
saveButton.disabled = false;
|
|
220
|
-
}
|
|
221
138
|
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// ── Discard changes ─────────────────────────────────────────────
|
|
225
|
-
window.discardChanges = function () {
|
|
226
|
-
var container = document.getElementById("settings-container");
|
|
227
|
-
renderForm(container, savedSettings);
|
|
228
|
-
clearDirty();
|
|
229
|
-
showToast("Changes discarded");
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// ── Helpers ─────────────────────────────────────────────────────
|
|
233
|
-
function escapeAttr(str) {
|
|
234
|
-
return String(str)
|
|
235
|
-
.replace(/&/g, "&")
|
|
236
|
-
.replace(/"/g, """)
|
|
237
|
-
.replace(/'/g, "'")
|
|
238
|
-
.replace(/</g, "<")
|
|
239
|
-
.replace(/>/g, ">");
|
|
240
139
|
}
|
|
140
|
+
|
|
141
|
+
// Expose for direct calls
|
|
142
|
+
window.saveSettings = saveSettings;
|
|
241
143
|
})();
|