fix ui auth forms and stabilize baseline tests
This commit is contained in:
@@ -220,27 +220,50 @@ function renderLoginPage({ returnTo = "/app", error = null }) {
|
|||||||
<div class="card bg-base-100 border border-base-content/10 shadow-sm">
|
<div class="card bg-base-100 border border-base-content/10 shadow-sm">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
${errorBlock}
|
${errorBlock}
|
||||||
<form method="POST" action="/auth/dev-login" class="space-y-4">
|
<form method="POST" action="/auth/x" class="mb-6">
|
||||||
<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />
|
<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />
|
||||||
|
<button class="btn btn-outline w-full">Continue with X</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="divider text-xs text-base-content/40 my-6">Email</div>
|
||||||
<label class="label">
|
|
||||||
<span class="label-text font-medium">Username</span>
|
|
||||||
</label>
|
|
||||||
<input name="userId" required minlength="2" maxlength="40" class="input input-bordered w-full" placeholder="Enter your username" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<form method="POST" action="/auth/email/sign-in" class="space-y-3">
|
||||||
|
<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text font-medium mb-1">Email</span>
|
||||||
|
<input name="email" type="email" required class="input input-bordered w-full" placeholder="you@domain.com" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text font-medium mb-1">Password</span>
|
||||||
|
<input name="password" type="password" required minlength="8" maxlength="128" class="input input-bordered w-full" placeholder="••••••••" />
|
||||||
|
</label>
|
||||||
<button class="btn btn-primary w-full shadow-sm hover:shadow">Sign in</button>
|
<button class="btn btn-primary w-full shadow-sm hover:shadow">Sign in</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="divider text-xs text-base-content/40 my-6">OR</div>
|
<div class="divider text-xs text-base-content/40 my-6">Create account</div>
|
||||||
|
|
||||||
<div class="text-center text-sm">
|
<form method="POST" action="/auth/email/sign-up" class="space-y-3">
|
||||||
<p class="text-base-content/60">Don't have an account? <a href="#" class="link link-primary font-medium hover:underline">Contact us</a></p>
|
<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}" />
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text font-medium mb-1">Name</span>
|
||||||
|
<input name="name" required minlength="2" maxlength="80" class="input input-bordered w-full" placeholder="Matiss" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text font-medium mb-1">Email</span>
|
||||||
|
<input name="email" type="email" required class="input input-bordered w-full" placeholder="you@domain.com" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text font-medium mb-1">Password</span>
|
||||||
|
<input name="password" type="password" required minlength="8" maxlength="128" class="input input-bordered w-full" placeholder="••••••••" />
|
||||||
|
</label>
|
||||||
|
<button class="btn btn-ghost border border-base-content/20 w-full">Create account</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="text-center text-xs text-base-content/50 mt-2">
|
||||||
|
X sign-in requires X_OAUTH_CLIENT_ID and X_OAUTH_CLIENT_SECRET.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center text-xs text-base-content/40 mt-6 font-mono">Dev mode: Enter any username to create a session.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ test("GET / renders landing page", async () => {
|
|||||||
const app = createApp();
|
const app = createApp();
|
||||||
const response = await call(app, { method: "GET", path: "/" });
|
const response = await call(app, { method: "GET", path: "/" });
|
||||||
assert.equal(response.status, 200);
|
assert.equal(response.status, 200);
|
||||||
assert.match(response.body, /From X Article to audiobook/);
|
assert.match(response.body, /Listen to X Articles/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("GET /assets/styles.css serves compiled stylesheet", async () => {
|
test("GET /assets/styles.css serves compiled stylesheet", async () => {
|
||||||
@@ -229,7 +229,7 @@ test("authenticated dashboard topup + simulate mention flow", async () => {
|
|||||||
headers: { cookie: cookieHeader },
|
headers: { cookie: cookieHeader },
|
||||||
});
|
});
|
||||||
assert.equal(dashboard.status, 200);
|
assert.equal(dashboard.status, 200);
|
||||||
assert.match(dashboard.body, /Recent audiobooks/);
|
assert.match(dashboard.body, /Recent Audiobooks/);
|
||||||
assert.match(dashboard.body, /Hello/);
|
assert.match(dashboard.body, /Hello/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ test("audio flow requires auth for unlock and supports permanent unlock", async
|
|||||||
path: audioPath,
|
path: audioPath,
|
||||||
headers: { cookie: "xartaudio_user=viewer" },
|
headers: { cookie: "xartaudio_user=viewer" },
|
||||||
});
|
});
|
||||||
assert.match(beforeUnlock.body, /Unlock required: 1 credits/);
|
assert.match(beforeUnlock.body, /Unlock this audiobook permanently for/);
|
||||||
|
|
||||||
const unlock = await call(app, {
|
const unlock = await call(app, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -278,7 +278,7 @@ test("audio flow requires auth for unlock and supports permanent unlock", async
|
|||||||
path: audioPath,
|
path: audioPath,
|
||||||
headers: { cookie: "xartaudio_user=viewer" },
|
headers: { cookie: "xartaudio_user=viewer" },
|
||||||
});
|
});
|
||||||
assert.match(afterUnlock.body, /Access granted/);
|
assert.match(afterUnlock.body, /Unlocked!/);
|
||||||
|
|
||||||
const wallet = await call(app, {
|
const wallet = await call(app, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ test("shell includes daisyui and pwa tags", () => {
|
|||||||
|
|
||||||
test("landing page renders hero and flow sections", () => {
|
test("landing page renders hero and flow sections", () => {
|
||||||
const html = renderLandingPage({ authenticated: false, userId: null });
|
const html = renderLandingPage({ authenticated: false, userId: null });
|
||||||
assert.match(html, /From X Article to audiobook/);
|
assert.match(html, /Listen to X Articles/);
|
||||||
assert.match(html, /id="how"/);
|
assert.match(html, /id="how"/);
|
||||||
assert.match(html, /id="pricing"/);
|
assert.match(html, /id="pricing"/);
|
||||||
});
|
});
|
||||||
@@ -40,8 +40,8 @@ test("app page renders stats and forms", () => {
|
|||||||
jobs: [{ assetId: "1", status: "completed", article: { title: "Hello" }, creditsCharged: 1 }],
|
jobs: [{ assetId: "1", status: "completed", article: { title: "Hello" }, creditsCharged: 1 }],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.match(html, /Top up credits/);
|
assert.match(html, /Top Up Credits/);
|
||||||
assert.match(html, /Simulate mention/);
|
assert.match(html, /Simulate Mention/);
|
||||||
assert.match(html, /Hello/);
|
assert.match(html, /Hello/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,5 +52,6 @@ test("audio page shows unlock action when payment is required", () => {
|
|||||||
userId: "u2",
|
userId: "u2",
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.match(html, /Pay 3 credits and unlock forever/);
|
assert.match(html, /3 credits/);
|
||||||
|
assert.match(html, /Pay & Listen/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user