← back to Dw Signup Fulfillment
theme-proposals/loggedin-trade-entry/deploy.py
157 lines
#!/usr/bin/env python3
"""
TK-10021 — Logged-in designers can now reach Apply-for-Trade (auto-render trade-only view).
Live theme: 144396058675 (main) on designer-laboratory-sandbox.myshopify.com
Token: SHOPIFY_THEME_TOKEN (ends 2954) from ~/Projects/secrets-manager/.env
Three anchored edits:
1. layout/theme.liquid:1360 gate {% unless customer %} -> {% unless customer.tags contains 'trade' %}
2. snippets/dw-signin-modal.liquid wrap sign-in/retail/toggle/google in {% unless customer %};
add logged-in trade header; default-show + prefill the trade form.
3. templates/customers/account.liquid add an "Apply for Trade Pricing" trigger for non-trade customers.
DRY-RUN by default: pulls each live asset, applies edits in-memory, prints a unified diff, writes a
timestamped local backup. Nothing is pushed. Pass --apply to PUT the patched assets back (Steve-gated).
Every anchor is asserted to appear EXACTLY ONCE before patching; any drift aborts the whole run so a
theme change under our feet can never produce a half-applied edit.
"""
import os, sys, json, difflib, urllib.request, urllib.parse, datetime, pathlib
STORE = "designer-laboratory-sandbox.myshopify.com"
THEME = "144396058675"
API = "2024-10"
HERE = pathlib.Path(__file__).resolve().parent
BKP = HERE / "backups"
APPLY = "--apply" in sys.argv
def token():
env = pathlib.Path.home() / "Projects/secrets-manager/.env"
for line in env.read_text().splitlines():
if line.startswith("SHOPIFY_THEME_TOKEN="):
return line.split("=", 1)[1].strip().strip('"').strip("'")
sys.exit("SHOPIFY_THEME_TOKEN not found")
TOK = token()
def _req(method, key, value=None):
url = f"https://{STORE}/admin/api/{API}/themes/{THEME}/assets.json"
if method == "GET":
url += "?asset[key]=" + urllib.parse.quote(key)
data = None
else:
data = json.dumps({"asset": {"key": key, "value": value}}).encode()
r = urllib.request.Request(url, data=data, method=method,
headers={"X-Shopify-Access-Token": TOK,
"Content-Type": "application/json"})
with urllib.request.urlopen(r) as resp:
return json.load(resp)
def get_asset(key):
return _req("GET", key)["asset"]["value"]
def put_asset(key, value):
_req("PUT", key, value) # Shopify validates Liquid on PUT; 200 == valid
def once(hay, needle, key):
n = hay.count(needle)
if n != 1:
sys.exit(f"ABORT [{key}]: anchor appears {n}x (expected 1). Live theme drifted:\n {needle[:80]!r}")
# ---- edit 1: theme.liquid gate --------------------------------------------
def patch_theme(v):
a = "{% unless customer %}{% render 'dw-signin-modal' %}{% endunless %}"
b = "{% unless customer.tags contains 'trade' %}{% render 'dw-signin-modal' %}{% endunless %}"
once(v, a, "theme.liquid"); return v.replace(a, b)
# ---- edit 2: dw-signin-modal.liquid ---------------------------------------
def patch_modal(v):
# (2a) open the logged-out-only run right before the returning-sign-in block
a1 = ' <div class="dwsm-return">'
once(v, a1, "modal/return-open")
v = v.replace(a1, ' {%- unless customer -%}\n' + a1, 1)
# (2b) close that run right after the Google block, before the trade form comment,
# then add the logged-in trade header.
a2 = " {%- comment -%} Moderated trade application"
once(v, a2, "modal/trade-comment")
header = (
' {%- endunless -%}\n'
' {%- if customer -%}\n'
' <div class="dwsm-return">\n'
' <h2>Apply for Trade Pricing</h2>\n'
' <p class="dwsm-return-cap">You\'re signed in — submit your business details and our team will review your trade account.</p>\n'
' </div>\n'
' {%- endif -%}\n'
)
v = v.replace(a2, header + a2, 1)
# (2c) trade form: default-show for logged-in, prefill business name + email
a3 = '<form class="dwsm-trade" data-dwsm-trade style="display:none;" novalidate>'
once(v, a3, "modal/form")
v = v.replace(a3, '<form class="dwsm-trade" data-dwsm-trade style="display:{% if customer %}block{% else %}none{% endif %};" novalidate>')
a4 = '<input type="text" name="business_name" autocomplete="organization" required>'
once(v, a4, "modal/bizname")
v = v.replace(a4, '<input type="text" name="business_name" autocomplete="organization" value="{{ customer.default_address.company }}" required>')
a5 = '<input type="email" name="email" autocomplete="email" required>'
once(v, a5, "modal/email")
v = v.replace(a5, '<input type="email" name="email" autocomplete="email" value="{{ customer.email }}" required>')
# (2d) wrap the retail sign-in/create block as logged-out-only
a6 = (' <div data-dwsm-retail>\n'
' <input type="button" class="dwsm-submit" data-dwsm-login="{{ routes.account_login_url }}" value="Sign In / Create Account">\n\n'
' <div class="dwsm-foot">\n'
' New to Designer Wallcoverings? <a href="{{ routes.account_register_url }}">Create account</a>\n'
' </div>\n'
' </div>')
once(v, a6, "modal/retail-block")
v = v.replace(a6, ' {%- unless customer -%}\n' + a6 + '\n {%- endunless -%}')
return v
# ---- edit 3: account.liquid trigger ---------------------------------------
def patch_account(v):
a = " {% render 'breadcrumbs' %}"
once(v, a, "account/breadcrumbs")
cta = (a + '\n\n'
" {%- unless customer.tags contains 'trade' -%}\n"
' <p class="dw-trade-cta" style="margin:16px 0;">\n'
' <a href="#dw-signin" data-dw-signin style="color:#b08212;font-weight:600;text-decoration:none;">Apply for Trade Pricing →</a>\n'
' </p>\n'
' {%- endunless -%}')
return v.replace(a, cta, 1)
EDITS = [
("layout/theme.liquid", patch_theme),
("snippets/dw-signin-modal.liquid", patch_modal),
("templates/customers/account.liquid", patch_account),
]
def main():
stamp = datetime.datetime.now().strftime("%Y%m%dT%H%M%S")
BKP.mkdir(parents=True, exist_ok=True)
changed = []
for key, fn in EDITS:
live = get_asset(key)
(BKP / (key.replace("/", "__") + f".{stamp}.bak")).write_text(live)
new = fn(live)
if new == live:
print(f"[= ] {key}: no change (already applied?)"); continue
diff = difflib.unified_diff(live.splitlines(), new.splitlines(),
fromfile=f"LIVE/{key}", tofile=f"NEW/{key}", lineterm="")
print("\n".join(diff)); print()
changed.append((key, new))
if not changed:
print("Nothing to change."); return
if not APPLY:
print(f"\nDRY-RUN. Backups in {BKP}. Re-run with --apply to PUT {len(changed)} asset(s) to the LIVE theme.")
return
for key, new in changed:
put_asset(key, new); print(f"[PUT] {key} (200 = Liquid valid)")
print("\nDeployed. Verify: open the store signed in as a NON-trade customer -> account page shows 'Apply for Trade Pricing'.")
if __name__ == "__main__":
main()