← back to Dw Signup Fulfillment
docs/theme-modal-proposal.md
279 lines
# PROPOSED theme change — `snippets/dw-signin-modal.liquid` (NOT deployed)
Ticket TK-10006. This is a **proposal only** — nothing here has been pushed to the
live theme. It documents the exact BEFORE → AFTER edits to the DW sign-in modal so
Steve can sign off before any deploy.
- **Store:** `designer-laboratory-sandbox.myshopify.com`
- **Live theme:** `144396058675` (role: main)
- **Asset:** `snippets/dw-signin-modal.liquid` (6089 bytes, `dwsm-*` classes)
- **Read via:** `GET /admin/api/2024-10/themes/144396058675/assets.json?asset[key]=snippets/dw-signin-modal.liquid` with `SHOPIFY_THEME_TOKEN` (ends `2954`).
Three faithful, minimal changes. Markup/classes and the existing JS behavior are
preserved; only the listed lines change.
---
## Why these changes
The wired retail flow (this service) does **not** hand out an instant free product
at sign-in. On `customers/create`, the fulfillment service emails the customer a
unique single-use **code** for 3 free samples (function-backed, sample-only). The
modal copy must match that reality (no "instant free" promise), the Professional
option must route to a **moderated Apply-for-Trade** step (not the same one-click
account create), and Google sign-up should be offered up front (the hosted account
portal's native Google works, and the `customers/create` webhook covers Google
signups automatically).
---
## (0) RETURNING SIGN-IN — front and center (Steve: "very important")
Old accounts must sign in easily. The store is passwordless New Customer Accounts, so
**every existing customer signs in with just their email → a 6-digit code** (no password,
even if they had one before). The modal must make this the FIRST, most obvious action —
not buried under the tier/offer pitch.
Layout order in the modal, top → bottom:
1. **Heading:** "Welcome back" / "Sign in to your account".
2. **PRIMARY sign-in block (returning customers):**
- `[ Sign In ]` button → `{{ routes.account_login_url }}` (hosted portal asks email → emails a code).
- `[ Continue with Google ]` (section (c) below).
- Caption, muted: **"Returning customer? Just enter your email — no password needed."**
3. **Divider:** "New to Designer Wallcoverings?"
4. **New-signup block (below, never blocks sign-in):** the Retail / Professional choice +
the "3 free samples" framing (sections (a)/(b)).
**REMOVE the "Forgot password" link entirely** — there are no passwords on this store, so it
is dead and actively confusing to a returning customer. (Handled in (b2) AFTER.)
Net effect: a returning client opens the modal and the very first thing they see is "Sign
in → your email, no password." One obvious path, no hunting.
---
## (a) RETAIL copy — stop implying instant free samples
The current header ("Up to 10 Free Samples" + "3 free samples") reads as an instant
entitlement. Reframe it around the emailed code.
### BEFORE (lines 38–50)
```liquid
<div class="dwsm-offer">
<h2>Up to 10 Free Samples</h2>
<p>Choose your account type, then sign in.</p>
</div>
<div class="dwsm-toggle">
<div class="dwsm-opt active" data-dwsm-type="retail" role="button" tabindex="0">
<span class="t">Retail</span><span class="s">3 free samples</span>
</div>
<div class="dwsm-opt" data-dwsm-type="professional" role="button" tabindex="0">
<span class="t">Professional</span><span class="s">up to 10 free</span>
</div>
</div>
```
### AFTER
```liquid
<div class="dwsm-offer">
<h2>Free Samples, On Us</h2>
<p>Sign in — we'll email you a code for 3 free samples.</p>
</div>
<div class="dwsm-toggle">
<div class="dwsm-opt active" data-dwsm-type="retail" role="button" tabindex="0">
<span class="t">Retail</span><span class="s">code for 3 free</span>
</div>
<div class="dwsm-opt" data-dwsm-type="professional" role="button" tabindex="0">
<span class="t">Professional</span><span class="s">apply for trade</span>
</div>
</div>
```
Notes: only the text inside `<h2>`, the `<p>`, and the two `.s` spans changes. No
class, structure, or JS touched. "Sign in — we'll email you a code for 3 free
samples" is verbatim per the brief.
---
## (b) PROFESSIONAL chip — route to a moderated "Apply for Trade", not the same create-account
Today, both Retail and Professional lead to the same passwordless
`routes.account_login_url` (the Professional path just sets a `dw_trade`
sessionStorage flag). Trade should be **moderated**: the Professional selection
should present an **Apply for Trade** action that submits to the fulfillment
service's `POST /trade/apply` endpoint (queued `pending`, reviewed at
`/admin/trade`, approved → `trade` tag + rep assignment).
Two coordinated edits.
### (b1) Add the Apply-for-Trade panel (shown only when Professional is selected)
Insert this block **after** the toggle (`</div>` closing `.dwsm-toggle`, line 50)
and **before** the Google button / divider. It is hidden by default and revealed by
the JS in (b3)/(c). It posts to the trade endpoint via `fetch`; on success it shows
a confirmation. `TRADE_APPLY_URL` is the public origin of this fulfillment service
(e.g. `https://signup.designerwallcoverings.com`) — set it once in the snippet.
```liquid
{%- comment -%} Moderated trade application — shown when "Professional" is selected. {%- endcomment -%}
<form class="dwsm-trade" data-dwsm-trade style="display:none;" novalidate>
<div class="dwsm-field">
<label>Business name</label>
<input type="text" name="business_name" autocomplete="organization" required>
</div>
<div class="dwsm-field">
<label>Work email</label>
<input type="email" name="email" autocomplete="email" required>
</div>
<div class="dwsm-field">
<label>Resale / business license #</label>
<input type="text" name="resale_cert">
</div>
<input type="button" class="dwsm-submit" data-dwsm-trade-submit value="Apply for Trade">
<p class="dwsm-trade-note" style="font-size:.78rem;color:#787878;margin:12px 0 0;text-align:center;">
Trade applications are reviewed by our team. We'll email you once approved.
</p>
</form>
```
Add one JS constant near the top of the existing `<script>` (right after
`if(!overlay) return;`, line 66) so the form knows where to POST:
```javascript
var TRADE_APPLY_URL = 'https://signup.designerwallcoverings.com/trade/apply'; // <-- set to this service's public URL
```
### (b2) Hide the sign-in submit + create-account footer while Professional is active
The existing Sign-In button (line 54) and footer (lines 56–59) stay for Retail; wrap
them so the JS can hide them when Professional is chosen.
BEFORE (lines 54–59):
```liquid
<input type="button" class="dwsm-submit" data-dwsm-login="{{ routes.account_login_url }}" value="Sign In / Create Account">
<div class="dwsm-foot">
New to Designer Wallcoverings? <a href="{{ routes.account_register_url }}">Create account</a>
· <a href="{{ routes.account_login_url }}#recover">Forgot password</a>
</div>
```
AFTER:
```liquid
<div data-dwsm-retail>
<input type="button" class="dwsm-submit" data-dwsm-login="{{ routes.account_login_url }}" value="Sign In / Create Account">
<div class="dwsm-foot">
New to Designer Wallcoverings? <a href="{{ routes.account_register_url }}">Create account</a>
</div>
</div>
```
> The `#recover` "Forgot password" link is **removed** (per section (0)) — the store is
> passwordless, so it leads nowhere useful and confuses returning customers.
### (b3) Toggle the two panels in the existing `setType()` + wire the trade submit
The snippet already has `setType(type)` (lines 83–86). Extend it to reveal the trade
form for Professional and hide the retail sign-in (and vice-versa), and add a submit
handler. BEFORE:
```javascript
function setType(type){
opts.forEach(function(o){o.classList.toggle('active',o.getAttribute('data-dwsm-type')===type);});
try{type==='professional'?sessionStorage.setItem('dw_trade','1'):sessionStorage.removeItem('dw_trade');}catch(e){}
}
```
AFTER:
```javascript
var retailBox=overlay.querySelector('[data-dwsm-retail]');
var googleBox=overlay.querySelector('[data-dwsm-google]'); // added in (c)
var tradeForm=overlay.querySelector('[data-dwsm-trade]');
function setType(type){
opts.forEach(function(o){o.classList.toggle('active',o.getAttribute('data-dwsm-type')===type);});
var pro=(type==='professional');
if(retailBox) retailBox.style.display=pro?'none':'';
if(googleBox) googleBox.style.display=pro?'none':'';
if(tradeForm) tradeForm.style.display=pro?'block':'none';
try{pro?sessionStorage.setItem('dw_trade','1'):sessionStorage.removeItem('dw_trade');}catch(e){}
}
// Moderated trade application submit -> POST /trade/apply on the fulfillment service.
overlay.addEventListener('click',function(e){
if(!e.target.closest('[data-dwsm-trade-submit]')) return;
e.preventDefault();
var f=tradeForm; if(!f) return;
var body={
business_name:(f.business_name.value||'').trim(),
email:(f.email.value||'').trim(),
resale_cert:(f.resale_cert.value||'').trim()
};
if(!body.email||!body.business_name){alert('Business name and work email are required.');return;}
fetch(TRADE_APPLY_URL,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})
.then(function(r){return r.json();})
.then(function(){f.innerHTML='<p style="text-align:center;color:#444;padding:8px 0;">Thanks — your trade application is in review. We\'ll email you once it\'s approved.</p>';})
.catch(function(){alert('Sorry, something went wrong. Please try again.');});
});
```
No existing behavior is removed — the retail Sign-In button and the `dw_trade`
persistence still work exactly as before when Retail is selected.
---
## (c) ADD "Continue with Google" — offered up front, deep-linked to the hosted portal
The hosted account portal's native Google sign-in works, and the
`customers/create` webhook covers Google signups automatically. Offer it up front
in the modal. It deep-links to `routes.account_login_url` (the hosted portal, which
shows the Google button). Reuse the existing `.dwsm-gbtn` / `.dwsm-div` styles that
already ship in this snippet's `<style>` (lines 17–21) — no new CSS needed.
Insert **between** the toggle (line 50) and the Sign-In submit (line 54), i.e. just
before the `data-dwsm-retail` wrapper from (b2):
```liquid
{%- comment -%} Google sign-up offered up front; deep-links to the hosted portal's native Google. {%- endcomment -%}
<div data-dwsm-google>
<button type="button" class="dwsm-gbtn" data-dwsm-login="{{ routes.account_login_url }}">
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"/><path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.81.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18z"/><path fill="#FBBC05" d="M3.97 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.01-2.33z"/><path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.47.9 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"/></svg>
Continue with Google
</button>
<div class="dwsm-div"><i></i><span>or</span><i></i></div>
</div>
```
The Google button reuses the existing `[data-dwsm-login]` click handler (lines
77–78), so it persists the `dw_trade` flag correctly and navigates to the hosted
portal — no new JS wiring is required for the button itself (only the `googleBox`
show/hide reference already added in (b3), so it hides when Professional is active).
---
## Summary of edits
| # | Area | Lines touched | Nature |
|---|------|---------------|--------|
| a | Retail copy | 38–50 | text only (header, subhead, 2 chip subtitles) |
| b1 | Trade form | insert after 50 | new hidden `<form data-dwsm-trade>` + one JS const |
| b2 | Retail wrapper | 54–59 | wrap existing submit+footer in `<div data-dwsm-retail>` |
| b3 | setType + submit | 83–86 (+ handler) | show/hide panels; POST to `/trade/apply` |
| c | Google button | insert 50→54 | new `.dwsm-gbtn` reusing existing styles/handler |
Nothing else in the snippet changes. Classes remain `dwsm-*`; the overlay
open/close, Escape handling, and hash-open (`#dw-signin`) behavior are untouched.
### Before deploy (Steve's call)
1. Set `TRADE_APPLY_URL` to this service's real public origin.
2. Back up the current asset (GET then save) before any PUT.
3. Confirm the hosted account portal shows Google (it does per the webhook design).
4. Deploy via the Asset API PUT to theme `144396058675` (or theme editor), then
verify with a `?preview_theme_id` render.