@mevdragon/vidfarm-devcli 0.17.0 → 0.18.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.
@@ -1,23 +1,115 @@
1
- // /reskin/login — the auth screen ported into the farmville design system.
1
+ // /reskin/login — the auth screen ported into the farmville design system,
2
+ // now MIGRATED to the REAL production auth (no more fake password-only form).
2
3
  //
3
4
  // Bare chrome (no shared top nav / footer): an auth screen owns its whole
4
5
  // canvas, so we render just the body over a warm sunny background. A two-column
5
6
  // composition — a sunlit brand/marketing panel on the left, a floating white
6
7
  // auth card on the right — collapses to a single centered card on small screens.
7
8
  //
8
- // Faithfully mirrors src/account-pages-legacy.ts renderLoginPage. The legacy
9
- // page is OTP-first with a password alternative; the design brief here favors a
10
- // classic email + password form with a (visual) Log in / Sign up pill toggle, so
11
- // the primary form posts to the REAL password endpoint /login/password (mode=
12
- // password) and the ghost secondary button links to the REAL passwordless OTP
13
- // flow at /login?mode=otp — both endpoints are honest.
14
- import { reskinDocument } from "./document.js";
15
- // Real login endpoints from the live app (src/app.ts).
16
- const PASSWORD_ACTION = "/login/password?mode=password";
17
- const OTP_HREF = "/login?mode=otp";
9
+ // This accepts the SAME input object the live /login route already builds (via
10
+ // `Parameters<typeof renderLoginPage>[0]`), so the route wiring is just "call
11
+ // this instead of the legacy renderer". Every form posts to the REAL login
12
+ // endpoints with the exact field names the live login uses:
13
+ // password: POST ${routePrefix}/password?mode=password {email,password}
14
+ // OTP request: POST ${routePrefix}/otp/request?mode=otp {email}
15
+ // • OTP verify: POST ${routePrefix}/otp/verify?mode=otp {email(hidden),code,name?}
16
+ // routePrefix defaults to "/login" but is honored from the input (e.g. the
17
+ // agency-client login uses "/agency-client-login"). On success the real
18
+ // endpoints own the redirect (post-login return path) — we never fake it.
19
+ //
20
+ // The segmented control at the top switches between the two REAL modes
21
+ // (Email code = otp, Password) via honest ?mode= links, so it mirrors the
22
+ // live login's mode handling exactly. Design stays farmville; NO emoji.
23
+ import { reskinDocument, rkEscape } from "./document.js";
18
24
  const EYE = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`;
19
25
  const MAIL = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="3"/><path d="m3.5 7 8.5 6 8.5-6"/></svg>`;
20
- export function renderReskinLogin() {
26
+ const LOCK = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="10" width="16" height="10" rx="2.5"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>`;
27
+ export function renderReskinLogin(input = {}) {
28
+ // Mirror the live renderer's normalization exactly.
29
+ const mode = input.mode === "password" ? "password" : "otp";
30
+ const routePrefix = input.routePrefix || "/login";
31
+ const rp = rkEscape(routePrefix);
32
+ const email = rkEscape(input.email ?? "");
33
+ const otpSent = Boolean(input.otpSent);
34
+ // REAL endpoints (same as src/account-pages-legacy.ts → renderLoginPage).
35
+ const PASSWORD_ACTION = `${rp}/password?mode=password`;
36
+ const OTP_REQUEST_ACTION = `${rp}/otp/request?mode=otp`;
37
+ const OTP_VERIFY_ACTION = `${rp}/otp/verify?mode=otp`;
38
+ const OTP_HREF = `${rp}?mode=otp`;
39
+ const PASSWORD_HREF = `${rp}?mode=password`;
40
+ const message = input.message
41
+ ? `<div class="rk-notice rk-notice-ok rk-login-flash">${rkEscape(input.message)}</div>`
42
+ : "";
43
+ const error = input.error
44
+ ? `<div class="rk-notice rk-notice-err rk-login-flash">${rkEscape(input.error)}</div>`
45
+ : "";
46
+ // ── segmented mode control (real ?mode= links) ──────────────────────────
47
+ // Hidden while verifying a code (mid-flow) — there we show "Change email".
48
+ const seg = otpSent
49
+ ? ""
50
+ : `<div class="rk-login-seg" role="tablist" aria-label="Choose a sign-in method">
51
+ <span class="rk-login-seg-thumb${mode === "password" ? " is-right" : ""}"></span>
52
+ <a class="rk-login-seg-btn${mode === "otp" ? " is-active" : ""}" href="${OTP_HREF}" role="tab" aria-selected="${mode === "otp" ? "true" : "false"}">Email code</a>
53
+ <a class="rk-login-seg-btn${mode === "password" ? " is-active" : ""}" href="${PASSWORD_HREF}" role="tab" aria-selected="${mode === "password" ? "true" : "false"}">Password</a>
54
+ </div>`;
55
+ // ── header copy per state ───────────────────────────────────────────────
56
+ const head = mode === "password"
57
+ ? { title: "Welcome back", sub: "Log in with your email and password." }
58
+ : otpSent
59
+ ? { title: "Check your email", sub: `Enter the 6-digit code we sent${input.email ? ` to ${email}` : ""}.` }
60
+ : { title: "Sign in with a code", sub: "We'll email you a 6-digit code — no password to remember." };
61
+ // ── the auth form (three honest states) ─────────────────────────────────
62
+ let authPane;
63
+ if (mode === "password") {
64
+ authPane = `
65
+ <form class="rk-stack rk-login-form" method="post" action="${PASSWORD_ACTION}">
66
+ <div class="rk-field">
67
+ <label class="rk-label" for="rk-login-email">Email</label>
68
+ <input class="rk-input" id="rk-login-email" type="email" name="email" value="${email}" placeholder="you@studio.co" autocomplete="email" required autofocus />
69
+ </div>
70
+ <div class="rk-field">
71
+ <label class="rk-label" for="rk-login-pass">Password</label>
72
+ <div class="rk-input-group">
73
+ <input class="rk-input" id="rk-login-pass" type="password" name="password" placeholder="••••••••" autocomplete="current-password" required />
74
+ <button class="rk-btn rk-btn-ghost rk-btn-sm rk-login-eye" type="button" data-rk-reveal="rk-login-pass" aria-label="Show password">${EYE}</button>
75
+ </div>
76
+ </div>
77
+ <button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">Log in <span class="rk-arrow">→</span></button>
78
+ </form>
79
+
80
+ <div class="rk-login-divider"><span>or</span></div>
81
+ <a class="rk-btn rk-btn-ghost rk-btn-lg rk-btn-block rk-login-alt" href="${OTP_HREF}">${MAIL} Email me a login code</a>`;
82
+ }
83
+ else if (otpSent) {
84
+ authPane = `
85
+ <form class="rk-stack rk-login-form" method="post" action="${OTP_VERIFY_ACTION}">
86
+ <input type="hidden" name="email" value="${email}" />
87
+ <div class="rk-field">
88
+ <label class="rk-label" for="rk-login-code">6-digit code</label>
89
+ <input class="rk-input rk-login-code" id="rk-login-code" name="code" inputmode="numeric" autocomplete="one-time-code" maxlength="6" pattern="[0-9]*" placeholder="000000" autofocus required />
90
+ </div>
91
+ <div class="rk-field">
92
+ <label class="rk-label" for="rk-login-name">Your name <span class="rk-login-optional">(optional — for new accounts)</span></label>
93
+ <input class="rk-input" id="rk-login-name" name="name" type="text" placeholder="Dylan Vane" autocomplete="name" />
94
+ </div>
95
+ <button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">Verify &amp; continue <span class="rk-arrow">→</span></button>
96
+ </form>
97
+
98
+ <p class="rk-login-foot"><a href="${OTP_HREF}">Use a different email</a></p>`;
99
+ }
100
+ else {
101
+ authPane = `
102
+ <form class="rk-stack rk-login-form" method="post" action="${OTP_REQUEST_ACTION}">
103
+ <div class="rk-field">
104
+ <label class="rk-label" for="rk-login-email">Email</label>
105
+ <input class="rk-input" id="rk-login-email" type="email" name="email" value="${email}" placeholder="you@studio.co" autocomplete="email" required autofocus />
106
+ </div>
107
+ <button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit">${MAIL} Email me a login code</button>
108
+ </form>
109
+
110
+ <div class="rk-login-divider"><span>or</span></div>
111
+ <a class="rk-btn rk-btn-ghost rk-btn-lg rk-btn-block rk-login-alt" href="${PASSWORD_HREF}">${LOCK} Use a password instead</a>`;
112
+ }
21
113
  const body = `
22
114
  <main class="rk-login">
23
115
  <div class="rk-login-art">
@@ -29,7 +121,6 @@ export function renderReskinLogin() {
29
121
  <span class="rk-brand-mark">V</span>vidfarm
30
122
  </a>
31
123
  <div class="rk-login-art-copy">
32
- <span class="rk-badge rk-login-eyebrow">Welcome back to the farm</span>
33
124
  <h1 class="rk-login-hed">A week of content, made in an afternoon.</h1>
34
125
  <p class="rk-login-lede">Sign in to spin up short-form ads from your own clips — punchy hooks, UGC energy, captions that pop.</p>
35
126
  <ul class="rk-login-feats">
@@ -55,41 +146,14 @@ export function renderReskinLogin() {
55
146
  </a>
56
147
 
57
148
  <div class="rk-login-head">
58
- <h2 class="rk-login-title" data-rk-title>Welcome back</h2>
59
- <p class="rk-muted" data-rk-subtitle>Log in to pick up where you left off.</p>
60
- </div>
61
-
62
- <div class="rk-login-seg" role="tablist" aria-label="Log in or sign up">
63
- <span class="rk-login-seg-thumb" data-rk-thumb></span>
64
- <button type="button" class="rk-login-seg-btn is-active" data-rk-seg="login" aria-selected="true">Log in</button>
65
- <button type="button" class="rk-login-seg-btn" data-rk-seg="signup" aria-selected="false">Sign up</button>
149
+ <h2 class="rk-login-title">${rkEscape(head.title)}</h2>
150
+ <p class="rk-muted">${head.sub}</p>
66
151
  </div>
67
152
 
68
- <form class="rk-stack rk-login-form" method="post" action="${PASSWORD_ACTION}">
69
- <div class="rk-field">
70
- <label class="rk-label" for="rk-login-email">Email</label>
71
- <input class="rk-input" id="rk-login-email" type="email" name="email" placeholder="you@studio.co" autocomplete="email" required />
72
- </div>
73
- <div class="rk-field">
74
- <div class="rk-login-label-row">
75
- <label class="rk-label" for="rk-login-pass">Password</label>
76
- <a class="rk-login-forgot" href="${OTP_HREF}" data-rk-forgot>Forgot password?</a>
77
- </div>
78
- <div class="rk-input-group">
79
- <input class="rk-input" id="rk-login-pass" type="password" name="password" placeholder="••••••••" autocomplete="current-password" required />
80
- <button class="rk-btn rk-btn-ghost rk-btn-sm rk-login-eye" type="button" data-rk-reveal="rk-login-pass" aria-label="Show password">${EYE}</button>
81
- </div>
82
- </div>
83
- <button class="rk-btn rk-btn-gold rk-btn-lg rk-btn-block rk-login-submit" type="submit" data-rk-submit>Log in <span class="rk-arrow">→</span></button>
84
- </form>
85
-
86
- <div class="rk-login-divider"><span>or</span></div>
87
-
88
- <a class="rk-btn rk-btn-ghost rk-btn-lg rk-btn-block rk-login-alt" href="${OTP_HREF}">${MAIL} Email me a login code</a>
89
-
90
- <p class="rk-login-foot" data-rk-foot>
91
- New to vidfarm? <a href="#" data-rk-foot-link>Start a free trial</a>
92
- </p>
153
+ ${seg}
154
+ ${message}
155
+ ${error}
156
+ ${authPane}
93
157
  </div>
94
158
  <p class="rk-login-legal">By continuing you agree to our <a href="#">Terms</a> &amp; <a href="#">Privacy Policy</a>.</p>
95
159
  </div>
@@ -142,9 +206,10 @@ export function renderReskinLogin() {
142
206
  @media(max-width:920px){.rk-login-card-brand{display:inline-flex;justify-self:center}}
143
207
  .rk-login-head{display:grid;gap:6px;text-align:center}
144
208
  .rk-login-title{font-size:var(--rk-text-3xl);letter-spacing:-.03em}
209
+ .rk-login-flash{font-size:13px;padding:11px 14px}
145
210
 
146
- /* segmented pill toggle (visual) — gold is reserved for submit, so the active
147
- thumb is a plain white pill with an ink label */
211
+ /* segmented mode controlreal ?mode= links. gold is reserved for submit, so
212
+ the active thumb is a plain white pill with an ink label */
148
213
  .rk-login-seg{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:2px;padding:4px;
149
214
  background:var(--rk-n-100);border:1px solid var(--rk-border);border-radius:var(--rk-r-full)}
150
215
  .rk-login-seg-thumb{position:absolute;z-index:0;top:4px;left:4px;width:calc(50% - 4px);height:calc(100% - 8px);
@@ -153,15 +218,15 @@ export function renderReskinLogin() {
153
218
  .rk-login-seg-thumb.is-right{transform:translateX(100%)}
154
219
  .rk-login-seg-btn{position:relative;z-index:1;appearance:none;border:0;background:transparent;cursor:pointer;
155
220
  padding:9px 12px;border-radius:var(--rk-r-full);font-family:var(--rk-font-body);font-size:14px;font-weight:600;
156
- color:var(--rk-n-500);transition:color var(--rk-dur) var(--rk-ease)}
221
+ text-align:center;color:var(--rk-n-500);transition:color var(--rk-dur) var(--rk-ease)}
157
222
  .rk-login-seg-btn.is-active{color:var(--rk-ink)}
158
223
 
159
224
  .rk-login-form{gap:16px}
160
- .rk-login-label-row{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
161
- .rk-login-forgot{font-size:12px;font-weight:600;color:var(--rk-gold-700)}
162
- .rk-login-forgot:hover{text-decoration:underline}
163
225
  .rk-login-eye{padding:0 12px;color:var(--rk-n-500)}
164
226
  .rk-login-submit{margin-top:4px}
227
+ .rk-login-code{font-family:var(--rk-font-mono);font-size:20px;letter-spacing:.42em;text-align:center;padding-right:0}
228
+ .rk-login-code::placeholder{letter-spacing:.42em}
229
+ .rk-login-optional{font-weight:500;color:var(--rk-text-faint)}
165
230
 
166
231
  .rk-login-divider{display:flex;align-items:center;gap:14px;color:var(--rk-text-faint);
167
232
  font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.08em}
@@ -176,35 +241,7 @@ export function renderReskinLogin() {
176
241
  const script = `
177
242
  (function(){
178
243
  var root=document;
179
- // segmented Log in / Sign up toggle — visual only; both keep the real password
180
- // form, we just relabel copy so the two states read honestly.
181
- var thumb=root.querySelector('[data-rk-thumb]');
182
- var title=root.querySelector('[data-rk-title]');
183
- var subtitle=root.querySelector('[data-rk-subtitle]');
184
- var submit=root.querySelector('[data-rk-submit]');
185
- var foot=root.querySelector('[data-rk-foot]');
186
- var forgot=root.querySelector('[data-rk-forgot]');
187
- var arrow='<span class="rk-arrow">\\u2192</span>';
188
- var copy={
189
- login:{title:'Welcome back',sub:'Log in to pick up where you left off.',btn:'Log in '+arrow,foot:'New to vidfarm? <a href="#" data-rk-foot-link>Start a free trial</a>',forgot:true},
190
- signup:{title:'Create your studio',sub:'Start making short-form ads in minutes.',btn:'Create account '+arrow,foot:'Already have an account? <a href="#" data-rk-foot-link>Log in</a>',forgot:false}
191
- };
192
- root.querySelectorAll('[data-rk-seg]').forEach(function(btn){
193
- btn.addEventListener('click',function(){
194
- var mode=btn.getAttribute('data-rk-seg');
195
- root.querySelectorAll('[data-rk-seg]').forEach(function(b){
196
- var on=b===btn; b.classList.toggle('is-active',on); b.setAttribute('aria-selected',on?'true':'false');
197
- });
198
- if(thumb)thumb.classList.toggle('is-right',mode==='signup');
199
- var c=copy[mode];
200
- if(title)title.textContent=c.title;
201
- if(subtitle)subtitle.textContent=c.sub;
202
- if(submit)submit.innerHTML=c.btn;
203
- if(foot)foot.innerHTML=c.foot;
204
- if(forgot)forgot.style.visibility=c.forgot?'visible':'hidden';
205
- });
206
- });
207
- // password reveal eye
244
+ // password reveal eye (only present in password mode)
208
245
  root.querySelectorAll('[data-rk-reveal]').forEach(function(btn){
209
246
  btn.addEventListener('click',function(){
210
247
  var el=root.getElementById(btn.getAttribute('data-rk-reveal'));