create-shopify-firebase-app 1.0.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.
@@ -0,0 +1,64 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{APP_NAME}}</title>
7
+ <link rel="stylesheet" href="/css/app.css" />
8
+ <!-- App Bridge: data-api-key must match your Shopify app's client_id -->
9
+ <script
10
+ data-api-key="{{API_KEY}}"
11
+ src="https://cdn.shopify.com/shopifycloud/app-bridge.js"
12
+ ></script>
13
+ <script src="/js/bridge.js"></script>
14
+ </head>
15
+ <body>
16
+ <div class="app-page" id="app">
17
+ <div class="loading-overlay" id="loading">
18
+ <div class="spinner"></div> Loading...
19
+ </div>
20
+ </div>
21
+
22
+ <script>
23
+ window.addEventListener("app-ready", async () => {
24
+ const app = window.App;
25
+ const container = document.getElementById("app");
26
+
27
+ try {
28
+ const data = await app.apiFetch("/api/shop");
29
+ render(data.shop);
30
+ } catch (err) {
31
+ container.innerHTML = `<div class="card"><p style="color:#d72c0d;">Error: ${err.message}</p></div>`;
32
+ }
33
+
34
+ function render(shop) {
35
+ container.innerHTML = `
36
+ <div class="page-header">
37
+ <h1>Dashboard</h1>
38
+ </div>
39
+
40
+ <div class="card">
41
+ <h2>Welcome, ${shop?.name || "merchant"}!</h2>
42
+ <p style="color:#6d7175;">
43
+ Your app is running on Firebase and embedded in the Shopify admin.
44
+ Edit <code>web/index.html</code> to build your dashboard.
45
+ </p>
46
+ </div>
47
+
48
+ <div class="card">
49
+ <h2>Quick Test</h2>
50
+ <div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap;">
51
+ <button class="btn btn-primary" onclick="App.apiFetch('/api/products/search?q=').then(d => App.showToast(d.products.length + ' products found'))">
52
+ Search Products
53
+ </button>
54
+ <button class="btn" onclick="App.showToast('Everything works!')">
55
+ Test Toast
56
+ </button>
57
+ </div>
58
+ </div>
59
+ `;
60
+ }
61
+ });
62
+ </script>
63
+ </body>
64
+ </html>
@@ -0,0 +1,98 @@
1
+ /**
2
+ * App Bridge helper — handles authentication, API calls, and navigation.
3
+ *
4
+ * Security is handled by App Bridge (session tokens) and server-side
5
+ * verification. No manual iframe checks needed.
6
+ */
7
+
8
+ (function () {
9
+ "use strict";
10
+
11
+ window.App = {
12
+ ready: false,
13
+ shop: null,
14
+ host: null,
15
+
16
+ async init() {
17
+ const params = new URLSearchParams(window.location.search);
18
+ this.host = params.get("host");
19
+ this.shop = params.get("shop");
20
+
21
+ await this._waitForShopify();
22
+
23
+ if (!window.shopify) {
24
+ document.getElementById("app").innerHTML = `
25
+ <div style="text-align:center;padding:60px 20px;color:#6d7175;">
26
+ <h2 style="color:#1a1a1a;">Loading...</h2>
27
+ <p>If this persists, reload from the Shopify admin.</p>
28
+ </div>
29
+ `;
30
+ return;
31
+ }
32
+
33
+ this.ready = true;
34
+ window.dispatchEvent(new Event("app-ready"));
35
+ },
36
+
37
+ _waitForShopify() {
38
+ return new Promise((resolve) => {
39
+ if (window.shopify) return resolve();
40
+ let attempts = 0;
41
+ const interval = setInterval(() => {
42
+ if (window.shopify || ++attempts > 50) {
43
+ clearInterval(interval);
44
+ resolve();
45
+ }
46
+ }, 100);
47
+ });
48
+ },
49
+
50
+ async getSessionToken() {
51
+ if (!window.shopify) throw new Error("App Bridge not loaded");
52
+ return await window.shopify.idToken();
53
+ },
54
+
55
+ async apiFetch(path, options = {}) {
56
+ const token = await this.getSessionToken();
57
+ const resp = await fetch(path, {
58
+ ...options,
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ Authorization: `Bearer ${token}`,
62
+ ...(options.headers || {}),
63
+ },
64
+ });
65
+ if (!resp.ok) {
66
+ const errText = await resp.text();
67
+ throw new Error(`API Error ${resp.status}: ${errText}`);
68
+ }
69
+ return resp.json();
70
+ },
71
+
72
+ navigate(page) {
73
+ const url = new URL(page, window.location.origin);
74
+ if (this.host) url.searchParams.set("host", this.host);
75
+ if (this.shop) url.searchParams.set("shop", this.shop);
76
+ window.location.href = url.pathname + url.search;
77
+ },
78
+
79
+ showToast(message) {
80
+ if (window.shopify?.toast) {
81
+ window.shopify.toast.show(message);
82
+ return;
83
+ }
84
+ const existing = document.querySelector(".toast");
85
+ if (existing) existing.remove();
86
+ const toast = document.createElement("div");
87
+ toast.className = "toast show";
88
+ toast.textContent = message;
89
+ document.body.appendChild(toast);
90
+ setTimeout(() => {
91
+ toast.classList.remove("show");
92
+ setTimeout(() => toast.remove(), 300);
93
+ }, 3000);
94
+ },
95
+ };
96
+
97
+ document.addEventListener("DOMContentLoaded", () => window.App.init());
98
+ })();